/* ======================================================================== * Copyright 2008-2011 Mark Crispin * ======================================================================== */ /* * Program: IMAP4rev1 server * * Author: Mark Crispin * * Date: 5 November 1990 * Last Edited: 30 July 2011 * * Previous versions of this file were * * Copyright 1988-2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * */ /* Parameter files */ #include #include #include extern int errno; /* just in case */ #include #include #include #include #include "c-client.h" #include "newsrc.h" #include #define CRLF PSOUT ("\015\012") /* primary output terpri */ /* Timeouts and timers */ #define MINUTES *60 #define LOGINTIMEOUT 3 MINUTES /* not logged in autologout timer */ #define TIMEOUT 30 MINUTES /* RFC 3501 minimum autologout timer */ #define INPUTTIMEOUT 5 MINUTES /* timer for additional command input */ #define ALERTTIMER 1 MINUTES /* alert check timer */ #define SHUTDOWNTIMER 1 MINUTES /* shutdown dally timer */ #define IDLETIMER 1 MINUTES /* IDLE command poll timer */ #define CHECKTIMER 5 MINUTES /* IDLE command last checkpoint timer */ #define SIGNALTIMER 10 MINUTES /* allocated time to die at signal */ #define MAXNLIBADCOMMAND 3 /* limit on number of NLI bad commands */ #define MAXTAG 50 /* maximum tag length */ #define LITSTKLEN 20 /* length of literal stack */ #define MAXCLIENTLIT 10000 /* maximum non-APPEND client literal size * must be smaller than 4294967295 */ #define MAXAPPENDTXT 0x40000000 /* maximum APPEND literal size * must be smaller than 4294967295 */ #define CMDLEN 65536 /* size of command buffer */ /* Server states */ typedef enum { LOGIN = 0, SELECT, OPEN, LOGOUT } IMAPSTATE; /* Body text fetching */ typedef struct text_args { char *section; /* body section */ STRINGLIST *lines; /* header lines */ unsigned long first; /* first octet to fetch */ unsigned long last; /* number of octets to fetch */ long flags; /* fetch flags */ long binary; /* binary flags */ } TEXTARGS; #define FTB_BINARY 0x1 /* fetch as binary */ #define FTB_SIZE 0x2 /* fetch size only */ /* Append data */ typedef struct append_data { unsigned char *arg; /* append argument pointer */ char *flags; /* message flags */ char *date; /* message date */ char *msg; /* message text */ STRING *message; /* message stringstruct */ } APPENDDATA; /* Message pointer */ typedef struct msg_data { MAILSTREAM *stream; /* stream */ unsigned long msgno; /* message number */ char *flags; /* current flags */ char *date; /* current date */ STRING *message; /* stringstruct of message */ } MSGDATA; /* Function prototypes */ int main (int argc,char *argv[]); void ping_mailbox (unsigned long uid); time_t palert (char *file,time_t oldtime); void msg_string_init (STRING *s,void *data,unsigned long size); char msg_string_next (STRING *s); void msg_string_setpos (STRING *s,unsigned long i); void new_flags (MAILSTREAM *stream); void settimeout (unsigned int i); void clkint (void); void dieint (void); void kodint (void); void hupint (void); void trmint (void); void staint (void); char *sout (char *s,char *t); char *nout (char *s,unsigned long n,unsigned long base); void slurp (char *s,int n,unsigned long timeout); void inliteral (char *s,unsigned long n); unsigned char *flush (void); void ioerror (FILE *f,char *reason); unsigned char *parse_astring (unsigned char **arg,unsigned long *i, unsigned char *del); unsigned char *parse_tag (unsigned char *cmd,unsigned char **ret); unsigned char *snarf (unsigned char **arg); unsigned char *snarf_base64 (unsigned char **arg); unsigned char *snarf_list (unsigned char **arg); STRINGLIST *parse_stringlist (unsigned char **s,int *list); unsigned long uidmax (MAILSTREAM *stream); long parse_criteria (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg, unsigned long maxuid,unsigned long depth); long parse_criterion (SEARCHPGM *pgm,unsigned char **arg,unsigned long msgmsg, unsigned long maxuid,unsigned long depth); long crit_date (unsigned short *date,unsigned char **arg); long crit_date_work (unsigned short *date,unsigned char **arg); long crit_set (SEARCHSET **set,unsigned char **arg,unsigned long maxima); long crit_number (unsigned long *number,unsigned char **arg); long crit_string (STRINGLIST **string,unsigned char **arg); void fetch (char *t,unsigned long uid); typedef void (*fetchfn_t) (unsigned long i,void *args); void fetch_work (char *t,unsigned long uid,fetchfn_t f[],void *fa[]); void fetch_bodystructure (unsigned long i,void *args); void fetch_body (unsigned long i,void *args); void fetch_body_part_mime (unsigned long i,void *args); void fetch_body_part_contents (unsigned long i,void *args); void fetch_body_part_binary (unsigned long i,void *args); void fetch_body_part_header (unsigned long i,void *args); void fetch_body_part_text (unsigned long i,void *args); void remember (unsigned long uid,char *id,SIZEDTEXT *st); void fetch_envelope (unsigned long i,void *args); void fetch_encoding (unsigned long i,void *args); void changed_flags (unsigned long i,int f); void fetch_flags (unsigned long i,void *args); void put_flag (int *c,char *s); void fetch_internaldate (unsigned long i,void *args); void fetch_uid (unsigned long i,void *args); void fetch_rfc822 (unsigned long i,void *args); void fetch_rfc822_header (unsigned long i,void *args); void fetch_rfc822_size (unsigned long i,void *args); void fetch_rfc822_text (unsigned long i,void *args); void penv (ENVELOPE *env); void pbodystructure (BODY *body); void pbody (BODY *body); void pparam (PARAMETER *param); void paddr (ADDRESS *a); void pset (SEARCHSET **set); void pnum (unsigned long i); void pstring (char *s); void pnstring (char *s); void pastring (char *s); void psizedquoted (SIZEDTEXT *s); void psizedliteral (SIZEDTEXT *s,STRING *st); void psizedstring (SIZEDTEXT *s,STRING *st); void psizedastring (SIZEDTEXT *s); void pastringlist (STRINGLIST *s); void pnstringorlist (STRINGLIST *s); void pbodypartstring (unsigned long msgno,char *id,SIZEDTEXT *st,STRING *bs, TEXTARGS *ta); void ptext (SIZEDTEXT *s,STRING *st); void pthread (THREADNODE *thr); void pcapability (long flag); long nameok (char *ref,char *name); char *bboardname (char *cmd,char *name); long isnewsproxy (char *name); long newsproxypattern (char *ref,char *pat,char *pattern,long flag); char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen); long proxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); long proxy_append (MAILSTREAM *stream,void *data,char **flags,char **date, STRING **message); long append_msg (MAILSTREAM *stream,void *data,char **flags,char **date, STRING **message); void copyuid (MAILSTREAM *stream,char *mailbox,unsigned long uidvalidity, SEARCHSET *sourceset,SEARCHSET *destset); void appenduid (char *mailbox,unsigned long uidvalidity,SEARCHSET *set); char *referral (MAILSTREAM *stream,char *url,long code); void mm_list_work (char *what,int delimiter,char *name,long attributes); char *lasterror (void); /* Global storage */ char *version = "417"; /* edit number of this server */ char *logout = "Logout"; /* syslogreason for logout */ char *goodbye = NIL; /* bye reason */ time_t alerttime = 0; /* time of last alert */ time_t sysalerttime = 0; /* time of last system alert */ time_t useralerttime = 0; /* time of last user alert */ time_t lastcheck = 0; /* time of last checkpoint */ time_t shutdowntime = 0; /* time of last shutdown */ int blackberry = 0; /* 1 if BlackBerry, -1 if not, 0 if unknown */ int nlibcm = MAXNLIBADCOMMAND; /* limit number of bad commands */ IMAPSTATE state = LOGIN; /* server state */ int cancelled = NIL; /* authenticate cancelled */ int trycreate = 0; /* saw a trycreate */ int finding = NIL; /* doing old FIND command */ int anonymous = 0; /* non-zero if anonymous */ int critical = NIL; /* non-zero if in critical code */ int quell_events = NIL; /* non-zero if in FETCH response */ int existsquelled = NIL; /* non-zero if an EXISTS was quelled */ int proxylist = NIL; /* doing a proxy LIST */ MAILSTREAM *stream = NIL; /* mailbox stream */ DRIVER *curdriver = NIL; /* note current driver */ MAILSTREAM *tstream = NIL; /* temporary mailbox stream */ unsigned int nflags = 0; /* current number of keywords */ unsigned long nmsgs =0xffffffff;/* last reported # of messages and recent */ unsigned long recent = 0xffffffff; char *nntpproxy = NIL; /* NNTP proxy name */ unsigned char *user = NIL; /* user name */ unsigned char *pass = NIL; /* password */ unsigned char *initial = NIL; /* initial response */ unsigned char cmdbuf[CMDLEN]; /* command buffer */ char *status = "starting up"; /* server status */ char *tag; /* tag portion of command */ unsigned char *cmd; /* command portion of command */ unsigned char *arg; /* pointer to current argument of command */ char *lstwrn = NIL; /* last warning message from c-client */ char *lsterr = NIL; /* last error message from c-client */ char *lstref = NIL; /* last referral from c-client */ char *response = NIL; /* command response */ char *sstate = NIL; /* strtok state */ struct { unsigned long size; /* size of current LITERAL+ */ unsigned int ok : 1; /* LITERAL+ in effect */ } litplus; int litsp = 0; /* literal stack pointer */ char *litstk[LITSTKLEN]; /* stack to hold literals */ unsigned long uidvalidity = 0; /* last reported UID validity */ unsigned long lastuid = 0; /* last fetched uid */ char *lastid = NIL; /* last fetched body id for this message */ char *lastsel = NIL; /* last selected mailbox name */ SIZEDTEXT lastst = {NIL,0}; /* last sizedtext */ unsigned long cauidvalidity = 0;/* UIDVALIDITY for COPYUID/APPENDUID */ SEARCHSET *csset = NIL; /* COPYUID source set */ SEARCHSET *caset = NIL; /* COPYUID/APPENDUID destination set */ jmp_buf jmpenv; /* stack context for setjmp */ /* Response texts which appear in multiple places */ char *win = "%.80s OK "; char *rowin = "%.80s OK [READ-ONLY] %.80s completed\015\012"; char *rwwin = "%.80s OK [READ-WRITE] %.80s completed\015\012"; char *lose = "%.80s NO "; char *logwin = "%.80s OK ["; char *losetry = "%.80s NO [TRYCREATE] %.80s failed: %.800s\015\012"; char *loseunknowncte = "%.80s NO [UNKNOWN-CTE] %.80s failed: %.800s\015\012"; char *badcmd = "%.80s BAD Command unrecognized: %.80s\015\012"; char *badcml = "%.80s BAD Command unrecognized\015\012"; char *misarg = "%.80s BAD Missing or invalid argument to %.80s\015\012"; char *badarg = "%.80s BAD Argument given to %.80s when none expected\015\012"; char *badseq = "%.80s BAD Bogus sequence in %.80s: %.80s\015\012"; char *badatt = "%.80s BAD Bogus attribute list in %.80s\015\012"; char *badbin = "%.80s BAD Syntax error in binary specifier\015\012"; /* Message string driver for message stringstructs */ STRINGDRIVER msg_string = { msg_string_init, /* initialize string structure */ msg_string_next, /* get next byte in string structure */ msg_string_setpos /* set position in string structure */ }; /* Main program */ int main (int argc,char *argv[]) { unsigned long i,uid; long f; unsigned char *s,*t,*u,*v,tmp[MAILTMPLEN]; struct stat sbuf; logouthook_t lgoh; int ret = 0; time_t autologouttime = 0; char *pgmname; /* if case we get borked immediately */ if (setjmp (jmpenv)) _exit (1); pgmname = (argc && argv[0]) ? (((s = strrchr (argv[0],'/')) || (s = strrchr (argv[0],'\\'))) ? (char *) s+1 : argv[0]) : "imapd"; /* set service name before linkage */ mail_parameters (NIL,SET_SERVICENAME,(void *) "imap"); #include "linkage.c" rfc822_date (tmp); /* get date/time at startup */ /* initialize server */ server_init (pgmname,"imap","imaps",clkint,kodint,hupint,trmint,staint); /* forbid automatic untagged expunge */ mail_parameters (NIL,SET_EXPUNGEATPING,NIL); /* arm proxy copy callback */ mail_parameters (NIL,SET_MAILPROXYCOPY,(void *) proxycopy); /* arm referral callback */ mail_parameters (NIL,SET_IMAPREFERRAL,(void *) referral); /* arm COPYUID callback */ mail_parameters (NIL,SET_COPYUID,(void *) copyuid); /* arm APPENDUID callback */ mail_parameters (NIL,SET_APPENDUID,(void *) appenduid); if (stat (SHUTDOWNFILE,&sbuf)) { char proxy[MAILTMPLEN]; FILE *nntp = fopen (NNTPFILE,"r"); if (nntp) { /* desire NNTP proxy? */ if (fgets (proxy,MAILTMPLEN,nntp)) { /* remove newline and set NNTP proxy */ if ((s = strchr (proxy,'\n')) != NULL) *s = '\0'; nntpproxy = cpystr (proxy); /* disable the news driver */ mail_parameters (NIL,DISABLE_DRIVER,"news"); } fclose (nntp); /* done reading proxy name */ } s = myusername_full (&i); /* get user name and flags */ switch (i) { case MU_NOTLOGGEDIN: PSOUT ("* OK ["); /* not logged in, ordinary startup */ pcapability (-1); break; case MU_ANONYMOUS: anonymous = T; /* anonymous user, fall into default */ s = "ANONYMOUS"; case MU_LOGGEDIN: PSOUT ("* PREAUTH ["); /* already logged in, pre-authorized */ pcapability (1); user = cpystr (s); /* copy user name */ pass = cpystr ("*"); /* set fake password */ state = SELECT; /* enter select state */ break; default: fatal ("Unknown state from myusername_full()"); } PSOUT ("] "); if (user) { /* preauthenticated as someone? */ PSOUT ("Pre-authenticated user "); PSOUT (user); PBOUT (' '); } } else { /* login disabled */ PSOUT ("* BYE Service not available "); state = LOGOUT; } PSOUT (tcp_serverhost ()); PSOUT (" Panda IMAP "); PSOUT (CCLIENTVERSION); PBOUT ('.'); PSOUT (version); PSOUT (" at "); PSOUT (tmp); CRLF; PFLUSH (); /* dump output buffer */ switch (state) { /* do this after the banner */ case LOGIN: autologouttime = time (0) + LOGINTIMEOUT; break; case SELECT: syslog (LOG_INFO,"Preauthenticated user=%.80s host=%.80s", user,tcp_clienthost ()); break; } if (setjmp (jmpenv)) { /* die if a signal handler say so */ /* in case we get borked now */ if (setjmp (jmpenv)) _exit (1); /* need to close stream gracefully? */ if (stream && !stream->lock && (stream->dtb->flags & DR_XPOINT)) stream = mail_close (stream); ret = 1; /* set exit status */ } else while (state != LOGOUT) {/* command processing loop */ slurp (cmdbuf,CMDLEN,TIMEOUT); /* no more last error or literal */ if (lstwrn) fs_give ((void **) &lstwrn); if (lsterr) fs_give ((void **) &lsterr); if (lstref) fs_give ((void **) &lstref); while (litsp) fs_give ((void **) &litstk[--litsp]); /* find end of line */ if ((t = strchr (cmdbuf,'\012')) != NULL) { /* tie off command termination */ if ((t > cmdbuf) && (t[-1] == '\015')) --t; *t = '\0'; /* tie off LF or CRLF */ } if (!t) { /* probably line too long if not terminated */ if ((t = strchr (cmdbuf,' ')) != NULL) { if ((t - cmdbuf) > MAXTAG) t = NIL; else *t = '\0'; } flush (); /* flush excess, set response */ if (state == LOGIN) { /* error if NLI */ syslog (LOG_INFO,"Line too long before authentication host=%.80s", tcp_clienthost ()); state = LOGOUT; } sprintf (tmp,response,t ? (char *) cmdbuf : "*"); PSOUT (tmp); } else if (!(tag = parse_tag (cmdbuf,&s))) { if (state == LOGIN) { /* error if NLI */ syslog (LOG_INFO,"Null command before authentication host=%.80s", tcp_clienthost ()); state = LOGOUT; } PSOUT ("* BAD Missing command tag\015\012"); } else if (strlen (tag) > MAXTAG) PSOUT ("* BAD Excessively long tag\015\012"); else if (!*s) { /* make sure have command */ if (state == LOGIN) { /* error if NLI */ syslog (LOG_INFO,"Missing command before authentication host=%.80s", tcp_clienthost ()); state = LOGOUT; } PSOUT (tag); PSOUT (" BAD Missing command\015\012"); } else { /* parse command */ response = win; /* set default response */ finding = NIL; /* no longer FINDing */ /* UID command? */ if (((s[0] == 'U') || (s[0] == 'u')) && ((s[1] == 'I') || (s[1] == 'i')) && ((s[2] == 'D') || (s[2] == 'd')) && (s[3] == ' ')) { uid = T; /* a UID command */ arg = strchr (s+4,' '); /* find argument */ } else { uid = NIL; /* not a UID command */ arg = strchr (s,' '); /* find argument */ } if (arg) *arg++ = '\0'; /* tie off command argument */ ucase (s); /* canonicalize command case */ /* flush previous saved command */ if (cmd) fs_give ((void **) &cmd); cmd = cpystr (s); /* save current command */ /* snarf argument, see if possible litplus */ if (arg && ((i = strlen (arg)) > 3) && (arg[i - 1] == '}') && (arg[i - 2] == '+') && isdigit (arg[i - 3])) { /* back over possible count */ for (i -= 4; i && isdigit (arg[i]); i--); if (arg[i] == '{') { /* found a literal? */ litplus.ok = T; /* yes, note LITERAL+ in effect, set size */ litplus.size = strtoul (arg + i + 1,NIL,10); } } if (!blackberry) { /* do we know if it's a BlackBerry yet? */ char *bb[] = {".blackberry.com", ".blackberry.net", NIL}; size_t bbs; unsigned char *cl = cpystr (tcp_clienthost ()); size_t cls; /* get just host name, calculate length */ if ((t = strchr (cl,' ')) != NULL) { *t = '\0'; cls = t - cl; } else cls = strlen (cl); for (i = 0; bb[i]; ++i) { if (((bbs = strlen (bb[i])) < cls) && !compare_cstring(bb[i], cl + cls - bbs)) blackberry = 1; } fs_give ((void **) & cl); /* not a blackberry if no match on list */ if(!blackberry) blackberry = -1; } /* these commands always valid */ if (!strcmp (cmd,"NOOP")) { if (arg) response = badarg; else if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } else if (!strcmp (cmd,"LOGOUT")) { if (arg) response = badarg; else { /* time to say farewell */ server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN); if (lastsel) fs_give ((void **) &lastsel); if (state == OPEN) stream = mail_close (stream); state = LOGOUT; PSOUT ("* BYE "); PSOUT (mylocalhost ()); PSOUT (" IMAP4rev1 server terminating connection\015\012"); } } else if (!strcmp (cmd,"CAPABILITY")) { if (arg) response = badarg; else { PSOUT ("* "); pcapability (0); /* print capabilities */ CRLF; } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } #ifdef NETSCAPE_BRAIN_DAMAGE else if (!strcmp (cmd,"NETSCAPE")) { PSOUT ("* OK [NETSCAPE]\015\012* VERSION 1.0 UNIX\015\012* ACCOUNT-URL \""); PSOUT (NETSCAPE_BRAIN_DAMAGE); PBOUT ('"'); CRLF; } #endif else switch (state) { /* dispatch depending upon state */ case LOGIN: /* waiting to get logged in */ /* new style authentication */ if (!strcmp (cmd,"AUTHENTICATE")) { if (user) fs_give ((void **) &user); if (pass) fs_give ((void **) &pass); initial = NIL; /* no initial argument */ cancelled = NIL; /* not cancelled */ /* mandatory first argument */ if (!(s = snarf (&arg))) response = misarg; else if (arg && !(initial = snarf_base64 (&arg))) response = misarg; /* optional second argument */ else if (arg) response = badarg; else if (!strcmp (ucase (s),"ANONYMOUS") && !stat (ANOFILE,&sbuf)) { if (!(s = imap_responder ("",0,NIL))) response ="%.80s BAD AUTHENTICATE ANONYMOUS cancelled\015\012"; else if (anonymous_login (argc,argv)) { anonymous = T; /* note we are anonymous */ user = cpystr ("ANONYMOUS"); pass = cpystr ("*"); state = SELECT; /* make select */ alerttime = 0; /* force alert */ response = logwin;/* return logged-in capabilities */ syslog (LOG_INFO,"Authenticated anonymous=%.80s host=%.80s",s, tcp_clienthost ()); fs_give ((void **) &s); } else response ="%.80s NO AUTHENTICATE ANONYMOUS failed\015\012"; } else if ((user = cpystr (mail_auth (s,imap_responder,argc,argv))) != NULL) { pass = cpystr ("*"); state = SELECT; /* make select */ alerttime = 0; /* force alert */ response = logwin; /* return logged-in capabilities */ syslog (LOG_INFO,"Authenticated user=%.80s host=%.80s mech=%.80s", user,tcp_clienthost (),s); } else { AUTHENTICATOR *auth = mail_lookup_auth (1); char *msg = (char *) fs_get (strlen (cmd) + strlen (s) + 2); sprintf (msg,"%s %s",cmd,s); fs_give ((void **) &cmd); cmd = msg; for (i = !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL); auth && compare_cstring (s,auth->name); auth = auth->next); /* Failed authentication when hidden looks like invalid command. * This is intentional but confused me when I was debugging. */ if (auth && auth->server && !(auth->flags & AU_DISABLE) && !(auth->flags & AU_HIDE) && (i || (auth->flags & AU_SECURE))) { response = lose; if (cancelled) { if (lsterr) fs_give ((void **) &lsterr); lsterr = cpystr ("cancelled by user"); } if (!lsterr) /* catch-all */ lsterr = cpystr ("Invalid authentication credentials"); syslog (LOG_INFO,"AUTHENTICATE %.80s failure host=%.80s",s, tcp_clienthost ()); } else { response = badcml; syslog (LOG_INFO,"AUTHENTICATE %.80s invalid host=%.80s",s, tcp_clienthost ()); } } } /* plaintext login with password */ else if (!strcmp (cmd,"LOGIN")) { if (user) fs_give ((void **) &user); if (pass) fs_give ((void **) &pass); /* two arguments */ if (!((user = cpystr (snarf (&arg))) && (pass = cpystr (snarf (&arg))))) response = misarg; else if (arg) response = badarg; /* see if we allow anonymous */ else if (!compare_cstring (user,"ANONYMOUS") && !stat (ANOFILE,&sbuf) && anonymous_login (argc,argv)) { anonymous = T; /* note we are anonymous */ ucase (user); /* make all uppercase for consistency */ state = SELECT; /* make select */ alerttime = 0; /* force alert */ response = logwin; /* return logged-in capabilities */ syslog (LOG_INFO,"Login anonymous=%.80s host=%.80s",pass, tcp_clienthost ()); } else { /* delimit user from possible admin */ if ((s = strchr (user,'*')) != NULL) *s++ ='\0'; /* see if username and password are OK */ if (server_login (user,pass,s,argc,argv)) { state = SELECT; /* make select */ alerttime = 0; /* force alert */ response = logwin;/* return logged-in capabilities */ syslog (LOG_INFO,"Login user=%.80s host=%.80s",user, tcp_clienthost ()); } else { response = lose; if (!lsterr) lsterr = cpystr ("Invalid login credentials"); } } } /* start TLS security */ else if (!strcmp (cmd,"STARTTLS")) { if (arg) response = badarg; else if ((lsterr = ssl_start_tls (pgmname)) != NULL) response = lose; } else { response = badcml; /* limit the number of bad commands */ if (--nlibcm <= 0) state = LOGOUT; } break; case OPEN: /* valid only when mailbox open */ /* fetch mailbox attributes */ if (!strcmp (cmd,"FETCH") || !strcmp (cmd,"UID FETCH")) { if (!(arg && (s = strtok_r (arg," ",&sstate)) && (t = strtok_r (NIL,"\015\012",&sstate)))) response = misarg; else if (uid ? mail_uid_sequence (stream,s) : mail_sequence (stream,s)) fetch (t,uid); else response = badseq; } /* store mailbox attributes */ else if (!strcmp (cmd,"STORE") || !strcmp (cmd,"UID STORE")) { /* must have three arguments */ if (!(arg && (s = strtok_r (arg," ",&sstate)) && (v = strtok_r (NIL," ",&sstate)) && (t = strtok_r (NIL,"\015\012",&sstate)))) response = misarg; else if (!(uid ? mail_uid_sequence (stream,s) : mail_sequence (stream,s))) response = badseq; else { f = ST_SET | (uid ? ST_UID : NIL)|((v[5]&&v[6]) ? ST_SILENT : NIL); if (!strcmp (ucase (v),"FLAGS") || !strcmp (v,"FLAGS.SILENT")) { strcpy (tmp,"\\Answered \\Flagged \\Deleted \\Draft \\Seen"); for (i = 0, u = tmp; (i < NUSERFLAGS) && (v = stream->user_flags[i]); i++) if (strlen (v) < ((size_t) (MAILTMPLEN - ((u += strlen (u)) + 2 - tmp)))) { *u++ = ' '; /* write next flag */ strcpy (u,v); } mail_flag (stream,s,tmp,f & ~ST_SET); } else if (!strcmp (v,"-FLAGS") || !strcmp (v,"-FLAGS.SILENT")) f &= ~ST_SET; /* clear flags */ else if (strcmp (v,"+FLAGS") && strcmp (v,"+FLAGS.SILENT")) { response = badatt; break; } /* find last keyword */ for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; i++); mail_flag (stream,s,t,f); /* any new keywords appeared? */ if (i < NUSERFLAGS && stream->user_flags[i]) new_flags (stream); /* return flags if silence not wanted */ if (uid ? mail_uid_sequence (stream,s) : mail_sequence (stream,s)) for (i = 1; i <= nmsgs; i++) if (mail_elt(stream,i)->sequence) mail_elt (stream,i)->spare2 = (f & ST_SILENT) ? NIL : T; } } /* check for new mail */ else if (!strcmp (cmd,"CHECK")) { /* no arguments */ if (arg) response = badarg; else if (!anonymous) { mail_check (stream); /* remember last check time */ lastcheck = time (0); } } /* expunge deleted messages */ else if (!(anonymous || (strcmp (cmd,"EXPUNGE") && strcmp (cmd,"UID EXPUNGE")))) { if (uid && !arg) response = misarg; else if (!uid && arg) response = badarg; else { /* expunge deleted or specified UIDs */ mail_expunge_full (stream,arg,arg ? EX_UID : NIL); /* remember last checkpoint */ lastcheck = time (0); } } /* close mailbox */ else if (!strcmp (cmd,"CLOSE") || !strcmp (cmd,"UNSELECT")) { /* no arguments */ if (arg) response = badarg; else { /* no last uid */ uidvalidity = lastuid = 0; if (lastsel) fs_give ((void **) &lastsel); if (lastid) fs_give ((void **) &lastid); if (lastst.data) fs_give ((void **) &lastst.data); stream = mail_close_full (stream,((*cmd == 'C') && !anonymous) ? CL_EXPUNGE : NIL); state = SELECT; /* no longer opened */ lastcheck = 0; /* no last checkpoint */ } } else if (!anonymous && /* copy message(s) */ (!strcmp (cmd,"COPY") || !strcmp (cmd,"UID COPY"))) { trycreate = NIL; /* no trycreate status */ if (!(arg && (s = strtok_r (arg," ",&sstate)) && (arg = strtok_r (NIL,"\015\012",&sstate)) && (t = snarf (&arg)))) response = misarg; else if (arg) response = badarg; else if (!nmsgs) { response = lose; if (!lsterr) lsterr = cpystr ("Mailbox is empty"); } else if (!(uid ? mail_uid_sequence (stream,s) : mail_sequence (stream,s))) response = badseq; /* try copy */ else if (!mail_copy_full (stream,s,t,uid ? CP_UID : NIL)) { response = trycreate ? losetry : lose; if (!lsterr) lsterr = cpystr ("No such destination mailbox"); } } /* sort mailbox */ else if (!strcmp (cmd,"SORT") || !strcmp (cmd,"UID SORT")) { /* must have four arguments */ if (!(arg && (*arg == '(') && (t = strchr (s = arg + 1,')')) && (t[1] == ' ') && (*(arg = t + 2)))) response = misarg; else { /* read criteria */ SEARCHPGM *spg = NIL; char *cs = NIL; SORTPGM *pgm = NIL,*pg = NIL; unsigned long *slst,*sl; *t = NIL; /* tie off criteria list */ if (!(s = strtok_r (ucase (s)," ",&sstate))) response = badatt; else { do { /* parse sort attributes */ if (pg) pg = pg->next = mail_newsortpgm (); else pgm = pg = mail_newsortpgm (); if (!strcmp (s,"REVERSE")) { pg->reverse = T; if (!(s = strtok_r (NIL," ",&sstate))) { s = ""; /* end of attributes */ break; } } if (!strcmp (s,"DATE")) pg->function = SORTDATE; else if (!strcmp (s,"ARRIVAL")) pg->function = SORTARRIVAL; else if (!strcmp (s,"FROM")) pg->function = SORTFROM; else if (!strcmp (s,"SUBJECT")) pg->function = SORTSUBJECT; else if (!strcmp (s,"TO")) pg->function = SORTTO; else if (!strcmp (s,"CC")) pg->function = SORTCC; else if (!strcmp (s,"SIZE")) pg->function = SORTSIZE; else break; } while ((s = strtok_r (NIL," ",&sstate)) != NULL); /* bad SORT attribute */ if (s) response = badatt; /* get charset and search criteria */ else if (!((t = snarf (&arg)) && (cs = cpystr (t)) && arg && *arg)) response = misarg; /* parse search criteria */ else if (!parse_criteria (spg = mail_newsearchpgm (),&arg,nmsgs, uidmax (stream),0)) response = badatt; else if (arg && *arg) response = badarg; else if ((slst = mail_sort (stream,cs,spg,pgm,uid ? SE_UID:NIL)) != NULL) { PSOUT ("* SORT"); for (sl = slst; *sl; sl++) { PBOUT (' '); pnum (*sl); } CRLF; fs_give ((void **) &slst); } } if (pgm) mail_free_sortpgm (&pgm); if (spg) mail_free_searchpgm (&spg); if (cs) fs_give ((void **) &cs); } } /* thread mailbox */ else if (!strcmp (cmd,"THREAD") || !strcmp (cmd,"UID THREAD")) { THREADNODE *thr; SEARCHPGM *spg = NIL; char *cs = NIL; /* must have four arguments */ if (!(arg && (s = strtok_r (arg," ",&sstate)) && (cs = strtok_r (NIL," ",&sstate)) && (cs = cpystr (cs)) && (arg = strtok_r (NIL,"\015\012",&sstate)))) response = misarg; else if (!parse_criteria (spg = mail_newsearchpgm (),&arg,nmsgs, uidmax (stream),0)) response = badatt; else if (arg && *arg) response = badarg; else { if ((thr = mail_thread (stream,s,cs,spg,uid ? SE_UID : NIL)) != NULL) { PSOUT ("* THREAD "); pthread (thr); mail_free_threadnode (&thr); } else PSOUT ("* THREAD"); CRLF; } if (spg) mail_free_searchpgm (&spg); if (cs) fs_give ((void **) &cs); } /* search mailbox */ else if (!strcmp (cmd,"SEARCH") || !strcmp (cmd,"UID SEARCH")) { int retval = NIL; char *charset = NIL; SEARCHPGM *pgm; response = misarg; /* assume failure */ if (!arg) break; /* one or more arguments required */ if (((arg[0] == 'R') || (arg[0] == 'r')) && ((arg[1] == 'E') || (arg[1] == 'e')) && ((arg[2] == 'T') || (arg[2] == 't')) && ((arg[3] == 'U') || (arg[3] == 'u')) && ((arg[4] == 'R') || (arg[4] == 'r')) && ((arg[5] == 'N') || (arg[5] == 'n')) && (arg[6] == ' ') && (arg[7] == '(')) { retval = 0x4000; /* return is specified */ for (arg += 8; *arg && (*arg != ')'); ) { if (((arg[0] == 'M') || (arg[0] == 'm')) && ((arg[1] == 'I') || (arg[1] == 'i')) && ((arg[2] == 'N') || (arg[2] == 'n')) && ((arg[3] == ' ') || (arg[3] == ')'))) { retval |= 0x1; arg += 3; } else if (((arg[0] == 'M') || (arg[0] == 'm')) && ((arg[1] == 'A') || (arg[1] == 'a')) && ((arg[2] == 'X') || (arg[2] == 'x')) && ((arg[3] == ' ') || (arg[3] == ')'))) { retval |= 0x2; arg += 3; } else if (((arg[0] == 'A') || (arg[0] == 'a')) && ((arg[1] == 'L') || (arg[1] == 'l')) && ((arg[2] == 'L') || (arg[2] == 'l')) && ((arg[3] == ' ') || (arg[3] == ')'))) { retval |= 0x4; arg += 3; } else if (((arg[0] == 'C') || (arg[0] == 'c')) && ((arg[1] == 'O') || (arg[1] == 'o')) && ((arg[2] == 'U') || (arg[2] == 'u')) && ((arg[3] == 'N') || (arg[3] == 'n')) && ((arg[4] == 'T') || (arg[4] == 't')) && ((arg[5] == ' ') || (arg[5] == ')'))) { retval |= 0x10; arg += 5; } else break; /* unknown return value */ /* more return values to come */ if ((*arg == ' ') && (arg[1] != ')')) ++arg; } /* RETURN list must be properly terminated */ if ((*arg++ != ')') || (*arg++ != ' ')) break; /* default return value is ALL */ if (!(retval &= 0x3fff)) retval = 0x4; } /* character set specified? */ if (((arg[0] == 'C') || (arg[0] == 'c')) && ((arg[1] == 'H') || (arg[1] == 'h')) && ((arg[2] == 'A') || (arg[2] == 'a')) && ((arg[3] == 'R') || (arg[3] == 'r')) && ((arg[4] == 'S') || (arg[4] == 's')) && ((arg[5] == 'E') || (arg[5] == 'e')) && ((arg[6] == 'T') || (arg[6] == 't')) && (arg[7] == ' ')) { arg += 8; /* yes, skip over CHARSET token */ if ((s = snarf (&arg)) != NULL) charset = cpystr (s); else break; /* missing character set */ } /* must have arguments here */ if (!(arg && *arg)) break; if (parse_criteria (pgm = mail_newsearchpgm (),&arg,nmsgs, uidmax (stream),0) && !*arg) { response = win; /* looks good, try the search */ mail_search_full (stream,charset,pgm,SE_FREE); /* output search results if success */ if (response == win) { if (retval) { /* ESEARCH desired */ PSOUT ("* ESEARCH (TAG "); pstring (tag); PBOUT (')'); if (uid) PSOUT (" UID"); /* wants MIN */ if (retval & 0x1) { for (i = 1; (i <= nmsgs) && !mail_elt (stream,i)->searched; ++i); if (i <= nmsgs) { PSOUT (" MIN "); pnum (uid ? mail_uid (stream,i) : i); } } /* wants MAX */ if (retval & 0x2) { for (i = nmsgs; i && !mail_elt (stream,i)->searched; --i); if (i) { PSOUT (" MAX "); pnum (uid ? mail_uid (stream,i) : i); } } /* wants ALL */ if (retval & 0x4) { unsigned long j,juid,iuid; /* find first match */ for (i = 1; (i <= nmsgs) && !mail_elt (stream,i)->searched; ++i); if (i <= nmsgs) { juid= (uid ? mail_uid (stream,i) : i); PSOUT (" ALL "); pnum (juid); j = i; /* last message index processed */ } if (uid) { /* doing UIDs, no MRC optimization */ while (++i <= nmsgs) { if (mail_elt (stream,i)->searched) { /* this message matches */ iuid= mail_uid (stream,i); if (juid == (iuid - (i-j))) { /* in sequence of UIDs */ if ( i < nmsgs) continue; /* may be more, check next */ PBOUT (':'); pnum(iuid); /* done with all, tie it off */ j = i; juid= iuid; } else { /* break in sequence of UIDs */ if ((i-1) > j) { /* prev sequence, tie it off */ j= i-1; juid= mail_uid (stream,j); PBOUT (':'); pnum(juid); } PBOUT (','); pnum(iuid); j = i; juid= iuid; /* record last done message/UID */ } } else { /* this message doesn't match, have a pending sequence? */ /* if pending sequence, tie it off & reset flags */ if ((i-1) > j) { /* prev sequence, tie it off */ j= i-1; juid= mail_uid (stream,j); PBOUT (':'); pnum(juid); } j= nmsgs+1; juid= ULONG_MAX; /* flag that we're not in a sequence of matches */ } } } else { /* doing message-IDs, use MRC optimization */ while (++i <= nmsgs) { if (mail_elt (stream,i)->searched) { while ((++i <= nmsgs) && mail_elt (stream,i)->searched); /* previous message is end of range */ if (j != --i) { PBOUT (':'); pnum (i); } } /* search for next match */ while ((++i <= nmsgs) && !mail_elt (stream,i)->searched); if (i <= nmsgs) { PBOUT (','); pnum (i); j = i; /* last message output */ } } } } /* wants COUNT */ if (retval & 0x10) { unsigned long j; for (i = 1, j = 0; i <= nmsgs; ++i) if (mail_elt (stream,i)->searched) ++j; PSOUT (" COUNT "); pnum (j); } } else { /* standard search */ PSOUT ("* SEARCH"); for (i = 1; i <= nmsgs; ++i) if (mail_elt (stream,i)->searched) { PBOUT (' '); pnum (uid ? mail_uid (stream,i) : i); } } CRLF; } } else mail_free_searchpgm (&pgm); if (charset) fs_give ((void **) &charset); } else /* fall into select case */ case SELECT: /* valid whenever logged in */ /* select new mailbox */ if (!(strcmp (cmd,"SELECT") && strcmp (cmd,"EXAMINE") && strcmp (cmd,"BBOARD"))) { /* single argument */ if (!(s = snarf (&arg))) response = misarg; else if (arg) response = badarg; else if (nameok (NIL,s = bboardname (cmd,s))) { DRIVER *factory = mail_valid (NIL,s,NIL); f = anonymous ? OP_ANONYMOUS | OP_READONLY : (((blackberry > 0) || (*cmd == 'S')) ? NIL : OP_READONLY); curdriver = NIL; /* no drivers known */ /* no last uid */ uidvalidity = lastuid = 0; if (lastid) fs_give ((void **) &lastid); if (lastst.data) fs_give ((void **) &lastst.data); nflags = 0; /* force update */ nmsgs = recent = 0xffffffff; if (factory && !strcmp (factory->name,"phile") && (stream = mail_open (stream,s,f | OP_SILENT)) && (response == win)) { BODY *b; /* see if proxy open */ if ((mail_elt (stream,1)->rfc822_size < 400) && mail_fetchstructure (stream,1,&b) && (b->type == TYPETEXT) && (t = mail_fetch_text (stream,1,NIL,&i,NIL)) && (i < MAILTMPLEN) && (t[0] == '{')) { /* copy and tie off */ strncpy (tmp,t,i)[i] = '\0'; /* nuke any trailing newline */ if ((t = strpbrk (tmp,"\r\n")) != NULL) *t = '\0'; /* try to open proxy */ if ((tstream = mail_open (NIL,tmp,f | OP_SILENT)) && (response == win) && tstream->nmsgs) { s = tmp; /* got it, close the link */ mail_close (stream); stream = tstream; tstream = NIL; } } /* now give the exists event */ stream->silent = NIL; mm_exists (stream,stream->nmsgs); } else if (!factory && isnewsproxy (s)) { sprintf (tmp,"{%.300s/nntp}%.300s",nntpproxy,(char *) s+6); stream = mail_open (stream,tmp,f); } /* open stream normally then */ else stream = mail_open (stream,s,f); if (stream && (response == win)) { state = OPEN; /* note state open */ if (lastsel) fs_give ((void **) &lastsel); /* canonicalize INBOX */ if (!compare_cstring (s,"#MHINBOX")) lastsel = cpystr ("#MHINBOX"); else lastsel = cpystr (compare_cstring (s,"INBOX") ? (char *) s : "INBOX"); /* note readonly/readwrite */ response = stream->rdonly ? rowin : rwwin; if (anonymous) syslog (LOG_INFO,"Anonymous select of %.80s host=%.80s", stream->mailbox,tcp_clienthost ()); lastcheck = 0; /* no last check */ } else { /* failed, nuke old selection */ if (stream) stream = mail_close (stream); state = SELECT; /* no mailbox open now */ if (lastsel) fs_give ((void **) &lastsel); response = lose; /* open failed */ } } } /* APPEND message to mailbox */ else if (!(anonymous || strcmp (cmd,"APPEND"))) { /* parse mailbox name */ if ((s = snarf (&arg)) && arg) { STRING st; /* message stringstruct */ APPENDDATA ad; ad.arg = arg; /* command arguments */ /* no message yet */ ad.flags = ad.date = ad.msg = NIL; ad.message = &st; /* pointer to stringstruct to use */ trycreate = NIL; /* no trycreate status */ if (!mail_append_multiple (NIL,s,append_msg,(void *) &ad)) { if (response == win) response = trycreate ? losetry : lose; /* this can happen with #driver. hack */ if (!lsterr) lsterr = cpystr ("No such destination mailbox"); } /* clean up any message text left behind */ if (ad.flags) fs_give ((void **) &ad.flags); if (ad.date) fs_give ((void **) &ad.date); if (ad.msg) fs_give ((void **) &ad.msg); } else response = misarg; if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* list mailboxes */ else if (!strcmp (cmd,"LIST") || !strcmp (cmd,"RLIST")) { /* get reference and mailbox argument */ if (!((s = snarf (&arg)) && (t = snarf_list (&arg)))) response = misarg; else if (arg) response = badarg; /* make sure anonymous can't do bad things */ else if (nameok (s,t)) { if (newsproxypattern (s,t,tmp,LONGT)) { proxylist = T; mail_list (NIL,"",tmp); proxylist = NIL; } else mail_list (NIL,s,t); } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* scan mailboxes */ else if (!strcmp (cmd,"SCAN")) { /* get arguments */ if (!((s = snarf (&arg)) && (t = snarf_list (&arg)) && (u = snarf (&arg)))) response = misarg; else if (arg) response = badarg; /* make sure anonymous can't do bad things */ else if (nameok (s,t)) { if (newsproxypattern (s,t,tmp,NIL)) mm_log ("SCAN not permitted for news",ERROR); else mail_scan (NIL,s,t,u); } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* list subscribed mailboxes */ else if (!strcmp (cmd,"LSUB") || !strcmp (cmd,"RLSUB")) { /* get reference and mailbox argument */ if (!((s = snarf (&arg)) && (t = snarf_list (&arg)))) response = misarg; else if (arg) response = badarg; /* make sure anonymous can't do bad things */ else if (nameok (s,t)) { if (newsproxypattern (s,t,tmp,NIL)) newsrc_lsub (NIL,tmp); else mail_lsub (NIL,s,t); } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* find mailboxes */ else if (!strcmp (cmd,"FIND")) { /* get subcommand and true argument */ if (!(arg && (s = strtok_r (arg," \015\012",&sstate)) && (s == cmd + 5) && (cmd[4] = ' ') && ucase (s) && (arg = strtok_r (NIL,"\015\012",&sstate)) && (s = snarf_list (&arg)))) response = misarg; /* missing required argument */ else if (arg) response = badarg; /* punt on single-char wildcards */ else if (strpbrk (s,"%?")) response = "%.80s NO IMAP2 ? and %% wildcards not supported: %.80s\015\012"; else if (nameok (NIL,s)) { finding = T; /* note that we are FINDing */ /* dispatch based on type */ if (!strcmp (cmd,"FIND MAILBOXES") && !anonymous) mail_lsub (NIL,NIL,s); else if (!strcmp (cmd,"FIND ALL.MAILBOXES")) { /* convert * to % for compatible behavior */ for (t = s; *t; t++) if (*t == '*') *t = '%'; mail_list (NIL,NIL,s); } else response = badcmd; } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* status of mailbox */ else if (!strcmp (cmd,"STATUS")) { if (!((s = snarf (&arg)) && arg && (*arg++ == '(') && (t = strchr (arg,')')) && (t - arg) && !t[1])) response = misarg; else { f = NIL; /* initially no flags */ *t = '\0'; /* tie off flag string */ /* read flags */ t = strtok_r (ucase (arg)," ",&sstate); do { /* parse each one; unknown generate warning */ if (!strcmp (t,"MESSAGES")) f |= SA_MESSAGES; else if (!strcmp (t,"RECENT")) f |= SA_RECENT; else if (!strcmp (t,"UNSEEN")) f |= SA_UNSEEN; else if (!strcmp (t,"UIDNEXT")) f |= SA_UIDNEXT; else if (!strcmp (t,"UIDVALIDITY")) f |= SA_UIDVALIDITY; else { PSOUT ("* NO Unknown status flag "); PSOUT (t); CRLF; } } while ((t = strtok_r (NIL," ",&sstate)) != NULL); ping_mailbox (uid); /* in case the fool did STATUS on open mbx */ PFLUSH (); /* make sure stdout is dumped in case slave */ if (!compare_cstring (s,"INBOX")) s = "INBOX"; else if (!compare_cstring (s,"#MHINBOX")) s = "#MHINBOX"; if (state == LOGOUT) response = lose; /* get mailbox status */ else if (lastsel && (!strcmp (s,lastsel) || (stream && !strcmp (s,stream->mailbox)))) { unsigned long unseen; /* snarl at cretins which do this */ PSOUT ("* NO CLIENT BUG DETECTED: STATUS on selected mailbox: "); PSOUT (s); CRLF; tmp[0] = ' '; tmp[1] = '\0'; if (f & SA_MESSAGES) sprintf (tmp + strlen (tmp)," MESSAGES %lu",stream->nmsgs); if (f & SA_RECENT) sprintf (tmp + strlen (tmp)," RECENT %lu",stream->recent); if (f & SA_UNSEEN) { for (i = 1,unseen = 0; i <= stream->nmsgs; i++) if (!mail_elt (stream,i)->seen) unseen++; sprintf (tmp + strlen (tmp)," UNSEEN %lu",unseen); } if (f & SA_UIDNEXT) sprintf (tmp + strlen (tmp)," UIDNEXT %lu",stream->uid_last+1); if (f & SA_UIDVALIDITY) sprintf (tmp + strlen(tmp)," UIDVALIDITY %lu", stream->uid_validity); tmp[1] = '('; strcat (tmp,")\015\012"); PSOUT ("* STATUS "); pastring (s); PSOUT (tmp); } else if (isnewsproxy (s)) { sprintf (tmp,"{%.300s/nntp}%.300s",nntpproxy,(char *) s+6); if (!mail_status (NIL,tmp,f)) response = lose; } else if (!mail_status (NIL,s,f)) response = lose; } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* subscribe to mailbox */ else if (!(anonymous || strcmp (cmd,"SUBSCRIBE"))) { /* get or MAILBOX */ if (!(s = snarf (&arg))) response = misarg; else if (arg) { /* IMAP2bis form */ if (compare_cstring (s,"MAILBOX")) response = badarg; else if (!(s = snarf (&arg))) response = misarg; else if (arg) response = badarg; else mail_subscribe (NIL,s); } else if (isnewsproxy (s)) newsrc_update (NIL,s+6,':'); else mail_subscribe (NIL,s); if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* unsubscribe to mailbox */ else if (!(anonymous || strcmp (cmd,"UNSUBSCRIBE"))) { /* get or MAILBOX */ if (!(s = snarf (&arg))) response = misarg; else if (arg) { /* IMAP2bis form */ if (compare_cstring (s,"MAILBOX")) response = badarg; else if (!(s = snarf (&arg))) response = misarg; else if (arg) response = badarg; else if (isnewsproxy (s)) newsrc_update (NIL,s+6,'!'); else mail_unsubscribe (NIL,s); } else mail_unsubscribe (NIL,s); if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } else if (!strcmp (cmd,"NAMESPACE")) { if (arg) response = badarg; else { NAMESPACE **ns = (NAMESPACE **) mail_parameters(NIL,GET_NAMESPACE, NIL); NAMESPACE *n; PARAMETER *p; PSOUT ("* NAMESPACE"); if (ns) for (i = 0; i < 3; i++) { if ((n = ns[i]) != NULL) { PSOUT (" ("); do { PBOUT ('('); pstring (n->name); switch (n->delimiter) { case '\\': /* quoted delimiter */ case '"': PSOUT (" \"\\\\\""); break; case '\0': /* no delimiter */ PSOUT (" NIL"); break; default: /* unquoted delimiter */ PSOUT (" \""); PBOUT (n->delimiter); PBOUT ('"'); break; } /* NAMESPACE extensions are hairy */ if ((p = n->param) != NULL) do { PBOUT (' '); pstring (p->attribute); PSOUT (" ("); do pstring (p->value); while (p->next && !p->next->attribute && (p = p->next)); PBOUT (')'); } while ((p = p->next) != NULL); PBOUT (')'); } while ((n = n->next) != NULL); PBOUT (')'); } else PSOUT (" NIL"); } else PSOUT (" NIL NIL NIL"); CRLF; } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* create mailbox */ else if (!(anonymous || strcmp (cmd,"CREATE"))) { if (!(s = snarf (&arg))) response = misarg; else if (arg) response = badarg; else mail_create (NIL,s); if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* delete mailbox */ else if (!(anonymous || strcmp (cmd,"DELETE"))) { if (!(s = snarf (&arg))) response = misarg; else if (arg) response = badarg; else { /* make sure not selected */ if (lastsel && (!strcmp (s,lastsel) || (stream && !strcmp (s,stream->mailbox)))) mm_log ("Can not DELETE the selected mailbox",ERROR); else mail_delete (NIL,s); } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* rename mailbox */ else if (!(anonymous || strcmp (cmd,"RENAME"))) { if (!((s = snarf (&arg)) && (t = snarf (&arg)))) response = misarg; else if (arg) response = badarg; else { /* make sure not selected */ if (!compare_cstring (s,"INBOX")) s = "INBOX"; else if (!compare_cstring (s,"#MHINBOX")) s = "#MHINBOX"; if (lastsel && (!strcmp (s,lastsel) || (stream && !strcmp (s,stream->mailbox)))) mm_log ("Can not RENAME the selected mailbox",ERROR); else mail_rename (NIL,s,t); } if (stream) /* allow untagged EXPUNGE */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream); } /* idle mode */ else if (!strcmp (cmd,"IDLE")) { /* no arguments */ if (arg) response = badarg; else { /* tell client ready for argument */ unsigned long donefake = 0; PSOUT ("+ Waiting for DONE\015\012"); PFLUSH (); /* dump output buffer */ /* inactivity countdown */ i = ((TIMEOUT) / (IDLETIMER)) + 1; do { /* main idle loop */ if (!donefake) { /* don't ping mailbox if faking */ mail_parameters (stream,SET_ONETIMEEXPUNGEATPING, (void *) stream); ping_mailbox (uid); /* maybe do a checkpoint if not anonymous */ if (!anonymous && stream && (time (0) > (lastcheck + CHECKTIMER))) { mail_check (stream); /* cancel likely altwin from mail_check() */ if (lsterr) fs_give ((void **) &lsterr); if (lstwrn) fs_give ((void **) &lstwrn); /* remember last checkpoint */ lastcheck = time (0); } } if (lstwrn) { /* have a warning? */ PSOUT ("* NO "); PSOUT (lstwrn); CRLF; fs_give ((void **) &lstwrn); } if (!(i % 2)) { /* prevent NAT timeouts */ sprintf (tmp,"* OK Timeout in %lu minutes\015\012", (i * IDLETIMER) / 60); PSOUT (tmp); } /* two minutes before the end... */ if ((state == OPEN) && (i <= 2)) { sprintf (tmp,"* %lu EXISTS\015\012* %lu RECENT\015\012", donefake = nmsgs + 1,recent + 1); PSOUT (tmp); /* prod client to wake up */ } PFLUSH (); /* dump output buffer */ } while ((state != LOGOUT) && !INWAIT (IDLETIMER) && --i); /* time to exit idle loop */ if (state != LOGOUT) { if (i) { /* still have time left? */ /* yes, read expected DONE */ slurp (tmp,MAILTMPLEN,INPUTTIMEOUT); if (((tmp[0] != 'D') && (tmp[0] != 'd')) || ((tmp[1] != 'O') && (tmp[1] != 'o')) || ((tmp[2] != 'N') && (tmp[2] != 'n')) || ((tmp[3] != 'E') && (tmp[3] != 'e')) || (((tmp[4] != '\015') || (tmp[5] != '\012')) && (tmp[4] != '\012'))) response = "%.80s BAD Bogus IDLE continuation\015\012"; if (donefake) { /* if faking at the end */ /* send EXPUNGE (should be just 1) */ while (donefake > nmsgs) { sprintf (tmp,"* %lu EXPUNGE\015\012",donefake--); PSOUT (tmp); } sprintf (tmp,"* %lu EXISTS\015\012* %lu RECENT\015\012", nmsgs,recent); PSOUT (tmp); } } else clkint (); /* otherwise do autologout action */ } } } else response = badcmd; break; default: response = "%.80s BAD Unknown state for %.80s command\015\012"; break; } while (litplus.ok) { /* any unread LITERAL+? */ litplus.ok = NIL; /* yes, cancel it now */ clearerr (stdin); /* clear stdin errors */ status = "discarding unread literal"; /* read literal and discard it */ while ((i = (litplus.size > MAILTMPLEN) ? MAILTMPLEN : litplus.size) != 0L){ if (state == LOGOUT) litplus.size = 0; else { settimeout (INPUTTIMEOUT); if (PSINR (tmp,i)) litplus.size -= i; else { ioerror (stdin,status); litplus.size = 0; /* in case it continues */ } } } settimeout (0); /* stop timeout */ /* get new command tail */ slurp (tmp,MAILTMPLEN,INPUTTIMEOUT); /* locate end of line */ if ((t = strchr (tmp,'\012')) != NULL) { /* back over CR */ if ((t > tmp) && (t[-1] == '\015')) --t; *t = NIL; /* tie off CRLF */ /* possible LITERAL+? */ if (((i = strlen (tmp)) > 3) && (tmp[i - 1] == '}') && (tmp[i - 2] == '+') && isdigit (tmp[i - 3])) { /* back over possible count */ for (i -= 4; i && isdigit (tmp[i]); i--); if (tmp[i] == '{') { /* found a literal? */ litplus.ok = T; /* yes, note LITERAL+ in effect, set size */ litplus.size = strtoul (tmp + i + 1,NIL,10); } } } else flush (); /* overlong line after LITERAL+, punt */ } ping_mailbox (uid); /* update mailbox status before response */ if (lstwrn && lsterr) { /* output most recent warning */ PSOUT ("* NO "); PSOUT (lstwrn); CRLF; fs_give ((void **) &lstwrn); } if (response == logwin) { /* authentication win message */ sprintf (tmp,response,lstref ? "*" : tag); PSOUT (tmp); /* start response */ pcapability (1); /* print logged-in capabilities */ PSOUT ("] User "); PSOUT (user); PSOUT (" authenticated\015\012"); if (lstref) { sprintf (tmp,response,tag); PSOUT (tmp); /* start response */ PSOUT ("[REFERRAL "); PSOUT (lstref); PSOUT ("] "); PSOUT (lasterror ()); CRLF; } } else if ((response == win) || (response == lose)) { sprintf (tmp,response,tag); PSOUT (tmp); if (cauidvalidity) { /* COPYUID/APPENDUID response? */ sprintf (tmp,"[%.80sUID %lu ",(char *) ((s = strchr (cmd,' ')) ? s+1 : cmd),cauidvalidity); PSOUT (tmp); cauidvalidity = 0; /* cancel response for future */ if (csset) { pset (&csset); PBOUT (' '); } pset (&caset); PSOUT ("] "); } else if (lstref) { /* have a referral? */ PSOUT ("[REFERRAL "); PSOUT (lstref); PSOUT ("] "); } if (lsterr || lstwrn) PSOUT (lasterror ()); else { PSOUT (cmd); PSOUT ((response == win) ? " completed" : "failed"); } CRLF; } else { /* normal response */ if ((response == rowin) || (response == rwwin)) { if (lstwrn) { /* output most recent warning */ PSOUT ("* NO "); PSOUT (lstwrn); CRLF; fs_give ((void **) &lstwrn); } } sprintf (tmp,response,tag,cmd,lasterror ()); PSOUT (tmp); /* output response */ } } PFLUSH (); /* make sure output blatted */ if (autologouttime) { /* have an autologout in effect? */ /* cancel if no longer waiting for login */ if (state != LOGIN) autologouttime = 0; /* took too long to login */ else if (autologouttime < time (0)) { logout = goodbye = "Autologout"; stream = NIL; state = LOGOUT; /* sayonara */ } } } if (goodbye && !quell_events){/* have a goodbye message? */ PSOUT ("* BYE "); /* utter it */ PSOUT (goodbye); CRLF; PFLUSH (); /* make sure blatted */ } syslog (LOG_INFO,"%s user=%.80s host=%.80s",logout, user ? (char *) user : "???",tcp_clienthost ()); /* do logout hook if needed */ if ((lgoh = (logouthook_t) mail_parameters (NIL,GET_LOGOUTHOOK,NIL)) != NULL) (*lgoh) (mail_parameters (NIL,GET_LOGOUTDATA,NIL)); _exit (ret); /* all done */ return ret; /* stupid compilers */ } /* Ping mailbox during each cycle. Also check alerts * Accepts: last command was UID flag */ void ping_mailbox (unsigned long uid) { unsigned long i; char tmp[MAILTMPLEN]; if (state == OPEN) { if (!mail_ping (stream)) { /* make sure stream still alive */ PSOUT ("* BYE "); PSOUT (mylocalhost ()); PSOUT (" Fatal mailbox error: "); PSOUT (lasterror ()); CRLF; stream = NIL; /* don't try to clean up stream */ state = LOGOUT; /* go away */ syslog (LOG_INFO, "Fatal mailbox error user=%.80s host=%.80s mbx=%.80s: %.80s", user ? (char *) user : "???",tcp_clienthost (), (stream && stream->mailbox) ? stream->mailbox : "???", lasterror ()); return; } /* change in number of messages? */ if (existsquelled || (nmsgs != stream->nmsgs)) { PSOUT ("* "); pnum (nmsgs = stream->nmsgs); PSOUT (" EXISTS\015\012"); } /* change in recent messages? */ if (existsquelled || (recent != stream->recent)) { PSOUT ("* "); pnum (recent = stream->recent); PSOUT (" RECENT\015\012"); } existsquelled = NIL; /* don't do this until asked again */ if (stream->uid_validity && (stream->uid_validity != uidvalidity)) { PSOUT ("* OK [UIDVALIDITY "); pnum (stream->uid_validity); PSOUT ("] UID validity status\015\012* OK [UIDNEXT "); pnum (stream->uid_last + 1); PSOUT ("] Predicted next UID\015\012"); if (stream->uid_nosticky) { PSOUT ("* NO [UIDNOTSTICKY] Non-permanent unique identifiers: "); PSOUT (stream->mailbox); CRLF; } uidvalidity = stream->uid_validity; } /* don't bother if driver changed */ if (curdriver == stream->dtb) { /* first report any new flags */ if ((nflags < NUSERFLAGS) && stream->user_flags[nflags]) new_flags (stream); for (i = 1; i <= nmsgs; i++) if (mail_elt (stream,i)->spare2) { PSOUT ("* "); pnum (i); PSOUT (" FETCH ("); fetch_flags (i,NIL); /* output changed flags */ if (uid) { /* need to include UIDs in response? */ PBOUT (' '); fetch_uid (i,NIL); } PSOUT (")\015\012"); } } else { /* driver changed */ new_flags (stream); /* send mailbox flags */ if (curdriver) { /* note readonly/write if possible change */ PSOUT ("* OK [READ-"); PSOUT (stream->rdonly ? "ONLY" : "WRITE"); PSOUT ("] Mailbox status\015\012"); } curdriver = stream->dtb; if (nmsgs) { /* get flags for all messages */ sprintf (tmp,"1:%lu",nmsgs); mail_fetch_flags (stream,tmp,NIL); /* don't do this if newsrc already did */ if (!(curdriver->flags & DR_NEWS)) { /* find first unseen message */ for (i = 1; i <= nmsgs && mail_elt (stream,i)->seen; i++); if (i <= nmsgs) { PSOUT ("* OK [UNSEEN "); pnum (i); PSOUT ("] first unseen message in "); PSOUT (stream->mailbox); CRLF; } } } } } if (shutdowntime && (time (0) > shutdowntime + SHUTDOWNTIMER)) { PSOUT ("* BYE Server shutting down\015\012"); state = LOGOUT; } /* don't do these stat()s every cycle */ else if (time (0) > alerttime + ALERTTIMER) { struct stat sbuf; /* have a shutdown file? */ if (!stat (SHUTDOWNFILE,&sbuf)) { PSOUT ("* OK [ALERT] Server shutting down shortly\015\012"); shutdowntime = time (0); } alerttime = time (0); /* output any new alerts */ sysalerttime = palert (ALERTFILE,sysalerttime); if (state != LOGIN) /* do user alert if logged in */ useralerttime = palert (mailboxfile (tmp,USERALERTFILE),useralerttime); } } /* Print an alert file * Accepts: path of alert file * time of last printed alert file * Returns: updated time of last printed alert file */ time_t palert (char *file,time_t oldtime) { FILE *alf; struct stat sbuf; int c,lc = '\012'; /* have a new alert file? */ if (stat (file,&sbuf) || (sbuf.st_mtime <= oldtime) || !(alf = fopen (file,"r"))) return oldtime; /* yes, display it */ while ((c = getc (alf)) != EOF) { if (lc == '\012') PSOUT ("* OK [ALERT] "); switch (c) { /* output character */ case '\012': /* newline means do CRLF */ CRLF; case '\015': /* flush CRs */ case '\0': /* flush nulls */ break; default: PBOUT (c); /* output all other characters */ break; } lc = c; /* note previous character */ } fclose (alf); if (lc != '\012') CRLF; /* final terminating CRLF */ return sbuf.st_mtime; /* return updated last alert time */ } /* Initialize file string structure for file stringstruct * Accepts: string structure * pointer to message data structure * size of string */ void msg_string_init (STRING *s,void *data,unsigned long size) { MSGDATA *md = (MSGDATA *) data; s->data = data; /* note stream/msgno and header length */ #if 0 s->size = size; /* message size */ s->curpos = s->chunk = /* load header */ mail_fetchheader_full (md->stream,md->msgno,NIL,&s->data1, FT_PREFETCHTEXT | FT_PEEK); #else /* This kludge is necessary because of broken mail stores */ mail_fetchtext_full (md->stream,md->msgno,&s->size,FT_PEEK); s->curpos = s->chunk = /* load header */ mail_fetchheader_full (md->stream,md->msgno,NIL,&s->data1,FT_PEEK); s->size += s->data1; /* header + body size */ #endif s->cursize = s->chunksize = s->data1; s->offset = 0; /* offset is start of message */ } /* Get next character from file stringstruct * Accepts: string structure * Returns: character, string structure chunk refreshed */ char msg_string_next (STRING *s) { char c = *s->curpos++; /* get next byte */ SETPOS (s,GETPOS (s)); /* move to next chunk */ return c; /* return the byte */ } /* Set string pointer position for file stringstruct * Accepts: string structure * new position */ void msg_string_setpos (STRING *s,unsigned long i) { MSGDATA *md = (MSGDATA *) s->data; if (i < s->data1) { /* want header? */ s->chunk = mail_fetchheader_full (md->stream,md->msgno,NIL,NIL,FT_PEEK); s->chunksize = s->data1; /* header length */ s->offset = 0; /* offset is start of message */ } else if (i < s->size) { /* want body */ s->chunk = mail_fetchtext_full (md->stream,md->msgno,NIL,FT_PEEK); s->chunksize = s->size - s->data1; s->offset = s->data1; /* offset is end of header */ } else { /* off end of message */ s->chunk = NIL; /* make sure that we crack on this then */ s->chunksize = 1; /* make sure SNX cracks the right way... */ s->offset = i; } /* initial position and size */ s->curpos = s->chunk + (i -= s->offset); s->cursize = s->chunksize - i; } /* Send flags for stream * Accepts: MAIL stream * scratch buffer */ void new_flags (MAILSTREAM *stream) { int i,c; PSOUT ("* FLAGS ("); for (i = 0; i < NUSERFLAGS; i++) if (stream->user_flags[i]) { PSOUT (stream->user_flags[i]); PBOUT (' '); nflags = i + 1; } PSOUT ("\\Answered \\Flagged \\Deleted \\Draft \\Seen)\015\012* OK [PERMANENTFLAGS ("); for (i = c = 0; i < NUSERFLAGS; i++) if ((stream->perm_user_flags & (1 << i)) && stream->user_flags[i]) put_flag (&c,stream->user_flags[i]); if (stream->kwd_create) put_flag (&c,"\\*"); if (stream->perm_answered) put_flag (&c,"\\Answered"); if (stream->perm_flagged) put_flag (&c,"\\Flagged"); if (stream->perm_deleted) put_flag (&c,"\\Deleted"); if (stream->perm_draft) put_flag (&c,"\\Draft"); if (stream->perm_seen) put_flag (&c,"\\Seen"); PSOUT (")] Permanent flags\015\012"); } /* Set timeout * Accepts: desired interval */ void settimeout (unsigned int i) { /* limit if not logged in */ if (i) alarm ((state == LOGIN) ? LOGINTIMEOUT : i); else alarm (0); } /* Clock interrupt * Returns only if critical code in progress */ void clkint (void) { settimeout (SIGNALTIMER); /* disable most interrupts */ server_init (NIL,NIL,NIL,dieint,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN); logout = "Autologout"; goodbye = "Autologout (idle for too long)"; if (critical) state = LOGOUT; /* must defer if in critical code */ else longjmp (jmpenv,1); /* die now */ } /* Clock interrupt after signal * Never returns */ void dieint (void) { _exit(1); /* slay the beast. Now. */ } /* Kiss Of Death interrupt * Returns only if critical code in progress */ void kodint (void) { settimeout (SIGNALTIMER); /* disable most interrupts */ server_init (NIL,NIL,NIL,dieint,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN); logout = goodbye = "Killed (lost mailbox lock)"; if (critical) state = LOGOUT; /* must defer if in critical code */ else longjmp (jmpenv,1); /* die now */ } /* Hangup interrupt * Returns only if critical code in progress */ void hupint (void) { settimeout (SIGNALTIMER); /* disable most interrupts */ server_init (NIL,NIL,NIL,dieint,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN); logout = "Hangup"; goodbye = NIL; /* other end is already gone */ if (critical) state = LOGOUT; /* must defer if in critical code */ else longjmp (jmpenv,1); /* die now */ } /* Termination interrupt * Returns only if critical code in progress */ void trmint (void) { settimeout (SIGNALTIMER); /* disable most interrupts */ server_init (NIL,NIL,NIL,dieint,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN); logout = goodbye = "Killed (terminated)"; /* Make no attempt at graceful closure since a shutdown may be in * progress, and we won't have any time to do mail_close() actions */ stream = NIL; if (critical) state = LOGOUT; /* must defer if in critical code */ else longjmp (jmpenv,1); /* die now */ } /* The routines on this and the next page eschew the use of non-syscall libc * routines (especially stdio) for a reason. Also, these hideous #if * condtionals need to be replaced. */ #ifndef unix #define unix 0 #endif /* Status request interrupt * Always returns */ void staint (void) { #if unix int fd; char *s,buf[8*MAILTMPLEN]; unsigned long pid = getpid (); /* build file name */ s = nout (sout (buf,"/tmp/imapd-status."),pid,10); if (user) s = sout (sout (s,"."),user); *s = '\0'; /* tie off file name */ if ((fd = open (buf,O_WRONLY | O_CREAT | O_TRUNC,0666)) >= 0) { fchmod (fd,0666); s = nout (sout (buf,"PID="),pid,10); switch (state) { case LOGIN: s = sout (s,", not logged in"); break; case SELECT: s = sout (s,", logged in"); break; case OPEN: s = sout (s,", mailbox open"); break; case LOGOUT: s = sout (s,", logging out"); break; } if (user) { s = sout (sout (s,"\nuser="),user); } if (blackberry) { /* Don't report client host until Blackberryness known. This is because tcp_clienthost() may not have been called yet and so the string might not be cached. As this is a signal handler, we want to minimize any system calls. */ s = sout (sout (s,"\nhost="),tcp_clienthost ()); if (blackberry > 0) s = sout (s, " (blackberry)"); } if (stream && stream->mailbox) { s = sout (sout (sout (s,"\nmailbox="),stream->mailbox), stream->rdonly ? " (read-only)" : " (read-write)"); } *s++ = '\n'; if (status) { s = sout (s,status); if (cmd) s = sout (sout (s,", last command="),cmd); } else if (cmd) s = sout (sout (s,cmd)," in progress"); else s = sout (s,"UNKNOWN STATE"); *s++ = '\n'; write (fd,buf,s-buf); close (fd); } #endif } /* Write string * Accepts: destination string pointer * string * Returns: updated string pointer */ char *sout (char *s,char *t) { while (*t) *s++ = *t++; return s; } /* Write number * Accepts: destination string pointer * number * base * Returns: updated string pointer */ char *nout (char *s,unsigned long n,unsigned long base) { char stack[256]; char *t = stack; /* push PID digits on stack */ do *t++ = (char) (n % base) + '0'; while (n /= base); /* pop digits from stack */ while (t > stack) *s++ = *--t; return s; } /* Slurp a command line * Accepts: buffer pointer * buffer size * input timeout */ void slurp (char *s,int n,unsigned long timeout) { memset (s,'\0',n); /* zap buffer */ if (state != LOGOUT) { /* get a command under timeout */ settimeout (timeout); clearerr (stdin); /* clear stdin errors */ status = "reading line"; if (!PSIN (s,n-1)) ioerror (stdin,status); settimeout (0); /* make sure timeout disabled */ status = NIL; } } /* Read a literal * Accepts: destination buffer (must be size+1 for trailing NUL) * size of buffer (must be less than 4294967295) */ void inliteral (char *s,unsigned long n) { unsigned long i; if (litplus.ok) { /* no more LITERAL+ to worry about */ litplus.ok = NIL; litplus.size = 0; } else { /* otherwise tell client ready for argument */ PSOUT ("+ Ready for argument\015\012"); PFLUSH (); /* dump output buffer */ } clearerr (stdin); /* clear stdin errors */ memset (s,'\0',n+1); /* zap buffer */ status = "reading literal"; while (n) { /* get data under timeout */ if (state == LOGOUT) n = 0; else { settimeout (INPUTTIMEOUT); i = min (n,8192); /* must read at least 8K within timeout */ if (PSINR (s,i)) { s += i; n -= i; } else { ioerror (stdin,status); n = 0; /* in case it continues */ } settimeout (0); /* stop timeout */ } } } /* Flush until newline seen * Returns: NIL and sets response, always */ unsigned char *flush (void) { int c; if (state != LOGOUT) { settimeout (INPUTTIMEOUT); clearerr (stdin); /* clear stdin errors */ status = "flushing line"; while ((c = PBIN ()) != '\012') if (c == EOF) ioerror (stdin,status); settimeout (0); /* make sure timeout disabled */ } response = "%.80s BAD Command line too long\015\012"; status = NIL; return NIL; } /* Report command stream error and die * Accepts: stdin or stdout (whichever got the error) * reason (what caller was doing) */ void ioerror (FILE *f,char *reason) { static char msg[MAILTMPLEN]; char *s,*t; if (logout) { /* say nothing if already dying */ settimeout (SIGNALTIMER); /* disable most interrupts */ server_init (NIL,NIL,NIL,dieint,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN); /* write error string */ for (s = ferror (f) ? strerror (errno) : "Unexpected client disconnect", t = logout = msg; *s; *t++ = *s++); for (s = ", while "; *s; *t++ = *s++); for (s = reason; *s; *t++ = *s++); if (critical) { /* must defer if in critical code */ state = LOGOUT; /* die as soon as we can */ } else longjmp (jmpenv,1); /* die now */ } } /* Parse an IMAP astring * Accepts: pointer to argument text pointer * pointer to returned size * pointer to returned delimiter * Returns: argument */ unsigned char *parse_astring (unsigned char **arg,unsigned long *size, unsigned char *del) { unsigned long i; unsigned char c,*s,*t,*v; if (!*arg) return NIL; /* better be an argument */ switch (**arg) { /* see what the argument is */ default: /* atom */ for (s = t = *arg, i = 0; (*t > ' ') && (*t < 0x7f) && (*t != '(') && (*t != ')') && (*t != '{') && (*t != '%') && (*t != '*') && (*t != '"') && (*t != '\\'); ++t,++i); if ((*size = i) != 0L) break; /* got atom if non-empty */ case ')': case '%': case '*': case '\\': case '\0': case ' ': return NIL; /* empty atom is a bogon */ case '"': /* hunt for trailing quote */ for (s = t = v = *arg + 1; (c = *t++) != '"'; *v++ = c) { /* quote next character */ if (c == '\\') switch (c = *t++) { case '"': case '\\': break; default: return NIL; /* invalid quote-next */ } /* else must be a CHAR */ if (!c || (c & 0x80)) return NIL; } *v = '\0'; /* tie off string */ *size = v - s; /* return size */ break; case '{': /* literal string */ s = *arg + 1; /* get size */ if (!isdigit (*s)) return NIL; if ((*size = i = strtoul (s,(char **) &t,10)) > MAXCLIENTLIT) { mm_notify (NIL,"Absurdly long client literal",ERROR); syslog (LOG_INFO,"Overlong (%lu) client literal user=%.80s host=%.80s", i,user ? (char *) user : "???",tcp_clienthost ()); return NIL; } switch (*t) { /* validate end of literal */ case '+': /* non-blocking literal */ if (*++t != '}') return NIL; case '}': if (!t[1]) break; /* OK if end of line */ default: return NIL; /* bad literal */ } if (litsp >= LITSTKLEN) { /* make sure don't overflow stack */ mm_notify (NIL,"Too many literals in command",ERROR); return NIL; } /* get a literal buffer */ inliteral (s = litstk[litsp++] = (char *) fs_get (i+1),i); /* get new command tail */ slurp (*arg = t,CMDLEN - (t - cmdbuf),INPUTTIMEOUT); /* if too long, flush and set response */ if (!strchr (t,'\012')) return flush (); /* reset strtok mechanism, tie off if done */ if (!strtok_r (t,"\015\012",&sstate)) *t = '\0'; /* possible LITERAL+? */ if (((i = strlen (t)) > 3) && (t[i - 1] == '}') && (t[i - 2] == '+') && isdigit (t[i - 3])) { /* back over possible count */ for (i -= 4; i && isdigit (t[i]); i--); if (t[i] == '{') { /* found a literal? */ litplus.ok = T; /* yes, note LITERAL+ in effect, set size */ litplus.size = strtoul (t + i + 1,NIL,10); } } break; } if ((*del = *t) != '\0') { /* have a delimiter? */ *t++ = '\0'; /* yes, stomp on it */ *arg = t; /* update argument pointer */ } else *arg = NIL; /* no more arguments */ return s; } /* Parse tag * Accepts: command tag * Returns: tag if valid, NIL otherwise */ unsigned char *parse_tag (unsigned char *cmd,unsigned char **ret) { unsigned char *s; *ret = cmd; /* make sure this stays defined */ if (!*cmd) return NIL; /* empty command line */ /* find end of tag */ for (s = cmd; *s && (*s > ' ') && (*s < 0x7f) && (*s != '(') && (*s != ')') && (*s != '{') && (*s != '%') && (*s != '*') && (*s != '"') && (*s != '\\'); ++s); if (*s != ' ') return NIL; /* tag must be delimited by space */ *s++ = '\0'; /* tie off tag */ *ret = s; /* set pointer to remainder of command */ return cmd; /* return tag */ } /* Snarf a command argument (simple jacket into parse_astring()) * Accepts: pointer to argument text pointer * Returns: argument */ unsigned char *snarf (unsigned char **arg) { unsigned long i; unsigned char c; unsigned char *s = parse_astring (arg,&i,&c); return ((c == ' ') || !c) ? s : NIL; } /* Snarf a BASE64 argument for SASL-IR * Accepts: pointer to argument text pointer * Returns: argument */ unsigned char *snarf_base64 (unsigned char **arg) { unsigned char *ret = *arg; unsigned char *s = ret + 1; static char base64mask[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; if (*(ret = *arg) == '='); /* easy case if zero-length argument */ /* must be at least one BASE64 char */ else if (!base64mask[*ret]) return NIL; else { /* quick and dirty */ while (base64mask[*s]) ++s; /* scan until end of BASE64 */ if (*s == '=') ++s; /* allow up to two padding chars */ if (*s == '=') ++s; } switch (*s) { /* anything following the argument? */ case ' ': /* another argument */ *s++ = '\0'; /* tie off previous argument */ *arg = s; /* and update argument pointer */ break; case '\0': /* end of command */ *arg = NIL; break; default: /* syntax error */ return NIL; } return ret; /* return BASE64 string */ } /* Snarf a list command argument (simple jacket into parse_astring()) * Accepts: pointer to argument text pointer * Returns: argument */ unsigned char *snarf_list (unsigned char **arg) { unsigned long i; unsigned char c,*s,*t; if (!*arg) return NIL; /* better be an argument */ switch (**arg) { default: /* atom and/or wildcard chars */ for (s = t = *arg, i = 0; (*t > ' ') && (*t != '(') && (*t != ')') && (*t != '{') && (*t != '"') && (*t != '\\'); ++t,++i); if ((c = *t) != '\0') { /* have a delimiter? */ *t++ = '\0'; /* stomp on it */ *arg = t; /* update argument pointer */ } else *arg = NIL; break; case ')': case '\\': case '\0': case ' ': return NIL; /* empty name is bogus */ case '"': /* quoted string? */ case '{': /* or literal? */ s = parse_astring (arg,&i,&c); break; } return ((c == ' ') || !c) ? s : NIL; } /* Get a list of header lines * Accepts: pointer to string pointer * pointer to list flag * Returns: string list */ STRINGLIST *parse_stringlist (unsigned char **s,int *list) { char *t; unsigned long i; char c = ' '; STRINGLIST *ret = NIL,*cur = NIL; if (*s && **s == '(') { /* proper list? */ ++*s; /* for each item in list */ while ((c == ' ') && (t = parse_astring (s,&i,&c))) { /* get new block */ if (cur) cur = cur->next = mail_newstringlist (); else cur = ret = mail_newstringlist (); /* note text */ cur->text.data = (unsigned char *) fs_get (i + 1); memcpy (cur->text.data,t,i); cur->text.size = i; /* and size */ } /* must be end of list */ if (c != ')') mail_free_stringlist (&ret); } if ((t = *s) != '\0') { /* need to reload strtok state? */ /* end of a list? */ if (*list && (*t == ')') && !t[1]) *list = NIL; else sstate = t; /* otherwise reset strtok state to s */ } return ret; } /* Get value of UID * for criteria parsing * Accepts: stream * Returns: maximum UID */ unsigned long uidmax (MAILSTREAM *stream) { return stream->nmsgs ? mail_uid (stream,stream->nmsgs) : 0xffffffff; } /* Parse search criteria * Accepts: search program to write criteria into * pointer to argument text pointer * maximum message number * maximum UID * logical nesting depth * Returns: T if success, NIL if error */ long parse_criteria (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg, unsigned long maxuid,unsigned long depth) { if (arg && *arg) { /* must be an argument */ /* parse criteria */ do if (!parse_criterion (pgm,arg,maxmsg,maxuid,depth)) return NIL; /* as long as a space delimiter */ while (**arg == ' ' && (*arg)++); /* failed if not end of criteria */ if (**arg && **arg != ')') return NIL; } return T; /* success */ } /* Parse a search criterion * Accepts: search program to write criterion into * pointer to argument text pointer * maximum message number * maximum UID * logical nesting depth * Returns: T if success, NIL if error */ long parse_criterion (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg, unsigned long maxuid,unsigned long depth) { unsigned long i; unsigned char c = NIL,*s,*t,*v,*tail,*del; SEARCHSET **set; SEARCHPGMLIST **not; SEARCHOR **or; SEARCHHEADER **hdr; long ret = NIL; /* better be an argument */ if ((depth > 500) || !(arg && *arg)); else if (**arg == '(') { /* list of criteria? */ (*arg)++; /* yes, parse the criteria */ if (parse_criteria (pgm,arg,maxmsg,maxuid,depth+1) && **arg == ')') { (*arg)++; /* skip closing paren */ ret = T; /* successful parse of list */ } } else { /* find end of criterion */ if (!(tail = strpbrk ((s = *arg)," )"))) tail = *arg + strlen (*arg); c = *(del = tail); /* remember the delimiter */ *del = '\0'; /* tie off criterion */ switch (*ucase (s)) { /* dispatch based on character */ case '*': /* sequence */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (*(set = &pgm->msgno)){/* already a sequence? */ /* silly, but not as silly as the client! */ for (not = &pgm->not; *not; not = &(*not)->next); *not = mail_newsearchpgmlist (); set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->msgno; } ret = crit_set (set,&s,maxmsg) && (tail == s); break; case 'A': /* possible ALL, ANSWERED */ if (!strcmp (s+1,"LL")) ret = T; else if (!strcmp (s+1,"NSWERED")) ret = pgm->answered = T; break; case 'B': /* possible BCC, BEFORE, BODY */ if (!strcmp (s+1,"CC") && c == ' ' && *++tail) ret = crit_string (&pgm->bcc,&tail); else if (!strcmp (s+1,"EFORE") && c == ' ' && *++tail) ret = crit_date (&pgm->before,&tail); else if (!strcmp (s+1,"ODY") && c == ' ' && *++tail) ret = crit_string (&pgm->body,&tail); break; case 'C': /* possible CC */ if (!strcmp (s+1,"C") && c == ' ' && *++tail) ret = crit_string (&pgm->cc,&tail); break; case 'D': /* possible DELETED */ if (!strcmp (s+1,"ELETED")) ret = pgm->deleted = T; if (!strcmp (s+1,"RAFT")) ret = pgm->draft = T; break; case 'F': /* possible FLAGGED, FROM */ if (!strcmp (s+1,"LAGGED")) ret = pgm->flagged = T; else if (!strcmp (s+1,"ROM") && c == ' ' && *++tail) ret = crit_string (&pgm->from,&tail); break; case 'H': /* possible HEADER */ if (!strcmp (s+1,"EADER") && c == ' ' && *(v = tail + 1) && (s = parse_astring (&v,&i,&c)) && i && c == ' ' && (t = parse_astring (&v,&i,&c))) { for (hdr = &pgm->header; *hdr; hdr = &(*hdr)->next); *hdr = mail_newsearchheader (s,t); /* update tail, restore delimiter */ *(tail = v ? v - 1 : t + i) = c; ret = T; /* success */ } break; case 'K': /* possible KEYWORD */ if (!strcmp (s+1,"EYWORD") && c == ' ' && *++tail) ret = crit_string (&pgm->keyword,&tail); break; case 'L': if (!strcmp (s+1,"ARGER") && c == ' ' && *++tail) ret = crit_number (&pgm->larger,&tail); break; case 'N': /* possible NEW, NOT */ if (!strcmp (s+1,"EW")) ret = pgm->recent = pgm->unseen = T; else if (!strcmp (s+1,"OT") && c == ' ' && *++tail) { for (not = &pgm->not; *not; not = &(*not)->next); *not = mail_newsearchpgmlist (); ret = parse_criterion ((*not)->pgm,&tail,maxmsg,maxuid,depth+1); } break; case 'O': /* possible OLD, ON */ if (!strcmp (s+1,"LD")) ret = pgm->old = T; else if (!strcmp (s+1,"N") && c == ' ' && *++tail) ret = crit_date (&pgm->on,&tail); else if (!strcmp (s+1,"R") && c == ' ') { for (or = &pgm->or; *or; or = &(*or)->next); *or = mail_newsearchor (); ret = *++tail && parse_criterion((*or)->first,&tail,maxmsg,maxuid, depth+1) && (*tail == ' ') && *++tail && parse_criterion ((*or)->second,&tail,maxmsg,maxuid,depth+1); } else if (!strcmp (s+1,"LDER") && c == ' ' && *++tail) ret = crit_number (&pgm->older,&tail); break; case 'R': /* possible RECENT */ if (!strcmp (s+1,"ECENT")) ret = pgm->recent = T; break; case 'S': /* possible SEEN, SINCE, SUBJECT */ if (!strcmp (s+1,"EEN")) ret = pgm->seen = T; else if (!strcmp (s+1,"ENTBEFORE") && c == ' ' && *++tail) ret = crit_date (&pgm->sentbefore,&tail); else if (!strcmp (s+1,"ENTON") && c == ' ' && *++tail) ret = crit_date (&pgm->senton,&tail); else if (!strcmp (s+1,"ENTSINCE") && c == ' ' && *++tail) ret = crit_date (&pgm->sentsince,&tail); else if (!strcmp (s+1,"INCE") && c == ' ' && *++tail) ret = crit_date (&pgm->since,&tail); else if (!strcmp (s+1,"MALLER") && c == ' ' && *++tail) ret = crit_number (&pgm->smaller,&tail); else if (!strcmp (s+1,"UBJECT") && c == ' ' && *++tail) ret = crit_string (&pgm->subject,&tail); break; case 'T': /* possible TEXT, TO */ if (!strcmp (s+1,"EXT") && c == ' ' && *++tail) ret = crit_string (&pgm->text,&tail); else if (!strcmp (s+1,"O") && c == ' ' && *++tail) ret = crit_string (&pgm->to,&tail); break; case 'U': /* possible UID, UN* */ if (!strcmp (s+1,"ID") && c== ' ' && *++tail) { if (*(set = &pgm->uid)){/* already a sequence? */ /* silly, but not as silly as the client! */ for (not = &pgm->not; *not; not = &(*not)->next); *not = mail_newsearchpgmlist (); set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->uid; } ret = crit_set (set,&tail,maxuid); } else if (!strcmp (s+1,"NANSWERED")) ret = pgm->unanswered = T; else if (!strcmp (s+1,"NDELETED")) ret = pgm->undeleted = T; else if (!strcmp (s+1,"NDRAFT")) ret = pgm->undraft = T; else if (!strcmp (s+1,"NFLAGGED")) ret = pgm->unflagged = T; else if (!strcmp (s+1,"NKEYWORD") && c == ' ' && *++tail) ret = crit_string (&pgm->unkeyword,&tail); else if (!strcmp (s+1,"NSEEN")) ret = pgm->unseen = T; break; case 'Y': /* possible YOUNGER */ if (!strcmp (s+1,"OUNGER") && c == ' ' && *++tail) ret = crit_number (&pgm->younger,&tail); break; default: /* oh dear */ break; } if (ret) { /* only bother if success */ *del = c; /* restore delimiter */ *arg = tail; /* update argument pointer */ } } return ret; /* return more to come */ } /* Parse a search date criterion * Accepts: date to write into * pointer to argument text pointer * Returns: T if success, NIL if error */ long crit_date (unsigned short *date,unsigned char **arg) { if (*date) return NIL; /* can't double this value */ /* handle quoted form */ if (**arg != '"') return crit_date_work (date,arg); (*arg)++; /* skip past opening quote */ if (!(crit_date_work (date,arg) && (**arg == '"'))) return NIL; (*arg)++; /* skip closing quote */ return T; } /* Worker routine to parse a search date criterion * Accepts: date to write into * pointer to argument text pointer * Returns: T if success, NIL if error */ long crit_date_work (unsigned short *date,unsigned char **arg) { int d,m,y; /* day */ if (isdigit (d = *(*arg)++) || ((d == ' ') && isdigit (**arg))) { if (d == ' ') d = 0; /* leading space */ else d -= '0'; /* first digit */ if (isdigit (**arg)) { /* if a second digit */ d *= 10; /* slide over first digit */ d += *(*arg)++ - '0'; /* second digit */ } if ((**arg == '-') && (y = *++(*arg))) { m = (y >= 'a' ? y - 'a' : y - 'A') * 1024; if ((y = *++(*arg))) { m += (y >= 'a' ? y - 'a' : y - 'A') * 32; if ((y = *++(*arg))) { m += (y >= 'a' ? y - 'a' : y - 'A'); switch (m) { /* determine the month */ case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break; case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break; case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break; case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break; case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break; case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break; case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break; case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break; case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break; case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10;break; case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11;break; case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12;break; default: return NIL; } if ((*++(*arg) == '-') && isdigit (*++(*arg))) { y = 0; /* init year */ do { y *= 10; /* add this number */ y += *(*arg)++ - '0'; } while (isdigit (**arg)); /* minimal validity check of date */ if (d < 1 || d > 31 || m < 1 || m > 12 || y < 0) return NIL; /* time began on UNIX in 1970 */ if (y < 100) y += (y >= (BASEYEAR - 1900)) ? 1900 : 2000; /* return value */ *date = mail_shortdate (y - BASEYEAR,m,d); return T; /* success */ } } } } } return NIL; /* else error */ } /* Parse a search set criterion * Accepts: set to write into * pointer to argument text pointer * maximum value permitted * Returns: T if success, NIL if error */ long crit_set (SEARCHSET **set,unsigned char **arg,unsigned long maxima) { unsigned long i = 0; if (*set) return NIL; /* can't double this value */ *set = mail_newsearchset (); /* instantiate a new search set */ if (**arg == '*') { /* maxnum? */ (*arg)++; /* skip past that number */ (*set)->first = maxima; } else if (crit_number (&i,arg) && i) (*set)->first = i; else return NIL; /* bogon */ switch (**arg) { /* decide based on delimiter */ case ':': /* sequence range */ i = 0; /* reset for crit_number() */ if (*++(*arg) == '*') { /* maxnum? */ (*arg)++; /* skip past that number */ (*set)->last = maxima; } else if (crit_number (&i,arg) && i) { if (i < (*set)->first) { /* backwards range */ (*set)->last = (*set)->first; (*set)->first = i; } else (*set)->last = i; /* set last number */ } else return NIL; /* bogon */ if (**arg != ',') break; /* drop into comma case if comma seen */ case ',': (*arg)++; /* skip past delimiter */ return crit_set (&(*set)->next,arg,maxima); default: break; } return T; /* return success */ } /* Parse a search number criterion * Accepts: number to write into * pointer to argument text pointer * Returns: T if success, NIL if error */ long crit_number (unsigned long *number,unsigned char **arg) { /* can't double this value */ if (*number || !isdigit (**arg)) return NIL; *number = 0; while (isdigit (**arg)) { /* found a digit? */ *number *= 10; /* add a decade */ *number += *(*arg)++ - '0'; /* add number */ } return T; } /* Parse a search string criterion * Accepts: date to write into * pointer to argument text pointer * Returns: T if success, NIL if error */ long crit_string (STRINGLIST **string,unsigned char **arg) { unsigned long i; char c; char *s = parse_astring (arg,&i,&c); if (!s) return NIL; /* find tail of list */ while (*string) string = &(*string)->next; *string = mail_newstringlist (); (*string)->text.data = (unsigned char *) fs_get (i + 1); memcpy ((*string)->text.data,s,i); (*string)->text.data[i] = '\0'; (*string)->text.size = i; /* if end of arguments, wrap it up here */ if (!*arg) *arg = (char *) (*string)->text.data + i; else (*--(*arg) = c); /* back up pointer, restore delimiter */ return T; } /* Fetch message data * Accepts: string of data items to be fetched (must be writeable) * UID fetch flag */ #define MAXFETCH 100 void fetch (char *t,unsigned long uid) { fetchfn_t f[MAXFETCH +2]; void *fa[MAXFETCH + 2]; int k; memset ((void *) f,NIL,sizeof (f)); memset ((void *) fa,NIL,sizeof (fa)); fetch_work (t,uid,f,fa); /* do the work */ /* clean up arguments */ for (k = 1; f[k]; k++) if (fa[k]) (*f[k]) (0,fa[k]); } /* Fetch message data worker routine * Accepts: string of data items to be fetched (must be writeable) * UID fetch flag * function dispatch vector * function argument vector */ void fetch_work (char *t,unsigned long uid,fetchfn_t f[],void *fa[]) { unsigned char *s,*v; unsigned long i; unsigned long k = 0; BODY *b; int list = NIL; int parse_envs = NIL; int parse_bodies = NIL; if (uid) { /* need to fetch UIDs? */ fa[k] = NIL; /* no argument */ f[k++] = fetch_uid; /* push a UID fetch on the stack */ } /* process macros */ if (!strcmp (ucase (t),"ALL")) strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)"); else if (!strcmp (t,"FULL")) strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)"); else if (!strcmp (t,"FAST")) strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE)"); if ((list = (*t == '(')) != 0) t++; /* skip open paren */ /* parse attribute list */ if ((s = strtok_r (t," ",&sstate)) != NULL) do { if (list && (i = strlen (s)) && (s[i-1] == ')')) { list = NIL; /* done with list */ s[i-1] = '\0'; /* tie off last item */ } fa[k] = NIL; /* default to no argument */ if (!strcmp (s,"UID")) { /* no-op if implicit */ if (!uid) f[k++] = fetch_uid; } else if (!strcmp (s,"FLAGS")) f[k++] = fetch_flags; else if (!strcmp (s,"INTERNALDATE")) f[k++] = fetch_internaldate; else if (!strcmp (s,"RFC822.SIZE")) f[k++] = fetch_rfc822_size; else if (!strcmp (s,"ENVELOPE")) { parse_envs = T; /* we will need to parse envelopes */ f[k++] = fetch_envelope; } else if (!strcmp (s,"BODY")) { parse_envs = parse_bodies = T; f[k++] = fetch_body; } else if (!strcmp (s,"BODYSTRUCTURE")) { parse_envs = parse_bodies = T; f[k++] = fetch_bodystructure; } else if (!strcmp (s,"RFC822")) { fa[k] = s[6] ? (void *) FT_PEEK : NIL; f[k++] = fetch_rfc822; } else if (!strcmp (s,"RFC822.HEADER")) f[k++] = fetch_rfc822_header; else if (!strcmp (s,"RFC822.TEXT")) { fa[k] = s[11] ? (void *) FT_PEEK : NIL; f[k++] = fetch_rfc822_text; } else if (!strncmp (s,"BODY[",5) || !strncmp (s,"BODY.PEEK[",10) || !strncmp (s,"BINARY[",7) || !strncmp (s,"BINARY.PEEK[",12) || !strncmp (s,"BINARY.SIZE[",12)) { TEXTARGS *ta = (TEXTARGS *) memset (fs_get (sizeof (TEXTARGS)),0,sizeof (TEXTARGS)); if (s[1] == 'I') { /* body or binary? */ ta->binary = FTB_BINARY;/* binary */ f[k] = fetch_body_part_binary; if (s[6] == '.') { /* wanted peek or size? */ if (s[7] == 'P') ta->flags = FT_PEEK; else ta->binary |= FTB_SIZE; s += 12; /* skip to section specifier */ } else s += 7; /* skip to section specifier */ if (!isdigit (*s)) { /* make sure top-level digit */ fs_give ((void **) &ta); response = badbin; return; } } else { /* body */ f[k] = fetch_body_part_contents; if (s[4] == '.') { /* wanted peek? */ ta->flags = FT_PEEK; s += 10; /* skip to section specifier */ } else s += 5; /* skip to section specifier */ } if (*(v = s) != ']') { /* non-empty section specifier? */ if (isdigit (*v)) { /* have section specifier? */ /* need envelopes and bodies */ parse_envs = parse_bodies = T; while (isdigit (*v)) /* scan to end of section specifier */ if ((*++v == '.') && isdigit (v[1])) v++; /* any IMAP4rev1 stuff following? */ if ((*v == '.') && isalpha (v[1])) { if (ta->binary) { /* not if binary you don't */ fs_give ((void **) &ta); response = badbin; return; } *v++ = '\0'; /* yes, tie off section specifier */ if (!strncmp (v,"MIME",4)) { v += 4; /* found
.MIME */ f[k] = fetch_body_part_mime; } } else if (*v != ']') { /* better be the end if no IMAP4rev1 stuff */ fs_give ((void **) &ta);/* clean up */ response = "%.80s BAD Syntax error in section specifier\015\012"; return; } } if (*v != ']') { /* IMAP4rev1 stuff here? */ if (!strncmp (v,"HEADER",6)) { *v = '\0'; /* tie off in case top level */ v += 6; /* found [
.]HEADER */ f[k] = fetch_body_part_header; /* partial headers wanted? */ if (!strncmp (v,".FIELDS",7)) { v += 7; /* yes */ if (!strncmp (v,".NOT",4)) { v += 4; /* want to exclude named headers */ ta->flags |= FT_NOT; } if (*v || !(v = strtok_r (NIL,"\015\012",&sstate)) || !(ta->lines = parse_stringlist (&v,&list))) { fs_give ((void **) &ta);/* clean up */ response = "%.80s BAD Syntax error in header fields\015\012"; return; } ucase (v); /* make sure followup is ucase */ } } else if (!strncmp (v,"TEXT",4)) { *v = '\0'; /* tie off in case top level */ v += 4; /* found [
.]TEXT */ f[k] = fetch_body_part_text; } else { fs_give ((void **) &ta);/* clean up */ response = "%.80s BAD Unknown section text specifier\015\012"; return; } } } /* tie off section */ if (*v == ']') *v++ = '\0'; else { /* bogon */ if (ta->lines) mail_free_stringlist (&ta->lines); fs_give ((void **) &ta);/* clean up */ response = "%.80s BAD Section specifier not terminated\015\012"; return; } if ((*v == '<') && /* partial specifier? */ ((ta->binary & FTB_SIZE) || !(isdigit (v[1]) && ((ta->first = strtoul (v+1,(char **) &v,10)) || v) && (*v++ == '.') && (ta->last = strtoul (v,(char **) &v,10)) && (*v++ == '>')))) { if (ta->lines) mail_free_stringlist (&ta->lines); fs_give ((void **) &ta); response ="%.80s BAD Syntax error in partial text specifier\015\012"; return; } switch (*v) { /* what's there now? */ case ' ': /* more follows */ sstate = v + 1; /* reset strtok mechanism */ break; case '\0': /* none */ break; case ')': /* end of list */ if (list && !v[1]) { /* make sure of that */ list = NIL; /* reset strtok mechanism */ strtok_r (v," ",&sstate); break; /* all done */ } /* otherwise it's a bogon, drop in */ default: /* bogon */ if (ta->lines) mail_free_stringlist (&ta->lines); fs_give ((void **) &ta); response = "%.80s BAD Syntax error after section specifier\015\012"; return; } /* make copy of section specifier */ if (s && *s) ta->section = cpystr (s); fa[k++] = (void *) ta; /* set argument */ } else { /* unknown attribute */ response = badatt; return; } } while ((s = strtok_r (NIL," ",&sstate)) && (k < MAXFETCH) && list); else { response = misarg; /* missing attribute list */ return; } if (s) { /* too many attributes? */ response = "%.80s BAD Excessively complex FETCH attribute list\015\012"; return; } if (list) { /* too many attributes? */ response = "%.80s BAD Unterminated FETCH attribute list\015\012"; return; } f[k] = NIL; /* tie off attribute list */ /* c-client clobbers sequence, use spare */ for (i = 1; i <= nmsgs; i++) mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence; /* for each requested message */ for (i = 1; (i <= nmsgs) && (response != loseunknowncte); i++) { /* kill if dying */ if (state == LOGOUT) longjmp (jmpenv,1); if (mail_elt (stream,i)->spare) { /* parse envelope, set body, do warnings */ if (parse_envs) mail_fetchstructure (stream,i,parse_bodies ? &b : NIL); quell_events = T; /* can't do any events now */ PSOUT ("* "); /* leader */ pnum (i); PSOUT (" FETCH ("); (*f[0]) (i,fa[0]); /* do first attribute */ /* for each subsequent attribute */ for (k = 1; f[k] && (response != loseunknowncte); k++) { PBOUT (' '); /* delimit with space */ (*f[k]) (i,fa[k]); /* do that attribute */ } PSOUT (")\015\012"); /* trailer */ quell_events = NIL; /* events alright now */ } } } /* Fetch message body structure (extensible) * Accepts: message number * extra argument */ void fetch_bodystructure (unsigned long i,void *args) { BODY *body; mail_fetchstructure (stream,i,&body); PSOUT ("BODYSTRUCTURE "); pbodystructure (body); /* output body */ } /* Fetch message body structure (non-extensible) * Accepts: message number * extra argument */ void fetch_body (unsigned long i,void *args) { BODY *body; mail_fetchstructure (stream,i,&body); PSOUT ("BODY "); /* output attribute */ pbody (body); /* output body */ } /* Fetch body part MIME header * Accepts: message number * extra argument */ void fetch_body_part_mime (unsigned long i,void *args) { TEXTARGS *ta = (TEXTARGS *) args; if (i) { /* do work? */ SIZEDTEXT st; unsigned long uid = mail_uid (stream,i); char *tmp = (char *) fs_get (100 + strlen (ta->section)); sprintf (tmp,"BODY[%s.MIME]",ta->section); /* try to use remembered text */ if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst; else { /* get data */ st.data = (unsigned char *) mail_fetch_mime (stream,i,ta->section,&st.size,ta->flags); if (ta->first || ta->last) remember (uid,tmp,&st); } pbodypartstring (i,tmp,&st,NIL,ta); fs_give ((void **) &tmp); } else { /* clean up the arguments */ fs_give ((void **) &ta->section); fs_give ((void **) &args); } } /* Fetch body part contents * Accepts: message number * extra argument */ void fetch_body_part_contents (unsigned long i,void *args) { TEXTARGS *ta = (TEXTARGS *) args; if (i) { /* do work? */ SIZEDTEXT st; char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0)); unsigned long uid = mail_uid (stream,i); sprintf (tmp,"BODY[%s]",ta->section ? ta->section : ""); /* try to use remembered text */ if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst; /* get data */ else if ((st.data = (unsigned char *) mail_fetch_body (stream,i,ta->section,&st.size, ta->flags | FT_RETURNSTRINGSTRUCT)) && (ta->first || ta->last)) remember (uid,tmp,&st); pbodypartstring (i,tmp,&st,&stream->private.string,ta); fs_give ((void **) &tmp); } else { /* clean up the arguments */ if (ta->section) fs_give ((void **) &ta->section); fs_give ((void **) &args); } } /* Fetch body part binary * Accepts: message number * extra argument * Someday fix this to use stringstruct instead of memory */ void fetch_body_part_binary (unsigned long i,void *args) { TEXTARGS *ta = (TEXTARGS *) args; if (i) { /* do work? */ SIZEDTEXT st,cst; BODY *body = mail_body (stream,i,ta->section); char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0)); unsigned long uid = mail_uid (stream,i); /* try to use remembered text */ if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst; else { /* get data */ st.data = (unsigned char *) mail_fetch_body (stream,i,ta->section,&st.size,ta->flags); if (ta->first || ta->last) remember (uid,tmp,&st); } /* what encoding was used? */ if (body) switch (body->encoding) { case ENCBASE64: if ((cst.data = rfc822_base64 (st.data,st.size,&cst.size)) != NULL) break; fetch_uid (i,NIL); /* wrote a space, so must do something */ if (lsterr) fs_give ((void **) &lsterr); lsterr = cpystr ("Undecodable BASE64 contents"); response = loseunknowncte; fs_give ((void **) &tmp); return; case ENCQUOTEDPRINTABLE: if ((cst.data = rfc822_qprint (st.data,st.size,&cst.size)) != NULL) break; fetch_uid (i,NIL); /* wrote a space, so must do something */ if (lsterr) fs_give ((void **) &lsterr); lsterr = cpystr ("Undecodable QUOTED-PRINTABLE contents"); response = loseunknowncte; fs_give ((void **) &tmp); return; case ENC7BIT: /* no need to convert any of these */ case ENC8BIT: case ENCBINARY: cst.data = NIL; /* no converted data to free */ break; default: /* unknown encoding, oops */ fetch_uid (i,NIL); /* wrote a space, so must do something */ if (lsterr) fs_give ((void **) &lsterr); lsterr = cpystr ("Unknown Content-Transfer-Encoding"); response = loseunknowncte; fs_give ((void **) &tmp); return; } else { if (lsterr) fs_give ((void **) &lsterr); lsterr = cpystr ("Invalid body part"); response = loseunknowncte; fs_give ((void **) &tmp); return; } /* use decoded version if exists */ if (cst.data) memcpy ((void *) &st,(void *) &cst,sizeof (SIZEDTEXT)); if (ta->binary & FTB_SIZE) {/* just want size? */ sprintf (tmp,"BINARY.SIZE[%s] %lu",ta->section ? ta->section : "", st.size); PSOUT (tmp); } else { /* no, blat binary data */ int f = mail_elt (stream,i)->seen; if (st.data) { /* only if have useful data */ /* partial specifier */ if (ta->first || ta->last) sprintf (tmp,"BINARY[%s]<%lu> ", ta->section ? ta->section : "",ta->first); else sprintf (tmp,"BINARY[%s] ",ta->section ? ta->section : ""); /* in case first byte beyond end of text */ if (st.size <= ta->first) st.size = ta->first = 0; else { /* offset and truncate */ st.data += ta->first; /* move to desired position */ st.size -= ta->first; /* reduced size */ if (ta->last && (st.size > ta->last)) st.size = ta->last; } if (st.size) sprintf (tmp + strlen (tmp),"{%lu}\015\012",st.size); else strcat (tmp,"\"\""); PSOUT (tmp); /* write binary output */ if (st.size && (PSOUTR (&st) == EOF)) ioerror(stdout,"writing binary"); } else { sprintf (tmp,"BINARY[%s] NIL",ta->section ? ta->section : ""); PSOUT (tmp); } changed_flags (i,f); /* write changed flags */ } /* free converted data */ if (cst.data) fs_give ((void **) &cst.data); fs_give ((void **) &tmp); /* and temporary string */ } else { /* clean up the arguments */ if (ta->section) fs_give ((void **) &ta->section); fs_give ((void **) &args); } } /* Fetch MESSAGE/RFC822 body part header * Accepts: message number * extra argument */ void fetch_body_part_header (unsigned long i,void *args) { TEXTARGS *ta = (TEXTARGS *) args; unsigned long len = 100 + (ta->section ? strlen (ta->section) : 0); STRINGLIST *s; for (s = ta->lines; s; s = s->next) len += s->text.size + 1; if (i) { /* do work? */ SIZEDTEXT st; char *tmp = (char *) fs_get (len); PSOUT ("BODY["); /* output attribute */ if (ta->section && *ta->section) { PSOUT (ta->section); PBOUT ('.'); } PSOUT ("HEADER"); if (ta->lines) { PSOUT ((ta->flags & FT_NOT) ? ".FIELDS.NOT " : ".FIELDS "); pastringlist (ta->lines); } strcpy (tmp,"]"); /* close section specifier */ st.data = (unsigned char *) /* get data (no hope in using remember here) */ mail_fetch_header (stream,i,ta->section,ta->lines,&st.size,ta->flags); pbodypartstring (i,tmp,&st,NIL,ta); fs_give ((void **) &tmp); } else { /* clean up the arguments */ if (ta->lines) mail_free_stringlist (&ta->lines); if (ta->section) fs_give ((void **) &ta->section); fs_give ((void **) &args); } } /* Fetch MESSAGE/RFC822 body part text * Accepts: message number * extra argument */ void fetch_body_part_text (unsigned long i,void *args) { TEXTARGS *ta = (TEXTARGS *) args; if (i) { /* do work? */ SIZEDTEXT st; char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0)); unsigned long uid = mail_uid (stream,i); /* output attribute */ if (ta->section && *ta->section) sprintf (tmp,"BODY[%s.TEXT]",ta->section); else strcpy (tmp,"BODY[TEXT]"); /* try to use remembered text */ if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst; /* get data */ else if ((st.data = (unsigned char *) mail_fetch_text (stream,i,ta->section,&st.size, ta->flags | FT_RETURNSTRINGSTRUCT)) && (ta->first || ta->last)) remember (uid,tmp,&st); pbodypartstring (i,tmp,&st,&stream->private.string,ta); fs_give ((void **) &tmp); } else { /* clean up the arguments */ if (ta->section) fs_give ((void **) &ta->section); fs_give ((void **) &args); } } /* Remember body part text for subsequent partial fetching * Accepts: message UID * body part id * text * string */ void remember (unsigned long uid,char *id,SIZEDTEXT *st) { lastuid = uid; /* remember UID */ if (lastid) fs_give ((void **) &lastid); lastid = cpystr (id); /* remember body part id */ if (lastst.data) fs_give ((void **) &lastst.data); /* remember text */ lastst.data = (unsigned char *) memcpy (fs_get (st->size + 1),st->data,st->size); lastst.size = st->size; } /* Fetch envelope * Accepts: message number * extra argument */ void fetch_envelope (unsigned long i,void *args) { ENVELOPE *env = mail_fetchenvelope (stream,i); PSOUT ("ENVELOPE "); /* output attribute */ penv (env); /* output envelope */ } /* Fetch flags * Accepts: message number * extra argument */ void fetch_flags (unsigned long i,void *args) { unsigned long u; char *t,tmp[MAILTMPLEN]; int c = NIL; MESSAGECACHE *elt = mail_elt (stream,i); if (!elt->valid) { /* have valid flags yet? */ sprintf (tmp,"%lu",i); mail_fetch_flags (stream,tmp,NIL); } PSOUT ("FLAGS ("); /* output attribute */ /* output system flags */ if (elt->recent) put_flag (&c,"\\Recent"); if (elt->seen) put_flag (&c,"\\Seen"); if (elt->deleted) put_flag (&c,"\\Deleted"); if (elt->flagged) put_flag (&c,"\\Flagged"); if (elt->answered) put_flag (&c,"\\Answered"); if (elt->draft) put_flag (&c,"\\Draft"); if ((u = elt->user_flags) != 0L) do /* any user flags? */ if ((t = stream->user_flags[find_rightmost_bit (&u)]) != NULL) put_flag (&c,t); while (u); /* until no more user flags */ PBOUT (')'); /* end of flags */ elt->spare2 = NIL; /* we've sent the update */ } /* Output a flag * Accepts: pointer to current delimiter character * flag to output * Changes delimiter character to space */ void put_flag (int *c,char *s) { if (*c) PBOUT (*c); /* put delimiter */ PSOUT (s); /* dump flag */ *c = ' '; /* change delimiter if necessary */ } /* Output flags if was unseen * Accepts: message number * prior value of Seen flag */ void changed_flags (unsigned long i,int f) { /* was unseen, now seen? */ if (!f && mail_elt (stream,i)->seen) { PBOUT (' '); /* yes, delimit with space */ fetch_flags (i,NIL); /* output flags */ } } /* Fetch message internal date * Accepts: message number * extra argument */ void fetch_internaldate (unsigned long i,void *args) { char tmp[MAILTMPLEN]; MESSAGECACHE *elt = mail_elt (stream,i); if (!elt->day) { /* have internal date yet? */ sprintf (tmp,"%lu",i); mail_fetch_fast (stream,tmp,NIL); } PSOUT ("INTERNALDATE \""); PSOUT (mail_date (tmp,elt)); PBOUT ('"'); } /* Fetch unique identifier * Accepts: message number * extra argument */ void fetch_uid (unsigned long i,void *args) { PSOUT ("UID "); pnum (mail_uid (stream,i)); } /* Fetch complete RFC-822 format message * Accepts: message number * extra argument */ void fetch_rfc822 (unsigned long i,void *args) { if (i) { /* do work? */ int f = mail_elt (stream,i)->seen; #if 0 SIZEDTEXT st; st.data = (unsigned char *) mail_fetch_message (stream,i,&st.size,(long) args); pbodypartstring (i,"RFC822",&st,NIL,NIL); #else /* Yes, this version is bletcherous, but mail_fetch_message() requires too much memory */ SIZEDTEXT txt,hdr; char *s = mail_fetch_header (stream,i,NIL,NIL,&hdr.size,FT_PEEK); hdr.data = (unsigned char *) memcpy (fs_get (hdr.size),s,hdr.size); txt.data = (unsigned char *) mail_fetch_text (stream,i,NIL,&txt.size, ((long) args) | FT_RETURNSTRINGSTRUCT); PSOUT ("RFC822 {"); pnum (hdr.size + txt.size); PSOUT ("}\015\012"); ptext (&hdr,NIL); ptext (&txt,&stream->private.string); fs_give ((void **) &hdr.data); #endif changed_flags (i,f); /* output changed flags */ } } /* Fetch RFC-822 header * Accepts: message number * extra argument */ void fetch_rfc822_header (unsigned long i,void *args) { SIZEDTEXT st; st.data = (unsigned char *) mail_fetch_header (stream,i,NIL,NIL,&st.size,FT_PEEK); pbodypartstring (i,"RFC822.HEADER",&st,NIL,NIL); } /* Fetch RFC-822 message length * Accepts: message number * extra argument */ void fetch_rfc822_size (unsigned long i,void *args) { char tmp[MAILTMPLEN]; MESSAGECACHE *elt = mail_elt (stream,i); if (!elt->rfc822_size) { /* have message size yet? */ sprintf (tmp,"%lu",i); mail_fetch_fast (stream,tmp,NIL); } PSOUT ("RFC822.SIZE "); pnum (elt->rfc822_size); } /* Fetch RFC-822 text only * Accepts: message number * extra argument */ void fetch_rfc822_text (unsigned long i,void *args) { if (i) { /* do work? */ int f = mail_elt (stream,i)->seen; SIZEDTEXT st; st.data = (unsigned char *) mail_fetch_text (stream,i,NIL,&st.size, ((long) args) | FT_RETURNSTRINGSTRUCT); pbodypartstring (i,"RFC822.TEXT",&st,&stream->private.string,NIL); } } /* Print envelope * Accepts: body */ void penv (ENVELOPE *env) { PBOUT ('('); /* delimiter */ if (env) { /* only if there is an envelope */ pnstring (env->date); /* output envelope fields */ PBOUT (' '); pnstring (env->subject); PBOUT (' '); paddr (env->from); PBOUT (' '); paddr (env->sender); PBOUT (' '); paddr (env->reply_to); PBOUT (' '); paddr (env->to); PBOUT (' '); paddr (env->cc); PBOUT (' '); paddr (env->bcc); PBOUT (' '); pnstring (env->in_reply_to); PBOUT (' '); pnstring (env->message_id); } /* no envelope */ else PSOUT ("NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL"); PBOUT (')'); /* end of envelope */ } /* Print body structure (extensible) * Accepts: body */ void pbodystructure (BODY *body) { PBOUT ('('); /* delimiter */ if (body) { /* only if there is a body */ PART *part; /* multipart type? */ if (body->type == TYPEMULTIPART) { /* print each part */ if ((part = body->nested.part) != NULL) for (; part; part = part->next) pbodystructure (&(part->body)); else pbodystructure (NIL); PBOUT (' '); /* space delimiter */ pstring (body->subtype); /* subtype */ PBOUT (' '); pparam (body->parameter); /* multipart body extension data */ PBOUT (' '); if (body->disposition.type) { PBOUT ('('); pstring (body->disposition.type); PBOUT (' '); pparam (body->disposition.parameter); PBOUT (')'); } else PSOUT ("NIL"); PBOUT (' '); pnstringorlist (body->language); PBOUT (' '); pnstring (body->location); } else { /* non-multipart body type */ pstring ((char *) body_types[body->type]); PBOUT (' '); pstring (body->subtype); PBOUT (' '); pparam (body->parameter); PBOUT (' '); pnstring (body->id); PBOUT (' '); pnstring (body->description); PBOUT (' '); pstring ((char *) body_encodings[body->encoding]); PBOUT (' '); pnum (body->size.bytes); switch (body->type) { /* extra stuff depends upon body type */ case TYPEMESSAGE: /* can't do this if not RFC822 */ if (strcmp (body->subtype,"RFC822")) break; PBOUT (' '); penv (body->nested.msg->env); PBOUT (' '); pbodystructure (body->nested.msg->body); case TYPETEXT: PBOUT (' '); pnum (body->size.lines); break; default: break; } PBOUT (' '); pnstring (body->md5); PBOUT (' '); if (body->disposition.type) { PBOUT ('('); pstring (body->disposition.type); PBOUT (' '); pparam (body->disposition.parameter); PBOUT (')'); } else PSOUT ("NIL"); PBOUT (' '); pnstringorlist (body->language); PBOUT (' '); pnstring (body->location); } } /* no body */ else PSOUT ("\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" 0 0 NIL NIL NIL NIL"); PBOUT (')'); /* end of body */ } /* Print body (non-extensible) * Accepts: body */ void pbody (BODY *body) { PBOUT ('('); /* delimiter */ if (body) { /* only if there is a body */ PART *part; /* multipart type? */ if (body->type == TYPEMULTIPART) { /* print each part */ if ((part = body->nested.part) != NULL) for (; part; part = part->next) pbody (&(part->body)); else pbody (NIL); PBOUT (' '); /* space delimiter */ pstring (body->subtype); /* and finally the subtype */ } else { /* non-multipart body type */ pstring ((char *) body_types[body->type]); PBOUT (' '); pstring (body->subtype); PBOUT (' '); pparam (body->parameter); PBOUT (' '); pnstring (body->id); PBOUT (' '); pnstring (body->description); PBOUT (' '); pstring ((char *) body_encodings[body->encoding]); PBOUT (' '); pnum (body->size.bytes); switch (body->type) { /* extra stuff depends upon body type */ case TYPEMESSAGE: /* can't do this if not RFC822 */ if (strcmp (body->subtype,"RFC822")) break; PBOUT (' '); penv (body->nested.msg ? body->nested.msg->env : NIL); PBOUT (' '); pbody (body->nested.msg ? body->nested.msg->body : NIL); case TYPETEXT: PBOUT (' '); pnum (body->size.lines); break; default: break; } } } /* no body */ else PSOUT ("\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" 0 0"); PBOUT (')'); /* end of body */ } /* Print parameter list * Accepts: paramter */ void pparam (PARAMETER *param) { if (param) { /* one specified? */ PBOUT ('('); do { pstring (param->attribute); PBOUT (' '); pstring (param->value); if ((param = param->next) != NULL) PBOUT (' '); } while (param); PBOUT (')'); /* end of parameters */ } else PSOUT ("NIL"); } /* Print address list * Accepts: address list */ void paddr (ADDRESS *a) { if (a) { /* have anything in address? */ PBOUT ('('); /* open the address list */ do { /* for each address */ PBOUT ('('); /* open the address */ pnstring (a->personal); /* personal name */ PBOUT (' '); pnstring (a->adl); /* at-domain-list */ PBOUT (' '); pnstring (a->mailbox); /* mailbox */ PBOUT (' '); pnstring (a->host); /* domain name of mailbox's host */ PBOUT (')'); /* terminate address */ } while ((a = a->next) != NULL); /* until end of address */ PBOUT (')'); /* close address list */ } else PSOUT ("NIL"); /* empty address */ } /* Print set * Accepts: set */ void pset (SEARCHSET **set) { SEARCHSET *cur = *set; while (cur) { /* while there's a set to do */ pnum (cur->first); /* output first value */ if (cur->last) { /* if range, output second value of range */ PBOUT (':'); pnum (cur->last); } if ((cur = cur->next) != NULL) PBOUT (','); } mail_free_searchset (set); /* flush set */ } /* Print number * Accepts: number */ void pnum (unsigned long i) { char tmp[MAILTMPLEN]; sprintf (tmp,"%lu",i); PSOUT (tmp); } /* Print string * Accepts: string */ void pstring (char *s) { SIZEDTEXT st; st.data = (unsigned char *) s;/* set up sized text */ st.size = strlen (s); psizedstring (&st,NIL); /* print string */ } /* Print nstring * Accepts: string or NIL */ void pnstring (char *s) { if (s) pstring (s); /* print string */ else PSOUT ("NIL"); } /* Print atom or string * Accepts: astring */ void pastring (char *s) { char *t; if (!*s) PSOUT ("\"\""); /* empty string */ else { /* see if atom */ for (t = s; (*t > ' ') && !(*t & 0x80) && (*t != '"') && (*t != '\\') && (*t != '(') && (*t != ')') && (*t != '{') && (*t != '%') && (*t != '*'); t++); if (*t) pstring (s); /* not an atom */ else PSOUT (s); /* else plop down as atomic */ } } /* Print sized text as quoted * Accepts: sized text */ void psizedquoted (SIZEDTEXT *s) { PBOUT ('"'); /* use quoted string */ ptext (s,NIL); PBOUT ('"'); } /* Print sized text as literal * Accepts: sized text */ void psizedliteral (SIZEDTEXT *s,STRING *st) { PBOUT ('{'); /* print literal size */ pnum (s->size); PSOUT ("}\015\012"); ptext (s,st); } /* Print sized text as literal or quoted string * Accepts: sized text * alternative stringstruct of text */ void psizedstring (SIZEDTEXT *s,STRING *st) { unsigned char c; unsigned long i; if (s->data) { /* if text, check if must use literal */ for (i = 0; ((i < s->size) && ((c = s->data[i]) & 0xe0) && !(c & 0x80) && (c != '"') && (c != '\\')); ++i); /* must use literal if not all QUOTED-CHAR */ if (i < s->size) psizedliteral (s,st); else psizedquoted (s); } else psizedliteral (s,st); } /* Print sized text as literal or quoted string * Accepts: sized text */ void psizedastring (SIZEDTEXT *s) { unsigned long i; unsigned int atomp = s->size ? T : NIL; for (i = 0; i < s->size; i++){/* check if must use literal */ if (!(s->data[i] & 0xe0) || (s->data[i] & 0x80) || (s->data[i] == '"') || (s->data[i] == '\\')) { psizedliteral (s,NIL); return; } else switch (s->data[i]) { /* else see if any atom-specials */ case '(': case ')': case '{': case ' ': case '%': case '*': /* list-wildcards */ case ']': /* resp-specials */ /* CTL and quoted-specials in literal check */ atomp = NIL; /* not an atom */ } } if (atomp) ptext (s,NIL); /* print as atom */ else psizedquoted (s); /* print as quoted string */ } /* Print string list * Accepts: string list */ void pastringlist (STRINGLIST *s) { PBOUT ('('); /* start list */ do { psizedastring (&s->text); /* output list member */ if (s->next) PBOUT (' '); } while ((s = s->next) != NULL); PBOUT (')'); /* terminate list */ } /* Print nstring or list of strings * Accepts: string / string list */ void pnstringorlist (STRINGLIST *s) { if (!s) PSOUT ("NIL"); /* no argument given */ else if (s->next) { /* output list as list of strings*/ PBOUT ('('); /* start list */ do { /* output list member */ psizedstring (&s->text,NIL); if (s->next) PBOUT (' '); } while ((s = s->next) != NULL); PBOUT (')'); /* terminate list */ } /* and single-element list as string */ else psizedstring (&s->text,NIL); } /* Print body part string * Accepts: message number * body part id (note: must have space at end to append stuff) * sized text of string * alternative stringstruct of string * text printing arguments */ void pbodypartstring (unsigned long msgno,char *id,SIZEDTEXT *st,STRING *bs, TEXTARGS *ta) { int f = mail_elt (stream,msgno)->seen; /* ignore stringstruct if non-initialized */ if (bs && !bs->curpos) bs = NIL; if (ta && st->size) { /* only if have useful data */ /* partial specifier */ if (ta->first || ta->last) sprintf (id + strlen (id),"<%lu>",ta->first); /* in case first byte beyond end of text */ if (st->size <= ta->first) st->size = ta->first = 0; else { if (st->data) { /* offset and truncate */ st->data += ta->first; /* move to desired position */ st->size -= ta->first; /* reduced size */ } else if (bs && (SIZE (bs) >= ta->first)) SETPOS (bs,ta->first + GETPOS (bs)); else st->size = 0; /* shouldn't happen */ if (ta->last && (st->size > ta->last)) st->size = ta->last; } } PSOUT (id); PBOUT (' '); psizedstring (st,bs); /* output string */ changed_flags (msgno,f); /* and changed flags */ } /* RFC 3501 technically forbids NULs in literals. Normally, the delivering * MTA would take care of MIME converting the message text so that it is * NUL-free. If it doesn't, then we have the choice of either violating * IMAP by sending NULs, corrupting the data, or going to lots of work to do * MIME conversion in the IMAP server. */ /* Print raw sized text * Accepts: sizedtext */ void ptext (SIZEDTEXT *txt,STRING *st) { unsigned char c,*s; unsigned long i = txt->size; if ((s = txt->data) != NULL) while (i && ((PBOUT ((c = *s++) ? c : 0x80) != EOF))) --i; else if (st) while (i && (PBOUT ((c = SNX (st)) ? c : 0x80) != EOF)) --i; /* failed to complete? */ if (i) ioerror (stdout,"writing text"); } /* Print thread * Accepts: thread */ void pthread (THREADNODE *thr) { THREADNODE *t; while (thr) { /* for each branch */ PBOUT ('('); /* open branch */ if (thr->num) { /* first node message number */ pnum (thr->num); if ((t = thr->next) != NULL) { /* any subsequent nodes? */ PBOUT (' '); while (t) { /* for each subsequent node */ if (t->branch) { /* branches? */ pthread (t); /* yes, recurse to do branch */ t = NIL; /* done */ } else { /* just output this number */ pnum (t->num); t = t->next; /* and do next message */ } if (t) PBOUT (' '); /* delimit if more to come */ } } } else pthread (thr->next); /* nest for dummy */ PBOUT (')'); /* done with this branch */ thr = thr->branch; /* do next branch */ } } /* Print capabilities * Accepts: option flag */ void pcapability (long flag) { unsigned long i; char *s; struct stat sbuf; AUTHENTICATOR *auth; THREADER *thr = (THREADER *) mail_parameters (NIL,GET_THREADERS,NIL); /* always output protocol level */ PSOUT ("CAPABILITY IMAP4REV1 I18NLEVEL=1 LITERAL+"); #ifdef NETSCAPE_BRAIN_DAMAGE PSOUT (" X-NETSCAPE"); #endif if (flag >= 0) { /* want post-authentication capabilities? */ PSOUT (" IDLE UIDPLUS NAMESPACE CHILDREN MAILBOX-REFERRALS BINARY UNSELECT"); #ifdef ESEARCH PSOUT (" ESEARCH"); #endif PSOUT (" WITHIN SORT"); while (thr) { /* threaders */ PSOUT (" THREAD="); PSOUT (thr->name); thr = thr->next; } if (!anonymous) PSOUT (" MULTIAPPEND"); PSOUT (" SCAN"); /* private extension */ } if (flag <= 0) { /* want pre-authentication capabilities? */ PSOUT (" SASL-IR LOGIN-REFERRALS"); if ((s = ssl_start_tls (NIL)) != NULL) fs_give ((void **) &s); else PSOUT (" STARTTLS"); /* disable plaintext */ if (!(i = !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL))) PSOUT (" LOGINDISABLED"); for (auth = mail_lookup_auth (1); auth; auth = auth->next) if (auth->server && !(auth->flags & AU_DISABLE) && !(auth->flags & AU_HIDE) && (i || (auth->flags & AU_SECURE))) { PSOUT (" AUTH="); PSOUT (auth->name); } if (!stat (ANOFILE,&sbuf)) PSOUT (" AUTH=ANONYMOUS"); } } /* Anonymous users may only use these mailboxes in these namespaces */ char *oktab[] = {"#news.", "#ftp/", "#public/", 0}; /* Check if mailbox name is OK * Accepts: reference name * mailbox name */ long nameok (char *ref,char *name) { int i; unsigned char *s,*t; if (!name) return NIL; /* failure if missing name */ if (!anonymous) return T; /* otherwise OK if not anonymous */ /* validate reference */ if (ref && ((*ref == '#') || (*ref == '{'))) for (i = 0; oktab[i]; i++) { for (s = ref, t = oktab[i]; *t && !compare_uchar (*s,*t); s++, t++); if (!*t) { /* reference OK */ if (*name == '#') break;/* check name if override */ else return T; /* otherwise done */ } } /* ordinary names are OK */ if ((*name != '#') && (*name != '{')) return T; for (i = 0; oktab[i]; i++) { /* validate mailbox */ for (s = name, t = oktab[i]; *t && !compare_uchar (*s,*t); s++, t++); if (!*t) return T; /* name is OK */ } response = "%.80s NO Anonymous may not %.80s this name\015\012"; return NIL; } /* Convert possible BBoard name to actual name * Accepts: command * mailbox name * Returns: maibox name */ char *bboardname (char *cmd,char *name) { if (cmd[0] == 'B') { /* want bboard? */ char *s = litstk[litsp++] = (char *) fs_get (strlen (name) + 9); sprintf (s,"#public/%s",(*name == '/') ? name+1 : name); name = s; } return name; } /* Test if name is news proxy * Accepts: name * Returns: T if news proxy, NIL otherwise */ long isnewsproxy (char *name) { return (nntpproxy && (name[0] == '#') && ((name[1] == 'N') || (name[1] == 'n')) && ((name[2] == 'E') || (name[2] == 'e')) && ((name[3] == 'W') || (name[3] == 'w')) && ((name[4] == 'S') || (name[4] == 's')) && (name[5] == '.')) ? LONGT : NIL; } /* News proxy generate canonical pattern * Accepts: reference * pattern * buffer to return canonical pattern * Returns: T on success with pattern in buffer, NIL on failure */ long newsproxypattern (char *ref,char *pat,char *pattern,long flag) { if (!nntpproxy) return NIL; if (strlen (ref) > NETMAXMBX) { sprintf (pattern,"Invalid reference specification: %.80s",ref); mm_log (pattern,ERROR); return NIL; } if (strlen (pat) > NETMAXMBX) { sprintf (pattern,"Invalid pattern specification: %.80s",pat); mm_log (pattern,ERROR); return NIL; } if (flag) { /* prepend proxy specifier */ sprintf (pattern,"{%.300s/nntp}",nntpproxy); pattern += strlen (pattern); } if (*ref) { /* have a reference */ strcpy (pattern,ref); /* copy reference to pattern */ /* # overrides mailbox field in reference */ if (*pat == '#') strcpy (pattern,pat); /* pattern starts, reference ends, with . */ else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.')) strcat (pattern,pat + 1); /* append, omitting one of the period */ else strcat (pattern,pat); /* anything else is just appended */ } else strcpy (pattern,pat); /* just have basic name */ return isnewsproxy (pattern); } /* IMAP4rev1 Authentication responder * Accepts: challenge * length of challenge * pointer to response length return location if non-NIL * Returns: response */ #define RESPBUFLEN 8*MAILTMPLEN char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen) { unsigned long i,j; unsigned char *t,resp[RESPBUFLEN]; if (initial) { /* initial response given? */ if (clen) return NIL; /* not permitted */ /* set up response */ i = strlen ((char *) (t = initial)); initial = NIL; /* no more initial response */ if ((*t == '=') && !t[1]) { /* SASL-IR does this for 0-length response */ if (rlen) *rlen = 0; /* set length zero if empty */ return cpystr (""); /* and return empty string as response */ } } else { /* issue challenge, get response */ PSOUT ("+ "); for (t = rfc822_binary ((void *) challenge,clen,&i),j = 0; j < i; j++) if (t[j] > ' ') PBOUT (t[j]); fs_give ((void **) &t); CRLF; PFLUSH (); /* dump output buffer */ /* slurp response buffer */ slurp ((char *) resp,RESPBUFLEN,INPUTTIMEOUT); if (!(t = (unsigned char *) strchr ((char *) resp,'\012'))) return (char *) flush (); if (t[-1] == '\015') --t; /* remove CR */ *t = '\0'; /* tie off buffer */ if (resp[0] == '*') { cancelled = T; return NIL; } i = t - resp; /* length of response */ t = resp; /* set up for return call */ } return (i % 4) ? NIL : /* return if valid BASE64 */ (char *) rfc822_base64 (t,i,rlen ? rlen : &i); } /* Proxy copy across mailbox formats * Accepts: mail stream * sequence to copy on this stream * destination mailbox * option flags * Returns: T if success, else NIL */ long proxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) { MAILSTREAM *ts; STRING st; MSGDATA md; SEARCHSET *set; char tmp[MAILTMPLEN]; unsigned long i,j; md.stream = stream; md.msgno = 0; md.flags = md.date = NIL; md.message = &st; /* Currently ignores CP_MOVE and CP_DEBUG */ if (!((options & CP_UID) ? /* validate sequence */ mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence))) return NIL; response = win; /* cancel previous errors */ if (lsterr) fs_give ((void **) &lsterr); /* c-client clobbers sequence, use spare */ for (i = 1,j = 0,set = mail_newsearchset (); i <= nmsgs; i++) if ((mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence) != 0L) { mail_append_set (set,mail_uid (stream,i)); if (!j) md.msgno = (j = i) - 1; } /* only if at least one message to copy */ if (j && !mail_append_multiple (NIL,mailbox,proxy_append,(void *) &md)) { response = trycreate ? losetry : lose; if (set) mail_free_searchset (&set); return NIL; } if (caset) csset = set; /* set for return value now */ else if (set) mail_free_searchset (&set); response = win; /* stomp any previous babble */ if (md.msgno) { /* get new driver name if was dummy */ sprintf (tmp,"Cross-format (%.80s -> %.80s) COPY completed", stream->dtb->name,(ts = mail_open (NIL,mailbox,OP_PROTOTYPE)) ? ts->dtb->name : "unknown"); mm_log (tmp,NIL); } return LONGT; } /* Proxy append message callback * Accepts: MAIL stream * append data package * pointer to return initial flags * pointer to return message internal date * pointer to return stringstruct of message or NIL to stop * Returns: T if success (have message or stop), NIL if error */ long proxy_append (MAILSTREAM *stream,void *data,char **flags,char **date, STRING **message) { MESSAGECACHE *elt; unsigned long i; char *s,*t,tmp[MAILTMPLEN]; MSGDATA *md = (MSGDATA *) data; if (md->flags) fs_give ((void **) &md->flags); if (md->date) fs_give ((void **) &md->date); *message = NIL; /* assume all done */ *flags = *date = NIL; while (++md->msgno <= nmsgs) if ((elt = mail_elt (md->stream,md->msgno))->spare) { if (!(elt->valid && elt->day)) { sprintf (tmp,"%lu",md->msgno); mail_fetch_fast (md->stream,tmp,NIL); } memset (s = tmp,0,MAILTMPLEN); /* copy flags */ if (elt->seen) strcat (s," \\Seen"); if (elt->deleted) strcat (s," \\Deleted"); if (elt->flagged) strcat (s," \\Flagged"); if (elt->answered) strcat (s," \\Answered"); if (elt->draft) strcat (s," \\Draft"); if ((i = elt->user_flags) != 0L) do if ((t = md->stream->user_flags[find_rightmost_bit (&i)]) && *t && (strlen (t) < ((size_t) (MAILTMPLEN-((s += strlen (s))+2-tmp))))) { *s++ = ' '; /* space delimiter */ strcpy (s,t); } while (i); /* until no more user flags */ *message = md->message; /* set up return values */ *flags = md->flags = cpystr (tmp + 1); *date = md->date = cpystr (mail_date (tmp,elt)); INIT (md->message,msg_string,(void *) md,elt->rfc822_size); break; /* process this message */ } return LONGT; } /* Append message callback * Accepts: MAIL stream * append data package * pointer to return initial flags * pointer to return message internal date * pointer to return stringstruct of message or NIL to stop * Returns: T if success (have message or stop), NIL if error */ long append_msg (MAILSTREAM *stream,void *data,char **flags,char **date, STRING **message) { unsigned long i,j; char *t; APPENDDATA *ad = (APPENDDATA *) data; unsigned char *arg = ad->arg; /* flush text of previous message */ if ((t = ad->flags) != NULL) fs_give ((void **) &ad->flags); if ((t = ad->date) != NULL) fs_give ((void **) &ad->date); if ((t = ad->msg) != NULL) fs_give ((void **) &ad->msg); *flags = *date = NIL; /* assume no flags or date */ if (t) { /* have previous message? */ if (!*arg) { /* if least one message, and no more coming */ *message = NIL; /* set stop */ return LONGT; /* return success */ } else if (*arg++ != ' ') { /* must have a delimiter to next argument */ response = misarg; /* oops */ return NIL; } } *message = ad->message; /* return pointer to message stringstruct */ if (*arg == '(') { /* parse optional flag list */ t = ++arg; /* pointer to flag list contents */ while (*arg && (*arg != ')')) arg++; if (*arg) *arg++ = '\0'; if (*arg == ' ') arg++; *flags = ad->flags = cpystr (t); } /* parse optional date */ if (*arg == '"') *date = ad->date = cpystr (snarf (&arg)); if (!arg || (*arg != '{')) /* parse message */ response = "%.80s BAD Missing literal in %.80s\015\012"; else if (!isdigit (arg[1])) response = "%.80s BAD Missing message to %.80s\015\012"; else if (!(i = strtoul (arg+1,&t,10))) response = "%.80s NO Empty message to %.80s\015\012"; else if (i > MAXAPPENDTXT) /* maybe relax this a little */ response = "%.80s NO Excessively large message to %.80s\015\012"; else if (((*t == '+') && (t[1] == '}') && !t[2]) || ((*t == '}') && !t[1])) { /* get a literal buffer */ inliteral (ad->msg = (char *) fs_get (i+1),i); /* get new command tail */ slurp (ad->arg,CMDLEN - (ad->arg - cmdbuf),INPUTTIMEOUT); if (strchr (ad->arg,'\012')) { /* reset strtok mechanism, tie off if done */ if (!strtok_r (ad->arg,"\015\012",&sstate)) *ad->arg = '\0'; /* possible LITERAL+? */ if (((j = strlen (ad->arg)) > 3) && (ad->arg[j - 1] == '}') && (ad->arg[j - 2] == '+') && isdigit (ad->arg[j - 3])) { /* back over possible count */ for (j -= 4; j && isdigit (ad->arg[j]); j--); if (ad->arg[j] == '{') {/* found a literal? */ litplus.ok = T; /* yes, note LITERAL+ in effect, set size */ litplus.size = strtoul (ad->arg + j + 1,NIL,10); } } /* initialize stringstruct */ INIT (ad->message,mail_string,(void *) ad->msg,i); return LONGT; /* ready to go */ } flush (); /* didn't find end of line? */ fs_give ((void **) &ad->msg); } else response = badarg; /* not a literal */ return NIL; /* error */ } /* Got COPY UID data * Accepts: MAIL stream * mailbox name * UID validity * source set of UIDs * destination set of UIDs */ void copyuid (MAILSTREAM *stream,char *mailbox,unsigned long uidvalidity, SEARCHSET *sourceset,SEARCHSET *destset) { if (cauidvalidity) fatal ("duplicate COPYUID/APPENDUID data"); cauidvalidity = uidvalidity; csset = sourceset; caset = destset; } /* Got APPEND UID data * Accepts: mailbox name * UID validity * destination set of UIDs */ void appenduid (char *mailbox,unsigned long uidvalidity,SEARCHSET *set) { copyuid (NIL,mailbox,uidvalidity,NIL,set); } /* Got a referral * Accepts: MAIL stream * URL * referral type code */ char *referral (MAILSTREAM *stream,char *url,long code) { if (lstref) fs_give ((void **) &lstref); lstref = cpystr (url); /* set referral */ /* set error if not a logged in referral */ if (code != REFAUTH) response = lose; if (!lsterr) lsterr = cpystr ("Try referral URL"); return NIL; /* don't chase referrals for now */ } /* Co-routines from MAIL library */ /* Message matches a search * Accepts: MAIL stream * message number */ void mm_searched (MAILSTREAM *s,unsigned long msgno) { /* nothing to do here */ } /* Message exists (i.e. there are that many messages in the mailbox) * Accepts: MAIL stream * message number */ void mm_exists (MAILSTREAM *s,unsigned long number) { /* note change in number of messages */ if ((s != tstream) && (nmsgs != number)) { nmsgs = number; /* always update number of messages */ if (quell_events) existsquelled = T; else { PSOUT ("* "); pnum (nmsgs); PSOUT (" EXISTS\015\012"); } recent = 0xffffffff; /* make sure update recent too */ } } /* Message expunged * Accepts: MAIL stream * message number */ void mm_expunged (MAILSTREAM *s,unsigned long number) { if (quell_events) fatal ("Impossible EXPUNGE event"); if (s != tstream) { PSOUT ("* "); pnum (number); PSOUT (" EXPUNGE\015\012"); } nmsgs--; existsquelled = T; /* do EXISTS when command done */ } /* Message status changed * Accepts: MAIL stream * message number */ void mm_flags (MAILSTREAM *s,unsigned long number) { if (s != tstream) mail_elt (s,number)->spare2 = T; } /* Mailbox found * Accepts: hierarchy delimiter * mailbox name * attributes */ void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes) { mm_list_work ("LIST",delimiter,name,attributes); } /* Subscribed mailbox found * Accepts: hierarchy delimiter * mailbox name * attributes */ void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes) { mm_list_work ("LSUB",delimiter,name,attributes); } /* Mailbox status * Accepts: MAIL stream * mailbox name * mailbox status */ void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status) { if (!quell_events) { char tmp[MAILTMPLEN]; tmp[0] = tmp[1] = '\0'; if (status->flags & SA_MESSAGES) sprintf (tmp + strlen (tmp)," MESSAGES %lu",status->messages); if (status->flags & SA_RECENT) sprintf (tmp + strlen (tmp)," RECENT %lu",status->recent); if (status->flags & SA_UNSEEN) sprintf (tmp + strlen (tmp)," UNSEEN %lu",status->unseen); if (status->flags & SA_UIDNEXT) sprintf (tmp + strlen (tmp)," UIDNEXT %lu",status->uidnext); if (status->flags & SA_UIDVALIDITY) sprintf (tmp + strlen(tmp)," UIDVALIDITY %lu",status->uidvalidity); PSOUT ("* STATUS "); pastring (mailbox); PSOUT (" ("); PSOUT (tmp+1); PBOUT (')'); CRLF; } } /* Worker routine for LIST and LSUB * Accepts: name of response * hierarchy delimiter * mailbox name * attributes */ void mm_list_work (char *what,int delimiter,char *name,long attributes) { char *s; if (!quell_events) { char tmp[MAILTMPLEN]; if (finding) { PSOUT ("* MAILBOX "); PSOUT (name); } /* new form */ else if ((cmd[0] == 'R') || !(attributes & LATT_REFERRAL)) { PSOUT ("* "); PSOUT (what); PSOUT (" ("); tmp[0] = tmp[1] = '\0'; if (attributes & LATT_NOINFERIORS) strcat (tmp," \\NoInferiors"); if (attributes & LATT_NOSELECT) strcat (tmp," \\NoSelect"); if (attributes & LATT_MARKED) strcat (tmp," \\Marked"); if (attributes & LATT_UNMARKED) strcat (tmp," \\UnMarked"); if (attributes & LATT_HASCHILDREN) strcat (tmp," \\HasChildren"); if (attributes & LATT_HASNOCHILDREN) strcat (tmp," \\HasNoChildren"); PSOUT (tmp+1); switch (delimiter) { case '\\': /* quoted delimiter */ case '"': PSOUT (") \"\\"); PBOUT (delimiter); PBOUT ('"'); break; case '\0': /* no delimiter */ PSOUT (") NIL"); break; default: /* unquoted delimiter */ PSOUT (") \""); PBOUT (delimiter); PBOUT ('"'); break; } PBOUT (' '); /* output mailbox name */ if (proxylist && (s = strchr (name,'}'))) pastring (s+1); else pastring (name); } CRLF; } } /* Notification event * Accepts: MAIL stream * string to log * error flag */ void mm_notify (MAILSTREAM *stream,char *string,long errflg) { SIZEDTEXT msg; char *s,*code; if (!quell_events && (!tstream || (stream != tstream))) { switch (errflg) { case NIL: /* information message, set as OK response */ if ((string[0] == '[') && ((string[1] == 'T') || (string[1] == 't')) && ((string[2] == 'R') || (string[2] == 'r')) && ((string[3] == 'Y') || (string[3] == 'y')) && ((string[4] == 'C') || (string[4] == 'c')) && ((string[5] == 'R') || (string[5] == 'r')) && ((string[6] == 'E') || (string[6] == 'e')) && ((string[7] == 'A') || (string[7] == 'a')) && ((string[8] == 'T') || (string[8] == 't')) && ((string[9] == 'E') || (string[9] == 'e')) && (string[10] == ']')) trycreate = T; case BYE: /* some other server signing off */ case PARSE: /* parse glitch, output unsolicited OK */ code = "* OK "; break; case WARN: /* warning, output unsolicited NO (kludge!) */ code = "* NO "; break; case ERROR: /* error that broke command */ default: /* default should never happen */ code = "* BAD "; break; } PSOUT (code); msg.size = (s = strpbrk ((char *) (msg.data = (unsigned char *) string), "\015\012")) ? (s - string) : strlen (string); PSOUTR (&msg); CRLF; PFLUSH (); /* let client see it immediately */ } } /* Log an event for the user to see * Accepts: string to log * error flag */ void mm_log (char *string,long errflg) { SIZEDTEXT msg; char *s; msg.size = (s = strpbrk ((char *) (msg.data = (unsigned char *) string),"\015\012")) ? (s - string) : strlen (string); switch (errflg) { case NIL: /* information message, set as OK response */ if (response == win) { /* only if no other response yet */ if (lsterr) { /* if there was a previous message */ if (!quell_events) { PSOUT ("* OK "); /* blat it out */ PSOUT (lsterr); CRLF; PFLUSH (); /* let client see it immediately */ } fs_give ((void **) &lsterr); } lsterr = cpystr (string); /* copy string for later use */ if (s) lsterr[s - string] = NIL; } break; case PARSE: /* parse glitch, output unsolicited OK */ if (!quell_events) { PSOUT ("* OK [PARSE] "); PSOUTR (&msg); CRLF; PFLUSH (); /* let client see it immediately */ } break; case WARN: /* warning, output unsolicited NO */ /* ignore "Mailbox is empty" (KLUDGE!) */ if (strcmp (string,"Mailbox is empty")) { if (lstwrn) { /* have previous warning? */ if (!quell_events) { PSOUT ("* NO "); PSOUT (lstwrn); CRLF; PFLUSH (); /* make sure client sees it immediately */ } fs_give ((void **) &lstwrn); } lstwrn = cpystr (string); /* note last warning */ if (s) lstwrn[s - string] = NIL; } break; case ERROR: /* error that broke command */ default: /* default should never happen */ response = trycreate ? losetry : lose; if (lsterr) fs_give ((void **) &lsterr); lsterr = cpystr (string); /* note last error */ if (s) lsterr[s - string] = NIL; break; } } /* Return last error */ char *lasterror (void) { if (lsterr) return lsterr; if (lstwrn) return lstwrn; return ""; } /* Log an event to debugging telemetry * Accepts: string to log */ void mm_dlog (char *string) { mm_log (string,WARN); /* shouldn't happen normally */ } /* Get user name and password for this host * Accepts: parse of network user name * where to return user name * where to return password * trial count */ void mm_login (NETMBX *mb,char *username,char *password,long trial) { /* set user name */ strncpy (username,*mb->user ? mb->user : (char *) user,NETMAXUSER); strncpy (password,pass,256); /* and password */ } /* About to enter critical code * Accepts: stream */ void mm_critical (MAILSTREAM *s) { ++critical; } /* About to exit critical code * Accepts: stream */ void mm_nocritical (MAILSTREAM *s) { /* go non-critical, pending death? */ if (!--critical && (state == LOGOUT)) { /* clean up iff needed */ if (s && (stream != s) && !s->lock && (s->dtb->flags & DR_XPOINT)) s = mail_close (s); longjmp (jmpenv,1); /* die now */ } } /* Disk error found * Accepts: stream * system error code * flag indicating that mailbox may be clobbered * Returns: abort flag */ long mm_diskerror (MAILSTREAM *s,long errcode,long serious) { if (serious) { /* try your damnest if clobberage likely */ mm_notify (s,"Retrying to fix probable mailbox damage!",ERROR); PFLUSH (); /* dump output buffer */ syslog (LOG_ALERT, "Retrying after disk error user=%.80s host=%.80s mbx=%.80s: %.80s", user ? (char *) user : "???",tcp_clienthost (), (stream && stream->mailbox) ? stream->mailbox : "???", strerror (errcode)); settimeout (0); /* make damn sure timeout disabled */ sleep (60); /* give it some time to clear up */ return NIL; } if (!quell_events) { /* otherwise die before more damage is done */ PSOUT ("* NO Disk error: "); PSOUT (strerror (errcode)); CRLF; } return T; } /* Log a fatal error event * Accepts: string to log */ void mm_fatal (char *string) { SIZEDTEXT msg; char *s; msg.size = (s = strpbrk ((char *) (msg.data = (unsigned char *) string),"\015\012")) ? (s - string) : strlen (string); if (!quell_events) { PSOUT ("* BYE [ALERT] IMAP4rev1 server crashing: "); PSOUTR (&msg); CRLF; PFLUSH (); } syslog (LOG_ALERT,"Fatal error user=%.80s host=%.80s mbx=%.80s: %.80s", user ? (char *) user : "???",tcp_clienthost (), (stream && stream->mailbox) ? stream->mailbox : "???",string); }