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