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