1 /*:ts=8*/
2 /*****************************************************************************
3 * FIDOGATE --- Gateway UNIX Mail/News <-> FIDO NetMail/EchoMail
4 *
5 * $Id: message.c,v 4.24 2004/08/22 20:19:11 n0ll Exp $
6 *
7 * Reading and processing FTN text body
8 *
9 *****************************************************************************
10 * Copyright (C) 1990-2004
11 * _____ _____
12 * | |___ | Martin Junius <mj.at.n0ll.dot.net>
13 * | | | | | | Radiumstr. 18
14 * |_|_|_|@home| D-51069 Koeln, Germany
15 *
16 * This file is part of FIDOGATE.
17 *
18 * FIDOGATE is free software; you can redistribute it and/or modify it
19 * under the terms of the GNU General Public License as published by the
20 * Free Software Foundation; either version 2, or (at your option) any
21 * later version.
22 *
23 * FIDOGATE is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with FIDOGATE; see the file COPYING. If not, write to the Free
30 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
31 *****************************************************************************/
32
33 #include "fidogate.h"
34
35
36
37 /*
38 * Global flag for ignoring (removing) 0x8d (Soft-CR)
39 */
40 int msg_ignore_0x8d = 1;
41
42
43
44 /*
45 * Debug output of line
46 */
debug_line(FILE * out,char * line,int crlf)47 static void debug_line(FILE *out, char *line, int crlf)
48 {
49 int c;
50
51 while( (c = *line++) )
52 if( !(c & 0xe0) )
53 {
54 if(crlf || (c!='\r' && c!='\n'))
55 fprintf(out, "^%c", '@' + c);
56 }
57 else
58 putc(c, out);
59 putc('\n', out);
60 }
61
62
63
64 /*
65 * Read one "line" from FTN text body. A line comprises arbitrary
66 * characters terminated with a CR '\r'. None, one, or many LFs '\n'
67 * may follow:
68 *
69 * x x x x x x x x x \r [\n...]
70 *
71 * This function checks for orphan 0-bytes in the message body and
72 * recognizes grunged messages produced by SQUISH 1.01.
73 *
74 * Return values:
75 * -1 ERROR an error occured
76 * 1 next line follows
77 * 0 MSG_END end of message, end of packet
78 * 2 MSG_TYPE end of message, next message header follows
79 */
pkt_get_line(FILE * fp,char * buf,int size)80 int pkt_get_line(FILE *fp, char *buf, int size)
81 {
82 char *p;
83 int c, c1, c2;
84 int read_lf=FALSE;
85 long pos;
86
87 p = buf;
88
89 while (size > 3) /* Room for current + 2 extra chars */
90 {
91 c = getc(fp);
92
93 if(read_lf && c!='\n') /* No more LFs, this is end of line */
94 {
95 ungetc(c, fp);
96 *p = 0;
97 return 1;
98 }
99
100 switch(c)
101 {
102 case EOF: /* premature EOF */
103 return ERROR;
104 break;
105
106 case 0: /* end of message or orphan */
107 c1 = getc(fp);
108 c2 = getc(fp);
109 if(c1==EOF || c2==EOF)
110 return ERROR;
111 if(c1==2 && c2==0) /* end of message */
112 {
113 *p = 0;
114 return MSG_TYPE;
115 }
116 if(c1==0 && c2==0) /* end of packet */
117 {
118 *p = 0;
119 return MSG_END;
120 }
121 /* orphan 0-byte, skip */
122 pos = ftell(fp);
123 if(pos == ERROR)
124 logit("pkt_get_line(): orphan 0-char (can't determine offset)");
125 else
126 logit("pkt_get_line(): orphan 0-char (offset=%ld)", pos);
127 if(c1)
128 {
129 size--;
130 *p++ = c1;
131 }
132 if(c2)
133 {
134 size--;
135 *p++ = c2;
136 }
137 continue;
138 break;
139
140 #if 1 /***** Work around for a SQUISH bug **********************************/
141 case 2: /* May be grunged packet: start of */
142 c1 = getc(fp); /* new message */
143 if(c1 == EOF)
144 return ERROR;
145 if(c1 == 0) /* Looks like it is ... */
146 {
147 *p = 0;
148 logit("pkt_get_line(): grunged packet");
149 return MSG_TYPE;
150 }
151 *p++ = c;
152 *p++ = c1;
153 size--;
154 size--;
155 break;
156
157 #endif /********************************************************************/
158
159 case '\r': /* End of line */
160 read_lf = TRUE;
161 /**fall thru**/
162
163 default:
164 *p++ = c;
165 size--;
166 break;
167 }
168 }
169
170 /* buf too small */
171 *p = 0;
172 return 1;
173 }
174
175
176
177 /*
178 * Read text body from packet into Textlist
179 *
180 * Return values:
181 * -1 ERROR an error occured
182 * 0 MSG_END end of packet
183 * 2 MSG_TYPE next message header follows
184 */
pkt_get_body(FILE * fp,Textlist * tl)185 int pkt_get_body(FILE *fp, Textlist *tl)
186 {
187 int type;
188
189 tl_clear(tl);
190
191 /* Read lines and put into textlist */
192 while( (type=pkt_get_line(fp, buffer, sizeof(buffer))) == 1 )
193 tl_append(tl, buffer);
194 /* Put incomplete last line into textlist, if any */
195 if( (type==MSG_END || type==MSG_TYPE) && buffer[0] )
196 {
197 /* Make sure that this line is terminated by \r\n */
198 BUF_APPEND(buffer, "\r\n");
199
200 tl_append(tl, buffer);
201 }
202
203 return type;
204 }
205
206
207
208 /*
209 * Initialize MsgBody
210 */
msg_body_init(MsgBody * body)211 void msg_body_init(MsgBody *body)
212 {
213 body->area = NULL;
214 tl_init(&body->kludge);
215 tl_init(&body->rfc);
216 tl_init(&body->body);
217 body->tear = NULL;
218 body->origin = NULL;
219 tl_init(&body->seenby);
220 tl_init(&body->path);
221 tl_init(&body->via);
222 }
223
224
225
226 /*
227 * Clear MsgBody
228 */
msg_body_clear(MsgBody * body)229 void msg_body_clear(MsgBody *body)
230 {
231 xfree(body->area);
232 body->area = NULL;
233 tl_clear(&body->kludge);
234 tl_clear(&body->rfc);
235 tl_clear(&body->body);
236 xfree(body->tear);
237 body->tear = NULL;
238 xfree(body->origin);
239 body->origin = NULL;
240 tl_clear(&body->seenby);
241 tl_clear(&body->path);
242 tl_clear(&body->via);
243 }
244
245
246
247 /*
248 * Convert message body from Textlist to MsgBody struct
249 *
250 * Return: -1 error during parsing, but still valid control info
251 * -2 fatal error, can't process message
252 */
253
254 static char *rfc_headers[] =
255 {
256 FTN_RFC_HEADERS, NULL
257 };
258
259
msg_body_parse_echomail(MsgBody * body)260 static int msg_body_parse_echomail(MsgBody *body)
261 {
262 Textline *p, *pn;
263
264 /*
265 * Work our way backwards from the end of the body to the tear line
266 */
267 /* Search for last ^APath or SEEN-BY line */
268 for(p=body->body.last;
269 p && strncmp(p->line, "\001PATH", 5) && strncmp(p->line, "SEEN-BY", 7);
270 p=p->prev) ;
271 if(p == NULL)
272 {
273 logit("ERROR: parsing echomail message: no ^APATH or SEEN-BY line");
274 return -2;
275 }
276 /* ^APATH */
277 for(; p && !strncmp(p->line, "\001PATH", 5); p=p->prev) ;
278 /* SEEN-BY */
279 for(; p && !strncmp(p->line, "SEEN-BY", 7); p=p->prev) ;
280 /* Some systems generate empty line[s] between Origin and SEEN-BY :-( */
281 for(; p && *p->line=='\r'; p=p->prev );
282 /* * Origin: */
283 if(p && !strncmp(p->line, " * Origin:", 10))
284 p = p->prev;
285 /* --- Tear line */
286 if(p && (!strncmp(p->line, "---\r", 4) ||
287 !strncmp(p->line, "--- ", 4) ))
288 p = p->prev;
289 /* Move back */
290 p = p ? p->next : body->body.first;
291
292 /*
293 * Copy to MsgBody
294 */
295 /* --- Tear line */
296 if(p && (!strncmp(p->line, "---\r", 4) ||
297 !strncmp(p->line, "--- ", 4) ))
298 {
299 pn = p->next;
300 body->tear = strsave(p->line);
301 tl_delete(&body->body, p);
302 p = pn;
303 }
304 /* * Origin: */
305 if(p && !strncmp(p->line, " * Origin:", 10))
306 {
307 pn = p->next;
308 body->origin = strsave(p->line);
309 tl_delete(&body->body, p);
310 p = pn;
311 }
312 /* There may be empty lines after Origin ... more FIDO brain damage */
313 while(p && *p->line=='\r')
314 {
315 pn = p->next;
316 tl_delete(&body->body, p);
317 p = pn;
318 }
319 /* SEEN-BY */
320 while(p)
321 {
322 pn = p->next;
323 if(!strncmp(p->line, "SEEN-BY", 7))
324 {
325 tl_remove(&body->body, p);
326 tl_add(&body->seenby, p);
327 p = pn;
328 }
329 else
330 break;
331 }
332 /* ^APATH */
333 while(p)
334 {
335 pn = p->next;
336 if(!strncmp(p->line, "\001PATH", 5))
337 {
338 tl_remove(&body->body, p);
339 tl_add(&body->path, p);
340 p = pn;
341 }
342 else
343 break;
344 }
345 /* Delete any following lines */
346 while(p)
347 {
348 pn = p->next;
349 tl_delete(&body->body, p);
350 p = pn;
351 }
352
353 if(body->seenby.n==0 /*|| body->path.n==0*/)
354 {
355 logit("ERROR: parsing echomail message: no SEEN-BY line");
356 return -2;
357 }
358 if(body->tear==NULL || body->origin==NULL /** || body->path.n==0 **/)
359 return -1;
360
361 return OK;
362 }
363
364
msg_body_parse_netmail(MsgBody * body)365 static int msg_body_parse_netmail(MsgBody *body)
366 {
367 Textline *p, *pn;
368
369 /*
370 * Work our way backwards from the end of the body to the tear line
371 */
372 /* ^AVia, there may be empty lines within the ^AVia lines */
373 for(p=body->body.last;
374 p && ( !strncmp(p->line, "\001Via", 4) || *p->line=='\r' );
375 p=p->prev) ;
376 /* * Origin: */
377 if(p && !strncmp(p->line, " * Origin:", 10))
378 p = p->prev;
379 /* --- Tear line */
380 if(p && (!strncmp(p->line, "---\r", 4) ||
381 !strncmp(p->line, "--- ", 4) ))
382 p = p->prev;
383 /* Move back */
384 p = p ? p->next : body->body.first;
385
386 /*
387 * Copy to MsgBody
388 */
389 /* --- Tear line */
390 if(p && (!strncmp(p->line, "---\r", 4) ||
391 !strncmp(p->line, "--- ", 4) ))
392 {
393 pn = p->next;
394 body->tear = strsave(p->line);
395 tl_delete(&body->body, p);
396 p = pn;
397 }
398 /* * Origin: */
399 if(p && !strncmp(p->line, " * Origin:", 10))
400 {
401 pn = p->next;
402 body->origin = strsave(p->line);
403 tl_delete(&body->body, p);
404 p = pn;
405 }
406 /* ^AVia */
407 while(p)
408 {
409 pn = p->next;
410 if(!strncmp(p->line, "\001Via", 4))
411 {
412 tl_remove(&body->body, p);
413 tl_add(&body->via, p);
414 p = pn;
415 }
416 else if(*p->line == '\r')
417 {
418 tl_remove(&body->body, p);
419 p = pn;
420 }
421 else
422 break;
423 }
424 /* Delete any following lines */
425 while(p)
426 {
427 pn = p->next;
428 tl_delete(&body->body, p);
429 p = pn;
430 }
431
432 return OK;
433 }
434
435
is_blank_line(char * s)436 int is_blank_line(char *s)
437 {
438 if(!s)
439 return TRUE;
440 while(*s)
441 {
442 if(!is_space(*s))
443 return FALSE;
444 s++;
445 }
446 return TRUE;
447 }
448
449
msg_body_parse(Textlist * text,MsgBody * body)450 int msg_body_parse(Textlist *text, MsgBody *body)
451 {
452 Textline *p, *pn;
453 int i;
454 int look_for_AREA;
455
456 msg_body_clear(body);
457
458 p = text->first;
459
460 /*
461 * 1st, look for ^A kludges and AREA: line
462 */
463 look_for_AREA = TRUE;
464 while(p)
465 {
466 pn = p->next;
467
468 if(p->line[0] == '\001') /* ^A kludge, */
469 {
470 tl_remove(text, p);
471 #if 0
472 if(! strncmp(p->line, "\001Via", 4))
473 tl_add(&body->via, p);
474 else
475 #endif
476 tl_add(&body->kludge, p);
477 p = pn;
478 continue;
479 }
480 if(look_for_AREA && /* Only 1st AREA line */
481 !strncmp(p->line, "AREA:", 5) ) /* AREA:XXX */
482 {
483 look_for_AREA = FALSE;
484 body->area = strsave(p->line);
485 tl_delete(text, p);
486 p = pn;
487 continue;
488 }
489
490 break;
491 }
492
493 /*
494 * Next, look for supported RFC header lines. Up to 3 blank
495 * lines before the RFC headers are allowed
496 */
497 if(p && is_blank_line(p->line))
498 p = p->next;
499 if(p && is_blank_line(p->line))
500 p = p->next;
501 if(p && is_blank_line(p->line))
502 p = p->next;
503
504 while(p)
505 {
506 int found;
507 pn = p->next;
508
509 for(found=FALSE, i=0; rfc_headers[i]; i++)
510 if(!strnicmp(p->line, rfc_headers[i], strlen(rfc_headers[i])))
511 {
512 tl_remove(text, p);
513 tl_add(&body->rfc, p);
514 p = pn;
515 found = TRUE;
516 break;
517 }
518
519 if(!found)
520 break;
521 }
522
523 /*
524 * Now, text contains just the message body after kludges
525 * and RFC headers, copy to body->body.
526 */
527 body->body = *text;
528 tl_init(text);
529
530 /*
531 * Call function for EchoMail and NetMail, respectively, parsing
532 * control info at end of text body.
533 */
534 return body->area ? msg_body_parse_echomail(body)
535 : msg_body_parse_netmail (body) ;
536 }
537
538
539
540 /*
541 * Debug output of message body
542 */
msg_body_debug(FILE * out,MsgBody * body,int crlf)543 void msg_body_debug(FILE *out, MsgBody *body, int crlf)
544 {
545 Textline *p;
546
547 fprintf(out, "----------------------------------------"
548 "--------------------------------------\n");
549 if(body->area)
550 debug_line(out, body->area, crlf);
551 for(p=body->kludge.first; p; p=p->next)
552 debug_line(out, p->line, crlf);
553 fprintf(out, "----------------------------------------"
554 "--------------------------------------\n");
555 if(body->rfc.first)
556 {
557 for(p=body->rfc.first; p; p=p->next)
558 debug_line(out, p->line, crlf);
559 fprintf(out, "----------------------------------------"
560 "--------------------------------------\n");
561 }
562 for(p=body->body.first; p; p=p->next)
563 debug_line(out, p->line, crlf);
564 fprintf(out, "----------------------------------------"
565 "--------------------------------------\n");
566 if(body->tear)
567 debug_line(out, body->tear, crlf);
568 if(body->origin)
569 debug_line(out, body->origin, crlf);
570 for(p=body->seenby.first; p; p=p->next)
571 debug_line(out, p->line, crlf);
572 for(p=body->path.first; p; p=p->next)
573 debug_line(out, p->line, crlf);
574 for(p=body->via.first; p; p=p->next)
575 debug_line(out, p->line, crlf);
576 fprintf(out, "========================================"
577 "======================================\n");
578 }
579
580
581
582 /*
583 * Write single line to packet file, checking for NULL
584 */
msg_put_line(FILE * fp,char * line)585 int msg_put_line(FILE *fp, char *line)
586 {
587 if(line)
588 fputs(line, fp);
589 return ferror(fp);
590 }
591
592
593
594 /*
595 * Write MsgBody to packet file
596 */
msg_put_msgbody(FILE * fp,MsgBody * body,int term)597 int msg_put_msgbody(FILE *fp, MsgBody *body, int term)
598 {
599 msg_put_line(fp, body->area );
600 tl_fput (fp, &body->kludge);
601 tl_fput (fp, &body->rfc );
602 tl_fput (fp, &body->body );
603 msg_put_line(fp, body->tear );
604 msg_put_line(fp, body->origin);
605 tl_fput (fp, &body->seenby);
606 tl_fput (fp, &body->path );
607 tl_fput (fp, &body->via );
608
609 if(term)
610 putc(0, fp); /* Terminating 0-byte */
611
612 return ferror(fp);
613 }
614
615
616
617 /*
618 * Convert text line read from FTN message body
619 */
msg_xlate_line(char * buf,int n,char * line,int qp)620 char *msg_xlate_line(char *buf, int n, char *line, int qp)
621 {
622 char *s, *p, *xl;
623 int c;
624
625 n--; /* Room for \0 char */
626
627 for(s=line, p=buf; *s; s++)
628 {
629 c = *s & 0xff;
630
631 /*
632 * Special chars require special treatment ...
633 */
634 if(c == '\n') /* Ignore \n */
635 continue;
636 if(msg_ignore_0x8d && c==0x8d) /* Ignore soft \r */
637 continue;
638 if(c == '\r')
639 c = '\n';
640 else if(c < ' ') {
641 /* Translate control chars to ^X */
642 if(c!='\t' && c!='\f')
643 {
644 if(!n--)
645 break;
646 *p++ = '^';
647 c = c + '@';
648 }
649 }
650 else if(qp && c=='=')
651 {
652 /* Translate '=' to MIME quoted-printable =3D */
653 xl = "=3D";
654 while(*xl)
655 {
656 if(!n--)
657 break;
658 *p++ = *xl++;
659 }
660 continue;
661 }
662 else if(c & 0x80)
663 {
664 /* Translate special characters according to character set */
665 xl = charset_map_c(c, qp);
666 if(!xl || !*xl)
667 continue;
668 while(*xl)
669 {
670 if(!n--)
671 break;
672 *p++ = *xl++;
673 }
674 continue;
675 }
676
677 /*
678 * Put normal char into buf
679 */
680 if(!n--)
681 break;
682 *p++ = c;
683 }
684
685 *p = 0;
686
687 return OK;
688 }
689
690
691
692 /*
693 * Format buffer line and put it into Textlist. Returns number of
694 * lines.
695 */
696 #define DEFAULT_LINE_LENGTH 72
697 #define NOBREAK_LINE_LENGTH 79
698 #define MAX_LINE_LENGTH 200
699
msg_get_line_length(void)700 static int msg_get_line_length(void)
701 {
702 static int message_line_length = 0;
703
704 if(!message_line_length)
705 {
706 char *p;
707 if( (p = cf_get_string("MessageLineLength", TRUE)) )
708 {
709 debug(8, "config: MessageLineLength %s", p);
710 message_line_length = atoi(p);
711 if(message_line_length < 20 ||
712 message_line_length > MAX_LINE_LENGTH)
713 {
714 logit("WARNING: illegal MessageLineLength value %d",
715 message_line_length);
716 message_line_length = DEFAULT_LINE_LENGTH;
717 }
718 }
719 else
720 message_line_length = DEFAULT_LINE_LENGTH;
721 }
722 return message_line_length;
723 }
724
725
msg_format_buffer(char * buffer,Textlist * tlist)726 int msg_format_buffer(char *buffer, Textlist *tlist)
727 {
728 int max_linelen;
729 char *p, *np;
730 char localbuffer[MAX_LINE_LENGTH + 16]; /* Some extra space */
731 int i;
732 int lines;
733
734 max_linelen = msg_get_line_length();
735
736 if(strlen(buffer) <= NOBREAK_LINE_LENGTH) /* Nothing to do */
737 {
738 tl_append(tlist, buffer);
739 return 1;
740 }
741 else
742 {
743 /* Break line with word wrap */
744 lines = 0;
745 p = buffer;
746
747 while(TRUE)
748 {
749 /* Search backward for a whitespace to break line. If no
750 * proper point is found, the line will not be split. */
751 for(i=max_linelen-1; i>=0; i--)
752 if(is_blank(p[i])) /* Found a white space */
753 break;
754 if(i < max_linelen/2) /* Not proper space to split found, */
755 { /* put line as is */
756 tl_append(tlist, p);
757 lines++;
758 return lines;
759 }
760 for(; i>=0 && is_blank(p[i]); i--); /* Skip more white space */
761 i++; /* Return to last white sp. */
762
763 /* Cut here and put into textlist */
764 np = p + i;
765 *np++ = 0;
766 BUF_COPY2(localbuffer, p, "\n");
767 tl_append(tlist, localbuffer);
768 lines++;
769
770 /* Advance buffer pointer and test length of remaining
771 * line */
772 p = np;
773 for(; *p && is_blank(*p); p++); /* Skip white space */
774 if(*p == 0) /* The end */
775 return lines;
776 if(strlen(p) <= max_linelen) /* No more wrappin' */
777 {
778 tl_append(tlist, p);
779 lines++;
780 return lines;
781 }
782
783 /* Play it again, Sam! */
784 }
785 }
786 /**NOT REACHED**/
787 return 0;
788 }
789
790
791
792 /*
793 * check_origin() --- Analyse ` * Origin: ...' line in FIDO message
794 * body and parse address in ()s into Node structure
795 *
796 * Origin line is checked for the rightmost occurence of
797 * ([text] z:n/n.p).
798 */
msg_parse_origin(char * buffer,Node * node)799 int msg_parse_origin(char *buffer, Node *node)
800 {
801 char *left, *right;
802 char *buf;
803
804 if(buffer == NULL)
805 return ERROR;
806
807 buf = strsave(buffer);
808 right = strrchr(buf, ')');
809 if(!right) {
810 xfree(buf);
811 return ERROR;
812 }
813 left = strrchr(buf, '(');
814 if(!left) {
815 xfree(buf);
816 return ERROR;
817 }
818
819 *right = 0;
820 *left++ = 0;
821
822 /* Parse node info */
823 while(*left && !is_digit(*left))
824 left++;
825 if(asc_to_node(left, node, FALSE) != OK)
826 /* Not a valid FIDO address */
827 node_invalid(node);
828
829 xfree(buf);
830 return node->zone != -1 ? OK : ERROR;
831 }
832