#include "a.h" #define IS_HEADER(p) ((p)[0]=='F'&&(p)[1]=='r'&&(p)[2]=='o'&&(p)[3]=='m'&&(p)[4]==' ') #define IS_BODY(p) ((p)[0]=='\n'&&(p)[1]==0) enum { HEADER, BOUNDARY, ENDBOUNDARY, REGULAR, ENDF, }; void initmsg(Message *m) { m->bcc=(*mbox_bcc); m->body=(*mbox_body); m->cc=(*mbox_cc); m->date=(*mbox_date); m->digest=(*mbox_digest); m->disposition=(*mbox_disposition); m->filename=(*mbox_filename); m->from=(*mbox_from); m->header=(*mbox_header); m->info=(*mbox_info); m->inreplyto=(*mbox_inreplyto); m->lines=(*mbox_lines); m->messageid=(*mbox_messageid); m->mimeheader=(*mbox_mimeheader); m->raw=(*mbox_raw); m->rawbody=(*mbox_rawbody); m->rawheader=(*mbox_rawheader); m->rawunix=(*mbox_rawunix); m->replyto=(*mbox_replyto); m->sender=(*mbox_sender); m->subject=(*mbox_subject); m->to=(*mbox_to); m->type=(*mbox_type); m->unixdate=(*mbox_unixdate); m->unixheader=(*mbox_unixheader); } void initbox(Mailbox *b) { b->sync=nil; b->open=(*mbox_open); b->fetch=(*mbox_fetch); b->purge=nil; } /* I/O routunes */ char* getline(Biobuf *b) { int c; String *line; char *res; line=s_new(); c=Bgetc(b); while(c > 0 && c != '\n' && c != '\r') { s_putc(line,c); c=Bgetc(b); } if (c == '\n' || c=='\r') s_putc(line,'\n'); if (s_len(line) > 0) { s_terminate(line); res=estrdup(s_to_c(line)); setmalloctag(res,getcallerpc(&b)); s_free(line); return res; } s_free(line); return nil; } vlong getdigest(Message *m, char **buff) { char *sdigest; uchar hash[SHA1dlen]; sdigest=emalloc(SHA1dlen*2+1); sha1(m->cache->b, m->cache->l, hash, nil); for(int i = 0; i < SHA1dlen; i++) sprint(sdigest+2*i, "%2.2ux", hash[i]); sdigest[SHA1dlen+1]=0; *buff=sdigest; return (vlong)strlen(*buff); } vlong getdatacache(Message *m, char **buff, vlong offset, vlong len) { uchar *pc; char *ptr; pc=m->cache->b+offset; ptr=emalloc(len); memcpy(ptr,pc,len); setmalloctag(ptr,getcallerpc(&m)); *buff=ptr; return len; } vlong getdatadisk(Message *m, char **buff, vlong offset, vlong len) { Mailbox *b; long n; char *ptr; b = m->b; if ( Bseek(&b->br,offset,SEEK_SET) != offset) { error(DEBUG,"getdatadisk(): error in Bseek (eof?)"); return -1; } ptr=emalloc(len); if ( (n=Bread(&b->br,ptr,len)) != len ) { error(DEBUG,"getdatadisk(): error in Bread (eof?)"); free(ptr); return -1; } setmalloctag(ptr,getcallerpc(&m)); *buff=ptr; return (vlong)n; } vlong getdata(Message *m, char **buff, vlong start, vlong end, vlong offset, vlong len) { Message *root; char *cbuff; char *dbuff; int merge; vlong cstart, clen, cend, rlen; vlong dstart, dlen; rlen=0; merge=0; root=m->root; if (len == 0 ) { *buff=nil; return 0; } if ( len > end-start ) len=end-start; cstart=(start+offset)-root->start; clen=len; cend=end-root->start; if ( cend > CACHELEN ) cend=CACHELEN; rlen=0; /* if there is something in cache, read it */ if ( cstart < cend ) { if ( cstart+clen > cend ) { merge=1; clen=cend-cstart; } /* rlen is allways clen here */ rlen=getdatacache(m, &cbuff, cstart,clen); if ( rlen <= 0 ) { error(DEBUG,"getdata(): I/O error %d\n", rlen); *buff=nil; return 0; } } clen=rlen; /* if we have all, return */ if ( clen == len ) { *buff=cbuff; return clen; } dstart=start+offset+clen; dlen=len-clen; if ( dstart < end ) { if ( dstart+dlen > end ) dlen=end-dstart; rlen=getdatadisk(m,&dbuff,dstart,dlen); if ( rlen < 0 ) { *buff=nil; return 0; } else dlen=rlen; } else { *buff=nil; return 0; } if ( merge == 1 ) { *buff=emalloc(clen+dlen); memcpy(*buff,cbuff,clen); memcpy(*buff+clen,dbuff,dlen); free(cbuff); free(dbuff); } else { *buff=dbuff; } return clen+dlen; } char* gethdr(Message *m, char *term) { String *item; int pos; vlong hlen; char *r; char *hdr; item=s_newalloc(MAX_LINE); // max line size rfc822 hlen=m->hend-m->hstart; hlen=getdata(m,&hdr,m->hstart,m->hend,0,hlen); if ( hdr == nil ) { return nil; } pos=0; while(hdr[pos] != 0 && pos <= hlen) { if ( hdr[pos] == '\n' ) { if ( pos < hlen ) pos++; if ( hdr[pos] != ' ' && hdr[pos] != '\t' ) { s_terminate(item); if (cistrncmp(s_to_c(item),term,strlen(term)) != 0 ) { s_free(item); item=s_newalloc(MAX_LINE); } else { break; } } } else { s_putc(item,hdr[pos]); pos++; } } if ( s_len(item) <= 0 ) { s_free(item); free(hdr); return nil; } s_terminate(item); r=estrdup(s_to_c(item)); s_free(item); free(hdr); setmalloctag(r,getcallerpc(&m)); return r; } vlong getterm(Message *m, char **buff, char *term) { char *q,*aux,*resp; aux=gethdr(m,term); if ( aux == nil ) { *buff=nil; return 0; } q=strstr(aux,":"); if(q!=nil) { q++; resp=strdup(q); free(aux); *buff=resp; return (vlong)strlen(resp); } free(aux); *buff=nil; return 0; } char* nexttok(char *p,int c) { char *q; q=p; while( *q != 0 && *q!=c) q++; return q; } char* getattr(char *str, char *attr) { String *val; char *v; char *q; val=s_new(); q=strtok(str,";"); while (q!=nil) { q++; //skip ; while( (*q == ' ' || *q == '\t') && *q != 0 ) q++; if ( *q != 0 && cistrncmp(q,attr,strlen(attr)) == 0 ) { q+=strlen(attr)+1; // skip the = sign if (*q == '"') q++; while(*q!=0 && *q!='"') { s_putc(val,*q); q++; } if( s_len(val) <= 0) { s_free(val); return nil; } s_terminate(val); v=strdup(s_to_c(val)); s_free(val); setmalloctag(v,getcallerpc(&str)); return v; } q=strtok(nil,";"); } s_free(val); return nil; } char* getval(char *str) { char *q; String *item; char *v; q=str; item=s_new(); while(*q == ' ' || *q == '\t') q++; while(*q != ';' && *q != 0) { s_putc(item,*q); q++; } if( s_len(item) <= 0 ) { s_free(item); return nil; } s_terminate(item); v=strdup(s_to_c(item)); s_free(item); setmalloctag(v,getcallerpc(&str)); return v; } int cache(Message *m, char *buff, int len) { if ( buff == nil ) return 0; if ( m->cache->l >= CACHELEN ) return 0; if ( (m->cache->l+len) > CACHELEN ) len=CACHELEN-m->cache->l; memcpy(&m->cache->b[m->cache->l],buff,len); m->cache->l+=len; return 1; } /* parsing functions */ char* parsedate(Message *m) { char *str; char *p, *q; Tm tm; char *date; char *resp; getterm(m,&str, "Received"); if ( str == nil ) return nil; if((q = strchr(str, ';')) != nil){ p = q; while((p = strchr(p, '\n')) != nil){ if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n') break; p++; } if ( strtotm(q+1,&tm) <0) { free(str); return nil; } } else { free(str); return nil; } date = asctime(&tm); if(q = strchr(date, '\n')) *q = '\0'; free(str); resp=estrdup(date); return resp; } int isbox(Mailbox *b) { char *line; vlong pos; pos=Boffset(&b->br); line=getline(&b->br); if ( line == nil ) return 0; /* A mbox file each msg should start with From we just check the From */ Bseek(&b->br, pos, SEEK_SET); if (IS_HEADER(line)) { free(line); return 1; }else { free(line); return 0; } } int islike(char *b1, char *b2) { if ( b1 != nil && b2 != nil) return cistrncmp(b1,b2,strlen(b2)); else return -1; } int skipnewlines(Message *m) { Mailbox *b; char *line; vlong pos; b=m->b; while( (line=getline(&b->br)) !=nil) { if ( line[0]!='\n') break; cache(m,line,strlen(line)); free(line); } if (line != nil) { pos=Boffset(&b->br)-strlen(line); Bseek(&b->br,pos,SEEK_SET); free(line); } return 1; } int ismark(Message *m, char *s, int lastnl) { if ( s == nil ) return ENDF; if (lastnl == '\n' && IS_HEADER(s)) return HEADER; if (islike(s,m->mime.endboundary) == 0 ) return ENDBOUNDARY; if (islike(s,m->mime.boundary) == 0) return BOUNDARY; return REGULAR; } int parsemime(Message *m) { char *str; char *aux; int l,r; r=0; l=getterm(m,&str,"Content-Type:"); if ( l > 0 ) { m->mime.type=getval(str); aux=getattr(str,"boundary"); if ( aux != nil ) { m->mime.boundary=smprint("--%s",aux); m->mime.endboundary=smprint("--%s--",aux); free(aux); r=1; } free(str); } l=getterm(m,&str,"Content-Disposition:"); if ( l > 0) { m->mime.disposition=getval(str); m->mime.filename=getattr(str,"filename"); free(str); } l=getterm(m,&str,"Content-Transfer-Encoding:"); if (l > 0){ m->mime.encoding=getval(str); free(str); } return r; } int parsehdr(Message *m) { Mailbox *b; char *line; int linelen; b=m->b; m->hstart=Boffset(&b->br); while( (line=getline(&b->br) ) != nil ) { m->linecount++; linelen=strlen(line); if ( cache(m, line, linelen) == 0 ) error(DEBUG,"parsehdr(): error caching line\n"); if ( line[0] == '\n' ) { // end of header is an empty line m->hend=Boffset(&b->br); free(line); return 1; } free(line); } m->hend=Boffset(&b->br); return 0; } int parsebody(Message *m) { Mailbox *b; char *line; int linelen; vlong pos; int lastnl; int q; b=m->b; m->bstart=Boffset(&b->br); lastnl=0; while ( (line=getline(&b->br) ) != nil ) { m->linecount++; linelen=strlen(line); q=ismark(m,line,lastnl); if ( q != REGULAR ) { pos=Boffset(&b->br)-linelen; Bseek(&b->br,pos,SEEK_SET); m->bend=pos; m->end=m->bend; free(line); return q; } cache(m, line, linelen); lastnl=line[0]; free(line); } m->bend=Boffset(&b->br); m->end=m->bend; return 0; } void copymimeboundary(Message *m1, Message *m2) { if ( m1 != nil && m2 != nil && m1==m2 ) return; if (m2->mime.boundary != nil ) m1->mime.boundary=strdup(m2->mime.boundary); if (m2->mime.endboundary != nil ) m1->mime.endboundary=strdup(m2->mime.endboundary); } int findboundary(Message *m) { Mailbox *b; char *line; int lastnl,q; b=m->b; lastnl=0; while ( (line=getline(&b->br) ) != nil ) { q=ismark(m,line,lastnl); cache(m,line,strlen(line)); if ( q != REGULAR && q != HEADER ) { free(line); return q; } lastnl=line[0]; free(line); } return ENDF; } Message* parseparts(Mailbox *b, Message *last) { Message *m, *part, *next; int r, nparts; m=newmsg(Boffset(&b->br)); initmsg(m); m->b=last->b; m->id=last->id+1; m->cache=last->cache; m->root=last->root; r=parsehdr(m); if ( r==0 ) { freem(m); return nil; } r=parsemime(m); if (r == 0 && last != nil ) copymimeboundary(m,last); part=nil; if ( islike(m->mime.type,"multipart/") == 0) { skipnewlines(m); do { r=findboundary(m); } while ( r != BOUNDARY && r != ENDF); if ( r == ENDF ) return nil; m->bstart=Boffset(&b->br); nparts=0; do { if ( (next=parseparts(b,m) ) == nil) break; nparts++; next->id=nparts; if ( nparts == 1 ) { m=ptoend(m,next); part=next; } else part=mtoend(part,next); } while ( next->status == BOUNDARY ); do { r=findboundary(m); } while ( r != BOUNDARY && r != ENDBOUNDARY && r != ENDF); if ( r == ENDF ) return nil; m->status=r; skipnewlines(m); m->end=Boffset(&b->br); } else m->status=parsebody(m); return m; } /* interface to fs module */ /* message operations */ vlong mbox_date(Message *m, char **buff) { *buff=parsedate(m); if ( *buff != nil ) return strlen(*buff); else return 0; } vlong mbox_from(Message *m,char **buff) { char *str; vlong len; len=getterm(m, &str, "From:"); *buff=addrrfc(str); free(str); if (*buff !=nil) return strlen(*buff); return 0; } vlong mbox_to(Message *m, char **buff) { char *str; vlong len; len=getterm(m, &str, "To:"); *buff=addrrfc(str); free(str); if (*buff !=nil) return strlen(*buff); return 0; } vlong mbox_inreplyto(Message *m,char **buff) { char *str; vlong len; len=getterm(m, &str, "In-Reply-To:"); *buff=addrrfc(str); free(str); if (*buff !=nil) return strlen(*buff); return 0; } vlong mbox_replyto(Message *m, char **buff) { char *str; vlong len; len=getterm(m, &str, "Reply-To:"); *buff=addrrfc(str); free(str); if (*buff !=nil) return strlen(*buff); return 0; } vlong mbox_sender(Message *m,char **buff) { char *str; vlong len; len=getterm(m, &str, "Sender:"); *buff=addrrfc(str); free(str); if (*buff !=nil) return strlen(*buff); return 0; } vlong mbox_bcc(Message* m,char **buff) { char *str; vlong len; len=getterm(m, &str, "Bcc:"); *buff=addrrfc(str); free(str); if (*buff !=nil) return strlen(*buff); return 0; } vlong mbox_cc(Message* m,char **buff) { char *str; vlong len; len=getterm(m, &str, "Cc:"); *buff=addrrfc(str); free(str); if (*buff !=nil) return strlen(*buff); return 0; } vlong mbox_subject(Message *m, char **buff) { return getterm(m,buff,"Subject:"); } vlong mbox_messageid(Message *m, char **buff) { return getterm(m,buff, "Message-ID:"); } vlong mbox_disposition(Message *, char **buff) { *buff=nil; return 0; } vlong mbox_filename(Message*,char **buff) { *buff=nil; return 0; } vlong mbox_digest(Message *m, char**buff) { return getdigest(m,buff); } vlong mbox_lines(Message *m, char **buff) { *buff=smprint("%d",m->linecount); return (vlong)strlen(*buff); } vlong mbox_header(Message *m ,char **buff) { Fmt info; char *s; char *a; fmtstrinit(&info); getterm(m,&s,"From:"); if ( s!= nil ) { a=addrrfc(s); fmtprint(&info,"From: %s\n",a); free(a); free(s); } getterm(m,&s,"To:"); if ( s!= nil ) { a=addrrfc(s); fmtprint(&info,"To: %s\n",a); free(a); free(s); } getterm(m,&s,"Cc:"); if ( s!= nil ) { a=addrrfc(s); fmtprint(&info,"Cc: %s\n",a); free(a); free(s); } getterm(m,&s,"Reply-To:"); if ( s!= nil ) { a=addrrfc(s); fmtprint(&info,"Reply-To: %s\n",a); free(a); free(s); } s=parsedate(m); if ( s!= nil ) { fmtprint(&info,"Date: %s\n",s); free(s); } getterm(m,&s, "Subject:"); if ( s!= nil ) { a=stringconvert(s); fmtprint(&info,"Subject: %s\n",a); free(a); free(s); } getterm(m,&s,"Bcc:"); if ( s!= nil ) { a=addrrfc(s); fmtprint(&info,"Bcc: %s\n",a); free(a); free(s); } getterm(m,&s,"In-Reply-To:"); if ( s!= nil ) { a=addrrfc(s); fmtprint(&info,"In-Reply-To: %s\n",a); free(a); free(s); } *buff=fmtstrflush(&info); return (vlong)strlen(*buff); } /* used by nedmail client sender address recipient addresses cc addresses reply address envelope date subject MIME content type MIME disposition filename SHA1 digest bcc addresses in-reply-to: contents RFC822 date message senders message id number of lines in body if not found a \n is returned; */ vlong mbox_info(Message*m, char **buff) { Fmt info; char *s; char *aux; fmtstrinit(&info); getterm(m,&s,"From:"); aux=addrrfc(s); if ( aux != nil ) fmtprint(&info,"%s\n",aux); else fmtprint(&info,"\n"); free(s); free(aux); getterm(m,&s,"To:"); aux=addrrfc(s); if ( aux != nil ) fmtprint(&info,"%s\n",aux); else fmtprint(&info,"\n"); free(s); free(aux); getterm(m,&s,"Cc:"); aux=addrrfc(s); if ( aux != nil ) fmtprint(&info,"%s\n",aux); else fmtprint(&info,"\n"); free(s); free(aux); getterm(m,&s,"Reply-To:"); aux=addrrfc(s); if ( aux != nil ) fmtprint(&info,"%s\n",aux); else fmtprint(&info,"\n"); free(s); free(aux); s=parsedate(m); if ( s != nil ) fmtprint(&info,"%s\n",s); else fmtprint(&info,"\n"); free(s); getterm(m,&s, "Subject:"); aux=stringconvert(s); if ( aux != nil ) fmtprint(&info,"%s\n",aux); else fmtprint(&info,"\n"); free(s);free(aux); if (m->mime.type != nil ) fmtprint(&info,"%s\n",m->mime.type); else fmtprint(&info,"\n"); if (m->mime.disposition != nil ) fmtprint(&info,"%s\n",m->mime.disposition); else fmtprint(&info,"\n"); if ( m->mime.filename != nil ) fmtprint(&info,"%s\n",m->mime.filename); else fmtprint(&info,"\n"); getdigest(m,&s); if ( s != nil ) fmtprint(&info,"%s\n",s); else fmtprint(&info,"\n"); free(s); getterm(m,&s,"Bcc:"); aux=addrrfc(s); if ( aux != nil ) fmtprint(&info,"%s\n",aux); else fmtprint(&info,"\n"); free(s); free(aux); getterm(m,&s,"In-Reply-To:"); aux=addrrfc(s); if ( aux != nil ) fmtprint(&info,"%s\n",aux); else fmtprint(&info,"\n"); free(s); free(aux); s=parsedate(m); if ( s != nil ) fmtprint(&info,"%s\n",s); else fmtprint(&info,"\n"); free(s); getterm(m,&s,"From:"); aux=stringconvert(s); if ( aux != nil ) fmtprint(&info,"%s\n",aux); else fmtprint(&info,"\n"); free(s); free(aux); getterm(m,&s,"Message-ID:"); if ( s != nil ) fmtprint(&info,"%s\n",s); else fmtprint(&info,"\n"); free(s); fmtprint(&info,"%8.d\n",m->linecount); *buff=fmtstrflush(&info); return (vlong)strlen(*buff); } vlong mbox_mimeheader(Message*,char **buff) { *buff=nil; return 0; } vlong mbox_raw(Message *m,char **buff, vlong offset, vlong len) { return getdata(m, buff, m->start, m->end, offset, len); } vlong mbox_rawbody(Message *m,char **buff, vlong offset, vlong len) { return getdata(m, buff, m->bstart, m->bend, offset, len); } vlong mbox_body(Message *m,char **buff, vlong offset, vlong len) { return getdata(m, buff, m->bstart, m->bend, offset, len); } vlong mbox_rawheader(Message *m,char **buff, vlong offset, vlong len) { return getdata(m, buff, m->hstart, m->hend, offset, len); } vlong mbox_rawunix(Message *m,char **buff, vlong offset, vlong len) { return getdata(m, buff, m->start, m->end, offset, len); } vlong mbox_type(Message *m,char **buff) { return getterm(m,buff, "Content-Type:"); } vlong mbox_unixdate(Message *m,char **buff) { *buff=parsedate(m); if ( *buff != nil ) return strlen(*buff); else return 0; } vlong mbox_unixheader(Message *m,char **buff) { char *str; str=gethdr(m,"From "); if ( str == nil ) return 0; *buff=smprint("%s\n",str); free(str); return strlen(*buff); } /* box operations */ int mbox_fetch(Mailbox *b) { Message *m, *part, *next; int r, np; if ( ! isbox(b) ) error(FATAL,"mbox_fetch(): error in mbox format\n"); do { m=newmsg(Boffset(&b->br)); initmsg(m); m->b=b; m->id=b->idc+1; m->cache=emalloc(sizeof *b->msgs->cache); m->cache->l=0; m->root=m; r=parsehdr(m); if ( r==0 ) { freem(m); break; } r=parsemime(m); np=1; part=m; m->bstart=Boffset(&b->br); if ( islike(m->mime.type,"multipart/") == 0) { do { next=parseparts(b,m); if ( next == nil ) break; next->id=np; if ( np == 1) { m=ptoend(m,next); part=next; } else part=mtoend(part,next); r=findboundary(m); skipnewlines(m); np++; } while ( r == BOUNDARY); } else r=parsebody(m); m->bend=Boffset(&b->br); m->end=m->bend; b->msgs=mtofront(b->msgs,m); b->idc++; } while ( r != ENDF ); return 1; } int mbox_open(Mailbox *b) { if ( b->file == nil ) error(FATAL,"mbox_open(): b->name == nil"); b->r=open(b->file,OREAD); b->w=open(b->file,ORDWR); b->idc=0; if ( b->r < 0 || b->w < 0 ) error(FATAL,"Error opening %s\n",b->file); Binit(&b->br, b->r,OREAD); Binit(&b->bw,b->w,OWRITE); return 1; } int mbox_delete(Message *m) { Dir *d; int c; vlong from, to, rpos, wpos, offset, newlen; Biobuf *r,*w; r=&m->b->br; w=&m->b->bw; from=m->start; to=m->end; offset=to-from; if (offset <=0 ) return -1; d=dirfstat(Bfildes(r)); if ( d == nil ) return -1; if ( from > d->length || from < 0 || to > d->length || to < 0 ) { free(d); return -1; } rpos = Bseek(r, to, SEEK_SET); if ( rpos == Beof) { free(d); return -1; } wpos=Bseek(w,from, SEEK_SET); if ( wpos == Beof) { free(d); return -1; } newlen=d->length-to; for(vlong i=0; ilength = newlen; dirfwstat(Bfildes(w),d); free(d); return 1; }