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