pop3.c (13496B)
1 #include "common.h" 2 #include <ctype.h> 3 #include <plumb.h> 4 #include <libsec.h> 5 #include <auth.h> 6 #include <thread.h> 7 #include "dat.h" 8 9 #pragma varargck type "M" uchar* 10 #pragma varargck argpos pop3cmd 2 11 12 typedef struct Pop Pop; 13 struct Pop { 14 char *freep; /* free this to free the strings below */ 15 16 char *host; 17 char *user; 18 char *port; 19 20 int ppop; 21 int refreshtime; 22 int debug; 23 int pipeline; 24 int encrypted; 25 int needtls; 26 int notls; 27 int needssl; 28 29 /* open network connection */ 30 Biobuf bin; 31 Biobuf bout; 32 int fd; 33 char *lastline; /* from Brdstr */ 34 35 Thumbprint *thumb; 36 }; 37 38 char* 39 geterrstr(void) 40 { 41 static char err[64]; 42 43 err[0] = '\0'; 44 errstr(err, sizeof(err)); 45 return err; 46 } 47 48 /* */ 49 /* get pop3 response line , without worrying */ 50 /* about multiline responses; the clients */ 51 /* will deal with that. */ 52 /* */ 53 static int 54 isokay(char *s) 55 { 56 return s!=nil && strncmp(s, "+OK", 3)==0; 57 } 58 59 static void 60 pop3cmd(Pop *pop, char *fmt, ...) 61 { 62 char buf[128], *p; 63 va_list va; 64 65 va_start(va, fmt); 66 vseprint(buf, buf+sizeof(buf), fmt, va); 67 va_end(va); 68 69 p = buf+strlen(buf); 70 if(p > (buf+sizeof(buf)-3)) 71 sysfatal("pop3 command too long"); 72 73 if(pop->debug) 74 fprint(2, "<- %s\n", buf); 75 strcpy(p, "\r\n"); 76 Bwrite(&pop->bout, buf, strlen(buf)); 77 Bflush(&pop->bout); 78 } 79 80 static char* 81 pop3resp(Pop *pop) 82 { 83 char *s; 84 char *p; 85 86 alarm(60*1000); 87 if((s = Brdstr(&pop->bin, '\n', 0)) == nil){ 88 close(pop->fd); 89 pop->fd = -1; 90 alarm(0); 91 return "unexpected eof"; 92 } 93 alarm(0); 94 95 p = s+strlen(s)-1; 96 while(p >= s && (*p == '\r' || *p == '\n')) 97 *p-- = '\0'; 98 99 if(pop->debug) 100 fprint(2, "-> %s\n", s); 101 free(pop->lastline); 102 pop->lastline = s; 103 return s; 104 } 105 106 #if 0 /* jpc */ 107 static int 108 pop3log(char *fmt, ...) 109 { 110 va_list ap; 111 112 va_start(ap,fmt); 113 syslog(0, "/sys/log/pop3", fmt, ap); 114 va_end(ap); 115 return 0; 116 } 117 #endif 118 119 static char* 120 pop3pushtls(Pop *pop) 121 { 122 int fd; 123 uchar digest[SHA1dlen]; 124 TLSconn conn; 125 126 memset(&conn, 0, sizeof conn); 127 /* conn.trace = pop3log; */ 128 fd = tlsClient(pop->fd, &conn); 129 if(fd < 0) 130 return "tls error"; 131 if(conn.cert==nil || conn.certlen <= 0){ 132 close(fd); 133 return "server did not provide TLS certificate"; 134 } 135 sha1(conn.cert, conn.certlen, digest, nil); 136 if(!pop->thumb || !okThumbprint(digest, pop->thumb)){ 137 fmtinstall('H', encodefmt); 138 close(fd); 139 free(conn.cert); 140 fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest); 141 return "bad server certificate"; 142 } 143 free(conn.cert); 144 close(pop->fd); 145 pop->fd = fd; 146 pop->encrypted = 1; 147 Binit(&pop->bin, pop->fd, OREAD); 148 Binit(&pop->bout, pop->fd, OWRITE); 149 return nil; 150 } 151 152 /* */ 153 /* get capability list, possibly start tls */ 154 /* */ 155 static char* 156 pop3capa(Pop *pop) 157 { 158 char *s; 159 int hastls; 160 161 pop3cmd(pop, "CAPA"); 162 if(!isokay(pop3resp(pop))) 163 return nil; 164 165 hastls = 0; 166 for(;;){ 167 s = pop3resp(pop); 168 if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0) 169 break; 170 if(strcmp(s, "STLS") == 0) 171 hastls = 1; 172 if(strcmp(s, "PIPELINING") == 0) 173 pop->pipeline = 1; 174 } 175 176 if(hastls && !pop->notls){ 177 pop3cmd(pop, "STLS"); 178 if(!isokay(s = pop3resp(pop))) 179 return s; 180 if((s = pop3pushtls(pop)) != nil) 181 return s; 182 } 183 return nil; 184 } 185 186 /* */ 187 /* log in using APOP if possible, password if allowed by user */ 188 /* */ 189 static char* 190 pop3login(Pop *pop) 191 { 192 int n; 193 char *s, *p, *q; 194 char ubuf[128], user[128]; 195 char buf[500]; 196 UserPasswd *up; 197 198 s = pop3resp(pop); 199 if(!isokay(s)) 200 return "error in initial handshake"; 201 202 if(pop->user) 203 snprint(ubuf, sizeof ubuf, " user=%q", pop->user); 204 else 205 ubuf[0] = '\0'; 206 207 /* look for apop banner */ 208 if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) { 209 *++q = '\0'; 210 if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s", 211 pop->host, ubuf)) < 0) 212 return "factotum failed"; 213 if(user[0]=='\0') 214 return "factotum did not return a user name"; 215 216 if(s = pop3capa(pop)) 217 return s; 218 219 pop3cmd(pop, "APOP %s %.*s", user, n, buf); 220 if(!isokay(s = pop3resp(pop))) 221 return s; 222 223 return nil; 224 } else { 225 if(pop->ppop == 0) 226 return "no APOP hdr from server"; 227 228 if(s = pop3capa(pop)) 229 return s; 230 231 if(pop->needtls && !pop->encrypted) 232 return "could not negotiate TLS"; 233 234 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=pop dom=%q%s", 235 pop->host, ubuf); 236 if(up == nil) 237 return "no usable keys found"; 238 239 pop3cmd(pop, "USER %s", up->user); 240 if(!isokay(s = pop3resp(pop))){ 241 free(up); 242 return s; 243 } 244 pop3cmd(pop, "PASS %s", up->passwd); 245 free(up); 246 if(!isokay(s = pop3resp(pop))) 247 return s; 248 249 return nil; 250 } 251 } 252 253 /* */ 254 /* dial and handshake with pop server */ 255 /* */ 256 static char* 257 pop3dial(Pop *pop) 258 { 259 char *err; 260 261 if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0) 262 return geterrstr(); 263 264 if(pop->needssl){ 265 if((err = pop3pushtls(pop)) != nil) 266 return err; 267 }else{ 268 Binit(&pop->bin, pop->fd, OREAD); 269 Binit(&pop->bout, pop->fd, OWRITE); 270 } 271 272 if(err = pop3login(pop)) { 273 close(pop->fd); 274 return err; 275 } 276 277 return nil; 278 } 279 280 /* */ 281 /* close connection */ 282 /* */ 283 static void 284 pop3hangup(Pop *pop) 285 { 286 pop3cmd(pop, "QUIT"); 287 pop3resp(pop); 288 close(pop->fd); 289 } 290 291 /* */ 292 /* download a single message */ 293 /* */ 294 static char* 295 pop3download(Pop *pop, Message *m) 296 { 297 char *s, *f[3], *wp, *ep; 298 char sdigest[SHA1dlen*2+1]; 299 int i, l, sz; 300 301 if(!pop->pipeline) 302 pop3cmd(pop, "LIST %d", m->mesgno); 303 if(!isokay(s = pop3resp(pop))) 304 return s; 305 306 if(tokenize(s, f, 3) != 3) 307 return "syntax error in LIST response"; 308 309 if(atoi(f[1]) != m->mesgno) 310 return "out of sync with pop3 server"; 311 312 sz = atoi(f[2])+200; /* 200 because the plan9 pop3 server lies */ 313 if(sz == 0) 314 return "invalid size in LIST response"; 315 316 m->start = wp = emalloc(sz+1); 317 ep = wp+sz; 318 319 if(!pop->pipeline) 320 pop3cmd(pop, "RETR %d", m->mesgno); 321 if(!isokay(s = pop3resp(pop))) { 322 m->start = nil; 323 free(wp); 324 return s; 325 } 326 327 s = nil; 328 while(wp <= ep) { 329 s = pop3resp(pop); 330 if(strcmp(s, "unexpected eof") == 0) { 331 free(m->start); 332 m->start = nil; 333 return "unexpected end of conversation"; 334 } 335 if(strcmp(s, ".") == 0) 336 break; 337 338 l = strlen(s)+1; 339 if(s[0] == '.') { 340 s++; 341 l--; 342 } 343 /* 344 * grow by 10%/200bytes - some servers 345 * lie about message sizes 346 */ 347 if(wp+l > ep) { 348 int pos = wp - m->start; 349 sz += ((sz / 10) < 200)? 200: sz/10; 350 m->start = erealloc(m->start, sz+1); 351 wp = m->start+pos; 352 ep = m->start+sz; 353 } 354 memmove(wp, s, l-1); 355 wp[l-1] = '\n'; 356 wp += l; 357 } 358 359 if(s == nil || strcmp(s, ".") != 0) 360 return "out of sync with pop3 server"; 361 362 m->end = wp; 363 364 /* make sure there's a trailing null */ 365 /* (helps in body searches) */ 366 *m->end = 0; 367 m->bend = m->rbend = m->end; 368 m->header = m->start; 369 370 /* digest message */ 371 sha1((uchar*)m->start, m->end - m->start, m->digest, nil); 372 for(i = 0; i < SHA1dlen; i++) 373 sprint(sdigest+2*i, "%2.2ux", m->digest[i]); 374 m->sdigest = s_copy(sdigest); 375 376 return nil; 377 } 378 379 /* */ 380 /* check for new messages on pop server */ 381 /* UIDL is not required by RFC 1939, but */ 382 /* netscape requires it, so almost every server supports it. */ 383 /* we'll use it to make our lives easier. */ 384 /* */ 385 static char* 386 pop3read(Pop *pop, Mailbox *mb, int doplumb) 387 { 388 char *s, *p, *uidl, *f[2]; 389 int mesgno, ignore, nnew; 390 Message *m, *next, **l; 391 392 /* Some POP servers disallow UIDL if the maildrop is empty. */ 393 pop3cmd(pop, "STAT"); 394 if(!isokay(s = pop3resp(pop))) 395 return s; 396 397 /* fetch message listing; note messages to grab */ 398 l = &mb->root->part; 399 if(strncmp(s, "+OK 0 ", 6) != 0) { 400 pop3cmd(pop, "UIDL"); 401 if(!isokay(s = pop3resp(pop))) 402 return s; 403 404 for(;;){ 405 p = pop3resp(pop); 406 if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0) 407 break; 408 409 if(tokenize(p, f, 2) != 2) 410 continue; 411 412 mesgno = atoi(f[0]); 413 uidl = f[1]; 414 if(strlen(uidl) > 75) /* RFC 1939 says 70 characters max */ 415 continue; 416 417 ignore = 0; 418 while(*l != nil) { 419 if(strcmp((*l)->uidl, uidl) == 0) { 420 /* matches mail we already have, note mesgno for deletion */ 421 (*l)->mesgno = mesgno; 422 ignore = 1; 423 l = &(*l)->next; 424 break; 425 } else { 426 /* old mail no longer in box mark deleted */ 427 if(doplumb) 428 mailplumb(mb, *l, 1); 429 (*l)->inmbox = 0; 430 (*l)->deleted = 1; 431 l = &(*l)->next; 432 } 433 } 434 if(ignore) 435 continue; 436 437 m = newmessage(mb->root); 438 m->mallocd = 1; 439 m->inmbox = 1; 440 m->mesgno = mesgno; 441 strcpy(m->uidl, uidl); 442 443 /* chain in; will fill in message later */ 444 *l = m; 445 l = &m->next; 446 } 447 } 448 449 /* whatever is left has been removed from the mbox, mark as deleted */ 450 while(*l != nil) { 451 if(doplumb) 452 mailplumb(mb, *l, 1); 453 (*l)->inmbox = 0; 454 (*l)->deleted = 1; 455 l = &(*l)->next; 456 } 457 458 /* download new messages */ 459 nnew = 0; 460 if(pop->pipeline){ 461 switch(rfork(RFPROC|RFMEM)){ 462 case -1: 463 fprint(2, "rfork: %r\n"); 464 pop->pipeline = 0; 465 466 default: 467 break; 468 469 case 0: 470 for(m = mb->root->part; m != nil; m = m->next){ 471 if(m->start != nil) 472 continue; 473 Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno); 474 } 475 Bflush(&pop->bout); 476 threadexits(nil); 477 /* _exits(nil); jpc */ 478 } 479 } 480 481 for(m = mb->root->part; m != nil; m = next) { 482 next = m->next; 483 484 if(m->start != nil) 485 continue; 486 487 if(s = pop3download(pop, m)) { 488 /* message disappeared? unchain */ 489 fprint(2, "download %d: %s\n", m->mesgno, s); 490 delmessage(mb, m); 491 mb->root->subname--; 492 continue; 493 } 494 nnew++; 495 parse(m, 0, mb, 1); 496 497 if(doplumb) 498 mailplumb(mb, m, 0); 499 } 500 if(pop->pipeline) 501 waitpid(); 502 503 if(nnew || mb->vers == 0) { 504 mb->vers++; 505 henter(PATH(0, Qtop), mb->name, 506 (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); 507 } 508 509 return nil; 510 } 511 512 /* */ 513 /* delete marked messages */ 514 /* */ 515 static void 516 pop3purge(Pop *pop, Mailbox *mb) 517 { 518 Message *m, *next; 519 520 if(pop->pipeline){ 521 switch(rfork(RFPROC|RFMEM)){ 522 case -1: 523 fprint(2, "rfork: %r\n"); 524 pop->pipeline = 0; 525 526 default: 527 break; 528 529 case 0: 530 for(m = mb->root->part; m != nil; m = next){ 531 next = m->next; 532 if(m->deleted && m->refs == 0){ 533 if(m->inmbox) 534 Bprint(&pop->bout, "DELE %d\r\n", m->mesgno); 535 } 536 } 537 Bflush(&pop->bout); 538 /* _exits(nil); jpc */ 539 threadexits(nil); 540 } 541 } 542 for(m = mb->root->part; m != nil; m = next) { 543 next = m->next; 544 if(m->deleted && m->refs == 0) { 545 if(m->inmbox) { 546 if(!pop->pipeline) 547 pop3cmd(pop, "DELE %d", m->mesgno); 548 if(isokay(pop3resp(pop))) 549 delmessage(mb, m); 550 } else 551 delmessage(mb, m); 552 } 553 } 554 } 555 556 557 /* connect to pop3 server, sync mailbox */ 558 static char* 559 pop3sync(Mailbox *mb, int doplumb) 560 { 561 char *err; 562 Pop *pop; 563 564 pop = mb->aux; 565 566 if(err = pop3dial(pop)) { 567 mb->waketime = time(0) + pop->refreshtime; 568 return err; 569 } 570 571 if((err = pop3read(pop, mb, doplumb)) == nil){ 572 pop3purge(pop, mb); 573 mb->d->atime = mb->d->mtime = time(0); 574 } 575 pop3hangup(pop); 576 mb->waketime = time(0) + pop->refreshtime; 577 return err; 578 } 579 580 static char Epop3ctl[] = "bad pop3 control message"; 581 582 static char* 583 pop3ctl(Mailbox *mb, int argc, char **argv) 584 { 585 int n; 586 Pop *pop; 587 char *m, *me; 588 589 pop = mb->aux; 590 if(argc < 1) 591 return Epop3ctl; 592 593 if(argc==1 && strcmp(argv[0], "debug")==0){ 594 pop->debug = 1; 595 return nil; 596 } 597 598 if(argc==1 && strcmp(argv[0], "nodebug")==0){ 599 pop->debug = 0; 600 return nil; 601 } 602 603 if(argc==1 && strcmp(argv[0], "thumbprint")==0){ 604 if(pop->thumb) 605 freeThumbprints(pop->thumb); 606 /* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */ 607 m = unsharp("#9/sys/lib/tls/mail"); 608 me = unsharp("#9/sys/lib/tls/mail.exclude"); 609 pop->thumb = initThumbprints(m, me); 610 } 611 if(strcmp(argv[0], "refresh")==0){ 612 if(argc==1){ 613 pop->refreshtime = 60; 614 return nil; 615 } 616 if(argc==2){ 617 n = atoi(argv[1]); 618 if(n < 15) 619 return Epop3ctl; 620 pop->refreshtime = n; 621 return nil; 622 } 623 } 624 625 return Epop3ctl; 626 } 627 628 /* free extra memory associated with mb */ 629 static void 630 pop3close(Mailbox *mb) 631 { 632 Pop *pop; 633 634 pop = mb->aux; 635 free(pop->freep); 636 free(pop); 637 } 638 639 /* */ 640 /* open mailboxes of the form /pop/host/user or /apop/host/user */ 641 /* */ 642 char* 643 pop3mbox(Mailbox *mb, char *path) 644 { 645 char *f[10]; 646 int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls; 647 Pop *pop; 648 char *m, *me; 649 650 quotefmtinstall(); 651 popssl = strncmp(path, "/pops/", 6) == 0; 652 apopssl = strncmp(path, "/apops/", 7) == 0; 653 poptls = strncmp(path, "/poptls/", 8) == 0; 654 popnotls = strncmp(path, "/popnotls/", 10) == 0; 655 ppop = popssl || poptls || popnotls || strncmp(path, "/pop/", 5) == 0; 656 apoptls = strncmp(path, "/apoptls/", 9) == 0; 657 apopnotls = strncmp(path, "/apopnotls/", 11) == 0; 658 apop = apopssl || apoptls || apopnotls || strncmp(path, "/apop/", 6) == 0; 659 660 if(!ppop && !apop) 661 return Enotme; 662 663 path = strdup(path); 664 if(path == nil) 665 return "out of memory"; 666 667 nf = getfields(path, f, nelem(f), 0, "/"); 668 if(nf != 3 && nf != 4) { 669 free(path); 670 return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]"; 671 } 672 673 pop = emalloc(sizeof(*pop)); 674 pop->freep = path; 675 pop->host = f[2]; 676 if(nf < 4) 677 pop->user = nil; 678 else 679 pop->user = f[3]; 680 pop->ppop = ppop; 681 pop->needssl = popssl || apopssl; 682 pop->needtls = poptls || apoptls; 683 pop->refreshtime = 60; 684 pop->notls = popnotls || apopnotls; 685 /* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */ 686 m = unsharp("#9/sys/lib/tls/mail"); 687 me = unsharp("#9/sys/lib/tls/mail.exclude"); 688 pop->thumb = initThumbprints(m, me); 689 690 mb->aux = pop; 691 mb->sync = pop3sync; 692 mb->close = pop3close; 693 mb->ctl = pop3ctl; 694 mb->d = emalloc(sizeof(*mb->d)); 695 696 return nil; 697 }