#include "common.h" #include #include #include #include "dat.h" typedef struct Header Header; struct Header { char *type; void (*f)(Message*, Header*, char*); int len; }; /* headers */ static void ctype(Message*, Header*, char*); static void cencoding(Message*, Header*, char*); static void cdisposition(Message*, Header*, char*); static void date822(Message*, Header*, char*); static void from822(Message*, Header*, char*); static void to822(Message*, Header*, char*); static void sender822(Message*, Header*, char*); static void replyto822(Message*, Header*, char*); static void subject822(Message*, Header*, char*); static void inreplyto822(Message*, Header*, char*); static void cc822(Message*, Header*, char*); static void bcc822(Message*, Header*, char*); static void messageid822(Message*, Header*, char*); static void mimeversion(Message*, Header*, char*); static void nullsqueeze(Message*); 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 fatal(char *fmt, ...); static void initquoted(void); static void startheader(Message*); static void startbody(Message*); static char* skipwhite(char*); static char* skiptosemi(char*); static char* getstring(char*, String*, int); static void setfilename(Message*, char*); static char* lowercase(char*); static int headerline(char**, String*); static void initheaders(void); static void parseattachments(Message*, Mailbox*); int debug; char *Enotme = "path not served by this file server"; enum { Chunksize = 1024, }; Mailboxinit *boxinit[] = { imap4mbox, pop3mbox, plan9mbox, }; char* syncmbox(Mailbox *mb, int doplumb) { return (*mb->sync)(mb, doplumb); } /* create a new mailbox */ char* newmbox(char *path, char *name, int std) { Mailbox *mb, **l; char *p, *rv; int i; initheaders(); 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 "bad mbox name"; } strncpy(mb->name, p, sizeof(mb->name)-1); } else { strncpy(mb->name, name, sizeof(mb->name)-1); } rv = nil; // check for a mailbox type for(i=0; inext){ if(strcmp((*l)->name, mb->name) == 0){ if(strcmp(path, (*l)->path) == 0) rv = nil; else rv = "mbox name in use"; if(mb->close) (*mb->close)(mb); free(mb); qunlock(&mbllock); return rv; } } // always try locking mb->dolock = 1; mb->refs = 1; mb->next = nil; mb->id = newid(); mb->root = newmessage(nil); mb->std = std; *l = mb; qunlock(&mbllock); qlock(mb); if(mb->ctl){ henter(PATH(mb->id, Qmbox), "ctl", (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb); } rv = syncmbox(mb, 0); qunlock(mb); return rv; } // close the named mailbox void freembox(char *name) { Mailbox **l, *mb; qlock(&mbllock); for(l=&mbl; *l != nil; l=&(*l)->next){ if(strcmp(name, (*l)->name) == 0){ mb = *l; *l = mb->next; mboxdecref(mb); break; } } hfree(PATH(0, Qtop), name); qunlock(&mbllock); } 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); } /* * 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); } /* * parse a message */ void parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom) { String *hl; Header *h; char *p, *q; int i; if(m->whole == m->whole->whole){ henter(PATH(mb->id, Qmbox), m->name, (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb); } else { henter(PATH(m->whole->id, Qdir), m->name, (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb); } for(i = 0; i < Qmax; i++) henter(PATH(m->id, Qdir), dirtab[i], (Qid){PATH(m->id, i), 0, QTFILE}, m, mb); // 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; } 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); } } } void parse(Message *m, int justmime, Mailbox *mb, int addfrom) { parseheaders(m, justmime, mb, addfrom); parsebody(m, mb); } static 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); } } /* * pick up a header line */ static 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; } static 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; } static void to822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->to822); m->to822 = addr822(p); } static void cc822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->cc822); m->cc822 = addr822(p); } static void bcc822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->bcc822); m->bcc822 = addr822(p); } static void from822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->from822); m->from822 = addr822(p); } static void sender822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->sender822); m->sender822 = addr822(p); } static void replyto822(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->replyto822); m->replyto822 = addr822(p); } static void mimeversion(Message *m, Header *h, char *p) { p += strlen(h->type); s_free(m->mimeversion); m->mimeversion = addr822(p); } static void killtrailingwhite(char *p) { char *e; e = p + strlen(p) - 1; while(e > p && isspace(*e)) *e-- = 0; } static 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); } static 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); } static 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); } static 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); } static 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 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); } } static 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; } static 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); } } ulong msgallocd, msgfreed; Message* newmessage(Message *parent) { static int id; Message *m; msgallocd++; 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 = newid(); if(parent) sprint(m->name, "%d", ++(parent->subname)); if(parent == nil) parent = m; m->whole = parent; m->hlen = -1; return m; } // delete a message from a mailbox void delmessage(Mailbox *mb, Message *m) { Message **l; int i; mb->vers++; msgfreed++; if(m->whole != m){ // unchain from parent for(l = &m->whole->part; *l && *l != m; l = &(*l)->next) ; if(*l != nil) *l = m->next; // clear out of name lookup hash table if(m->whole->whole == m->whole) hfree(PATH(mb->id, Qmbox), m->name); else hfree(PATH(m->whole->id, Qdir), m->name); for(i = 0; i < Qmax; i++) hfree(PATH(m->id, Qdir), dirtab[i]); } /* 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); } // mark messages (identified by path) for deletion void delmessages(int ac, char **av) { Mailbox *mb; Message *m; int i, needwrite; qlock(&mbllock); for(mb = mbl; mb != nil; mb = mb->next) if(strcmp(av[0], mb->name) == 0){ qlock(mb); break; } qunlock(&mbllock); if(mb == nil) return; needwrite = 0; for(i = 1; i < ac; i++){ for(m = mb->root->part; m != nil; m = m->next) if(strcmp(m->name, av[i]) == 0){ if(!m->deleted){ mailplumb(mb, m, 1); needwrite = 1; m->deleted = 1; logmsg("deleting", m); } break; } } if(needwrite) syncmbox(mb, 1); qunlock(mb); } /* * the following are called with the mailbox qlocked */ void msgincref(Message *m) { m->refs++; } void msgdecref(Mailbox *mb, Message *m) { m->refs--; if(m->refs == 0 && m->deleted) syncmbox(mb, 1); } /* * the following are called with mbllock'd */ void mboxincref(Mailbox *mb) { assert(mb->refs > 0); mb->refs++; } void mboxdecref(Mailbox *mb) { assert(mb->refs > 0); qlock(mb); mb->refs--; if(mb->refs == 0){ delmessage(mb, mb->root); if(mb->ctl) hfree(PATH(mb->id, Qmbox), "ctl"); if(mb->close) (*mb->close)(mb); free(mb); } else qunlock(mb); } 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 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 = '_'; } // // undecode 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, 0); if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; break; default: break; } m->decoded = 1; } // convert 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 || m->part != nil || cistrncmp(s_to_c(m->type), "text", 4) != 0) return; m->converted = 1; len = xtoutf(s_to_c(m->charset), &x, m->body, m->bend); if(len != 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } 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; } // underscores are translated in 2047 headers but not in the body static char* decquotedline(char *out, char *in, char *e, int uscores) { int c, soft; /* dump trailing white space */ while(e >= in && isspace(*e)) e--; /* trailing '=' means no newline */ if(*e == '='){ soft = 1; e--; } else soft = 0; while(in <= e){ c = (*in++) & 0xff; switch(c){ case '=': c = hex2int(*in++)<<4; c |= hex2int(*in++); *out++ = c; break; case '_': *out++ = uscores ? '_' : ' '; break; default: *out++ = c; } } if(!soft) *out++ = '\n'; *out = 0; return out; } int decquoted(char *out, char *in, char *e, int uscores) { char *p, *nl; p = out; while((nl = strchr(in, '\n')) != nil && nl < e){ p = decquotedline(p, in, nl, uscores); in = nl + 1; } if(in < e) p = decquotedline(p, in, e-1, uscores); // make sure we end with a new line if(*(p-1) != '\n'){ *p++ = '\n'; *p = 0; } return p - out; } static char* lowercase(char *p) { char *op; int c; for(op = p; c = *p; p++) if(isupper(c)) *p = tolower(c); return op; } // translate latin1 directly since it fits neatly in utf static int latin1toutf(char **out, char *in, char *e) { Rune r; char *p; int n; for(n = 0, p = in; psubject822 == nil) subject = ""; else subject = s_to_c(m->subject822); if(m->from822 != nil) from = s_to_c(m->from822); else if(m->unixfrom != nil) from = s_to_c(m->unixfrom); else from = ""; if(m->unixdate != nil) date = s_to_c(m->unixdate); else date = ""; sprint(lenstr, "%ld", m->end-m->start); if(biffing && !delete) print("[ %s / %s / %s ]\n", from, subject, lenstr); if(!plumbing) return; if(fd < 0) fd = plumbopen("send", OWRITE); if(fd < 0) return; p.src = "mailfs"; p.dst = "seemail"; p.wdir = "/mail/fs"; p.type = "text"; ai = 0; a[ai].name = "filetype"; a[ai].value = "mail"; a[++ai].name = "sender"; a[ai].value = from; a[ai-1].next = &a[ai]; a[++ai].name = "length"; a[ai].value = lenstr; a[ai-1].next = &a[ai]; a[++ai].name = "mailtype"; a[ai].value = delete?"delete":"new"; a[ai-1].next = &a[ai]; a[++ai].name = "date"; a[ai].value = date; a[ai-1].next = &a[ai]; if(m->sdigest){ a[++ai].name = "digest"; a[ai].value = s_to_c(m->sdigest); a[ai-1].next = &a[ai]; } a[ai].next = nil; p.attr = a; snprint(buf, sizeof(buf), "%s/%s/%s", mntpt, mb->name, m->name); p.ndata = strlen(buf); p.data = buf; plumbsend(fd, &p); } // // count the number of lines in the body (for imap4) // void countlines(Message *m) { int i; char *p; i = 0; for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n')) i++; sprint(m->lines, "%d", i); } char *LOG = "fs"; void logmsg(char *s, Message *m) { int pid; if(!logging) return; pid = getpid(); if(m == nil) syslog(0, LOG, "%s.%d: %s", user, pid, s); else syslog(0, LOG, "%s.%d: %s msg from %s digest %s", user, pid, s, m->from822 ? s_to_c(m->from822) : "?", s_to_c(m->sdigest)); } /* * 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; } // // convert an RFC822 date into a Unix style date // for when the Unix From line isn't there (e.g. POP3). // enough client programs depend on having a Unix date // that it's easiest to write this conversion code once, right here. // // people don't follow RFC822 particularly closely, // so we use strtotm, which is a bunch of heuristics. // 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); }