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 fprint(2, "got type %s\n", a->type);
911 close(pfd[1]);
912 procwait(pid);
914 for(c = ctype; ; c++)
915 if(strncmp(a->type, c->type, strlen(c->type)) == 0){
916 a->ctype = c;
917 break;
920 return a;
923 char*
924 mkboundary(void)
926 char buf[32];
927 int i;
929 srand((time(0)<<16)|getpid());
930 strcpy(buf, "upas-");
931 for(i = 5; i < sizeof(buf)-1; i++)
932 buf[i] = 'a' + nrand(26);
933 buf[i] = 0;
934 return estrdup(buf);
937 /* copy types to two fd's */
938 static void
939 tee(int in, int out1, int out2)
941 char buf[8*1024];
942 int n;
944 for(;;){
945 n = read(in, buf, sizeof(buf));
946 if(n <= 0)
947 break;
948 if(write(out1, buf, n) < 0)
949 break;
950 if(write(out2, buf, n) < 0)
951 break;
955 static void
956 teeproc(void *v)
958 int *a;
960 a = v;
961 tee(a[0], a[1], a[2]);
962 write(a[2], "\n", 1);
965 /* print the unix from line */
966 int
967 printunixfrom(int fd)
969 Tm *tm;
970 int tz;
972 tm = localtime(time(0));
973 tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);
975 return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n",
976 user,
977 ascwday[tm->wday], ascmon[tm->mon], tm->mday,
978 tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900+tm->year);
981 char *specialfile[] =
983 "pipeto",
984 "pipefrom",
985 "L.mbox",
986 "forward",
987 "names"
988 };
990 /* return 1 if this is a special file */
991 static int
992 special(String *s)
994 char *p;
995 int i;
997 p = strrchr(s_to_c(s), '/');
998 if(p == nil)
999 p = s_to_c(s);
1000 else
1001 p++;
1002 for(i = 0; i < nelem(specialfile); i++)
1003 if(strcmp(p, specialfile[i]) == 0)
1004 return 1;
1005 return 0;
1008 /* open the folder using the recipients account name */
1009 static int
1010 openfolder(char *rcvr)
1012 char *p;
1013 int c;
1014 String *file;
1015 Dir *d;
1016 int fd;
1017 int scarey;
1019 file = s_new();
1020 mboxpath("f", user, file, 0);
1022 /* if $mail/f exists, store there, otherwise in $mail */
1023 d = dirstat(s_to_c(file));
1024 if(d == nil || d->qid.type != QTDIR){
1025 scarey = 1;
1026 file->ptr -= 1;
1027 } else {
1028 s_putc(file, '/');
1029 scarey = 0;
1031 free(d);
1033 p = strrchr(rcvr, '!');
1034 if(p != nil)
1035 rcvr = p+1;
1037 while(*rcvr && *rcvr != '@'){
1038 c = *rcvr++;
1039 if(c == '/')
1040 c = '_';
1041 s_putc(file, c);
1043 s_terminate(file);
1045 if(scarey && special(file)){
1046 fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file));
1047 s_free(file);
1048 return -1;
1051 fd = open(s_to_c(file), OWRITE);
1052 if(fd < 0)
1053 fd = create(s_to_c(file), OWRITE, 0660);
1055 s_free(file);
1056 return fd;
1059 /* start up sendmail and return an fd to talk to it with */
1060 int
1061 sendmail(Addr *to, Addr *cc, int *pid, char *rcvr)
1063 char **av, **v;
1064 int ac, fd, *targ;
1065 int pfd[2], sfd, xfd[3];
1066 String *cmd;
1067 char *x;
1068 Addr *a;
1070 fd = -1;
1071 if(rcvr != nil)
1072 fd = openfolder(rcvr);
1074 ac = 0;
1075 for(a = to; a != nil; a = a->next)
1076 ac++;
1077 for(a = cc; a != nil; a = a->next)
1078 ac++;
1079 v = av = emalloc(sizeof(char*)*(ac+20));
1080 ac = 0;
1081 v[ac++] = "sendmail";
1082 if(xflag)
1083 v[ac++] = "-x";
1084 if(rflag)
1085 v[ac++] = "-r";
1086 if(lbflag)
1087 v[ac++] = "-#";
1088 if(dflag)
1089 v[ac++] = "-d";
1090 for(a = to; a != nil; a = a->next)
1091 v[ac++] = a->v;
1092 for(a = cc; a != nil; a = a->next)
1093 v[ac++] = a->v;
1094 v[ac] = 0;
1096 if(pipe(pfd) < 0)
1097 fatal("pipe: %r");
1099 xfd[0] = pfd[0];
1100 xfd[1] = dup(1, -1);
1101 xfd[2] = dup(2, -1);
1103 if(replymsg != nil)
1104 putenv("replymsg", replymsg);
1105 cmd = mboxpath("pipefrom", login, s_new(), 0);
1107 if((*pid = threadspawn(xfd, x=s_to_c(cmd), av)) < 0
1108 && (*pid = threadspawn(xfd, x="myupassend", av)) < 0
1109 && (*pid = threadspawn(xfd, x=unsharp("#9/bin/upas/send"), av)) < 0)
1110 fatal("exec: %r");
1111 /* threadspawn closed pfd[0] (== xfd[0]) */
1112 sfd = pfd[1];
1114 if(rcvr != nil){
1115 if(pipe(pfd) < 0)
1116 fatal("pipe: %r");
1117 seek(fd, 0, 2);
1118 printunixfrom(fd);
1119 targ = emalloc(3*sizeof targ[0]);
1120 targ[0] = sfd;
1121 targ[1] = pfd[0];
1122 targ[2] = fd;
1123 proccreate(teeproc, targ, STACK);
1124 sfd = pfd[1];
1127 return sfd;
1130 /* start up pgp process and return an fd to talk to it with. */
1131 /* its standard output will be the original fd, which goes to sendmail. */
1132 int
1133 pgpfilter(int *pid, int fd, int pgpflag)
1135 char **av, **v;
1136 int ac;
1137 int pfd[2];
1139 v = av = emalloc(sizeof(char*)*8);
1140 ac = 0;
1141 v[ac++] = "pgp";
1142 v[ac++] = "-fat"; /* operate as a filter, generate text */
1143 if(pgpflag & PGPsign)
1144 v[ac++] = "-s";
1145 if(pgpflag & PGPencrypt)
1146 v[ac++] = "-e";
1147 v[ac] = 0;
1149 if(pipe(pfd) < 0)
1150 fatal("%r");
1151 switch(*pid = fork()){
1152 case -1:
1153 fatal("%r");
1154 break;
1155 case 0:
1156 close(pfd[1]);
1157 dup(pfd[0], 0);
1158 close(pfd[0]);
1159 dup(fd, 1);
1160 close(fd);
1161 /* add newline to avoid confusing pgp output with 822 headers */
1162 write(1, "\n", 1);
1164 exec("pgp", av);
1165 fatal("execing: %r");
1166 break;
1167 default:
1168 close(pfd[0]);
1169 break;
1171 close(fd);
1172 return pfd[1];
1175 /* wait for sendmail and pgp to exit; exit here if either failed */
1176 char*
1177 waitforsubprocs(void)
1179 Waitmsg *w;
1180 char *err;
1182 err = nil;
1183 if(pgppid >= 0 && (w=procwait(pgppid)) && w->msg[0])
1184 err = w->msg;
1185 if(pid >= 0 && (w=procwait(pid)) && w->msg[0])
1186 err = w->msg;
1187 return nil;
1190 int
1191 cistrncmp(char *a, char *b, int n)
1193 while(n-- > 0){
1194 if(tolower(*a++) != tolower(*b++))
1195 return -1;
1197 return 0;
1200 int
1201 cistrcmp(char *a, char *b)
1203 for(;;){
1204 if(tolower(*a) != tolower(*b++))
1205 return -1;
1206 if(*a++ == 0)
1207 break;
1209 return 0;
1212 static uchar t64d[256];
1213 static char t64e[64];
1215 static void
1216 init64(void)
1218 int c, i;
1220 memset(t64d, 255, 256);
1221 memset(t64e, '=', 64);
1222 i = 0;
1223 for(c = 'A'; c <= 'Z'; c++){
1224 t64e[i] = c;
1225 t64d[c] = i++;
1227 for(c = 'a'; c <= 'z'; c++){
1228 t64e[i] = c;
1229 t64d[c] = i++;
1231 for(c = '0'; c <= '9'; c++){
1232 t64e[i] = c;
1233 t64d[c] = i++;
1235 t64e[i] = '+';
1236 t64d['+'] = i++;
1237 t64e[i] = '/';
1238 t64d['/'] = i;
1241 int
1242 enc64(char *out, int lim, uchar *in, int n)
1244 int i;
1245 ulong b24;
1246 char *start = out;
1247 char *e = out + lim;
1249 if(t64e[0] == 0)
1250 init64();
1251 for(i = 0; i < n/3; i++){
1252 b24 = (*in++)<<16;
1253 b24 |= (*in++)<<8;
1254 b24 |= *in++;
1255 if(out + 5 >= e)
1256 goto exhausted;
1257 *out++ = t64e[(b24>>18)];
1258 *out++ = t64e[(b24>>12)&0x3f];
1259 *out++ = t64e[(b24>>6)&0x3f];
1260 *out++ = t64e[(b24)&0x3f];
1261 if((i%18) == 17)
1262 *out++ = '\n';
1265 switch(n%3){
1266 case 2:
1267 b24 = (*in++)<<16;
1268 b24 |= (*in)<<8;
1269 if(out + 4 >= e)
1270 goto exhausted;
1271 *out++ = t64e[(b24>>18)];
1272 *out++ = t64e[(b24>>12)&0x3f];
1273 *out++ = t64e[(b24>>6)&0x3f];
1274 break;
1275 case 1:
1276 b24 = (*in)<<16;
1277 if(out + 4 >= e)
1278 goto exhausted;
1279 *out++ = t64e[(b24>>18)];
1280 *out++ = t64e[(b24>>12)&0x3f];
1281 *out++ = '=';
1282 break;
1283 case 0:
1284 if((i%18) != 0)
1285 *out++ = '\n';
1286 *out = 0;
1287 return out - start;
1289 exhausted:
1290 *out++ = '=';
1291 *out++ = '\n';
1292 *out = 0;
1293 return out - start;
1296 void
1297 freealias(Alias *a)
1299 freeaddrs(a->addr);
1300 free(a);
1303 void
1304 freealiases(Alias *a)
1306 Alias *next;
1308 while(a != nil){
1309 next = a->next;
1310 freealias(a);
1311 a = next;
1315 /* */
1316 /* read alias file */
1317 /* */
1318 Alias*
1319 readaliases(void)
1321 Alias *a, **l, *first;
1322 Addr *addr, **al;
1323 String *file, *line, *token;
1324 Sinstack *sp;
1326 first = nil;
1327 file = s_new();
1328 line = s_new();
1329 token = s_new();
1331 /* open and get length */
1332 mboxpath("names", login, file, 0);
1333 sp = s_allocinstack(s_to_c(file));
1334 if(sp == nil)
1335 goto out;
1337 l = &first;
1339 /* read a line at a time. */
1340 while(s_rdinstack(sp, s_restart(line))!=nil) {
1341 s_restart(line);
1342 a = emalloc(sizeof(Alias));
1343 al = &a->addr;
1344 for(;;){
1345 if(s_parse(line, s_restart(token))==0)
1346 break;
1347 addr = emalloc(sizeof(Addr));
1348 addr->v = strdup(s_to_c(token));
1349 addr->next = 0;
1350 *al = addr;
1351 al = &addr->next;
1353 if(a->addr == nil || a->addr->next == nil){
1354 freealias(a);
1355 continue;
1357 a->next = nil;
1358 *l = a;
1359 l = &a->next;
1361 s_freeinstack(sp);
1363 out:
1364 s_free(file);
1365 s_free(line);
1366 s_free(token);
1367 return first;
1370 Addr*
1371 newaddr(char *name)
1373 Addr *a;
1375 a = emalloc(sizeof(*a));
1376 a->next = nil;
1377 a->v = estrdup(name);
1378 if(a->v == nil)
1379 sysfatal("%r");
1380 return a;
1383 /* */
1384 /* expand personal aliases since the names are meaningless in */
1385 /* other contexts */
1386 /* */
1387 Addr*
1388 _expand(Addr *old, int *changedp)
1390 Alias *al;
1391 Addr *first, *next, **l, *a;
1393 *changedp = 0;
1394 first = nil;
1395 l = &first;
1396 for(;old != nil; old = next){
1397 next = old->next;
1398 for(al = aliases; al != nil; al = al->next){
1399 if(strcmp(al->addr->v, old->v) == 0){
1400 for(a = al->addr->next; a != nil; a = a->next){
1401 *l = newaddr(a->v);
1402 if(*l == nil)
1403 sysfatal("%r");
1404 l = &(*l)->next;
1405 *changedp = 1;
1407 break;
1410 if(al != nil){
1411 freeaddr(old);
1412 continue;
1414 *l = old;
1415 old->next = nil;
1416 l = &(*l)->next;
1418 return first;
1421 Addr*
1422 rexpand(Addr *old)
1424 int i, changed;
1426 changed = 0;
1427 for(i=0; i<32; i++){
1428 old = _expand(old, &changed);
1429 if(changed == 0)
1430 break;
1432 return old;
1435 Addr*
1436 unique(Addr *first)
1438 Addr *a, **l, *x;
1440 for(a = first; a != nil; a = a->next){
1441 for(l = &a->next; *l != nil;){
1442 if(strcmp(a->v, (*l)->v) == 0){
1443 x = *l;
1444 *l = x->next;
1445 freeaddr(x);
1446 } else
1447 l = &(*l)->next;
1450 return first;
1453 Addr*
1454 expand(int ac, char **av)
1456 Addr *first, **l;
1457 int i;
1459 first = nil;
1461 /* make a list of the starting addresses */
1462 l = &first;
1463 for(i = 0; i < ac; i++){
1464 *l = newaddr(av[i]);
1465 if(*l == nil)
1466 sysfatal("%r");
1467 l = &(*l)->next;
1470 /* recurse till we don't change any more */
1471 return unique(rexpand(first));
1474 Addr*
1475 concataddr(Addr *a, Addr *b)
1477 Addr *oa;
1479 if(a == nil)
1480 return b;
1482 oa = a;
1483 for(; a->next; a=a->next)
1485 a->next = b;
1486 return oa;
1489 void
1490 freeaddr(Addr *ap)
1492 free(ap->v);
1493 free(ap);
1496 void
1497 freeaddrs(Addr *ap)
1499 Addr *next;
1501 for(; ap; ap=next) {
1502 next = ap->next;
1503 freeaddr(ap);
1507 String*
1508 s_copyn(char *s, int n)
1510 return s_nappend(s_reset(nil), s, n);
1513 /* fetch the next token from an RFC822 address string */
1514 /* we assume the header is RFC822-conformant in that */
1515 /* we recognize escaping anywhere even though it is only */
1516 /* supposed to be in quoted-strings, domain-literals, and comments. */
1517 /* */
1518 /* i'd use yylex or yyparse here, but we need to preserve */
1519 /* things like comments, which i think it tosses away. */
1520 /* */
1521 /* we're not strictly RFC822 compliant. we misparse such nonsense as */
1522 /* */
1523 /* To: gre @ (Grace) plan9 . (Emlin) bell-labs.com */
1524 /* */
1525 /* make sure there's no whitespace in your addresses and */
1526 /* you'll be fine. */
1527 /* */
1528 enum {
1529 Twhite,
1530 Tcomment,
1531 Twords,
1532 Tcomma,
1533 Tleftangle,
1534 Trightangle,
1535 Terror,
1536 Tend
1538 /*char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"}; */
1539 #define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r')
1540 int
1541 get822token(String **tok, char *p, char **pp)
1543 char *op;
1544 int type;
1545 int quoting;
1547 op = p;
1548 switch(*p){
1549 case '\0':
1550 *tok = nil;
1551 *pp = nil;
1552 return Tend;
1554 case ' ': /* get whitespace */
1555 case '\t':
1556 case '\n':
1557 case '\r':
1558 type = Twhite;
1559 while(ISWHITE(*p))
1560 p++;
1561 break;
1563 case '(': /* get comment */
1564 type = Tcomment;
1565 for(p++; *p && *p != ')'; p++)
1566 if(*p == '\\') {
1567 if(*(p+1) == '\0') {
1568 *tok = nil;
1569 return Terror;
1571 p++;
1574 if(*p != ')') {
1575 *tok = nil;
1576 return Terror;
1578 p++;
1579 break;
1580 case ',':
1581 type = Tcomma;
1582 p++;
1583 break;
1584 case '<':
1585 type = Tleftangle;
1586 p++;
1587 break;
1588 case '>':
1589 type = Trightangle;
1590 p++;
1591 break;
1592 default: /* bunch of letters, perhaps quoted strings tossed in */
1593 type = Twords;
1594 quoting = 0;
1595 for(; *p && (quoting || (!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) {
1596 if(*p == '"')
1597 quoting = !quoting;
1598 if(*p == '\\') {
1599 if(*(p+1) == '\0') {
1600 *tok = nil;
1601 return Terror;
1603 p++;
1606 break;
1609 if(pp)
1610 *pp = p;
1611 *tok = s_copyn(op, p-op);
1612 return type;
1615 /* expand local aliases in an RFC822 mail line */
1616 /* add list of expanded addresses to to. */
1617 Addr*
1618 expandline(String **s, Addr *to)
1620 Addr *na, *nto, *ap;
1621 char *p;
1622 int tok, inangle, hadangle, nword;
1623 String *os, *ns, *stok, *lastword, *sinceword;
1625 os = s_copy(s_to_c(*s));
1626 p = strchr(s_to_c(*s), ':');
1627 assert(p != nil);
1628 p++;
1630 ns = s_copyn(s_to_c(*s), p-s_to_c(*s));
1631 stok = nil;
1632 nto = nil;
1633 /* */
1634 /* the only valid mailbox namings are word */
1635 /* and word* < addr > */
1636 /* without comments this would be simple. */
1637 /* we keep the following: */
1638 /* lastword - current guess at the address */
1639 /* sinceword - whitespace and comment seen since lastword */
1640 /* */
1641 lastword = s_new();
1642 sinceword = s_new();
1643 inangle = 0;
1644 nword = 0;
1645 hadangle = 0;
1646 for(;;) {
1647 stok = nil;
1648 switch(tok = get822token(&stok, p, &p)){
1649 default:
1650 abort();
1651 case Tcomma:
1652 case Tend:
1653 if(inangle)
1654 goto Error;
1655 if(nword != 1)
1656 goto Error;
1657 na = rexpand(newaddr(s_to_c(lastword)));
1658 s_append(ns, na->v);
1659 s_append(ns, s_to_c(sinceword));
1660 for(ap=na->next; ap; ap=ap->next) {
1661 s_append(ns, ", ");
1662 s_append(ns, ap->v);
1664 nto = concataddr(na, nto);
1665 if(tok == Tcomma){
1666 s_append(ns, ",");
1667 s_free(stok);
1669 if(tok == Tend)
1670 goto Break2;
1671 inangle = 0;
1672 nword = 0;
1673 hadangle = 0;
1674 s_reset(sinceword);
1675 s_reset(lastword);
1676 break;
1677 case Twhite:
1678 case Tcomment:
1679 s_append(sinceword, s_to_c(stok));
1680 s_free(stok);
1681 break;
1682 case Trightangle:
1683 if(!inangle)
1684 goto Error;
1685 inangle = 0;
1686 hadangle = 1;
1687 s_append(sinceword, s_to_c(stok));
1688 s_free(stok);
1689 break;
1690 case Twords:
1691 case Tleftangle:
1692 if(hadangle)
1693 goto Error;
1694 if(tok != Tleftangle && inangle && s_len(lastword))
1695 goto Error;
1696 if(tok == Tleftangle) {
1697 inangle = 1;
1698 nword = 1;
1700 s_append(ns, s_to_c(lastword));
1701 s_append(ns, s_to_c(sinceword));
1702 s_reset(sinceword);
1703 if(tok == Tleftangle) {
1704 s_append(ns, "<");
1705 s_reset(lastword);
1706 } else {
1707 s_free(lastword);
1708 lastword = stok;
1710 if(!inangle)
1711 nword++;
1712 break;
1713 case Terror: /* give up, use old string, addrs */
1714 Error:
1715 ns = os;
1716 os = nil;
1717 freeaddrs(nto);
1718 nto = nil;
1719 werrstr("rfc822 syntax error");
1720 rfc822syntaxerror = 1;
1721 goto Break2;
1724 Break2:
1725 s_free(*s);
1726 s_free(os);
1727 *s = ns;
1728 nto = concataddr(nto, to);
1729 return nto;
1732 void
1733 Bdrain(Biobuf *b)
1735 char buf[8192];
1737 while(Bread(b, buf, sizeof buf) > 0)
1741 void
1742 readmimetypes(void)
1744 Biobuf *b;
1745 char *p;
1746 char *f[6];
1747 char type[256];
1748 static int alloced, inuse;
1750 if(mimetypes == 0){
1751 alloced = 256;
1752 mimetypes = emalloc(alloced*sizeof(Ctype));
1753 mimetypes[0].ext = "";
1756 b = Bopen(unsharp("#9/lib/mimetype"), OREAD);
1757 if(b == nil)
1758 return;
1759 for(;;){
1760 p = Brdline(b, '\n');
1761 if(p == nil)
1762 break;
1763 p[Blinelen(b)-1] = 0;
1764 if(tokenize(p, f, 6) < 4)
1765 continue;
1766 if(strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 || strcmp(f[2], "-") == 0)
1767 continue;
1768 if(inuse + 1 >= alloced){
1769 alloced += 256;
1770 mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype));
1772 snprint(type, sizeof(type), "%s/%s", f[1], f[2]);
1773 mimetypes[inuse].type = estrdup(type);
1774 mimetypes[inuse].ext = estrdup(f[0]+1);
1775 mimetypes[inuse].display = !strcmp(type, "text/plain");
1776 inuse++;
1778 /* always make sure there's a terminator */
1779 mimetypes[inuse].ext = 0;
1781 Bterm(b);
1784 char*
1785 estrdup(char *x)
1787 x = strdup(x);
1788 if(x == nil)
1789 fatal("memory");
1790 return x;
1793 void*
1794 emalloc(int n)
1796 void *x;
1798 x = malloc(n);
1799 if(x == nil)
1800 fatal("%r");
1801 return x;
1804 void*
1805 erealloc(void *x, int n)
1807 x = realloc(x, n);
1808 if(x == nil)
1809 fatal("%r");
1810 return x;
1813 /* */
1814 /* Formatter for %" */
1815 /* Use double quotes to protect white space, frogs, \ and " */
1816 /* */
1817 enum
1819 Qok = 0,
1820 Qquote,
1821 Qbackslash
1824 static int
1825 needtoquote(Rune r)
1827 if(r >= Runeself)
1828 return Qquote;
1829 if(r <= ' ')
1830 return Qquote;
1831 if(r=='\\' || r=='"')
1832 return Qbackslash;
1833 return Qok;
1836 int
1837 doublequote(Fmt *f)
1839 char *s, *t;
1840 int w, quotes;
1841 Rune r;
1843 s = va_arg(f->args, char*);
1844 if(s == nil || *s == '\0')
1845 return fmtstrcpy(f, "\"\"");
1847 quotes = 0;
1848 for(t=s; *t; t+=w){
1849 w = chartorune(&r, t);
1850 quotes |= needtoquote(r);
1852 if(quotes == 0)
1853 return fmtstrcpy(f, s);
1855 fmtrune(f, '"');
1856 for(t=s; *t; t+=w){
1857 w = chartorune(&r, t);
1858 if(needtoquote(r) == Qbackslash)
1859 fmtrune(f, '\\');
1860 fmtrune(f, r);
1862 return fmtrune(f, '"');
1865 int
1866 mountmail(void)
1868 if(mailfs != nil)
1869 return 0;
1870 if((mailfs = nsmount("mail", nil)) == nil)
1871 return -1;
1872 return 0;
1875 int
1876 rfc2047fmt(Fmt *fmt)
1878 char *s, *p;
1880 s = va_arg(fmt->args, char*);
1881 if(s == nil)
1882 return fmtstrcpy(fmt, "");
1883 for(p=s; *p; p++)
1884 if((uchar)*p >= 0x80)
1885 goto hard;
1886 return fmtstrcpy(fmt, s);
1888 hard:
1889 fmtprint(fmt, "=?utf-8?q?");
1890 for(p=s; *p; p++){
1891 if(*p == ' ')
1892 fmtrune(fmt, '_');
1893 else if(*p == '_' || *p == '\t' || *p == '=' || *p == '?' || (uchar)*p >= 0x80)
1894 fmtprint(fmt, "=%.2uX", (uchar)*p);
1895 else
1896 fmtrune(fmt, (uchar)*p);
1898 fmtprint(fmt, "?=");
1899 return 0;
1902 char*
1903 mksubject(char *line)
1905 char *p, *q;
1906 static char buf[1024];
1908 p = strchr(line, ':')+1;
1909 while(*p == ' ')
1910 p++;
1911 for(q=p; *q; q++)
1912 if((uchar)*q >= 0x80)
1913 goto hard;
1914 return line;
1916 hard:
1917 snprint(buf, sizeof buf, "Subject: %U", p);
1918 return buf;