1 /* ========================================================================
2  * Copyright 1988-2008 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *
11  * ========================================================================
12  */
13 
14 /*
15  * Program:	IMAP4rev1 server
16  *
17  * Author:	Mark Crispin
18  *		UW Technology
19  *		University of Washington
20  *		Seattle, WA  98195
21  *		Internet: MRC@Washington.EDU
22  *
23  * Date:	5 November 1990
24  * Last Edited:	22 July 2011
25  */
26 
27 /* Parameter files */
28 
29 #include <stdio.h>
30 #include <ctype.h>
31 #include <errno.h>
32 extern int errno;		/* just in case */
33 #include <signal.h>
34 #include <setjmp.h>
35 #include <time.h>
36 #include "c-client.h"
37 #include "newsrc.h"
38 #include <sys/stat.h>
39 
40 
41 #define CRLF PSOUT ("\015\012")	/* primary output terpri */
42 
43 
44 /* Timeouts and timers */
45 
46 #define MINUTES *60
47 
48 #define LOGINTIMEOUT 3 MINUTES	/* not logged in autologout timer */
49 #define TIMEOUT 30 MINUTES	/* RFC 3501 minimum autologout timer */
50 #define INPUTTIMEOUT 5 MINUTES	/* timer for additional command input */
51 #define ALERTTIMER 1 MINUTES	/* alert check timer */
52 #define SHUTDOWNTIMER 1 MINUTES	/* shutdown dally timer */
53 #define IDLETIMER 1 MINUTES	/* IDLE command poll timer */
54 #define CHECKTIMER 15 MINUTES	/* IDLE command last checkpoint timer */
55 
56 
57 #define LITSTKLEN 20		/* length of literal stack */
58 #define MAXCLIENTLIT 10000	/* maximum non-APPEND client literal size
59 				 * must be smaller than 4294967295
60 				 */
61 #define MAXAPPENDTXT 0x40000000	/* maximum APPEND literal size
62 				 * must be smaller than 4294967295
63 				 */
64 #define CMDLEN 65536		/* size of command buffer */
65 
66 
67 /* Server states */
68 
69 #define LOGIN 0
70 #define SELECT 1
71 #define OPEN 2
72 #define LOGOUT 3
73 
74 /* Body text fetching */
75 
76 typedef struct text_args {
77   char *section;		/* body section */
78   STRINGLIST *lines;		/* header lines */
79   unsigned long first;		/* first octet to fetch */
80   unsigned long last;		/* number of octets to fetch */
81   long flags;			/* fetch flags */
82   long binary;			/* binary flags */
83 } TEXTARGS;
84 
85 #define FTB_BINARY 0x1		/* fetch as binary */
86 #define FTB_SIZE 0x2		/* fetch size only */
87 
88 
89 /* Append data */
90 
91 typedef struct append_data {
92   unsigned char *arg;		/* append argument pointer */
93   char *flags;			/* message flags */
94   char *date;			/* message date */
95   char *msg;			/* message text */
96   STRING *message;		/* message stringstruct */
97 } APPENDDATA;
98 
99 
100 /* Message pointer */
101 
102 typedef struct msg_data {
103   MAILSTREAM *stream;		/* stream */
104   unsigned long msgno;		/* message number */
105   char *flags;			/* current flags */
106   char *date;			/* current date */
107   STRING *message;		/* stringstruct of message */
108 } MSGDATA;
109 
110 /* Function prototypes */
111 
112 int main (int argc,char *argv[]);
113 void ping_mailbox (unsigned long uid);
114 time_t palert (char *file,time_t oldtime);
115 void msg_string_init (STRING *s,void *data,unsigned long size);
116 char msg_string_next (STRING *s);
117 void msg_string_setpos (STRING *s,unsigned long i);
118 void new_flags (MAILSTREAM *stream);
119 void settimeout (unsigned int i);
120 void clkint (void);
121 void kodint (void);
122 void hupint (void);
123 void trmint (void);
124 void staint (void);
125 char *sout (char *s,char *t);
126 char *nout (char *s,unsigned long n,unsigned long base);
127 void slurp (char *s,int n,unsigned long timeout);
128 void inliteral (char *s,unsigned long n);
129 unsigned char *flush (void);
130 void ioerror (FILE *f,char *reason);
131 unsigned char *parse_astring (unsigned char **arg,unsigned long *i,
132 			      unsigned char *del);
133 unsigned char *snarf (unsigned char **arg);
134 unsigned char *snarf_base64 (unsigned char **arg);
135 unsigned char *snarf_list (unsigned char **arg);
136 STRINGLIST *parse_stringlist (unsigned char **s,int *list);
137 unsigned long uidmax (MAILSTREAM *stream);
138 long parse_criteria (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg,
139 		     unsigned long maxuid,unsigned long depth);
140 long parse_criterion (SEARCHPGM *pgm,unsigned char **arg,unsigned long msgmsg,
141 		      unsigned long maxuid,unsigned long depth);
142 long crit_date (unsigned short *date,unsigned char **arg);
143 long crit_date_work (unsigned short *date,unsigned char **arg);
144 long crit_set (SEARCHSET **set,unsigned char **arg,unsigned long maxima);
145 long crit_number (unsigned long *number,unsigned char **arg);
146 long crit_string (STRINGLIST **string,unsigned char **arg);
147 
148 void fetch (char *t,unsigned long uid);
149 typedef void (*fetchfn_t) (unsigned long i,void *args);
150 void fetch_work (char *t,unsigned long uid,fetchfn_t f[],void *fa[]);
151 void fetch_bodystructure (unsigned long i,void *args);
152 void fetch_body (unsigned long i,void *args);
153 void fetch_body_part_mime (unsigned long i,void *args);
154 void fetch_body_part_contents (unsigned long i,void *args);
155 void fetch_body_part_binary (unsigned long i,void *args);
156 void fetch_body_part_header (unsigned long i,void *args);
157 void fetch_body_part_text (unsigned long i,void *args);
158 void remember (unsigned long uid,char *id,SIZEDTEXT *st);
159 void fetch_envelope (unsigned long i,void *args);
160 void fetch_encoding (unsigned long i,void *args);
161 void changed_flags (unsigned long i,int f);
162 void fetch_flags (unsigned long i,void *args);
163 void put_flag (int *c,char *s);
164 void fetch_internaldate (unsigned long i,void *args);
165 void fetch_uid (unsigned long i,void *args);
166 void fetch_rfc822 (unsigned long i,void *args);
167 void fetch_rfc822_header (unsigned long i,void *args);
168 void fetch_rfc822_size (unsigned long i,void *args);
169 void fetch_rfc822_text (unsigned long i,void *args);
170 void penv (ENVELOPE *env);
171 void pbodystructure (BODY *body);
172 void pbody (BODY *body);
173 void pparam (PARAMETER *param);
174 void paddr (ADDRESS *a);
175 void pset (SEARCHSET **set);
176 void pnum (unsigned long i);
177 void pstring (char *s);
178 void pnstring (char *s);
179 void pastring (char *s);
180 void psizedquoted (SIZEDTEXT *s);
181 void psizedliteral (SIZEDTEXT *s,STRING *st);
182 void psizedstring (SIZEDTEXT *s,STRING *st);
183 void psizedastring (SIZEDTEXT *s);
184 void pastringlist (STRINGLIST *s);
185 void pnstringorlist (STRINGLIST *s);
186 void pbodypartstring (unsigned long msgno,char *id,SIZEDTEXT *st,STRING *bs,
187 		      TEXTARGS *ta);
188 void ptext (SIZEDTEXT *s,STRING *st);
189 void pthread (THREADNODE *thr);
190 void pcapability (long flag);
191 long nameok (char *ref,char *name);
192 char *bboardname (char *cmd,char *name);
193 long isnewsproxy (char *name);
194 long newsproxypattern (char *ref,char *pat,char *pattern,long flag);
195 char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen);
196 long proxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
197 long proxy_append (MAILSTREAM *stream,void *data,char **flags,char **date,
198 		   STRING **message);
199 long append_msg (MAILSTREAM *stream,void *data,char **flags,char **date,
200 		 STRING **message);
201 void copyuid (MAILSTREAM *stream,char *mailbox,unsigned long uidvalidity,
202 	      SEARCHSET *sourceset,SEARCHSET *destset);
203 void appenduid (char *mailbox,unsigned long uidvalidity,SEARCHSET *set);
204 char *referral (MAILSTREAM *stream,char *url,long code);
205 void mm_list_work (char *what,int delimiter,char *name,long attributes);
206 char *lasterror (void);
207 
208 /* Global storage */
209 
210 char *version = "404";		/* edit number of this server */
211 char *logout = "Logout";	/* syslogreason for logout */
212 char *goodbye = NIL;		/* bye reason */
213 time_t alerttime = 0;		/* time of last alert */
214 time_t sysalerttime = 0;	/* time of last system alert */
215 time_t useralerttime = 0;	/* time of last user alert */
216 time_t lastcheck = 0;		/* time of last checkpoint */
217 time_t shutdowntime = 0;	/* time of last shutdown */
218 int state = LOGIN;		/* server state */
219 int cancelled = NIL;		/* authenticate cancelled */
220 int trycreate = 0;		/* saw a trycreate */
221 int finding = NIL;		/* doing old FIND command */
222 int anonymous = 0;		/* non-zero if anonymous */
223 int critical = NIL;		/* non-zero if in critical code */
224 int quell_events = NIL;		/* non-zero if in FETCH response */
225 int existsquelled = NIL;	/* non-zero if an EXISTS was quelled */
226 int proxylist = NIL;		/* doing a proxy LIST */
227 MAILSTREAM *stream = NIL;	/* mailbox stream */
228 DRIVER *curdriver = NIL;	/* note current driver */
229 MAILSTREAM *tstream = NIL;	/* temporary mailbox stream */
230 unsigned int nflags = 0;	/* current number of keywords */
231 unsigned long nmsgs =0xffffffff;/* last reported # of messages and recent */
232 unsigned long recent = 0xffffffff;
233 char *nntpproxy = NIL;		/* NNTP proxy name */
234 unsigned char *user = NIL;	/* user name */
235 unsigned char *pass = NIL;	/* password */
236 unsigned char *initial = NIL;	/* initial response */
237 unsigned char cmdbuf[CMDLEN];	/* command buffer */
238 char *status = "starting up";	/* server status */
239 char *tag;			/* tag portion of command */
240 unsigned char *cmd;		/* command portion of command */
241 unsigned char *arg;		/* pointer to current argument of command */
242 char *lstwrn = NIL;		/* last warning message from c-client */
243 char *lsterr = NIL;		/* last error message from c-client */
244 char *lstref = NIL;		/* last referral from c-client */
245 char *response = NIL;		/* command response */
246 struct {
247   unsigned long size;		/* size of current LITERAL+ */
248   unsigned int ok : 1;		/* LITERAL+ in effect */
249 } litplus;
250 int litsp = 0;			/* literal stack pointer */
251 char *litstk[LITSTKLEN];	/* stack to hold literals */
252 unsigned long uidvalidity = 0;	/* last reported UID validity */
253 unsigned long lastuid = 0;	/* last fetched uid */
254 char *lastid = NIL;		/* last fetched body id for this message */
255 char *lastsel = NIL;		/* last selected mailbox name */
256 SIZEDTEXT lastst = {NIL,0};	/* last sizedtext */
257 unsigned long cauidvalidity = 0;/* UIDVALIDITY for COPYUID/APPENDUID */
258 SEARCHSET *csset = NIL;		/* COPYUID source set */
259 SEARCHSET *caset = NIL;		/* COPYUID/APPENDUID destination set */
260 jmp_buf jmpenv;			/* stack context for setjmp */
261 
262 
263 /* Response texts which appear in multiple places */
264 
265 char *win = "%.80s OK ";
266 char *rowin = "%.80s OK [READ-ONLY] %.80s completed\015\012";
267 char *rwwin = "%.80s OK [READ-WRITE] %.80s completed\015\012";
268 char *lose = "%.80s NO ";
269 char *logwin = "%.80s OK [";
270 char *losetry = "%.80s NO [TRYCREATE] %.80s failed: %.900s\015\012";
271 char *loseunknowncte = "%.80s NO [UNKNOWN-CTE] %.80s failed: %.900s\015\012";
272 char *badcmd = "%.80s BAD Command unrecognized: %.80s\015\012";
273 char *misarg = "%.80s BAD Missing or invalid argument to %.80s\015\012";
274 char *badarg = "%.80s BAD Argument given to %.80s when none expected\015\012";
275 char *badseq = "%.80s BAD Bogus sequence in %.80s: %.80s\015\012";
276 char *badatt = "%.80s BAD Bogus attribute list in %.80s\015\012";
277 char *badbin = "%.80s BAD Syntax error in binary specifier\015\012";
278 
279 /* Message string driver for message stringstructs */
280 
281 STRINGDRIVER msg_string = {
282   msg_string_init,		/* initialize string structure */
283   msg_string_next,		/* get next byte in string structure */
284   msg_string_setpos		/* set position in string structure */
285 };
286 
287 /* Main program */
288 
main(int argc,char * argv[])289 int main (int argc,char *argv[])
290 {
291   unsigned long i,uid;
292   long f;
293   unsigned char *s,*t,*u,*v,tmp[MAILTMPLEN];
294   struct stat sbuf;
295   logouthook_t lgoh;
296   int ret = 0;
297   time_t autologouttime = 0;
298   char *pgmname;
299 				/* if case we get borked immediately */
300   if (setjmp (jmpenv)) _exit (1);
301   pgmname = (argc && argv[0]) ?
302     (((s = strrchr (argv[0],'/')) || (s = strrchr (argv[0],'\\'))) ?
303      (char *) s+1 : argv[0]) : "imapd";
304 				/* set service name before linkage */
305   mail_parameters (NIL,SET_SERVICENAME,(void *) "imap");
306 #include "linkage.c"
307   rfc822_date (tmp);		/* get date/time at startup */
308 				/* initialize server */
309   server_init (pgmname,"imap","imaps",clkint,kodint,hupint,trmint,staint);
310 				/* forbid automatic untagged expunge */
311   mail_parameters (NIL,SET_EXPUNGEATPING,NIL);
312 				/* arm proxy copy callback */
313   mail_parameters (NIL,SET_MAILPROXYCOPY,(void *) proxycopy);
314 				/* arm referral callback */
315   mail_parameters (NIL,SET_IMAPREFERRAL,(void *) referral);
316 				/* arm COPYUID callback */
317   mail_parameters (NIL,SET_COPYUID,(void *) copyuid);
318 				/* arm APPENDUID callback */
319   mail_parameters (NIL,SET_APPENDUID,(void *) appenduid);
320 
321   if (stat (SHUTDOWNFILE,&sbuf)) {
322     char proxy[MAILTMPLEN];
323     FILE *nntp = fopen (NNTPFILE,"r");
324     if (nntp) {			/* desire NNTP proxy? */
325       if (fgets (proxy,MAILTMPLEN,nntp)) {
326 				/* remove newline and set NNTP proxy */
327 	if (s = strchr (proxy,'\n')) *s = '\0';
328 	nntpproxy = cpystr (proxy);
329 				/* disable the news driver */
330 	mail_parameters (NIL,DISABLE_DRIVER,"news");
331       }
332       fclose (nntp);		/* done reading proxy name */
333     }
334     s = myusername_full (&i);	/* get user name and flags */
335     switch (i) {
336     case MU_NOTLOGGEDIN:
337       PSOUT ("* OK [");		/* not logged in, ordinary startup */
338       pcapability (-1);
339       break;
340     case MU_ANONYMOUS:
341       anonymous = T;		/* anonymous user, fall into default */
342       s = "ANONYMOUS";
343     case MU_LOGGEDIN:
344       PSOUT ("* PREAUTH [");	/* already logged in, pre-authorized */
345       pcapability (1);
346       user = cpystr (s);	/* copy user name */
347       pass = cpystr ("*");	/* set fake password */
348       state = SELECT;		/* enter select state */
349       break;
350     default:
351       fatal ("Unknown state from myusername_full()");
352     }
353     PSOUT ("] ");
354     if (user) {			/* preauthenticated as someone? */
355       PSOUT ("Pre-authenticated user ");
356       PSOUT (user);
357       PBOUT (' ');
358     }
359   }
360   else {			/* login disabled */
361     PSOUT ("* BYE Service not available ");
362     state = LOGOUT;
363   }
364   PSOUT (tcp_serverhost ());
365   PSOUT (" IMAP4rev1 ");
366   PSOUT (CCLIENTVERSION);
367   PBOUT ('.');
368   PSOUT (version);
369   PSOUT (" at ");
370   PSOUT (tmp);
371   CRLF;
372   PFLUSH ();			/* dump output buffer */
373   switch (state) {		/* do this after the banner */
374   case LOGIN:
375     autologouttime = time (0) + LOGINTIMEOUT;
376     break;
377   case SELECT:
378     syslog (LOG_INFO,"Preauthenticated user=%.80s host=%.80s",
379 	    user,tcp_clienthost ());
380     break;
381   }
382 
383   if (setjmp (jmpenv)) {	/* die if a signal handler say so */
384 				/* in case we get borked now */
385     if (setjmp (jmpenv)) _exit (1);
386 				/* need to close stream gracefully? */
387     if (stream && !stream->lock && (stream->dtb->flags & DR_XPOINT))
388       stream = mail_close (stream);
389     ret = 1;			/* set exit status */
390   }
391   else while (state != LOGOUT) {/* command processing loop */
392     slurp (cmdbuf,CMDLEN,TIMEOUT);
393 				/* no more last error or literal */
394     if (lstwrn) fs_give ((void **) &lstwrn);
395     if (lsterr) fs_give ((void **) &lsterr);
396     if (lstref) fs_give ((void **) &lstref);
397     while (litsp) fs_give ((void **) &litstk[--litsp]);
398 				/* find end of line */
399     if (!strchr (cmdbuf,'\012')) {
400       if (t = strchr (cmdbuf,' ')) *t = '\0';
401       if ((t - cmdbuf) > 100) t = NIL;
402       flush ();			/* flush excess */
403       if (state == LOGIN)	/* error if NLI */
404 	syslog (LOG_INFO,"Line too long before authentication host=%.80s",
405 		tcp_clienthost ());
406       sprintf (tmp,response,t ? (char *) cmdbuf : "*");
407       PSOUT (tmp);
408     }
409     else if (!(tag = strtok (cmdbuf," \015\012"))) {
410       if (state == LOGIN)	/* error if NLI */
411 	syslog (LOG_INFO,"Null command before authentication host=%.80s",
412 		tcp_clienthost ());
413       PSOUT ("* BAD Null command\015\012");
414     }
415     else if (strlen (tag) > 50) PSOUT ("* BAD Excessively long tag\015\012");
416     else if (!(s = strtok (NIL," \015\012"))) {
417       if (state == LOGIN)	/* error if NLI */
418 	syslog (LOG_INFO,"Missing command before authentication host=%.80s",
419 		tcp_clienthost ());
420       PSOUT (tag);
421       PSOUT (" BAD Missing command\015\012");
422     }
423     else {			/* parse command */
424       response = win;		/* set default response */
425       finding = NIL;		/* no longer FINDing */
426       ucase (s);		/* canonicalize command case */
427 				/* UID command? */
428       if (!strcmp (s,"UID") && strtok (NIL," \015\012")) {
429 	uid = T;		/* a UID command */
430 	s[3] = ' ';		/* restore the space delimiter */
431 	ucase (s);		/* make sure command all uppercase */
432       }
433       else uid = NIL;		/* not a UID command */
434 				/* flush previous saved command */
435       if (cmd) fs_give ((void **) &cmd);
436       cmd = cpystr (s);		/* save current command */
437 				/* snarf argument, see if possible litplus */
438       if ((arg = strtok (NIL,"\015\012")) && ((i = strlen (arg)) > 3) &&
439 	  (arg[i - 1] == '}') && (arg[i - 2] == '+') && isdigit (arg[i - 3])) {
440 				/* back over possible count */
441 	for (i -= 4; i && isdigit (arg[i]); i--);
442 	if (arg[i] == '{') {	/* found a literal? */
443 	  litplus.ok = T;	/* yes, note LITERAL+ in effect, set size */
444 	  litplus.size = strtoul (arg + i + 1,NIL,10);
445 	}
446       }
447 
448 				/* these commands always valid */
449       if (!strcmp (cmd,"NOOP")) {
450 	if (arg) response = badarg;
451 	else if (stream)	/* allow untagged EXPUNGE */
452 	  mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
453       }
454       else if (!strcmp (cmd,"LOGOUT")) {
455 	if (arg) response = badarg;
456 	else {			/* time to say farewell */
457 	  server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
458 	  if (lastsel) fs_give ((void **) &lastsel);
459 	  if (state == OPEN) stream = mail_close (stream);
460 	  state = LOGOUT;
461 	  PSOUT ("* BYE ");
462 	  PSOUT (mylocalhost ());
463 	  PSOUT (" IMAP4rev1 server terminating connection\015\012");
464 	}
465       }
466       else if (!strcmp (cmd,"CAPABILITY")) {
467 	if (arg) response = badarg;
468 	else {
469 	  PSOUT ("* ");
470 	  pcapability (0);	/* print capabilities */
471 	  CRLF;
472 	}
473 	if (stream)		/* allow untagged EXPUNGE */
474 	  mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
475       }
476 #ifdef NETSCAPE_BRAIN_DAMAGE
477       else if (!strcmp (cmd,"NETSCAPE")) {
478 	PSOUT ("* OK [NETSCAPE]\015\012* VERSION 1.0 UNIX\015\012* ACCOUNT-URL \"");
479 	PSOUT (NETSCAPE_BRAIN_DAMAGE);
480 	PBOUT ('"');
481 	CRLF;
482       }
483 #endif
484 
485       else switch (state) {	/* dispatch depending upon state */
486       case LOGIN:		/* waiting to get logged in */
487 				/* new style authentication */
488 	if (!strcmp (cmd,"AUTHENTICATE")) {
489 	  if (user) fs_give ((void **) &user);
490 	  if (pass) fs_give ((void **) &pass);
491 	  initial = NIL;	/* no initial argument */
492 	  cancelled = NIL;	/* not cancelled */
493 				/* mandatory first argument */
494 	  if (!(s = snarf (&arg))) response = misarg;
495 	  else if (arg && !(initial = snarf_base64 (&arg)))
496 	    response = misarg;	/* optional second argument */
497 	  else if (arg) response = badarg;
498 	  else if (!strcmp (ucase (s),"ANONYMOUS") && !stat (ANOFILE,&sbuf)) {
499 	    if (!(s = imap_responder ("",0,NIL)))
500 	      response ="%.80s BAD AUTHENTICATE ANONYMOUS cancelled\015\012";
501 	    else if (anonymous_login (argc,argv)) {
502 	      anonymous = T;	/* note we are anonymous */
503 	      user = cpystr ("ANONYMOUS");
504 	      pass = cpystr ("*");
505 	      state = SELECT;	/* make select */
506 	      alerttime = 0;	/* force alert */
507 	      response = logwin;/* return logged-in capabilities */
508 	      syslog (LOG_INFO,"Authenticated anonymous=%.80s host=%.80s",s,
509 		      tcp_clienthost ());
510 	      fs_give ((void **) &s);
511 	    }
512 	    else response ="%.80s NO AUTHENTICATE ANONYMOUS failed\015\012";
513 	  }
514 	  else if (user = cpystr (mail_auth (s,imap_responder,argc,argv))) {
515 	    pass = cpystr ("*");
516 	    state = SELECT;	/* make select */
517 	    alerttime = 0;	/* force alert */
518 	    response = logwin;	/* return logged-in capabilities */
519 	    syslog (LOG_INFO,"Authenticated user=%.80s host=%.80s mech=%.80s",
520 		    user,tcp_clienthost (),s);
521 	  }
522 
523 	  else {
524 	    AUTHENTICATOR *auth = mail_lookup_auth (1);
525 	    char *msg = (char *) fs_get (strlen (cmd) + strlen (s) + 2);
526 	    sprintf (msg,"%s %s",cmd,s);
527 	    fs_give ((void **) &cmd);
528 	    cmd = msg;
529 	    for (i = !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL);
530 		 auth && compare_cstring (s,auth->name); auth = auth->next);
531 	    /* Failed authentication when hidden looks like invalid command.
532 	     * This is intentional but confused me when I was debugging.
533 	     */
534 	    if (auth && auth->server && !(auth->flags & AU_DISABLE) &&
535 		!(auth->flags & AU_HIDE) && (i || (auth->flags & AU_SECURE))) {
536 	      response = lose;
537 	      if (cancelled) {
538 		if (lsterr) fs_give ((void **) &lsterr);
539 		lsterr = cpystr ("cancelled by user");
540 	      }
541 	      if (!lsterr)	/* catch-all */
542 		lsterr = cpystr ("Invalid authentication credentials");
543 	      syslog (LOG_INFO,"AUTHENTICATE %.80s failure host=%.80s",s,
544 		      tcp_clienthost ());
545 	    }
546 	    else {
547 	      response = badcmd;
548 	      syslog (LOG_INFO,"AUTHENTICATE %.80s invalid host=%.80s",s,
549 		      tcp_clienthost ());
550 	    }
551 	  }
552 	}
553 
554 				/* plaintext login with password */
555 	else if (!strcmp (cmd,"LOGIN")) {
556 	  if (user) fs_give ((void **) &user);
557 	  if (pass) fs_give ((void **) &pass);
558 				/* two arguments */
559 	  if (!((user = cpystr (snarf (&arg))) &&
560 		(pass = cpystr (snarf (&arg))))) response = misarg;
561 	  else if (arg) response = badarg;
562 				/* see if we allow anonymous */
563 	  else if (!compare_cstring (user,"ANONYMOUS") &&
564 		   !stat (ANOFILE,&sbuf) && anonymous_login (argc,argv)) {
565 	    anonymous = T;	/* note we are anonymous */
566 	    ucase (user);	/* make all uppercase for consistency */
567 	    state = SELECT;	/* make select */
568 	    alerttime = 0;	/* force alert */
569 	    response = logwin;	/* return logged-in capabilities */
570 	    syslog (LOG_INFO,"Login anonymous=%.80s host=%.80s",pass,
571 		    tcp_clienthost ());
572 	  }
573 	  else {		/* delimit user from possible admin */
574 	    if (s = strchr (user,'*')) *s++ ='\0';
575 				/* see if username and password are OK */
576 	    if (server_login (user,pass,s,argc,argv)) {
577 	      state = SELECT;	/* make select */
578 	      alerttime = 0;	/* force alert */
579 	      response = logwin;/* return logged-in capabilities */
580 	      syslog (LOG_INFO,"Login user=%.80s host=%.80s",user,
581 		      tcp_clienthost ());
582 	    }
583 	    else {
584 	      response = lose;
585 	      if (!lsterr) lsterr = cpystr ("Invalid login credentials");
586 	    }
587 	  }
588 	}
589 				/* start TLS security */
590 	else if (!strcmp (cmd,"STARTTLS")) {
591 	  if (arg) response = badarg;
592 	  else if (lsterr = ssl_start_tls (pgmname)) response = lose;
593 	}
594 	else response = badcmd;
595 	break;
596 
597       case OPEN:		/* valid only when mailbox open */
598 				/* fetch mailbox attributes */
599 	if (!strcmp (cmd,"FETCH") || !strcmp (cmd,"UID FETCH")) {
600 	  if (!(arg && (s = strtok (arg," ")) && (t = strtok(NIL,"\015\012"))))
601 	    response = misarg;
602 	  else if (uid ? mail_uid_sequence (stream,s) :
603 		   mail_sequence (stream,s)) fetch (t,uid);
604 	  else response = badseq;
605 	}
606 				/* store mailbox attributes */
607 	else if (!strcmp (cmd,"STORE") || !strcmp (cmd,"UID STORE")) {
608 				/* must have three arguments */
609 	  if (!(arg && (s = strtok (arg," ")) && (v = strtok (NIL," ")) &&
610 		(t = strtok (NIL,"\015\012")))) response = misarg;
611 	  else if (!(uid ? mail_uid_sequence (stream,s) :
612 		     mail_sequence (stream,s))) response = badseq;
613 	  else {
614 	    f = ST_SET | (uid ? ST_UID : NIL)|((v[5]&&v[6]) ? ST_SILENT : NIL);
615 	    if (!strcmp (ucase (v),"FLAGS") || !strcmp (v,"FLAGS.SILENT")) {
616 	      strcpy (tmp,"\\Answered \\Flagged \\Deleted \\Draft \\Seen");
617 	      for (i = 0, u = tmp;
618 		   (i < NUSERFLAGS) && (v = stream->user_flags[i]); i++)
619 	        if (strlen (v) <
620 		    ((size_t) (MAILTMPLEN - ((u += strlen (u)) + 2 - tmp)))) {
621 		  *u++ = ' ';	/* write next flag */
622 		  strcpy (u,v);
623 		}
624 	      mail_flag (stream,s,tmp,f & ~ST_SET);
625 	    }
626 	    else if (!strcmp (v,"-FLAGS") || !strcmp (v,"-FLAGS.SILENT"))
627 	      f &= ~ST_SET;	/* clear flags */
628 	    else if (strcmp (v,"+FLAGS") && strcmp (v,"+FLAGS.SILENT")) {
629 	      response = badatt;
630 	      break;
631 	    }
632 				/* find last keyword */
633 	    for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; i++);
634 	    mail_flag (stream,s,t,f);
635 				/* any new keywords appeared? */
636 	    if (i < NUSERFLAGS && stream->user_flags[i]) new_flags (stream);
637 				/* return flags if silence not wanted */
638 	    if (uid ? mail_uid_sequence (stream,s) : mail_sequence (stream,s))
639 	      for (i = 1; i <= nmsgs; i++) if (mail_elt(stream,i)->sequence)
640 		mail_elt (stream,i)->spare2 = (f & ST_SILENT) ? NIL : T;
641 	  }
642 	}
643 
644 				/* check for new mail */
645 	else if (!strcmp (cmd,"CHECK")) {
646 				/* no arguments */
647 	  if (arg) response = badarg;
648 	  else if (!anonymous) {
649 	    mail_check (stream);
650 				/* remember last check time */
651 	    lastcheck = time (0);
652 	  }
653 	}
654 				/* expunge deleted messages */
655 	else if (!(anonymous || (strcmp (cmd,"EXPUNGE") &&
656 				 strcmp (cmd,"UID EXPUNGE")))) {
657 	  if (uid && !arg) response = misarg;
658 	  else if (!uid && arg) response = badarg;
659 	  else {		/* expunge deleted or specified UIDs */
660 	    mail_expunge_full (stream,arg,arg ? EX_UID : NIL);
661 				/* remember last checkpoint */
662 	    lastcheck = time (0);
663 	  }
664 	}
665 				/* close mailbox */
666 	else if (!strcmp (cmd,"CLOSE") || !strcmp (cmd,"UNSELECT")) {
667 				/* no arguments */
668 	  if (arg) response = badarg;
669 	  else {
670 				/* no last uid */
671 	    uidvalidity = lastuid = 0;
672 	    if (lastsel) fs_give ((void **) &lastsel);
673 	    if (lastid) fs_give ((void **) &lastid);
674 	    if (lastst.data) fs_give ((void **) &lastst.data);
675 	    stream = mail_close_full (stream,((*cmd == 'C') && !anonymous) ?
676 				      CL_EXPUNGE : NIL);
677 	    state = SELECT;	/* no longer opened */
678 	    lastcheck = 0;	/* no last checkpoint */
679 	  }
680 	}
681 	else if (!anonymous &&	/* copy message(s) */
682 		 (!strcmp (cmd,"COPY") || !strcmp (cmd,"UID COPY"))) {
683 	  trycreate = NIL;	/* no trycreate status */
684 	  if (!(arg && (s = strtok (arg," ")) && (arg = strtok(NIL,"\015\012"))
685 		&& (t = snarf (&arg)))) response = misarg;
686 	  else if (arg) response = badarg;
687 	  else if (!nmsgs) {
688 	    response = lose;
689 	    if (!lsterr) lsterr = cpystr ("Mailbox is empty");
690 	  }
691 	  else if (!(uid ? mail_uid_sequence (stream,s) :
692 		     mail_sequence (stream,s))) response = badseq;
693 				/* try copy */
694 	  else if (!mail_copy_full (stream,s,t,uid ? CP_UID : NIL)) {
695 	    response = trycreate ? losetry : lose;
696 	    if (!lsterr) lsterr = cpystr ("No such destination mailbox");
697 	  }
698 	}
699 
700 				/* sort mailbox */
701 	else if (!strcmp (cmd,"SORT") || !strcmp (cmd,"UID SORT")) {
702 				/* must have four arguments */
703 	  if (!(arg && (*arg == '(') && (t = strchr (s = arg + 1,')')) &&
704 		(t[1] == ' ') && (*(arg = t + 2)))) response = misarg;
705 	  else {		/* read criteria */
706 	    SEARCHPGM *spg = NIL;
707 	    char *cs = NIL;
708 	    SORTPGM *pgm = NIL,*pg = NIL;
709 	    unsigned long *slst,*sl;
710 	    *t = NIL;		/* tie off criteria list */
711 	    if (!(s = strtok (ucase (s)," "))) response = badatt;
712 	    else {
713 	      do {		/* parse sort attributes */
714 		if (pg) pg = pg->next = mail_newsortpgm ();
715 		else pgm = pg = mail_newsortpgm ();
716 		if (!strcmp (s,"REVERSE")) {
717 		  pg->reverse = T;
718 		  if (!(s = strtok (NIL," "))) {
719 		    s = "";	/* end of attributes */
720 		    break;
721 		  }
722 		}
723 		if (!strcmp (s,"DATE")) pg->function = SORTDATE;
724 		else if (!strcmp (s,"ARRIVAL")) pg->function = SORTARRIVAL;
725 		else if (!strcmp (s,"FROM")) pg->function = SORTFROM;
726 		else if (!strcmp (s,"SUBJECT")) pg->function = SORTSUBJECT;
727 		else if (!strcmp (s,"TO")) pg->function = SORTTO;
728 		else if (!strcmp (s,"CC")) pg->function = SORTCC;
729 		else if (!strcmp (s,"SIZE")) pg->function = SORTSIZE;
730 		else break;
731 	      } while (s = strtok (NIL," "));
732 				/* bad SORT attribute */
733 	      if (s) response = badatt;
734 				/* get charset and search criteria */
735 	      else if (!((t = snarf (&arg)) && (cs = cpystr (t)) && arg &&
736 			 *arg)) response = misarg;
737 				/* parse search criteria  */
738 	      else if (!parse_criteria (spg = mail_newsearchpgm (),&arg,nmsgs,
739 					uidmax (stream),0)) response = badatt;
740 	      else if (arg && *arg) response = badarg;
741 	      else if (slst = mail_sort (stream,cs,spg,pgm,uid ? SE_UID:NIL)) {
742 		PSOUT ("* SORT");
743 		for (sl = slst; *sl; sl++) {
744 		  PBOUT (' ');
745 		  pnum (*sl);
746 		}
747 		CRLF;
748 		fs_give ((void **) &slst);
749 	      }
750 	    }
751 	    if (pgm) mail_free_sortpgm (&pgm);
752 	    if (spg) mail_free_searchpgm (&spg);
753 	    if (cs) fs_give ((void **) &cs);
754 	  }
755 	}
756 
757 				/* thread mailbox */
758 	else if (!strcmp (cmd,"THREAD") || !strcmp (cmd,"UID THREAD")) {
759 	  THREADNODE *thr;
760 	  SEARCHPGM *spg = NIL;
761 	  char *cs = NIL;
762 				/* must have four arguments */
763 	  if (!(arg && (s = strtok (arg," ")) && (cs = strtok (NIL," ")) &&
764 		(cs = cpystr (cs)) && (arg = strtok (NIL,"\015\012"))))
765 	    response = misarg;
766 	  else if (!parse_criteria (spg = mail_newsearchpgm (),&arg,nmsgs,
767 				    uidmax (stream),0)) response = badatt;
768 	  else if (arg && *arg) response = badarg;
769 	  else {
770 	    if (thr = mail_thread (stream,s,cs,spg,uid ? SE_UID : NIL)) {
771 	      PSOUT ("* THREAD ");
772 	      pthread (thr);
773 	      mail_free_threadnode (&thr);
774 	    }
775 	    else PSOUT ("* THREAD");
776 	    CRLF;
777 	  }
778 	  if (spg) mail_free_searchpgm (&spg);
779 	  if (cs) fs_give ((void **) &cs);
780 	}
781 
782 				/* search mailbox */
783         else if (!strcmp (cmd,"SEARCH") || !strcmp (cmd,"UID SEARCH")) {
784 	  int retval = NIL;
785 	  char *charset = NIL;
786 	  SEARCHPGM *pgm;
787 	  response = misarg;	/* assume failure */
788 	  if (!arg) break;	/* one or more arguments required */
789 	  if (((arg[0] == 'R') || (arg[0] == 'r')) &&
790 	      ((arg[1] == 'E') || (arg[1] == 'e')) &&
791 	      ((arg[2] == 'T') || (arg[2] == 't')) &&
792 	      ((arg[3] == 'U') || (arg[3] == 'u')) &&
793 	      ((arg[4] == 'R') || (arg[4] == 'r')) &&
794 	      ((arg[5] == 'N') || (arg[5] == 'n')) &&
795 	      (arg[6] == ' ') && (arg[7] == '(')) {
796 	    retval = 0x4000;	/* return is specified */
797 	    for (arg += 8; *arg && (*arg != ')'); ) {
798 	      if (((arg[0] == 'M') || (arg[0] == 'm')) &&
799 		  ((arg[1] == 'I') || (arg[1] == 'i')) &&
800 		  ((arg[2] == 'N') || (arg[2] == 'n')) &&
801 		  ((arg[3] == ' ') || (arg[3] == ')'))) {
802 		retval |= 0x1;
803 		arg += 3;
804 	      }
805 	      else if (((arg[0] == 'M') || (arg[0] == 'm')) &&
806 		       ((arg[1] == 'A') || (arg[1] == 'a')) &&
807 		       ((arg[2] == 'X') || (arg[2] == 'x')) &&
808 		       ((arg[3] == ' ') || (arg[3] == ')'))) {
809 		retval |= 0x2;
810 		arg += 3;
811 	      }
812 	      else if (((arg[0] == 'A') || (arg[0] == 'a')) &&
813 		       ((arg[1] == 'L') || (arg[1] == 'l')) &&
814 		       ((arg[2] == 'L') || (arg[2] == 'l')) &&
815 		       ((arg[3] == ' ') || (arg[3] == ')'))) {
816 		retval |= 0x4;
817 		arg += 3;
818 	      }
819 	      else if (((arg[0] == 'C') || (arg[0] == 'c')) &&
820 		       ((arg[1] == 'O') || (arg[1] == 'o')) &&
821 		       ((arg[2] == 'U') || (arg[2] == 'u')) &&
822 		       ((arg[3] == 'N') || (arg[3] == 'n')) &&
823 		       ((arg[4] == 'T') || (arg[4] == 't')) &&
824 		       ((arg[5] == ' ') || (arg[5] == ')'))) {
825 		retval |= 0x10;
826 		arg += 5;
827 	      }
828 	      else break;	/* unknown return value */
829 				/* more return values to come */
830 	      if ((*arg == ' ') && (arg[1] != ')')) ++arg;
831 	    }
832 				/* RETURN list must be properly terminated */
833 	    if ((*arg++ != ')') || (*arg++ != ' ')) break;
834 				/* default return value is ALL */
835 	    if (!(retval &= 0x3fff)) retval = 0x4;
836 	  }
837 
838 				/* character set specified? */
839 	  if (((arg[0] == 'C') || (arg[0] == 'c')) &&
840 	      ((arg[1] == 'H') || (arg[1] == 'h')) &&
841 	      ((arg[2] == 'A') || (arg[2] == 'a')) &&
842 	      ((arg[3] == 'R') || (arg[3] == 'r')) &&
843 	      ((arg[4] == 'S') || (arg[4] == 's')) &&
844 	      ((arg[5] == 'E') || (arg[5] == 'e')) &&
845 	      ((arg[6] == 'T') || (arg[6] == 't')) &&
846 	      (arg[7] == ' ')) {
847 	    arg += 8;		/* yes, skip over CHARSET token */
848 	    if (s = snarf (&arg)) charset = cpystr (s);
849 	    else break;		/* missing character set */
850 	  }
851 				/* must have arguments here */
852 	  if (!(arg && *arg)) break;
853 	  if (parse_criteria (pgm = mail_newsearchpgm (),&arg,nmsgs,
854 			      uidmax (stream),0) && !*arg) {
855 	    response = win;	/* looks good, try the search */
856 	    mail_search_full (stream,charset,pgm,SE_FREE);
857 				/* output search results if success */
858 	    if (response == win) {
859 	      if (retval) {	/* ESEARCH desired */
860 		PSOUT ("* ESEARCH (TAG ");
861 		pstring (tag);
862 		PBOUT (')');
863 		if (uid) PSOUT (" UID");
864 				/* wants MIN */
865 		if (retval & 0x1) {
866 		  for (i = 1; (i <= nmsgs) && !mail_elt (stream,i)->searched;
867 		       ++i);
868 		  if (i <= nmsgs) {
869 		    PSOUT (" MIN ");
870 		    pnum (uid ? mail_uid (stream,i) : i);
871 		  }
872 		}
873 				/* wants MAX */
874 		if (retval & 0x2) {
875 		  for (i = nmsgs; i && !mail_elt (stream,i)->searched; --i);
876 		  if (i) {
877 		    PSOUT (" MAX ");
878 		    pnum (uid ? mail_uid (stream,i) : i);
879 		  }
880 		}
881 
882 				/* wants ALL */
883 		if (retval & 0x4) {
884 		  unsigned long j;
885 				/* find first match */
886 		  for (i = 1; (i <= nmsgs) && !mail_elt (stream,i)->searched;
887 		       ++i);
888 		  if (i <= nmsgs) {
889 		    PSOUT (" ALL ");
890 		    pnum (uid ? mail_uid (stream,i) : i);
891 		    j = i;	/* last message output */
892 		  }
893 		  while (++i <= nmsgs) {
894 		    if (mail_elt (stream,i)->searched) {
895 		      while ((++i <= nmsgs) && mail_elt (stream,i)->searched);
896 				/* previous message is end of range */
897 		      if (j != --i) {
898 			PBOUT (':');
899 			pnum (uid ? mail_uid (stream,i) : i);
900 		      }
901 		    }
902 				/* search for next match */
903 		    while ((++i <= nmsgs) && !mail_elt (stream,i)->searched);
904 		    if (i <= nmsgs) {
905 		      PBOUT (',');
906 		      pnum (uid ? mail_uid (stream,i) : i);
907 		      j = i;	/* last message output */
908 		    }
909 		  }
910 		}
911 				/* wants COUNT */
912 		if (retval & 0x10) {
913 		  unsigned long j;
914 		  for (i = 1, j = 0; i <= nmsgs; ++i)
915 		    if (mail_elt (stream,i)->searched) ++j;
916 		  PSOUT (" COUNT ");
917 		  pnum (j);
918 		}
919 	      }
920 	      else {		/* standard search */
921 		PSOUT ("* SEARCH");
922 		for (i = 1; i <= nmsgs; ++i)
923 		  if (mail_elt (stream,i)->searched) {
924 		    PBOUT (' ');
925 		    pnum (uid ? mail_uid (stream,i) : i);
926 		  }
927 	      }
928 	      CRLF;
929 	    }
930 	  }
931 	  else mail_free_searchpgm (&pgm);
932 	  if (charset) fs_give ((void **) &charset);
933 	}
934 
935 	else			/* fall into select case */
936       case SELECT:		/* valid whenever logged in */
937 				/* select new mailbox */
938 	  if (!(strcmp (cmd,"SELECT") && strcmp (cmd,"EXAMINE") &&
939 		strcmp (cmd,"BBOARD"))) {
940 				/* single argument */
941 	  if (!(s = snarf (&arg))) response = misarg;
942 	  else if (arg) response = badarg;
943 	  else if (nameok (NIL,s = bboardname (cmd,s))) {
944 	    DRIVER *factory = mail_valid (NIL,s,NIL);
945 	    f = (anonymous ? OP_ANONYMOUS + OP_READONLY : NIL) |
946 	      ((*cmd == 'S') ? NIL : OP_READONLY);
947 	    curdriver = NIL;	/* no drivers known */
948 				/* no last uid */
949 	    uidvalidity = lastuid = 0;
950 	    if (lastid) fs_give ((void **) &lastid);
951 	    if (lastst.data) fs_give ((void **) &lastst.data);
952 	    nflags = 0;		/* force update */
953 	    nmsgs = recent = 0xffffffff;
954 	    if (factory && !strcmp (factory->name,"phile") &&
955 		(stream = mail_open (stream,s,f | OP_SILENT)) &&
956 		(response == win)) {
957 	      BODY *b;
958 				/* see if proxy open */
959 	      if ((mail_elt (stream,1)->rfc822_size < 400) &&
960 		  mail_fetchstructure (stream,1,&b) && (b->type == TYPETEXT) &&
961 		  (t = mail_fetch_text (stream,1,NIL,&i,NIL)) &&
962 		  (i < MAILTMPLEN) && (t[0] == '{')) {
963 				/* copy and tie off */
964 		strncpy (tmp,t,i)[i] = '\0';
965 				/* nuke any trailing newline */
966 		if (t = strpbrk (tmp,"\r\n")) *t = '\0';
967 				/* try to open proxy */
968 		if ((tstream = mail_open (NIL,tmp,f | OP_SILENT)) &&
969 		    (response == win) && tstream->nmsgs) {
970 		  s = tmp;	/* got it, close the link */
971 		  mail_close (stream);
972 		  stream = tstream;
973 		  tstream = NIL;
974 		}
975 	      }
976 				/* now give the exists event */
977 	      stream->silent = NIL;
978 	      mm_exists (stream,stream->nmsgs);
979 	    }
980 	    else if (!factory && isnewsproxy (s)) {
981 	      sprintf (tmp,"{%.300s/nntp}%.300s",nntpproxy,(char *) s+6);
982 	      stream = mail_open (stream,tmp,f);
983 	    }
984 				/* open stream normally then */
985 	    else stream = mail_open (stream,s,f);
986 
987 	    if (stream && (response == win)) {
988 	      state = OPEN;	/* note state open */
989 	      if (lastsel) fs_give ((void **) &lastsel);
990 				/* canonicalize INBOX */
991 	      if (!compare_cstring (s,"#MHINBOX"))
992 		lastsel = cpystr ("#MHINBOX");
993 	      else lastsel = cpystr (compare_cstring (s,"INBOX") ?
994 				     (char *) s : "INBOX");
995 				/* note readonly/readwrite */
996 	      response = stream->rdonly ? rowin : rwwin;
997 	      if (anonymous)
998 		syslog (LOG_INFO,"Anonymous select of %.80s host=%.80s",
999 			stream->mailbox,tcp_clienthost ());
1000 	      lastcheck = 0;	/* no last check */
1001 	    }
1002 	    else {		/* failed, nuke old selection */
1003 	      if (stream) stream = mail_close (stream);
1004 	      state = SELECT;	/* no mailbox open now */
1005 	      if (lastsel) fs_give ((void **) &lastsel);
1006 	      response = lose;	/* open failed */
1007 	    }
1008 	  }
1009 	}
1010 
1011 				/* APPEND message to mailbox */
1012 	else if (!(anonymous || strcmp (cmd,"APPEND"))) {
1013 				/* parse mailbox name */
1014 	  if ((s = snarf (&arg)) && arg) {
1015 	    STRING st;		/* message stringstruct */
1016 	    APPENDDATA ad;
1017 	    ad.arg = arg;	/* command arguments */
1018 				/* no message yet */
1019 	    ad.flags = ad.date = ad.msg = NIL;
1020 	    ad.message = &st;	/* pointer to stringstruct to use */
1021 	    trycreate = NIL;	/* no trycreate status */
1022 	    if (!mail_append_multiple (NIL,s,append_msg,(void *) &ad)) {
1023 	      if (response == win) response = trycreate ? losetry : lose;
1024 				/* this can happen with #driver. hack */
1025 	      if (!lsterr) lsterr = cpystr ("No such destination mailbox");
1026 	    }
1027 				/* clean up any message text left behind */
1028 	    if (ad.flags) fs_give ((void **) &ad.flags);
1029 	    if (ad.date) fs_give ((void **) &ad.date);
1030 	    if (ad.msg) fs_give ((void **) &ad.msg);
1031 	  }
1032 	  else response = misarg;
1033 	  if (stream)		/* allow untagged EXPUNGE */
1034 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1035 	}
1036 				/* list mailboxes */
1037 	else if (!strcmp (cmd,"LIST") || !strcmp (cmd,"RLIST")) {
1038 				/* get reference and mailbox argument */
1039 	  if (!((s = snarf (&arg)) && (t = snarf_list (&arg))))
1040 	    response = misarg;
1041 	  else if (arg) response = badarg;
1042 				/* make sure anonymous can't do bad things */
1043 	  else if (nameok (s,t)) {
1044 	    if (newsproxypattern (s,t,tmp,LONGT)) {
1045 	      proxylist = T;
1046 	      mail_list (NIL,"",tmp);
1047 	      proxylist = NIL;
1048 	    }
1049 	    else mail_list (NIL,s,t);
1050 	  }
1051 	  if (stream)		/* allow untagged EXPUNGE */
1052 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1053 	}
1054 				/* scan mailboxes */
1055 	else if (!strcmp (cmd,"SCAN")) {
1056 				/* get arguments */
1057 	  if (!((s = snarf (&arg)) && (t = snarf_list (&arg)) &&
1058 		(u = snarf (&arg)))) response = misarg;
1059 	  else if (arg) response = badarg;
1060 				/* make sure anonymous can't do bad things */
1061 	  else if (nameok (s,t)) {
1062 	    if (newsproxypattern (s,t,tmp,NIL))
1063 	      mm_log ("SCAN not permitted for news",ERROR);
1064 	    else mail_scan (NIL,s,t,u);
1065 	  }
1066 	  if (stream)		/* allow untagged EXPUNGE */
1067 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1068 	}
1069 				/* list subscribed mailboxes */
1070 	else if (!strcmp (cmd,"LSUB") || !strcmp (cmd,"RLSUB")) {
1071 				/* get reference and mailbox argument */
1072 	  if (!((s = snarf (&arg)) && (t = snarf_list (&arg))))
1073 	    response = misarg;
1074 	  else if (arg) response = badarg;
1075 				/* make sure anonymous can't do bad things */
1076 	  else if (nameok (s,t)) {
1077 	    if (newsproxypattern (s,t,tmp,NIL)) newsrc_lsub (NIL,tmp);
1078 	    else mail_lsub (NIL,s,t);
1079 	  }
1080 	  if (stream)		/* allow untagged EXPUNGE */
1081 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1082 	}
1083 
1084 				/* find mailboxes */
1085 	else if (!strcmp (cmd,"FIND")) {
1086 				/* get subcommand and true argument */
1087 	  if (!(arg && (s = strtok (arg," \015\012")) && (s == cmd + 5) &&
1088 		(cmd[4] = ' ') && ucase (s) &&
1089 		(arg = strtok (NIL,"\015\012")) && (s = snarf_list (&arg))))
1090 	    response = misarg;	/* missing required argument */
1091 	  else if (arg) response = badarg;
1092 				/* punt on single-char wildcards */
1093 	  else if (strpbrk (s,"%?")) response =
1094 	    "%.80s NO IMAP2 ? and %% wildcards not supported: %.80s\015\012";
1095 	  else if (nameok (NIL,s)) {
1096 	    finding = T;	/* note that we are FINDing */
1097 				/* dispatch based on type */
1098 	    if (!strcmp (cmd,"FIND MAILBOXES") && !anonymous)
1099 	      mail_lsub (NIL,NIL,s);
1100 	    else if (!strcmp (cmd,"FIND ALL.MAILBOXES")) {
1101 				/* convert * to % for compatible behavior */
1102 	      for (t = s; *t; t++) if (*t == '*') *t = '%';
1103 	      mail_list (NIL,NIL,s);
1104 	    }
1105 	    else response = badcmd;
1106 	  }
1107 	  if (stream)		/* allow untagged EXPUNGE */
1108 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1109 	}
1110 
1111 				/* status of mailbox */
1112 	else if (!strcmp (cmd,"STATUS")) {
1113 	  if (!((s = snarf (&arg)) && arg && (*arg++ == '(') &&
1114 		(t = strchr (arg,')')) && (t - arg) && !t[1]))
1115 	    response = misarg;
1116 	  else {
1117 	    f = NIL;		/* initially no flags */
1118 	    *t = '\0';		/* tie off flag string */
1119 				/* read flags */
1120 	    t = strtok (ucase (arg)," ");
1121 	    do {		/* parse each one; unknown generate warning */
1122 	      if (!strcmp (t,"MESSAGES")) f |= SA_MESSAGES;
1123 	      else if (!strcmp (t,"RECENT")) f |= SA_RECENT;
1124 	      else if (!strcmp (t,"UNSEEN")) f |= SA_UNSEEN;
1125 	      else if (!strcmp (t,"UIDNEXT")) f |= SA_UIDNEXT;
1126 	      else if (!strcmp (t,"UIDVALIDITY")) f |= SA_UIDVALIDITY;
1127 	      else {
1128 		PSOUT ("* NO Unknown status flag ");
1129 		PSOUT (t);
1130 		CRLF;
1131 	      }
1132 	    } while (t = strtok (NIL," "));
1133 	    ping_mailbox (uid);	/* in case the fool did STATUS on open mbx */
1134 	    PFLUSH ();		/* make sure stdout is dumped in case slave */
1135 	    if (!compare_cstring (s,"INBOX")) s = "INBOX";
1136 	    else if (!compare_cstring (s,"#MHINBOX")) s = "#MHINBOX";
1137 	    if (state == LOGOUT) response = lose;
1138 				/* get mailbox status */
1139 	    else if (lastsel && (!strcmp (s,lastsel) ||
1140 				 (stream && !strcmp (s,stream->mailbox)))) {
1141 	      unsigned long unseen;
1142 				/* snarl at cretins which do this */
1143 	      PSOUT ("* NO CLIENT BUG DETECTED: STATUS on selected mailbox: ");
1144 	      PSOUT (s);
1145 	      CRLF;
1146 	      tmp[0] = ' '; tmp[1] = '\0';
1147 	      if (f & SA_MESSAGES)
1148 		sprintf (tmp + strlen (tmp)," MESSAGES %lu",stream->nmsgs);
1149 	      if (f & SA_RECENT)
1150 		sprintf (tmp + strlen (tmp)," RECENT %lu",stream->recent);
1151 	      if (f & SA_UNSEEN) {
1152 		for (i = 1,unseen = 0; i <= stream->nmsgs; i++)
1153 		  if (!mail_elt (stream,i)->seen) unseen++;
1154 		sprintf (tmp + strlen (tmp)," UNSEEN %lu",unseen);
1155 	      }
1156 	      if (f & SA_UIDNEXT)
1157 		sprintf (tmp + strlen (tmp)," UIDNEXT %lu",stream->uid_last+1);
1158 	      if (f & SA_UIDVALIDITY)
1159 		sprintf (tmp + strlen(tmp)," UIDVALIDITY %lu",
1160 			 stream->uid_validity);
1161 	      tmp[1] = '(';
1162 	      strcat (tmp,")\015\012");
1163 	      PSOUT ("* STATUS ");
1164 	      pastring (s);
1165 	      PSOUT (tmp);
1166 	    }
1167 	    else if (isnewsproxy (s)) {
1168 	      sprintf (tmp,"{%.300s/nntp}%.300s",nntpproxy,(char *) s+6);
1169 	      if (!mail_status (NIL,tmp,f)) response = lose;
1170 	    }
1171 	    else if (!mail_status (NIL,s,f)) response = lose;
1172 	  }
1173 	  if (stream)		/* allow untagged EXPUNGE */
1174 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1175 	}
1176 
1177 				/* subscribe to mailbox */
1178 	else if (!(anonymous || strcmp (cmd,"SUBSCRIBE"))) {
1179 				/* get <mailbox> or MAILBOX <mailbox> */
1180 	  if (!(s = snarf (&arg))) response = misarg;
1181 	  else if (arg) {	/* IMAP2bis form */
1182 	    if (compare_cstring (s,"MAILBOX")) response = badarg;
1183 	    else if (!(s = snarf (&arg))) response = misarg;
1184 	    else if (arg) response = badarg;
1185 	    else mail_subscribe (NIL,s);
1186 	  }
1187 	  else if (isnewsproxy (s)) newsrc_update (NIL,s+6,':');
1188 	  else mail_subscribe (NIL,s);
1189 	  if (stream)		/* allow untagged EXPUNGE */
1190 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1191 	}
1192 				/* unsubscribe to mailbox */
1193 	else if (!(anonymous || strcmp (cmd,"UNSUBSCRIBE"))) {
1194 				/* get <mailbox> or MAILBOX <mailbox> */
1195 	  if (!(s = snarf (&arg))) response = misarg;
1196 	  else if (arg) {	/* IMAP2bis form */
1197 	    if (compare_cstring (s,"MAILBOX")) response = badarg;
1198 	    else if (!(s = snarf (&arg))) response = misarg;
1199 	    else if (arg) response = badarg;
1200 	    else if (isnewsproxy (s)) newsrc_update (NIL,s+6,'!');
1201 	    else mail_unsubscribe (NIL,s);
1202 	  }
1203 	  else mail_unsubscribe (NIL,s);
1204 	  if (stream)		/* allow untagged EXPUNGE */
1205 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1206 	}
1207 
1208 	else if (!strcmp (cmd,"NAMESPACE")) {
1209 	  if (arg) response = badarg;
1210 	  else {
1211 	    NAMESPACE **ns = (NAMESPACE **) mail_parameters(NIL,GET_NAMESPACE,
1212 							     NIL);
1213 	    NAMESPACE *n;
1214 	    PARAMETER *p;
1215 	    PSOUT ("* NAMESPACE");
1216 	    if (ns) for (i = 0; i < 3; i++) {
1217 	      if (n = ns[i]) {
1218 		PSOUT (" (");
1219 		do {
1220 		  PBOUT ('(');
1221 		  pstring (n->name);
1222 		  switch (n->delimiter) {
1223 		  case '\\':	/* quoted delimiter */
1224 		  case '"':
1225 		    PSOUT (" \"\\\\\"");
1226 		    break;
1227 		  case '\0':	/* no delimiter */
1228 		    PSOUT (" NIL");
1229 		    break;
1230 		  default:	/* unquoted delimiter */
1231 		    PSOUT (" \"");
1232 		    PBOUT (n->delimiter);
1233 		    PBOUT ('"');
1234 		    break;
1235 		  }
1236 				/* NAMESPACE extensions are hairy */
1237 		  if (p = n->param) do {
1238 		    PBOUT (' ');
1239 		    pstring (p->attribute);
1240 		    PSOUT (" (");
1241 		    do pstring (p->value);
1242 		    while (p->next && !p->next->attribute && (p = p->next));
1243 		    PBOUT (')');
1244 		  } while (p = p->next);
1245 		  PBOUT (')');
1246 		} while (n = n->next);
1247 		PBOUT (')');
1248 	      }
1249 	      else PSOUT (" NIL");
1250 	    }
1251 	    else PSOUT (" NIL NIL NIL");
1252 	    CRLF;
1253 	  }
1254 	  if (stream)		/* allow untagged EXPUNGE */
1255 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1256 	}
1257 
1258 				/* create mailbox */
1259 	else if (!(anonymous || strcmp (cmd,"CREATE"))) {
1260 	  if (!(s = snarf (&arg))) response = misarg;
1261 	  else if (arg) response = badarg;
1262 	  else mail_create (NIL,s);
1263 	  if (stream)		/* allow untagged EXPUNGE */
1264 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1265 	}
1266 				/* delete mailbox */
1267 	else if (!(anonymous || strcmp (cmd,"DELETE"))) {
1268 	  if (!(s = snarf (&arg))) response = misarg;
1269 	  else if (arg) response = badarg;
1270 	  else {		/* make sure not selected */
1271 	    if (lastsel && (!strcmp (s,lastsel) ||
1272 			    (stream && !strcmp (s,stream->mailbox))))
1273 	      mm_log ("Can not DELETE the selected mailbox",ERROR);
1274 	    else mail_delete (NIL,s);
1275 	  }
1276 	  if (stream)		/* allow untagged EXPUNGE */
1277 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1278 	}
1279 				/* rename mailbox */
1280 	else if (!(anonymous || strcmp (cmd,"RENAME"))) {
1281 	  if (!((s = snarf (&arg)) && (t = snarf (&arg)))) response = misarg;
1282 	  else if (arg) response = badarg;
1283 	  else {		/* make sure not selected */
1284 	    if (!compare_cstring (s,"INBOX")) s = "INBOX";
1285 	    else if (!compare_cstring (s,"#MHINBOX")) s = "#MHINBOX";
1286 	    if (lastsel && (!strcmp (s,lastsel) ||
1287 			    (stream && !strcmp (s,stream->mailbox))))
1288 	      mm_log ("Can not RENAME the selected mailbox",ERROR);
1289 	    else mail_rename (NIL,s,t);
1290 	  }
1291 	  if (stream)		/* allow untagged EXPUNGE */
1292 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
1293 	}
1294 
1295 				/* idle mode */
1296 	else if (!strcmp (cmd,"IDLE")) {
1297 				/* no arguments */
1298 	  if (arg) response = badarg;
1299 	  else {		/* tell client ready for argument */
1300 	    unsigned long donefake = 0;
1301 	    PSOUT ("+ Waiting for DONE\015\012");
1302 	    PFLUSH ();		/* dump output buffer */
1303 				/* inactivity countdown */
1304 	    i = ((TIMEOUT) / (IDLETIMER)) + 1;
1305 	    do {		/* main idle loop */
1306 	      if (!donefake) {	/* don't ping mailbox if faking */
1307 		mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,
1308 				 (void *) stream);
1309 		ping_mailbox (uid);
1310 				/* maybe do a checkpoint if not anonymous */
1311 		if (!anonymous && stream && (time (0) > lastcheck + CHECKTIMER)) {
1312 		  mail_check (stream);
1313 				/* cancel likely altwin from mail_check() */
1314 		  if (lsterr) fs_give ((void **) &lsterr);
1315 		  if (lstwrn) fs_give ((void **) &lstwrn);
1316 				/* remember last checkpoint */
1317 		  lastcheck = time (0);
1318 		}
1319 	      }
1320 	      if (lstwrn) {	/* have a warning? */
1321 		PSOUT ("* NO ");
1322 		PSOUT (lstwrn);
1323 		CRLF;
1324 		fs_give ((void **) &lstwrn);
1325 	      }
1326 	      if (!(i % 2)) {	/* prevent NAT timeouts */
1327 		sprintf (tmp,"* OK Timeout in %lu minutes\015\012",
1328 			 (i * IDLETIMER) / 60);
1329 		PSOUT (tmp);
1330 	      }
1331 				/* two minutes before the end... */
1332 	      if ((state == OPEN) && (i <= 2)) {
1333 		sprintf (tmp,"* %lu EXISTS\015\012* %lu RECENT\015\012",
1334 			 donefake = nmsgs + 1,recent + 1);
1335 		PSOUT (tmp);	/* prod client to wake up */
1336 	      }
1337 	      PFLUSH ();	/* dump output buffer */
1338 	    } while ((state != LOGOUT) && !INWAIT (IDLETIMER) && --i);
1339 
1340 				/* time to exit idle loop */
1341 	    if (state != LOGOUT) {
1342 	      if (i) {		/* still have time left? */
1343 				/* yes, read expected DONE */
1344 		slurp (tmp,MAILTMPLEN,INPUTTIMEOUT);
1345 		if (((tmp[0] != 'D') && (tmp[0] != 'd')) ||
1346 		    ((tmp[1] != 'O') && (tmp[1] != 'o')) ||
1347 		    ((tmp[2] != 'N') && (tmp[2] != 'n')) ||
1348 		    ((tmp[3] != 'E') && (tmp[3] != 'e')) ||
1349 		    (((tmp[4] != '\015') || (tmp[5] != '\012')) &&
1350 		     (tmp[4] != '\012')))
1351 		  response = "%.80s BAD Bogus IDLE continuation\015\012";
1352 		if (donefake) {	/* if faking at the end */
1353 				/* send EXPUNGE (should be just 1) */
1354 		  while (donefake > nmsgs) {
1355 		    sprintf (tmp,"* %lu EXPUNGE\015\012",donefake--);
1356 		    PSOUT (tmp);
1357 		  }
1358 		  sprintf (tmp,"* %lu EXISTS\015\012* %lu RECENT\015\012",
1359 			   nmsgs,recent);
1360 		  PSOUT (tmp);
1361 		}
1362 	      }
1363 	      else clkint ();	/* otherwise do autologout action */
1364 	    }
1365 	  }
1366 	}
1367 	else response = badcmd;
1368 	break;
1369       default:
1370         response = "%.80s BAD Unknown state for %.80s command\015\012";
1371 	break;
1372       }
1373 
1374       while (litplus.ok) {	/* any unread LITERAL+? */
1375 	litplus.ok = NIL;	/* yes, cancel it now */
1376 	clearerr (stdin);	/* clear stdin errors */
1377 	status = "discarding unread literal";
1378 				/* read literal and discard it */
1379 	while (i = (litplus.size > MAILTMPLEN) ? MAILTMPLEN : litplus.size) {
1380 	  if (state == LOGOUT) litplus.size = 0;
1381 	  else {
1382 	    settimeout (INPUTTIMEOUT);
1383 	    if (PSINR (tmp,i)) litplus.size -= i;
1384 	    else {
1385 	      ioerror (stdin,status);
1386 	      litplus.size = 0;	/* in case it continues */
1387 	    }
1388 	  }
1389 	}
1390 	settimeout (0);		/* stop timeout */
1391 				/* get new command tail */
1392 	slurp (tmp,MAILTMPLEN,INPUTTIMEOUT);
1393 				/* locate end of line */
1394 	if (t = strchr (tmp,'\012')) {
1395 				/* back over CR */
1396 	  if ((t > tmp) && (t[-1] == '\015')) --t;
1397 	  *t = NIL;		/* tie off CRLF */
1398 				/* possible LITERAL+? */
1399 	  if (((i = strlen (tmp)) > 3) && (tmp[i - 1] == '}') &&
1400 	      (tmp[i - 2] == '+') && isdigit (tmp[i - 3])) {
1401 				/* back over possible count */
1402 	    for (i -= 4; i && isdigit (tmp[i]); i--);
1403 	    if (tmp[i] == '{') {	/* found a literal? */
1404 	      litplus.ok = T;	/* yes, note LITERAL+ in effect, set size */
1405 	      litplus.size = strtoul (tmp + i + 1,NIL,10);
1406 	    }
1407 	  }
1408 	}
1409 	else flush ();		/* overlong line after LITERAL+, punt */
1410       }
1411       ping_mailbox (uid);	/* update mailbox status before response */
1412       if (lstwrn && lsterr) {	/* output most recent warning */
1413 	PSOUT ("* NO ");
1414 	PSOUT (lstwrn);
1415 	CRLF;
1416 	fs_give ((void **) &lstwrn);
1417       }
1418 
1419       if (response == logwin) {	/* authentication win message */
1420 	sprintf (tmp,response,lstref ? "*" : tag);
1421 	PSOUT (tmp);		/* start response */
1422 	pcapability (1);	/* print logged-in capabilities */
1423 	PSOUT ("] User ");
1424 	PSOUT (user);
1425 	PSOUT (" authenticated\015\012");
1426 	if (lstref) {
1427 	  sprintf (tmp,response,tag);
1428 	  PSOUT (tmp);		/* start response */
1429 	  PSOUT ("[REFERRAL ");
1430 	  PSOUT (lstref);
1431 	  PSOUT ("] ");
1432 	  PSOUT (lasterror ());
1433 	  CRLF;
1434 	}
1435       }
1436       else if ((response == win) || (response == lose)) {
1437 	sprintf (tmp,response,tag);
1438 	PSOUT (tmp);
1439 	if (cauidvalidity) {	/* COPYUID/APPENDUID response? */
1440 	  sprintf (tmp,"[%.80sUID %lu ",(char *)
1441 		   ((s = strchr (cmd,' ')) ? s+1 : cmd),cauidvalidity);
1442 	  PSOUT (tmp);
1443 	  cauidvalidity = 0;	/* cancel response for future */
1444 	  if (csset) {
1445 	    pset (&csset);
1446 	    PBOUT (' ');
1447 	  }
1448 	  pset (&caset);
1449 	  PSOUT ("] ");
1450 	}
1451 	else if (lstref) {	/* have a referral? */
1452 	  PSOUT ("[REFERRAL ");
1453 	  PSOUT (lstref);
1454 	  PSOUT ("] ");
1455 	}
1456 	if (lsterr || lstwrn) PSOUT (lasterror ());
1457 	else {
1458 	  PSOUT (cmd);
1459 	  PSOUT ((response == win) ? " completed" : "failed");
1460 	}
1461 	CRLF;
1462       }
1463       else {			/* normal response */
1464 	if ((response == rowin) || (response == rwwin)) {
1465 	  if (lstwrn) {		/* output most recent warning */
1466 	    PSOUT ("* NO ");
1467 	    PSOUT (lstwrn);
1468 	    CRLF;
1469 	    fs_give ((void **) &lstwrn);
1470 	  }
1471 	}
1472 	sprintf (tmp,response,tag,cmd,lasterror ());
1473 	PSOUT (tmp);		/* output response */
1474       }
1475     }
1476     PFLUSH ();			/* make sure output blatted */
1477 
1478     if (autologouttime) {	/* have an autologout in effect? */
1479 				/* cancel if no longer waiting for login */
1480       if (state != LOGIN) autologouttime = 0;
1481 				/* took too long to login */
1482       else if (autologouttime < time (0)) {
1483 	logout = goodbye = "Autologout";
1484 	stream = NIL;
1485 	state = LOGOUT;		/* sayonara */
1486       }
1487     }
1488   }
1489   if (goodbye && !quell_events){/* have a goodbye message? */
1490     PSOUT ("* BYE ");		/* utter it */
1491     PSOUT (goodbye);
1492     CRLF;
1493     PFLUSH ();			/* make sure blatted */
1494   }
1495   syslog (LOG_INFO,"%s user=%.80s host=%.80s",logout,
1496 	  user ? (char *) user : "???",tcp_clienthost ());
1497 				/* do logout hook if needed */
1498   if (lgoh = (logouthook_t) mail_parameters (NIL,GET_LOGOUTHOOK,NIL))
1499     (*lgoh) (mail_parameters (NIL,GET_LOGOUTDATA,NIL));
1500   _exit (ret);			/* all done */
1501   return ret;			/* stupid compilers */
1502 }
1503 
1504 /* Ping mailbox during each cycle.  Also check alerts
1505  * Accepts: last command was UID flag
1506  */
1507 
ping_mailbox(unsigned long uid)1508 void ping_mailbox (unsigned long uid)
1509 {
1510   unsigned long i;
1511   char tmp[MAILTMPLEN];
1512   if (state == OPEN) {
1513     if (!mail_ping (stream)) {	/* make sure stream still alive */
1514       PSOUT ("* BYE ");
1515       PSOUT (mylocalhost ());
1516       PSOUT (" Fatal mailbox error: ");
1517       PSOUT (lasterror ());
1518       CRLF;
1519       stream = NIL;		/* don't try to clean up stream */
1520       state = LOGOUT;		/* go away */
1521       syslog (LOG_INFO,
1522 	      "Fatal mailbox error user=%.80s host=%.80s mbx=%.80s: %.80s",
1523 	      user ? (char *) user : "???",tcp_clienthost (),
1524 	      (stream && stream->mailbox) ? stream->mailbox : "???",
1525 	      lasterror ());
1526       return;
1527     }
1528 				/* change in number of messages? */
1529     if (existsquelled || (nmsgs != stream->nmsgs)) {
1530       PSOUT ("* ");
1531       pnum (nmsgs = stream->nmsgs);
1532       PSOUT (" EXISTS\015\012");
1533     }
1534 				/* change in recent messages? */
1535     if (existsquelled || (recent != stream->recent)) {
1536       PSOUT ("* ");
1537       pnum (recent = stream->recent);
1538       PSOUT (" RECENT\015\012");
1539     }
1540     existsquelled = NIL;	/* don't do this until asked again */
1541     if (stream->uid_validity && (stream->uid_validity != uidvalidity)) {
1542       PSOUT ("* OK [UIDVALIDITY ");
1543       pnum (stream->uid_validity);
1544       PSOUT ("] UID validity status\015\012* OK [UIDNEXT ");
1545       pnum (stream->uid_last + 1);
1546       PSOUT ("] Predicted next UID\015\012");
1547       if (stream->uid_nosticky) {
1548 	PSOUT ("* NO [UIDNOTSTICKY] Non-permanent unique identifiers: ");
1549 	PSOUT (stream->mailbox);
1550 	CRLF;
1551       }
1552       uidvalidity = stream->uid_validity;
1553     }
1554 
1555 				/* don't bother if driver changed */
1556     if (curdriver == stream->dtb) {
1557 				/* first report any new flags */
1558       if ((nflags < NUSERFLAGS) && stream->user_flags[nflags])
1559 	new_flags (stream);
1560       for (i = 1; i <= nmsgs; i++) if (mail_elt (stream,i)->spare2) {
1561 	PSOUT ("* ");
1562 	pnum (i);
1563 	PSOUT (" FETCH (");
1564 	fetch_flags (i,NIL);	/* output changed flags */
1565 	if (uid) {		/* need to include UIDs in response? */
1566 	  PBOUT (' ');
1567 	  fetch_uid (i,NIL);
1568 	}
1569 	PSOUT (")\015\012");
1570       }
1571     }
1572     else {			/* driver changed */
1573       new_flags (stream);	/* send mailbox flags */
1574       if (curdriver) {		/* note readonly/write if possible change */
1575 	PSOUT ("* OK [READ-");
1576 	PSOUT (stream->rdonly ? "ONLY" : "WRITE");
1577 	PSOUT ("] Mailbox status\015\012");
1578       }
1579       curdriver = stream->dtb;
1580       if (nmsgs) {		/* get flags for all messages */
1581 	sprintf (tmp,"1:%lu",nmsgs);
1582 	mail_fetch_flags (stream,tmp,NIL);
1583 				/* don't do this if newsrc already did */
1584 	if (!(curdriver->flags & DR_NEWS)) {
1585 				/* find first unseen message */
1586 	  for (i = 1; i <= nmsgs && mail_elt (stream,i)->seen; i++);
1587 	  if (i <= nmsgs) {
1588 	    PSOUT ("* OK [UNSEEN ");
1589 	    pnum (i);
1590 	    PSOUT ("] first unseen message in ");
1591 	    PSOUT (stream->mailbox);
1592 	    CRLF;
1593 	  }
1594 	}
1595       }
1596     }
1597   }
1598   if (shutdowntime && (time (0) > shutdowntime + SHUTDOWNTIMER)) {
1599     PSOUT ("* BYE Server shutting down\015\012");
1600     state = LOGOUT;
1601   }
1602 				/* don't do these stat()s every cycle */
1603   else if (time (0) > alerttime + ALERTTIMER) {
1604     struct stat sbuf;
1605 				/* have a shutdown file? */
1606     if (!stat (SHUTDOWNFILE,&sbuf)) {
1607       PSOUT ("* OK [ALERT] Server shutting down shortly\015\012");
1608       shutdowntime = time (0);
1609     }
1610     alerttime = time (0);	/* output any new alerts */
1611     sysalerttime = palert (ALERTFILE,sysalerttime);
1612     if (state != LOGIN)		/* do user alert if logged in */
1613       useralerttime = palert (mailboxfile (tmp,USERALERTFILE),useralerttime);
1614   }
1615 }
1616 
1617 /* Print an alert file
1618  * Accepts: path of alert file
1619  *	    time of last printed alert file
1620  * Returns: updated time of last printed alert file
1621  */
1622 
palert(char * file,time_t oldtime)1623 time_t palert (char *file,time_t oldtime)
1624 {
1625   FILE *alf;
1626   struct stat sbuf;
1627   int c,lc = '\012';
1628 				/* have a new alert file? */
1629   if (stat (file,&sbuf) || (sbuf.st_mtime <= oldtime) ||
1630       !(alf = fopen (file,"r"))) return oldtime;
1631 				/* yes, display it */
1632   while ((c = getc (alf)) != EOF) {
1633     if (lc == '\012') PSOUT ("* OK [ALERT] ");
1634     switch (c) {		/* output character */
1635     case '\012':		/* newline means do CRLF */
1636       CRLF;
1637     case '\015':		/* flush CRs */
1638     case '\0':			/* flush nulls */
1639       break;
1640     default:
1641       PBOUT (c);		/* output all other characters */
1642       break;
1643     }
1644     lc = c;			/* note previous character */
1645   }
1646   fclose (alf);
1647   if (lc != '\012') CRLF;	/* final terminating CRLF */
1648   return sbuf.st_mtime;		/* return updated last alert time */
1649 }
1650 
1651 /* Initialize file string structure for file stringstruct
1652  * Accepts: string structure
1653  *	    pointer to message data structure
1654  *	    size of string
1655  */
1656 
msg_string_init(STRING * s,void * data,unsigned long size)1657 void msg_string_init (STRING *s,void *data,unsigned long size)
1658 {
1659   MSGDATA *md = (MSGDATA *) data;
1660   s->data = data;		/* note stream/msgno and header length */
1661 #if 0
1662   s->size = size;		/* message size */
1663   s->curpos = s->chunk =	/* load header */
1664     mail_fetchheader_full (md->stream,md->msgno,NIL,&s->data1,
1665 			   FT_PREFETCHTEXT | FT_PEEK);
1666 #else	/* This kludge is necessary because of broken mail stores */
1667   mail_fetchtext_full (md->stream,md->msgno,&s->size,FT_PEEK);
1668   s->curpos = s->chunk =	/* load header */
1669     mail_fetchheader_full (md->stream,md->msgno,NIL,&s->data1,FT_PEEK);
1670   s->size += s->data1;		/* header + body size */
1671 #endif
1672   s->cursize = s->chunksize = s->data1;
1673   s->offset = 0;		/* offset is start of message */
1674 }
1675 
1676 
1677 /* Get next character from file stringstruct
1678  * Accepts: string structure
1679  * Returns: character, string structure chunk refreshed
1680  */
1681 
msg_string_next(STRING * s)1682 char msg_string_next (STRING *s)
1683 {
1684   char c = *s->curpos++;	/* get next byte */
1685   SETPOS (s,GETPOS (s));	/* move to next chunk */
1686   return c;			/* return the byte */
1687 }
1688 
1689 
1690 /* Set string pointer position for file stringstruct
1691  * Accepts: string structure
1692  *	    new position
1693  */
1694 
msg_string_setpos(STRING * s,unsigned long i)1695 void msg_string_setpos (STRING *s,unsigned long i)
1696 {
1697   MSGDATA *md = (MSGDATA *) s->data;
1698   if (i < s->data1) {		/* want header? */
1699     s->chunk = mail_fetchheader_full (md->stream,md->msgno,NIL,NIL,FT_PEEK);
1700     s->chunksize = s->data1;	/* header length */
1701     s->offset = 0;		/* offset is start of message */
1702   }
1703   else if (i < s->size) {	/* want body */
1704     s->chunk = mail_fetchtext_full (md->stream,md->msgno,NIL,FT_PEEK);
1705     s->chunksize = s->size - s->data1;
1706     s->offset = s->data1;	/* offset is end of header */
1707   }
1708   else {			/* off end of message */
1709     s->chunk = NIL;		/* make sure that we crack on this then */
1710     s->chunksize = 1;		/* make sure SNX cracks the right way... */
1711     s->offset = i;
1712   }
1713 				/* initial position and size */
1714   s->curpos = s->chunk + (i -= s->offset);
1715   s->cursize = s->chunksize - i;
1716 }
1717 
1718 /* Send flags for stream
1719  * Accepts: MAIL stream
1720  *	    scratch buffer
1721  */
1722 
new_flags(MAILSTREAM * stream)1723 void new_flags (MAILSTREAM *stream)
1724 {
1725   int i,c;
1726   PSOUT ("* FLAGS (");
1727   for (i = 0; i < NUSERFLAGS; i++) if (stream->user_flags[i]) {
1728     PSOUT (stream->user_flags[i]);
1729     PBOUT (' ');
1730     nflags = i + 1;
1731   }
1732   PSOUT ("\\Answered \\Flagged \\Deleted \\Draft \\Seen)\015\012* OK [PERMANENTFLAGS (");
1733   for (i = c = 0; i < NUSERFLAGS; i++)
1734     if ((stream->perm_user_flags & (1 << i)) && stream->user_flags[i])
1735       put_flag (&c,stream->user_flags[i]);
1736   if (stream->kwd_create) put_flag (&c,"\\*");
1737   if (stream->perm_answered) put_flag (&c,"\\Answered");
1738   if (stream->perm_flagged) put_flag (&c,"\\Flagged");
1739   if (stream->perm_deleted) put_flag (&c,"\\Deleted");
1740   if (stream->perm_draft) put_flag (&c,"\\Draft");
1741   if (stream->perm_seen) put_flag (&c,"\\Seen");
1742   PSOUT (")] Permanent flags\015\012");
1743 }
1744 
1745 /* Set timeout
1746  * Accepts: desired interval
1747  */
1748 
settimeout(unsigned int i)1749 void settimeout (unsigned int i)
1750 {
1751 				/* limit if not logged in */
1752   if (i) alarm ((state == LOGIN) ? LOGINTIMEOUT : i);
1753   else alarm (0);
1754 }
1755 
1756 
1757 /* Clock interrupt
1758  * Returns only if critical code in progress
1759  */
1760 
clkint(void)1761 void clkint (void)
1762 {
1763   settimeout (0);		/* disable all interrupts */
1764   server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
1765   logout = "Autologout";
1766   goodbye = "Autologout (idle for too long)";
1767   if (critical) {		/* must defer if in critical code(?) */
1768     close (0);			/* kill stdin */
1769     state = LOGOUT;		/* die as soon as we can */
1770   }
1771   else longjmp (jmpenv,1);	/* die now */
1772 }
1773 
1774 
1775 /* Kiss Of Death interrupt
1776  * Returns only if critical code in progress
1777  */
1778 
kodint(void)1779 void kodint (void)
1780 {
1781   settimeout (0);		/* disable all interrupts */
1782   server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
1783   logout = goodbye = "Killed (lost mailbox lock)";
1784   if (critical) {		/* must defer if in critical code */
1785     close (0);			/* kill stdin */
1786     state = LOGOUT;		/* die as soon as we can */
1787   }
1788   else longjmp (jmpenv,1);	/* die now */
1789 }
1790 
1791 /* Hangup interrupt
1792  * Returns only if critical code in progress
1793  */
1794 
hupint(void)1795 void hupint (void)
1796 {
1797   settimeout (0);		/* disable all interrupts */
1798   server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
1799   logout = "Hangup";
1800   goodbye = NIL;		/* other end is already gone */
1801   if (critical) {		/* must defer if in critical code */
1802     close (0);			/* kill stdin */
1803     close (1);			/* and stdout */
1804     state = LOGOUT;		/* die as soon as we can */
1805   }
1806   else longjmp (jmpenv,1);	/* die now */
1807 }
1808 
1809 
1810 /* Termination interrupt
1811  * Returns only if critical code in progress
1812  */
1813 
trmint(void)1814 void trmint (void)
1815 {
1816   settimeout (0);		/* disable all interrupts */
1817   server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
1818   logout = goodbye = "Killed (terminated)";
1819   /* Make no attempt at graceful closure since a shutdown may be in
1820    * progress, and we won't have any time to do mail_close() actions
1821    */
1822   stream = NIL;
1823   if (critical) {		/* must defer if in critical code */
1824     close (0);			/* kill stdin */
1825     close (1);			/* and stdout */
1826     state = LOGOUT;		/* die as soon as we can */
1827   }
1828   else longjmp (jmpenv,1);	/* die now */
1829 }
1830 
1831 /* The routines on this and the next page eschew the use of non-syscall libc
1832  * routines (especially stdio) for a reason.  Also, these hideous #if
1833  * condtionals need to be replaced.
1834  */
1835 
1836 #ifndef unix
1837 #define unix 0
1838 #endif
1839 
1840 
1841 /* Status request interrupt
1842  * Always returns
1843  */
1844 
staint(void)1845 void staint (void)
1846 {
1847 #if unix
1848   int fd;
1849   char *s,buf[8*MAILTMPLEN];
1850   unsigned long pid = getpid ();
1851 				/* build file name */
1852   s = nout (sout (buf,"/tmp/imapd-status."),pid,10);
1853   if (user) s = sout (sout (s,"."),user);
1854   *s = '\0';			/* tie off file name */
1855   if ((fd = open (buf,O_WRONLY | O_CREAT | O_TRUNC,0666)) >= 0) {
1856     fchmod (fd,0666);
1857     s = nout (sout (buf,"PID="),pid,10);
1858     if (user) s = sout (sout (s,", user="),user);
1859     switch (state) {
1860     case LOGIN:
1861       s = sout (s,", not logged in");
1862       break;
1863     case SELECT:
1864       s = sout (s,", logged in");
1865       break;
1866     case OPEN:
1867       s = sout (s,", mailbox open");
1868       break;
1869     case LOGOUT:
1870       s = sout (s,", logging out");
1871       break;
1872     }
1873     if (stream && stream->mailbox)
1874       s = sout (sout (s,"\nmailbox="),stream->mailbox);
1875     *s++ = '\n';
1876     if (status) {
1877       s = sout (s,status);
1878       if (cmd) s = sout (sout (s,", last command="),cmd);
1879     }
1880     else s = sout (sout (s,cmd)," in progress");
1881     *s++ = '\n';
1882     write (fd,buf,s-buf);
1883     close (fd);
1884   }
1885 #endif
1886 }
1887 
1888 /* Write string
1889  * Accepts: destination string pointer
1890  *	    string
1891  * Returns: updated string pointer
1892  */
1893 
sout(char * s,char * t)1894 char *sout (char *s,char *t)
1895 {
1896   while (*t) *s++ = *t++;
1897   return s;
1898 }
1899 
1900 
1901 /* Write number
1902  * Accepts: destination string pointer
1903  *	    number
1904  *	    base
1905  * Returns: updated string pointer
1906  */
1907 
nout(char * s,unsigned long n,unsigned long base)1908 char *nout (char *s,unsigned long n,unsigned long base)
1909 {
1910   char stack[256];
1911   char *t = stack;
1912 				/* push PID digits on stack */
1913   do *t++ = (char) (n % base) + '0';
1914   while (n /= base);
1915 				/* pop digits from stack */
1916   while (t > stack) *s++ = *--t;
1917   return s;
1918 }
1919 
1920 /* Slurp a command line
1921  * Accepts: buffer pointer
1922  *	    buffer size
1923  *	    input timeout
1924  */
1925 
slurp(char * s,int n,unsigned long timeout)1926 void slurp (char *s,int n,unsigned long timeout)
1927 {
1928   memset (s,'\0',n);		/* zap buffer */
1929   if (state != LOGOUT) {	/* get a command under timeout */
1930     settimeout (timeout);
1931     clearerr (stdin);		/* clear stdin errors */
1932     status = "reading line";
1933     if (!PSIN (s,n-1)) ioerror (stdin,status);
1934     settimeout (0);		/* make sure timeout disabled */
1935     status = NIL;
1936   }
1937 }
1938 
1939 
1940 /* Read a literal
1941  * Accepts: destination buffer (must be size+1 for trailing NUL)
1942  *	    size of buffer (must be less than 4294967295)
1943  */
1944 
inliteral(char * s,unsigned long n)1945 void inliteral (char *s,unsigned long n)
1946 {
1947   unsigned long i;
1948   if (litplus.ok) {		/* no more LITERAL+ to worry about */
1949     litplus.ok = NIL;
1950     litplus.size = 0;
1951   }
1952   else {			/* otherwise tell client ready for argument */
1953     PSOUT ("+ Ready for argument\015\012");
1954     PFLUSH ();			/* dump output buffer */
1955   }
1956   clearerr (stdin);		/* clear stdin errors */
1957   memset (s,'\0',n+1);		/* zap buffer */
1958   status = "reading literal";
1959   while (n) {			/* get data under timeout */
1960     if (state == LOGOUT) n = 0;
1961     else {
1962       settimeout (INPUTTIMEOUT);
1963       i = min (n,8192);		/* must read at least 8K within timeout */
1964       if (PSINR (s,i)) {
1965 	s += i;
1966 	n -= i;
1967       }
1968       else {
1969 	ioerror (stdin,status);
1970 	n = 0;			/* in case it continues */
1971       }
1972       settimeout (0);		/* stop timeout */
1973     }
1974   }
1975 }
1976 
1977 /* Flush until newline seen
1978  * Returns: NIL, always
1979  */
1980 
flush(void)1981 unsigned char *flush (void)
1982 {
1983   int c;
1984   if (state != LOGOUT) {
1985     settimeout (INPUTTIMEOUT);
1986     clearerr (stdin);		/* clear stdin errors */
1987     status = "flushing line";
1988     while ((c = PBIN ()) != '\012') if (c == EOF) ioerror (stdin,status);
1989     settimeout (0);		/* make sure timeout disabled */
1990   }
1991   response = "%.80s BAD Command line too long\015\012";
1992   status = NIL;
1993   return NIL;
1994 }
1995 
1996 
1997 /* Report command stream error and die
1998  * Accepts: stdin or stdout (whichever got the error)
1999  *	    reason (what caller was doing)
2000  */
2001 
ioerror(FILE * f,char * reason)2002 void ioerror (FILE *f,char *reason)
2003 {
2004   static char msg[MAILTMPLEN];
2005   char *s,*t;
2006   if (logout) {			/* say nothing if already dying */
2007     settimeout (0);		/* disable all interrupts */
2008     server_init (NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
2009 				/* write error string */
2010     for (s = ferror (f) ? strerror (errno) : "Unexpected client disconnect",
2011 	   t = logout = msg; *s; *t++ = *s++);
2012     for (s = ", while "; *s; *t++ = *s++);
2013     for (s = reason; *s; *t++ = *s++);
2014     if (critical) {		/* must defer if in critical code */
2015       close (0);		/* kill stdin */
2016       close (1);		/* and stdout */
2017       state = LOGOUT;		/* die as soon as we can */
2018     }
2019     else longjmp (jmpenv,1);	/* die now */
2020   }
2021 }
2022 
2023 /* Parse an IMAP astring
2024  * Accepts: pointer to argument text pointer
2025  *	    pointer to returned size
2026  *	    pointer to returned delimiter
2027  * Returns: argument
2028  */
2029 
parse_astring(unsigned char ** arg,unsigned long * size,unsigned char * del)2030 unsigned char *parse_astring (unsigned char **arg,unsigned long *size,
2031 			      unsigned char *del)
2032 {
2033   unsigned long i;
2034   unsigned char c,*s,*t,*v;
2035   if (!*arg) return NIL;	/* better be an argument */
2036   switch (**arg) {		/* see what the argument is */
2037   default:			/* atom */
2038     for (s = t = *arg, i = 0;
2039 	 (*t > ' ') && (*t < 0x7f) && (*t != '(') && (*t != ')') &&
2040 	 (*t != '{') && (*t != '%') && (*t != '*') && (*t != '"') &&
2041 	 (*t != '\\'); ++t,++i);
2042     if (*size = i) break;	/* got atom if non-empty */
2043   case ')': case '%': case '*': case '\\': case '\0': case ' ':
2044    return NIL;			/* empty atom is a bogon */
2045   case '"':			/* hunt for trailing quote */
2046     for (s = t = v = *arg + 1; (c = *t++) != '"'; *v++ = c) {
2047 				/* quote next character */
2048       if (c == '\\') switch (c = *t++) {
2049       case '"': case '\\': break;
2050       default: return NIL;	/* invalid quote-next */
2051       }
2052 				/* else must be a CHAR */
2053       if (!c || (c & 0x80)) return NIL;
2054     }
2055     *v = '\0';			/* tie off string */
2056     *size = v - s;		/* return size */
2057     break;
2058 
2059   case '{':			/* literal string */
2060     s = *arg + 1;		/* get size */
2061     if (!isdigit (*s)) return NIL;
2062     if ((*size = i = strtoul (s,(char **) &t,10)) > MAXCLIENTLIT) {
2063       mm_notify (NIL,"Absurdly long client literal",ERROR);
2064       syslog (LOG_INFO,"Overlong (%lu) client literal user=%.80s host=%.80s",
2065 	      i,user ? (char *) user : "???",tcp_clienthost ());
2066       return NIL;
2067     }
2068     switch (*t) {		/* validate end of literal */
2069     case '+':			/* non-blocking literal */
2070       if (*++t != '}') return NIL;
2071     case '}':
2072       if (!t[1]) break;		/* OK if end of line */
2073     default:
2074       return NIL;		/* bad literal */
2075     }
2076     if (litsp >= LITSTKLEN) {	/* make sure don't overflow stack */
2077       mm_notify (NIL,"Too many literals in command",ERROR);
2078       return NIL;
2079     }
2080 				/* get a literal buffer */
2081     inliteral (s = litstk[litsp++] = (char *) fs_get (i+1),i);
2082     				/* get new command tail */
2083     slurp (*arg = t,CMDLEN - (t - cmdbuf),INPUTTIMEOUT);
2084     if (!strchr (t,'\012')) return flush ();
2085 				/* reset strtok mechanism, tie off if done */
2086     if (!strtok (t,"\015\012")) *t = '\0';
2087 				/* possible LITERAL+? */
2088     if (((i = strlen (t)) > 3) && (t[i - 1] == '}') &&
2089 	(t[i - 2] == '+') && isdigit (t[i - 3])) {
2090 				/* back over possible count */
2091       for (i -= 4; i && isdigit (t[i]); i--);
2092       if (t[i] == '{') {	/* found a literal? */
2093 	litplus.ok = T;		/* yes, note LITERAL+ in effect, set size */
2094 	litplus.size = strtoul (t + i + 1,NIL,10);
2095       }
2096     }
2097     break;
2098   }
2099   if (*del = *t) {		/* have a delimiter? */
2100     *t++ = '\0';		/* yes, stomp on it */
2101     *arg = t;			/* update argument pointer */
2102   }
2103   else *arg = NIL;		/* no more arguments */
2104   return s;
2105 }
2106 
2107 /* Snarf a command argument (simple jacket into parse_astring())
2108  * Accepts: pointer to argument text pointer
2109  * Returns: argument
2110  */
2111 
snarf(unsigned char ** arg)2112 unsigned char *snarf (unsigned char **arg)
2113 {
2114   unsigned long i;
2115   unsigned char c;
2116   unsigned char *s = parse_astring (arg,&i,&c);
2117   return ((c == ' ') || !c) ? s : NIL;
2118 }
2119 
2120 
2121 /* Snarf a BASE64 argument for SASL-IR
2122  * Accepts: pointer to argument text pointer
2123  * Returns: argument
2124  */
2125 
snarf_base64(unsigned char ** arg)2126 unsigned char *snarf_base64 (unsigned char **arg)
2127 {
2128   unsigned char *ret = *arg;
2129   unsigned char *s = ret + 1;
2130   static char base64mask[256] = {
2131    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,
2132    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,
2133    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,
2134    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,
2135    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,
2136    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,
2137    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,
2138    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
2139   };
2140   if (*(ret = *arg) == '=');	/* easy case if zero-length argument */
2141 				/* must be at least one BASE64 char */
2142   else if (!base64mask[*ret]) return NIL;
2143   else {			/* quick and dirty */
2144     while (base64mask[*s]) s++;	/* scan until end of BASE64 */
2145     if (*s == '=') ++s;		/* allow up to two padding chars */
2146     if (*s == '=') ++s;
2147   }
2148   switch (*s) {			/* anything following the argument? */
2149   case ' ':			/* another argument */
2150     *s++ = '\0';		/* tie off previous argument */
2151     *arg = s;			/* and update argument pointer */
2152     break;
2153   case '\0':			/* end of command */
2154     *arg = NIL;
2155     break;
2156   default:			/* syntax error */
2157     return NIL;
2158   }
2159   return ret;			/* return BASE64 string */
2160 }
2161 
2162 /* Snarf a list command argument (simple jacket into parse_astring())
2163  * Accepts: pointer to argument text pointer
2164  * Returns: argument
2165  */
2166 
snarf_list(unsigned char ** arg)2167 unsigned char *snarf_list (unsigned char **arg)
2168 {
2169   unsigned long i;
2170   unsigned char c,*s,*t;
2171   if (!*arg) return NIL;	/* better be an argument */
2172   switch (**arg) {
2173   default:			/* atom and/or wildcard chars */
2174     for (s = t = *arg, i = 0;
2175 	 (*t > ' ') && (*t != '(') && (*t != ')') && (*t != '{') &&
2176 	 (*t != '"') && (*t != '\\'); ++t,++i);
2177     if (c = *t) {		/* have a delimiter? */
2178       *t++ = '\0';		/* stomp on it */
2179       *arg = t;			/* update argument pointer */
2180     }
2181     else *arg = NIL;
2182     break;
2183   case ')': case '\\': case '\0': case ' ':
2184     return NIL;			/* empty name is bogus */
2185   case '"':			/* quoted string? */
2186   case '{':			/* or literal? */
2187     s = parse_astring (arg,&i,&c);
2188     break;
2189   }
2190   return ((c == ' ') || !c) ? s : NIL;
2191 }
2192 
2193 /* Get a list of header lines
2194  * Accepts: pointer to string pointer
2195  *	    pointer to list flag
2196  * Returns: string list
2197  */
2198 
parse_stringlist(unsigned char ** s,int * list)2199 STRINGLIST *parse_stringlist (unsigned char **s,int *list)
2200 {
2201   char c = ' ',*t;
2202   unsigned long i;
2203   STRINGLIST *ret = NIL,*cur = NIL;
2204   if (*s && **s == '(') {	/* proper list? */
2205     ++*s;			/* for each item in list */
2206     while ((c == ' ') && (t = parse_astring (s,&i,&c))) {
2207 				/* get new block */
2208       if (cur) cur = cur->next = mail_newstringlist ();
2209       else cur = ret = mail_newstringlist ();
2210 				/* note text */
2211       cur->text.data = (unsigned char *) fs_get (i + 1);
2212       memcpy (cur->text.data,t,i);
2213       cur->text.size = i;		/* and size */
2214     }
2215 				/* must be end of list */
2216     if (c != ')') mail_free_stringlist (&ret);
2217   }
2218   if (t = *s) {			/* need to reload strtok() state? */
2219 				/* end of a list? */
2220     if (*list && (*t == ')') && !t[1]) *list = NIL;
2221     else {
2222       *--t = ' ';		/* patch a space back in */
2223       *--t = 'x';		/* and a hokey character before that */
2224       t = strtok (t," ");	/* reset to *s */
2225     }
2226   }
2227   return ret;
2228 }
2229 
2230 /* Get value of UID * for criteria parsing
2231  * Accepts: stream
2232  * Returns: maximum UID
2233  */
2234 
uidmax(MAILSTREAM * stream)2235 unsigned long uidmax (MAILSTREAM *stream)
2236 {
2237   return stream->nmsgs ? mail_uid (stream,stream->nmsgs) : 0xffffffff;
2238 }
2239 
2240 
2241 /* Parse search criteria
2242  * Accepts: search program to write criteria into
2243  *	    pointer to argument text pointer
2244  *	    maximum message number
2245  *	    maximum UID
2246  *	    logical nesting depth
2247  * Returns: T if success, NIL if error
2248  */
2249 
parse_criteria(SEARCHPGM * pgm,unsigned char ** arg,unsigned long maxmsg,unsigned long maxuid,unsigned long depth)2250 long parse_criteria (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg,
2251 		     unsigned long maxuid,unsigned long depth)
2252 {
2253   if (arg && *arg) {		/* must be an argument */
2254 				/* parse criteria */
2255     do if (!parse_criterion (pgm,arg,maxmsg,maxuid,depth)) return NIL;
2256 				/* as long as a space delimiter */
2257     while (**arg == ' ' && (*arg)++);
2258 				/* failed if not end of criteria */
2259     if (**arg && **arg != ')') return NIL;
2260   }
2261   return T;			/* success */
2262 }
2263 
2264 /* Parse a search criterion
2265  * Accepts: search program to write criterion into
2266  *	    pointer to argument text pointer
2267  *	    maximum message number
2268  *	    maximum UID
2269  *	    logical nesting depth
2270  * Returns: T if success, NIL if error
2271  */
2272 
parse_criterion(SEARCHPGM * pgm,unsigned char ** arg,unsigned long maxmsg,unsigned long maxuid,unsigned long depth)2273 long parse_criterion (SEARCHPGM *pgm,unsigned char **arg,unsigned long maxmsg,
2274 		      unsigned long maxuid,unsigned long depth)
2275 {
2276   unsigned long i;
2277   unsigned char c = NIL,*s,*t,*v,*tail,*del;
2278   SEARCHSET **set;
2279   SEARCHPGMLIST **not;
2280   SEARCHOR **or;
2281   SEARCHHEADER **hdr;
2282   long ret = NIL;
2283 				/* better be an argument */
2284   if ((depth > 500) || !(arg && *arg));
2285   else if (**arg == '(') {	/* list of criteria? */
2286     (*arg)++;			/* yes, parse the criteria */
2287     if (parse_criteria (pgm,arg,maxmsg,maxuid,depth+1) && **arg == ')') {
2288       (*arg)++;			/* skip closing paren */
2289       ret = T;			/* successful parse of list */
2290     }
2291   }
2292   else {			/* find end of criterion */
2293     if (!(tail = strpbrk ((s = *arg)," )"))) tail = *arg + strlen (*arg);
2294     c = *(del = tail);		/* remember the delimiter */
2295     *del = '\0';		/* tie off criterion */
2296     switch (*ucase (s)) {	/* dispatch based on character */
2297     case '*':			/* sequence */
2298     case '0': case '1': case '2': case '3': case '4':
2299     case '5': case '6': case '7': case '8': case '9':
2300       if (*(set = &pgm->msgno)){/* already a sequence? */
2301 				/* silly, but not as silly as the client! */
2302 	for (not = &pgm->not; *not; not = &(*not)->next);
2303 	*not = mail_newsearchpgmlist ();
2304 	set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->msgno;
2305       }
2306       ret = crit_set (set,&s,maxmsg) && (tail == s);
2307       break;
2308     case 'A':			/* possible ALL, ANSWERED */
2309       if (!strcmp (s+1,"LL")) ret = T;
2310       else if (!strcmp (s+1,"NSWERED")) ret = pgm->answered = T;
2311       break;
2312 
2313     case 'B':			/* possible BCC, BEFORE, BODY */
2314       if (!strcmp (s+1,"CC") && c == ' ' && *++tail)
2315 	ret = crit_string (&pgm->bcc,&tail);
2316       else if (!strcmp (s+1,"EFORE") && c == ' ' && *++tail)
2317 	ret = crit_date (&pgm->before,&tail);
2318       else if (!strcmp (s+1,"ODY") && c == ' ' && *++tail)
2319 	ret = crit_string (&pgm->body,&tail);
2320       break;
2321     case 'C':			/* possible CC */
2322       if (!strcmp (s+1,"C") && c == ' ' && *++tail)
2323 	ret = crit_string (&pgm->cc,&tail);
2324       break;
2325     case 'D':			/* possible DELETED */
2326       if (!strcmp (s+1,"ELETED")) ret = pgm->deleted = T;
2327       if (!strcmp (s+1,"RAFT")) ret = pgm->draft = T;
2328       break;
2329     case 'F':			/* possible FLAGGED, FROM */
2330       if (!strcmp (s+1,"LAGGED")) ret = pgm->flagged = T;
2331       else if (!strcmp (s+1,"ROM") && c == ' ' && *++tail)
2332 	ret = crit_string (&pgm->from,&tail);
2333       break;
2334     case 'H':			/* possible HEADER */
2335       if (!strcmp (s+1,"EADER") && c == ' ' && *(v = tail + 1) &&
2336 	  (s = parse_astring (&v,&i,&c)) && i && c == ' ' &&
2337 	  (t = parse_astring (&v,&i,&c))) {
2338 	for (hdr = &pgm->header; *hdr; hdr = &(*hdr)->next);
2339 	*hdr = mail_newsearchheader (s,t);
2340 				/* update tail, restore delimiter */
2341 	*(tail = v ? v - 1 : t + i) = c;
2342 	ret = T;		/* success */
2343       }
2344       break;
2345     case 'K':			/* possible KEYWORD */
2346       if (!strcmp (s+1,"EYWORD") && c == ' ' && *++tail)
2347 	ret = crit_string (&pgm->keyword,&tail);
2348       break;
2349     case 'L':
2350       if (!strcmp (s+1,"ARGER") && c == ' ' && *++tail)
2351 	ret = crit_number (&pgm->larger,&tail);
2352       break;
2353     case 'N':			/* possible NEW, NOT */
2354       if (!strcmp (s+1,"EW")) ret = pgm->recent = pgm->unseen = T;
2355       else if (!strcmp (s+1,"OT") && c == ' ' && *++tail) {
2356 	for (not = &pgm->not; *not; not = &(*not)->next);
2357 	*not = mail_newsearchpgmlist ();
2358 	ret = parse_criterion ((*not)->pgm,&tail,maxmsg,maxuid,depth+1);
2359       }
2360       break;
2361 
2362     case 'O':			/* possible OLD, ON */
2363       if (!strcmp (s+1,"LD")) ret = pgm->old = T;
2364       else if (!strcmp (s+1,"N") && c == ' ' && *++tail)
2365 	ret = crit_date (&pgm->on,&tail);
2366       else if (!strcmp (s+1,"R") && c == ' ') {
2367 	for (or = &pgm->or; *or; or = &(*or)->next);
2368 	*or = mail_newsearchor ();
2369 	ret = *++tail && parse_criterion((*or)->first,&tail,maxmsg,maxuid,
2370 					 depth+1) &&
2371 	  (*tail == ' ') && *++tail &&
2372 	  parse_criterion ((*or)->second,&tail,maxmsg,maxuid,depth+1);
2373       }
2374       else if (!strcmp (s+1,"LDER") && c == ' ' && *++tail)
2375 	ret = crit_number (&pgm->older,&tail);
2376       break;
2377     case 'R':			/* possible RECENT */
2378       if (!strcmp (s+1,"ECENT")) ret = pgm->recent = T;
2379       break;
2380     case 'S':			/* possible SEEN, SINCE, SUBJECT */
2381       if (!strcmp (s+1,"EEN")) ret = pgm->seen = T;
2382       else if (!strcmp (s+1,"ENTBEFORE") && c == ' ' && *++tail)
2383 	ret = crit_date (&pgm->sentbefore,&tail);
2384       else if (!strcmp (s+1,"ENTON") && c == ' ' && *++tail)
2385 	ret = crit_date (&pgm->senton,&tail);
2386       else if (!strcmp (s+1,"ENTSINCE") && c == ' ' && *++tail)
2387 	ret = crit_date (&pgm->sentsince,&tail);
2388       else if (!strcmp (s+1,"INCE") && c == ' ' && *++tail)
2389 	ret = crit_date (&pgm->since,&tail);
2390       else if (!strcmp (s+1,"MALLER") && c == ' ' && *++tail)
2391 	ret = crit_number (&pgm->smaller,&tail);
2392       else if (!strcmp (s+1,"UBJECT") && c == ' ' && *++tail)
2393 	ret = crit_string (&pgm->subject,&tail);
2394       break;
2395     case 'T':			/* possible TEXT, TO */
2396       if (!strcmp (s+1,"EXT") && c == ' ' && *++tail)
2397 	ret = crit_string (&pgm->text,&tail);
2398       else if (!strcmp (s+1,"O") && c == ' ' && *++tail)
2399 	ret = crit_string (&pgm->to,&tail);
2400       break;
2401 
2402     case 'U':			/* possible UID, UN* */
2403       if (!strcmp (s+1,"ID") && c== ' ' && *++tail) {
2404 	if (*(set = &pgm->uid)){/* already a sequence? */
2405 				/* silly, but not as silly as the client! */
2406 	  for (not = &pgm->not; *not; not = &(*not)->next);
2407 	  *not = mail_newsearchpgmlist ();
2408 	  set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->uid;
2409 	}
2410 	ret = crit_set (set,&tail,maxuid);
2411       }
2412       else if (!strcmp (s+1,"NANSWERED")) ret = pgm->unanswered = T;
2413       else if (!strcmp (s+1,"NDELETED")) ret = pgm->undeleted = T;
2414       else if (!strcmp (s+1,"NDRAFT")) ret = pgm->undraft = T;
2415       else if (!strcmp (s+1,"NFLAGGED")) ret = pgm->unflagged = T;
2416       else if (!strcmp (s+1,"NKEYWORD") && c == ' ' && *++tail)
2417 	ret = crit_string (&pgm->unkeyword,&tail);
2418       else if (!strcmp (s+1,"NSEEN")) ret = pgm->unseen = T;
2419       break;
2420     case 'Y':			/* possible YOUNGER */
2421       if (!strcmp (s+1,"OUNGER") && c == ' ' && *++tail)
2422 	ret = crit_number (&pgm->younger,&tail);
2423       break;
2424     default:			/* oh dear */
2425       break;
2426     }
2427     if (ret) {			/* only bother if success */
2428       *del = c;			/* restore delimiter */
2429       *arg = tail;		/* update argument pointer */
2430     }
2431   }
2432   return ret;			/* return more to come */
2433 }
2434 
2435 /* Parse a search date criterion
2436  * Accepts: date to write into
2437  *	    pointer to argument text pointer
2438  * Returns: T if success, NIL if error
2439  */
2440 
crit_date(unsigned short * date,unsigned char ** arg)2441 long crit_date (unsigned short *date,unsigned char **arg)
2442 {
2443   if (*date) return NIL;	/* can't double this value */
2444 				/* handle quoted form */
2445   if (**arg != '"') return crit_date_work (date,arg);
2446   (*arg)++;			/* skip past opening quote */
2447   if (!(crit_date_work (date,arg) && (**arg == '"'))) return NIL;
2448   (*arg)++;			/* skip closing quote */
2449   return T;
2450 }
2451 
2452 /* Worker routine to parse a search date criterion
2453  * Accepts: date to write into
2454  *	    pointer to argument text pointer
2455  * Returns: T if success, NIL if error
2456  */
2457 
crit_date_work(unsigned short * date,unsigned char ** arg)2458 long crit_date_work (unsigned short *date,unsigned char **arg)
2459 {
2460   int d,m,y;
2461 				/* day */
2462   if (isdigit (d = *(*arg)++) || ((d == ' ') && isdigit (**arg))) {
2463     if (d == ' ') d = 0;	/* leading space */
2464     else d -= '0';		/* first digit */
2465     if (isdigit (**arg)) {	/* if a second digit */
2466       d *= 10;			/* slide over first digit */
2467       d += *(*arg)++ - '0';	/* second digit */
2468     }
2469     if ((**arg == '-') && (y = *++(*arg))) {
2470       m = (y >= 'a' ? y - 'a' : y - 'A') * 1024;
2471       if ((y = *++(*arg))) {
2472 	m += (y >= 'a' ? y - 'a' : y - 'A') * 32;
2473 	if ((y = *++(*arg))) {
2474 	  m += (y >= 'a' ? y - 'a' : y - 'A');
2475 	  switch (m) {		/* determine the month */
2476 	  case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
2477 	  case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
2478 	  case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
2479 	  case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
2480 	  case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
2481 	  case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
2482 	  case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
2483 	  case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
2484 	  case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
2485 	  case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10;break;
2486 	  case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11;break;
2487 	  case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12;break;
2488 	  default: return NIL;
2489 	  }
2490 	  if ((*++(*arg) == '-') && isdigit (*++(*arg))) {
2491 	    y = 0;		/* init year */
2492 	    do {
2493 	      y *= 10;		/* add this number */
2494 	      y += *(*arg)++ - '0';
2495 	    }
2496 	    while (isdigit (**arg));
2497 				/* minimal validity check of date */
2498 	    if (d < 1 || d > 31 || m < 1 || m > 12 || y < 0) return NIL;
2499 				/* time began on UNIX in 1970 */
2500 	    if (y < 100) y += (y >= (BASEYEAR - 1900)) ? 1900 : 2000;
2501 				/* return value */
2502 	    *date = mail_shortdate (y - BASEYEAR,m,d);
2503 	    return T;		/* success */
2504 	  }
2505 	}
2506       }
2507     }
2508   }
2509   return NIL;			/* else error */
2510 }
2511 
2512 /* Parse a search set criterion
2513  * Accepts: set to write into
2514  *	    pointer to argument text pointer
2515  *	    maximum value permitted
2516  * Returns: T if success, NIL if error
2517  */
2518 
crit_set(SEARCHSET ** set,unsigned char ** arg,unsigned long maxima)2519 long crit_set (SEARCHSET **set,unsigned char **arg,unsigned long maxima)
2520 {
2521   unsigned long i = 0;
2522   if (*set) return NIL;		/* can't double this value */
2523   *set = mail_newsearchset ();	/* instantiate a new search set */
2524   if (**arg == '*') {		/* maxnum? */
2525     (*arg)++;			/* skip past that number */
2526     (*set)->first = maxima;
2527   }
2528   else if (crit_number (&i,arg) && i) (*set)->first = i;
2529   else return NIL;		/* bogon */
2530   switch (**arg) {		/* decide based on delimiter */
2531   case ':':			/* sequence range */
2532     i = 0;			/* reset for crit_number() */
2533     if (*++(*arg) == '*') {	/* maxnum? */
2534       (*arg)++;			/* skip past that number */
2535       (*set)->last = maxima;
2536     }
2537     else if (crit_number (&i,arg) && i) {
2538       if (i < (*set)->first) {	/* backwards range */
2539 	(*set)->last = (*set)->first;
2540 	(*set)->first = i;
2541       }
2542       else (*set)->last = i;	/* set last number */
2543     }
2544     else return NIL;		/* bogon */
2545     if (**arg != ',') break;	/* drop into comma case if comma seen */
2546   case ',':
2547     (*arg)++;			/* skip past delimiter */
2548     return crit_set (&(*set)->next,arg,maxima);
2549   default:
2550     break;
2551   }
2552   return T;			/* return success */
2553 }
2554 
2555 /* Parse a search number criterion
2556  * Accepts: number to write into
2557  *	    pointer to argument text pointer
2558  * Returns: T if success, NIL if error
2559  */
2560 
crit_number(unsigned long * number,unsigned char ** arg)2561 long crit_number (unsigned long *number,unsigned char **arg)
2562 {
2563 				/* can't double this value */
2564   if (*number || !isdigit (**arg)) return NIL;
2565   *number = 0;
2566   while (isdigit (**arg)) {	/* found a digit? */
2567     *number *= 10;		/* add a decade */
2568     *number += *(*arg)++ - '0';	/* add number */
2569   }
2570   return T;
2571 }
2572 
2573 
2574 /* Parse a search string criterion
2575  * Accepts: date to write into
2576  *	    pointer to argument text pointer
2577  * Returns: T if success, NIL if error
2578  */
2579 
crit_string(STRINGLIST ** string,unsigned char ** arg)2580 long crit_string (STRINGLIST **string,unsigned char **arg)
2581 {
2582   unsigned long i;
2583   char c;
2584   char *s = parse_astring (arg,&i,&c);
2585   if (!s) return NIL;
2586 				/* find tail of list */
2587   while (*string) string = &(*string)->next;
2588   *string = mail_newstringlist ();
2589   (*string)->text.data = (unsigned char *) fs_get (i + 1);
2590   memcpy ((*string)->text.data,s,i);
2591   (*string)->text.data[i] = '\0';
2592   (*string)->text.size = i;
2593 				/* if end of arguments, wrap it up here */
2594   if (!*arg) *arg = (char *) (*string)->text.data + i;
2595   else (*--(*arg) = c);		/* back up pointer, restore delimiter */
2596   return T;
2597 }
2598 
2599 /* Fetch message data
2600  * Accepts: string of data items to be fetched (must be writeable)
2601  *	    UID fetch flag
2602  */
2603 
2604 #define MAXFETCH 100
2605 
fetch(char * t,unsigned long uid)2606 void fetch (char *t,unsigned long uid)
2607 {
2608   fetchfn_t f[MAXFETCH +2];
2609   void *fa[MAXFETCH + 2];
2610   int k;
2611   memset ((void *) f,NIL,sizeof (f));
2612   memset ((void *) fa,NIL,sizeof (fa));
2613   fetch_work (t,uid,f,fa);	/* do the work */
2614 				/* clean up arguments */
2615   for (k = 1; f[k]; k++) if (fa[k]) (*f[k]) (0,fa[k]);
2616 }
2617 
2618 
2619 /* Fetch message data worker routine
2620  * Accepts: string of data items to be fetched (must be writeable)
2621  *	    UID fetch flag
2622  *	    function dispatch vector
2623  *	    function argument vector
2624  */
2625 
fetch_work(char * t,unsigned long uid,fetchfn_t f[],void * fa[])2626 void fetch_work (char *t,unsigned long uid,fetchfn_t f[],void *fa[])
2627 {
2628   unsigned char *s,*v;
2629   unsigned long i;
2630   unsigned long k = 0;
2631   BODY *b;
2632   int list = NIL;
2633   int parse_envs = NIL;
2634   int parse_bodies = NIL;
2635   if (uid) {			/* need to fetch UIDs? */
2636     fa[k] = NIL;		/* no argument */
2637     f[k++] = fetch_uid;		/* push a UID fetch on the stack */
2638   }
2639 
2640 				/* process macros */
2641   if (!strcmp (ucase (t),"ALL"))
2642     strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)");
2643   else if (!strcmp (t,"FULL"))
2644     strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)");
2645   else if (!strcmp (t,"FAST")) strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE)");
2646   if (list = (*t == '(')) t++;	/* skip open paren */
2647   if (s = strtok (t," ")) do {	/* parse attribute list */
2648     if (list && (i = strlen (s)) && (s[i-1] == ')')) {
2649       list = NIL;		/* done with list */
2650       s[i-1] = '\0';		/* tie off last item */
2651     }
2652     fa[k] = NIL;		/* default to no argument */
2653     if (!strcmp (s,"UID")) {	/* no-op if implicit */
2654       if (!uid) f[k++] = fetch_uid;
2655     }
2656     else if (!strcmp (s,"FLAGS")) f[k++] = fetch_flags;
2657     else if (!strcmp (s,"INTERNALDATE")) f[k++] = fetch_internaldate;
2658     else if (!strcmp (s,"RFC822.SIZE")) f[k++] = fetch_rfc822_size;
2659     else if (!strcmp (s,"ENVELOPE")) {
2660       parse_envs = T;		/* we will need to parse envelopes */
2661       f[k++] = fetch_envelope;
2662     }
2663     else if (!strcmp (s,"BODY")) {
2664       parse_envs = parse_bodies = T;
2665       f[k++] = fetch_body;
2666     }
2667     else if (!strcmp (s,"BODYSTRUCTURE")) {
2668       parse_envs = parse_bodies = T;
2669       f[k++] = fetch_bodystructure;
2670     }
2671     else if (!strcmp (s,"RFC822")) {
2672       fa[k] = s[6] ? (void *) FT_PEEK : NIL;
2673       f[k++] = fetch_rfc822;
2674     }
2675     else if (!strcmp (s,"RFC822.HEADER")) f[k++] = fetch_rfc822_header;
2676     else if (!strcmp (s,"RFC822.TEXT")) {
2677       fa[k] = s[11] ? (void *) FT_PEEK : NIL;
2678       f[k++] = fetch_rfc822_text;
2679     }
2680 
2681     else if (!strncmp (s,"BODY[",5) || !strncmp (s,"BODY.PEEK[",10) ||
2682 	     !strncmp (s,"BINARY[",7) || !strncmp (s,"BINARY.PEEK[",12) ||
2683 	     !strncmp (s,"BINARY.SIZE[",12)) {
2684       TEXTARGS *ta = (TEXTARGS *)
2685 	memset (fs_get (sizeof (TEXTARGS)),0,sizeof (TEXTARGS));
2686       if (s[1] == 'I') {	/* body or binary? */
2687 	ta->binary = FTB_BINARY;/* binary */
2688 	f[k] = fetch_body_part_binary;
2689 	if (s[6] == '.') {	/* wanted peek or size? */
2690 	  if (s[7] == 'P') ta->flags = FT_PEEK;
2691 	  else ta->binary |= FTB_SIZE;
2692 	  s += 12;		/* skip to section specifier */
2693 	}
2694 	else s += 7;		/* skip to section specifier */
2695 	if (!isdigit (*s)) {	/* make sure top-level digit */
2696 	  fs_give ((void **) &ta);
2697 	  response = badbin;
2698 	  return;
2699 	}
2700       }
2701       else {			/* body */
2702 	f[k] = fetch_body_part_contents;
2703 	if (s[4] == '.') {	/* wanted peek? */
2704 	  ta->flags = FT_PEEK;
2705 	  s += 10;		/* skip to section specifier */
2706 	}
2707 	else s += 5;		/* skip to section specifier */
2708       }
2709       if (*(v = s) != ']') {	/* non-empty section specifier? */
2710 	if (isdigit (*v)) {	/* have section specifier? */
2711 				/* need envelopes and bodies */
2712 	  parse_envs = parse_bodies = T;
2713 	  while (isdigit (*v))	/* scan to end of section specifier */
2714 	    if ((*++v == '.') && isdigit (v[1])) v++;
2715 				/* any IMAP4rev1 stuff following? */
2716 	  if ((*v == '.') && isalpha (v[1])) {
2717 	    if (ta->binary) {	/* not if binary you don't */
2718 	      fs_give ((void **) &ta);
2719 	      response = badbin;
2720 	      return;
2721 	    }
2722 	    *v++ = '\0';	/* yes, tie off section specifier */
2723 	    if (!strncmp (v,"MIME",4)) {
2724 	      v += 4;		/* found <section>.MIME */
2725 	      f[k] = fetch_body_part_mime;
2726 	    }
2727 	  }
2728 	  else if (*v != ']') {	/* better be the end if no IMAP4rev1 stuff */
2729 	    fs_give ((void **) &ta);/* clean up */
2730 	    response = "%.80s BAD Syntax error in section specifier\015\012";
2731 	    return;
2732 	  }
2733 	}
2734 
2735 	if (*v != ']') {	/* IMAP4rev1 stuff here? */
2736 	  if (!strncmp (v,"HEADER",6)) {
2737 	    *v = '\0';		/* tie off in case top level */
2738 	    v += 6;		/* found [<section>.]HEADER */
2739 	    f[k] = fetch_body_part_header;
2740 				/* partial headers wanted? */
2741 	    if (!strncmp (v,".FIELDS",7)) {
2742 	      v += 7;		/* yes */
2743 	      if (!strncmp (v,".NOT",4)) {
2744 		v += 4;		/* want to exclude named headers */
2745 		ta->flags |= FT_NOT;
2746 	      }
2747 	      if (*v || !(v = strtok (NIL,"\015\012")) ||
2748 		  !(ta->lines = parse_stringlist (&v,&list))) {
2749 		fs_give ((void **) &ta);/* clean up */
2750 		response = "%.80s BAD Syntax error in header fields\015\012";
2751 		return;
2752 	      }
2753 	    }
2754 	  }
2755 	  else if (!strncmp (v,"TEXT",4)) {
2756 	    *v = '\0';		/* tie off in case top level */
2757 	    v += 4;		/* found [<section>.]TEXT */
2758 	    f[k] = fetch_body_part_text;
2759 	  }
2760 	  else {
2761 	    fs_give ((void **) &ta);/* clean up */
2762 	    response = "%.80s BAD Unknown section text specifier\015\012";
2763 	    return;
2764 	  }
2765 	}
2766       }
2767 				/* tie off section */
2768       if (*v == ']') *v++ = '\0';
2769       else {			/* bogon */
2770 	if (ta->lines) mail_free_stringlist (&ta->lines);
2771 	fs_give ((void **) &ta);/* clean up */
2772 	response = "%.80s BAD Section specifier not terminated\015\012";
2773 	return;
2774       }
2775 
2776       if ((*v == '<') &&	/* partial specifier? */
2777 	  ((ta->binary & FTB_SIZE) ||
2778 	   !(isdigit (v[1]) && ((ta->first = strtoul (v+1,(char **) &v,10)) ||
2779 				v) &&
2780 	     (*v++ == '.') && (ta->last = strtoul (v,(char **) &v,10)) &&
2781 	     (*v++ == '>')))) {
2782 	if (ta->lines) mail_free_stringlist (&ta->lines);
2783 	fs_give ((void **) &ta);
2784 	response ="%.80s BAD Syntax error in partial text specifier\015\012";
2785 	return;
2786       }
2787       switch (*v) {		/* what's there now? */
2788       case ' ':			/* more follows */
2789 	*--v = ' ';		/* patch a space back in */
2790 	*--v = 'x';		/* and a hokey character before that */
2791 	strtok (v," ");		/* reset strtok mechanism */
2792 	break;
2793       case '\0':		/* none */
2794 	break;
2795       case ')':			/* end of list */
2796 	if (list && !v[1]) {	/* make sure of that */
2797 	  list = NIL;
2798 	  strtok (v," ");	/* reset strtok mechanism */
2799 	  break;		/* all done */
2800 	}
2801 				/* otherwise it's a bogon, drop in */
2802       default:			/* bogon */
2803 	if (ta->lines) mail_free_stringlist (&ta->lines);
2804 	fs_give ((void **) &ta);
2805 	response = "%.80s BAD Syntax error after section specifier\015\012";
2806 	return;
2807       }
2808 				/* make copy of section specifier */
2809       if (s && *s) ta->section = cpystr (s);
2810       fa[k++] = (void *) ta;	/* set argument */
2811     }
2812     else {			/* unknown attribute */
2813       response = badatt;
2814       return;
2815     }
2816   } while ((s = strtok (NIL," ")) && (k < MAXFETCH) && list);
2817   else {
2818     response = misarg;		/* missing attribute list */
2819     return;
2820   }
2821 
2822   if (s) {			/* too many attributes? */
2823     response = "%.80s BAD Excessively complex FETCH attribute list\015\012";
2824     return;
2825   }
2826   if (list) {			/* too many attributes? */
2827     response = "%.80s BAD Unterminated FETCH attribute list\015\012";
2828     return;
2829   }
2830   f[k] = NIL;			/* tie off attribute list */
2831 				/* c-client clobbers sequence, use spare */
2832   for (i = 1; i <= nmsgs; i++)
2833     mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence;
2834 				/* for each requested message */
2835   for (i = 1; (i <= nmsgs) && (response != loseunknowncte); i++) {
2836 				/* kill if dying */
2837     if (state == LOGOUT) longjmp (jmpenv,1);
2838     if (mail_elt (stream,i)->spare) {
2839 				/* parse envelope, set body, do warnings */
2840       if (parse_envs) mail_fetchstructure (stream,i,parse_bodies ? &b : NIL);
2841       quell_events = T;		/* can't do any events now */
2842       PSOUT ("* ");		/* leader */
2843       pnum (i);
2844       PSOUT (" FETCH (");
2845       (*f[0]) (i,fa[0]);	/* do first attribute */
2846 				/* for each subsequent attribute */
2847       for (k = 1; f[k] && (response != loseunknowncte); k++) {
2848 	PBOUT (' ');		/* delimit with space */
2849 	(*f[k]) (i,fa[k]);	/* do that attribute */
2850       }
2851       PSOUT (")\015\012");	/* trailer */
2852       quell_events = NIL;	/* events alright now */
2853     }
2854   }
2855 }
2856 
2857 /* Fetch message body structure (extensible)
2858  * Accepts: message number
2859  *	    extra argument
2860  */
2861 
fetch_bodystructure(unsigned long i,void * args)2862 void fetch_bodystructure (unsigned long i,void *args)
2863 {
2864   BODY *body;
2865   mail_fetchstructure (stream,i,&body);
2866   PSOUT ("BODYSTRUCTURE ");
2867   pbodystructure (body);	/* output body */
2868 }
2869 
2870 
2871 /* Fetch message body structure (non-extensible)
2872  * Accepts: message number
2873  *	    extra argument
2874  */
2875 
2876 
fetch_body(unsigned long i,void * args)2877 void fetch_body (unsigned long i,void *args)
2878 {
2879   BODY *body;
2880   mail_fetchstructure (stream,i,&body);
2881   PSOUT ("BODY ");		/* output attribute */
2882   pbody (body);			/* output body */
2883 }
2884 
2885 /* Fetch body part MIME header
2886  * Accepts: message number
2887  *	    extra argument
2888  */
2889 
fetch_body_part_mime(unsigned long i,void * args)2890 void fetch_body_part_mime (unsigned long i,void *args)
2891 {
2892   TEXTARGS *ta = (TEXTARGS *) args;
2893   if (i) {			/* do work? */
2894     SIZEDTEXT st;
2895     unsigned long uid = mail_uid (stream,i);
2896     char *tmp = (char *) fs_get (100 + strlen (ta->section));
2897     sprintf (tmp,"BODY[%s.MIME]",ta->section);
2898 				/* try to use remembered text */
2899     if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
2900     else {			/* get data */
2901       st.data = (unsigned char *)
2902 	mail_fetch_mime (stream,i,ta->section,&st.size,ta->flags);
2903       if (ta->first || ta->last) remember (uid,tmp,&st);
2904     }
2905     pbodypartstring (i,tmp,&st,NIL,ta);
2906     fs_give ((void **) &tmp);
2907   }
2908   else {			/* clean up the arguments */
2909     fs_give ((void **) &ta->section);
2910     fs_give ((void **) &args);
2911   }
2912 }
2913 
2914 
2915 /* Fetch body part contents
2916  * Accepts: message number
2917  *	    extra argument
2918  */
2919 
fetch_body_part_contents(unsigned long i,void * args)2920 void fetch_body_part_contents (unsigned long i,void *args)
2921 {
2922   TEXTARGS *ta = (TEXTARGS *) args;
2923   if (i) {			/* do work? */
2924     SIZEDTEXT st;
2925     char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0));
2926     unsigned long uid = mail_uid (stream,i);
2927     sprintf (tmp,"BODY[%s]",ta->section ? ta->section : "");
2928 				/* try to use remembered text */
2929     if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
2930 				/* get data */
2931     else if ((st.data = (unsigned char *)
2932 	      mail_fetch_body (stream,i,ta->section,&st.size,
2933 			       ta->flags | FT_RETURNSTRINGSTRUCT)) &&
2934 	     (ta->first || ta->last)) remember (uid,tmp,&st);
2935     pbodypartstring (i,tmp,&st,&stream->private.string,ta);
2936     fs_give ((void **) &tmp);
2937   }
2938   else {			/* clean up the arguments */
2939     if (ta->section) fs_give ((void **) &ta->section);
2940     fs_give ((void **) &args);
2941   }
2942 }
2943 
2944 /* Fetch body part binary
2945  * Accepts: message number
2946  *	    extra argument
2947  * Someday fix this to use stringstruct instead of memory
2948  */
2949 
fetch_body_part_binary(unsigned long i,void * args)2950 void fetch_body_part_binary (unsigned long i,void *args)
2951 {
2952   TEXTARGS *ta = (TEXTARGS *) args;
2953   if (i) {			/* do work? */
2954     SIZEDTEXT st,cst;
2955     BODY *body = mail_body (stream,i,ta->section);
2956     char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0));
2957     unsigned long uid = mail_uid (stream,i);
2958 				/* try to use remembered text */
2959     if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
2960     else {			/* get data */
2961       st.data = (unsigned char *)
2962 	mail_fetch_body (stream,i,ta->section,&st.size,ta->flags);
2963       if (ta->first || ta->last) remember (uid,tmp,&st);
2964     }
2965 				/* what encoding was used? */
2966     if (body) switch (body->encoding) {
2967     case ENCBASE64:
2968       if (cst.data = rfc822_base64 (st.data,st.size,&cst.size)) break;
2969       fetch_uid (i,NIL);	/* wrote a space, so must do something */
2970       if (lsterr) fs_give ((void **) &lsterr);
2971       lsterr = cpystr ("Undecodable BASE64 contents");
2972       response = loseunknowncte;
2973       fs_give ((void **) &tmp);
2974       return;
2975     case ENCQUOTEDPRINTABLE:
2976       if (cst.data = rfc822_qprint (st.data,st.size,&cst.size)) break;
2977       fetch_uid (i,NIL);	/* wrote a space, so must do something */
2978       if (lsterr) fs_give ((void **) &lsterr);
2979       lsterr = cpystr ("Undecodable QUOTED-PRINTABLE contents");
2980       response = loseunknowncte;
2981       fs_give ((void **) &tmp);
2982       return;
2983     case ENC7BIT:		/* no need to convert any of these */
2984     case ENC8BIT:
2985     case ENCBINARY:
2986       cst.data = NIL;		/* no converted data to free */
2987       break;
2988     default:			/* unknown encoding, oops */
2989       fetch_uid (i,NIL);	/* wrote a space, so must do something */
2990       if (lsterr) fs_give ((void **) &lsterr);
2991       lsterr = cpystr ("Unknown Content-Transfer-Encoding");
2992       response = loseunknowncte;
2993       fs_give ((void **) &tmp);
2994       return;
2995     }
2996     else {
2997       if (lsterr) fs_give ((void **) &lsterr);
2998       lsterr = cpystr ("Invalid body part");
2999       response = loseunknowncte;
3000       fs_give ((void **) &tmp);
3001       return;
3002     }
3003 
3004 				/* use decoded version if exists */
3005     if (cst.data) memcpy ((void *) &st,(void *) &cst,sizeof (SIZEDTEXT));
3006     if (ta->binary & FTB_SIZE) {/* just want size? */
3007       sprintf (tmp,"BINARY.SIZE[%s] %lu",ta->section ? ta->section : "",
3008 	       st.size);
3009       PSOUT (tmp);
3010     }
3011     else {			/* no, blat binary data */
3012       int f = mail_elt (stream,i)->seen;
3013       if (st.data) {		/* only if have useful data */
3014 				/* partial specifier */
3015 	if (ta->first || ta->last)
3016 	  sprintf (tmp,"BINARY[%s]<%lu> ",
3017 		   ta->section ? ta->section : "",ta->first);
3018 	else sprintf (tmp,"BINARY[%s] ",ta->section ? ta->section : "");
3019   				/* in case first byte beyond end of text */
3020 	if (st.size <= ta->first) st.size = ta->first = 0;
3021 	else {			/* offset and truncate */
3022 	  st.data += ta->first;	/* move to desired position */
3023 	  st.size -= ta->first;	/* reduced size */
3024 	  if (ta->last && (st.size > ta->last)) st.size = ta->last;
3025 	}
3026 	if (st.size) sprintf (tmp + strlen (tmp),"{%lu}\015\012",st.size);
3027 	else strcat (tmp,"\"\"");
3028 	PSOUT (tmp);		/* write binary output */
3029 	if (st.size && (PSOUTR (&st) == EOF)) ioerror(stdout,"writing binary");
3030       }
3031       else {
3032 	sprintf (tmp,"BINARY[%s] NIL",ta->section ? ta->section : "");
3033 	PSOUT (tmp);
3034       }
3035       changed_flags (i,f);	/* write changed flags */
3036     }
3037 				/* free converted data */
3038     if (cst.data) fs_give ((void **) &cst.data);
3039     fs_give ((void **) &tmp);	/* and temporary string */
3040   }
3041   else {			/* clean up the arguments */
3042     if (ta->section) fs_give ((void **) &ta->section);
3043     fs_give ((void **) &args);
3044   }
3045 }
3046 
3047 /* Fetch MESSAGE/RFC822 body part header
3048  * Accepts: message number
3049  *	    extra argument
3050  */
3051 
fetch_body_part_header(unsigned long i,void * args)3052 void fetch_body_part_header (unsigned long i,void *args)
3053 {
3054   TEXTARGS *ta = (TEXTARGS *) args;
3055   unsigned long len = 100 + (ta->section ? strlen (ta->section) : 0);
3056   STRINGLIST *s;
3057   for (s = ta->lines; s; s = s->next) len += s->text.size + 1;
3058   if (i) {			/* do work? */
3059     SIZEDTEXT st;
3060     char *tmp = (char *) fs_get (len);
3061     PSOUT ("BODY[");
3062 				/* output attribute */
3063     if (ta->section && *ta->section) {
3064       PSOUT (ta->section);
3065       PBOUT ('.');
3066     }
3067     PSOUT ("HEADER");
3068     if (ta->lines) {
3069       PSOUT ((ta->flags & FT_NOT) ? ".FIELDS.NOT " : ".FIELDS ");
3070       pastringlist (ta->lines);
3071     }
3072     strcpy (tmp,"]");		/* close section specifier */
3073     st.data = (unsigned char *)	/* get data (no hope in using remember here) */
3074       mail_fetch_header (stream,i,ta->section,ta->lines,&st.size,ta->flags);
3075     pbodypartstring (i,tmp,&st,NIL,ta);
3076     fs_give ((void **) &tmp);
3077   }
3078   else {			/* clean up the arguments */
3079     if (ta->lines) mail_free_stringlist (&ta->lines);
3080     if (ta->section) fs_give ((void **) &ta->section);
3081     fs_give ((void **) &args);
3082   }
3083 }
3084 
3085 /* Fetch MESSAGE/RFC822 body part text
3086  * Accepts: message number
3087  *	    extra argument
3088  */
3089 
fetch_body_part_text(unsigned long i,void * args)3090 void fetch_body_part_text (unsigned long i,void *args)
3091 {
3092   TEXTARGS *ta = (TEXTARGS *) args;
3093   if (i) {			/* do work? */
3094     SIZEDTEXT st;
3095     char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0));
3096     unsigned long uid = mail_uid (stream,i);
3097 				/* output attribute */
3098     if (ta->section && *ta->section) sprintf (tmp,"BODY[%s.TEXT]",ta->section);
3099     else strcpy (tmp,"BODY[TEXT]");
3100 				/* try to use remembered text */
3101     if (lastuid && (uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
3102 				/* get data */
3103     else if ((st.data = (unsigned char *)
3104 	      mail_fetch_text (stream,i,ta->section,&st.size,
3105 			       ta->flags | FT_RETURNSTRINGSTRUCT)) &&
3106 	     (ta->first || ta->last)) remember (uid,tmp,&st);
3107     pbodypartstring (i,tmp,&st,&stream->private.string,ta);
3108     fs_give ((void **) &tmp);
3109   }
3110   else {			/* clean up the arguments */
3111     if (ta->section) fs_give ((void **) &ta->section);
3112     fs_give ((void **) &args);
3113   }
3114 }
3115 
3116 
3117 /* Remember body part text for subsequent partial fetching
3118  * Accepts: message UID
3119  *	    body part id
3120  *	    text
3121  *	    string
3122  */
3123 
remember(unsigned long uid,char * id,SIZEDTEXT * st)3124 void remember (unsigned long uid,char *id,SIZEDTEXT *st)
3125 {
3126   lastuid = uid;		/* remember UID */
3127   if (lastid) fs_give ((void **) &lastid);
3128   lastid = cpystr (id);		/* remember body part id */
3129   if (lastst.data) fs_give ((void **) &lastst.data);
3130 				/* remember text */
3131   lastst.data = (unsigned char *)
3132     memcpy (fs_get (st->size + 1),st->data,st->size);
3133   lastst.size = st->size;
3134 }
3135 
3136 
3137 /* Fetch envelope
3138  * Accepts: message number
3139  *	    extra argument
3140  */
3141 
fetch_envelope(unsigned long i,void * args)3142 void fetch_envelope (unsigned long i,void *args)
3143 {
3144   ENVELOPE *env = mail_fetchenvelope (stream,i);
3145   PSOUT ("ENVELOPE ");		/* output attribute */
3146   penv (env);			/* output envelope */
3147 }
3148 
3149 /* Fetch flags
3150  * Accepts: message number
3151  *	    extra argument
3152  */
3153 
fetch_flags(unsigned long i,void * args)3154 void fetch_flags (unsigned long i,void *args)
3155 {
3156   unsigned long u;
3157   char *t,tmp[MAILTMPLEN];
3158   int c = NIL;
3159   MESSAGECACHE *elt = mail_elt (stream,i);
3160   if (!elt->valid) {		/* have valid flags yet? */
3161     sprintf (tmp,"%lu",i);
3162     mail_fetch_flags (stream,tmp,NIL);
3163   }
3164   PSOUT ("FLAGS (");		/* output attribute */
3165 				/* output system flags */
3166   if (elt->recent) put_flag (&c,"\\Recent");
3167   if (elt->seen) put_flag (&c,"\\Seen");
3168   if (elt->deleted) put_flag (&c,"\\Deleted");
3169   if (elt->flagged) put_flag (&c,"\\Flagged");
3170   if (elt->answered) put_flag (&c,"\\Answered");
3171   if (elt->draft) put_flag (&c,"\\Draft");
3172   if (u = elt->user_flags) do	/* any user flags? */
3173     if (t = stream->user_flags[find_rightmost_bit (&u)]) put_flag (&c,t);
3174   while (u);			/* until no more user flags */
3175   PBOUT (')');			/* end of flags */
3176   elt->spare2 = NIL;		/* we've sent the update */
3177 }
3178 
3179 
3180 /* Output a flag
3181  * Accepts: pointer to current delimiter character
3182  *	    flag to output
3183  * Changes delimiter character to space
3184  */
3185 
put_flag(int * c,char * s)3186 void put_flag (int *c,char *s)
3187 {
3188   if (*c) PBOUT (*c);		/* put delimiter */
3189   PSOUT (s);			/* dump flag */
3190   *c = ' ';			/* change delimiter if necessary */
3191 }
3192 
3193 
3194 /* Output flags if was unseen
3195  * Accepts: message number
3196  *	    prior value of Seen flag
3197  */
3198 
changed_flags(unsigned long i,int f)3199 void changed_flags (unsigned long i,int f)
3200 {
3201 				/* was unseen, now seen? */
3202   if (!f && mail_elt (stream,i)->seen) {
3203     PBOUT (' ');		/* yes, delimit with space */
3204     fetch_flags (i,NIL);	/* output flags */
3205   }
3206 }
3207 
3208 /* Fetch message internal date
3209  * Accepts: message number
3210  *	    extra argument
3211  */
3212 
fetch_internaldate(unsigned long i,void * args)3213 void fetch_internaldate (unsigned long i,void *args)
3214 {
3215   char tmp[MAILTMPLEN];
3216   MESSAGECACHE *elt = mail_elt (stream,i);
3217   if (!elt->day) {		/* have internal date yet? */
3218     sprintf (tmp,"%lu",i);
3219     mail_fetch_fast (stream,tmp,NIL);
3220   }
3221   PSOUT ("INTERNALDATE \"");
3222   PSOUT (mail_date (tmp,elt));
3223   PBOUT ('"');
3224 }
3225 
3226 
3227 /* Fetch unique identifier
3228  * Accepts: message number
3229  *	    extra argument
3230  */
3231 
fetch_uid(unsigned long i,void * args)3232 void fetch_uid (unsigned long i,void *args)
3233 {
3234   PSOUT ("UID ");
3235   pnum (mail_uid (stream,i));
3236 }
3237 
3238 /* Fetch complete RFC-822 format message
3239  * Accepts: message number
3240  *	    extra argument
3241  */
3242 
fetch_rfc822(unsigned long i,void * args)3243 void fetch_rfc822 (unsigned long i,void *args)
3244 {
3245   if (i) {			/* do work? */
3246     int f = mail_elt (stream,i)->seen;
3247 #if 0
3248     SIZEDTEXT st;
3249     st.data = (unsigned char *)
3250       mail_fetch_message (stream,i,&st.size,(long) args);
3251     pbodypartstring (i,"RFC822",&st,NIL,NIL);
3252 #else
3253     /* Yes, this version is bletcherous, but mail_fetch_message() requires
3254        too much memory */
3255     SIZEDTEXT txt,hdr;
3256     char *s = mail_fetch_header (stream,i,NIL,NIL,&hdr.size,FT_PEEK);
3257     hdr.data = (unsigned char *) memcpy (fs_get (hdr.size),s,hdr.size);
3258     txt.data = (unsigned char *)
3259       mail_fetch_text (stream,i,NIL,&txt.size,
3260 		       ((long) args) | FT_RETURNSTRINGSTRUCT);
3261     PSOUT ("RFC822 {");
3262     pnum (hdr.size + txt.size);
3263     PSOUT ("}\015\012");
3264     ptext (&hdr,NIL);
3265     ptext (&txt,&stream->private.string);
3266     fs_give ((void **) &hdr.data);
3267 #endif
3268     changed_flags (i,f);	/* output changed flags */
3269   }
3270 }
3271 
3272 
3273 /* Fetch RFC-822 header
3274  * Accepts: message number
3275  *	    extra argument
3276  */
3277 
fetch_rfc822_header(unsigned long i,void * args)3278 void fetch_rfc822_header (unsigned long i,void *args)
3279 {
3280   SIZEDTEXT st;
3281   st.data = (unsigned char *)
3282     mail_fetch_header (stream,i,NIL,NIL,&st.size,FT_PEEK);
3283   pbodypartstring (i,"RFC822.HEADER",&st,NIL,NIL);
3284 }
3285 
3286 
3287 /* Fetch RFC-822 message length
3288  * Accepts: message number
3289  *	    extra argument
3290  */
3291 
fetch_rfc822_size(unsigned long i,void * args)3292 void fetch_rfc822_size (unsigned long i,void *args)
3293 {
3294   char tmp[MAILTMPLEN];
3295   MESSAGECACHE *elt = mail_elt (stream,i);
3296   if (!elt->rfc822_size) {	/* have message size yet? */
3297     sprintf (tmp,"%lu",i);
3298     mail_fetch_fast (stream,tmp,NIL);
3299   }
3300   PSOUT ("RFC822.SIZE ");
3301   pnum (elt->rfc822_size);
3302 }
3303 
3304 /* Fetch RFC-822 text only
3305  * Accepts: message number
3306  *	    extra argument
3307  */
3308 
fetch_rfc822_text(unsigned long i,void * args)3309 void fetch_rfc822_text (unsigned long i,void *args)
3310 {
3311   if (i) {			/* do work? */
3312     int f = mail_elt (stream,i)->seen;
3313     SIZEDTEXT st;
3314     st.data = (unsigned char *)
3315       mail_fetch_text (stream,i,NIL,&st.size,
3316 		       ((long) args) | FT_RETURNSTRINGSTRUCT);
3317     pbodypartstring (i,"RFC822.TEXT",&st,&stream->private.string,NIL);
3318   }
3319 }
3320 
3321 /* Print envelope
3322  * Accepts: body
3323  */
3324 
penv(ENVELOPE * env)3325 void penv (ENVELOPE *env)
3326 {
3327   PBOUT ('(');			/* delimiter */
3328   if (env) {			/* only if there is an envelope */
3329     pnstring (env->date);	/* output envelope fields */
3330     PBOUT (' ');
3331     pnstring (env->subject);
3332     PBOUT (' ');
3333     paddr (env->from);
3334     PBOUT (' ');
3335     paddr (env->sender);
3336     PBOUT (' ');
3337     paddr (env->reply_to);
3338     PBOUT (' ');
3339     paddr (env->to);
3340     PBOUT (' ');
3341     paddr (env->cc);
3342     PBOUT (' ');
3343     paddr (env->bcc);
3344     PBOUT (' ');
3345     pnstring (env->in_reply_to);
3346     PBOUT (' ');
3347     pnstring (env->message_id);
3348   }
3349 				/* no envelope */
3350   else PSOUT ("NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL");
3351   PBOUT (')');			/* end of envelope */
3352 }
3353 
3354 /* Print body structure (extensible)
3355  * Accepts: body
3356  */
3357 
pbodystructure(BODY * body)3358 void pbodystructure (BODY *body)
3359 {
3360   PBOUT ('(');			/* delimiter */
3361   if (body) {			/* only if there is a body */
3362     PART *part;
3363 				/* multipart type? */
3364     if (body->type == TYPEMULTIPART) {
3365 				/* print each part */
3366       if (part = body->nested.part)
3367 	for (; part; part = part->next) pbodystructure (&(part->body));
3368       else pbodystructure (NIL);
3369       PBOUT (' ');		/* space delimiter */
3370       pstring (body->subtype);	/* subtype */
3371       PBOUT (' ');
3372       pparam (body->parameter);	/* multipart body extension data */
3373       PBOUT (' ');
3374       if (body->disposition.type) {
3375 	PBOUT ('(');
3376 	pstring (body->disposition.type);
3377 	PBOUT (' ');
3378 	pparam (body->disposition.parameter);
3379 	PBOUT (')');
3380       }
3381       else PSOUT ("NIL");
3382       PBOUT (' ');
3383       pnstringorlist (body->language);
3384       PBOUT (' ');
3385       pnstring (body->location);
3386     }
3387 
3388     else {			/* non-multipart body type */
3389       pstring ((char *) body_types[body->type]);
3390       PBOUT (' ');
3391       pstring (body->subtype);
3392       PBOUT (' ');
3393       pparam (body->parameter);
3394       PBOUT (' ');
3395       pnstring (body->id);
3396       PBOUT (' ');
3397       pnstring (body->description);
3398       PBOUT (' ');
3399       pstring ((char *) body_encodings[body->encoding]);
3400       PBOUT (' ');
3401       pnum (body->size.bytes);
3402       switch (body->type) {	/* extra stuff depends upon body type */
3403       case TYPEMESSAGE:
3404 				/* can't do this if not RFC822 */
3405 	if (strcmp (body->subtype,"RFC822")) break;
3406 	PBOUT (' ');
3407 	penv (body->nested.msg->env);
3408 	PBOUT (' ');
3409 	pbodystructure (body->nested.msg->body);
3410       case TYPETEXT:
3411 	PBOUT (' ');
3412 	pnum (body->size.lines);
3413 	break;
3414       default:
3415 	break;
3416       }
3417       PBOUT (' ');
3418       pnstring (body->md5);
3419       PBOUT (' ');
3420       if (body->disposition.type) {
3421 	PBOUT ('(');
3422 	pstring (body->disposition.type);
3423 	PBOUT (' ');
3424 	pparam (body->disposition.parameter);
3425 	PBOUT (')');
3426       }
3427       else PSOUT ("NIL");
3428       PBOUT (' ');
3429       pnstringorlist (body->language);
3430       PBOUT (' ');
3431       pnstring (body->location);
3432     }
3433   }
3434 				/* no body */
3435   else PSOUT ("\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" 0 0 NIL NIL NIL NIL");
3436   PBOUT (')');			/* end of body */
3437 }
3438 
3439 /* Print body (non-extensible)
3440  * Accepts: body
3441  */
3442 
pbody(BODY * body)3443 void pbody (BODY *body)
3444 {
3445   PBOUT ('(');			/* delimiter */
3446   if (body) {			/* only if there is a body */
3447     PART *part;
3448 				/* multipart type? */
3449     if (body->type == TYPEMULTIPART) {
3450 				/* print each part */
3451       if (part = body->nested.part)
3452 	for (; part; part = part->next) pbody (&(part->body));
3453       else pbody (NIL);
3454       PBOUT (' ');		/* space delimiter */
3455       pstring (body->subtype);	/* and finally the subtype */
3456     }
3457     else {			/* non-multipart body type */
3458       pstring ((char *) body_types[body->type]);
3459       PBOUT (' ');
3460       pstring (body->subtype);
3461       PBOUT (' ');
3462       pparam (body->parameter);
3463       PBOUT (' ');
3464       pnstring (body->id);
3465       PBOUT (' ');
3466       pnstring (body->description);
3467       PBOUT (' ');
3468       pstring ((char *) body_encodings[body->encoding]);
3469       PBOUT (' ');
3470       pnum (body->size.bytes);
3471       switch (body->type) {	/* extra stuff depends upon body type */
3472       case TYPEMESSAGE:
3473 				/* can't do this if not RFC822 */
3474 	if (strcmp (body->subtype,"RFC822")) break;
3475 	PBOUT (' ');
3476 	penv (body->nested.msg ? body->nested.msg->env : NIL);
3477 	PBOUT (' ');
3478 	pbody (body->nested.msg ? body->nested.msg->body : NIL);
3479       case TYPETEXT:
3480 	PBOUT (' ');
3481 	pnum (body->size.lines);
3482 	break;
3483       default:
3484 	break;
3485       }
3486     }
3487   }
3488 				/* no body */
3489   else PSOUT ("\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" 0 0");
3490   PBOUT (')');			/* end of body */
3491 }
3492 
3493 /* Print parameter list
3494  * Accepts: paramter
3495  */
3496 
pparam(PARAMETER * param)3497 void pparam (PARAMETER *param)
3498 {
3499   if (param) {			/* one specified? */
3500     PBOUT ('(');
3501     do {
3502       pstring (param->attribute);
3503       PBOUT (' ');
3504       pstring (param->value);
3505       if (param = param->next) PBOUT (' ');
3506     } while (param);
3507     PBOUT (')');		/* end of parameters */
3508   }
3509   else PSOUT ("NIL");
3510 }
3511 
3512 
3513 /* Print address list
3514  * Accepts: address list
3515  */
3516 
paddr(ADDRESS * a)3517 void paddr (ADDRESS *a)
3518 {
3519   if (a) {			/* have anything in address? */
3520     PBOUT ('(');		/* open the address list */
3521     do {			/* for each address */
3522       PBOUT ('(');		/* open the address */
3523       pnstring (a->personal);	/* personal name */
3524       PBOUT (' ');
3525       pnstring (a->adl);	/* at-domain-list */
3526       PBOUT (' ');
3527       pnstring (a->mailbox);	/* mailbox */
3528       PBOUT (' ');
3529       pnstring (a->host);	/* domain name of mailbox's host */
3530       PBOUT (')');		/* terminate address */
3531     } while (a = a->next);	/* until end of address */
3532     PBOUT (')');		/* close address list */
3533   }
3534   else PSOUT ("NIL");		/* empty address */
3535 }
3536 
3537 /* Print set
3538  * Accepts: set
3539  */
3540 
pset(SEARCHSET ** set)3541 void pset (SEARCHSET **set)
3542 {
3543   SEARCHSET *cur = *set;
3544   while (cur) {			/* while there's a set to do */
3545     pnum (cur->first);		/* output first value */
3546     if (cur->last) {		/* if range, output second value of range */
3547       PBOUT (':');
3548       pnum (cur->last);
3549     }
3550     if (cur = cur->next) PBOUT (',');
3551   }
3552   mail_free_searchset (set);	/* flush set */
3553 }
3554 
3555 
3556 /* Print number
3557  * Accepts: number
3558  */
3559 
pnum(unsigned long i)3560 void pnum (unsigned long i)
3561 {
3562   char tmp[MAILTMPLEN];
3563   sprintf (tmp,"%lu",i);
3564   PSOUT (tmp);
3565 }
3566 
3567 
3568 /* Print string
3569  * Accepts: string
3570  */
3571 
pstring(char * s)3572 void pstring (char *s)
3573 {
3574   SIZEDTEXT st;
3575   st.data = (unsigned char *) s;/* set up sized text */
3576   st.size = strlen (s);
3577   psizedstring (&st,NIL);	/* print string */
3578 }
3579 
3580 
3581 /* Print nstring
3582  * Accepts: string or NIL
3583  */
3584 
pnstring(char * s)3585 void pnstring (char *s)
3586 {
3587   if (s) pstring (s);		/* print string */
3588   else PSOUT ("NIL");
3589 }
3590 
3591 
3592 /* Print atom or string
3593  * Accepts: astring
3594  */
3595 
pastring(char * s)3596 void pastring (char *s)
3597 {
3598   char *t;
3599   if (!*s) PSOUT ("\"\"");	/* empty string */
3600   else {			/* see if atom */
3601     for (t = s; (*t > ' ') && !(*t & 0x80) &&
3602 	 (*t != '"') && (*t != '\\') && (*t != '(') && (*t != ')') &&
3603 	 (*t != '{') && (*t != '%') && (*t != '*'); t++);
3604     if (*t) pstring (s);	/* not an atom */
3605     else PSOUT (s);		/* else plop down as atomic */
3606   }
3607 }
3608 
3609 /* Print sized text as quoted
3610  * Accepts: sized text
3611  */
3612 
psizedquoted(SIZEDTEXT * s)3613 void psizedquoted (SIZEDTEXT *s)
3614 {
3615   PBOUT ('"');			/* use quoted string */
3616   ptext (s,NIL);
3617   PBOUT ('"');
3618 }
3619 
3620 
3621 /* Print sized text as literal
3622  * Accepts: sized text
3623  */
3624 
psizedliteral(SIZEDTEXT * s,STRING * st)3625 void psizedliteral (SIZEDTEXT *s,STRING *st)
3626 {
3627   PBOUT ('{');			/* print literal size */
3628   pnum (s->size);
3629   PSOUT ("}\015\012");
3630   ptext (s,st);
3631 }
3632 
3633 /* Print sized text as literal or quoted string
3634  * Accepts: sized text
3635  *	    alternative stringstruct of text
3636  */
3637 
psizedstring(SIZEDTEXT * s,STRING * st)3638 void psizedstring (SIZEDTEXT *s,STRING *st)
3639 {
3640   unsigned char c;
3641   unsigned long i;
3642 
3643   if (s->data) {		/* if text, check if must use literal */
3644     for (i = 0; ((i < s->size) && ((c = s->data[i]) & 0xe0) &&
3645 		 !(c & 0x80) && (c != '"') && (c != '\\')); ++i);
3646 				/* must use literal if not all QUOTED-CHAR */
3647     if (i < s->size) psizedliteral (s,st);
3648     else psizedquoted (s);
3649   }
3650   else psizedliteral (s,st);
3651 }
3652 
3653 
3654 /* Print sized text as literal or quoted string
3655  * Accepts: sized text
3656  */
3657 
psizedastring(SIZEDTEXT * s)3658 void psizedastring (SIZEDTEXT *s)
3659 {
3660   unsigned long i;
3661   unsigned int atomp = s->size ? T : NIL;
3662   for (i = 0; i < s->size; i++){/* check if must use literal */
3663     if (!(s->data[i] & 0xe0) || (s->data[i] & 0x80) ||
3664 	(s->data[i] == '"') || (s->data[i] == '\\')) {
3665       psizedliteral (s,NIL);
3666       return;
3667     }
3668     else switch (s->data[i]) {	/* else see if any atom-specials */
3669     case '(': case ')': case '{': case ' ':
3670     case '%': case '*':		/* list-wildcards */
3671     case ']':			/* resp-specials */
3672 				/* CTL and quoted-specials in literal check */
3673       atomp = NIL;		/* not an atom */
3674     }
3675   }
3676   if (atomp) ptext (s,NIL);	/* print as atom */
3677   else psizedquoted (s);	/* print as quoted string */
3678 }
3679 
3680 /* Print string list
3681  * Accepts: string list
3682  */
3683 
pastringlist(STRINGLIST * s)3684 void pastringlist (STRINGLIST *s)
3685 {
3686   PBOUT ('(');			/* start list */
3687   do {
3688     psizedastring (&s->text);	/* output list member */
3689     if (s->next) PBOUT (' ');
3690   } while (s = s->next);
3691   PBOUT (')');			/* terminate list */
3692 }
3693 
3694 
3695 /* Print nstring or list of strings
3696  * Accepts: string / string list
3697  */
3698 
pnstringorlist(STRINGLIST * s)3699 void pnstringorlist (STRINGLIST *s)
3700 {
3701   if (!s) PSOUT ("NIL");	/* no argument given */
3702   else if (s->next) {		/* output list as list of strings*/
3703     PBOUT ('(');		/* start list */
3704     do {			/* output list member */
3705       psizedstring (&s->text,NIL);
3706       if (s->next) PBOUT (' ');
3707     } while (s = s->next);
3708     PBOUT (')');		/* terminate list */
3709   }
3710 				/* and single-element list as string */
3711   else psizedstring (&s->text,NIL);
3712 }
3713 
3714 /* Print body part string
3715  * Accepts: message number
3716  *	    body part id (note: must have space at end to append stuff)
3717  *	    sized text of string
3718  *	    alternative stringstruct of string
3719  *	    text printing arguments
3720  */
3721 
pbodypartstring(unsigned long msgno,char * id,SIZEDTEXT * st,STRING * bs,TEXTARGS * ta)3722 void pbodypartstring (unsigned long msgno,char *id,SIZEDTEXT *st,STRING *bs,
3723 		      TEXTARGS *ta)
3724 {
3725   int f = mail_elt (stream,msgno)->seen;
3726 				/* ignore stringstruct if non-initialized */
3727   if (bs && !bs->curpos) bs = NIL;
3728   if (ta && st->size) {		/* only if have useful data */
3729 				/* partial specifier */
3730     if (ta->first || ta->last) sprintf (id + strlen (id),"<%lu>",ta->first);
3731   				/* in case first byte beyond end of text */
3732     if (st->size <= ta->first) st->size = ta->first = 0;
3733     else {
3734       if (st->data) {		/* offset and truncate */
3735 	st->data += ta->first;	/* move to desired position */
3736 	st->size -= ta->first;	/* reduced size */
3737       }
3738       else if (bs && (SIZE (bs) >= ta->first))
3739 	SETPOS (bs,ta->first + GETPOS (bs));
3740       else st->size = 0;	/* shouldn't happen */
3741       if (ta->last && (st->size > ta->last)) st->size = ta->last;
3742     }
3743   }
3744   PSOUT (id);
3745   PBOUT (' ');
3746   psizedstring (st,bs);		/* output string */
3747   changed_flags (msgno,f);	/* and changed flags */
3748 }
3749 
3750 /*  RFC 3501 technically forbids NULs in literals.  Normally, the delivering
3751  * MTA would take care of MIME converting the message text so that it is
3752  * NUL-free.  If it doesn't, then we have the choice of either violating
3753  * IMAP by sending NULs, corrupting the data, or going to lots of work to do
3754  * MIME conversion in the IMAP server.
3755  */
3756 
3757 /* Print raw sized text
3758  * Accepts: sizedtext
3759  */
3760 
ptext(SIZEDTEXT * txt,STRING * st)3761 void ptext (SIZEDTEXT *txt,STRING *st)
3762 {
3763   unsigned char c,*s;
3764   unsigned long i = txt->size;
3765   if (s = txt->data) while (i && ((PBOUT ((c = *s++) ? c : 0x80) != EOF))) --i;
3766   else if (st) while (i && (PBOUT ((c = SNX (st)) ? c : 0x80) != EOF)) --i;
3767 				/* failed to complete? */
3768   if (i) ioerror (stdout,"writing text");
3769 }
3770 
3771 /* Print thread
3772  * Accepts: thread
3773  */
3774 
pthread(THREADNODE * thr)3775 void pthread (THREADNODE *thr)
3776 {
3777   THREADNODE *t;
3778   while (thr) {			/* for each branch */
3779     PBOUT ('(');		/* open branch */
3780     if (thr->num) {		/* first node message number */
3781       pnum (thr->num);
3782       if (t = thr->next) {	/* any subsequent nodes? */
3783 	PBOUT (' ');
3784 	while (t) {		/* for each subsequent node */
3785 	  if (t->branch) {	/* branches? */
3786 	    pthread (t);	/* yes, recurse to do branch */
3787 	    t = NIL;		/* done */
3788 	  }
3789 	  else {		/* just output this number */
3790 	    pnum (t->num);
3791 	    t = t->next;	/* and do next message */
3792 	  }
3793 	  if (t) PBOUT (' ');	/* delimit if more to come */
3794 	}
3795       }
3796     }
3797     else pthread (thr->next);	/* nest for dummy */
3798     PBOUT (')');		/* done with this branch */
3799     thr = thr->branch;		/* do next branch */
3800   }
3801 }
3802 
3803 /* Print capabilities
3804  * Accepts: option flag
3805  */
3806 
pcapability(long flag)3807 void pcapability (long flag)
3808 {
3809   unsigned long i;
3810   char *s;
3811   struct stat sbuf;
3812   AUTHENTICATOR *auth;
3813   THREADER *thr = (THREADER *) mail_parameters (NIL,GET_THREADERS,NIL);
3814 				/* always output protocol level */
3815   PSOUT ("CAPABILITY IMAP4REV1 I18NLEVEL=1 LITERAL+");
3816 #ifdef NETSCAPE_BRAIN_DAMAGE
3817   PSOUT (" X-NETSCAPE");
3818 #endif
3819   if (flag >= 0) {		/* want post-authentication capabilities? */
3820     PSOUT (" IDLE UIDPLUS NAMESPACE CHILDREN MAILBOX-REFERRALS BINARY UNSELECT ESEARCH WITHIN SCAN SORT");
3821     while (thr) {		/* threaders */
3822       PSOUT (" THREAD=");
3823       PSOUT (thr->name);
3824       thr = thr->next;
3825     }
3826     if (!anonymous) PSOUT (" MULTIAPPEND");
3827   }
3828   if (flag <= 0) {		/* want pre-authentication capabilities? */
3829     PSOUT (" SASL-IR LOGIN-REFERRALS");
3830     if (s = ssl_start_tls (NIL)) fs_give ((void **) &s);
3831     else PSOUT (" STARTTLS");
3832 				/* disable plaintext */
3833     if (!(i = !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL)))
3834       PSOUT (" LOGINDISABLED");
3835     for (auth = mail_lookup_auth (1); auth; auth = auth->next)
3836       if (auth->server && !(auth->flags & AU_DISABLE) &&
3837 	  !(auth->flags & AU_HIDE) && (i || (auth->flags & AU_SECURE))) {
3838 	PSOUT (" AUTH=");
3839 	PSOUT (auth->name);
3840       }
3841     if (!stat (ANOFILE,&sbuf)) PSOUT (" AUTH=ANONYMOUS");
3842   }
3843 }
3844 
3845 /* Anonymous users may only use these mailboxes in these namespaces */
3846 
3847 char *oktab[] = {"#news.", "#ftp/", "#public/", 0};
3848 
3849 
3850 /* Check if mailbox name is OK
3851  * Accepts: reference name
3852  *	    mailbox name
3853  */
3854 
nameok(char * ref,char * name)3855 long nameok (char *ref,char *name)
3856 {
3857   int i;
3858   unsigned char *s,*t;
3859   if (!name) return NIL;	/* failure if missing name */
3860   if (!anonymous) return T;	/* otherwise OK if not anonymous */
3861 				/* validate reference */
3862   if (ref && ((*ref == '#') || (*ref == '{')))
3863     for (i = 0; oktab[i]; i++) {
3864       for (s = ref, t = oktab[i]; *t && !compare_uchar (*s,*t); s++, t++);
3865       if (!*t) {		/* reference OK */
3866 	if (*name == '#') break;/* check name if override */
3867 	else return T;		/* otherwise done */
3868       }
3869     }
3870 				/* ordinary names are OK */
3871   if ((*name != '#') && (*name != '{')) return T;
3872   for (i = 0; oktab[i]; i++) {	/* validate mailbox */
3873     for (s = name, t = oktab[i]; *t && !compare_uchar (*s,*t); s++, t++);
3874     if (!*t) return T;		/* name is OK */
3875   }
3876   response = "%.80s NO Anonymous may not %.80s this name\015\012";
3877   return NIL;
3878 }
3879 
3880 
3881 /* Convert possible BBoard name to actual name
3882  * Accepts: command
3883  *	    mailbox name
3884  * Returns: maibox name
3885  */
3886 
bboardname(char * cmd,char * name)3887 char *bboardname (char *cmd,char *name)
3888 {
3889   if (cmd[0] == 'B') {		/* want bboard? */
3890     char *s = litstk[litsp++] = (char *) fs_get (strlen (name) + 9);
3891     sprintf (s,"#public/%s",(*name == '/') ? name+1 : name);
3892     name = s;
3893   }
3894   return name;
3895 }
3896 
3897 /* Test if name is news proxy
3898  * Accepts: name
3899  * Returns: T if news proxy, NIL otherwise
3900  */
3901 
isnewsproxy(char * name)3902 long isnewsproxy (char *name)
3903 {
3904   return (nntpproxy && (name[0] == '#') &&
3905 	  ((name[1] == 'N') || (name[1] == 'n')) &&
3906 	  ((name[2] == 'E') || (name[2] == 'e')) &&
3907 	  ((name[3] == 'W') || (name[3] == 'w')) &&
3908 	  ((name[4] == 'S') || (name[4] == 's')) && (name[5] == '.')) ?
3909     LONGT : NIL;
3910 }
3911 
3912 
3913 /* News proxy generate canonical pattern
3914  * Accepts: reference
3915  *	    pattern
3916  *	    buffer to return canonical pattern
3917  * Returns: T on success with pattern in buffer, NIL on failure
3918  */
3919 
newsproxypattern(char * ref,char * pat,char * pattern,long flag)3920 long newsproxypattern (char *ref,char *pat,char *pattern,long flag)
3921 {
3922   if (!nntpproxy) return NIL;
3923   if (strlen (ref) > NETMAXMBX) {
3924     sprintf (pattern,"Invalid reference specification: %.80s",ref);
3925     mm_log (pattern,ERROR);
3926     return NIL;
3927   }
3928   if (strlen (pat) > NETMAXMBX) {
3929     sprintf (pattern,"Invalid pattern specification: %.80s",pat);
3930     mm_log (pattern,ERROR);
3931     return NIL;
3932   }
3933   if (flag) {			/* prepend proxy specifier */
3934     sprintf (pattern,"{%.300s/nntp}",nntpproxy);
3935     pattern += strlen (pattern);
3936   }
3937   if (*ref) {			/* have a reference */
3938     strcpy (pattern,ref);	/* copy reference to pattern */
3939 				/* # overrides mailbox field in reference */
3940     if (*pat == '#') strcpy (pattern,pat);
3941 				/* pattern starts, reference ends, with . */
3942     else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
3943       strcat (pattern,pat + 1);	/* append, omitting one of the period */
3944     else strcat (pattern,pat);	/* anything else is just appended */
3945   }
3946   else strcpy (pattern,pat);	/* just have basic name */
3947   return isnewsproxy (pattern);
3948 }
3949 
3950 /* IMAP4rev1 Authentication responder
3951  * Accepts: challenge
3952  *	    length of challenge
3953  *	    pointer to response length return location if non-NIL
3954  * Returns: response
3955  */
3956 
3957 #define RESPBUFLEN 8*MAILTMPLEN
3958 
imap_responder(void * challenge,unsigned long clen,unsigned long * rlen)3959 char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen)
3960 {
3961   unsigned long i,j;
3962   unsigned char *t,resp[RESPBUFLEN];
3963   if (initial) {		/* initial response given? */
3964     if (clen) return NIL;	/* not permitted */
3965 				/* set up response */
3966     i = strlen ((char *) (t = initial));
3967     initial = NIL;		/* no more initial response */
3968     if ((*t == '=') && !t[1]) {	/* SASL-IR does this for 0-length response */
3969       if (rlen) *rlen = 0;	/* set length zero if empty */
3970       return cpystr ("");	/* and return empty string as response */
3971     }
3972   }
3973   else {			/* issue challenge, get response */
3974     PSOUT ("+ ");
3975     for (t = rfc822_binary ((void *) challenge,clen,&i),j = 0; j < i; j++)
3976       if (t[j] > ' ') PBOUT (t[j]);
3977     fs_give ((void **) &t);
3978     CRLF;
3979     PFLUSH ();			/* dump output buffer */
3980 				/* slurp response buffer */
3981     slurp ((char *) resp,RESPBUFLEN,INPUTTIMEOUT);
3982     if (!(t = (unsigned char *) strchr ((char *) resp,'\012')))
3983       return (char *) flush ();
3984     if (t[-1] == '\015') --t;	/* remove CR */
3985     *t = '\0';			/* tie off buffer */
3986     if (resp[0] == '*') {
3987       cancelled = T;
3988       return NIL;
3989     }
3990     i = t - resp;		/* length of response */
3991     t = resp;			/* set up for return call */
3992   }
3993   return (i % 4) ? NIL :	/* return if valid BASE64 */
3994     (char *) rfc822_base64 (t,i,rlen ? rlen : &i);
3995 }
3996 
3997 /* Proxy copy across mailbox formats
3998  * Accepts: mail stream
3999  *	    sequence to copy on this stream
4000  *	    destination mailbox
4001  *	    option flags
4002  * Returns: T if success, else NIL
4003  */
4004 
proxycopy(MAILSTREAM * stream,char * sequence,char * mailbox,long options)4005 long proxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
4006 {
4007   MAILSTREAM *ts;
4008   STRING st;
4009   MSGDATA md;
4010   SEARCHSET *set;
4011   char tmp[MAILTMPLEN];
4012   unsigned long i,j;
4013   md.stream = stream;
4014   md.msgno = 0;
4015   md.flags = md.date = NIL;
4016   md.message = &st;
4017   /* Currently ignores CP_MOVE and CP_DEBUG */
4018   if (!((options & CP_UID) ?	/* validate sequence */
4019 	mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence)))
4020     return NIL;
4021   response = win;		/* cancel previous errors */
4022   if (lsterr) fs_give ((void **) &lsterr);
4023 				/* c-client clobbers sequence, use spare */
4024   for (i = 1,j = 0,set = mail_newsearchset (); i <= nmsgs; i++)
4025     if (mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence) {
4026       mail_append_set (set,mail_uid (stream,i));
4027       if (!j) md.msgno = (j = i) - 1;
4028     }
4029 				/* only if at least one message to copy */
4030   if (j && !mail_append_multiple (NIL,mailbox,proxy_append,(void *) &md)) {
4031     response = trycreate ? losetry : lose;
4032     if (set) mail_free_searchset (&set);
4033     return NIL;
4034   }
4035   if (caset) csset = set;	/* set for return value now */
4036   else if (set) mail_free_searchset (&set);
4037   response = win;		/* stomp any previous babble */
4038   if (md.msgno) {		/* get new driver name if was dummy */
4039     sprintf (tmp,"Cross-format (%.80s -> %.80s) COPY completed",
4040 	     stream->dtb->name,(ts = mail_open (NIL,mailbox,OP_PROTOTYPE)) ?
4041 	     ts->dtb->name : "unknown");
4042     mm_log (tmp,NIL);
4043   }
4044   return LONGT;
4045 }
4046 
4047 /* Proxy append message callback
4048  * Accepts: MAIL stream
4049  *	    append data package
4050  *	    pointer to return initial flags
4051  *	    pointer to return message internal date
4052  *	    pointer to return stringstruct of message or NIL to stop
4053  * Returns: T if success (have message or stop), NIL if error
4054  */
4055 
proxy_append(MAILSTREAM * stream,void * data,char ** flags,char ** date,STRING ** message)4056 long proxy_append (MAILSTREAM *stream,void *data,char **flags,char **date,
4057 		   STRING **message)
4058 {
4059   MESSAGECACHE *elt;
4060   unsigned long i;
4061   char *s,*t,tmp[MAILTMPLEN];
4062   MSGDATA *md = (MSGDATA *) data;
4063   if (md->flags) fs_give ((void **) &md->flags);
4064   if (md->date) fs_give ((void **) &md->date);
4065   *message = NIL;		/* assume all done */
4066   *flags = *date = NIL;
4067   while (++md->msgno <= nmsgs)
4068     if ((elt = mail_elt (md->stream,md->msgno))->spare) {
4069       if (!(elt->valid && elt->day)) {
4070 	sprintf (tmp,"%lu",md->msgno);
4071 	mail_fetch_fast (md->stream,tmp,NIL);
4072       }
4073       memset (s = tmp,0,MAILTMPLEN);
4074 				/* copy flags */
4075       if (elt->seen) strcat (s," \\Seen");
4076       if (elt->deleted) strcat (s," \\Deleted");
4077       if (elt->flagged) strcat (s," \\Flagged");
4078       if (elt->answered) strcat (s," \\Answered");
4079       if (elt->draft) strcat (s," \\Draft");
4080       if (i = elt->user_flags) do
4081 	if ((t = md->stream->user_flags[find_rightmost_bit (&i)]) && *t &&
4082 	    (strlen (t) < ((size_t) (MAILTMPLEN-((s += strlen (s))+2-tmp))))) {
4083 	*s++ = ' ';		/* space delimiter */
4084 	strcpy (s,t);
4085       } while (i);		/* until no more user flags */
4086       *message = md->message;	/* set up return values */
4087       *flags = md->flags = cpystr (tmp + 1);
4088       *date = md->date = cpystr (mail_date (tmp,elt));
4089       INIT (md->message,msg_string,(void *) md,elt->rfc822_size);
4090       break;			/* process this message */
4091     }
4092   return LONGT;
4093 }
4094 
4095 /* Append message callback
4096  * Accepts: MAIL stream
4097  *	    append data package
4098  *	    pointer to return initial flags
4099  *	    pointer to return message internal date
4100  *	    pointer to return stringstruct of message or NIL to stop
4101  * Returns: T if success (have message or stop), NIL if error
4102  */
4103 
append_msg(MAILSTREAM * stream,void * data,char ** flags,char ** date,STRING ** message)4104 long append_msg (MAILSTREAM *stream,void *data,char **flags,char **date,
4105 		 STRING **message)
4106 {
4107   unsigned long i,j;
4108   char *t;
4109   APPENDDATA *ad = (APPENDDATA *) data;
4110   unsigned char *arg = ad->arg;
4111 				/* flush text of previous message */
4112   if (t = ad->flags) fs_give ((void **) &ad->flags);
4113   if (t = ad->date) fs_give ((void **) &ad->date);
4114   if (t = ad->msg) fs_give ((void **) &ad->msg);
4115   *flags = *date = NIL;		/* assume no flags or date */
4116   if (t) {			/* have previous message? */
4117     if (!*arg) {		/* if least one message, and no more coming */
4118       *message = NIL;		/* set stop */
4119       return LONGT;		/* return success */
4120     }
4121     else if (*arg++ != ' ') {	/* must have a delimiter to next argument */
4122       response = misarg;	/* oops */
4123       return NIL;
4124     }
4125   }
4126   *message = ad->message;	/* return pointer to message stringstruct */
4127   if (*arg == '(') {		/* parse optional flag list */
4128     t = ++arg;			/* pointer to flag list contents */
4129     while (*arg && (*arg != ')')) arg++;
4130     if (*arg) *arg++ = '\0';
4131     if (*arg == ' ') arg++;
4132     *flags = ad->flags = cpystr (t);
4133   }
4134 				/* parse optional date */
4135   if (*arg == '"') *date = ad->date = cpystr (snarf (&arg));
4136   if (!arg || (*arg != '{'))	/* parse message */
4137     response = "%.80s BAD Missing literal in %.80s\015\012";
4138   else if (!isdigit (arg[1]))
4139     response = "%.80s BAD Missing message to %.80s\015\012";
4140   else if (!(i = strtoul (arg+1,&t,10)))
4141     response = "%.80s NO Empty message to %.80s\015\012";
4142   else if (i > MAXAPPENDTXT)	/* maybe relax this a little */
4143     response = "%.80s NO Excessively large message to %.80s\015\012";
4144   else if (((*t == '+') && (t[1] == '}') && !t[2]) || ((*t == '}') && !t[1])) {
4145 				/* get a literal buffer */
4146     inliteral (ad->msg = (char *) fs_get (i+1),i);
4147     				/* get new command tail */
4148     slurp (ad->arg,CMDLEN - (ad->arg - cmdbuf),INPUTTIMEOUT);
4149     if (strchr (ad->arg,'\012')) {
4150 				/* reset strtok mechanism, tie off if done */
4151       if (!strtok (ad->arg,"\015\012")) *ad->arg = '\0';
4152 				/* possible LITERAL+? */
4153       if (((j = strlen (ad->arg)) > 3) && (ad->arg[j - 1] == '}') &&
4154 	  (ad->arg[j - 2] == '+') && isdigit (ad->arg[j - 3])) {
4155 				/* back over possible count */
4156 	for (j -= 4; j && isdigit (ad->arg[j]); j--);
4157 	if (ad->arg[j] == '{') {/* found a literal? */
4158 	  litplus.ok = T;	/* yes, note LITERAL+ in effect, set size */
4159 	  litplus.size = strtoul (ad->arg + j + 1,NIL,10);
4160 	}
4161       }
4162 				/* initialize stringstruct */
4163       INIT (ad->message,mail_string,(void *) ad->msg,i);
4164       return LONGT;		/* ready to go */
4165     }
4166     flush ();			/* didn't find end of line? */
4167     fs_give ((void **) &ad->msg);
4168   }
4169   else response = badarg;	/* not a literal */
4170   return NIL;			/* error */
4171 }
4172 
4173 /* Got COPY UID data
4174  * Accepts: MAIL stream
4175  *	    mailbox name
4176  *	    UID validity
4177  *	    source set of UIDs
4178  *	    destination set of UIDs
4179  */
4180 
copyuid(MAILSTREAM * stream,char * mailbox,unsigned long uidvalidity,SEARCHSET * sourceset,SEARCHSET * destset)4181 void copyuid (MAILSTREAM *stream,char *mailbox,unsigned long uidvalidity,
4182 	      SEARCHSET *sourceset,SEARCHSET *destset)
4183 {
4184   if (cauidvalidity) fatal ("duplicate COPYUID/APPENDUID data");
4185   cauidvalidity = uidvalidity;
4186   csset = sourceset;
4187   caset = destset;
4188 }
4189 
4190 
4191 /* Got APPEND UID data
4192  * Accepts: mailbox name
4193  *	    UID validity
4194  *	    destination set of UIDs
4195  */
4196 
appenduid(char * mailbox,unsigned long uidvalidity,SEARCHSET * set)4197 void appenduid (char *mailbox,unsigned long uidvalidity,SEARCHSET *set)
4198 {
4199   copyuid (NIL,mailbox,uidvalidity,NIL,set);
4200 }
4201 
4202 
4203 /* Got a referral
4204  * Accepts: MAIL stream
4205  *	    URL
4206  *	    referral type code
4207  */
4208 
referral(MAILSTREAM * stream,char * url,long code)4209 char *referral (MAILSTREAM *stream,char *url,long code)
4210 {
4211   if (lstref) fs_give ((void **) &lstref);
4212   lstref = cpystr (url);	/* set referral */
4213 				/* set error if not a logged in referral */
4214   if (code != REFAUTH) response = lose;
4215   if (!lsterr) lsterr = cpystr ("Try referral URL");
4216   return NIL;			/* don't chase referrals for now */
4217 }
4218 
4219 /* Co-routines from MAIL library */
4220 
4221 
4222 /* Message matches a search
4223  * Accepts: MAIL stream
4224  *	    message number
4225  */
4226 
mm_searched(MAILSTREAM * s,unsigned long msgno)4227 void mm_searched (MAILSTREAM *s,unsigned long msgno)
4228 {
4229 				/* nothing to do here */
4230 }
4231 
4232 
4233 /* Message exists (i.e. there are that many messages in the mailbox)
4234  * Accepts: MAIL stream
4235  *	    message number
4236  */
4237 
mm_exists(MAILSTREAM * s,unsigned long number)4238 void mm_exists (MAILSTREAM *s,unsigned long number)
4239 {
4240 				/* note change in number of messages */
4241   if ((s != tstream) && (nmsgs != number)) {
4242     nmsgs = number;		/* always update number of messages */
4243     if (quell_events) existsquelled = T;
4244     else {
4245       PSOUT ("* ");
4246       pnum (nmsgs);
4247       PSOUT (" EXISTS\015\012");
4248     }
4249     recent = 0xffffffff;	/* make sure update recent too */
4250   }
4251 }
4252 
4253 
4254 /* Message expunged
4255  * Accepts: MAIL stream
4256  *	    message number
4257  */
4258 
mm_expunged(MAILSTREAM * s,unsigned long number)4259 void mm_expunged (MAILSTREAM *s,unsigned long number)
4260 {
4261   if (quell_events) fatal ("Impossible EXPUNGE event");
4262   if (s != tstream) {
4263     PSOUT ("* ");
4264     pnum (number);
4265     PSOUT (" EXPUNGE\015\012");
4266   }
4267   nmsgs--;
4268   existsquelled = T;		/* do EXISTS when command done */
4269 }
4270 
4271 
4272 /* Message status changed
4273  * Accepts: MAIL stream
4274  *	    message number
4275  */
4276 
mm_flags(MAILSTREAM * s,unsigned long number)4277 void mm_flags (MAILSTREAM *s,unsigned long number)
4278 {
4279   if (s != tstream) mail_elt (s,number)->spare2 = T;
4280 }
4281 
4282 /* Mailbox found
4283  * Accepts: hierarchy delimiter
4284  *	    mailbox name
4285  *	    attributes
4286  */
4287 
mm_list(MAILSTREAM * stream,int delimiter,char * name,long attributes)4288 void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes)
4289 {
4290   mm_list_work ("LIST",delimiter,name,attributes);
4291 }
4292 
4293 
4294 /* Subscribed mailbox found
4295  * Accepts: hierarchy delimiter
4296  *	    mailbox name
4297  *	    attributes
4298  */
4299 
mm_lsub(MAILSTREAM * stream,int delimiter,char * name,long attributes)4300 void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes)
4301 {
4302   mm_list_work ("LSUB",delimiter,name,attributes);
4303 }
4304 
4305 
4306 /* Mailbox status
4307  * Accepts: MAIL stream
4308  *	    mailbox name
4309  *	    mailbox status
4310  */
4311 
mm_status(MAILSTREAM * stream,char * mailbox,MAILSTATUS * status)4312 void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
4313 {
4314   if (!quell_events) {
4315     char tmp[MAILTMPLEN];
4316     tmp[0] = tmp[1] = '\0';
4317     if (status->flags & SA_MESSAGES)
4318       sprintf (tmp + strlen (tmp)," MESSAGES %lu",status->messages);
4319     if (status->flags & SA_RECENT)
4320       sprintf (tmp + strlen (tmp)," RECENT %lu",status->recent);
4321     if (status->flags & SA_UNSEEN)
4322       sprintf (tmp + strlen (tmp)," UNSEEN %lu",status->unseen);
4323     if (status->flags & SA_UIDNEXT)
4324       sprintf (tmp + strlen (tmp)," UIDNEXT %lu",status->uidnext);
4325     if (status->flags & SA_UIDVALIDITY)
4326       sprintf (tmp + strlen(tmp)," UIDVALIDITY %lu",status->uidvalidity);
4327     PSOUT ("* STATUS ");
4328     pastring (mailbox);
4329     PSOUT (" (");
4330     PSOUT (tmp+1);
4331     PBOUT (')');
4332     CRLF;
4333   }
4334 }
4335 
4336 /* Worker routine for LIST and LSUB
4337  * Accepts: name of response
4338  *	    hierarchy delimiter
4339  *	    mailbox name
4340  *	    attributes
4341  */
4342 
mm_list_work(char * what,int delimiter,char * name,long attributes)4343 void mm_list_work (char *what,int delimiter,char *name,long attributes)
4344 {
4345   char *s;
4346   if (!quell_events) {
4347     char tmp[MAILTMPLEN];
4348     if (finding) {
4349       PSOUT ("* MAILBOX ");
4350       PSOUT (name);
4351     }
4352 				/* new form */
4353     else if ((cmd[0] == 'R') || !(attributes & LATT_REFERRAL)) {
4354       PSOUT ("* ");
4355       PSOUT (what);
4356       PSOUT (" (");
4357       tmp[0] = tmp[1] = '\0';
4358       if (attributes & LATT_NOINFERIORS) strcat (tmp," \\NoInferiors");
4359       if (attributes & LATT_NOSELECT) strcat (tmp," \\NoSelect");
4360       if (attributes & LATT_MARKED) strcat (tmp," \\Marked");
4361       if (attributes & LATT_UNMARKED) strcat (tmp," \\UnMarked");
4362       if (attributes & LATT_HASCHILDREN) strcat (tmp," \\HasChildren");
4363       if (attributes & LATT_HASNOCHILDREN) strcat (tmp," \\HasNoChildren");
4364       PSOUT (tmp+1);
4365       switch (delimiter) {
4366       case '\\':		/* quoted delimiter */
4367       case '"':
4368 	PSOUT (") \"\\");
4369 	PBOUT (delimiter);
4370 	PBOUT ('"');
4371 	break;
4372       case '\0':		/* no delimiter */
4373 	PSOUT (") NIL");
4374 	break;
4375       default:			/* unquoted delimiter */
4376 	PSOUT (") \"");
4377 	PBOUT (delimiter);
4378 	PBOUT ('"');
4379 	break;
4380       }
4381       PBOUT (' ');
4382 				/* output mailbox name */
4383       if (proxylist && (s = strchr (name,'}'))) pastring (s+1);
4384       else pastring (name);
4385     }
4386     CRLF;
4387   }
4388 }
4389 
4390 /* Notification event
4391  * Accepts: MAIL stream
4392  *	    string to log
4393  *	    error flag
4394  */
4395 
mm_notify(MAILSTREAM * stream,char * string,long errflg)4396 void mm_notify (MAILSTREAM *stream,char *string,long errflg)
4397 {
4398   SIZEDTEXT msg;
4399   char *s,*code;
4400   if (!quell_events && (!tstream || (stream != tstream))) {
4401     switch (errflg) {
4402     case NIL:			/* information message, set as OK response */
4403       if ((string[0] == '[') &&
4404 	  ((string[1] == 'T') || (string[1] == 't')) &&
4405 	  ((string[2] == 'R') || (string[2] == 'r')) &&
4406 	  ((string[3] == 'Y') || (string[3] == 'y')) &&
4407 	  ((string[4] == 'C') || (string[4] == 'c')) &&
4408 	  ((string[5] == 'R') || (string[5] == 'r')) &&
4409 	  ((string[6] == 'E') || (string[6] == 'e')) &&
4410 	  ((string[7] == 'A') || (string[7] == 'a')) &&
4411 	  ((string[8] == 'T') || (string[8] == 't')) &&
4412 	  ((string[9] == 'E') || (string[9] == 'e')) && (string[10] == ']'))
4413 	trycreate = T;
4414     case BYE:			/* some other server signing off */
4415     case PARSE:			/* parse glitch, output unsolicited OK */
4416       code = "* OK ";
4417       break;
4418     case WARN:			/* warning, output unsolicited NO (kludge!) */
4419       code = "* NO ";
4420       break;
4421     case ERROR:			/* error that broke command */
4422     default:			/* default should never happen */
4423       code = "* BAD ";
4424       break;
4425     }
4426     PSOUT (code);
4427     msg.size = (s = strpbrk ((char *) (msg.data = (unsigned char *) string),
4428 			     "\015\012")) ?
4429       (s - string) : strlen (string);
4430     PSOUTR (&msg);
4431     CRLF;
4432     PFLUSH ();			/* let client see it immediately */
4433   }
4434 }
4435 
4436 /* Log an event for the user to see
4437  * Accepts: string to log
4438  *	    error flag
4439  */
4440 
mm_log(char * string,long errflg)4441 void mm_log (char *string,long errflg)
4442 {
4443   SIZEDTEXT msg;
4444   char *s;
4445   msg.size =
4446     (s = strpbrk ((char *) (msg.data = (unsigned char *) string),"\015\012")) ?
4447       (s - string) : strlen (string);
4448   switch (errflg) {
4449   case NIL:			/* information message, set as OK response */
4450     if (response == win) {	/* only if no other response yet */
4451       if (lsterr) {		/* if there was a previous message */
4452 	if (!quell_events) {
4453 	  PSOUT ("* OK ");	/* blat it out */
4454 	  PSOUT (lsterr);
4455 	  CRLF;
4456 	  PFLUSH ();		/* let client see it immediately */
4457 	}
4458 	fs_give ((void **) &lsterr);
4459       }
4460       lsterr = cpystr (string); /* copy string for later use */
4461       if (s) lsterr[s - string] = NIL;
4462     }
4463     break;
4464   case PARSE:			/* parse glitch, output unsolicited OK */
4465     if (!quell_events) {
4466       PSOUT ("* OK [PARSE] ");
4467       PSOUTR (&msg);
4468       CRLF;
4469       PFLUSH ();		/* let client see it immediately */
4470     }
4471     break;
4472   case WARN:			/* warning, output unsolicited NO */
4473 				/* ignore "Mailbox is empty" (KLUDGE!) */
4474     if (strcmp (string,"Mailbox is empty")) {
4475       if (lstwrn) {		/* have previous warning? */
4476 	if (!quell_events) {
4477 	  PSOUT ("* NO ");
4478 	  PSOUT (lstwrn);
4479 	  CRLF;
4480 	  PFLUSH ();		/* make sure client sees it immediately */
4481 	}
4482 	fs_give ((void **) &lstwrn);
4483       }
4484       lstwrn = cpystr (string); /* note last warning */
4485       if (s) lstwrn[s - string] = NIL;
4486     }
4487     break;
4488   case ERROR:			/* error that broke command */
4489   default:			/* default should never happen */
4490     response = trycreate ? losetry : lose;
4491     if (lsterr) fs_give ((void **) &lsterr);
4492     lsterr = cpystr (string);	/* note last error */
4493     if (s) lsterr[s - string] = NIL;
4494     break;
4495   }
4496 }
4497 
4498 /* Return last error
4499  */
4500 
lasterror(void)4501 char *lasterror (void)
4502 {
4503   if (lsterr) return lsterr;
4504   if (lstwrn) return lstwrn;
4505   return "<unknown>";
4506 }
4507 
4508 
4509 /* Log an event to debugging telemetry
4510  * Accepts: string to log
4511  */
4512 
mm_dlog(char * string)4513 void mm_dlog (char *string)
4514 {
4515   mm_log (string,WARN);		/* shouldn't happen normally */
4516 }
4517 
4518 /* Get user name and password for this host
4519  * Accepts: parse of network user name
4520  *	    where to return user name
4521  *	    where to return password
4522  *	    trial count
4523  */
4524 
mm_login(NETMBX * mb,char * username,char * password,long trial)4525 void mm_login (NETMBX *mb,char *username,char *password,long trial)
4526 {
4527 				/* set user name */
4528   strncpy (username,*mb->user ? mb->user : (char *) user,NETMAXUSER);
4529   strncpy (password,pass,256);	/* and password */
4530 }
4531 
4532 
4533 /* About to enter critical code
4534  * Accepts: stream
4535  */
4536 
mm_critical(MAILSTREAM * s)4537 void mm_critical (MAILSTREAM *s)
4538 {
4539   ++critical;
4540 }
4541 
4542 
4543 /* About to exit critical code
4544  * Accepts: stream
4545  */
4546 
mm_nocritical(MAILSTREAM * s)4547 void mm_nocritical (MAILSTREAM *s)
4548 {
4549 				/* go non-critical, pending death? */
4550   if (!--critical && (state == LOGOUT)) {
4551 				/* clean up iff needed */
4552     if (s && (stream != s) && !s->lock && (s->dtb->flags & DR_XPOINT))
4553       s = mail_close (s);
4554     longjmp (jmpenv,1);		/* die now */
4555   }
4556 }
4557 
4558 /* Disk error found
4559  * Accepts: stream
4560  *	    system error code
4561  *	    flag indicating that mailbox may be clobbered
4562  * Returns: abort flag
4563  */
4564 
mm_diskerror(MAILSTREAM * s,long errcode,long serious)4565 long mm_diskerror (MAILSTREAM *s,long errcode,long serious)
4566 {
4567   if (serious) {		/* try your damnest if clobberage likely */
4568     mm_notify (s,"Retrying to fix probable mailbox damage!",ERROR);
4569     PFLUSH ();			/* dump output buffer */
4570     syslog (LOG_ALERT,
4571 	    "Retrying after disk error user=%.80s host=%.80s mbx=%.80s: %.80s",
4572 	    user ? (char *) user : "???",tcp_clienthost (),
4573 	    (stream && stream->mailbox) ? stream->mailbox : "???",
4574 	    strerror (errcode));
4575     settimeout (0);		/* make damn sure timeout disabled */
4576     sleep (60);			/* give it some time to clear up */
4577     return NIL;
4578   }
4579   if (!quell_events) {		/* otherwise die before more damage is done */
4580     PSOUT ("* NO Disk error: ");
4581     PSOUT (strerror (errcode));
4582     CRLF;
4583   }
4584   return T;
4585 }
4586 
4587 
4588 /* Log a fatal error event
4589  * Accepts: string to log
4590  */
4591 
mm_fatal(char * string)4592 void mm_fatal (char *string)
4593 {
4594   SIZEDTEXT msg;
4595   char *s;
4596   msg.size =
4597     (s = strpbrk ((char *) (msg.data = (unsigned char *) string),"\015\012")) ?
4598       (s - string) : strlen (string);
4599   if (!quell_events) {
4600     PSOUT ("* BYE [ALERT] IMAP4rev1 server crashing: ");
4601     PSOUTR (&msg);
4602     CRLF;
4603     PFLUSH ();
4604   }
4605   syslog (LOG_ALERT,"Fatal error user=%.80s host=%.80s mbx=%.80s: %.80s",
4606 	  user ? (char *) user : "???",tcp_clienthost (),
4607 	  (stream && stream->mailbox) ? stream->mailbox : "???",string);
4608 }
4609