Blob


1 #include "common.h"
2 #include <thread.h>
3 #include <9pclient.h>
4 #include <ctype.h>
6 enum
7 {
8 STACK = 32768
9 };
11 #define inline _inline
13 typedef struct Attach Attach;
14 typedef struct Alias Alias;
15 typedef struct Addr Addr;
16 typedef struct Ctype Ctype;
18 struct Attach {
19 Attach *next;
20 char *path;
21 int fd;
22 char *type;
23 int inline;
24 Ctype *ctype;
25 };
27 struct Alias
28 {
29 Alias *next;
30 int n;
31 Addr *addr;
32 };
34 struct Addr
35 {
36 Addr *next;
37 char *v;
38 };
40 enum {
41 Hfrom,
42 Hto,
43 Hcc,
44 Hbcc,
45 Hsender,
46 Hreplyto,
47 Hinreplyto,
48 Hdate,
49 Hsubject,
50 Hmime,
51 Hpriority,
52 Hmsgid,
53 Hcontent,
54 Hx,
55 Hprecedence,
56 Nhdr
57 };
59 enum {
60 PGPsign = 1,
61 PGPencrypt = 2
62 };
64 char *hdrs[Nhdr] = {
65 [Hfrom] "from:",
66 [Hto] "to:",
67 [Hcc] "cc:",
68 [Hbcc] "bcc:",
69 [Hreplyto] "reply-to:",
70 [Hinreplyto] "in-reply-to:",
71 [Hsender] "sender:",
72 [Hdate] "date:",
73 [Hsubject] "subject:",
74 [Hpriority] "priority:",
75 [Hmsgid] "message-id:",
76 [Hmime] "mime-",
77 [Hcontent] "content-",
78 [Hx] "x-",
79 [Hprecedence] "precedence"
80 };
82 struct Ctype {
83 char *type;
84 char *ext;
85 int display;
86 };
88 Ctype ctype[] = {
89 { "text/plain", "txt", 1, },
90 { "text/html", "html", 1, },
91 { "text/html", "htm", 1, },
92 { "text/tab-separated-values", "tsv", 1, },
93 { "text/richtext", "rtx", 1, },
94 { "message/rfc822", "txt", 1, },
95 { "", 0, 0, }
96 };
98 Ctype *mimetypes;
100 int pid = -1;
101 int pgppid = -1;
103 Attach* mkattach(char*, char*, int);
104 int readheaders(Biobuf*, int*, String**, Addr**, int);
105 void body(Biobuf*, Biobuf*, int);
106 char* mkboundary(void);
107 int printdate(Biobuf*);
108 int printfrom(Biobuf*);
109 int printto(Biobuf*, Addr*);
110 int printcc(Biobuf*, Addr*);
111 int printsubject(Biobuf*, char*);
112 int printinreplyto(Biobuf*, char*);
113 int sendmail(Addr*, Addr*, int*, char*);
114 void attachment(Attach*, Biobuf*);
115 int cistrncmp(char*, char*, int);
116 int cistrcmp(char*, char*);
117 char* waitforsubprocs(void);
118 int enc64(char*, int, uchar*, int);
119 Addr* expand(int, char**);
120 Alias* readaliases(void);
121 Addr* expandline(String**, Addr*);
122 void Bdrain(Biobuf*);
123 void freeaddr(Addr *);
124 int pgpopts(char*);
125 int pgpfilter(int*, int, int);
126 void readmimetypes(void);
127 char* estrdup(char*);
128 void* emalloc(int);
129 void* erealloc(void*, int);
130 void freeaddr(Addr*);
131 void freeaddrs(Addr*);
132 void freealias(Alias*);
133 void freealiases(Alias*);
134 int doublequote(Fmt*);
135 int mountmail(void);
136 int nprocexec;
137 int rfc2047fmt(Fmt*);
138 char* mksubject(char*);
140 int rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag;
141 int pgpflag = 0;
142 char *user;
143 char *login;
144 Alias *aliases;
145 int rfc822syntaxerror;
146 char lastchar;
147 char *replymsg;
149 CFsys *mailfs;
151 enum
153 Ok = 0,
154 Nomessage = 1,
155 Nobody = 2,
156 Error = -1
157 };
159 #pragma varargck type "Z" char*
161 void
162 usage(void)
164 fprint(2, "usage: %s [-Fr#xn] [-s subject] [-c ccrecipient] [-t type] [-aA attachment] [-p[es]] [-R replymsg] -8 | recipient-list\n",
165 argv0);
166 threadexitsall("usage");
169 void
170 fatal(char *fmt, ...)
172 char buf[1024];
173 va_list arg;
175 if(pid >= 0)
176 postnote(PNPROC, pid, "die");
177 if(pgppid >= 0)
178 postnote(PNPROC, pgppid, "die");
180 va_start(arg, fmt);
181 vseprint(buf, buf+sizeof(buf), fmt, arg);
182 va_end(arg);
183 fprint(2, "%s: %s\n", argv0, buf);
184 holdoff(holding);
185 threadexitsall(buf);
188 void
189 threadmain(int argc, char **argv)
191 Attach *first, **l, *a;
192 char *subject, *type, *boundary;
193 int flags, fd;
194 Biobuf in, out, *b;
195 Addr *to;
196 Addr *cc;
197 String *file, *hdrstring;
198 int noinput, headersrv;
199 int ccargc;
200 char *ccargv[32];
202 noinput = 0;
203 subject = nil;
204 first = nil;
205 l = &first;
206 type = nil;
207 hdrstring = nil;
208 ccargc = 0;
210 quotefmtinstall();
211 fmtinstall('Z', doublequote);
212 fmtinstall('U', rfc2047fmt);
213 threadwaitchan();
215 ARGBEGIN{
216 case 't':
217 type = EARGF(usage());
218 break;
219 case 'a':
220 flags = 0;
221 goto aflag;
222 case 'A':
223 flags = 1;
224 aflag:
225 a = mkattach(EARGF(usage()), type, flags);
226 if(a == nil)
227 threadexitsall("bad args");
228 type = nil;
229 *l = a;
230 l = &a->next;
231 break;
232 case 'C':
233 if(ccargc >= nelem(ccargv)-1)
234 sysfatal("too many cc's");
235 ccargv[ccargc] = ARGF();
236 if(ccargv[ccargc] == nil)
237 usage();
238 ccargc++;
239 break;
240 case 'R':
241 replymsg = EARGF(usage());
242 break;
243 case 's':
244 subject = EARGF(usage());
245 break;
246 case 'F':
247 Fflag = 1; /* file message */
248 break;
249 case 'r':
250 rflag = 1; /* for sendmail */
251 break;
252 case 'd':
253 dflag = 1; /* for sendmail */
254 break;
255 case '#':
256 lbflag = 1; /* for sendmail */
257 break;
258 case 'x':
259 xflag = 1; /* for sendmail */
260 break;
261 case 'n': /* no standard input */
262 nflag = 1;
263 break;
264 case '8': /* read recipients from rfc822 header */
265 eightflag = 1;
266 break;
267 case 'p': /* pgp flag: encrypt, sign, or both */
268 if(pgpopts(EARGF(usage())) < 0)
269 sysfatal("bad pgp options");
270 break;
271 default:
272 usage();
273 break;
274 }ARGEND;
276 login = getlog();
277 user = getenv("upasname");
278 if(user == nil || *user == 0)
279 user = login;
280 if(user == nil || *user == 0)
281 sysfatal("can't read user name");
283 if(Binit(&in, 0, OREAD) < 0)
284 sysfatal("can't Binit 0: %r");
286 if(nflag && eightflag)
287 sysfatal("can't use both -n and -8");
288 if(eightflag && argc >= 1)
289 usage();
290 else if(!eightflag && argc < 1)
291 usage();
293 aliases = readaliases();
294 if(!eightflag){
295 to = expand(argc, argv);
296 cc = expand(ccargc, ccargv);
297 } else {
298 to = nil;
299 cc = nil;
302 flags = 0;
303 headersrv = Nomessage;
304 if(!nflag && !xflag && !lbflag &&!dflag) {
305 /* pass through headers, keeping track of which we've seen, */
306 /* perhaps building to list. */
307 holding = holdon();
308 headersrv = readheaders(&in, &flags, &hdrstring, eightflag ? &to : nil, 1);
309 if(rfc822syntaxerror){
310 Bdrain(&in);
311 fatal("rfc822 syntax error, message not sent");
313 if(to == nil){
314 Bdrain(&in);
315 fatal("no addresses found, message not sent");
318 switch(headersrv){
319 case Error: /* error */
320 fatal("reading");
321 break;
322 case Nomessage: /* no message, just exit mimicking old behavior */
323 noinput = 1;
324 if(first == nil)
325 threadexitsall(0);
326 break;
330 fd = sendmail(to, cc, &pid, Fflag ? argv[0] : nil);
331 if(fd < 0)
332 sysfatal("execing sendmail: %r\n:");
333 if(xflag || lbflag || dflag){
334 close(fd);
335 threadexitsall(waitforsubprocs());
338 if(Binit(&out, fd, OWRITE) < 0)
339 fatal("can't Binit 1: %r");
341 if(!nflag){
342 if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring))
343 fatal("write error");
344 s_free(hdrstring);
345 hdrstring = nil;
347 /* read user's standard headers */
348 file = s_new();
349 mboxpath("headers", user, file, 0);
350 b = Bopen(s_to_c(file), OREAD);
351 if(b != nil){
352 switch(readheaders(b, &flags, &hdrstring, nil, 0)){
353 case Error: /* error */
354 fatal("reading");
356 Bterm(b);
357 if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring))
358 fatal("write error");
359 s_free(hdrstring);
360 hdrstring = nil;
364 /* add any headers we need */
365 if((flags & (1<<Hdate)) == 0)
366 if(printdate(&out) < 0)
367 fatal("writing");
368 if((flags & (1<<Hfrom)) == 0)
369 if(printfrom(&out) < 0)
370 fatal("writing");
371 if((flags & (1<<Hto)) == 0)
372 if(printto(&out, to) < 0)
373 fatal("writing");
374 if((flags & (1<<Hcc)) == 0)
375 if(printcc(&out, cc) < 0)
376 fatal("writing");
377 if((flags & (1<<Hsubject)) == 0 && subject != nil)
378 if(printsubject(&out, subject) < 0)
379 fatal("writing");
380 if(replymsg != nil)
381 printinreplyto(&out, replymsg); /* ignore errors */
382 Bprint(&out, "MIME-Version: 1.0\n");
384 if(pgpflag){ /* interpose pgp process between us and sendmail to handle body */
385 Bflush(&out);
386 Bterm(&out);
387 fd = pgpfilter(&pgppid, fd, pgpflag);
388 if(Binit(&out, fd, OWRITE) < 0)
389 fatal("can't Binit 1: %r");
392 /* if attachments, stick in multipart headers */
393 boundary = nil;
394 if(first != nil){
395 boundary = mkboundary();
396 Bprint(&out, "Content-Type: multipart/mixed;\n");
397 Bprint(&out, "\tboundary=\"%s\"\n\n", boundary);
398 Bprint(&out, "This is a multi-part message in MIME format.\n");
399 Bprint(&out, "--%s\n", boundary);
400 Bprint(&out, "Content-Disposition: inline\n");
403 if(!nflag){
404 if(!noinput && headersrv == Ok){
405 body(&in, &out, 1);
407 } else
408 Bprint(&out, "\n");
409 holdoff(holding);
411 Bflush(&out);
412 for(a = first; a != nil; a = a->next){
413 if(lastchar != '\n')
414 Bprint(&out, "\n");
415 Bprint(&out, "--%s\n", boundary);
416 attachment(a, &out);
419 if(first != nil){
420 if(lastchar != '\n')
421 Bprint(&out, "\n");
422 Bprint(&out, "--%s--\n", boundary);
425 Bterm(&out);
426 close(fd);
427 threadexitsall(waitforsubprocs());
430 /* evaluate pgp option string */
431 int
432 pgpopts(char *s)
434 if(s == nil || s[0] == '\0')
435 return -1;
436 while(*s){
437 switch(*s++){
438 case 's': case 'S':
439 pgpflag |= PGPsign;
440 break;
441 case 'e': case 'E':
442 pgpflag |= PGPencrypt;
443 break;
444 default:
445 return -1;
448 return 0;
451 /* read headers from stdin into a String, expanding local aliases, */
452 /* keep track of which headers are there, which addresses we have */
453 /* remove Bcc: line. */
454 int
455 readheaders(Biobuf *in, int *fp, String **sp, Addr **top, int strict)
457 Addr *to;
458 String *s, *sline;
459 char *p;
460 int i, seen, hdrtype;
462 s = s_new();
463 sline = nil;
464 to = nil;
465 hdrtype = -1;
466 seen = 0;
467 for(;;) {
468 if((p = Brdline(in, '\n')) != nil) {
469 seen = 1;
470 p[Blinelen(in)-1] = 0;
472 /* coalesce multiline headers */
473 if((*p == ' ' || *p == '\t') && sline){
474 s_append(sline, "\n");
475 s_append(sline, p);
476 p[Blinelen(in)-1] = '\n';
477 continue;
481 /* process the current header, it's all been read */
482 if(sline) {
483 assert(hdrtype != -1);
484 if(top){
485 switch(hdrtype){
486 case Hto:
487 case Hcc:
488 case Hbcc:
489 to = expandline(&sline, to);
490 break;
493 if(hdrtype == Hsubject){
494 s_append(s, mksubject(s_to_c(sline)));
495 s_append(s, "\n");
496 }else if(top==nil || hdrtype!=Hbcc){
497 s_append(s, s_to_c(sline));
498 s_append(s, "\n");
500 s_free(sline);
501 sline = nil;
504 if(p == nil)
505 break;
507 /* if no :, it's not a header, seek back and break */
508 if(strchr(p, ':') == nil){
509 p[Blinelen(in)-1] = '\n';
510 Bseek(in, -Blinelen(in), 1);
511 break;
514 sline = s_copy(p);
516 /* classify the header. If we don't recognize it, break. This is */
517 /* to take care of user's that start messages with lines that contain */
518 /* ':'s but that aren't headers. This is a bit hokey. Since I decided */
519 /* to let users type headers, I need some way to distinguish. Therefore, */
520 /* marshal tries to know all likely headers and will indeed screw up if */
521 /* the user types an unlikely one. -- presotto */
522 hdrtype = -1;
523 for(i = 0; i < nelem(hdrs); i++){
524 if(cistrncmp(hdrs[i], p, strlen(hdrs[i])) == 0){
525 *fp |= 1<<i;
526 hdrtype = i;
527 break;
530 if(strict){
531 if(hdrtype == -1){
532 p[Blinelen(in)-1] = '\n';
533 Bseek(in, -Blinelen(in), 1);
534 break;
536 } else
537 hdrtype = 0;
538 p[Blinelen(in)-1] = '\n';
541 *sp = s;
542 if(top)
543 *top = to;
545 if(seen == 0){
546 if(Blinelen(in) == 0)
547 return Nomessage;
548 else
549 return Ok;
551 if(p == nil)
552 return Nobody;
553 return Ok;
556 /* pass the body to sendmail, make sure body starts and ends with a newline */
557 void
558 body(Biobuf *in, Biobuf *out, int docontenttype)
560 char *buf, *p;
561 int i, n, len;
563 n = 0;
564 len = 16*1024;
565 buf = emalloc(len);
567 /* first char must be newline */
568 i = Bgetc(in);
569 if(i > 0){
570 if(i != '\n')
571 buf[n++] = '\n';
572 buf[n++] = i;
573 } else {
574 buf[n++] = '\n';
577 /* read into memory */
578 if(docontenttype){
579 while(docontenttype){
580 if(n == len){
581 len += len>>2;
582 buf = realloc(buf, len);
583 if(buf == nil)
584 sysfatal("%r");
586 p = buf+n;
587 i = Bread(in, p, len - n);
588 if(i < 0)
589 fatal("input error2");
590 if(i == 0)
591 break;
592 n += i;
593 for(; i > 0; i--)
594 if((*p++ & 0x80) && docontenttype){
595 Bprint(out, "Content-Type: text/plain; charset=\"UTF-8\"\n");
596 Bprint(out, "Content-Transfer-Encoding: 8bit\n");
597 docontenttype = 0;
598 break;
601 if(docontenttype){
602 Bprint(out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
603 Bprint(out, "Content-Transfer-Encoding: 7bit\n");
607 /* write what we already read */
608 if(Bwrite(out, buf, n) < 0)
609 fatal("output error");
610 if(n > 0)
611 lastchar = buf[n-1];
612 else
613 lastchar = '\n';
616 /* pass the rest */
617 for(;;){
618 n = Bread(in, buf, len);
619 if(n < 0)
620 fatal("input error2");
621 if(n == 0)
622 break;
623 if(Bwrite(out, buf, n) < 0)
624 fatal("output error");
625 lastchar = buf[n-1];
629 /* pass the body to sendmail encoding with base64 */
630 /* */
631 /* the size of buf is very important to enc64. Anything other than */
632 /* a multiple of 3 will cause enc64 to output a termination sequence. */
633 /* To ensure that a full buf corresponds to a multiple of complete lines, */
634 /* we make buf a multiple of 3*18 since that's how many enc64 sticks on */
635 /* a single line. This avoids short lines in the output which is pleasing */
636 /* but not necessary. */
637 /* */
638 void
639 body64(Biobuf *in, Biobuf *out)
641 uchar buf[3*18*54];
642 char obuf[3*18*54*2];
643 int m, n;
645 Bprint(out, "\n");
646 for(;;){
647 n = Bread(in, buf, sizeof(buf));
648 if(n < 0)
649 fatal("input error");
650 if(n == 0)
651 break;
652 m = enc64(obuf, sizeof(obuf), buf, n);
653 if((n=Bwrite(out, obuf, m)) < 0)
654 fatal("output error");
656 lastchar = '\n';
659 /* pass message to sendmail, make sure body starts with a newline */
660 void
661 copy(Biobuf *in, Biobuf *out)
663 char buf[4*1024];
664 int n;
666 for(;;){
667 n = Bread(in, buf, sizeof(buf));
668 if(n < 0)
669 fatal("input error");
670 if(n == 0)
671 break;
672 if(Bwrite(out, buf, n) < 0)
673 fatal("output error");
677 void
678 attachment(Attach *a, Biobuf *out)
680 Biobuf *f;
681 char *p;
683 f = emalloc(sizeof *f);
684 Binit(f, a->fd, OREAD);
685 /* if it's already mime encoded, just copy */
686 if(strcmp(a->type, "mime") == 0){
687 copy(f, out);
688 Bterm(f);
689 free(f);
690 return;
693 /* if it's not already mime encoded ... */
694 if(strcmp(a->type, "text/plain") != 0)
695 Bprint(out, "Content-Type: %s\n", a->type);
697 if(a->inline){
698 Bprint(out, "Content-Disposition: inline\n");
699 } else {
700 p = strrchr(a->path, '/');
701 if(p == nil)
702 p = a->path;
703 else
704 p++;
705 Bprint(out, "Content-Disposition: attachment; filename=%Z\n", p);
708 /* dump our local 'From ' line when passing along mail messages */
709 if(strcmp(a->type, "message/rfc822") == 0){
710 p = Brdline(f, '\n');
711 if(strncmp(p, "From ", 5) != 0)
712 Bseek(f, 0, 0);
714 if(a->ctype->display){
715 body(f, out, strcmp(a->type, "text/plain") == 0);
716 } else {
717 Bprint(out, "Content-Transfer-Encoding: base64\n");
718 body64(f, out);
720 Bterm(f);
721 free(f);
724 char *ascwday[] =
726 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
727 };
729 char *ascmon[] =
731 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
732 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
733 };
735 int
736 printdate(Biobuf *b)
738 Tm *tm;
739 int tz;
741 tm = localtime(time(0));
742 tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);
744 return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n",
745 ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900+tm->year,
746 tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz);
749 int
750 printfrom(Biobuf *b)
752 return Bprint(b, "From: %s\n", user);
755 int
756 printto(Biobuf *b, Addr *a)
758 int i;
760 if(Bprint(b, "To: %s", a->v) < 0)
761 return -1;
762 i = 0;
763 for(a = a->next; a != nil; a = a->next)
764 if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
765 return -1;
766 if(Bprint(b, "\n") < 0)
767 return -1;
768 return 0;
771 int
772 printcc(Biobuf *b, Addr *a)
774 int i;
776 if(a == nil)
777 return 0;
778 if(Bprint(b, "CC: %s", a->v) < 0)
779 return -1;
780 i = 0;
781 for(a = a->next; a != nil; a = a->next)
782 if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
783 return -1;
784 if(Bprint(b, "\n") < 0)
785 return -1;
786 return 0;
789 int
790 printsubject(Biobuf *b, char *subject)
792 return Bprint(b, "Subject: %s\n", subject);
795 int
796 printinreplyto(Biobuf *out, char *dir)
798 String *s;
799 char buf[256];
800 int fd;
801 int n;
803 if(mountmail() < 0)
804 return -1;
805 if(strncmp(dir, "Mail/", 5) != 0)
806 return -1;
807 s = s_copy(dir+5);
808 s_append(s, "/messageid");
809 fd = fsopenfd(mailfs, s_to_c(s), OREAD);
810 s_free(s);
811 if(fd < 0)
812 return -1;
813 n = readn(fd, buf, sizeof(buf)-1);
814 close(fd);
815 if(n <= 0)
816 return -1;
817 buf[n] = 0;
818 return Bprint(out, "In-Reply-To: %s\n", buf);
821 int
822 mopen(char *file, int mode)
824 int fd;
826 if((fd = open(file, mode)) >= 0)
827 return fd;
828 if(strncmp(file, "Mail/", 5) == 0 && mountmail() >= 0 && (fd = fsopenfd(mailfs, file+5, mode)) >= 0)
829 return fd;
830 return -1;
833 Attach*
834 mkattach(char *file, char *type, int inline)
836 Ctype *c;
837 Attach *a;
838 char ftype[64];
839 char *p;
840 int fd, n, pfd[2], xfd[3];
842 if(file == nil)
843 return nil;
844 if((fd = mopen(file, OREAD)) < 0)
845 return nil;
846 a = emalloc(sizeof(*a));
847 a->fd = fd;
848 a->path = file;
849 a->next = nil;
850 a->type = type;
851 a->inline = inline;
852 a->ctype = nil;
853 if(type != nil){
854 for(c = ctype; ; c++)
855 if(strncmp(type, c->type, strlen(c->type)) == 0){
856 a->ctype = c;
857 break;
859 return a;
862 /* pick a type depending on extension */
863 p = strchr(file, '.');
864 if(p != nil)
865 p++;
867 /* check the builtin extensions */
868 if(p != nil){
869 for(c = ctype; c->ext != nil; c++)
870 if(strcmp(p, c->ext) == 0){
871 a->type = c->type;
872 a->ctype = c;
873 return a;
877 /* try the mime types file */
878 if(p != nil){
879 if(mimetypes == nil)
880 readmimetypes();
881 for(c = mimetypes; c != nil && c->ext != nil; c++)
882 if(strcmp(p, c->ext) == 0){
883 a->type = c->type;
884 a->ctype = c;
885 return a;
889 /* run file to figure out the type */
890 a->type = "application/octet-stream"; /* safest default */
891 if(pipe(pfd) < 0)
892 return a;
894 xfd[0] = mopen(file, OREAD);
895 xfd[1] = pfd[0];
896 xfd[2] = dup(2, -1);
897 if((pid=threadspawnl(xfd, unsharp("#9/bin/file"), "file", "-m", nil)) < 0){
898 close(xfd[0]);
899 close(xfd[1]);
900 close(xfd[2]);
901 return a;
903 /* threadspawnl closed pfd[0] */
905 n = readn(pfd[1], ftype, sizeof(ftype));
906 if(n > 0){
907 ftype[n-1] = 0;
908 a->type = estrdup(ftype);
910 close(pfd[1]);
911 procwait(pid);
913 for(c = ctype; ; c++)
914 if(strncmp(a->type, c->type, strlen(c->type)) == 0){
915 a->ctype = c;
916 break;
919 return a;
922 char*
923 mkboundary(void)
925 char buf[32];
926 int i;
928 srand((time(0)<<16)|getpid());
929 strcpy(buf, "upas-");
930 for(i = 5; i < sizeof(buf)-1; i++)
931 buf[i] = 'a' + nrand(26);
932 buf[i] = 0;
933 return estrdup(buf);
936 /* copy types to two fd's */
937 static void
938 tee(int in, int out1, int out2)
940 char buf[8*1024];
941 int n;
943 for(;;){
944 n = read(in, buf, sizeof(buf));
945 if(n <= 0)
946 break;
947 if(write(out1, buf, n) < 0)
948 break;
949 if(write(out2, buf, n) < 0)
950 break;
954 static void
955 teeproc(void *v)
957 int *a;
959 a = v;
960 tee(a[0], a[1], a[2]);
961 write(a[2], "\n", 1);
964 /* print the unix from line */
965 int
966 printunixfrom(int fd)
968 Tm *tm;
969 int tz;
971 tm = localtime(time(0));
972 tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);
974 return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n",
975 user,
976 ascwday[tm->wday], ascmon[tm->mon], tm->mday,
977 tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900+tm->year);
980 char *specialfile[] =
982 "pipeto",
983 "pipefrom",
984 "L.mbox",
985 "forward",
986 "names"
987 };
989 /* return 1 if this is a special file */
990 static int
991 special(String *s)
993 char *p;
994 int i;
996 p = strrchr(s_to_c(s), '/');
997 if(p == nil)
998 p = s_to_c(s);
999 else
1000 p++;
1001 for(i = 0; i < nelem(specialfile); i++)
1002 if(strcmp(p, specialfile[i]) == 0)
1003 return 1;
1004 return 0;
1007 /* open the folder using the recipients account name */
1008 static int
1009 openfolder(char *rcvr)
1011 char *p;
1012 int c;
1013 String *file;
1014 Dir *d;
1015 int fd;
1016 int scarey;
1018 file = s_new();
1019 mboxpath("f", user, file, 0);
1021 /* if $mail/f exists, store there, otherwise in $mail */
1022 d = dirstat(s_to_c(file));
1023 if(d == nil || d->qid.type != QTDIR){
1024 scarey = 1;
1025 file->ptr -= 1;
1026 } else {
1027 s_putc(file, '/');
1028 scarey = 0;
1030 free(d);
1032 p = strrchr(rcvr, '!');
1033 if(p != nil)
1034 rcvr = p+1;
1036 while(*rcvr && *rcvr != '@'){
1037 c = *rcvr++;
1038 if(c == '/')
1039 c = '_';
1040 s_putc(file, c);
1042 s_terminate(file);
1044 if(scarey && special(file)){
1045 fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file));
1046 s_free(file);
1047 return -1;
1050 fd = open(s_to_c(file), OWRITE);
1051 if(fd < 0)
1052 fd = create(s_to_c(file), OWRITE, 0660);
1054 s_free(file);
1055 return fd;
1058 /* start up sendmail and return an fd to talk to it with */
1059 int
1060 sendmail(Addr *to, Addr *cc, int *pid, char *rcvr)
1062 char **av, **v;
1063 int ac, fd, *targ;
1064 int pfd[2], sfd, xfd[3];
1065 String *cmd;
1066 char *x;
1067 Addr *a;
1069 fd = -1;
1070 if(rcvr != nil)
1071 fd = openfolder(rcvr);
1073 ac = 0;
1074 for(a = to; a != nil; a = a->next)
1075 ac++;
1076 for(a = cc; a != nil; a = a->next)
1077 ac++;
1078 v = av = emalloc(sizeof(char*)*(ac+20));
1079 ac = 0;
1080 v[ac++] = "sendmail";
1081 if(xflag)
1082 v[ac++] = "-x";
1083 if(rflag)
1084 v[ac++] = "-r";
1085 if(lbflag)
1086 v[ac++] = "-#";
1087 if(dflag)
1088 v[ac++] = "-d";
1089 for(a = to; a != nil; a = a->next)
1090 v[ac++] = a->v;
1091 for(a = cc; a != nil; a = a->next)
1092 v[ac++] = a->v;
1093 v[ac] = 0;
1095 if(pipe(pfd) < 0)
1096 fatal("pipe: %r");
1098 xfd[0] = pfd[0];
1099 xfd[1] = dup(1, -1);
1100 xfd[2] = dup(2, -1);
1102 if(replymsg != nil)
1103 putenv("replymsg", replymsg);
1104 cmd = mboxpath("pipefrom", login, s_new(), 0);
1106 if((*pid = threadspawn(xfd, x=s_to_c(cmd), av)) < 0
1107 && (*pid = threadspawn(xfd, x="myupassend", av)) < 0
1108 && (*pid = threadspawn(xfd, x=unsharp("#9/bin/upas/send"), av)) < 0)
1109 fatal("exec: %r");
1110 /* threadspawn closed pfd[0] (== xfd[0]) */
1111 sfd = pfd[1];
1113 if(rcvr != nil){
1114 if(pipe(pfd) < 0)
1115 fatal("pipe: %r");
1116 seek(fd, 0, 2);
1117 printunixfrom(fd);
1118 targ = emalloc(3*sizeof targ[0]);
1119 targ[0] = sfd;
1120 targ[1] = pfd[0];
1121 targ[2] = fd;
1122 proccreate(teeproc, targ, STACK);
1123 sfd = pfd[1];
1126 return sfd;
1129 /* start up pgp process and return an fd to talk to it with. */
1130 /* its standard output will be the original fd, which goes to sendmail. */
1131 int
1132 pgpfilter(int *pid, int fd, int pgpflag)
1134 char **av, **v;
1135 int ac;
1136 int pfd[2];
1138 v = av = emalloc(sizeof(char*)*8);
1139 ac = 0;
1140 v[ac++] = "pgp";
1141 v[ac++] = "-fat"; /* operate as a filter, generate text */
1142 if(pgpflag & PGPsign)
1143 v[ac++] = "-s";
1144 if(pgpflag & PGPencrypt)
1145 v[ac++] = "-e";
1146 v[ac] = 0;
1148 if(pipe(pfd) < 0)
1149 fatal("%r");
1150 switch(*pid = fork()){
1151 case -1:
1152 fatal("%r");
1153 break;
1154 case 0:
1155 close(pfd[1]);
1156 dup(pfd[0], 0);
1157 close(pfd[0]);
1158 dup(fd, 1);
1159 close(fd);
1160 /* add newline to avoid confusing pgp output with 822 headers */
1161 write(1, "\n", 1);
1163 exec("pgp", av);
1164 fatal("execing: %r");
1165 break;
1166 default:
1167 close(pfd[0]);
1168 break;
1170 close(fd);
1171 return pfd[1];
1174 /* wait for sendmail and pgp to exit; exit here if either failed */
1175 char*
1176 waitforsubprocs(void)
1178 Waitmsg *w;
1179 char *err;
1181 err = nil;
1182 if(pgppid >= 0 && (w=procwait(pgppid)) && w->msg[0])
1183 err = w->msg;
1184 if(pid >= 0 && (w=procwait(pid)) && w->msg[0])
1185 err = w->msg;
1186 return nil;
1189 int
1190 cistrncmp(char *a, char *b, int n)
1192 while(n-- > 0){
1193 if(tolower(*a++) != tolower(*b++))
1194 return -1;
1196 return 0;
1199 int
1200 cistrcmp(char *a, char *b)
1202 for(;;){
1203 if(tolower(*a) != tolower(*b++))
1204 return -1;
1205 if(*a++ == 0)
1206 break;
1208 return 0;
1211 static uchar t64d[256];
1212 static char t64e[64];
1214 static void
1215 init64(void)
1217 int c, i;
1219 memset(t64d, 255, 256);
1220 memset(t64e, '=', 64);
1221 i = 0;
1222 for(c = 'A'; c <= 'Z'; c++){
1223 t64e[i] = c;
1224 t64d[c] = i++;
1226 for(c = 'a'; c <= 'z'; c++){
1227 t64e[i] = c;
1228 t64d[c] = i++;
1230 for(c = '0'; c <= '9'; c++){
1231 t64e[i] = c;
1232 t64d[c] = i++;
1234 t64e[i] = '+';
1235 t64d['+'] = i++;
1236 t64e[i] = '/';
1237 t64d['/'] = i;
1240 int
1241 enc64(char *out, int lim, uchar *in, int n)
1243 int i;
1244 ulong b24;
1245 char *start = out;
1246 char *e = out + lim;
1248 if(t64e[0] == 0)
1249 init64();
1250 for(i = 0; i < n/3; i++){
1251 b24 = (*in++)<<16;
1252 b24 |= (*in++)<<8;
1253 b24 |= *in++;
1254 if(out + 5 >= e)
1255 goto exhausted;
1256 *out++ = t64e[(b24>>18)];
1257 *out++ = t64e[(b24>>12)&0x3f];
1258 *out++ = t64e[(b24>>6)&0x3f];
1259 *out++ = t64e[(b24)&0x3f];
1260 if((i%18) == 17)
1261 *out++ = '\n';
1264 switch(n%3){
1265 case 2:
1266 b24 = (*in++)<<16;
1267 b24 |= (*in)<<8;
1268 if(out + 4 >= e)
1269 goto exhausted;
1270 *out++ = t64e[(b24>>18)];
1271 *out++ = t64e[(b24>>12)&0x3f];
1272 *out++ = t64e[(b24>>6)&0x3f];
1273 break;
1274 case 1:
1275 b24 = (*in)<<16;
1276 if(out + 4 >= e)
1277 goto exhausted;
1278 *out++ = t64e[(b24>>18)];
1279 *out++ = t64e[(b24>>12)&0x3f];
1280 *out++ = '=';
1281 break;
1282 case 0:
1283 if((i%18) != 0)
1284 *out++ = '\n';
1285 *out = 0;
1286 return out - start;
1288 exhausted:
1289 *out++ = '=';
1290 *out++ = '\n';
1291 *out = 0;
1292 return out - start;
1295 void
1296 freealias(Alias *a)
1298 freeaddrs(a->addr);
1299 free(a);
1302 void
1303 freealiases(Alias *a)
1305 Alias *next;
1307 while(a != nil){
1308 next = a->next;
1309 freealias(a);
1310 a = next;
1314 /* */
1315 /* read alias file */
1316 /* */
1317 Alias*
1318 readaliases(void)
1320 Alias *a, **l, *first;
1321 Addr *addr, **al;
1322 String *file, *line, *token;
1323 Sinstack *sp;
1325 first = nil;
1326 file = s_new();
1327 line = s_new();
1328 token = s_new();
1330 /* open and get length */
1331 mboxpath("names", login, file, 0);
1332 sp = s_allocinstack(s_to_c(file));
1333 if(sp == nil)
1334 goto out;
1336 l = &first;
1338 /* read a line at a time. */
1339 while(s_rdinstack(sp, s_restart(line))!=nil) {
1340 s_restart(line);
1341 a = emalloc(sizeof(Alias));
1342 al = &a->addr;
1343 for(;;){
1344 if(s_parse(line, s_restart(token))==0)
1345 break;
1346 addr = emalloc(sizeof(Addr));
1347 addr->v = strdup(s_to_c(token));
1348 addr->next = 0;
1349 *al = addr;
1350 al = &addr->next;
1352 if(a->addr == nil || a->addr->next == nil){
1353 freealias(a);
1354 continue;
1356 a->next = nil;
1357 *l = a;
1358 l = &a->next;
1360 s_freeinstack(sp);
1362 out:
1363 s_free(file);
1364 s_free(line);
1365 s_free(token);
1366 return first;
1369 Addr*
1370 newaddr(char *name)
1372 Addr *a;
1374 a = emalloc(sizeof(*a));
1375 a->next = nil;
1376 a->v = estrdup(name);
1377 if(a->v == nil)
1378 sysfatal("%r");
1379 return a;
1382 /* */
1383 /* expand personal aliases since the names are meaningless in */
1384 /* other contexts */
1385 /* */
1386 Addr*
1387 _expand(Addr *old, int *changedp)
1389 Alias *al;
1390 Addr *first, *next, **l, *a;
1392 *changedp = 0;
1393 first = nil;
1394 l = &first;
1395 for(;old != nil; old = next){
1396 next = old->next;
1397 for(al = aliases; al != nil; al = al->next){
1398 if(strcmp(al->addr->v, old->v) == 0){
1399 for(a = al->addr->next; a != nil; a = a->next){
1400 *l = newaddr(a->v);
1401 if(*l == nil)
1402 sysfatal("%r");
1403 l = &(*l)->next;
1404 *changedp = 1;
1406 break;
1409 if(al != nil){
1410 freeaddr(old);
1411 continue;
1413 *l = old;
1414 old->next = nil;
1415 l = &(*l)->next;
1417 return first;
1420 Addr*
1421 rexpand(Addr *old)
1423 int i, changed;
1425 changed = 0;
1426 for(i=0; i<32; i++){
1427 old = _expand(old, &changed);
1428 if(changed == 0)
1429 break;
1431 return old;
1434 Addr*
1435 unique(Addr *first)
1437 Addr *a, **l, *x;
1439 for(a = first; a != nil; a = a->next){
1440 for(l = &a->next; *l != nil;){
1441 if(strcmp(a->v, (*l)->v) == 0){
1442 x = *l;
1443 *l = x->next;
1444 freeaddr(x);
1445 } else
1446 l = &(*l)->next;
1449 return first;
1452 Addr*
1453 expand(int ac, char **av)
1455 Addr *first, **l;
1456 int i;
1458 first = nil;
1460 /* make a list of the starting addresses */
1461 l = &first;
1462 for(i = 0; i < ac; i++){
1463 *l = newaddr(av[i]);
1464 if(*l == nil)
1465 sysfatal("%r");
1466 l = &(*l)->next;
1469 /* recurse till we don't change any more */
1470 return unique(rexpand(first));
1473 Addr*
1474 concataddr(Addr *a, Addr *b)
1476 Addr *oa;
1478 if(a == nil)
1479 return b;
1481 oa = a;
1482 for(; a->next; a=a->next)
1484 a->next = b;
1485 return oa;
1488 void
1489 freeaddr(Addr *ap)
1491 free(ap->v);
1492 free(ap);
1495 void
1496 freeaddrs(Addr *ap)
1498 Addr *next;
1500 for(; ap; ap=next) {
1501 next = ap->next;
1502 freeaddr(ap);
1506 String*
1507 s_copyn(char *s, int n)
1509 return s_nappend(s_reset(nil), s, n);
1512 /* fetch the next token from an RFC822 address string */
1513 /* we assume the header is RFC822-conformant in that */
1514 /* we recognize escaping anywhere even though it is only */
1515 /* supposed to be in quoted-strings, domain-literals, and comments. */
1516 /* */
1517 /* i'd use yylex or yyparse here, but we need to preserve */
1518 /* things like comments, which i think it tosses away. */
1519 /* */
1520 /* we're not strictly RFC822 compliant. we misparse such nonsense as */
1521 /* */
1522 /* To: gre @ (Grace) plan9 . (Emlin) bell-labs.com */
1523 /* */
1524 /* make sure there's no whitespace in your addresses and */
1525 /* you'll be fine. */
1526 /* */
1527 enum {
1528 Twhite,
1529 Tcomment,
1530 Twords,
1531 Tcomma,
1532 Tleftangle,
1533 Trightangle,
1534 Terror,
1535 Tend
1537 /*char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"}; */
1538 #define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r')
1539 int
1540 get822token(String **tok, char *p, char **pp)
1542 char *op;
1543 int type;
1544 int quoting;
1546 op = p;
1547 switch(*p){
1548 case '\0':
1549 *tok = nil;
1550 *pp = nil;
1551 return Tend;
1553 case ' ': /* get whitespace */
1554 case '\t':
1555 case '\n':
1556 case '\r':
1557 type = Twhite;
1558 while(ISWHITE(*p))
1559 p++;
1560 break;
1562 case '(': /* get comment */
1563 type = Tcomment;
1564 for(p++; *p && *p != ')'; p++)
1565 if(*p == '\\') {
1566 if(*(p+1) == '\0') {
1567 *tok = nil;
1568 return Terror;
1570 p++;
1573 if(*p != ')') {
1574 *tok = nil;
1575 return Terror;
1577 p++;
1578 break;
1579 case ',':
1580 type = Tcomma;
1581 p++;
1582 break;
1583 case '<':
1584 type = Tleftangle;
1585 p++;
1586 break;
1587 case '>':
1588 type = Trightangle;
1589 p++;
1590 break;
1591 default: /* bunch of letters, perhaps quoted strings tossed in */
1592 type = Twords;
1593 quoting = 0;
1594 for(; *p && (quoting || (!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) {
1595 if(*p == '"')
1596 quoting = !quoting;
1597 if(*p == '\\') {
1598 if(*(p+1) == '\0') {
1599 *tok = nil;
1600 return Terror;
1602 p++;
1605 break;
1608 if(pp)
1609 *pp = p;
1610 *tok = s_copyn(op, p-op);
1611 return type;
1614 /* expand local aliases in an RFC822 mail line */
1615 /* add list of expanded addresses to to. */
1616 Addr*
1617 expandline(String **s, Addr *to)
1619 Addr *na, *nto, *ap;
1620 char *p;
1621 int tok, inangle, hadangle, nword;
1622 String *os, *ns, *stok, *lastword, *sinceword;
1624 os = s_copy(s_to_c(*s));
1625 p = strchr(s_to_c(*s), ':');
1626 assert(p != nil);
1627 p++;
1629 ns = s_copyn(s_to_c(*s), p-s_to_c(*s));
1630 stok = nil;
1631 nto = nil;
1632 /* */
1633 /* the only valid mailbox namings are word */
1634 /* and word* < addr > */
1635 /* without comments this would be simple. */
1636 /* we keep the following: */
1637 /* lastword - current guess at the address */
1638 /* sinceword - whitespace and comment seen since lastword */
1639 /* */
1640 lastword = s_new();
1641 sinceword = s_new();
1642 inangle = 0;
1643 nword = 0;
1644 hadangle = 0;
1645 for(;;) {
1646 stok = nil;
1647 switch(tok = get822token(&stok, p, &p)){
1648 default:
1649 abort();
1650 case Tcomma:
1651 case Tend:
1652 if(inangle)
1653 goto Error;
1654 if(nword != 1)
1655 goto Error;
1656 na = rexpand(newaddr(s_to_c(lastword)));
1657 s_append(ns, na->v);
1658 s_append(ns, s_to_c(sinceword));
1659 for(ap=na->next; ap; ap=ap->next) {
1660 s_append(ns, ", ");
1661 s_append(ns, ap->v);
1663 nto = concataddr(na, nto);
1664 if(tok == Tcomma){
1665 s_append(ns, ",");
1666 s_free(stok);
1668 if(tok == Tend)
1669 goto Break2;
1670 inangle = 0;
1671 nword = 0;
1672 hadangle = 0;
1673 s_reset(sinceword);
1674 s_reset(lastword);
1675 break;
1676 case Twhite:
1677 case Tcomment:
1678 s_append(sinceword, s_to_c(stok));
1679 s_free(stok);
1680 break;
1681 case Trightangle:
1682 if(!inangle)
1683 goto Error;
1684 inangle = 0;
1685 hadangle = 1;
1686 s_append(sinceword, s_to_c(stok));
1687 s_free(stok);
1688 break;
1689 case Twords:
1690 case Tleftangle:
1691 if(hadangle)
1692 goto Error;
1693 if(tok != Tleftangle && inangle && s_len(lastword))
1694 goto Error;
1695 if(tok == Tleftangle) {
1696 inangle = 1;
1697 nword = 1;
1699 s_append(ns, s_to_c(lastword));
1700 s_append(ns, s_to_c(sinceword));
1701 s_reset(sinceword);
1702 if(tok == Tleftangle) {
1703 s_append(ns, "<");
1704 s_reset(lastword);
1705 } else {
1706 s_free(lastword);
1707 lastword = stok;
1709 if(!inangle)
1710 nword++;
1711 break;
1712 case Terror: /* give up, use old string, addrs */
1713 Error:
1714 ns = os;
1715 os = nil;
1716 freeaddrs(nto);
1717 nto = nil;
1718 werrstr("rfc822 syntax error");
1719 rfc822syntaxerror = 1;
1720 goto Break2;
1723 Break2:
1724 s_free(*s);
1725 s_free(os);
1726 *s = ns;
1727 nto = concataddr(nto, to);
1728 return nto;
1731 void
1732 Bdrain(Biobuf *b)
1734 char buf[8192];
1736 while(Bread(b, buf, sizeof buf) > 0)
1740 void
1741 readmimetypes(void)
1743 Biobuf *b;
1744 char *p;
1745 char *f[6];
1746 char type[256];
1747 static int alloced, inuse;
1749 if(mimetypes == 0){
1750 alloced = 256;
1751 mimetypes = emalloc(alloced*sizeof(Ctype));
1752 mimetypes[0].ext = "";
1755 b = Bopen(unsharp("#9/lib/mimetype"), OREAD);
1756 if(b == nil)
1757 return;
1758 for(;;){
1759 p = Brdline(b, '\n');
1760 if(p == nil)
1761 break;
1762 p[Blinelen(b)-1] = 0;
1763 if(tokenize(p, f, 6) < 4)
1764 continue;
1765 if(strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 || strcmp(f[2], "-") == 0)
1766 continue;
1767 if(inuse + 1 >= alloced){
1768 alloced += 256;
1769 mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype));
1771 snprint(type, sizeof(type), "%s/%s", f[1], f[2]);
1772 mimetypes[inuse].type = estrdup(type);
1773 mimetypes[inuse].ext = estrdup(f[0]+1);
1774 mimetypes[inuse].display = !strcmp(type, "text/plain");
1775 inuse++;
1777 /* always make sure there's a terminator */
1778 mimetypes[inuse].ext = 0;
1780 Bterm(b);
1783 char*
1784 estrdup(char *x)
1786 x = strdup(x);
1787 if(x == nil)
1788 fatal("memory");
1789 return x;
1792 void*
1793 emalloc(int n)
1795 void *x;
1797 x = malloc(n);
1798 if(x == nil)
1799 fatal("%r");
1800 return x;
1803 void*
1804 erealloc(void *x, int n)
1806 x = realloc(x, n);
1807 if(x == nil)
1808 fatal("%r");
1809 return x;
1812 /* */
1813 /* Formatter for %" */
1814 /* Use double quotes to protect white space, frogs, \ and " */
1815 /* */
1816 enum
1818 Qok = 0,
1819 Qquote,
1820 Qbackslash
1823 static int
1824 needtoquote(Rune r)
1826 if(r >= Runeself)
1827 return Qquote;
1828 if(r <= ' ')
1829 return Qquote;
1830 if(r=='\\' || r=='"')
1831 return Qbackslash;
1832 return Qok;
1835 int
1836 doublequote(Fmt *f)
1838 char *s, *t;
1839 int w, quotes;
1840 Rune r;
1842 s = va_arg(f->args, char*);
1843 if(s == nil || *s == '\0')
1844 return fmtstrcpy(f, "\"\"");
1846 quotes = 0;
1847 for(t=s; *t; t+=w){
1848 w = chartorune(&r, t);
1849 quotes |= needtoquote(r);
1851 if(quotes == 0)
1852 return fmtstrcpy(f, s);
1854 fmtrune(f, '"');
1855 for(t=s; *t; t+=w){
1856 w = chartorune(&r, t);
1857 if(needtoquote(r) == Qbackslash)
1858 fmtrune(f, '\\');
1859 fmtrune(f, r);
1861 return fmtrune(f, '"');
1864 int
1865 mountmail(void)
1867 if(mailfs != nil)
1868 return 0;
1869 if((mailfs = nsmount("mail", nil)) == nil)
1870 return -1;
1871 return 0;
1874 int
1875 rfc2047fmt(Fmt *fmt)
1877 char *s, *p;
1879 s = va_arg(fmt->args, char*);
1880 if(s == nil)
1881 return fmtstrcpy(fmt, "");
1882 for(p=s; *p; p++)
1883 if((uchar)*p >= 0x80)
1884 goto hard;
1885 return fmtstrcpy(fmt, s);
1887 hard:
1888 fmtprint(fmt, "=?utf-8?q?");
1889 for(p=s; *p; p++){
1890 if(*p == ' ')
1891 fmtrune(fmt, '_');
1892 else if(*p == '_' || *p == '\t' || *p == '=' || *p == '?' || (uchar)*p >= 0x80)
1893 fmtprint(fmt, "=%.2uX", (uchar)*p);
1894 else
1895 fmtrune(fmt, (uchar)*p);
1897 fmtprint(fmt, "?=");
1898 return 0;
1901 char*
1902 mksubject(char *line)
1904 char *p, *q;
1905 static char buf[1024];
1907 p = strchr(line, ':')+1;
1908 while(*p == ' ')
1909 p++;
1910 for(q=p; *q; q++)
1911 if((uchar)*q >= 0x80)
1912 goto hard;
1913 return line;
1915 hard:
1916 snprint(buf, sizeof buf, "Subject: %U", p);
1917 return buf;