home *** CD-ROM | disk | FTP | other *** search
/ PC Online 1999 November / PCONLINE_11_99.ISO / filesbbs / OS2 / MMSRC029.ZIP / mmail-0.29 / mmail / qwk.cc < prev    next >
Encoding:
C/C++ Source or Header  |  1999-08-12  |  18.5 KB  |  905 lines

  1. /*
  2.  * MultiMail offline mail reader
  3.  * QWK
  4.  
  5.  Copyright (c) 1997 John Zero <john@graphisoft.hu>
  6.  Copyright (c) 1999 William McBrine <wmcbrine@clark.net>
  7.  
  8.  Distributed under the GNU General Public License.
  9.  For details, see the file COPYING in the parent directory. */
  10.  
  11. #include "qwk.h"
  12. #include "compress.h"
  13.  
  14. unsigned char *onecomp(unsigned char *p, char *dest, const char *comp)
  15. {
  16.     int len = strlen(comp);
  17.  
  18.     if (!strncasecmp((char *) p, comp, len)) {
  19.         p += len;
  20.         while (*p == ' ')
  21.             p++;
  22.         int x;
  23.         for (x = 0; *p && (*p != '\n') && (x < 71); x++)
  24.             dest[x] = *p++;
  25.         dest[x] = '\0';
  26.  
  27.         while (*p == '\n')
  28.             p++;
  29.         return p;
  30.     }
  31.     return 0;
  32. }
  33.  
  34. // ---------------------------------------------------------------------------
  35. // The qheader methods
  36. // ---------------------------------------------------------------------------
  37.  
  38. bool qheader::init(FILE *datFile)
  39. {
  40.     qwkmsg_header qh;
  41.     char buf[9];
  42.  
  43.     if (!fread(&qh, 1, sizeof qh, datFile))
  44.         return false;
  45.  
  46.     getQfield(from, qh.from, 25);
  47.     getQfield(to, qh.to, 25);
  48.     getQfield(subject, qh.subject, 25);
  49.  
  50.     cropesp(from);
  51.     cropesp(to);
  52.     cropesp(subject);
  53.  
  54.     getQfield(date, qh.date, 8);
  55.     date[2] = '-';
  56.     date[5] = '-';        // To deal with some broken messages
  57.     strcat(date, " ");
  58.  
  59.     getQfield(buf, qh.time, 5);
  60.     strcat(date, buf);
  61.  
  62.     getQfield(buf, qh.refnum, 8);
  63.     refnum = atoi(buf);
  64.  
  65.     getQfield(buf, qh.msgnum, 7);
  66.     msgnum = atoi(buf);
  67.  
  68.     getQfield(buf, qh.chunks, 6);
  69.     msglen = (atoi(buf) - 1) << 7;
  70.  
  71.     privat = (qh.status == '*') || (qh.status == '+');
  72.  
  73.     origArea = getshort(&qh.confLSB);
  74.  
  75.     return true;
  76. }
  77.  
  78. void qheader::output(FILE *repFile)
  79. {
  80.     qwkmsg_header qh;
  81.     char buf[10];
  82.     int chunks, length, sublen;
  83.  
  84.     length = msglen;
  85.     sublen = strlen(subject);
  86.     if (sublen > 25) {
  87.         length += sublen + 11;
  88.         sublen = 25;
  89.     }
  90.  
  91.     memset(&qh, ' ', sizeof qh);
  92.  
  93.     chunks = (length + 127) / 128;
  94.     if (!chunks)
  95.         chunks = 1;
  96.  
  97.     sprintf(buf, " %-6d", msgnum);
  98.     strncpy(qh.msgnum, buf, 7);
  99.  
  100.     putshort(&qh.confLSB, msgnum);
  101.  
  102.     if (refnum) {
  103.         sprintf(buf, " %-7d", refnum);
  104.         strncpy(qh.refnum, buf, 8);
  105.     }
  106.     strncpy(qh.to, to, strlen(to));
  107.     strncpy(qh.from, from, strlen(from));
  108.     strncpy(qh.subject, subject, sublen);
  109.  
  110.     qh.alive = (char) 0xE1;
  111.  
  112.     strncpy(qh.date, date, 8);
  113.     strncpy(qh.time, &date[9], 5);
  114.  
  115.     sprintf(buf, "%-6d", chunks + 1);
  116.     strncpy(qh.chunks, buf, 6);
  117.     if (privat)
  118.         qh.status = '*';
  119.  
  120.     fwrite(&qh, 1, sizeof qh, repFile);
  121. }
  122.  
  123. // ---------------------------------------------------------------------------
  124. // The QWK methods
  125. // ---------------------------------------------------------------------------
  126.  
  127. qwkpack::qwkpack(mmail *mmA)
  128. {
  129.     mm = mmA;
  130.     ID = 0;
  131.     bodyString = 0;
  132.  
  133.     qwke = !(!mm->workList->exists("toreader.ext"));
  134.  
  135.     readControlDat();
  136.     if (qwke)
  137.         readToReader();
  138.     else
  139.         readDoorId();
  140.     initMessagesDat();
  141.     readIndices();
  142.  
  143.     listBulletins(textfiles, 3);
  144. }
  145.  
  146. qwkpack::~qwkpack()
  147. {
  148.     delete[] bulletins;
  149.  
  150.     while (maxConf--) {
  151.         delete[] body[maxConf];
  152.         delete[] areas[maxConf].name;
  153.     }
  154.     delete[] body;
  155.     delete[] areas;
  156.     delete[] bodyString;
  157.  
  158.     fclose(msgdatFile);
  159. }
  160.  
  161. unsigned long qwkpack::MSBINtolong(unsigned const char *ms)
  162. {
  163.     return ((((unsigned long) ms[0] + ((unsigned long) ms[1] << 8) +
  164.           ((unsigned long) ms[2] << 16)) | 0x800000L) >>
  165.             (24 + 0x80 - ms[3]));
  166. }
  167.  
  168. area_header *qwkpack::getNextArea()
  169. {
  170.     int cMsgNum = areas[ID].nummsgs;
  171.     bool x = (areas[ID].num == -1);
  172.  
  173.     area_header *tmp = new area_header(mm,
  174.         ID + mm->driverList->getOffset(this),
  175.         areas[ID].numA, areas[ID].name,
  176.         (x ? "Letters addressed to you" : areas[ID].name),
  177.         (qwke ? (x ? "QWKE personal" : "QWKE") :
  178.         (x ? "QWK personal" : "QWK")),
  179.         areas[ID].attr | hasOffConfig | (x ? COLLECTION : 0) |
  180.         (cMsgNum ? ACTIVE : 0), cMsgNum, 0, 25);
  181.     ID++;
  182.     return tmp;
  183. }
  184.  
  185. int qwkpack::getNoOfLetters()
  186. {
  187.     return areas[currentArea].nummsgs;
  188. }
  189.  
  190. bool qwkpack::isQWKE()
  191. {
  192.     return qwke;
  193. }
  194.  
  195. void qwkpack::readIndices()
  196. {
  197.     const char *p;
  198.     int x, cMsgNum;
  199.  
  200.     body = new struct bodytype *[maxConf];
  201.  
  202.     for (x = 0; x < maxConf; x++) {
  203.         body[x] = 0;
  204.         areas[x].nummsgs = 0;
  205.     }
  206.  
  207.     if ((p = mm->workList->exists(".ndx"))) {
  208.         struct {
  209.                 unsigned char MSB[4];
  210.                 unsigned char confnum;
  211.         } ndx_rec;
  212.  
  213.         FILE *idxFile;
  214.         char fname[13];
  215.  
  216.         while (p) {
  217.             cMsgNum = 0;
  218.  
  219.             strncpy(fname, p, strlen(p) - 4);
  220.             fname[strlen(p) - 4] = '\0';
  221.             x = atoi(fname);
  222.             if (!x)
  223.                 if (!strcasecmp(fname, "personal"))
  224.                     x = -1;
  225.             x = getXNum(x);
  226.  
  227.             if ((idxFile = mm->workList->ftryopen(p, "rb"))) {
  228.                 cMsgNum = mm->workList->getSize() / ndxRecLen;
  229.                 body[x] = new struct bodytype[cMsgNum];
  230.                 for (int y = 0; y < cMsgNum; y++) {
  231.                     fread(&ndx_rec, ndxRecLen, 1, idxFile);
  232.                     body[x][y].pointer =
  233.                         MSBINtolong(ndx_rec.MSB);
  234.                 }
  235.                 fclose(idxFile);
  236.             }
  237.  
  238.             areas[x].nummsgs = cMsgNum;
  239.             p = mm->workList->getNext(".ndx");
  240.         }
  241.     } else {    // if no .ndx files exist
  242.         struct ndx_fake {
  243.             int confnum, pointer;
  244.         };
  245.  
  246.         qheader qHead;
  247.         struct ndx_fake *tmpndx = new struct ndx_fake[numMsgs];
  248.         int counter, current = 0;
  249.  
  250.         fseek(msgdatFile, 128, SEEK_SET);
  251.         while ((current < numMsgs) && qHead.init(msgdatFile)) {
  252.             counter = ftell(msgdatFile) >> 7;
  253.             x = getXNum(qHead.origArea);
  254.             tmpndx[current].confnum = x;
  255.             tmpndx[current++].pointer = counter;
  256.             areas[x].nummsgs++;
  257.             fseek(msgdatFile, qHead.msglen, SEEK_CUR);
  258.         }
  259.         numMsgs = current;
  260.  
  261.         for (x = 0; x < maxConf; x++) {
  262.             cMsgNum = areas[x].nummsgs;
  263.             if (cMsgNum) {
  264.                 body[x] = new struct bodytype[cMsgNum];
  265.                 areas[x].nummsgs = 0;
  266.             }
  267.         }
  268.  
  269.         for (x = 0; x < numMsgs; x++) {
  270.             current = tmpndx[x].confnum;
  271.             body[current][areas[current].nummsgs++].pointer =
  272.                 tmpndx[x].pointer;
  273.         }
  274.  
  275.         delete[] tmpndx;
  276.     }
  277. }
  278.  
  279. int qwkpack::getXNum(int area)
  280. {
  281.     int c;
  282.  
  283.     for (c = 0; c < maxConf; c++)
  284.         if (areas[c].num == area)
  285.             break;
  286.     return c;
  287. }
  288.  
  289. int qwkpack::getYNum(int area, unsigned long rawpos)
  290. {
  291.     int c;
  292.  
  293.     for (c = 0; c < areas[area].nummsgs; c++)
  294.         if ((unsigned) body[area][c].pointer == rawpos)
  295.             break;
  296.     return c;
  297. }
  298.  
  299. letter_header *qwkpack::getNextLetter()
  300. {
  301.     qheader q;
  302.     unsigned long pos, rawpos;
  303.     int areaID, letterID;
  304.  
  305.     rawpos = body[currentArea][currentLetter].pointer;
  306.     pos = (rawpos - 1) << 7;
  307.  
  308.     fseek(msgdatFile, pos, SEEK_SET);
  309.     if (!q.init(msgdatFile))
  310.         fatalError("Error reading MESSAGES.DAT");
  311.  
  312.     if (areas[currentArea].num == -1) {
  313.         areaID = getXNum(q.origArea);
  314.         letterID = getYNum(areaID, rawpos);
  315.     } else {
  316.         areaID = currentArea;
  317.         letterID = currentLetter;
  318.     }
  319.  
  320.     body[areaID][letterID].msgLength = q.msglen;
  321.  
  322.     currentLetter++;
  323.  
  324.     static net_address nullNet;
  325.  
  326.     return new letter_header(mm, q.subject, q.to, q.from,
  327.         q.date, 0, q.refnum, letterID, q.msgnum, areaID,
  328.         q.privat, q.msglen, this, nullNet);
  329. }
  330.  
  331. // returns the body of the requested letter
  332. const char *qwkpack::getBody(letter_header &mhead)
  333. {
  334.     unsigned char *p;
  335.     int c, kar, AreaID, LetterID;
  336.  
  337.     AreaID = mhead.getAreaID() - mm->driverList->getOffset(this);
  338.     LetterID = mhead.getLetterID();
  339.  
  340.     delete[] bodyString;
  341.     bodyString = new char[body[AreaID][LetterID].msgLength + 1];
  342.     fseek(msgdatFile, body[AreaID][LetterID].pointer << 7, SEEK_SET);
  343.  
  344.     for (c = 0, p = (unsigned char *) bodyString;
  345.          c < body[AreaID][LetterID].msgLength; c++) {
  346.         kar = fgetc(msgdatFile);
  347.  
  348.         if (!kar)
  349.             kar = ' ';
  350.  
  351.         *p++ = (kar == 227) ? '\n' : kar;    
  352.     }
  353.     do
  354.         p--;
  355.     while ((*p == ' ') || (*p == '\n'));    // Strip blank lines
  356.     p[1] = '\0';
  357.  
  358.     // Get extended (QWKE-type) info, if available:
  359.  
  360.     char extsubj[72];    // extended subject or other line
  361.     bool anyfound;
  362.     unsigned char *q;
  363.     p = (unsigned char *) bodyString;
  364.  
  365.     do {
  366.         anyfound = false;
  367.  
  368.         q = onecomp(p, extsubj, "subject:");
  369.  
  370.         if (!q) {
  371.             q = onecomp(p + 1, extsubj, "@subject:");
  372.             if (q) {
  373.                 extsubj[strlen(extsubj) - 1] = '\0';
  374.                 cropesp(extsubj);
  375.             }
  376.         }
  377.  
  378.         // For WWIV QWK door:
  379.         if (!q)
  380.             q = onecomp(p, extsubj, "title:");
  381.  
  382.         if (q) {
  383.             p = q;
  384.             mhead.changeSubject(extsubj);
  385.             anyfound = true;
  386.         }
  387.  
  388.         q = onecomp(p, extsubj, "to:");
  389.         if (q) {
  390.             p = q;
  391.             mhead.changeTo(extsubj);
  392.             anyfound = true;
  393.         }
  394.  
  395.         q = onecomp(p, extsubj, "from:");
  396.         if (q) {
  397.             p = q;
  398.             mhead.changeFrom(extsubj);
  399.             anyfound = true;
  400.         }
  401.  
  402.     } while (anyfound);
  403.  
  404.     // Change to Latin character set, if necessary:
  405.     checkLatin(mhead);
  406.  
  407.     return (char *) p;
  408. }
  409.  
  410. char *qwkpack::nextLine()
  411. {
  412.     static char line[128];
  413.  
  414.     fgets(line, sizeof line, ctrdatFile);
  415.     strtok(line, "\r\n");
  416.     return line;
  417. }
  418.  
  419. void qwkpack::readControlDat()
  420. {
  421.     char *p, *q;
  422.     int pers;
  423.  
  424.     if (!(ctrdatFile = mm->workList->ftryopen("control.dat", "rb")))
  425.         fatalError("Could not open CONTROL.DAT");
  426.  
  427.     mm->resourceObject->set(BBSName, nextLine());    // 1: BBS name
  428.     nextLine();                    // 2: city/state
  429.     nextLine();                    // 3: phone#
  430.  
  431.     q = nextLine();                    // 4: sysop's name
  432.     int slen = strlen(q);
  433.     if (slen > 6) {
  434.         p = q + slen - 5;
  435.         if (!strcasecmp(p, "Sysop")) {
  436.             if (*--p == ' ')
  437.                 p--;
  438.             if (*p == ',')
  439.                 *p = '\0';
  440.         }
  441.     }
  442.     mm->resourceObject->set(SysOpName, q);
  443.  
  444.     q = nextLine();                    // 5: doorserno,BBSid
  445.     p = strtok(q, ",");
  446.     p = strtok(0, " ");
  447.     strncpy(packetBaseName, p, 8);
  448.     packetBaseName[8] = '\0';
  449.  
  450.     nextLine();                    // 6: time&date
  451.  
  452.     p = nextLine();                    // 7: user's name
  453.     cropesp(p);
  454.     mm->resourceObject->set(LoginName, p);
  455.     mm->resourceObject->set(AliasName, p);
  456.  
  457.     nextLine();                    // 8: blank/any
  458.     nextLine();                    // 9: anyth.
  459.  
  460.     numMsgs = atoi(nextLine());            // 10: # messages
  461.                             // (not reliable)
  462.     maxConf = atoi(nextLine()) + 1;            // 11: Max conf #
  463.  
  464.     pers = !(!mm->workList->exists("personal.ndx"));
  465.     if (pers)
  466.         maxConf++;
  467.  
  468.     areas = new AREAs[maxConf];
  469.     if (pers) {
  470.         areas[0].num = -1;
  471.         strcpy(areas[0].numA, "PERS");
  472.         areas[0].name = strdupplus("PERSONAL");
  473.         areas[0].attr = PUBLIC | PRIVATE;
  474.     }
  475.     for (int c = pers; c < maxConf; c++) {
  476.         areas[c].num = atoi(nextLine());        // conf #
  477.         sprintf(areas[c].numA, "%d", areas[c].num);
  478.         areas[c].name = strdupplus(nextLine());        // conf name
  479.         areas[c].attr = PUBLIC | PRIVATE;
  480.     }
  481.  
  482.     for (int c = 0; c < 3; c++)
  483.         strncpy(textfiles[c], nextLine(), 12);
  484.  
  485.     fclose(ctrdatFile);
  486. }
  487.  
  488. void qwkpack::readDoorId()
  489. {
  490.     const char *s;
  491.     bool hasAdd = false, hasDrop = false;
  492.  
  493.     strcpy(controlname, "QMAIL");
  494.  
  495.     if ((ctrdatFile = mm->workList->ftryopen("door.id", "rb"))) {
  496.         while (!feof(ctrdatFile)) {
  497.             s = nextLine();
  498.             if (!strcasecmp(s, "CONTROLTYPE = ADD"))
  499.                 hasAdd = true;
  500.             else
  501.                 if (!strcasecmp(s, "CONTROLTYPE = DROP"))
  502.                 hasDrop = true;
  503.                 else
  504.                 if (!strncasecmp(s, "CONTROLNAME", 11))
  505.                     sprintf(controlname, "%.25s", s + 14);
  506.         }
  507.         fclose(ctrdatFile);
  508.     }
  509.     hasOffConfig = (hasAdd && hasDrop) ? OFFCONFIG : 0;
  510. }
  511.  
  512. // Read the QWKE file TOREADER.EXT
  513. void qwkpack::readToReader()
  514. {
  515.     char *s;
  516.     int cnum, attr;
  517.  
  518.     hasOffConfig = OFFCONFIG;
  519.  
  520.     if ((ctrdatFile = mm->workList->ftryopen("toreader.ext", "rb"))) {
  521.         while (!feof(ctrdatFile)) {
  522.             s = nextLine();
  523.  
  524.             if (!strncasecmp(s, "area ", 5)) {
  525.                 if (sscanf(s + 5, "%d %s", &cnum, s) == 2) {
  526.  
  527.                 attr = SUBKNOWN;
  528.  
  529.                 // If a group is marked subscribed:
  530.                 if (strchr(s, 'a'))
  531.                     attr |= ACTIVE;
  532.                 else
  533.                     if (strchr(s, 'p'))
  534.                     attr |= (ACTIVE | PERSONLY);
  535.                     else
  536.                     if (strchr(s, 'g'))
  537.                         attr |= (ACTIVE | PERSALL);
  538.  
  539.                 // "Handles" or "Anonymous":
  540.                 if (strchr(s, 'H') || strchr(s, 'A'))
  541.                     attr |= ALIAS;
  542.  
  543.                 // Public-only/Private-only:
  544.                 if (strchr(s, 'P'))
  545.                     attr |= PRIVATE;
  546.                 else
  547.                     if (strchr(s, 'O'))
  548.                         attr |= PUBLIC;
  549.                     else
  550.                         attr |= (PUBLIC | PRIVATE);
  551.  
  552.                 /* Set character set to Latin-1 for
  553.                    Internet or Usenet areas -- but is this
  554.                    the right thing here?
  555.                 */
  556.                 if (strchr(s, 'U') || strchr(s, 'I'))
  557.                     attr |= LATINCHAR;
  558.  
  559.                 if (strchr(s, 'E'))
  560.                     attr |= ECHO;
  561.  
  562.                 areas[getXNum(cnum)].attr = attr;
  563.                 }
  564.             } else
  565.                 if (!strncasecmp(s, "alias ", 6)) {
  566.                     cropesp(s);
  567.                     mm->resourceObject->set(AliasName,
  568.                         s + 6);
  569.                 }
  570.         }
  571.         fclose(ctrdatFile);
  572.     }
  573. }
  574.  
  575. void qwkpack::initMessagesDat()
  576. {
  577.     if (!(msgdatFile = mm->workList->ftryopen("messages.dat", "rb")))
  578.         fatalError("Could not open MESSAGES.DAT");
  579.     if (!numMsgs)
  580.         numMsgs = mm->workList->getSize() >> 7;
  581. }
  582.  
  583. const char *qwkpack::ctrlName()
  584. {
  585.     return controlname;
  586. }
  587.  
  588. // ---------------------------------------------------------------------------
  589. // The QWK reply methods
  590. // ---------------------------------------------------------------------------
  591.  
  592. qwkreply::qwkreply(mmail *mmA, specific_driver *baseClassA)
  593. {
  594.     mm = mmA;
  595.     baseClass = (pktbase *) baseClassA;
  596.     extent = ".rep";
  597.     extent2 = ".msg";
  598.  
  599.     replyText = 0;
  600.     qwke = ((qwkpack *) baseClass)->isQWKE();
  601.  
  602.     uplListHead = 0;
  603.  
  604.     replyExists = false;
  605. }
  606.  
  607. qwkreply::~qwkreply()
  608. {
  609.     cleanup();
  610. }
  611.  
  612. bool qwkreply::getRep1(FILE *rep, upl_qwk *l)
  613. {
  614.     FILE *replyFile;
  615.     char *p, *q, *replyText;
  616.  
  617.     if (!l->qHead.init(rep))
  618.         return false;
  619.  
  620.     mytmpnam(l->fname);
  621.  
  622.     if (!(replyFile = fopen(l->fname, "wt")))
  623.         return false;
  624.  
  625.     replyText = new char[l->qHead.msglen + 1];
  626.     fread(replyText, l->qHead.msglen, 1, rep);
  627.  
  628.     for (p = &replyText[l->qHead.msglen - 1]; ((*p == ' ') ||
  629.         (*p == (char) 227)) && (p > replyText); p--);
  630.     p[1] = '\0';
  631.  
  632.     for (p = replyText; *p; p++)
  633.         if (*p == (char) 227)
  634.             *p = '\n';    // PI-softcr
  635.  
  636.     // Get extended (QWKE-type) subject line, if available:
  637.  
  638.     bool anyfound;
  639.     q = replyText;
  640.  
  641.     do {
  642.         char *q2;
  643.         anyfound = false;
  644.  
  645.         q2 = (char *) onecomp((unsigned char *) q,
  646.                 l->qHead.subject, "subject:");
  647.         if (q2) {
  648.             q = q2;
  649.             anyfound = true;
  650.         }
  651.  
  652.     } while (anyfound);
  653.  
  654.     l->qHead.msglen = l->msglen = p - q;    //strlen(replyText);
  655.  
  656.     fwrite(q, 1, l->msglen, replyFile);
  657.     fclose(replyFile);
  658.     delete[] replyText;
  659.  
  660.     return true;
  661. }
  662.  
  663. void qwkreply::getReplies(FILE *repFile)
  664. {
  665.     fseek(repFile, 128, SEEK_SET);
  666.  
  667.     noOfLetters = 0;
  668.  
  669.     upl_qwk baseUplList, *currUplList = &baseUplList;
  670.  
  671.     while (!feof(repFile)) {
  672.         currUplList->nextRecord = new upl_qwk;
  673.         currUplList = (upl_qwk *) currUplList->nextRecord;
  674.         if (!getRep1(repFile, currUplList)) {
  675.             delete currUplList;
  676.             break;
  677.         }
  678.         noOfLetters++;
  679.     }
  680.     uplListHead = baseUplList.nextRecord;
  681. }
  682.  
  683. area_header *qwkreply::getNextArea()
  684. {
  685.     return new area_header(mm, 0, "REPLY", "REPLIES",
  686.         "Letters written by you", (qwke ? "QWKE replies" :
  687.         "QWK replies"), (COLLECTION | REPLYAREA | ACTIVE |
  688.         PUBLIC | PRIVATE), noOfLetters, 0, 25);
  689. }
  690.  
  691. letter_header *qwkreply::getNextLetter()
  692. {
  693.     static net_address nullNet;
  694.     upl_qwk *current = (upl_qwk *) uplListCurrent;
  695.  
  696.     letter_header *newLetter = new letter_header(mm,
  697.         current->qHead.subject, current->qHead.to,
  698.         current->qHead.from, current->qHead.date, 0,
  699.         current->qHead.refnum, currentLetter, currentLetter,
  700.         ((qwkpack *) baseClass)->getXNum(current->qHead.msgnum) + 1,
  701.         current->qHead.privat, current->qHead.msglen, this, nullNet);
  702.  
  703.     currentLetter++;
  704.     uplListCurrent = uplListCurrent->nextRecord;
  705.     return newLetter;
  706. }
  707.  
  708. int qwkreply::monthval(const char *abbr)
  709. {
  710.     static const char *month_abbr[] =
  711.         {"Jan", "Feb", "Mar", "Apr", "Mar", "Jun",
  712.          "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  713.     int c;
  714.  
  715.     for (c = 0; c < 12; c++)
  716.         if (!strcmp(month_abbr[c], abbr))
  717.             break;
  718.     return c + 1;
  719. }
  720.  
  721. void qwkreply::enterLetter(letter_header &newLetter,
  722.                 const char *newLetterFileName, int length)
  723. {
  724.     upl_qwk *newList = new upl_qwk;
  725.     memset(newList, 0, sizeof(upl_qwk));
  726.  
  727.     strncpy(newList->qHead.subject, newLetter.getSubject(), 71);
  728.     strncpy(newList->qHead.from, newLetter.getFrom(), 25);
  729.     strncpy(newList->qHead.to, newLetter.getTo(), 25);
  730.  
  731.     newList->qHead.msgnum = atoi(mm->areaList->getShortName());
  732.     newList->qHead.privat = newLetter.getPrivate();
  733.     newList->qHead.refnum = newLetter.getReplyTo();
  734.     strcpy(newList->fname, newLetterFileName);
  735.  
  736.     time_t tt = time(0);
  737.     strftime(newList->qHead.date, 15, "%m-%d-%y %H:%M",
  738.         localtime(&tt));
  739.  
  740.     newList->qHead.msglen = newList->msglen = length;
  741.  
  742.     addUpl(newList);
  743. }
  744.  
  745. void qwkreply::addRep1(FILE *rep, upl_base *node)
  746. {
  747.     FILE *replyFile;
  748.     upl_qwk *l = (upl_qwk *) node;
  749.     char *replyText, *p, *lastsp = 0;
  750.     int chunks, length, sublen, count = 0;
  751.     bool longsub;
  752.  
  753.     l->qHead.output(rep);
  754.  
  755.     length = l->qHead.msglen;
  756.     sublen = strlen(l->qHead.subject);
  757.     longsub = (sublen > 25);
  758.  
  759.     if (longsub)
  760.         length += sublen + 11;
  761.  
  762.     chunks = (length + 127) / 128;
  763.     if (!chunks)
  764.         chunks = 1;
  765.  
  766.     replyText = new char[chunks * 128 + 1];
  767.     memset(&replyText[(chunks - 1) * 128], ' ', 128);
  768.  
  769.     p = replyText;
  770.  
  771.     if (longsub)
  772.         p += sprintf(p, "Subject: %s\n\n", l->qHead.subject);
  773.  
  774.     if ((replyFile = fopen(l->fname, "rt"))) {
  775.         fread(p, l->qHead.msglen, 1, replyFile);
  776.         fclose(replyFile);
  777.  
  778.         replyText[length] = 0;
  779.         for (p = replyText; *p; p++) {
  780.             if (*p == '\n') {
  781.                 *p = (char) 227;
  782.                 count = 0;
  783.                 lastsp = 0;
  784.             } else
  785.                 count++;
  786.             if (*p == ' ')
  787.                 lastsp = p;
  788.             if ((count >= 80) && lastsp) {
  789.                 *lastsp = (char) 227;
  790.                 count = p - lastsp;
  791.                 lastsp = 0;
  792.             }
  793.         }
  794.         replyText[length] = (char) 227;
  795.     }
  796.     fwrite(replyText, 1, chunks * 128, rep);
  797.     delete[] replyText;
  798. }
  799.  
  800. void qwkreply::addHeader(FILE *repFile)
  801. {
  802.     char tmp[256];
  803.     sprintf(tmp, "%-128s", baseClass->getBaseName());
  804.     fwrite(tmp, 128, 1, repFile);
  805. }
  806.  
  807. const char *qwkreply::repTemplate(const char *basename, bool offres)
  808. {
  809.     static char buff[30];
  810.  
  811.     sprintf(buff, (offres && qwke) ? "%s.msg todoor.ext" : "%s.msg",
  812.         basename);
  813.  
  814.     return buff;
  815. }
  816.  
  817. bool qwkreply::getOffConfig()
  818. {
  819.     bool status = false;
  820.  
  821.     if (qwke) {
  822.         FILE *olc;
  823.  
  824.         upWorkList = new file_list(mm->resourceObject->get(UpWorkDir));
  825.  
  826.         if ((olc = upWorkList->ftryopen("todoor.ext", "rb"))) {
  827.             char line[128], mode;
  828.             int areaQWK, areaNo;
  829.  
  830.             while (!feof(olc)) {
  831.                 fgets(line, sizeof line, olc);
  832.                 if (sscanf(line, "AREA %d %c", &areaQWK,
  833.                     &mode) == 2) {
  834.                     areaNo = ((qwkpack *) baseClass)->
  835.                         getXNum(areaQWK) + 1;
  836.                     mm->areaList->gotoArea(areaNo);
  837.                     if (mode == 'D')
  838.                         mm->areaList->Drop();
  839.                     else
  840.                         mm->areaList->Add();
  841.                 }
  842.             }
  843.             fclose(olc);
  844.             upWorkList->kill();
  845.  
  846.             status = true;
  847.         }
  848.         delete upWorkList;
  849.     }
  850.     return status;
  851. }
  852.  
  853. bool qwkreply::makeOffConfig()
  854. {
  855.     FILE *todoor = 0;    // warning suppression
  856.     net_address bogus;
  857.     letter_header *ctrlMsg;
  858.     const char *myname = 0, *ctrlName = 0;
  859.     int maxareas = mm->areaList->noOfAreas();
  860.  
  861.     if (qwke) {
  862.         todoor = fopen("todoor.ext", "wb");
  863.         if (!todoor)
  864.             return false;
  865.     } else {
  866.         myname = mm->resourceObject->get(LoginName);
  867.         ctrlName = ((qwkpack *) baseClass)->ctrlName();
  868.     }
  869.  
  870.     for (int areaNo = 0; areaNo < maxareas; areaNo++) {
  871.         mm->areaList->gotoArea(areaNo);
  872.         int attrib = mm->areaList->getType();
  873.  
  874.         if (attrib & (ADDED | DROPPED)) {
  875.             if (qwke)
  876.                 fprintf(todoor, "AREA %s %c\r\n",
  877.                     mm->areaList->getShortName(),
  878.                     (attrib & ADDED) ?
  879.                     ((attrib & PERSONLY) ? 'p' :
  880.                     ((attrib & PERSALL) ? 'g' : 'a'))
  881.                     : 'D');
  882.             else {
  883.                 ctrlMsg = new letter_header(mm, (attrib &
  884.                 ADDED) ? "ADD" : "DROP", ctrlName, myname,
  885.                 "", 0, 0, 0, 0, areaNo, false, 0, this,
  886.                 bogus, false);
  887.  
  888.                 enterLetter(*ctrlMsg, "", 0);
  889.  
  890.                 delete ctrlMsg;
  891.  
  892.                 if (attrib & ADDED)
  893.                     mm->areaList->Drop();
  894.                 else
  895.                     mm->areaList->Add();
  896.             }
  897.         }
  898.     }
  899.     if (qwke)
  900.         fclose(todoor);
  901.     else
  902.         mm->areaList->refreshArea();
  903.     return true;
  904. }
  905.