xref: /plan9/sys/src/cmd/upas/send/message.c (revision ff8c3af2f44d95267f67219afa20ba82ff6cf7e4)
1 #include "common.h"
2 #include "send.h"
3 
4 #include "../smtp/smtp.h"
5 #include "../smtp/y.tab.h"
6 
7 /* global to this file */
8 static Reprog *rfprog;
9 static Reprog *fprog;
10 
11 #define VMLIMIT (64*1024)
12 #define MSGLIMIT (128*1024*1024)
13 
14 int received;	/* from rfc822.y */
15 
16 static String*	getstring(Node *p);
17 static String*	getaddr(Node *p);
18 
19 extern int
20 default_from(message *mp)
21 {
22 	char *cp, *lp;
23 
24 	cp = getenv("upasname");
25 	lp = getlog();
26 	if(lp == nil)
27 		return -1;
28 
29 	if(cp && *cp)
30 		s_append(mp->sender, cp);
31 	else
32 		s_append(mp->sender, lp);
33 	s_append(mp->date, thedate());
34 	return 0;
35 }
36 
37 extern message *
38 m_new(void)
39 {
40 	message *mp;
41 
42 	mp = (message *)mallocz(sizeof(message), 1);
43 	if (mp == 0) {
44 		perror("message:");
45 		exit(1);
46 	}
47 	mp->sender = s_new();
48 	mp->replyaddr = s_new();
49 	mp->date = s_new();
50 	mp->body = s_new();
51 	mp->size = 0;
52 	mp->fd = -1;
53 	return mp;
54 }
55 
56 extern void
57 m_free(message *mp)
58 {
59 	if(mp->fd >= 0){
60 		close(mp->fd);
61 		sysremove(s_to_c(mp->tmp));
62 		s_free(mp->tmp);
63 	}
64 	s_free(mp->sender);
65 	s_free(mp->date);
66 	s_free(mp->body);
67 	s_free(mp->havefrom);
68 	s_free(mp->havesender);
69 	s_free(mp->havereplyto);
70 	s_free(mp->havesubject);
71 	free((char *)mp);
72 }
73 
74 /* read a message into a temp file , return an open fd to it */
75 static int
76 m_read_to_file(Biobuf *fp, message *mp)
77 {
78 	int fd;
79 	int n;
80 	String *file;
81 	char buf[4*1024];
82 
83 	file = s_new();
84 	/*
85 	 *  create temp file to be remove on close
86 	 */
87 	abspath("mtXXXXXX", UPASTMP, file);
88 	mktemp(s_to_c(file));
89 	if((fd = syscreate(s_to_c(file), ORDWR|ORCLOSE, 0600))<0){
90 		s_free(file);
91 		return -1;
92 	}
93 	mp->tmp = file;
94 
95 	/*
96 	 *  read the rest into the temp file
97 	 */
98 	while((n = Bread(fp, buf, sizeof(buf))) > 0){
99 		if(write(fd, buf, n) != n){
100 			close(fd);
101 			return -1;
102 		}
103 		mp->size += n;
104 		if(mp->size > MSGLIMIT){
105 			mp->size = -1;
106 			break;
107 		}
108 	}
109 
110 	mp->fd = fd;
111 	return 0;
112 }
113 
114 /* get the first address from a node */
115 static String*
116 getaddr(Node *p)
117 {
118 	for(; p; p = p->next)
119 		if(p->s && p->addr)
120 			return s_copy(s_to_c(p->s));
121 }
122 
123 /* get the text of a header line minus the field name */
124 static String*
125 getstring(Node *p)
126 {
127 	String *s;
128 
129 	s = s_new();
130 	if(p == nil)
131 		return s;
132 
133 	for(p = p->next; p; p = p->next){
134 		if(p->s){
135 			s_append(s, s_to_c(p->s));
136 		}else{
137 			s_putc(s, p->c);
138 			s_terminate(s);
139 		}
140 		if(p->white)
141 			s_append(s, s_to_c(p->white));
142 	}
143 	return s;
144 }
145 
146 static char *fieldname[] =
147 {
148 [WORD-WORD]	"WORD",
149 [DATE-WORD]	"DATE",
150 [RESENT_DATE-WORD]	"RESENT_DATE",
151 [RETURN_PATH-WORD]	"RETURN_PATH",
152 [FROM-WORD]	"FROM",
153 [SENDER-WORD]	"SENDER",
154 [REPLY_TO-WORD]	"REPLY_TO",
155 [RESENT_FROM-WORD]	"RESENT_FROM",
156 [RESENT_SENDER-WORD]	"RESENT_SENDER",
157 [RESENT_REPLY_TO-WORD]	"RESENT_REPLY_TO",
158 [SUBJECT-WORD]	"SUBJECT",
159 [TO-WORD]	"TO",
160 [CC-WORD]	"CC",
161 [BCC-WORD]	"BCC",
162 [RESENT_TO-WORD]	"RESENT_TO",
163 [RESENT_CC-WORD]	"RESENT_CC",
164 [RESENT_BCC-WORD]	"RESENT_BCC",
165 [REMOTE-WORD]	"REMOTE",
166 [PRECEDENCE-WORD]	"PRECEDENCE",
167 [MIMEVERSION-WORD]	"MIMEVERSION",
168 [CONTENTTYPE-WORD]	"CONTENTTYPE",
169 [MESSAGEID-WORD]	"MESSAGEID",
170 [RECEIVED-WORD]	"RECEIVED",
171 [MAILER-WORD]	"MAILER",
172 [BADTOKEN-WORD]	"BADTOKEN",
173 };
174 
175 /* fix 822 addresses */
176 static void
177 rfc822cruft(message *mp)
178 {
179 	Field *f;
180 	Node *p;
181 	String *body, *s;
182 	char *cp;
183 
184 	/*
185 	 *  parse headers in in-core part
186 	 */
187 	yyinit(s_to_c(mp->body), s_len(mp->body));
188 	mp->rfc822headers = 0;
189 	yyparse();
190 	mp->rfc822headers = 1;
191 	mp->received = received;
192 
193 	/*
194 	 *  remove equivalent systems in all addresses
195 	 */
196 	body = s_new();
197 	cp = s_to_c(mp->body);
198 	for(f = firstfield; f; f = f->next){
199 		if(f->node->c == MIMEVERSION)
200 			mp->havemime = 1;
201 		if(f->node->c == FROM)
202 			mp->havefrom = getaddr(f->node);
203 		if(f->node->c == SENDER)
204 			mp->havesender = getaddr(f->node);
205 		if(f->node->c == REPLY_TO)
206 			mp->havereplyto = getaddr(f->node);
207 		if(f->node->c == TO)
208 			mp->haveto = 1;
209 		if(f->node->c == DATE)
210 			mp->havedate = 1;
211 		if(f->node->c == SUBJECT)
212 			mp->havesubject = getstring(f->node);
213 		if(f->node->c == PRECEDENCE && f->node->next && f->node->next->next){
214 			s = f->node->next->next->s;
215 			if(s && (strcmp(s_to_c(s), "bulk") == 0
216 				|| strcmp(s_to_c(s), "Bulk") == 0))
217 					mp->bulk = 1;
218 		}
219 		for(p = f->node; p; p = p->next){
220 			if(p->s){
221 				if(p->addr){
222 					cp = skipequiv(s_to_c(p->s));
223 					s_append(body, cp);
224 				} else
225 					s_append(body, s_to_c(p->s));
226 			}else{
227 				s_putc(body, p->c);
228 				s_terminate(body);
229 			}
230 			if(p->white)
231 				s_append(body, s_to_c(p->white));
232 			cp = p->end+1;
233 		}
234 		s_append(body, "\n");
235 	}
236 
237 	if(*s_to_c(body) == 0){
238 		s_free(body);
239 		return;
240 	}
241 
242 	if(*cp != '\n')
243 		s_append(body, "\n");
244 	s_memappend(body, cp, s_len(mp->body) - (cp - s_to_c(mp->body)));
245 	s_terminate(body);
246 
247 	firstfield = 0;
248 	mp->size += s_len(body) - s_len(mp->body);
249 	s_free(mp->body);
250 	mp->body = body;
251 }
252 
253 /* read in a message, interpret the 'From' header */
254 extern message *
255 m_read(Biobuf *fp, int rmail, int interactive)
256 {
257 	message *mp;
258 	Resub subexp[10];
259 	char *line;
260 	int first;
261 	int n;
262 
263 	mp = m_new();
264 
265 	/* parse From lines if remote */
266 	if (rmail) {
267 		/* get remote address */
268 		String *sender=s_new();
269 
270 		if (rfprog == 0)
271 			rfprog = regcomp(REMFROMRE);
272 		first = 1;
273 		while(s_read_line(fp, s_restart(mp->body)) != 0) {
274 			memset(subexp, 0, sizeof(subexp));
275 			if (regexec(rfprog, s_to_c(mp->body), subexp, 10) == 0){
276 				if(first == 0)
277 					break;
278 				if (fprog == 0)
279 					fprog = regcomp(FROMRE);
280 				memset(subexp, 0, sizeof(subexp));
281 				if(regexec(fprog, s_to_c(mp->body), subexp,10) == 0)
282 					break;
283 				s_restart(mp->body);
284 				append_match(subexp, s_restart(sender), SENDERMATCH);
285 				append_match(subexp, s_restart(mp->date), DATEMATCH);
286 				break;
287 			}
288 			append_match(subexp, s_restart(sender), REMSENDERMATCH);
289 			append_match(subexp, s_restart(mp->date), REMDATEMATCH);
290 			if(subexp[REMSYSMATCH].sp!=subexp[REMSYSMATCH].ep){
291 				append_match(subexp, mp->sender, REMSYSMATCH);
292 				s_append(mp->sender, "!");
293 			}
294 			first = 0;
295 		}
296 		s_append(mp->sender, s_to_c(sender));
297 
298 		s_free(sender);
299 	}
300 	if(*s_to_c(mp->sender)=='\0')
301 		default_from(mp);
302 
303 	/* if sender address is unreturnable, treat message as bulk mail */
304 	if(!returnable(s_to_c(mp->sender)))
305 		mp->bulk = 1;
306 
307 	/* get body */
308 	if(interactive && !rmail){
309 		/* user typing on terminal: terminator == '.' or EOF */
310 		for(;;) {
311 			line = s_read_line(fp, mp->body);
312 			if (line == 0)
313 				break;
314 			if (strcmp(".\n", line)==0) {
315 				mp->body->ptr -= 2;
316 				*mp->body->ptr = '\0';
317 				break;
318 			}
319 		}
320 		mp->size = mp->body->ptr - mp->body->base;
321 	} else {
322 		/*
323 		 *  read up to VMLIMIT bytes (more or less) into main memory.
324 		 *  if message is longer put the rest in a tmp file.
325 		 */
326 		mp->size = mp->body->ptr - mp->body->base;
327 		n = s_read(fp, mp->body, VMLIMIT);
328 		if(n < 0){
329 			perror("m_read");
330 			exit(1);
331 		}
332 		mp->size += n;
333 		if(n == VMLIMIT){
334 			if(m_read_to_file(fp, mp) < 0){
335 				perror("m_read");
336 				exit(1);
337 			}
338 		}
339 
340 	}
341 
342 	/*
343 	 *  ignore 0 length messages from a terminal
344 	 */
345 	if (!rmail && mp->size == 0)
346 		return 0;
347 
348 	rfc822cruft(mp);
349 
350 	return mp;
351 }
352 
353 /* return a piece of message starting at `offset' */
354 extern int
355 m_get(message *mp, long offset, char **pp)
356 {
357 	static char buf[4*1024];
358 
359 	/*
360 	 *  are we past eof?
361 	 */
362 	if(offset >= mp->size)
363 		return 0;
364 
365 	/*
366 	 *  are we in the virtual memory portion?
367 	 */
368 	if(offset < s_len(mp->body)){
369 		*pp = mp->body->base + offset;
370 		return mp->body->ptr - mp->body->base - offset;
371 	}
372 
373 	/*
374 	 *  read it from the temp file
375 	 */
376 	offset -= s_len(mp->body);
377 	if(mp->fd < 0)
378 		return -1;
379 	if(seek(mp->fd, offset, 0)<0)
380 		return -1;
381 	*pp = buf;
382 	return read(mp->fd, buf, sizeof buf);
383 }
384 
385 /* output the message body without ^From escapes */
386 static int
387 m_noescape(message *mp, Biobuf *fp)
388 {
389 	long offset;
390 	int n;
391 	char *p;
392 
393 	for(offset = 0; offset < mp->size; offset += n){
394 		n = m_get(mp, offset, &p);
395 		if(n <= 0){
396 			Bflush(fp);
397 			return -1;
398 		}
399 		if(Bwrite(fp, p, n) < 0)
400 			return -1;
401 	}
402 	return Bflush(fp);
403 }
404 
405 /*
406  *  Output the message body with '^From ' escapes.
407  *  Ensures that any line starting with a 'From ' gets a ' ' stuck
408  *  in front of it.
409  */
410 static int
411 m_escape(message *mp, Biobuf *fp)
412 {
413 	char *p, *np;
414 	char *end;
415 	long offset;
416 	int m, n;
417 	char *start;
418 
419 	for(offset = 0; offset < mp->size; offset += n){
420 		n = m_get(mp, offset, &start);
421 		if(n < 0){
422 			Bflush(fp);
423 			return -1;
424 		}
425 
426 		p = start;
427 		for(end = p+n; p < end; p += m){
428 			np = memchr(p, '\n', end-p);
429 			if(np == 0){
430 				Bwrite(fp, p, end-p);
431 				break;
432 			}
433 			m = np - p + 1;
434 			if(m > 5 && strncmp(p, "From ", 5) == 0)
435 				Bputc(fp, ' ');
436 			Bwrite(fp, p, m);
437 		}
438 	}
439 	Bflush(fp);
440 	return 0;
441 }
442 
443 static int
444 printfrom(message *mp, Biobuf *fp)
445 {
446 	String *s;
447 	int rv;
448 
449 	if(!returnable(s_to_c(mp->sender)))
450 		return Bprint(fp, "From: Postmaster\n");
451 
452 	s = username(mp->sender);
453 	if(s) {
454 		s_append(s, " <");
455 		s_append(s, s_to_c(mp->sender));
456 		s_append(s, ">");
457 	} else {
458 		s = s_copy(s_to_c(mp->sender));
459 	}
460 	s = unescapespecial(s);
461 	rv = Bprint(fp, "From: %s\n", s_to_c(s));
462 	s_free(s);
463 	return rv;
464 }
465 
466 static char *
467 rewritezone(char *z)
468 {
469 	int mindiff;
470 	char s;
471 	Tm *tm;
472 	static char x[7];
473 
474 	tm = localtime(time(0));
475 	mindiff = tm->tzoff/60;
476 
477 	/* if not in my timezone, don't change anything */
478 	if(strcmp(tm->zone, z) != 0)
479 		return z;
480 
481 	if(mindiff < 0){
482 		s = '-';
483 		mindiff = -mindiff;
484 	} else
485 		s = '+';
486 
487 	sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
488 	return x;
489 }
490 
491 int
492 isutf8(String *s)
493 {
494 	char *p;
495 
496 	for(p = s_to_c(s);  *p; p++)
497 		if(*p&0x80)
498 			return 1;
499 	return 0;
500 }
501 
502 void
503 printutf8mime(Biobuf *b)
504 {
505 	Bprint(b, "MIME-Version: 1.0\n");
506 	Bprint(b, "Content-Type: text/plain; charset=\"UTF-8\"\n");
507 	Bprint(b, "Content-Transfer-Encoding: 8bit\n");
508 }
509 
510 /* output a message */
511 extern int
512 m_print(message *mp, Biobuf *fp, char *remote, int mbox)
513 {
514 	String *date, *sender;
515 	char *f[6];
516 	int n;
517 
518 	sender = unescapespecial(s_clone(mp->sender));
519 
520 	if (remote != 0){
521 		if(print_remote_header(fp,s_to_c(sender),s_to_c(mp->date),remote) < 0){
522 			s_free(sender);
523 			return -1;
524 		}
525 	} else {
526 		if(print_header(fp, s_to_c(sender), s_to_c(mp->date)) < 0){
527 			s_free(sender);
528 			return -1;
529 		}
530 	}
531 	s_free(sender);
532 	if(!rmail && !mp->havedate){
533 		/* add a date: line Date: Sun, 19 Apr 1998 12:27:52 -0400 */
534 		date = s_copy(s_to_c(mp->date));
535 		n = getfields(s_to_c(date), f, 6, 1, " \t");
536 		if(n == 6)
537 			Bprint(fp, "Date: %s, %s %s %s %s %s\n", f[0], f[2], f[1],
538 			 f[5], f[3], rewritezone(f[4]));
539 	}
540 	if(!rmail && !mp->havemime && isutf8(mp->body))
541 		printutf8mime(fp);
542 	if(mp->to){
543 		/* add the to: line */
544 		if (Bprint(fp, "%s\n", s_to_c(mp->to)) < 0)
545 			return -1;
546 		/* add the from: line */
547 		if (!mp->havefrom && printfrom(mp, fp) < 0)
548 			return -1;
549 		if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
550 			if (Bprint(fp, "\n") < 0)
551 				return -1;
552 	} else if(!rmail){
553 		/* add the from: line */
554 		if (!mp->havefrom && printfrom(mp, fp) < 0)
555 			return -1;
556 		if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
557 			if (Bprint(fp, "\n") < 0)
558 				return -1;
559 	}
560 
561 	if (!mbox)
562 		return m_noescape(mp, fp);
563 	return m_escape(mp, fp);
564 }
565 
566 /* print just the message body */
567 extern int
568 m_bprint(message *mp, Biobuf *fp)
569 {
570 	return m_noescape(mp, fp);
571 }
572