#include "common.h" #include #include #include "all.h" /* * This is a kludge, at best. * All the horrors of mail processing are kept here, copied * from upas/fs and other places. * Cleanup is required, although it may not be feasible to * do so unless the world decides to start using a decent * format to encode mails. */ enum { Mhead= 11, /* offset of first mime header */ }; Header head[] = { { "date:", date822, }, { "from:", from822, }, { "to:", to822, }, { "sender:", sender822, }, { "reply-to:", replyto822, }, { "subject:", subject822, }, { "cc:", cc822, }, { "bcc:", bcc822, }, { "in-reply-to:", inreplyto822, }, { "mime-version:", mimeversion, }, { "message-id:", messageid822, }, [Mhead] { "content-type:", ctype, }, { "content-transfer-encoding:", cencoding, }, { "content-disposition:", cdisposition, }, { 0, }, }; static void initheaders(void) { Header *h; static int already; if(already) return; already = 1; for(h = head; h->type != nil; h++) h->len = strlen(h->type); } int cistrncmp(char *a, char *b, int n) { while(n-- > 0){ if(tolower(*a++) != tolower(*b++)) return -1; } return 0; } int cistrcmp(char *a, char *b) { for(;;){ if(tolower(*a) != tolower(*b++)) return -1; if(*a++ == 0) break; } return 0; } static char* skipwhite(char *p) { while(isspace(*p)) p++; return p; } static char* skiptosemi(char *p) { while(*p && *p != ';') p++; while(*p == ';' || isspace(*p)) p++; return p; } static char* getstring(char *p, String *s, int dolower) { s = s_reset(s); p = skipwhite(p); if(*p == '"'){ p++; for(;*p && *p != '"'; p++) if(dolower) s_putc(s, tolower(*p)); else s_putc(s, *p); if(*p == '"') p++; s_terminate(s); return p; } for(; *p && !isspace(*p) && *p != ';'; p++) if(dolower) s_putc(s, tolower(*p)); else s_putc(s, *p); s_terminate(s); return p; } static char* lowercase(char *p) { char *op; int c; for(op = p; c = *p; p++) if(isupper(c)) *p = tolower(c); return op; } /* * return number of 8 bit characters */ static int is8bit(Message *m) { int count = 0; char *p; for(p = m->body; p < m->bend; p++) if(*p & 0x80) count++; return count; } // translate latin1 directly since it fits neatly in utf int latin1toutf(char *out, char *in, char *e) { Rune r; char *p; p = out; for(; in < e; in++){ r = (*in) & 0xff; p += runetochar(p, &r); } *p = 0; return p - out; } // translate any thing else using the tcs program int xtoutf(char *charset, char **out, char *in, char *e) { char *av[4]; int totcs[2]; int fromtcs[2]; int n, len, sofar; char *p; len = e-in+1; sofar = 0; *out = p = malloc(len+1); if(p == nil) return 0; av[0] = charset; av[1] = "-f"; av[2] = charset; av[3] = 0; if(pipe(totcs) < 0) return 0; if(pipe(fromtcs) < 0){ close(totcs[0]); close(totcs[1]); return 0; } switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ case -1: close(fromtcs[0]); close(fromtcs[1]); close(totcs[0]); close(totcs[1]); return 0; case 0: close(fromtcs[0]); close(totcs[1]); dup(fromtcs[1], 1); dup(totcs[0], 0); close(fromtcs[1]); close(totcs[0]); dup(open("/dev/null", OWRITE), 2); exec("/bin/tcs", av); _exits(0); default: close(fromtcs[1]); close(totcs[0]); switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ case -1: close(fromtcs[0]); close(totcs[1]); return 0; case 0: close(fromtcs[0]); while(in < e){ n = write(totcs[1], in, e-in); if(n <= 0) break; in += n; } close(totcs[1]); _exits(0); default: close(totcs[1]); for(;;){ n = read(fromtcs[0], &p[sofar], len-sofar); if(n <= 0) break; sofar += n; p[sofar] = 0; if(sofar == len){ len += 1024; *out = p = realloc(p, len+1); if(p == nil) return 0; } } close(fromtcs[0]); break; } break; } return sofar; } enum { Winstart= 0x7f, Winend= 0x9f, }; Rune winchars[] = { L'•', L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡', L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•', L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—', L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ', }; int windows1257toutf(char *out, char *in, char *e) { Rune r; char *p; p = out; for(; in < e; in++){ r = (*in) & 0xff; if(r >= 0x7f && r <= 0x9f) r = winchars[r-0x7f]; p += runetochar(p, &r); } *p = 0; return p - out; } // convert latin1 to utf void convert(Message *m) { int len; char *x; // don't convert if we're not a leaf, not text, or already converted if(m->converted) return; if(m->part != nil) return; if(cistrncmp(s_to_c(m->type), "text", 4) != 0) return; if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 || cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){ len = is8bit(m); if(len > 0){ len = 2*len + m->bend - m->body + 1; x = emalloc(len); len = latin1toutf(x, m->body, m->bend); if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){ len = xtoutf("8859-2", &x, m->body, m->bend); if(len != 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){ len = xtoutf("8859-15", &x, m->body, m->bend); if(len != 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } else if(cistrcmp(s_to_c(m->charset), "big5") == 0){ len = xtoutf("big5", &x, m->body, m->bend); if(len != 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){ len = xtoutf("jis", &x, m->body, m->bend); if(len != 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0 || cistrcmp(s_to_c(m->charset), "windows-1252") == 0){ len = is8bit(m); if(len > 0){ len = 2*len + m->bend - m->body + 1; x = emalloc(len); len = windows1257toutf(x, m->body, m->bend); if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){ len = xtoutf("cp1251", &x, m->body, m->bend); if(len != 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){ len = xtoutf("koi8", &x, m->body, m->bend); if(len != 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } m->converted = 1; } void * erealloc(void *p, ulong n) { if(n == 0) n = 1; p = realloc(p, n); if(!p){ fprint(2, "%s: out of memory realloc %lud\n", argv0, n); exits("out of memory"); } setrealloctag(p, getcallerpc(&p)); return p; } void * emalloc(ulong n) { void *p; p = mallocz(n, 1); if(!p){ fprint(2, "%s: out of memory alloc %lud\n", argv0, n); exits("out of memory"); } setmalloctag(p, getcallerpc(&n)); return p; } void addtomessage(Message *m, uchar *p, int n, int done) { int i, len; // add to message (+ 1 in malloc is for a trailing null) if(m->lim - m->end < n){ if(m->start != nil){ i = m->end-m->start; if(done) len = i + n; else len = (4*(i+n))/3; m->start = erealloc(m->start, len + 1); m->end = m->start + i; } else { if(done) len = n; else len = 2*n; m->start = emalloc(len + 1); m->end = m->start; } m->lim = m->start + len; } memmove(m->end, p, n); m->end += n; } int readmessage(Message *m, Inbuf *inb) { int i, n, done; uchar *p, *np; char sdigest[SHA1dlen*2+1]; char tmp[64]; for(done = 0; !done;){ n = inb->wptr - inb->rptr; if(n < 6){ if(n) memmove(inb->data, inb->rptr, n); inb->rptr = inb->data; inb->wptr = inb->rptr + n; i = read(inb->fd, inb->wptr, Buffersize); if(i < 0){ if(fd2path(inb->fd, tmp, sizeof tmp) < 0) strcpy(tmp, "unknown mailbox"); fprint(2, "error reading '%s': %r\n", tmp); return -1; } if(i == 0){ if(n != 0) addtomessage(m, inb->rptr, n, 1); if(m->end == m->start) return -1; break; } inb->wptr += i; } // look for end of message for(p = inb->rptr; p < inb->wptr; p = np+1){ // first part of search for '\nFrom ' np = memchr(p, '\n', inb->wptr - p); if(np == nil){ p = inb->wptr; break; } /* * if we've found a \n but there's * not enough room for '\nFrom ', don't do * the comparison till we've read in more. */ if(inb->wptr - np < 6){ p = np; break; } if(strncmp((char*)np, "\nFrom ", 6) == 0){ done = 1; p = np+1; break; } } // add to message (+ 1 in malloc is for a trailing null) n = p - inb->rptr; addtomessage(m, inb->rptr, n, done); inb->rptr += n; } // if it doesn't start with a 'From ', this ain't a mailbox if(strncmp(m->start, "From ", 5) != 0) return -1; // dump trailing newline, make sure there's a trailing null // (helps in body searches) if(*(m->end-1) == '\n') m->end--; *m->end = 0; m->bend = m->rbend = m->end; // digest message sha1((uchar*)m->start, m->end - m->start, m->digest, nil); for(i = 0; i < SHA1dlen; i++) sprint(sdigest+2*i, "%2.2ux", m->digest[i]); m->sdigest = s_copy(sdigest); return 0; } // delete a message from a mailbox void delmessage(Mailbox *mb, Message *m) { Message **l; mb->vers++; if(m->whole != m){ // unchain from parent for(l = &m->whole->part; *l && *l != m; l = &(*l)->next) ; if(*l != nil) *l = m->next; } /* recurse through sub-parts */ while(m->part) delmessage(mb, m->part); /* free memory */ if(m->mallocd) free(m->start); if(m->hallocd) free(m->header); if(m->ballocd) free(m->body); s_free(m->unixfrom); s_free(m->unixdate); s_free(m->unixheader); s_free(m->from822); s_free(m->sender822); s_free(m->to822); s_free(m->bcc822); s_free(m->cc822); s_free(m->replyto822); s_free(m->date822); s_free(m->inreplyto822); s_free(m->subject822); s_free(m->messageid822); s_free(m->addrs); s_free(m->mimeversion); s_free(m->sdigest); s_free(m->boundary); s_free(m->type); s_free(m->charset); s_free(m->filename); free(m); } /* * pick up a header line */ int headerline(char **pp, String *hl) { char *p, *x; s_reset(hl); p = *pp; x = strpbrk(p, ":\n"); if(x == nil || *x == '\n') return 0; for(;;){ x = strchr(p, '\n'); if(x == nil) x = p + strlen(p); s_nappend(hl, p, x-p); p = x; if(*p != '\n' || *++p != ' ' && *p != '\t') break; while(*p == ' ' || *p == '\t') p++; s_putc(hl, ' '); } *pp = p; return 1; } String* addr822(char *p) { String *s, *list; int incomment, addrdone, inanticomment, quoted; int n; int c; list = s_new(); s = s_new(); quoted = incomment = addrdone = inanticomment = 0; n = 0; for(; *p; p++){ c = *p; // whitespace is ignored if(!quoted && isspace(c) || c == '\r') continue; // strings are always treated as atoms if(!quoted && c == '"'){ if(!addrdone && !incomment) s_putc(s, c); for(p++; *p; p++){ if(!addrdone && !incomment) s_putc(s, *p); if(!quoted && *p == '"') break; if(*p == '\\') quoted = 1; else quoted = 0; } if(*p == 0) break; quoted = 0; continue; } // ignore everything in an expicit comment if(!quoted && c == '('){ incomment = 1; continue; } if(incomment){ if(!quoted && c == ')') incomment = 0; quoted = 0; continue; } // anticomments makes everything outside of them comments if(!quoted && c == '<' && !inanticomment){ inanticomment = 1; s = s_reset(s); continue; } if(!quoted && c == '>' && inanticomment){ addrdone = 1; inanticomment = 0; continue; } // commas separate addresses if(!quoted && c == ',' && !inanticomment){ s_terminate(s); addrdone = 0; if(n++ != 0) s_append(list, " "); s_append(list, s_to_c(s)); s = s_reset(s); continue; } // what's left is part of the address s_putc(s, c); // quoted characters are recognized only as characters if(c == '\\') quoted = 1; else quoted = 0; } if(*s_to_c(s) != 0){ s_terminate(s); if(n++ != 0) s_append(list, " "); s_append(list, s_to_c(s)); } s_free(s); if(n == 0){ s_free(list); return nil; } return list; } void to822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->to822); m->to822 = addr822(p); } void cc822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->cc822); m->cc822 = addr822(p); } void bcc822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->bcc822); m->bcc822 = addr822(p); } void from822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->from822); m->from822 = addr822(p); } void sender822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->sender822); m->sender822 = addr822(p); } void replyto822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->replyto822); m->replyto822 = addr822(p); } void mimeversion(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->mimeversion); m->mimeversion = addr822(p); } void killtrailingwhite(char *p) { char *e; e = p + strlen(p) - 1; while(e > p && isspace(*e)) *e-- = 0; } void date822(Message *m, Header *h, char *p) { p += strlen(h->type); p = skipwhite(p); s_free(m->date822); m->date822 = s_copy(p); p = s_to_c(m->date822); killtrailingwhite(p); } void subject822(Message *m, Header *h, char *p) { p += strlen(h->type); p = skipwhite(p); s_free(m->subject822); m->subject822 = s_copy(p); p = s_to_c(m->subject822); killtrailingwhite(p); } void inreplyto822(Message *m, Header *h, char *p) { p += strlen(h->type); p = skipwhite(p); s_free(m->inreplyto822); m->inreplyto822 = s_copy(p); p = s_to_c(m->inreplyto822); killtrailingwhite(p); } void messageid822(Message *m, Header *h, char *p) { p += strlen(h->type); p = skipwhite(p); s_free(m->messageid822); m->messageid822 = s_copy(p); p = s_to_c(m->messageid822); killtrailingwhite(p); } int isattribute(char **pp, char *attr) { char *p; int n; n = strlen(attr); p = *pp; if(cistrncmp(p, attr, n) != 0) return 0; p += n; while(*p == ' ') p++; if(*p++ != '=') return 0; while(*p == ' ') p++; *pp = p; return 1; } static void setfilename(Message *m, char *p) { m->filename = s_reset(m->filename); getstring(p, m->filename, 0); for(p = s_to_c(m->filename); *p; p++) if(*p == ' ' || *p == '\t' || *p == ';') *p = '_'; } void ctype(Message *m, Header *h, char *p) { String *s; p += h->len; p = skipwhite(p); p = getstring(p, m->type, 1); while(*p){ if(isattribute(&p, "boundary")){ s = s_new(); p = getstring(p, s, 0); m->boundary = s_reset(m->boundary); s_append(m->boundary, "--"); s_append(m->boundary, s_to_c(s)); s_free(s); } else if(cistrncmp(p, "multipart", 9) == 0){ /* * the first unbounded part of a multipart message, * the preamble, is not displayed or saved */ } else if(isattribute(&p, "name")){ if(m->filename == nil) setfilename(m, p); } else if(isattribute(&p, "charset")){ p = getstring(p, s_reset(m->charset), 0); } p = skiptosemi(p); } } void cencoding(Message *m, Header *h, char *p) { p += h->len; p = skipwhite(p); if(cistrncmp(p, "base64", 6) == 0) m->encoding = Ebase64; else if(cistrncmp(p, "quoted-printable", 16) == 0) m->encoding = Equoted; } void cdisposition(Message *m, Header *h, char *p) { p += h->len; p = skipwhite(p); while(*p){ if(cistrncmp(p, "inline", 6) == 0){ m->disposition = Dinline; } else if(cistrncmp(p, "attachment", 10) == 0){ m->disposition = Dfile; } else if(cistrncmp(p, "filename=", 9) == 0){ p += 9; setfilename(m, p); } p = skiptosemi(p); } } /* * parse a Unix style header */ void parseunix(Message *m) { char *p; String *h; h = s_new(); for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++) s_putc(h, *p); s_terminate(h); s_restart(h); m->unixfrom = s_parse(h, s_reset(m->unixfrom)); m->unixdate = s_append(s_reset(m->unixdate), h->ptr); s_free(h); } /* * squeeze nulls out of the body */ static void nullsqueeze(Message *m) { char *p, *q; q = memchr(m->body, 0, m->end-m->body); if(q == nil) return; for(p = m->body; q < m->end; q++){ if(*q == 0) continue; *p++ = *q; } m->bend = m->rbend = m->end = p; } extern int strtotm(char*, Tm*); String* date822tounix(char *s) { char *p, *q; Tm tm; if(strtotm(s, &tm) < 0) return nil; p = asctime(&tm); if(q = strchr(p, '\n')) *q = '\0'; return s_copy(p); } /* * parse a message */ void parseheaders(Message *m, int justmime, Mailbox *, int addfrom) { String *hl; Header *h; char *p, *q; // parse mime headers p = m->header; hl = s_new(); while(headerline(&p, hl)){ if(justmime) h = &head[Mhead]; else h = head; for(; h->type; h++){ if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){ (*h->f)(m, h, s_to_c(hl)); break; } } s_reset(hl); } s_free(hl); // the blank line isn't really part of the body or header if(justmime){ m->mhend = p; m->hend = m->header; } else { m->hend = p; } if(*p == '\n') p++; m->rbody = m->body = p; // if type is text, get any nulls out of the body. This is // for the two seans and imap clients that get confused. if(strncmp(s_to_c(m->type), "text/", 5) == 0) nullsqueeze(m); // // cobble together Unix-style from line // for local mailbox messages, we end up recreating the // original header. // for pop3 messages, the best we can do is // use the From: information and the RFC822 date. // if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0 || strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){ if(m->unixdate){ s_free(m->unixdate); m->unixdate = nil; } // look for the date in the first Received: line. // it's likely to be the right time zone (it's // the local system) and in a convenient format. if(cistrncmp(m->header, "received:", 9)==0){ if((q = strchr(m->header, ';')) != nil){ p = q; while((p = strchr(p, '\n')) != nil){ if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n') break; p++; } if(p){ *p = '\0'; m->unixdate = date822tounix(q+1); *p = '\n'; } } } // fall back on the rfc822 date if(m->unixdate==nil && m->date822) m->unixdate = date822tounix(s_to_c(m->date822)); } if(m->unixheader != nil) s_free(m->unixheader); // only fake header for top-level messages for pop3 and imap4 // clients (those protocols don't include the unix header). // adding the unix header all the time screws up mime-attached // rfc822 messages. if(!addfrom && !m->unixfrom){ m->unixheader = nil; return; } m->unixheader = s_copy("From "); if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0) s_append(m->unixheader, s_to_c(m->unixfrom)); else if(m->from822) s_append(m->unixheader, s_to_c(m->from822)); else s_append(m->unixheader, "???"); s_append(m->unixheader, " "); if(m->unixdate) s_append(m->unixheader, s_to_c(m->unixdate)); else s_append(m->unixheader, "Thu Jan 1 00:00:00 GMT 1970"); s_append(m->unixheader, "\n"); } String* promote(String **sp) { String *s; if(*sp != nil) s = s_clone(*sp); else s = nil; return s; } enum { Self= 1, Hex= 2, }; uchar tableqp[256]; static void initquoted(void) { int c; memset(tableqp, 0, 256); for(c = ' '; c <= '<'; c++) tableqp[c] = Self; for(c = '>'; c <= '~'; c++) tableqp[c] = Self; tableqp['\t'] = Self; tableqp['='] = Hex; } static int hex2int(int x) { if(x >= '0' && x <= '9') return x - '0'; if(x >= 'A' && x <= 'F') return (x - 'A') + 10; if(x >= 'a' && x <= 'f') return (x - 'a') + 10; return 0; } static char* decquotedline(char *out, char *in, char *e) { int c, soft; /* dump trailing white space */ while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) e--; /* trailing '=' means no newline */ if(*e == '='){ soft = 1; e--; } else soft = 0; while(in <= e){ c = (*in++) & 0xff; switch(tableqp[c]){ case Self: *out++ = c; break; case Hex: c = hex2int(*in++)<<4; c |= hex2int(*in++); *out++ = c; break; } } if(!soft) *out++ = '\n'; *out = 0; return out; } int decquoted(char *out, char *in, char *e) { char *p, *nl; if(tableqp[' '] == 0) initquoted(); p = out; while((nl = strchr(in, '\n')) != nil && nl < e){ p = decquotedline(p, in, nl); in = nl + 1; } if(in < e) p = decquotedline(p, in, e-1); // make sure we end with a new line if(*(p-1) != '\n'){ *p++ = '\n'; *p = 0; } return p - out; } // // decode message body // void decode(Message *m) { int i, len; char *x; if(m->decoded) return; switch(m->encoding){ case Ebase64: len = m->bend - m->body; i = (len*3)/4+1; // room for max chars + null x = emalloc(i); len = dec64((uchar*)x, i, m->body, len); if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; break; case Equoted: len = m->bend - m->body; x = emalloc(len+2); // room for null and possible extra nl len = decquoted(x, m->body, m->bend); if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; break; default: break; } m->decoded = 1; } void parsebody(Message *m, Mailbox *mb) { Message *nm; // recurse if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){ parseattachments(m, mb); } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){ decode(m); parseattachments(m, mb); nm = m->part; // promote headers if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){ m->from822 = promote(&nm->from822); m->to822 = promote(&nm->to822); m->date822 = promote(&nm->date822); m->sender822 = promote(&nm->sender822); m->replyto822 = promote(&nm->replyto822); m->subject822 = promote(&nm->subject822); m->unixdate = promote(&nm->unixdate); } convert(m); } } void digest(Message* m) { char sdigest[SHA1dlen*2+1]; DigestState* d; char* s; int i; s = " "; if (m->subject822 && s_to_c(m->subject822)) s = s_to_c(m->subject822); d = sha1((uchar*)s, strlen(s), nil, nil); if (m->body && m->bend > m->body) sha1((uchar*)m->body, m->bend - m->body, m->digest, d); else sha1((uchar*)" ", 1, m->digest, d); for(i = 0; i < SHA1dlen; i++) sprint(sdigest+2*i, "%2.2ux", m->digest[i]); m->sdigest = s_copy(sdigest); } void parse(Message *m, int justmime, Mailbox *mb, int addfrom) { parseheaders(m, justmime, mb, addfrom); parsebody(m, mb); decode(m); convert(m); digest(m); } Message* newmessage(Message *parent) { static int id; Message *m; m = emalloc(sizeof(*m)); memset(m, 0, sizeof(*m)); m->disposition = Dnone; m->type = s_copy("text/plain"); m->charset = s_copy("iso-8859-1"); m->id = id++; if(parent) sprint(m->name, "%d", ++(parent->subname)); if(parent == nil) parent = m; m->whole = parent; m->hlen = -1; return m; } void parseattachments(Message *m, Mailbox *mb) { Message *nm, **l; char *p, *x; // if there's a boundary, recurse... if(m->boundary != nil){ p = m->body; nm = nil; l = &m->part; for(;;){ x = strstr(p, s_to_c(m->boundary)); /* no boundary, we're done */ if(x == nil){ if(nm != nil) nm->rbend = nm->bend = nm->end = m->bend; break; } /* boundary must be at the start of a line */ if(x != m->body && *(x-1) != '\n'){ p = x+1; continue; } if(nm != nil) nm->rbend = nm->bend = nm->end = x; x += strlen(s_to_c(m->boundary)); /* is this the last part? ignore anything after it */ if(strncmp(x, "--", 2) == 0) break; p = strchr(x, '\n'); if(p == nil) break; nm = newmessage(m); nm->start = nm->header = nm->body = nm->rbody = ++p; nm->mheader = nm->header; *l = nm; l = &nm->next; } for(nm = m->part; nm != nil; nm = nm->next) parse(nm, 1, mb, 0); return; } // if we've got an rfc822 message, recurse... if(strcmp(s_to_c(m->type), "message/rfc822") == 0){ nm = newmessage(m); m->part = nm; nm->start = nm->header = nm->body = nm->rbody = m->body; nm->end = nm->bend = nm->rbend = m->bend; parse(nm, 0, mb, 0); } } char* readmbox(Mailbox *mb) { int fd; String *tmp; Dir *d; static char err[128]; Message *m, **l; Inbuf *inb; char *x; initheaders(); l = &mb->root->part; /* * open the mailbox. If it doesn't exist, try the temporary one. */ retry: dprint(2, "opening %s\n", mb->path); fd = open(mb->path, OREAD); if(fd < 0){ errstr(err, sizeof(err)); if(strstr(err, "exist") != 0){ tmp = s_copy(mb->path); s_append(tmp, ".tmp"); if(sysrename(s_to_c(tmp), mb->path) == 0){ s_free(tmp); goto retry; } s_free(tmp); } return err; } d = dirfstat(fd); if(d == nil){ close(fd); errstr(err, sizeof(err)); return err; } mb->d = d; mb->vers++; inb = emalloc(sizeof(Inbuf)); inb->rptr = inb->wptr = inb->data; inb->fd = fd; // read new messages snprint(err, sizeof err, "reading '%s'", mb->path); for(;;){ m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; if(readmessage(m, inb) < 0){ delmessage(mb, m); mb->root->subname--; break; } dprint(2, "readmessage %d\n", m->id); // merge mailbox versions while(*l != nil){ if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){ // matches mail we already read, discard delmessage(mb, m); mb->root->subname--; m = nil; l = &(*l)->next; break; } else { (*l)->inmbox = 0; (*l)->deleted = 1; l = &(*l)->next; } } if(m == nil) continue; x = strchr(m->start, '\n'); if(x == nil) m->header = m->end; else m->header = x + 1; m->mheader = m->mhend = m->header; parseunix(m); parse(m, 0, mb, 0); /* chain in */ *l = m; l = &m->next; } close(fd); free(inb); return nil; } /* create a new mailbox */ Mailbox* newmbox(char *path, char *name, int std) { Mailbox *mb; char *p, *rv; mb = emalloc(sizeof(*mb)); strncpy(mb->path, path, sizeof(mb->path)-1); if(name == nil){ p = strrchr(path, '/'); if(p == nil) p = path; else p++; if(*p == 0){ free(mb); return nil; } strncpy(mb->name, p, sizeof(mb->name)-1); } else { strncpy(mb->name, name, sizeof(mb->name)-1); } rv = nil; // on error, give up if(rv){ free(mb); return nil; } mb->refs = 1; mb->next = nil; mb->id = 0; mb->root = newmessage(nil); mb->std = std; return mb; }