tnedmail.c - plan9port - [fork] Plan 9 from user space | |
git clone git://src.adamsgaard.dk/plan9port | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
tnedmail.c (47272B) | |
--- | |
1 #include "common.h" | |
2 #include <ctype.h> | |
3 #include <plumb.h> | |
4 #include <9pclient.h> | |
5 #include <thread.h> | |
6 | |
7 #define system nedsystem | |
8 #define rcmd nedrcmd | |
9 | |
10 typedef struct Message Message; | |
11 typedef struct Ctype Ctype; | |
12 typedef struct Cmd Cmd; | |
13 | |
14 char root[Pathlen]; | |
15 char mbname[Elemlen]; | |
16 int rootlen; | |
17 int didopen; | |
18 char *user; | |
19 char wd[2048]; | |
20 String *mbpath; | |
21 int natural; | |
22 int doflush; | |
23 | |
24 int interrupted; | |
25 | |
26 struct Message { | |
27 Message *next; | |
28 Message *prev; | |
29 Message *cmd; | |
30 Message *child; | |
31 Message *parent; | |
32 String *path; | |
33 int id; | |
34 int len; | |
35 int fileno; /* number of directory */ | |
36 String *info; | |
37 char *from; | |
38 char *to; | |
39 char *cc; | |
40 char *replyto; | |
41 char *date; | |
42 char *subject; | |
43 char *type; | |
44 char *disposition; | |
45 char *filename; | |
46 char deleted; | |
47 char stored; | |
48 }; | |
49 | |
50 Message top; | |
51 | |
52 struct Ctype { | |
53 char *type; | |
54 char *ext; | |
55 int display; | |
56 char *plumbdest; | |
57 Ctype *next; | |
58 }; | |
59 | |
60 Ctype ctype[] = { | |
61 { "text/plain", "txt", 1, 0… | |
62 { "text/html", "htm", 1, 0 … | |
63 { "text/html", "html", 1, 0… | |
64 { "text/tab-separated-values", "tsv", 1, 0 … | |
65 { "text/richtext", "rtx", 1, 0 … | |
66 { "text/rtf", "rtf", 1, 0 … | |
67 { "text", "txt", 1, 0 … | |
68 { "message/rfc822", "msg", 0, 0 … | |
69 { "message/delivery-status", "txt", 1, 0 … | |
70 { "image/bmp", "bmp", 0, "i… | |
71 { "image/jpeg", "jpg", 0, "… | |
72 { "image/gif", "gif", 0, "i… | |
73 { "image/png", "png", 0, "i… | |
74 { "application/pdf", "pdf", 0, "pos… | |
75 { "application/postscript", "ps", 0, "posts… | |
76 { "application/", 0, 0, 0 }, | |
77 { "image/", 0, 0, 0 … | |
78 { "multipart/", "mul", 0, 0… | |
79 | |
80 }; | |
81 | |
82 Message* acmd(Cmd*, Message*); | |
83 Message* bcmd(Cmd*, Message*); | |
84 Message* dcmd(Cmd*, Message*); | |
85 Message* eqcmd(Cmd*, Message*); | |
86 Message* hcmd(Cmd*, Message*); | |
87 Message* Hcmd(Cmd*, Message*); | |
88 Message* helpcmd(Cmd*, Message*); | |
89 Message* icmd(Cmd*, Message*); | |
90 Message* pcmd(Cmd*, Message*); | |
91 Message* qcmd(Cmd*, Message*); | |
92 Message* rcmd(Cmd*, Message*); | |
93 Message* scmd(Cmd*, Message*); | |
94 Message* ucmd(Cmd*, Message*); | |
95 Message* wcmd(Cmd*, Message*); | |
96 Message* xcmd(Cmd*, Message*); | |
97 Message* ycmd(Cmd*, Message*); | |
98 Message* pipecmd(Cmd*, Message*); | |
99 Message* rpipecmd(Cmd*, Message*); | |
100 Message* bangcmd(Cmd*, Message*); | |
101 Message* Pcmd(Cmd*, Message*); | |
102 Message* mcmd(Cmd*, Message*); | |
103 Message* fcmd(Cmd*, Message*); | |
104 Message* quotecmd(Cmd*, Message*); | |
105 | |
106 struct { | |
107 char *cmd; | |
108 int args; | |
109 Message* (*f)(Cmd*, Message*); | |
110 char *help; | |
111 } cmdtab[] = { | |
112 { "a", 1, acmd, "a reply to sender a… | |
113 { "A", 1, acmd, "A reply to sender a… | |
114 { "b", 0, bcmd, "b print the next 10… | |
115 { "d", 0, dcmd, "d mark for deletion… | |
116 { "f", 0, fcmd, "f file message by f… | |
117 { "h", 0, hcmd, "h print elided mess… | |
118 { "help", 0, helpcmd, "help print this info" }, | |
119 { "H", 0, Hcmd, "H print message's M… | |
120 { "i", 0, icmd, "i incorporate new m… | |
121 { "m", 1, mcmd, "m addr forward mail" }, | |
122 { "M", 1, mcmd, "M addr forward mail with… | |
123 { "p", 0, pcmd, "p print the process… | |
124 { "P", 0, Pcmd, "P print the raw mes… | |
125 { "\"", 0, quotecmd, "\" print a quoted ver… | |
126 { "q", 0, qcmd, "q exit and remove a… | |
127 { "r", 1, rcmd, "r [addr] reply to sender p… | |
128 { "rf", 1, rcmd, "rf [addr]file message and… | |
129 { "R", 1, rcmd, "R [addr] reply including c… | |
130 { "Rf", 1, rcmd, "Rf [addr]file message and… | |
131 { "s", 1, scmd, "s file append raw messag… | |
132 { "u", 0, ucmd, "u remove deletion m… | |
133 { "w", 1, wcmd, "w file store message con… | |
134 { "x", 0, xcmd, "x exit without flus… | |
135 { "y", 0, ycmd, "y synchronize with … | |
136 { "=", 1, eqcmd, "= print current me… | |
137 { "|", 1, pipecmd, "|cmd pipe message body to … | |
138 { "||", 1, rpipecmd, "||cmd pipe raw message t… | |
139 { "!", 1, bangcmd, "!cmd run a command" }, | |
140 { nil, 0, nil, nil } | |
141 }; | |
142 | |
143 enum | |
144 { | |
145 NARG= 32 | |
146 }; | |
147 | |
148 struct Cmd { | |
149 Message *msgs; | |
150 Message *(*f)(Cmd*, Message*); | |
151 int an; | |
152 char *av[NARG]; | |
153 int delete; | |
154 }; | |
155 | |
156 Biobuf out; | |
157 int startedfs; | |
158 int reverse; | |
159 int longestfrom = 12; | |
160 | |
161 String* file2string(String*, char*); | |
162 int dir2message(Message*, int); | |
163 int filelen(String*, char*); | |
164 String* extendpath(String*, char*); | |
165 void snprintheader(char*, int, Message*); | |
166 void cracktime(char*, char*, int); | |
167 int cistrncmp(char*, char*, int); | |
168 int cistrcmp(char*, char*); | |
169 Reprog* parsesearch(char**); | |
170 char* parseaddr(char**, Message*, Message*, Message*, Mes… | |
171 char* parsecmd(char*, Cmd*, Message*, Message*); | |
172 char* readline(char*, char*, int); | |
173 void messagecount(Message*); | |
174 void system(char*, char**, int); | |
175 void mkid(String*, Message*); | |
176 int switchmb(char*, char*); | |
177 void closemb(void); | |
178 int lineize(char*, char**, int); | |
179 int rawsearch(Message*, Reprog*); | |
180 Message* dosingleton(Message*, char*); | |
181 String* rooted(String*); | |
182 int plumb(Message*, Ctype*); | |
183 String* addrecolon(char*); | |
184 void exitfs(char*); | |
185 Message* flushdeleted(Message*); | |
186 | |
187 CFsys *mailfs; | |
188 | |
189 void | |
190 usage(void) | |
191 { | |
192 fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0… | |
193 fprint(2, " %s -c dir\n", argv0); | |
194 threadexitsall("usage"); | |
195 } | |
196 | |
197 void | |
198 catchnote(void *x, char *note) | |
199 { | |
200 USED(x); | |
201 | |
202 if(strstr(note, "interrupt") != nil){ | |
203 interrupted = 1; | |
204 noted(NCONT); | |
205 } | |
206 noted(NDFLT); | |
207 } | |
208 | |
209 char * | |
210 plural(int n) | |
211 { | |
212 if (n == 1) | |
213 return ""; | |
214 | |
215 return "s"; | |
216 } | |
217 | |
218 void | |
219 threadmain(int argc, char **argv) | |
220 { | |
221 Message *cur, *m, *x; | |
222 char cmdline[4*1024]; | |
223 Cmd cmd; | |
224 Ctype *cp; | |
225 char *err; | |
226 int n, cflag; | |
227 String *prompt; | |
228 char *file, *singleton, *service; | |
229 | |
230 Binit(&out, 1, OWRITE); | |
231 | |
232 file = nil; | |
233 singleton = nil; | |
234 reverse = 1; | |
235 cflag = 0; | |
236 service = "mail"; | |
237 ARGBEGIN { | |
238 case 'S': | |
239 service = EARGF(usage()); | |
240 break; | |
241 case 'c': | |
242 cflag = 1; | |
243 break; | |
244 case 'f': | |
245 file = EARGF(usage()); | |
246 break; | |
247 case 's': | |
248 singleton = EARGF(usage()); | |
249 break; | |
250 case 'r': | |
251 reverse = 0; | |
252 break; | |
253 case 'n': | |
254 natural = 1; | |
255 reverse = 0; | |
256 break; | |
257 default: | |
258 usage(); | |
259 break; | |
260 } ARGEND; | |
261 | |
262 user = getlog(); | |
263 if(user == nil || *user == 0) | |
264 sysfatal("can't read user name"); | |
265 | |
266 if(cflag){ | |
267 if(argc > 0) | |
268 creatembox(user, argv[0]); | |
269 else | |
270 creatembox(user, nil); | |
271 threadexitsall(0); | |
272 } | |
273 | |
274 if(argc) | |
275 usage(); | |
276 if((mailfs = nsmount(service, nil)) == nil) | |
277 sysfatal("cannot mount %s: %r", service); | |
278 | |
279 switchmb(file, singleton); | |
280 | |
281 top.path = s_copy(root); | |
282 | |
283 for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++) | |
284 cp->next = cp+1; | |
285 | |
286 if(singleton != nil){ | |
287 cur = dosingleton(&top, singleton); | |
288 if(cur == nil){ | |
289 Bprint(&out, "no message\n"); | |
290 exitfs(0); | |
291 } | |
292 pcmd(nil, cur); | |
293 } else { | |
294 cur = ⊤ | |
295 n = dir2message(&top, reverse); | |
296 if(n < 0) | |
297 sysfatal("can't read %s", s_to_c(top.path)); | |
298 Bprint(&out, "%d message%s\n", n, plural(n)); | |
299 } | |
300 | |
301 | |
302 notify(catchnote); | |
303 prompt = s_new(); | |
304 for(;;){ | |
305 s_reset(prompt); | |
306 if(cur == &top) | |
307 s_append(prompt, ": "); | |
308 else { | |
309 mkid(prompt, cur); | |
310 s_append(prompt, ": "); | |
311 } | |
312 | |
313 /* leave space at the end of cmd line in case parsecmd n… | |
314 /* add a space after a '|' or '!' */ | |
315 if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) … | |
316 break; | |
317 err = parsecmd(cmdline, &cmd, top.child, cur); | |
318 if(err != nil){ | |
319 Bprint(&out, "!%s\n", err); | |
320 continue; | |
321 } | |
322 if(singleton != nil && cmd.f == icmd){ | |
323 Bprint(&out, "!illegal command\n"); | |
324 continue; | |
325 } | |
326 interrupted = 0; | |
327 if(cmd.msgs == nil || cmd.msgs == &top){ | |
328 x = (*cmd.f)(&cmd, &top); | |
329 if(x != nil) | |
330 cur = x; | |
331 } else for(m = cmd.msgs; m != nil; m = m->cmd){ | |
332 x = m; | |
333 if(cmd.delete){ | |
334 dcmd(&cmd, x); | |
335 | |
336 /* dp acts differently than all other co… | |
337 /* since its an old lesk idiom that peop… | |
338 /* it deletes the current message, moves… | |
339 /* pointer ahead one and prints. */ | |
340 if(cmd.f == pcmd){ | |
341 if(x->next == nil){ | |
342 Bprint(&out, "!address\n… | |
343 cur = x; | |
344 break; | |
345 } else | |
346 x = x->next; | |
347 } | |
348 } | |
349 x = (*cmd.f)(&cmd, x); | |
350 if(x != nil) | |
351 cur = x; | |
352 if(interrupted) | |
353 break; | |
354 if(singleton != nil && (cmd.delete || cmd.f == d… | |
355 qcmd(nil, nil); | |
356 } | |
357 if(doflush) | |
358 cur = flushdeleted(cur); | |
359 } | |
360 qcmd(nil, nil); | |
361 } | |
362 | |
363 static char* | |
364 mkaddrs(char *t) | |
365 { | |
366 int i, nf, inquote; | |
367 char **f, *s; | |
368 Fmt fmt; | |
369 | |
370 inquote = 0; | |
371 nf = 2; | |
372 for(s=t; *s; s++){ | |
373 if(*s == '\'') | |
374 inquote = !inquote; | |
375 if(*s == ' ' && !inquote) | |
376 nf++; | |
377 } | |
378 f = malloc(nf*sizeof f[0]); | |
379 if(f == nil) | |
380 return nil; | |
381 nf = tokenize(t, f, nf); | |
382 fmtstrinit(&fmt); | |
383 for(i=0; i+1<nf; i+=2){ | |
384 if(i > 0) | |
385 fmtprint(&fmt, " "); | |
386 /* if(f[i][0] == 0 || strcmp(f[i], f[i+1]) == 0) */ | |
387 fmtprint(&fmt, "%s", f[i+1]); | |
388 /* else */ | |
389 /* fmtprint(&fmt, "%s <%s>", f[i], f[i+1]); */ | |
390 } | |
391 free(f); | |
392 return fmtstrflush(&fmt); | |
393 } | |
394 | |
395 /* */ | |
396 /* read the message info */ | |
397 /* */ | |
398 Message* | |
399 file2message(Message *parent, char *name) | |
400 { | |
401 Message *m; | |
402 String *path; | |
403 char *f[30], *s, *t; | |
404 int i, nf; | |
405 | |
406 m = mallocz(sizeof(Message), 1); | |
407 if(m == nil) | |
408 return nil; | |
409 m->path = path = extendpath(parent->path, name); | |
410 m->fileno = atoi(name); | |
411 m->info = file2string(path, "info"); | |
412 m->from = ""; | |
413 m->to = ""; | |
414 m->cc = ""; | |
415 m->replyto = ""; | |
416 m->date = ""; | |
417 m->subject = ""; | |
418 m->type = ""; | |
419 m->disposition = ""; | |
420 m->filename = ""; | |
421 nf = lineize(s_to_c(m->info), f, nelem(f)); | |
422 for(i=0; i<nf; i++){ | |
423 s = f[i]; | |
424 t = strchr(f[i], ' '); | |
425 if(t == nil) | |
426 continue; | |
427 *t++ = 0; | |
428 | |
429 if(strcmp(s, "from") == 0) | |
430 m->from = mkaddrs(t); | |
431 else if(strcmp(s, "to") == 0) | |
432 m->to = mkaddrs(t); | |
433 else if(strcmp(s, "cc") == 0) | |
434 m->cc = mkaddrs(t); | |
435 else if(strcmp(s, "replyto") == 0) | |
436 m->replyto = mkaddrs(t); | |
437 else if(strcmp(s, "unixdate") == 0 && (t=strchr(t, ' '))… | |
438 m->date = t; | |
439 else if(strcmp(s, "subject") == 0) | |
440 m->subject = t; | |
441 else if(strcmp(s, "type") == 0) | |
442 m->type = t; | |
443 else if(strcmp(s, "disposition") == 0) | |
444 m->disposition = t; | |
445 else if(strcmp(s, "filename") == 0) | |
446 m->filename = t; | |
447 } | |
448 m->len = filelen(path, "raw"); | |
449 if(strstr(m->type, "multipart") != nil || strcmp(m->type, "messa… | |
450 dir2message(m, 0); | |
451 m->parent = parent; | |
452 | |
453 return m; | |
454 } | |
455 | |
456 void | |
457 freemessage(Message *m) | |
458 { | |
459 Message *nm, *next; | |
460 | |
461 for(nm = m->child; nm != nil; nm = next){ | |
462 next = nm->next; | |
463 freemessage(nm); | |
464 } | |
465 s_free(m->path); | |
466 s_free(m->info); | |
467 free(m); | |
468 } | |
469 | |
470 /* */ | |
471 /* read a directory into a list of messages */ | |
472 /* */ | |
473 int | |
474 dir2message(Message *parent, int reverse) | |
475 { | |
476 int i, n, highest, newmsgs; | |
477 CFid *fd; | |
478 | |
479 Dir *d; | |
480 Message *first, *last, *m; | |
481 | |
482 fd = fsopen(mailfs, s_to_c(parent->path), OREAD); | |
483 if(fd == nil) | |
484 return -1; | |
485 | |
486 /* count current entries */ | |
487 first = parent->child; | |
488 highest = newmsgs = 0; | |
489 for(last = parent->child; last != nil && last->next != nil; last… | |
490 if(last->fileno > highest) | |
491 highest = last->fileno; | |
492 if(last != nil) | |
493 if(last->fileno > highest) | |
494 highest = last->fileno; | |
495 | |
496 n = fsdirreadall(fd, &d); | |
497 for(i = 0; i < n; i++){ | |
498 if((d[i].qid.type & QTDIR) == 0) | |
499 continue; | |
500 if(atoi(d[i].name) <= highest) | |
501 continue; | |
502 m = file2message(parent, d[i].name); | |
503 /* fprint(2,"returned from file2message\n"); */ | |
504 if(m == nil) | |
505 break; | |
506 newmsgs++; | |
507 if(reverse){ | |
508 m->next = first; | |
509 if(first != nil) | |
510 first->prev = m; | |
511 first = m; | |
512 } else { | |
513 if(first == nil) | |
514 first = m; | |
515 else | |
516 last->next = m; | |
517 m->prev = last; | |
518 last = m; | |
519 } | |
520 } | |
521 free(d); | |
522 fsclose(fd); | |
523 parent->child = first; | |
524 | |
525 /* renumber and file longest from */ | |
526 i = 1; | |
527 longestfrom = 12; | |
528 for(m = first; m != nil; m = m->next){ | |
529 m->id = natural ? m->fileno : i++; | |
530 n = strlen(m->from); | |
531 if(n > longestfrom) | |
532 longestfrom = n; | |
533 } | |
534 | |
535 return newmsgs; | |
536 } | |
537 | |
538 /* */ | |
539 /* point directly to a message */ | |
540 /* */ | |
541 Message* | |
542 dosingleton(Message *parent, char *path) | |
543 { | |
544 char *p, *np; | |
545 Message *m; | |
546 | |
547 /* walk down to message and read it */ | |
548 if(strlen(path) < rootlen) | |
549 return nil; | |
550 if(path[rootlen] != '/') | |
551 return nil; | |
552 p = path+rootlen+1; | |
553 np = strchr(p, '/'); | |
554 if(np != nil) | |
555 *np = 0; | |
556 m = file2message(parent, p); | |
557 if(m == nil) | |
558 return nil; | |
559 parent->child = m; | |
560 m->id = 1; | |
561 | |
562 /* walk down to requested component */ | |
563 while(np != nil){ | |
564 *np = '/'; | |
565 np = strchr(np+1, '/'); | |
566 if(np != nil) | |
567 *np = 0; | |
568 for(m = m->child; m != nil; m = m->next) | |
569 if(strcmp(path, s_to_c(m->path)) == 0) | |
570 return m; | |
571 if(m == nil) | |
572 return nil; | |
573 } | |
574 return m; | |
575 } | |
576 | |
577 /* */ | |
578 /* read a file into a string */ | |
579 /* */ | |
580 String* | |
581 file2string(String *dir, char *file) | |
582 { | |
583 String *s; | |
584 int n, m; | |
585 CFid *fd; | |
586 | |
587 s = extendpath(dir, file); | |
588 fd = fsopen(mailfs, s_to_c(s), OREAD); | |
589 s_grow(s, 512); /* avoid multiple reads o… | |
590 s_reset(s); | |
591 if(fd == nil) | |
592 return s; | |
593 | |
594 for(;;){ | |
595 n = s->end - s->ptr; | |
596 if(n == 0){ | |
597 s_grow(s, 128); | |
598 continue; | |
599 } | |
600 m = fsread(fd, s->ptr, n); | |
601 if(m <= 0) | |
602 break; | |
603 s->ptr += m; | |
604 if(m < n) | |
605 break; | |
606 } | |
607 s_terminate(s); | |
608 fsclose(fd); | |
609 | |
610 return s; | |
611 } | |
612 | |
613 /* */ | |
614 /* get the length of a file */ | |
615 /* */ | |
616 int | |
617 filelen(String *dir, char *file) | |
618 { | |
619 String *path; | |
620 Dir *d; | |
621 int rv; | |
622 | |
623 path = extendpath(dir, file); | |
624 d = fsdirstat(mailfs, s_to_c(path)); | |
625 if(d == nil){ | |
626 s_free(path); | |
627 return -1; | |
628 } | |
629 s_free(path); | |
630 rv = d->length; | |
631 free(d); | |
632 return rv; | |
633 } | |
634 | |
635 /* */ | |
636 /* walk the path name an element */ | |
637 /* */ | |
638 String* | |
639 extendpath(String *dir, char *name) | |
640 { | |
641 String *path; | |
642 | |
643 if(strcmp(s_to_c(dir), ".") == 0) | |
644 path = s_new(); | |
645 else { | |
646 path = s_copy(s_to_c(dir)); | |
647 s_append(path, "/"); | |
648 } | |
649 s_append(path, name); | |
650 return path; | |
651 } | |
652 | |
653 int | |
654 cistrncmp(char *a, char *b, int n) | |
655 { | |
656 while(n-- > 0){ | |
657 if(tolower(*a++) != tolower(*b++)) | |
658 return -1; | |
659 } | |
660 return 0; | |
661 } | |
662 | |
663 int | |
664 cistrcmp(char *a, char *b) | |
665 { | |
666 for(;;){ | |
667 if(tolower(*a) != tolower(*b++)) | |
668 return -1; | |
669 if(*a++ == 0) | |
670 break; | |
671 } | |
672 return 0; | |
673 } | |
674 | |
675 char* | |
676 nosecs(char *t) | |
677 { | |
678 char *p; | |
679 | |
680 p = strchr(t, ':'); | |
681 if(p == nil) | |
682 return t; | |
683 p = strchr(p+1, ':'); | |
684 if(p != nil) | |
685 *p = 0; | |
686 return t; | |
687 } | |
688 | |
689 char *months[12] = | |
690 { | |
691 "jan", "feb", "mar", "apr", "may", "jun", | |
692 "jul", "aug", "sep", "oct", "nov", "dec" | |
693 }; | |
694 | |
695 int | |
696 month(char *m) | |
697 { | |
698 int i; | |
699 | |
700 for(i = 0; i < 12; i++) | |
701 if(cistrcmp(m, months[i]) == 0) | |
702 return i+1; | |
703 return 1; | |
704 } | |
705 | |
706 enum | |
707 { | |
708 Yearsecs= 365*24*60*60 | |
709 }; | |
710 | |
711 void | |
712 cracktime(char *d, char *out, int len) | |
713 { | |
714 char in[64]; | |
715 char *f[6]; | |
716 int n; | |
717 Tm tm; | |
718 long now, then; | |
719 char *dtime; | |
720 | |
721 *out = 0; | |
722 if(d == nil) | |
723 return; | |
724 strncpy(in, d, sizeof(in)); | |
725 in[sizeof(in)-1] = 0; | |
726 n = getfields(in, f, 6, 1, " \t\r\n"); | |
727 if(n != 6){ | |
728 /* unknown style */ | |
729 snprint(out, 16, "%10.10s", d); | |
730 return; | |
731 } | |
732 now = time(0); | |
733 memset(&tm, 0, sizeof tm); | |
734 if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){ | |
735 /* 822 style */ | |
736 tm.year = atoi(f[3])-1900; | |
737 tm.mon = month(f[2]); | |
738 tm.mday = atoi(f[1]); | |
739 dtime = nosecs(f[4]); | |
740 then = tm2sec(&tm); | |
741 } else if(strchr(f[3], ':') != nil){ | |
742 /* unix style */ | |
743 tm.year = atoi(f[5])-1900; | |
744 tm.mon = month(f[1]); | |
745 tm.mday = atoi(f[2]); | |
746 dtime = nosecs(f[3]); | |
747 then = tm2sec(&tm); | |
748 } else { | |
749 then = now; | |
750 tm = *localtime(now); | |
751 dtime = ""; | |
752 } | |
753 | |
754 if(now - then < Yearsecs/2) | |
755 snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime… | |
756 else | |
757 snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.… | |
758 } | |
759 | |
760 Ctype* | |
761 findctype(Message *m) | |
762 { | |
763 char *p; | |
764 char ftype[128]; | |
765 int n, pfd[2]; | |
766 Ctype *a, *cp; | |
767 static Ctype bintype = { "application/octet-stream", "bi… | |
768 | |
769 for(cp = ctype; cp; cp = cp->next) | |
770 if(strncmp(cp->type, m->type, strlen(cp->type)) == 0) | |
771 return cp; | |
772 | |
773 if(pipe(pfd) < 0) | |
774 return &bintype; | |
775 | |
776 *ftype = 0; | |
777 switch(fork()){ | |
778 case -1: | |
779 break; | |
780 case 0: | |
781 close(pfd[1]); | |
782 close(0); | |
783 dup(pfd[0], 0); | |
784 close(1); | |
785 dup(pfd[0], 1); | |
786 execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(exten… | |
787 threadexits(0); | |
788 default: | |
789 close(pfd[0]); | |
790 n = read(pfd[1], ftype, sizeof(ftype)); | |
791 if(n > 0) | |
792 ftype[n] = 0; | |
793 close(pfd[1]); | |
794 waitpid(); | |
795 break; | |
796 } | |
797 | |
798 if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil) | |
799 return &bintype; | |
800 *p++ = 0; | |
801 | |
802 a = mallocz(sizeof(Ctype), 1); | |
803 a->type = strdup(ftype); | |
804 a->ext = strdup(p); | |
805 a->display = 0; | |
806 a->plumbdest = strdup(ftype); | |
807 for(cp = ctype; cp->next; cp = cp->next) | |
808 continue; | |
809 cp->next = a; | |
810 a->next = nil; | |
811 return a; | |
812 } | |
813 | |
814 void | |
815 mkid(String *s, Message *m) | |
816 { | |
817 char buf[32]; | |
818 | |
819 if(m->parent != &top){ | |
820 mkid(s, m->parent); | |
821 s_append(s, "."); | |
822 } | |
823 sprint(buf, "%d", m->id); | |
824 s_append(s, buf); | |
825 } | |
826 | |
827 void | |
828 snprintheader(char *buf, int len, Message *m) | |
829 { | |
830 char timebuf[32]; | |
831 String *id; | |
832 char *p, *q;; | |
833 | |
834 /* create id */ | |
835 id = s_new(); | |
836 mkid(id, m); | |
837 | |
838 if(*m->from == 0){ | |
839 /* no from */ | |
840 snprint(buf, len, "%-3s %s %6d %s", | |
841 s_to_c(id), | |
842 m->type, | |
843 m->len, | |
844 m->filename); | |
845 } else if(*m->subject){ | |
846 q = p = strdup(m->subject); | |
847 while(*p == ' ') | |
848 p++; | |
849 if(strlen(p) > 50) | |
850 p[50] = 0; | |
851 cracktime(m->date, timebuf, sizeof(timebuf)); | |
852 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s", | |
853 s_to_c(id), | |
854 m->child ? 'H' : ' ', | |
855 m->deleted ? 'd' : ' ', | |
856 m->stored ? 's' : ' ', | |
857 m->len, | |
858 timebuf, | |
859 longestfrom, longestfrom, m->from, | |
860 p); | |
861 free(q); | |
862 } else { | |
863 cracktime(m->date, timebuf, sizeof(timebuf)); | |
864 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s", | |
865 s_to_c(id), | |
866 m->child ? 'H' : ' ', | |
867 m->deleted ? 'd' : ' ', | |
868 m->stored ? 's' : ' ', | |
869 m->len, | |
870 timebuf, | |
871 m->from); | |
872 } | |
873 s_free(id); | |
874 } | |
875 | |
876 char *spaces = " … | |
877 | |
878 void | |
879 snprintHeader(char *buf, int len, int indent, Message *m) | |
880 { | |
881 String *id; | |
882 char typeid[64]; | |
883 char *p, *e; | |
884 | |
885 /* create id */ | |
886 id = s_new(); | |
887 mkid(id, m); | |
888 | |
889 e = buf + len; | |
890 | |
891 snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type); | |
892 if(indent < 6) | |
893 p = seprint(buf, e, "%-32s %-6d ", typeid, m->len); | |
894 else | |
895 p = seprint(buf, e, "%-64s %-6d ", typeid, m->len); | |
896 if(m->filename && *m->filename) | |
897 p = seprint(p, e, "(file,%s)", m->filename); | |
898 if(m->from && *m->from) | |
899 p = seprint(p, e, "(from,%s)", m->from); | |
900 if(m->subject && *m->subject) | |
901 seprint(p, e, "(subj,%s)", m->subject); | |
902 | |
903 s_free(id); | |
904 } | |
905 | |
906 char sstring[256]; | |
907 | |
908 /* cmd := range cmd ' ' arg-list ; */ | |
909 /* range := address */ | |
910 /* | address ',' address */ | |
911 /* | 'g' search ; */ | |
912 /* address := msgno */ | |
913 /* | search ; */ | |
914 /* msgno := number */ | |
915 /* | number '/' msgno ; */ | |
916 /* search := '/' string '/' */ | |
917 /* | '%' string '%' ; */ | |
918 /* */ | |
919 Reprog* | |
920 parsesearch(char **pp) | |
921 { | |
922 char *p, *np; | |
923 int c, n; | |
924 | |
925 p = *pp; | |
926 c = *p++; | |
927 np = strchr(p, c); | |
928 if(np != nil){ | |
929 *np++ = 0; | |
930 *pp = np; | |
931 } else { | |
932 n = strlen(p); | |
933 *pp = p + n; | |
934 } | |
935 if(*p == 0) | |
936 p = sstring; | |
937 else{ | |
938 strncpy(sstring, p, sizeof(sstring)); | |
939 sstring[sizeof(sstring)-1] = 0; | |
940 } | |
941 return regcomp(p); | |
942 } | |
943 | |
944 char* | |
945 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Mess… | |
946 { | |
947 int n; | |
948 Message *m; | |
949 char *p; | |
950 Reprog *prog; | |
951 int c, sign; | |
952 char buf[256]; | |
953 | |
954 *mp = nil; | |
955 p = *pp; | |
956 | |
957 if(*p == '+'){ | |
958 sign = 1; | |
959 p++; | |
960 *pp = p; | |
961 } else if(*p == '-'){ | |
962 sign = -1; | |
963 p++; | |
964 *pp = p; | |
965 } else | |
966 sign = 0; | |
967 | |
968 switch(*p){ | |
969 default: | |
970 if(sign){ | |
971 n = 1; | |
972 goto number; | |
973 } | |
974 *mp = unspec; | |
975 break; | |
976 case '0': case '1': case '2': case '3': case '4': | |
977 case '5': case '6': case '7': case '8': case '9': | |
978 n = strtoul(p, pp, 10); | |
979 if(n == 0){ | |
980 if(sign) | |
981 *mp = cur; | |
982 else | |
983 *mp = ⊤ | |
984 break; | |
985 } | |
986 number: | |
987 m = nil; | |
988 switch(sign){ | |
989 case 0: | |
990 for(m = first; m != nil; m = m->next) | |
991 if(m->id == n) | |
992 break; | |
993 break; | |
994 case -1: | |
995 if(cur != &top) | |
996 for(m = cur; m != nil && n > 0; n--) | |
997 m = m->prev; | |
998 break; | |
999 case 1: | |
1000 if(cur == &top){ | |
1001 n--; | |
1002 cur = first; | |
1003 } | |
1004 for(m = cur; m != nil && n > 0; n--) | |
1005 m = m->next; | |
1006 break; | |
1007 } | |
1008 if(m == nil) | |
1009 return "address"; | |
1010 *mp = m; | |
1011 break; | |
1012 case '%': | |
1013 case '/': | |
1014 case '?': | |
1015 c = *p; | |
1016 prog = parsesearch(pp); | |
1017 if(prog == nil) | |
1018 return "badly formed regular expression"; | |
1019 m = nil; | |
1020 switch(c){ | |
1021 case '%': | |
1022 for(m = cur == &top ? first : cur->next; m != ni… | |
1023 if(rawsearch(m, prog)) | |
1024 break; | |
1025 } | |
1026 break; | |
1027 case '/': | |
1028 for(m = cur == &top ? first : cur->next; m != ni… | |
1029 snprintheader(buf, sizeof(buf), m); | |
1030 if(regexec(prog, buf, nil, 0)) | |
1031 break; | |
1032 } | |
1033 break; | |
1034 case '?': | |
1035 for(m = cur == &top ? nil : cur->prev; m != nil;… | |
1036 snprintheader(buf, sizeof(buf), m); | |
1037 if(regexec(prog, buf, nil, 0)) | |
1038 break; | |
1039 } | |
1040 break; | |
1041 } | |
1042 if(m == nil) | |
1043 return "search"; | |
1044 *mp = m; | |
1045 free(prog); | |
1046 break; | |
1047 case '$': | |
1048 for(m = first; m != nil && m->next != nil; m = m->next) | |
1049 ; | |
1050 *mp = m; | |
1051 *pp = p+1; | |
1052 break; | |
1053 case '.': | |
1054 *mp = cur; | |
1055 *pp = p+1; | |
1056 break; | |
1057 case ',': | |
1058 *mp = first; | |
1059 *pp = p; | |
1060 break; | |
1061 } | |
1062 | |
1063 if(*mp != nil && **pp == '.'){ | |
1064 (*pp)++; | |
1065 if((*mp)->child == nil) | |
1066 return "no sub parts"; | |
1067 return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->… | |
1068 } | |
1069 if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%') | |
1070 return parseaddr(pp, first, *mp, *mp, mp); | |
1071 | |
1072 return nil; | |
1073 } | |
1074 | |
1075 /* */ | |
1076 /* search a message for a regular expression match */ | |
1077 /* */ | |
1078 int | |
1079 rawsearch(Message *m, Reprog *prog) | |
1080 { | |
1081 char buf[4096+1]; | |
1082 int i, rv; | |
1083 CFid *fd; | |
1084 String *path; | |
1085 | |
1086 path = extendpath(m->path, "raw"); | |
1087 fd = fsopen(mailfs, s_to_c(path), OREAD); | |
1088 if(fd == nil) | |
1089 return 0; | |
1090 | |
1091 /* march through raw message 4096 bytes at a time */ | |
1092 /* with a 128 byte overlap to chain the re search. */ | |
1093 rv = 0; | |
1094 for(;;){ | |
1095 i = fsread(fd, buf, sizeof(buf)-1); | |
1096 if(i <= 0) | |
1097 break; | |
1098 buf[i] = 0; | |
1099 if(regexec(prog, buf, nil, 0)){ | |
1100 rv = 1; | |
1101 break; | |
1102 } | |
1103 if(i < sizeof(buf)-1) | |
1104 break; | |
1105 if(fsseek(fd, -128LL, 1) < 0) | |
1106 break; | |
1107 } | |
1108 | |
1109 fsclose(fd); | |
1110 s_free(path); | |
1111 return rv; | |
1112 } | |
1113 | |
1114 | |
1115 char* | |
1116 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur) | |
1117 { | |
1118 Reprog *prog; | |
1119 Message *m, *s, *e, **l, *last; | |
1120 char buf[256]; | |
1121 char *err; | |
1122 int i, c; | |
1123 char *q; | |
1124 static char errbuf[Errlen]; | |
1125 | |
1126 cmd->delete = 0; | |
1127 l = &cmd->msgs; | |
1128 *l = nil; | |
1129 | |
1130 /* eat white space */ | |
1131 while(*p == ' ') | |
1132 p++; | |
1133 | |
1134 /* null command is a special case (advance and print) */ | |
1135 if(*p == 0){ | |
1136 if(cur == &top){ | |
1137 /* special case */ | |
1138 m = first; | |
1139 } else { | |
1140 /* walk to the next message even if we have to g… | |
1141 m = cur->next; | |
1142 while(m == nil && cur->parent != nil){ | |
1143 cur = cur->parent; | |
1144 m = cur->next; | |
1145 } | |
1146 } | |
1147 if(m == nil) | |
1148 return "address"; | |
1149 *l = m; | |
1150 m->cmd = nil; | |
1151 cmd->an = 0; | |
1152 cmd->f = pcmd; | |
1153 return nil; | |
1154 } | |
1155 | |
1156 /* global search ? */ | |
1157 if(*p == 'g'){ | |
1158 p++; | |
1159 | |
1160 /* no search string means all messages */ | |
1161 if(*p != '/' && *p != '%'){ | |
1162 for(m = first; m != nil; m = m->next){ | |
1163 *l = m; | |
1164 l = &m->cmd; | |
1165 *l = nil; | |
1166 } | |
1167 } else { | |
1168 /* mark all messages matching this search string… | |
1169 c = *p; | |
1170 prog = parsesearch(&p); | |
1171 if(prog == nil) | |
1172 return "badly formed regular expression"; | |
1173 if(c == '%'){ | |
1174 for(m = first; m != nil; m = m->next){ | |
1175 if(rawsearch(m, prog)){ | |
1176 *l = m; | |
1177 l = &m->cmd; | |
1178 *l = nil; | |
1179 } | |
1180 } | |
1181 } else { | |
1182 for(m = first; m != nil; m = m->next){ | |
1183 snprintheader(buf, sizeof(buf), … | |
1184 if(regexec(prog, buf, nil, 0)){ | |
1185 *l = m; | |
1186 l = &m->cmd; | |
1187 *l = nil; | |
1188 } | |
1189 } | |
1190 } | |
1191 free(prog); | |
1192 } | |
1193 } else { | |
1194 | |
1195 /* parse an address */ | |
1196 s = e = nil; | |
1197 err = parseaddr(&p, first, cur, cur, &s); | |
1198 if(err != nil) | |
1199 return err; | |
1200 if(*p == ','){ | |
1201 /* this is an address range */ | |
1202 if(s == &top) | |
1203 s = first; | |
1204 p++; | |
1205 for(last = s; last != nil && last->next != nil; … | |
1206 ; | |
1207 err = parseaddr(&p, first, cur, last, &e); | |
1208 if(err != nil) | |
1209 return err; | |
1210 | |
1211 /* select all messages in the range */ | |
1212 for(; s != nil; s = s->next){ | |
1213 *l = s; | |
1214 l = &s->cmd; | |
1215 *l = nil; | |
1216 if(s == e) | |
1217 break; | |
1218 } | |
1219 if(s == nil) | |
1220 return "null address range"; | |
1221 } else { | |
1222 /* single address */ | |
1223 if(s != &top){ | |
1224 *l = s; | |
1225 s->cmd = nil; | |
1226 } | |
1227 } | |
1228 } | |
1229 | |
1230 /* insert a space after '!'s and '|'s */ | |
1231 for(q = p; *q; q++) | |
1232 if(*q != '!' && *q != '|') | |
1233 break; | |
1234 if(q != p && *q != ' '){ | |
1235 memmove(q+1, q, strlen(q)+1); | |
1236 *q = ' '; | |
1237 } | |
1238 | |
1239 cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n"… | |
1240 if(cmd->an == 0 || *cmd->av[0] == 0) | |
1241 cmd->f = pcmd; | |
1242 else { | |
1243 /* hack to allow all messages to start with 'd' */ | |
1244 if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){ | |
1245 cmd->delete = 1; | |
1246 cmd->av[0]++; | |
1247 } | |
1248 | |
1249 /* search command table */ | |
1250 for(i = 0; cmdtab[i].cmd != nil; i++) | |
1251 if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0) | |
1252 break; | |
1253 if(cmdtab[i].cmd == nil) | |
1254 return "illegal command"; | |
1255 if(cmdtab[i].args == 0 && cmd->an > 1){ | |
1256 snprint(errbuf, sizeof(errbuf), "%s doesn't take… | |
1257 return errbuf; | |
1258 } | |
1259 cmd->f = cmdtab[i].f; | |
1260 } | |
1261 return nil; | |
1262 } | |
1263 | |
1264 /* inefficient read from standard input */ | |
1265 char* | |
1266 readline(char *prompt, char *line, int len) | |
1267 { | |
1268 char *p, *e; | |
1269 int n; | |
1270 | |
1271 retry: | |
1272 interrupted = 0; | |
1273 Bprint(&out, "%s", prompt); | |
1274 Bflush(&out); | |
1275 e = line + len; | |
1276 for(p = line; p < e; p++){ | |
1277 n = read(0, p, 1); | |
1278 if(n < 0){ | |
1279 if(interrupted) | |
1280 goto retry; | |
1281 return nil; | |
1282 } | |
1283 if(n == 0) | |
1284 return nil; | |
1285 if(*p == '\n') | |
1286 break; | |
1287 } | |
1288 *p = 0; | |
1289 return line; | |
1290 } | |
1291 | |
1292 void | |
1293 messagecount(Message *m) | |
1294 { | |
1295 int i; | |
1296 | |
1297 i = 0; | |
1298 for(; m != nil; m = m->next) | |
1299 i++; | |
1300 Bprint(&out, "%d message%s\n", i, plural(i)); | |
1301 } | |
1302 | |
1303 Message* | |
1304 aichcmd(Message *m, int indent) | |
1305 { | |
1306 char hdr[256]; | |
1307 | |
1308 if(m == &top) | |
1309 return nil; | |
1310 | |
1311 snprintHeader(hdr, sizeof(hdr), indent, m); | |
1312 Bprint(&out, "%s\n", hdr); | |
1313 for(m = m->child; m != nil; m = m->next) | |
1314 aichcmd(m, indent+1); | |
1315 return nil; | |
1316 } | |
1317 | |
1318 Message* | |
1319 Hcmd(Cmd *x, Message *m) | |
1320 { | |
1321 USED(x); | |
1322 | |
1323 if(m == &top) | |
1324 return nil; | |
1325 aichcmd(m, 0); | |
1326 return nil; | |
1327 } | |
1328 | |
1329 Message* | |
1330 hcmd(Cmd *x, Message *m) | |
1331 { | |
1332 char hdr[256]; | |
1333 | |
1334 USED(x); | |
1335 if(m == &top) | |
1336 return nil; | |
1337 | |
1338 snprintheader(hdr, sizeof(hdr), m); | |
1339 Bprint(&out, "%s\n", hdr); | |
1340 return nil; | |
1341 } | |
1342 | |
1343 Message* | |
1344 bcmd(Cmd *x, Message *m) | |
1345 { | |
1346 int i; | |
1347 Message *om = m; | |
1348 | |
1349 USED(x); | |
1350 if(m == &top) | |
1351 m = top.child; | |
1352 for(i = 0; i < 10 && m != nil; i++){ | |
1353 hcmd(nil, m); | |
1354 om = m; | |
1355 m = m->next; | |
1356 } | |
1357 | |
1358 return om; | |
1359 } | |
1360 | |
1361 Message* | |
1362 ncmd(Cmd *x, Message *m) | |
1363 { | |
1364 USED(x); | |
1365 if(m == &top) | |
1366 return m->child; | |
1367 return m->next; | |
1368 } | |
1369 | |
1370 int | |
1371 printpart(String *s, char *part) | |
1372 { | |
1373 char buf[4096]; | |
1374 int n, tot; | |
1375 CFid *fd; | |
1376 String *path; | |
1377 | |
1378 path = extendpath(s, part); | |
1379 fd = fsopen(mailfs, s_to_c(path), OREAD); | |
1380 s_free(path); | |
1381 if(fd == nil){ | |
1382 fprint(2, "!message dissappeared\n"); | |
1383 return 0; | |
1384 } | |
1385 tot = 0; | |
1386 while((n = fsread(fd, buf, sizeof(buf))) > 0){ | |
1387 if(interrupted) | |
1388 break; | |
1389 if(Bwrite(&out, buf, n) <= 0) | |
1390 break; | |
1391 tot += n; | |
1392 } | |
1393 fsclose(fd); | |
1394 return tot; | |
1395 } | |
1396 | |
1397 int | |
1398 printhtml(Message *m) | |
1399 { | |
1400 Cmd c; | |
1401 | |
1402 c.an = 3; | |
1403 c.av[1] = "htmlfmt"; | |
1404 c.av[2] = "-l 40 -cutf-8"; | |
1405 Bprint(&out, "!%s\n", c.av[1]); | |
1406 Bflush(&out); | |
1407 pipecmd(&c, m); | |
1408 return 0; | |
1409 } | |
1410 | |
1411 Message* | |
1412 Pcmd(Cmd *x, Message *m) | |
1413 { | |
1414 USED(x); | |
1415 if(m == &top) | |
1416 return ⊤ | |
1417 if(m->parent == &top) | |
1418 printpart(m->path, "unixheader"); | |
1419 printpart(m->path, "raw"); | |
1420 return m; | |
1421 } | |
1422 | |
1423 void | |
1424 compress(char *p) | |
1425 { | |
1426 char *np; | |
1427 int last; | |
1428 | |
1429 last = ' '; | |
1430 for(np = p; *p; p++){ | |
1431 if(*p != ' ' || last != ' '){ | |
1432 last = *p; | |
1433 *np++ = last; | |
1434 } | |
1435 } | |
1436 *np = 0; | |
1437 } | |
1438 | |
1439 Message* | |
1440 pcmd(Cmd *x, Message *m) | |
1441 { | |
1442 Message *nm; | |
1443 Ctype *cp; | |
1444 String *s; | |
1445 char buf[128]; | |
1446 | |
1447 USED(x); | |
1448 if(m == &top) | |
1449 return ⊤ | |
1450 if(m->parent == &top) | |
1451 printpart(m->path, "unixheader"); | |
1452 if(printpart(m->path, "header") > 0) | |
1453 Bprint(&out, "\n"); | |
1454 cp = findctype(m); | |
1455 if(cp->display){ | |
1456 if(strcmp(m->type, "text/html") == 0) | |
1457 printhtml(m); | |
1458 else | |
1459 printpart(m->path, "body"); | |
1460 } else if(strcmp(m->type, "multipart/alternative") == 0){ | |
1461 for(nm = m->child; nm != nil; nm = nm->next){ | |
1462 cp = findctype(nm); | |
1463 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) … | |
1464 break; | |
1465 } | |
1466 if(nm == nil) | |
1467 for(nm = m->child; nm != nil; nm = nm->next){ | |
1468 cp = findctype(nm); | |
1469 if(cp->display) | |
1470 break; | |
1471 } | |
1472 if(nm != nil) | |
1473 pcmd(nil, nm); | |
1474 else | |
1475 hcmd(nil, m); | |
1476 } else if(strncmp(m->type, "multipart/", 10) == 0){ | |
1477 nm = m->child; | |
1478 if(nm != nil){ | |
1479 /* always print first part */ | |
1480 pcmd(nil, nm); | |
1481 | |
1482 for(nm = nm->next; nm != nil; nm = nm->next){ | |
1483 s = rooted(s_clone(nm->path)); | |
1484 cp = findctype(nm); | |
1485 snprintHeader(buf, sizeof buf, -1, nm); | |
1486 compress(buf); | |
1487 if(strcmp(nm->disposition, "inline") == … | |
1488 if(cp->ext != nil) | |
1489 Bprint(&out, "\n--- %s %… | |
1490 buf, s_to_c(s), … | |
1491 else | |
1492 Bprint(&out, "\n--- %s %… | |
1493 buf, s_to_c(s)); | |
1494 pcmd(nil, nm); | |
1495 } else { | |
1496 if(cp->ext != nil) | |
1497 Bprint(&out, "\n!--- %s … | |
1498 buf, s_to_c(s), … | |
1499 else | |
1500 Bprint(&out, "\n!--- %s … | |
1501 buf, s_to_c(s)); | |
1502 } | |
1503 s_free(s); | |
1504 } | |
1505 } else { | |
1506 hcmd(nil, m); | |
1507 } | |
1508 } else if(strcmp(m->type, "message/rfc822") == 0){ | |
1509 pcmd(nil, m->child); | |
1510 } else if(plumb(m, cp) >= 0) | |
1511 Bprint(&out, "\n!--- using plumber to display message of… | |
1512 else | |
1513 Bprint(&out, "\n!--- cannot display messages of type %s\… | |
1514 | |
1515 return m; | |
1516 } | |
1517 | |
1518 void | |
1519 printpartindented(String *s, char *part, char *indent) | |
1520 { | |
1521 int fd; | |
1522 char *p; | |
1523 String *path; | |
1524 Biobuf *b; | |
1525 | |
1526 path = extendpath(s, part); | |
1527 fd = fsopenfd(mailfs, s_to_c(path), OREAD); | |
1528 s_free(path); | |
1529 if(fd < 0){ | |
1530 fprint(2, "!message disappeared\n"); | |
1531 return; | |
1532 } | |
1533 b = Bfdopen(fd, OREAD); | |
1534 if(b == 0){ | |
1535 fprint(2, "out of memory\n"); | |
1536 close(fd); | |
1537 return; | |
1538 } | |
1539 while((p = Brdline(b, '\n')) != nil){ | |
1540 if(interrupted) | |
1541 break; | |
1542 p[Blinelen(b)-1] = 0; | |
1543 if(Bprint(&out, "%s%s\n", indent, p) < 0) | |
1544 break; | |
1545 } | |
1546 Bprint(&out, "\n"); | |
1547 Bterm(b); | |
1548 } | |
1549 | |
1550 Message* | |
1551 quotecmd(Cmd *x, Message *m) | |
1552 { | |
1553 Message *nm; | |
1554 Ctype *cp; | |
1555 | |
1556 USED(x); | |
1557 if(m == &top) | |
1558 return ⊤ | |
1559 Bprint(&out, "\n"); | |
1560 if(m->from != nil && *m->from) | |
1561 Bprint(&out, "On %s, %s wrote:\n", m->date, m->from); | |
1562 cp = findctype(m); | |
1563 if(cp->display){ | |
1564 printpartindented(m->path, "body", "> "); | |
1565 } else if(strcmp(m->type, "multipart/alternative") == 0){ | |
1566 for(nm = m->child; nm != nil; nm = nm->next){ | |
1567 cp = findctype(nm); | |
1568 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) … | |
1569 break; | |
1570 } | |
1571 if(nm == nil) | |
1572 for(nm = m->child; nm != nil; nm = nm->next){ | |
1573 cp = findctype(nm); | |
1574 if(cp->display) | |
1575 break; | |
1576 } | |
1577 if(nm != nil) | |
1578 quotecmd(nil, nm); | |
1579 } else if(strncmp(m->type, "multipart/", 10) == 0){ | |
1580 nm = m->child; | |
1581 if(nm != nil){ | |
1582 cp = findctype(nm); | |
1583 if(cp->display || strncmp(m->type, "multipart/",… | |
1584 quotecmd(nil, nm); | |
1585 } | |
1586 } | |
1587 return m; | |
1588 } | |
1589 | |
1590 /* really delete messages */ | |
1591 Message* | |
1592 flushdeleted(Message *cur) | |
1593 { | |
1594 Message *m, **l; | |
1595 char buf[1024], *p, *e, *msg; | |
1596 int deld, n; | |
1597 CFid *fd; | |
1598 int i; | |
1599 | |
1600 doflush = 0; | |
1601 deld = 0; | |
1602 | |
1603 snprint(buf, sizeof buf, "%s/ctl", mbname); | |
1604 fd = fsopen(mailfs, buf, OWRITE); | |
1605 if(fd == nil){ | |
1606 fprint(2, "!can't delete mail, opening %s: %r\n", buf); | |
1607 exitfs(0); | |
1608 } | |
1609 e = &buf[sizeof(buf)]; | |
1610 p = seprint(buf, e, "delete"); | |
1611 n = 0; | |
1612 for(l = &top.child; *l != nil;){ | |
1613 m = *l; | |
1614 if(!m->deleted){ | |
1615 l = &(*l)->next; | |
1616 continue; | |
1617 } | |
1618 | |
1619 /* don't return a pointer to a deleted message */ | |
1620 if(m == cur) | |
1621 cur = m->next; | |
1622 | |
1623 deld++; | |
1624 msg = strrchr(s_to_c(m->path), '/'); | |
1625 if(msg == nil) | |
1626 msg = s_to_c(m->path); | |
1627 else | |
1628 msg++; | |
1629 if(e-p < 10){ | |
1630 fswrite(fd, buf, p-buf); | |
1631 n = 0; | |
1632 p = seprint(buf, e, "delete"); | |
1633 } | |
1634 p = seprint(p, e, " %s", msg); | |
1635 n++; | |
1636 | |
1637 /* unchain and free */ | |
1638 *l = m->next; | |
1639 if(m->next) | |
1640 m->next->prev = m->prev; | |
1641 freemessage(m); | |
1642 } | |
1643 if(n) | |
1644 fswrite(fd, buf, p-buf); | |
1645 | |
1646 fsclose(fd); | |
1647 | |
1648 if(deld) | |
1649 Bprint(&out, "!%d message%s deleted\n", deld, plural(del… | |
1650 | |
1651 /* renumber */ | |
1652 i = 1; | |
1653 for(m = top.child; m != nil; m = m->next) | |
1654 m->id = natural ? m->fileno : i++; | |
1655 | |
1656 /* if we're out of messages, go back to first */ | |
1657 /* if no first, return the fake first */ | |
1658 if(cur == nil){ | |
1659 if(top.child) | |
1660 return top.child; | |
1661 else | |
1662 return ⊤ | |
1663 } | |
1664 return cur; | |
1665 } | |
1666 | |
1667 Message* | |
1668 qcmd(Cmd *x, Message *m) | |
1669 { | |
1670 USED(x); | |
1671 USED(m); | |
1672 | |
1673 flushdeleted(nil); | |
1674 | |
1675 if(didopen) | |
1676 closemb(); | |
1677 Bflush(&out); | |
1678 | |
1679 exitfs(0); | |
1680 return nil; /* not reached */ | |
1681 } | |
1682 | |
1683 Message* | |
1684 ycmd(Cmd *x, Message *m) | |
1685 { | |
1686 USED(x); | |
1687 | |
1688 doflush = 1; | |
1689 | |
1690 return icmd(nil, m); | |
1691 } | |
1692 | |
1693 Message* | |
1694 xcmd(Cmd *x, Message *m) | |
1695 { | |
1696 USED(x); | |
1697 USED(m); | |
1698 | |
1699 exitfs(0); | |
1700 return nil; /* not reached */ | |
1701 } | |
1702 | |
1703 Message* | |
1704 eqcmd(Cmd *x, Message *m) | |
1705 { | |
1706 USED(x); | |
1707 | |
1708 if(m == &top) | |
1709 Bprint(&out, "0\n"); | |
1710 else | |
1711 Bprint(&out, "%d\n", m->id); | |
1712 return nil; | |
1713 } | |
1714 | |
1715 Message* | |
1716 dcmd(Cmd *x, Message *m) | |
1717 { | |
1718 USED(x); | |
1719 | |
1720 if(m == &top){ | |
1721 Bprint(&out, "!address\n"); | |
1722 return nil; | |
1723 } | |
1724 while(m->parent != &top) | |
1725 m = m->parent; | |
1726 m->deleted = 1; | |
1727 return m; | |
1728 } | |
1729 | |
1730 Message* | |
1731 ucmd(Cmd *x, Message *m) | |
1732 { | |
1733 USED(x); | |
1734 | |
1735 if(m == &top) | |
1736 return nil; | |
1737 while(m->parent != &top) | |
1738 m = m->parent; | |
1739 if(m->deleted < 0) | |
1740 Bprint(&out, "!can't undelete, already flushed\n"); | |
1741 m->deleted = 0; | |
1742 return m; | |
1743 } | |
1744 | |
1745 | |
1746 Message* | |
1747 icmd(Cmd *x, Message *m) | |
1748 { | |
1749 int n; | |
1750 char buf[1024]; | |
1751 CFid *fd; | |
1752 | |
1753 USED(x); | |
1754 snprint(buf, sizeof buf, "%s/ctl", mbname); | |
1755 fd = fsopen(mailfs, buf, OWRITE); | |
1756 if(fd){ | |
1757 fswrite(fd, "refresh", 7); | |
1758 fsclose(fd); | |
1759 } | |
1760 n = dir2message(&top, reverse); | |
1761 if(n > 0) | |
1762 Bprint(&out, "%d new message%s\n", n, plural(n)); | |
1763 return m; | |
1764 } | |
1765 | |
1766 Message* | |
1767 helpcmd(Cmd *x, Message *m) | |
1768 { | |
1769 int i; | |
1770 | |
1771 USED(x); | |
1772 Bprint(&out, "Commands are of the form [<range>] <command> [args… | |
1773 Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n… | |
1774 Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | … | |
1775 Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'… | |
1776 Bprint(&out, "<command> :=\n"); | |
1777 for(i = 0; cmdtab[i].cmd != nil; i++) | |
1778 Bprint(&out, "%s\n", cmdtab[i].help); | |
1779 return m; | |
1780 } | |
1781 | |
1782 int | |
1783 tomailer(char **av) | |
1784 { | |
1785 static char *marshal; | |
1786 Waitmsg *w; | |
1787 int pid, i; | |
1788 | |
1789 if(marshal == nil) | |
1790 marshal = unsharp("#9/bin/upas/marshal"); | |
1791 | |
1792 /* start the mailer and get out of the way */ | |
1793 switch(pid = fork()){ | |
1794 case -1: | |
1795 fprint(2, "can't fork: %r\n"); | |
1796 return -1; | |
1797 case 0: | |
1798 Bprint(&out, "!%s", marshal); | |
1799 for(i = 1; av[i]; i++){ | |
1800 if(strchr(av[i], ' ') != nil) | |
1801 Bprint(&out, " '%s'", av[i]); | |
1802 else | |
1803 Bprint(&out, " %s", av[i]); | |
1804 } | |
1805 Bprint(&out, "\n"); | |
1806 Bflush(&out); | |
1807 av[0] = "marshal"; | |
1808 chdir(wd); | |
1809 exec(marshal, av); | |
1810 fprint(2, "couldn't exec %s\n", marshal); | |
1811 threadexits(0); | |
1812 default: | |
1813 w = wait(); | |
1814 if(w == nil){ | |
1815 if(interrupted) | |
1816 postnote(PNPROC, pid, "die"); | |
1817 waitpid(); | |
1818 return -1; | |
1819 } | |
1820 if(w->msg[0]){ | |
1821 fprint(2, "mailer failed: %s\n", w->msg); | |
1822 free(w); | |
1823 return -1; | |
1824 } | |
1825 free(w); | |
1826 Bprint(&out, "!\n"); | |
1827 break; | |
1828 } | |
1829 return 0; | |
1830 } | |
1831 | |
1832 /* */ | |
1833 /* like tokenize but obey "" quoting */ | |
1834 /* */ | |
1835 int | |
1836 tokenize822(char *str, char **args, int max) | |
1837 { | |
1838 int na; | |
1839 int intok = 0, inquote = 0; | |
1840 | |
1841 if(max <= 0) | |
1842 return 0; | |
1843 for(na=0; ;str++) | |
1844 switch(*str) { | |
1845 case ' ': | |
1846 case '\t': | |
1847 if(inquote) | |
1848 goto Default; | |
1849 /* fall through */ | |
1850 case '\n': | |
1851 *str = 0; | |
1852 if(!intok) | |
1853 continue; | |
1854 intok = 0; | |
1855 if(na < max) | |
1856 continue; | |
1857 /* fall through */ | |
1858 case 0: | |
1859 return na; | |
1860 case '"': | |
1861 inquote ^= 1; | |
1862 /* fall through */ | |
1863 Default: | |
1864 default: | |
1865 if(intok) | |
1866 continue; | |
1867 args[na++] = str; | |
1868 intok = 1; | |
1869 } | |
1870 return 0; /* can't get here; silence compiler */ | |
1871 } | |
1872 | |
1873 Message* | |
1874 rcmd(Cmd *c, Message *m) | |
1875 { | |
1876 char *av[128]; | |
1877 int i, ai = 1; | |
1878 Message *nm; | |
1879 char *addr; | |
1880 String *path = nil; | |
1881 String *rpath; | |
1882 String *subject = nil; | |
1883 String *from; | |
1884 | |
1885 if(m == &top){ | |
1886 Bprint(&out, "!address\n"); | |
1887 return nil; | |
1888 } | |
1889 | |
1890 addr = nil; | |
1891 for(nm = m; nm != ⊤ nm = nm->parent){ | |
1892 if(*nm->replyto != 0){ | |
1893 addr = nm->replyto; | |
1894 break; | |
1895 } | |
1896 } | |
1897 if(addr == nil){ | |
1898 Bprint(&out, "!no reply address\n"); | |
1899 return nil; | |
1900 } | |
1901 | |
1902 if(nm == &top){ | |
1903 print("!noone to reply to\n"); | |
1904 return nil; | |
1905 } | |
1906 | |
1907 for(nm = m; nm != ⊤ nm = nm->parent){ | |
1908 if(*nm->subject){ | |
1909 av[ai++] = "-s"; | |
1910 subject = addrecolon(nm->subject); | |
1911 av[ai++] = s_to_c(subject);; | |
1912 break; | |
1913 } | |
1914 } | |
1915 | |
1916 av[ai++] = "-R"; | |
1917 rpath = rooted(s_clone(m->path)); | |
1918 av[ai++] = s_to_c(rpath); | |
1919 | |
1920 if(strchr(c->av[0], 'f') != nil){ | |
1921 fcmd(c, m); | |
1922 av[ai++] = "-F"; | |
1923 } | |
1924 | |
1925 if(strchr(c->av[0], 'R') != nil){ | |
1926 av[ai++] = "-t"; | |
1927 av[ai++] = "message/rfc822"; | |
1928 av[ai++] = "-A"; | |
1929 path = rooted(extendpath(m->path, "raw")); | |
1930 av[ai++] = s_to_c(path); | |
1931 } | |
1932 | |
1933 for(i = 1; i < c->an && ai < nelem(av)-1; i++) | |
1934 av[ai++] = c->av[i]; | |
1935 from = s_copy(addr); | |
1936 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); | |
1937 av[ai] = 0; | |
1938 if(tomailer(av) < 0) | |
1939 m = nil; | |
1940 s_free(path); | |
1941 s_free(rpath); | |
1942 s_free(subject); | |
1943 s_free(from); | |
1944 return m; | |
1945 } | |
1946 | |
1947 Message* | |
1948 mcmd(Cmd *c, Message *m) | |
1949 { | |
1950 char **av; | |
1951 int i, ai; | |
1952 String *path; | |
1953 | |
1954 if(m == &top){ | |
1955 Bprint(&out, "!address\n"); | |
1956 return nil; | |
1957 } | |
1958 | |
1959 if(c->an < 2){ | |
1960 fprint(2, "!usage: M list-of addresses\n"); | |
1961 return nil; | |
1962 } | |
1963 | |
1964 ai = 1; | |
1965 av = malloc(sizeof(char*)*(c->an + 8)); | |
1966 | |
1967 av[ai++] = "-t"; | |
1968 if(m->parent == &top) | |
1969 av[ai++] = "message/rfc822"; | |
1970 else | |
1971 av[ai++] = "mime"; | |
1972 | |
1973 av[ai++] = "-A"; | |
1974 path = rooted(extendpath(m->path, "raw")); | |
1975 av[ai++] = s_to_c(path); | |
1976 | |
1977 if(strchr(c->av[0], 'M') == nil) | |
1978 av[ai++] = "-n"; | |
1979 | |
1980 for(i = 1; i < c->an; i++) | |
1981 av[ai++] = c->av[i]; | |
1982 av[ai] = 0; | |
1983 | |
1984 if(tomailer(av) < 0) | |
1985 m = nil; | |
1986 if(path != nil) | |
1987 s_free(path); | |
1988 free(av); | |
1989 return m; | |
1990 } | |
1991 | |
1992 Message* | |
1993 acmd(Cmd *c, Message *m) | |
1994 { | |
1995 char *av[128]; | |
1996 int i, ai; | |
1997 String *from, *to, *cc, *path = nil, *subject = nil; | |
1998 | |
1999 if(m == &top){ | |
2000 Bprint(&out, "!address\n"); | |
2001 return nil; | |
2002 } | |
2003 | |
2004 ai = 1; | |
2005 if(*m->subject){ | |
2006 av[ai++] = "-s"; | |
2007 subject = addrecolon(m->subject); | |
2008 av[ai++] = s_to_c(subject); | |
2009 } | |
2010 | |
2011 if(strchr(c->av[0], 'A') != nil){ | |
2012 av[ai++] = "-t"; | |
2013 av[ai++] = "message/rfc822"; | |
2014 av[ai++] = "-A"; | |
2015 path = rooted(extendpath(m->path, "raw")); | |
2016 av[ai++] = s_to_c(path); | |
2017 } | |
2018 | |
2019 for(i = 1; i < c->an && ai < nelem(av)-1; i++) | |
2020 av[ai++] = c->av[i]; | |
2021 from = s_copy(m->from); | |
2022 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); | |
2023 to = s_copy(m->to); | |
2024 ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai); | |
2025 cc = s_copy(m->cc); | |
2026 ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai); | |
2027 av[ai] = 0; | |
2028 if(tomailer(av) < 0) | |
2029 return nil; | |
2030 s_free(from); | |
2031 s_free(to); | |
2032 s_free(cc); | |
2033 s_free(subject); | |
2034 s_free(path); | |
2035 return m; | |
2036 } | |
2037 | |
2038 String * | |
2039 relpath(char *path, String *to) | |
2040 { | |
2041 if (*path=='/' || strncmp(path, "./", 2) == 0 | |
2042 || strncmp(path, "../", 3) == 0) { | |
2043 to = s_append(to, path); | |
2044 } else if(mbpath) { | |
2045 to = s_append(to, s_to_c(mbpath)); | |
2046 to->ptr = strrchr(to->base, '/')+1; | |
2047 s_append(to, path); | |
2048 } | |
2049 return to; | |
2050 } | |
2051 | |
2052 int | |
2053 appendtofile(Message *m, char *part, char *base, int mbox) | |
2054 { | |
2055 String *file, *h; | |
2056 int in, out, rv; | |
2057 | |
2058 file = extendpath(m->path, part); | |
2059 in = open(s_to_c(file), OREAD); | |
2060 if(in < 0){ | |
2061 fprint(2, "!message disappeared\n"); | |
2062 return -1; | |
2063 } | |
2064 | |
2065 s_reset(file); | |
2066 | |
2067 relpath(base, file); | |
2068 if(sysisdir(s_to_c(file))){ | |
2069 s_append(file, "/"); | |
2070 if(m->filename && strchr(m->filename, '/') == nil) | |
2071 s_append(file, m->filename); | |
2072 else { | |
2073 s_append(file, "att.XXXXXXXXXXX"); | |
2074 mktemp(s_to_c(file)); | |
2075 } | |
2076 } | |
2077 if(mbox) | |
2078 out = open(s_to_c(file), OWRITE); | |
2079 else | |
2080 out = open(s_to_c(file), OWRITE|OTRUNC); | |
2081 if(out < 0){ | |
2082 out = create(s_to_c(file), OWRITE, 0666); | |
2083 if(out < 0){ | |
2084 fprint(2, "!can't open %s: %r\n", s_to_c(file)); | |
2085 close(in); | |
2086 s_free(file); | |
2087 return -1; | |
2088 } | |
2089 } | |
2090 if(mbox) | |
2091 seek(out, 0, 2); | |
2092 | |
2093 /* put on a 'From ' line */ | |
2094 if(mbox){ | |
2095 while(m->parent != &top) | |
2096 m = m->parent; | |
2097 h = file2string(m->path, "unixheader"); | |
2098 fprint(out, "%s", s_to_c(h)); | |
2099 s_free(h); | |
2100 } | |
2101 | |
2102 /* copy the message escaping what we have to ad adding newlines … | |
2103 if(mbox) | |
2104 rv = appendfiletombox(in, out); | |
2105 else | |
2106 rv = appendfiletofile(in, out); | |
2107 | |
2108 close(in); | |
2109 close(out); | |
2110 | |
2111 if(rv >= 0) | |
2112 print("!saved in %s\n", s_to_c(file)); | |
2113 s_free(file); | |
2114 return rv; | |
2115 } | |
2116 | |
2117 Message* | |
2118 scmd(Cmd *c, Message *m) | |
2119 { | |
2120 char buf[256]; | |
2121 CFid *fd; | |
2122 char *file, *msg; | |
2123 | |
2124 if(m == &top){ | |
2125 Bprint(&out, "!address\n"); | |
2126 return nil; | |
2127 } | |
2128 | |
2129 switch(c->an){ | |
2130 case 1: | |
2131 file = "stored"; | |
2132 break; | |
2133 case 2: | |
2134 file = c->av[1]; | |
2135 break; | |
2136 default: | |
2137 fprint(2, "!usage: s filename\n"); | |
2138 return nil; | |
2139 } | |
2140 | |
2141 if(file[0] == '/' || (file[0]=='.' && file[1]=='/')){ | |
2142 if(appendtofile(m, "raw", file, 1) < 0) | |
2143 return nil; | |
2144 }else{ | |
2145 snprint(buf, sizeof buf, "%s/ctl", mbname); | |
2146 if((fd = fsopen(mailfs, buf, OWRITE)) == nil) | |
2147 return nil; | |
2148 msg = strrchr(s_to_c(m->path), '/'); | |
2149 if(msg == nil) | |
2150 msg = s_to_c(m->path); | |
2151 else | |
2152 msg++; | |
2153 if(fsprint(fd, "save %s %s", file, msg) < 0){ | |
2154 fsclose(fd); | |
2155 return nil; | |
2156 } | |
2157 fsclose(fd); | |
2158 } | |
2159 m->stored = 1; | |
2160 return m; | |
2161 } | |
2162 | |
2163 Message* | |
2164 wcmd(Cmd *c, Message *m) | |
2165 { | |
2166 char *file; | |
2167 | |
2168 if(m == &top){ | |
2169 Bprint(&out, "!address\n"); | |
2170 return nil; | |
2171 } | |
2172 | |
2173 switch(c->an){ | |
2174 case 2: | |
2175 file = c->av[1]; | |
2176 break; | |
2177 case 1: | |
2178 if(*m->filename == 0){ | |
2179 fprint(2, "!usage: w filename\n"); | |
2180 return nil; | |
2181 } | |
2182 file = strrchr(m->filename, '/'); | |
2183 if(file != nil) | |
2184 file++; | |
2185 else | |
2186 file = m->filename; | |
2187 break; | |
2188 default: | |
2189 fprint(2, "!usage: w filename\n"); | |
2190 return nil; | |
2191 } | |
2192 | |
2193 if(appendtofile(m, "body", file, 0) < 0) | |
2194 return nil; | |
2195 m->stored = 1; | |
2196 return m; | |
2197 } | |
2198 | |
2199 char *specialfile[] = | |
2200 { | |
2201 "pipeto", | |
2202 "pipefrom", | |
2203 "L.mbox", | |
2204 "forward", | |
2205 "names" | |
2206 }; | |
2207 | |
2208 /* return 1 if this is a special file */ | |
2209 static int | |
2210 special(String *s) | |
2211 { | |
2212 char *p; | |
2213 int i; | |
2214 | |
2215 p = strrchr(s_to_c(s), '/'); | |
2216 if(p == nil) | |
2217 p = s_to_c(s); | |
2218 else | |
2219 p++; | |
2220 for(i = 0; i < nelem(specialfile); i++) | |
2221 if(strcmp(p, specialfile[i]) == 0) | |
2222 return 1; | |
2223 return 0; | |
2224 } | |
2225 | |
2226 /* open the folder using the recipients account name */ | |
2227 static String* | |
2228 foldername(char *rcvr) | |
2229 { | |
2230 char *p; | |
2231 int c; | |
2232 String *file; | |
2233 Dir *d; | |
2234 int scarey; | |
2235 | |
2236 file = s_new(); | |
2237 mboxpath("f", user, file, 0); | |
2238 d = dirstat(s_to_c(file)); | |
2239 | |
2240 /* if $mail/f exists, store there, otherwise in $mail */ | |
2241 s_restart(file); | |
2242 if(d && d->qid.type == QTDIR){ | |
2243 scarey = 0; | |
2244 s_append(file, "f/"); | |
2245 } else { | |
2246 scarey = 1; | |
2247 } | |
2248 free(d); | |
2249 | |
2250 p = strrchr(rcvr, '!'); | |
2251 if(p != nil) | |
2252 rcvr = p+1; | |
2253 | |
2254 while(*rcvr && *rcvr != '@'){ | |
2255 c = *rcvr++; | |
2256 if(c == '/') | |
2257 c = '_'; | |
2258 s_putc(file, c); | |
2259 } | |
2260 s_terminate(file); | |
2261 | |
2262 if(scarey && special(file)){ | |
2263 fprint(2, "!won't overwrite %s\n", s_to_c(file)); | |
2264 s_free(file); | |
2265 return nil; | |
2266 } | |
2267 | |
2268 return file; | |
2269 } | |
2270 | |
2271 Message* | |
2272 fcmd(Cmd *c, Message *m) | |
2273 { | |
2274 String *folder; | |
2275 | |
2276 if(c->an > 1){ | |
2277 fprint(2, "!usage: f takes no arguments\n"); | |
2278 return nil; | |
2279 } | |
2280 | |
2281 if(m == &top){ | |
2282 Bprint(&out, "!address\n"); | |
2283 return nil; | |
2284 } | |
2285 | |
2286 folder = foldername(m->from); | |
2287 if(folder == nil) | |
2288 return nil; | |
2289 | |
2290 if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){ | |
2291 s_free(folder); | |
2292 return nil; | |
2293 } | |
2294 s_free(folder); | |
2295 | |
2296 m->stored = 1; | |
2297 return m; | |
2298 } | |
2299 | |
2300 void | |
2301 system(char *cmd, char **av, int in) | |
2302 { | |
2303 int pid; | |
2304 | |
2305 switch(pid=fork()){ | |
2306 case -1: | |
2307 return; | |
2308 case 0: | |
2309 if(strcmp(cmd, "rc") == 0) | |
2310 cmd = unsharp("#9/bin/rc"); | |
2311 if(in >= 0){ | |
2312 close(0); | |
2313 dup(in, 0); | |
2314 close(in); | |
2315 } | |
2316 if(wd[0] != 0) | |
2317 chdir(wd); | |
2318 exec(cmd, av); | |
2319 fprint(2, "!couldn't exec %s\n", cmd); | |
2320 threadexits(0); | |
2321 default: | |
2322 if(in >= 0) | |
2323 close(in); | |
2324 while(waitpid() < 0){ | |
2325 if(!interrupted) | |
2326 break; | |
2327 postnote(PNPROC, pid, "die"); | |
2328 continue; | |
2329 } | |
2330 break; | |
2331 } | |
2332 } | |
2333 | |
2334 Message* | |
2335 bangcmd(Cmd *c, Message *m) | |
2336 { | |
2337 char cmd[4*1024]; | |
2338 char *p, *e; | |
2339 char *av[4]; | |
2340 int i; | |
2341 | |
2342 cmd[0] = 0; | |
2343 p = cmd; | |
2344 e = cmd+sizeof(cmd); | |
2345 for(i = 1; i < c->an; i++) | |
2346 p = seprint(p, e, "%s ", c->av[i]); | |
2347 av[0] = "rc"; | |
2348 av[1] = "-c"; | |
2349 av[2] = cmd; | |
2350 av[3] = 0; | |
2351 system("rc", av, -1); | |
2352 Bprint(&out, "!\n"); | |
2353 return m; | |
2354 } | |
2355 | |
2356 Message* | |
2357 xpipecmd(Cmd *c, Message *m, char *part) | |
2358 { | |
2359 char cmd[128]; | |
2360 char *p, *e; | |
2361 char *av[4]; | |
2362 String *path; | |
2363 int i, fd; | |
2364 | |
2365 if(c->an < 2){ | |
2366 Bprint(&out, "!usage: | cmd\n"); | |
2367 return nil; | |
2368 } | |
2369 | |
2370 if(m == &top){ | |
2371 Bprint(&out, "!address\n"); | |
2372 return nil; | |
2373 } | |
2374 | |
2375 path = extendpath(m->path, part); | |
2376 fd = fsopenfd(mailfs, s_to_c(path), OREAD); | |
2377 s_free(path); | |
2378 | |
2379 if(fd < 0){ /* compatibility with older upas/fs */ | |
2380 path = extendpath(m->path, "raw"); | |
2381 fd = fsopenfd(mailfs, s_to_c(path), OREAD); | |
2382 s_free(path); | |
2383 } | |
2384 if(fd < 0){ | |
2385 fprint(2, "!message disappeared\n"); | |
2386 return nil; | |
2387 } | |
2388 | |
2389 p = cmd; | |
2390 e = cmd+sizeof(cmd); | |
2391 cmd[0] = 0; | |
2392 for(i = 1; i < c->an; i++) | |
2393 p = seprint(p, e, "%s ", c->av[i]); | |
2394 av[0] = "rc"; | |
2395 av[1] = "-c"; | |
2396 av[2] = cmd; | |
2397 av[3] = 0; | |
2398 system("rc", av, fd); /* system closes fd */ | |
2399 Bprint(&out, "!\n"); | |
2400 return m; | |
2401 } | |
2402 | |
2403 Message* | |
2404 pipecmd(Cmd *c, Message *m) | |
2405 { | |
2406 return xpipecmd(c, m, "body"); | |
2407 } | |
2408 | |
2409 Message* | |
2410 rpipecmd(Cmd *c, Message *m) | |
2411 { | |
2412 return xpipecmd(c, m, "rawunix"); | |
2413 } | |
2414 | |
2415 void | |
2416 closemb(void) | |
2417 { | |
2418 CFid *fd; | |
2419 | |
2420 fd = fsopen(mailfs, "ctl", OWRITE); | |
2421 if(fd == nil) | |
2422 sysfatal("can't open ctl: %r"); | |
2423 | |
2424 /* close current mailbox */ | |
2425 if(*mbname && strcmp(mbname, "mbox") != 0) | |
2426 fsprint(fd, "close %s", mbname); | |
2427 | |
2428 fsclose(fd); | |
2429 } | |
2430 | |
2431 int | |
2432 switchmb(char *file, char *singleton) | |
2433 { | |
2434 char *p; | |
2435 int n, fd; | |
2436 String *path; | |
2437 char buf[256]; | |
2438 | |
2439 /* if the user didn't say anything and there */ | |
2440 /* is an mbox mounted already, use that one */ | |
2441 /* so that the upas/fs -fdefault default is honored. */ | |
2442 if(0 && (file || (singleton && fsaccess(mailfs, singleton, 0) < … | |
2443 /* XXX all wrong */ | |
2444 fprint(2, "file=%s singleton=%s\n", file, singleton); | |
2445 if(file == nil) | |
2446 file = "mbox"; | |
2447 | |
2448 /* close current mailbox */ | |
2449 closemb(); | |
2450 didopen = 1; | |
2451 | |
2452 fd = open("/mail/fs/ctl", ORDWR); | |
2453 if(fd < 0) | |
2454 sysfatal("can't open /mail/fs/ctl: %r"); | |
2455 | |
2456 path = s_new(); | |
2457 | |
2458 /* get an absolute path to the mail box */ | |
2459 if(strncmp(file, "./", 2) == 0){ | |
2460 /* resolve path here since upas/fs doesn't know … | |
2461 /* our working directory */ | |
2462 if(getwd(buf, sizeof(buf)-strlen(file)) == nil){ | |
2463 fprint(2, "!can't get working directory:… | |
2464 return -1; | |
2465 } | |
2466 s_append(path, buf); | |
2467 s_append(path, file+1); | |
2468 } else { | |
2469 mboxpath(file, user, path, 0); | |
2470 } | |
2471 | |
2472 /* make up a handle to use when talking to fs */ | |
2473 p = strrchr(file, '/'); | |
2474 if(p == nil){ | |
2475 /* if its in the mailbox directory, just use the… | |
2476 strncpy(mbname, file, sizeof(mbname)); | |
2477 mbname[sizeof(mbname)-1] = 0; | |
2478 } else { | |
2479 /* make up a mailbox name */ | |
2480 p = strrchr(s_to_c(path), '/'); | |
2481 p++; | |
2482 if(*p == 0){ | |
2483 fprint(2, "!bad mbox name"); | |
2484 return -1; | |
2485 } | |
2486 strncpy(mbname, p, sizeof(mbname)); | |
2487 mbname[sizeof(mbname)-1] = 0; | |
2488 n = strlen(mbname); | |
2489 if(n > Elemlen-12) | |
2490 n = Elemlen-12; | |
2491 sprint(mbname+n, "%ld", time(0)); | |
2492 } | |
2493 | |
2494 if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){ | |
2495 fprint(2, "!can't 'open %s %s': %r\n", file, mbn… | |
2496 s_free(path); | |
2497 return -1; | |
2498 } | |
2499 close(fd); | |
2500 }else | |
2501 if (singleton && fsaccess(mailfs, singleton, 0)==0){ | |
2502 if ((p = strchr(singleton, '/')) == nil){ | |
2503 fprint(2, "!bad mbox name"); | |
2504 return -1; | |
2505 } | |
2506 n = p-singleton; | |
2507 strncpy(mbname, singleton, n); | |
2508 mbname[n+1] = 0; | |
2509 path = s_reset(nil); | |
2510 mboxpath(mbname, user, path, 0); | |
2511 }else{ | |
2512 if(file) | |
2513 strecpy(mbname, mbname+sizeof mbname, file); | |
2514 else | |
2515 strcpy(mbname, "mbox"); | |
2516 path = s_reset(nil); | |
2517 mboxpath(mbname, user, path, 0); | |
2518 } | |
2519 | |
2520 snprint(root, sizeof root, "%s", mbname); | |
2521 rootlen = strlen(root); | |
2522 | |
2523 if(mbpath != nil) | |
2524 s_free(mbpath); | |
2525 mbpath = path; | |
2526 return 0; | |
2527 } | |
2528 | |
2529 /* like tokenize but for into lines */ | |
2530 int | |
2531 lineize(char *s, char **f, int n) | |
2532 { | |
2533 int i; | |
2534 | |
2535 for(i = 0; *s && i < n; i++){ | |
2536 f[i] = s; | |
2537 s = strchr(s, '\n'); | |
2538 if(s == nil) | |
2539 break; | |
2540 *s++ = 0; | |
2541 } | |
2542 return i; | |
2543 } | |
2544 | |
2545 | |
2546 | |
2547 String* | |
2548 rooted(String *s) | |
2549 { | |
2550 static char buf[256]; | |
2551 | |
2552 if(strcmp(root, ".") != 0) | |
2553 return s; | |
2554 snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s)); | |
2555 s_free(s); | |
2556 return s_copy(buf); | |
2557 } | |
2558 | |
2559 int | |
2560 plumb(Message *m, Ctype *cp) | |
2561 { | |
2562 String *s; | |
2563 Plumbmsg *pm; | |
2564 static int fd = -2; | |
2565 | |
2566 if(cp->plumbdest == nil) | |
2567 return -1; | |
2568 | |
2569 if(fd < -1) | |
2570 fd = plumbopen("send", OWRITE); | |
2571 if(fd < 0) | |
2572 return -1; | |
2573 | |
2574 pm = mallocz(sizeof(Plumbmsg), 1); | |
2575 pm->src = strdup("mail"); | |
2576 if(*cp->plumbdest) | |
2577 pm->dst = strdup(cp->plumbdest); | |
2578 pm->wdir = nil; | |
2579 pm->type = strdup("text"); | |
2580 pm->ndata = -1; | |
2581 s = rooted(extendpath(m->path, "body")); | |
2582 if(cp->ext != nil){ | |
2583 s_append(s, "."); | |
2584 s_append(s, cp->ext); | |
2585 } | |
2586 pm->data = strdup(s_to_c(s)); | |
2587 s_free(s); | |
2588 plumbsend(fd, pm); | |
2589 plumbfree(pm); | |
2590 return 0; | |
2591 } | |
2592 | |
2593 void | |
2594 regerror(char *x) | |
2595 { | |
2596 USED(x); | |
2597 } | |
2598 | |
2599 String* | |
2600 addrecolon(char *s) | |
2601 { | |
2602 String *str; | |
2603 | |
2604 if(cistrncmp(s, "re:", 3) != 0){ | |
2605 str = s_copy("Re: "); | |
2606 s_append(str, s); | |
2607 } else | |
2608 str = s_copy(s); | |
2609 return str; | |
2610 } | |
2611 | |
2612 void | |
2613 exitfs(char *rv) | |
2614 { | |
2615 threadexitsall(rv); | |
2616 } |