1 #include <u.h> 2 #include <libc.h> 3 #include <bio.h> 4 #include <thread.h> 5 #include <ctype.h> 6 #include <plumb.h> 7 #include "dat.h" 8 9 static int replyid; 10 11 int 12 quote(Message *m, Biobuf *b, char *dir, char *quotetext) 13 { 14 char *body, *type; 15 int i, n, nlines; 16 char **lines; 17 18 if(quotetext){ 19 body = quotetext; 20 n = strlen(body); 21 type = nil; 22 }else{ 23 /* look for first textual component to quote */ 24 type = readfile(dir, "type", &n); 25 if(type == nil){ 26 print("no type in %s\n", dir); 27 return 0; 28 } 29 if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){ 30 dir = estrstrdup(dir, "1/"); 31 if(quote(m, b, dir, nil)){ 32 free(type); 33 free(dir); 34 return 1; 35 } 36 free(dir); 37 } 38 if(strncmp(type, "text", 4) != 0){ 39 free(type); 40 return 0; 41 } 42 body = readbody(m->type, dir, &n); 43 if(body == nil) 44 return 0; 45 } 46 nlines = 0; 47 for(i=0; i<n; i++) 48 if(body[i] == '\n') 49 nlines++; 50 nlines++; 51 lines = emalloc(nlines*sizeof(char*)); 52 nlines = getfields(body, lines, nlines, 0, "\n"); 53 /* delete leading and trailing blank lines */ 54 i = 0; 55 while(i<nlines && lines[i][0]=='\0') 56 i++; 57 while(i<nlines && lines[nlines-1][0]=='\0') 58 nlines--; 59 while(i < nlines){ 60 Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]); 61 i++; 62 } 63 free(lines); 64 free(body); /* will free quotetext if non-nil */ 65 free(type); 66 return 1; 67 } 68 69 void 70 mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext) 71 { 72 Message *r; 73 char *dir, *t; 74 int quotereply; 75 Plumbattr *a; 76 77 quotereply = (label[0] == 'Q'); 78 r = emalloc(sizeof(Message)); 79 r->isreply = 1; 80 if(m != nil) 81 r->replyname = estrdup(m->name); 82 r->next = replies.head; 83 r->prev = nil; 84 if(replies.head != nil) 85 replies.head->prev = r; 86 replies.head = r; 87 if(replies.tail == nil) 88 replies.tail = r; 89 r->name = emalloc(strlen(mbox.name)+strlen(label)+10); 90 sprint(r->name, "%s%s%d", mbox.name, label, ++replyid); 91 r->w = newwindow(); 92 winname(r->w, r->name); 93 wintagwrite(r->w, "|fmt Post", 5+4); 94 r->tagposted = 1; 95 threadcreate(mesgctl, r, STACK); 96 winopenbody(r->w, OWRITE); 97 if(to!=nil && to[0]!='\0') 98 Bprint(r->w->body, "%s\n", to); 99 for(a=attr; a; a=a->next) 100 Bprint(r->w->body, "%s: %s\n", a->name, a->value); 101 dir = nil; 102 if(m != nil){ 103 dir = estrstrdup(mbox.name, m->name); 104 if(to == nil && attr == nil){ 105 /* Reply goes to replyto; Reply all goes to From and To and CC */ 106 if(strstr(label, "all") == nil) 107 Bprint(r->w->body, "To: %s\n", m->replyto); 108 else{ /* Replyall */ 109 if(strlen(m->from) > 0) 110 Bprint(r->w->body, "To: %s\n", m->from); 111 if(strlen(m->to) > 0) 112 Bprint(r->w->body, "To: %s\n", m->to); 113 if(strlen(m->cc) > 0) 114 Bprint(r->w->body, "CC: %s\n", m->cc); 115 } 116 } 117 if(strlen(m->subject) > 0){ 118 t = "Subject: Re: "; 119 if(strlen(m->subject) >= 3) 120 if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':') 121 t = "Subject: "; 122 Bprint(r->w->body, "%s%s\n", t, m->subject); 123 } 124 if(!quotereply){ 125 Bprint(r->w->body, "Include: %sraw\n", dir); 126 free(dir); 127 } 128 } 129 Bprint(r->w->body, "\n"); 130 if(m == nil) 131 Bprint(r->w->body, "\n"); 132 else if(quotereply){ 133 quote(m, r->w->body, dir, quotetext); 134 free(dir); 135 } 136 winclosebody(r->w); 137 if(m==nil && (to==nil || to[0]=='\0')) 138 winselect(r->w, "0", 0); 139 else 140 winselect(r->w, "$", 0); 141 winclean(r->w); 142 windormant(r->w); 143 } 144 145 void 146 delreply(Message *m) 147 { 148 if(m->next == nil) 149 replies.tail = m->prev; 150 else 151 m->next->prev = m->prev; 152 if(m->prev == nil) 153 replies.head = m->next; 154 else 155 m->prev->next = m->next; 156 mesgfreeparts(m); 157 free(m); 158 } 159 160 /* copy argv to stack and free the incoming strings, so we don't leak argument vectors */ 161 void 162 buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR]) 163 { 164 int i, n; 165 char *s, *a; 166 167 s = args; 168 for(i=0; i<NARGS; i++){ 169 a = inargv[i]; 170 if(a == nil) 171 break; 172 n = strlen(a)+1; 173 if((s-args)+n >= NARGCHAR) /* too many characters */ 174 break; 175 argv[i] = s; 176 memmove(s, a, n); 177 s += n; 178 free(a); 179 } 180 argv[i] = nil; 181 } 182 183 void 184 execproc(void *v) 185 { 186 struct Exec *e; 187 int p[2], q[2]; 188 char *prog; 189 char *argv[NARGS+1], args[NARGCHAR]; 190 191 e = v; 192 p[0] = e->p[0]; 193 p[1] = e->p[1]; 194 q[0] = e->q[0]; 195 q[1] = e->q[1]; 196 prog = e->prog; /* known not to be malloc'ed */ 197 rfork(RFFDG); 198 sendul(e->sync, 1); 199 buildargv(e->argv, argv, args); 200 free(e->argv); 201 chanfree(e->sync); 202 free(e); 203 dup(p[0], 0); 204 close(p[0]); 205 close(p[1]); 206 if(q[0]){ 207 dup(q[1], 1); 208 close(q[0]); 209 close(q[1]); 210 } 211 procexec(nil, prog, argv); 212 //fprint(2, "exec: %s", e->prog); 213 //{int i; 214 //for(i=0; argv[i]; i++) print(" '%s'", argv[i]); 215 //print("\n"); 216 //} 217 //argv[0] = "cat"; 218 //argv[1] = nil; 219 //procexec(nil, "/bin/cat", argv); 220 fprint(2, "Mail: can't exec %s: %r\n", prog); 221 threadexits("can't exec"); 222 } 223 224 enum{ 225 ATTACH, 226 BCC, 227 CC, 228 FROM, 229 INCLUDE, 230 TO, 231 }; 232 233 char *headers[] = { 234 "attach:", 235 "bcc:", 236 "cc:", 237 "from:", 238 "include:", 239 "to:", 240 nil, 241 }; 242 243 int 244 whichheader(char *h) 245 { 246 int i; 247 248 for(i=0; headers[i]!=nil; i++) 249 if(cistrcmp(h, headers[i]) == 0) 250 return i; 251 return -1; 252 } 253 254 char *tolist[200]; 255 char *cclist[200]; 256 char *bcclist[200]; 257 int ncc, nbcc, nto; 258 char *attlist[200]; 259 char included[200]; 260 261 int 262 addressed(char *name) 263 { 264 int i; 265 266 for(i=0; i<nto; i++) 267 if(strcmp(name, tolist[i]) == 0) 268 return 1; 269 for(i=0; i<ncc; i++) 270 if(strcmp(name, cclist[i]) == 0) 271 return 1; 272 for(i=0; i<nbcc; i++) 273 if(strcmp(name, bcclist[i]) == 0) 274 return 1; 275 return 0; 276 } 277 278 char* 279 skipbl(char *s, char *e) 280 { 281 while(s < e){ 282 if(*s!=' ' && *s!='\t' && *s!=',') 283 break; 284 s++; 285 } 286 return s; 287 } 288 289 char* 290 findbl(char *s, char *e) 291 { 292 while(s < e){ 293 if(*s==' ' || *s=='\t' || *s==',') 294 break; 295 s++; 296 } 297 return s; 298 } 299 300 /* 301 * comma-separate possibly blank-separated strings in line; e points before newline 302 */ 303 void 304 commas(char *s, char *e) 305 { 306 char *t; 307 308 /* may have initial blanks */ 309 s = skipbl(s, e); 310 while(s < e){ 311 s = findbl(s, e); 312 if(s == e) 313 break; 314 t = skipbl(s, e); 315 if(t == e) /* no more words */ 316 break; 317 /* patch comma */ 318 *s++ = ','; 319 while(s < t) 320 *s++ = ' '; 321 } 322 } 323 324 int 325 print2(int fd, int ofd, char *fmt, ...) 326 { 327 int m, n; 328 char *s; 329 va_list arg; 330 331 va_start(arg, fmt); 332 s = vsmprint(fmt, arg); 333 va_end(arg); 334 if(s == nil) 335 return -1; 336 m = strlen(s); 337 n = write(fd, s, m); 338 if(ofd > 0) 339 write(ofd, s, m); 340 return n; 341 } 342 343 void 344 write2(int fd, int ofd, char *buf, int n, int nofrom) 345 { 346 char *from, *p; 347 int m; 348 349 write(fd, buf, n); 350 351 if(ofd <= 0) 352 return; 353 354 if(nofrom == 0){ 355 write(ofd, buf, n); 356 return; 357 } 358 359 /* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */ 360 for(p=buf; *p; p+=m){ 361 from = cistrstr(p, "from"); 362 if(from == nil) 363 m = n; 364 else 365 m = from - p; 366 if(m > 0) 367 write(ofd, p, m); 368 if(from){ 369 if(p==buf || from[-1]=='\n') 370 write(ofd, " ", 1); /* escape with space if From is at start of line */ 371 write(ofd, from, 4); 372 m += 4; 373 } 374 n -= m; 375 } 376 } 377 378 void 379 mesgsend(Message *m) 380 { 381 char *s, *body, *to; 382 int i, j, h, n, natt, p[2]; 383 struct Exec *e; 384 Channel *sync; 385 int first, nfld, delit, ofd; 386 char *copy, *fld[100], *now; 387 388 body = winreadbody(m->w, &n); 389 /* assemble to: list from first line, to: line, and cc: line */ 390 nto = 0; 391 natt = 0; 392 ncc = 0; 393 nbcc = 0; 394 first = 1; 395 to = body; 396 for(;;){ 397 for(s=to; *s!='\n'; s++) 398 if(*s == '\0'){ 399 free(body); 400 return; 401 } 402 if(s++ == to) /* blank line */ 403 break; 404 /* make copy of line to tokenize */ 405 copy = emalloc(s-to); 406 memmove(copy, to, s-to); 407 copy[s-to-1] = '\0'; 408 nfld = tokenizec(copy, fld, nelem(fld), ", \t"); 409 if(nfld == 0){ 410 free(copy); 411 break; 412 } 413 n -= s-to; 414 switch(h = whichheader(fld[0])){ 415 case TO: 416 case FROM: 417 delit = 1; 418 commas(to+strlen(fld[0]), s-1); 419 for(i=1; i<nfld && nto<nelem(tolist); i++) 420 if(!addressed(fld[i])) 421 tolist[nto++] = estrdup(fld[i]); 422 break; 423 case BCC: 424 delit = 1; 425 commas(to+strlen(fld[0]), s-1); 426 for(i=1; i<nfld && nbcc<nelem(bcclist); i++) 427 if(!addressed(fld[i])) 428 bcclist[nbcc++] = estrdup(fld[i]); 429 break; 430 case CC: 431 delit = 1; 432 commas(to+strlen(fld[0]), s-1); 433 for(i=1; i<nfld && ncc<nelem(cclist); i++) 434 if(!addressed(fld[i])) 435 cclist[ncc++] = estrdup(fld[i]); 436 break; 437 case ATTACH: 438 case INCLUDE: 439 delit = 1; 440 for(i=1; i<nfld && natt<nelem(attlist); i++){ 441 attlist[natt] = estrdup(fld[i]); 442 included[natt++] = (h == INCLUDE); 443 } 444 break; 445 default: 446 if(first){ 447 delit = 1; 448 for(i=0; i<nfld && nto<nelem(tolist); i++) 449 tolist[nto++] = estrdup(fld[i]); 450 }else /* ignore it */ 451 delit = 0; 452 break; 453 } 454 if(delit){ 455 /* delete line from body */ 456 memmove(to, s, n+1); 457 }else 458 to = s; 459 free(copy); 460 first = 0; 461 } 462 463 ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */ 464 if(ofd > 0){ 465 /* From dhog Fri Aug 24 22:13:00 EDT 2001 */ 466 now = ctime(time(0)); 467 fprint(ofd, "From %s %s", user, now); 468 fprint(ofd, "From: %s\n", user); 469 fprint(ofd, "Date: %s", now); 470 for(i=0; i<natt; i++) 471 if(included[i]) 472 fprint(ofd, "Include: %s\n", attlist[i]); 473 else 474 fprint(ofd, "Attach: %s\n", attlist[i]); 475 /* needed because mail is by default Latin-1 */ 476 fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n"); 477 fprint(ofd, "Content-Transfer-Encoding: 8bit\n"); 478 } 479 480 e = emalloc(sizeof(struct Exec)); 481 if(pipe(p) < 0) 482 error("can't create pipe: %r"); 483 e->p[0] = p[0]; 484 e->p[1] = p[1]; 485 e->prog = "/bin/upas/marshal"; 486 e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*)); 487 e->argv[0] = estrdup("marshal"); 488 e->argv[1] = estrdup("-8"); 489 j = 2; 490 if(m->replyname){ 491 e->argv[j++] = estrdup("-R"); 492 e->argv[j++] = estrstrdup(mbox.name, m->replyname); 493 } 494 for(i=0; i<natt; i++){ 495 if(included[i]) 496 e->argv[j++] = estrdup("-A"); 497 else 498 e->argv[j++] = estrdup("-a"); 499 e->argv[j++] = estrdup(attlist[i]); 500 } 501 sync = chancreate(sizeof(int), 0); 502 e->sync = sync; 503 proccreate(execproc, e, EXECSTACK); 504 recvul(sync); 505 close(p[0]); 506 507 /* using marshal -8, so generate rfc822 headers */ 508 if(nto > 0){ 509 print2(p[1], ofd, "To: "); 510 for(i=0; i<nto-1; i++) 511 print2(p[1], ofd, "%s, ", tolist[i]); 512 print2(p[1], ofd, "%s\n", tolist[i]); 513 } 514 if(ncc > 0){ 515 print2(p[1], ofd, "CC: "); 516 for(i=0; i<ncc-1; i++) 517 print2(p[1], ofd, "%s, ", cclist[i]); 518 print2(p[1], ofd, "%s\n", cclist[i]); 519 } 520 if(nbcc > 0){ 521 print2(p[1], ofd, "BCC: "); 522 for(i=0; i<nbcc-1; i++) 523 print2(p[1], ofd, "%s, ", bcclist[i]); 524 print2(p[1], ofd, "%s\n", bcclist[i]); 525 } 526 527 i = strlen(body); 528 if(i > 0) 529 write2(p[1], ofd, body, i, 1); 530 531 /* guarantee a blank line, to ensure attachments are separated from body */ 532 if(i==0 || body[i-1]!='\n') 533 write2(p[1], ofd, "\n\n", 2, 0); 534 else if(i>1 && body[i-2]!='\n') 535 write2(p[1], ofd, "\n", 1, 0); 536 537 /* these look like pseudo-attachments in the "outgoing" box */ 538 if(ofd>0 && natt>0){ 539 for(i=0; i<natt; i++) 540 if(included[i]) 541 fprint(ofd, "=====> Include: %s\n", attlist[i]); 542 else 543 fprint(ofd, "=====> Attach: %s\n", attlist[i]); 544 } 545 if(ofd > 0) 546 write(ofd, "\n", 1); 547 548 for(i=0; i<natt; i++) 549 free(attlist[i]); 550 close(ofd); 551 close(p[1]); 552 free(body); 553 554 if(m->replyname != nil) 555 mesgmenumark(mbox.w, m->replyname, "\t[replied]"); 556 if(m->name[0] == '/') 557 s = estrdup(m->name); 558 else 559 s = estrstrdup(mbox.name, m->name); 560 s = egrow(s, "-R", nil); 561 winname(m->w, s); 562 free(s); 563 winclean(m->w); 564 /* mark message unopened because it's no longer the original message */ 565 m->opened = 0; 566 } 567