1 /*****************************************************************************
2 * Link for HPT (FTN NetMail/EchoMail Tosser)
3 *****************************************************************************
4 * Copyright (C) 1998
5 *
6 * Kolya Nesterov
7 *
8 * Fido:     2:463/7208.53
9 * Kiev, Ukraine
10 *
11 * This file is part of HPT.
12 *
13 * HPT is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU General Public License as published by the
15 * Free Software Foundation; either version 2, or (at your option) any
16 * later version.
17 *
18 * HPT is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 * General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with HPT; see the file COPYING.  If not, write to the Free
25 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
26 *****************************************************************************
27 * $Id$
28 */
29 /* Revision log:
30 19.12.98 - first version
31 28.11.99 - write modified chains only (by L. Lisowsky 2:5020/454.2)
32 */
33 
34 /*
35 For now reply linking is performed using the msgid/reply kludges
36 TODO:
37 subject linking
38 FIXME:	do better when finding if msg links need to be updated
39 The problem with original patch by Leonid was that if msg had
40 some reply links written in replies or replyto fields but
41 no replies were found during linkage reply&replyto fields won't
42 be cleared.
43 */
44 
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <ctype.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 
52 #include <huskylib/compiler.h>
53 
54 #ifdef __OS2__
55 #define INCL_DOSFILEMSG /* for DosSetMaxFH() */
56 #include <os2.h>
57 #endif
58 
59 #include <smapi/msgapi.h>
60 #include <smapi/api_jam.h>
61 
62 #include <fidoconf/fidoconf.h>
63 #include <fidoconf/common.h>
64 #include <huskylib/typesize.h>
65 #include <huskylib/crc.h>
66 #include <huskylib/log.h>
67 
68 #include <areafix/areafix.h>
69 #include "global.h"
70 #include "fcommon.h"
71 #include "hptafix.h"
72 
73 #define MAX_INCORE	10000UL	/*  if more messages, do not link incore */
74 
75 dword Jam_Crc32(unsigned char* buff, dword len);
76 char *Jam_GetKludge(HAREA jm, dword msgnum, word what);
77 JAMHDR *Jam_GetHdr(HAREA jm, dword msgnum);
78 void Jam_WriteHdr(HAREA jm, JAMHDR *jamhdr, dword msgnum);
79 
80 /* internal structure holding msg's related to link information */
81 /* used by linkArea */
82 struct msginfo {
83     char *msgId;
84     char *replyId;
85     HMSG msgh;
86     union {
87         XMSG *xmsg;
88         JAMHDR *jamhdr;
89     } hdr;
90     UMSGID msgPos;
91 
92     short freeReply;
93     char relinked;
94     UMSGID replyto, replynext;
95     UMSGID replies[MAX_REPLY];
96 };
97 
98 struct hashinfo {
99     dword crc;
100     int idx;
101 };
102 
103 typedef struct msginfo s_msginfo;
104 
105 int linkJamByCRC = 0;
106 
findMsgId(s_msginfo * entries,struct hashinfo * hash,dword hashSize,char * msgId,int add,dword crc32)107 static s_msginfo *findMsgId(s_msginfo *entries, struct hashinfo *hash, dword hashSize, char *msgId, int add, dword crc32)
108 {
109     unsigned long h, d = 1;
110     dword crc;
111     if (crc32 == 0)
112         h = crc = strcrc32(strUpper(msgId), 0xFFFFFFFFL);
113     else if (crc32 == 0xFFFFFFFFL)
114         h = crc = Jam_Crc32((UCHAR*)strUpper(msgId), strlen(msgId));
115     else
116         h = crc = crc32;
117     while (d < hashSize) {
118         h %= hashSize;
119         if (hash[h].idx == 0) {
120             /* Found free entry */
121             if (!add) return NULL;
122             hash[h].idx = add;
123             hash[h].crc = crc;
124             return &(entries[add-1]);
125         }
126         if (hash[h].crc == crc && !stricmp(entries[hash[h].idx-1].msgId, msgId)) {
127             /* Found it ! */
128             return &(entries[hash[h].idx-1]);
129         } else {
130             /* Collision, resolve it */
131             h++;
132             d++;
133         };
134     };
135     return NULL;
136 }
137 
stripDomain(char * msgid)138 static char *stripDomain(char *msgid)
139 {
140     char *p, *p1;
141     hs_addr addr = {0};
142 
143     if (msgid == NULL) return msgid;
144     for (p=msgid; *p && (isdigit(*p) || *p==':' || *p=='/' || *p=='.'); p++);
145     if (*p != '@') return msgid;
146     if (parseFtnAddrZ(msgid, &addr, FTNADDR_GOOD, (const char**)(&p1)) & FTNADDR_ERROR) return msgid;
147      /*  '(const char**)(&p1)' is needs for prevent warning "passing arg 4 of `parseFtnAddrZ' from incompatible pointer type" */
148 
149     for (; *p1 && !isspace(*p1); p1++);
150     strcpy(p, p1);
151     return msgid;
152 }
153 
GetKludgeText(byte * ctl,char * kludge)154 static char *GetKludgeText(byte *ctl, char *kludge)
155 {
156     char *pKludge, *pToken;
157 
158     pToken = (char *) GetCtrlToken(ctl, (byte *)kludge);
159     if (pToken) {
160         pKludge = safe_strdup(pToken+1+strlen(kludge));
161         nfree(pToken);
162         return stripDomain(pKludge);
163     } else
164         return NULL;
165 }
166 
crc2str(dword crc)167 static char *crc2str(dword crc)
168 {
169    char *ptr, *str;
170 
171    if (crc==0 || crc==0xFFFFFFFFul)
172       return NULL;
173    ptr=str=safe_malloc(11);
174    /* backward figures order, it's not mistake */
175    while (crc) {
176       *ptr++=(char)('0'+crc%10);
177       crc/=10;
178    }
179    *ptr='\0';
180    return str;
181 }
182 
183 /* linking for msgid/reply */
linkArea(s_area * area,int netMail)184 int linkArea(s_area *area, int netMail)
185 {
186 
187     HAREA harea;
188     HMSG  hmsg = NULL;
189     XMSG  xmsg;
190     s_msginfo *msgs;
191     dword msgsNum, hashNums, i, ctlen, cctlen;
192     struct hashinfo *hash;
193     byte *ctl;
194     char *msgId;
195     int jam;
196 
197     s_msginfo *curr;
198 
199     if (area->msgbType == MSGTYPE_PASSTHROUGH) return 0;
200 
201     if (area->nolink) {
202         w_log(LL_LINKING, "%s has nolink option, ignoring", area->areaName);
203         return 0;
204     }
205 
206     harea = MsgOpenArea((UCHAR *) area->fileName, MSGAREA_NORMAL,
207         (word)(area->msgbType | (netMail ? 0 : MSGTYPE_ECHO)));
208     if (harea) {
209         w_log(LL_LINKING, "Linking area %s", area->areaName);
210         msgsNum = MsgGetNumMsg(harea);
211         if (msgsNum < 2) { /* Really nothing to link */
212             MsgCloseArea(harea);
213             return 0;
214         };
215 #ifdef __OS2__
216         if (area->msgbType & MSGTYPE_SDM)
217 #if defined(__WATCOMC__)
218             _grow_handles(msgsNum+20);
219 #else /* EMX */
220         DosSetMaxFH(msgsNum+20);
221 #endif
222 #endif
223 
224         hashNums = msgsNum + msgsNum / 10 + 10;
225         hash = safe_malloc(hashNums * sizeof(*hash));
226         memset(hash, '\0', hashNums * sizeof(*hash));
227         msgs = safe_malloc(msgsNum * sizeof(s_msginfo));
228         memset(msgs, '\0', msgsNum * sizeof(s_msginfo));
229         ctl = (byte *) safe_malloc(cctlen = 1); /* Some libs doesn't accept relloc(NULL, ..
230                                                 * So let it be initalized
231         */
232         jam = !!(area->msgbType & MSGTYPE_JAM);
233         /* Area linking is done in three passes */
234         /* Pass 1st : read all message information in memory */
235 
236         for (i = 1; i <= msgsNum; i++) {
237             if (jam) {
238                 if (linkJamByCRC)
239                    msgId = crc2str(Jam_GetHdr(harea, i)->MsgIdCRC);
240                 else {
241                    msgId = Jam_GetKludge(harea, i, JAMSFLD_MSGID);
242                    stripDomain(msgId);
243                 }
244             } else {
245                 hmsg  = MsgOpenMsg(harea, (word)((msgsNum >= MAX_INCORE) ? MOPEN_READ : (MOPEN_READ|MOPEN_WRITE)), i);
246                 if (hmsg == NULL) {
247                     continue;
248                 }
249                 ctlen = MsgGetCtrlLen(hmsg);
250                 if (ctlen == 0 ) {
251                     MsgCloseMsg(hmsg);
252                     w_log(LL_WARN, "msg %ld has no control information: thrown from reply chain", i);
253                     continue;
254                 }
255 
256                 if (ctlen > cctlen) {
257                     cctlen = ctlen;
258                     ctl   = (byte *) safe_realloc(ctl, ctlen + 1);
259                 }
260 
261                 MsgReadMsg(hmsg, &xmsg, 0, 0, NULL, ctlen, ctl);
262                 ctl[ctlen] = '\0';
263                 msgId   = GetKludgeText(ctl, "MSGID");
264             }
265             if (msgId == NULL) {
266                 w_log(LL_WARN, "msg %ld hasn't got any MSGID, replying is not possible", i);
267                 if (!jam)
268                     MsgCloseMsg(hmsg);
269                 continue;
270             }
271             curr = findMsgId(msgs, hash, hashNums, msgId, i,
272                 (jam && linkJamByCRC) ? Jam_GetHdr(harea, i)->MsgIdCRC : 0);
273             if (curr == NULL) {
274                 w_log(LL_ERR, "hash table overflow. Tell developers about it!");
275                 /*  try to free as much as possible */
276                 /*  FIXME : remove blocks themselves */
277                 nfree(msgId);
278                 if (!jam) {
279                     nfree(ctl);
280                     MsgCloseMsg(hmsg);
281                     MsgCloseArea(harea);
282                 }
283                 return 0;
284             };
285             if (curr -> msgId != NULL) {
286                 w_log(LL_DEBUGB, "msg %ld has dupes in msgbase: thrown out from reply chain", i);
287                 if (!jam) {
288                     MsgCloseMsg(hmsg);
289                     nfree(msgId);
290                 }
291                 continue;
292             }
293             curr -> msgId = msgId;
294             curr -> msgPos  = MsgMsgnToUid(harea, i);
295             curr -> freeReply = 0;
296             curr -> relinked = 0;
297             curr -> replyto = curr -> replynext = curr->replies[0] = 0;
298             curr -> msgh = NULL;
299             if (jam) {
300                 curr -> hdr.jamhdr = Jam_GetHdr(harea, i);
301                 if (linkJamByCRC)
302                    curr -> replyId = crc2str(curr->hdr.jamhdr->ReplyCRC);
303                 else {
304                    curr -> replyId = Jam_GetKludge(harea, i, JAMSFLD_REPLYID);
305                    stripDomain(curr -> replyId);
306                 }
307             } else {
308                 curr -> replyId = GetKludgeText(ctl, "REPLY");
309                 curr -> hdr.xmsg = memdup(&xmsg, sizeof(XMSG));
310                 if (msgsNum >= MAX_INCORE)
311                     MsgCloseMsg(hmsg), curr -> msgh = NULL;
312                 else
313                     curr -> msgh = hmsg;
314             }
315         }
316 
317         /* Pass 2nd : search for reply links and build relations */
318         for (i = 0; i < msgsNum; i++) {
319             if (msgs[i].msgId == NULL || msgs[i].replyId == NULL)
320                 continue;
321             curr = findMsgId(msgs, hash, hashNums, msgs[i].replyId, 0,
322                 (jam && linkJamByCRC) ? Jam_GetHdr(harea, i+1)->ReplyCRC : 0);
323             if (curr == NULL) continue;
324             if (curr -> msgId == NULL) continue;
325             if (curr -> freeReply >= MAX_REPLY-4 &&
326                 (area->msgbType & (MSGTYPE_JAM | MSGTYPE_SDM))) {
327                 curr -> freeReply--;
328                 curr -> replies[curr -> freeReply - 1] = curr -> replies[curr -> freeReply];
329             }
330             if (curr -> freeReply >= MAX_REPLY) {
331                 w_log(LL_WARN, "msg %ld: replies count for msg %ld exceeds %d, rest of the replies won't be linked", i+1, curr-msgs+1, MAX_REPLY);
332                 continue;
333             }
334             curr -> replies[curr -> freeReply] = i;
335             if (!jam) {
336                 if (curr->hdr.xmsg->replies[curr->freeReply]!=msgs[i].msgPos) {
337                     curr->hdr.xmsg->replies[curr->freeReply]=msgs[i].msgPos;
338                     if ((area->msgbType & MSGTYPE_SQUISH) ||
339                         curr->freeReply == 0)
340                         curr -> relinked = 1;
341                 }
342                 (curr -> freeReply)++;
343                 msgs[i].replyto = curr -> msgPos;
344                 if (msgs[i].hdr.xmsg -> replyto != curr -> msgPos) {
345                     msgs[i].hdr.xmsg -> replyto = curr -> msgPos;
346                     msgs[i].relinked = 1;
347                 }
348             } else {
349                 if (curr -> freeReply == 0) {
350                     if (curr->hdr.jamhdr->Reply1st != msgs[i].msgPos) {
351                         curr->hdr.jamhdr->Reply1st = msgs[i].msgPos;
352                         curr -> relinked = 1;
353                     }
354                 } else {
355                     int replyprev = curr -> replies[curr -> freeReply - 1];
356                     msgs[replyprev].replynext = msgs[i].msgPos;
357                     if (msgs[replyprev].hdr.jamhdr -> ReplyNext != msgs[i].msgPos) {
358                         msgs[replyprev].hdr.jamhdr -> ReplyNext = msgs[i].msgPos;
359                         msgs[replyprev].relinked = 1;
360                     }
361                 }
362                 (curr -> freeReply)++;
363                 msgs[i].replyto = curr -> msgPos;
364                 if (msgs[i].hdr.jamhdr -> ReplyTo != curr -> msgPos) {
365                     msgs[i].hdr.jamhdr -> ReplyTo = curr -> msgPos;
366                     msgs[i].relinked = 1;
367                 }
368             }
369         }
370 
371         /* Pass 3rd : write information back to msgbase */
372         for (i = 0; i < msgsNum; i++) {
373             int j;
374             if (msgs[i].msgId == NULL)
375                 continue;
376             if (jam) {
377                 if ((msgs[i].replyto   != msgs[i].hdr.jamhdr->ReplyTo) ||
378                     (msgs[i].replynext != msgs[i].hdr.jamhdr->ReplyNext) ||
379                     (msgs[i].hdr.jamhdr->Reply1st && !msgs[i].freeReply) ||
380                     msgs[i].relinked) {
381                     if (!msgs[i].freeReply) msgs[i].hdr.jamhdr->Reply1st = 0;
382                     msgs[i].hdr.jamhdr->ReplyTo   = msgs[i].replyto;
383                     msgs[i].hdr.jamhdr->ReplyNext = msgs[i].replynext;
384                     Jam_WriteHdr(harea, msgs[i].hdr.jamhdr, i+1);
385                 }
386                 nfree(msgs[i].msgId);
387                 nfree(msgs[i].replyId);
388                 continue;
389             }
390             for (j=0; j<MAX_REPLY && msgs[i].hdr.xmsg->replies[j]; j++);
391             if (msgs[i].relinked != 0 ||
392                 msgs[i].replyto != msgs[i].hdr.xmsg->replyto ||
393                 ((area->msgbType & MSGTYPE_SQUISH) && msgs[i].freeReply != j) ||
394                 (msgs[i].freeReply == 0 && j)) {
395                 if (msgs[i].freeReply<MAX_REPLY)
396                     msgs[i].hdr.xmsg->replies[msgs[i].freeReply] = 0;
397                 msgs[i].hdr.xmsg->replyto = msgs[i].replyto;
398                 if (msgs[i].msgh == NULL)
399                     msgs[i].msgh = MsgOpenMsg(harea, MOPEN_READ|MOPEN_WRITE, i+1);
400                 if (msgs[i].msgh)
401                     if (0!=MsgWriteMsg(msgs[i].msgh, 0, msgs[i].hdr.xmsg, NULL, 0, 0, 0, NULL))
402                         w_log(LL_ERR, "Could not update msg in area %s! Check the wholeness of messagebase, please.", area->areaName);
403             }
404             if (msgs[i].msgh)
405                 MsgCloseMsg(msgs[i].msgh);
406 
407             /* free this node */
408             nfree(msgs[i].msgId);
409             nfree(msgs[i].replyId);
410             nfree(msgs[i].hdr.xmsg);
411         }
412         /* close everything, free all allocated memory */
413         nfree(msgs);
414         nfree(hash);
415         nfree(ctl);
416         MsgCloseArea(harea);
417    } else {
418        w_log(LL_ERR, "could not open area %s", area->areaName);
419        return 0;
420    }
421    return 1;
422 }
423 
linkAreas(char * name)424 void linkAreas(char *name)
425 {
426     FILE *f;
427     char *line;
428     s_area *area;
429     unsigned int i;
430 
431     /*  link only one area */
432     if (name != NULL) {
433         if ((area = getNetMailArea(config, name)) != NULL) {
434             linkArea(area,1);
435         } else {
436             area = getArea(config, name);
437             if (area->areaName != config->badArea.areaName) linkArea(area,0);
438             else w_log(LL_ERR, "area %s not found for link",name);
439         }
440         return;
441     }
442 
443     /*  open importlog file */
444     if (config->LinkWithImportlog != lwiNo) {
445         f = fopen(config->importlog, "r");
446     } else {
447         f = NULL;
448     }
449 
450     if (f == NULL) {
451 
452         if(config->LinkWithImportlog != lwiNo && config->importlog)
453         {
454             w_log(LL_CRIT, "Nothing to link. Use \"hpt link *\" to perform linking all areas");
455             return;
456         }
457         /*  if importlog does not exist link all areas */
458         w_log(LL_LINKING, "Linking all Areas.");
459 
460         /* link all echomail areas */
461         for (i = 0; i < config -> echoAreaCount; i++)
462             linkArea(&(config -> echoAreas[i]), 0);
463         /* link all local areas */
464         for (i = 0; i < config -> localAreaCount; i++)
465             linkArea(&(config -> localAreas[i]), 0);
466         /* link NetMailAreas */
467         for (i = 0; i < config -> netMailAreaCount; i++)
468             linkArea(&(config -> netMailAreas[i]), 1);
469 
470     } else {
471         w_log(LL_LINKING, "Using importlogfile -> linking only listed Areas");
472 
473         while (!feof(f)) {
474             line = readLine(f);
475 
476             if (line != NULL) {
477 
478                 if ((area = getNetMailArea(config, line)) != NULL) {
479                     if(area->scn == 0)
480                     {
481                         linkArea(area,1);
482                         area->scn = 1;
483                     }
484 
485                 } else {
486                     area = getArea(config, line);
487                     if (area->areaName != config->badArea.areaName)
488                         if(area->scn == 0)
489                         {
490                             linkArea(area,1);
491                             area->scn = 1;
492                         }
493                         nfree(line);
494                 }
495             }
496         }
497         fclose(f);
498         if (config->LinkWithImportlog == lwiKill)
499             remove(config->importlog);
500     }
501 }
502