1 /* ========================================================================
2  * Copyright 1988-2008 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *
11  * ========================================================================
12  */
13 
14 /*
15  * Program:	UNIX mail routines
16  *
17  * Author:	Mark Crispin
18  *		UW Technology
19  *		University of Washington
20  *		Seattle, WA  98195
21  *		Internet: MRC@Washington.EDU
22  *
23  * Date:	20 December 1989
24  * Last Edited:	27 March 2008
25  */
26 
27 
28 /*				DEDICATION
29  *
30  *  This file is dedicated to my dog, Unix, also known as Yun-chan and
31  * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast.  Unix
32  * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
33  * a two-month bout with cirrhosis of the liver.
34  *
35  *  He was a dear friend, and I miss him terribly.
36  *
37  *  Lift a leg, Yunie.  Luv ya forever!!!!
38  */
39 
40 #include <stdio.h>
41 #include <ctype.h>
42 #include <errno.h>
43 extern int errno;		/* just in case */
44 #include <signal.h>
45 #include "mail.h"
46 #include "osdep.h"
47 #include <time.h>
48 #include <sys/stat.h>
49 #include "unix.h"
50 #include "pseudo.h"
51 #include "fdstring.h"
52 #include "misc.h"
53 #include "dummy.h"
54 
55 /* UNIX I/O stream local data */
56 
57 typedef struct unix_local {
58   unsigned int dirty : 1;	/* disk copy needs updating */
59   unsigned int ddirty : 1;	/* double-dirty, ping becomes checkpoint */
60   unsigned int pseudo : 1;	/* uses a pseudo message */
61   unsigned int appending : 1;	/* don't mark new messages as old */
62   int fd;			/* mailbox file descriptor */
63   int ld;			/* lock file descriptor */
64   char *lname;			/* lock file name */
65   off_t filesize;		/* file size parsed */
66   time_t filetime;		/* last file time */
67   time_t lastsnarf;		/* last snarf time (for mbox driver) */
68   unsigned char *buf;		/* temporary buffer */
69   unsigned long buflen;		/* current size of temporary buffer */
70   unsigned long uid;		/* current text uid */
71   SIZEDTEXT text;		/* current text */
72   unsigned long textlen;	/* current text length */
73   char *line;			/* returned line */
74   char *linebuf;		/* line readin buffer */
75   unsigned long linebuflen;	/* current line readin buffer length */
76 } UNIXLOCAL;
77 
78 
79 /* Convenient access to local data */
80 
81 #define LOCAL ((UNIXLOCAL *) stream->local)
82 
83 
84 /* UNIX protected file structure */
85 
86 typedef struct unix_file {
87   MAILSTREAM *stream;		/* current stream */
88   off_t curpos;			/* current file position */
89   off_t protect;		/* protected position */
90   off_t filepos;		/* current last written file position */
91   char *buf;			/* overflow buffer */
92   size_t buflen;		/* current overflow buffer length */
93   char *bufpos;			/* current buffer position */
94 } UNIXFILE;
95 
96 /* Function prototypes */
97 
98 DRIVER *unix_valid (char *name);
99 long unix_isvalid_fd (int fd);
100 void *unix_parameters (long function,void *value);
101 void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
102 void unix_list (MAILSTREAM *stream,char *ref,char *pat);
103 void unix_lsub (MAILSTREAM *stream,char *ref,char *pat);
104 long unix_create (MAILSTREAM *stream,char *mailbox);
105 long unix_delete (MAILSTREAM *stream,char *mailbox);
106 long unix_rename (MAILSTREAM *stream,char *old,char *newname);
107 MAILSTREAM *unix_open (MAILSTREAM *stream);
108 void unix_close (MAILSTREAM *stream,long options);
109 char *unix_header (MAILSTREAM *stream,unsigned long msgno,
110 		   unsigned long *length,long flags);
111 long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
112 char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
113 		      unsigned long *length,long flags);
114 void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
115 long unix_ping (MAILSTREAM *stream);
116 void unix_check (MAILSTREAM *stream);
117 long unix_expunge (MAILSTREAM *stream,char *sequence,long options);
118 long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
119 long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
120 int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
121 		     STRING *msg);
122 int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set);
123 
124 void unix_abort (MAILSTREAM *stream);
125 char *unix_file (char *dst,char *name);
126 int unix_lock (char *file,int flags,int mode,DOTLOCK *lock,int op);
127 void unix_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock);
128 int unix_parse (MAILSTREAM *stream,DOTLOCK *lock,int op);
129 char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size);
130 unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr);
131 unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
132 			    unsigned long uid,long flag);
133 long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
134 		   long flags);
135 long unix_extend (MAILSTREAM *stream,unsigned long size);
136 void unix_write (UNIXFILE *f,char *s,unsigned long i);
137 void unix_phys_write (UNIXFILE *f,char *buf,size_t size);
138 
139 /* mbox mail routines */
140 
141 /* Function prototypes */
142 
143 DRIVER *mbox_valid (char *name);
144 long mbox_create (MAILSTREAM *stream,char *mailbox);
145 long mbox_delete (MAILSTREAM *stream,char *mailbox);
146 long mbox_rename (MAILSTREAM *stream,char *old,char *newname);
147 long mbox_status (MAILSTREAM *stream,char *mbx,long flags);
148 MAILSTREAM *mbox_open (MAILSTREAM *stream);
149 long mbox_ping (MAILSTREAM *stream);
150 void mbox_check (MAILSTREAM *stream);
151 long mbox_expunge (MAILSTREAM *stream,char *sequence,long options);
152 long mbox_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
153 
154 
155 /* UNIX mail routines */
156 
157 
158 /* Driver dispatch used by MAIL */
159 
160 DRIVER unixdriver = {
161   "unix",			/* driver name */
162 				/* driver flags */
163   DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY|DR_XPOINT,
164   (DRIVER *) NIL,		/* next driver */
165   unix_valid,			/* mailbox is valid for us */
166   unix_parameters,		/* manipulate parameters */
167   unix_scan,			/* scan mailboxes */
168   unix_list,			/* list mailboxes */
169   unix_lsub,			/* list subscribed mailboxes */
170   NIL,				/* subscribe to mailbox */
171   NIL,				/* unsubscribe from mailbox */
172   unix_create,			/* create mailbox */
173   unix_delete,			/* delete mailbox */
174   unix_rename,			/* rename mailbox */
175   mail_status_default,		/* status of mailbox */
176   unix_open,			/* open mailbox */
177   unix_close,			/* close mailbox */
178   NIL,				/* fetch message "fast" attributes */
179   NIL,				/* fetch message flags */
180   NIL,				/* fetch overview */
181   NIL,				/* fetch message envelopes */
182   unix_header,			/* fetch message header */
183   unix_text,			/* fetch message text */
184   NIL,				/* fetch partial message text */
185   NIL,				/* unique identifier */
186   NIL,				/* message number */
187   NIL,				/* modify flags */
188   unix_flagmsg,			/* per-message modify flags */
189   NIL,				/* search for message based on criteria */
190   NIL,				/* sort messages */
191   NIL,				/* thread messages */
192   unix_ping,			/* ping mailbox to see if still alive */
193   unix_check,			/* check for new messages */
194   unix_expunge,			/* expunge deleted messages */
195   unix_copy,			/* copy messages to another mailbox */
196   unix_append,			/* append string message to mailbox */
197   NIL				/* garbage collect stream */
198 };
199 
200 				/* prototype stream */
201 MAILSTREAM unixproto = {&unixdriver};
202 
203 				/* driver parameters */
204 static long unix_fromwidget = T;
205 
206 /* UNIX mail validate mailbox
207  * Accepts: mailbox name
208  * Returns: our driver if name is valid, NIL otherwise
209  */
210 
unix_valid(char * name)211 DRIVER *unix_valid (char *name)
212 {
213   int fd;
214   DRIVER *ret = NIL;
215   char *t,file[MAILTMPLEN];
216   struct stat sbuf;
217   time_t tp[2];
218   errno = EINVAL;		/* assume invalid argument */
219 				/* must be non-empty file */
220   if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) {
221     if (!sbuf.st_size)errno = 0;/* empty file */
222     else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
223 				/* OK if mailbox format good */
224       if (unix_isvalid_fd (fd)) ret = &unixdriver;
225       else errno = -1;		/* invalid format */
226       close (fd);		/* close the file */
227 				/* \Marked status? */
228       if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) {
229 	tp[0] = sbuf.st_atime;	/* yes, preserve atime and mtime */
230 	tp[1] = sbuf.st_mtime;
231 	utime (file,tp);	/* set the times */
232       }
233     }
234   }
235   return ret;			/* return what we should */
236 }
237 
238 /* UNIX mail test for valid mailbox
239  * Accepts: file descriptor
240  *	    scratch buffer
241  * Returns: T if valid, NIL otherwise
242  */
243 
unix_isvalid_fd(int fd)244 long unix_isvalid_fd (int fd)
245 {
246   int zn;
247   int ret = NIL;
248   char tmp[MAILTMPLEN],*s,*t,c = '\n';
249   memset (tmp,'\0',MAILTMPLEN);
250   if (read (fd,tmp,MAILTMPLEN-1) >= 0) {
251     for (s = tmp; (*s == '\r') || (*s == '\n') || (*s == ' ') || (*s == '\t');)
252       c = *s++;
253     if (c == '\n') VALID (s,t,ret,zn);
254   }
255   return ret;			/* return what we should */
256 }
257 
258 
259 /* UNIX manipulate driver parameters
260  * Accepts: function code
261  *	    function-dependent value
262  * Returns: function-dependent return value
263  */
264 
unix_parameters(long function,void * value)265 void *unix_parameters (long function,void *value)
266 {
267   void *ret = NIL;
268   switch ((int) function) {
269   case GET_INBOXPATH:
270     if (value) ret = dummy_file ((char *) value,"INBOX");
271     break;
272   case SET_FROMWIDGET:
273     unix_fromwidget = (long) value;
274   case GET_FROMWIDGET:
275     ret = (void *) unix_fromwidget;
276     break;
277   }
278   return ret;
279 }
280 
281 /* UNIX mail scan mailboxes
282  * Accepts: mail stream
283  *	    reference
284  *	    pattern to search
285  *	    string to scan
286  */
287 
unix_scan(MAILSTREAM * stream,char * ref,char * pat,char * contents)288 void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
289 {
290   if (stream) dummy_scan (NIL,ref,pat,contents);
291 }
292 
293 
294 /* UNIX mail list mailboxes
295  * Accepts: mail stream
296  *	    reference
297  *	    pattern to search
298  */
299 
unix_list(MAILSTREAM * stream,char * ref,char * pat)300 void unix_list (MAILSTREAM *stream,char *ref,char *pat)
301 {
302   if (stream) dummy_list (NIL,ref,pat);
303 }
304 
305 
306 /* UNIX mail list subscribed mailboxes
307  * Accepts: mail stream
308  *	    reference
309  *	    pattern to search
310  */
311 
unix_lsub(MAILSTREAM * stream,char * ref,char * pat)312 void unix_lsub (MAILSTREAM *stream,char *ref,char *pat)
313 {
314   if (stream) dummy_lsub (NIL,ref,pat);
315 }
316 
317 /* UNIX mail create mailbox
318  * Accepts: MAIL stream
319  *	    mailbox name to create
320  * Returns: T on success, NIL on failure
321  */
322 
unix_create(MAILSTREAM * stream,char * mailbox)323 long unix_create (MAILSTREAM *stream,char *mailbox)
324 {
325   char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
326   long ret = NIL;
327   int i,fd;
328   time_t ti = time (0);
329   if (!(s = dummy_file (mbx,mailbox))) {
330     sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
331     MM_LOG (tmp,ERROR);
332   }
333 				/* create underlying file */
334   else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) {
335 				/* done if dir-only or whiner */
336     if (((s = strrchr (s,'/')) && !s[1]) ||
337 	mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T;
338     else if ((fd = open (mbx,O_WRONLY,
339 		    (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) {
340       sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
341       MM_LOG (tmp,ERROR);
342       unlink (mbx);		/* delete the file */
343     }
344     else {			/* initialize header */
345       memset (tmp,'\0',MAILTMPLEN);
346       sprintf (tmp,"From %s %sDate: ",pseudo_from,ctime (&ti));
347       rfc822_fixed_date (s = tmp + strlen (tmp));
348 				/* write the pseudo-header */
349       sprintf (s += strlen (s),
350 	       "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000",
351 	       pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
352 	       (unsigned long) ti);
353       for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i))
354 	sprintf (s += strlen (s)," %s",default_user_flag (i));
355       sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n\n",pseudo_msg);
356       if (write (fd,tmp,strlen (tmp)) > 0) ret = T;
357       else {
358 	sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
359 		 strerror (errno));
360 	MM_LOG (tmp,ERROR);
361 	unlink (mbx);		/* delete the file */
362       }
363       close (fd);		/* close file */
364     }
365   }
366 				/* set proper protections */
367   return ret ? set_mbx_protections (mailbox,mbx) : NIL;
368 }
369 
370 /* UNIX mail delete mailbox
371  * Accepts: MAIL stream
372  *	    mailbox name to delete
373  * Returns: T on success, NIL on failure
374  */
375 
unix_delete(MAILSTREAM * stream,char * mailbox)376 long unix_delete (MAILSTREAM *stream,char *mailbox)
377 {
378   return unix_rename (stream,mailbox,NIL);
379 }
380 
381 
382 /* UNIX mail rename mailbox
383  * Accepts: MAIL stream
384  *	    old mailbox name
385  *	    new mailbox name (or NIL for delete)
386  * Returns: T on success, NIL on failure
387  */
388 
unix_rename(MAILSTREAM * stream,char * old,char * newname)389 long unix_rename (MAILSTREAM *stream,char *old,char *newname)
390 {
391   long ret = NIL;
392   char c,*s = NIL;
393   char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
394   DOTLOCK lockx;
395   int fd,ld;
396   long i;
397   struct stat sbuf;
398   MM_CRITICAL (stream);		/* get the c-client lock */
399   if (!dummy_file (file,old) ||
400       (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
401 		   ((s = strrchr (tmp,'/')) && !s[1]))))
402     sprintf (tmp,newname ?
403 	     "Can't rename mailbox %.80s to %.80s: invalid name" :
404 	     "Can't delete mailbox %.80s: invalid name",
405 	     old,newname);
406 				/* lock out other c-clients */
407   else if ((ld = lockname (lock,file,LOCK_EX|LOCK_NB,&i)) < 0)
408     sprintf (tmp,"Mailbox %.80s is in use by another process",old);
409 
410   else {
411     if ((fd = unix_lock (file,O_RDWR,
412 			 (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
413 			 &lockx,LOCK_EX)) < 0)
414       sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
415     else {
416       if (newname) {		/* want rename? */
417 				/* found superior to destination name? */
418 	if (s = strrchr (s,'/')) {
419 	  c = *++s;		/* remember first character of inferior */
420 	  *s = '\0';		/* tie off to get just superior */
421 				/* name doesn't exist, create it */
422 	  if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
423 	      !dummy_create_path (stream,tmp,get_dir_protection (newname))) {
424 	    unix_unlock (fd,NIL,&lockx);
425 	    unix_unlock (ld,NIL,NIL);
426 	    unlink (lock);
427 	    MM_NOCRITICAL (stream);
428 	    return ret;		/* return success or failure */
429 	  }
430 	  *s = c;		/* restore full name */
431 	}
432 	if (rename (file,tmp))
433 	  sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
434 		   strerror (errno));
435 	else ret = T;		/* set success */
436       }
437       else if (unlink (file))
438 	sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
439       else ret = T;		/* set success */
440       unix_unlock (fd,NIL,&lockx);
441     }
442     unix_unlock (ld,NIL,NIL);	/* flush the lock */
443     unlink (lock);
444   }
445   MM_NOCRITICAL (stream);	/* no longer critical */
446   if (!ret) MM_LOG (tmp,ERROR);	/* log error */
447   return ret;			/* return success or failure */
448 }
449 
450 /* UNIX mail open
451  * Accepts: Stream to open
452  * Returns: Stream on success, NIL on failure
453  */
454 
unix_open(MAILSTREAM * stream)455 MAILSTREAM *unix_open (MAILSTREAM *stream)
456 {
457   long i;
458   int fd;
459   char tmp[MAILTMPLEN];
460   DOTLOCK lock;
461   long retry;
462 				/* return prototype for OP_PROTOTYPE call */
463   if (!stream) return user_flags (&unixproto);
464   retry = stream->silent ? 1 : KODRETRY;
465   if (stream->local) fatal ("unix recycle stream");
466   stream->local = memset (fs_get (sizeof (UNIXLOCAL)),0,sizeof (UNIXLOCAL));
467 				/* note if an INBOX or not */
468   stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
469 				/* canonicalize the stream mailbox name */
470   if (!dummy_file (tmp,stream->mailbox)) {
471     sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
472     MM_LOG (tmp,ERROR);
473     return NIL;
474   }
475 				/* flush old name */
476   fs_give ((void **) &stream->mailbox);
477 				/* save canonical name */
478   stream->mailbox = cpystr (tmp);
479   LOCAL->fd = LOCAL->ld = -1;	/* no file or state locking yet */
480   LOCAL->buf = (char *) fs_get (CHUNKSIZE);
481   LOCAL->buflen = CHUNKSIZE - 1;
482   LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
483   LOCAL->text.size = CHUNKSIZE - 1;
484   LOCAL->linebuf = (char *) fs_get (CHUNKSIZE);
485   LOCAL->linebuflen = CHUNKSIZE - 1;
486   stream->sequence++;		/* bump sequence number */
487 
488 				/* make lock for read/write access */
489   if (!stream->rdonly) while (retry) {
490 				/* try to lock file */
491     if ((fd = lockname (tmp,stream->mailbox,LOCK_EX|LOCK_NB,&i)) < 0) {
492 				/* suppressing kiss-of-death? */
493       if (stream->nokod) retry = 0;
494 				/* no, first time through? */
495       else if (retry-- == KODRETRY) {
496 				/* learned other guy's PID and can signal? */
497 	if (i && !kill ((int) i,SIGUSR2)) {
498 	  sprintf (tmp,"Trying to get mailbox lock from process %ld",i);
499 	  MM_LOG (tmp,WARN);
500 	}
501 	else retry = 0;		/* give up */
502       }
503       if (!stream->silent) {	/* nothing if silent stream */
504 	if (retry) sleep (1);	/* wait a second before trying again */
505 	else MM_LOG ("Mailbox is open by another process, access is readonly",
506 		     WARN);
507       }
508     }
509     else {			/* got the lock, nobody else can alter state */
510       LOCAL->ld = fd;		/* note lock's fd and name */
511       LOCAL->lname = cpystr (tmp);
512 				/* make sure mode OK (don't use fchmod()) */
513       chmod (LOCAL->lname,(long) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
514       if (stream->silent) i = 0;/* silent streams won't accept KOD */
515       else {			/* note our PID in the lock */
516 	sprintf (tmp,"%d",getpid ());
517 	write (fd,tmp,(i = strlen (tmp))+1);
518       }
519       ftruncate (fd,i);		/* make sure tied off */
520       fsync (fd);		/* make sure it's available */
521       retry = 0;		/* no more need to try */
522     }
523   }
524 
525 				/* parse mailbox */
526   stream->nmsgs = stream->recent = 0;
527 				/* will we be able to get write access? */
528   if ((LOCAL->ld >= 0) && access (stream->mailbox,W_OK) && (errno == EACCES)) {
529     MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
530     flock (LOCAL->ld,LOCK_UN);	/* release the lock */
531     close (LOCAL->ld);		/* close the lock file */
532     LOCAL->ld = -1;		/* no more lock fd */
533     unlink (LOCAL->lname);	/* delete it */
534   }
535 				/* reset UID validity */
536   stream->uid_validity = stream->uid_last = 0;
537   if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
538     unix_abort (stream);	/* abort if can't get RW silent stream */
539 				/* parse mailbox */
540   else if (unix_parse (stream,&lock,LOCK_SH)) {
541     unix_unlock (LOCAL->fd,stream,&lock);
542     mail_unlock (stream);
543     MM_NOCRITICAL (stream);	/* done with critical */
544   }
545   if (!LOCAL) return NIL;	/* failure if stream died */
546 				/* make sure upper level knows readonly */
547   stream->rdonly = (LOCAL->ld < 0);
548 				/* notify about empty mailbox */
549   if (!(stream->nmsgs || stream->silent)) MM_LOG ("Mailbox is empty",NIL);
550   if (!stream->rdonly) {	/* flags stick if readwrite */
551     stream->perm_seen = stream->perm_deleted =
552       stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
553     if (!stream->uid_nosticky) {/* users with lives get permanent keywords */
554       stream->perm_user_flags = 0xffffffff;
555 				/* and maybe can create them too! */
556       stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
557     }
558   }
559   return stream;		/* return stream alive to caller */
560 }
561 
562 
563 /* UNIX mail close
564  * Accepts: MAIL stream
565  *	    close options
566  */
567 
unix_close(MAILSTREAM * stream,long options)568 void unix_close (MAILSTREAM *stream,long options)
569 {
570   int silent = stream->silent;
571   stream->silent = T;		/* go silent */
572 				/* expunge if requested */
573   if (options & CL_EXPUNGE) unix_expunge (stream,NIL,NIL);
574 				/* else dump final checkpoint */
575   else if (LOCAL->dirty) unix_check (stream);
576   stream->silent = silent;	/* restore old silence state */
577   unix_abort (stream);		/* now punt the file and local data */
578 }
579 
580 /* UNIX mail fetch message header
581  * Accepts: MAIL stream
582  *	    message # to fetch
583  *	    pointer to returned header text length
584  *	    option flags
585  * Returns: message header in RFC822 format
586  */
587 
588 				/* lines to filter from header */
589 static STRINGLIST *unix_hlines = NIL;
590 
unix_header(MAILSTREAM * stream,unsigned long msgno,unsigned long * length,long flags)591 char *unix_header (MAILSTREAM *stream,unsigned long msgno,
592 		   unsigned long *length,long flags)
593 {
594   MESSAGECACHE *elt;
595   unsigned char *s,*t,*tl;
596   *length = 0;			/* default to empty */
597   if (flags & FT_UID) return "";/* UID call "impossible" */
598   elt = mail_elt (stream,msgno);/* get cache */
599   if (!unix_hlines) {		/* once only code */
600     STRINGLIST *lines = unix_hlines = mail_newstringlist ();
601     lines->text.size = strlen ((char *) (lines->text.data =
602 					 (unsigned char *) "Status"));
603     lines = lines->next = mail_newstringlist ();
604     lines->text.size = strlen ((char *) (lines->text.data =
605 					 (unsigned char *) "X-Status"));
606     lines = lines->next = mail_newstringlist ();
607     lines->text.size = strlen ((char *) (lines->text.data =
608 					 (unsigned char *) "X-Keywords"));
609     lines = lines->next = mail_newstringlist ();
610     lines->text.size = strlen ((char *) (lines->text.data =
611 					 (unsigned char *) "X-UID"));
612     lines = lines->next = mail_newstringlist ();
613     lines->text.size = strlen ((char *) (lines->text.data =
614 					 (unsigned char *) "X-IMAP"));
615     lines = lines->next = mail_newstringlist ();
616     lines->text.size = strlen ((char *) (lines->text.data =
617 					 (unsigned char *) "X-IMAPbase"));
618   }
619 				/* go to header position */
620   lseek (LOCAL->fd,elt->private.special.offset +
621 	 elt->private.msg.header.offset,L_SET);
622 
623   if (flags & FT_INTERNAL) {	/* initial data OK? */
624     if (elt->private.msg.header.text.size > LOCAL->buflen) {
625       fs_give ((void **) &LOCAL->buf);
626       LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
627 				     elt->private.msg.header.text.size) + 1);
628     }
629 				/* read message */
630     read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
631 				/* got text, tie off string */
632     LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
633 				/* squeeze out CRs (in case from PC) */
634     for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
635       if (*t != '\r') *s++ = *t;
636     *s = '\0';
637     *length = s - LOCAL->buf;	/* adjust length */
638   }
639   else {			/* need to make a CRLF version */
640     read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
641 	  elt->private.msg.header.text.size);
642 				/* tie off string, and convert to CRLF */
643     s[elt->private.msg.header.text.size] = '\0';
644     *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
645 			  elt->private.msg.header.text.size);
646     fs_give ((void **) &s);	/* free readin buffer */
647 				/* squeeze out spurious CRs */
648     for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
649       if ((*t != '\r') || (t[1] == '\n')) *s++ = *t;
650     *s = '\0';
651     *length = s - LOCAL->buf;	/* adjust length */
652   }
653   *length = mail_filter (LOCAL->buf,*length,unix_hlines,FT_NOT);
654   return (char *) LOCAL->buf;	/* return processed copy */
655 }
656 
657 /* UNIX mail fetch message text
658  * Accepts: MAIL stream
659  *	    message # to fetch
660  *	    pointer to returned stringstruct
661  *	    option flags
662  * Returns: T on success, NIL if failure
663  */
664 
unix_text(MAILSTREAM * stream,unsigned long msgno,STRING * bs,long flags)665 long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
666 {
667   char *s;
668   unsigned long i;
669   MESSAGECACHE *elt;
670 				/* UID call "impossible" */
671   if (flags & FT_UID) return NIL;
672   elt = mail_elt (stream,msgno);/* get cache element */
673 				/* if message not seen */
674   if (!(flags & FT_PEEK) && !elt->seen) {
675 				/* mark message seen and dirty */
676     elt->seen = elt->private.dirty = LOCAL->dirty = T;
677     MM_FLAGS (stream,msgno);
678   }
679   s = unix_text_work (stream,elt,&i,flags);
680   INIT (bs,mail_string,s,i);	/* set up stringstruct */
681   return T;			/* success */
682 }
683 
684 /* UNIX mail fetch message text worker routine
685  * Accepts: MAIL stream
686  *	    message cache element
687  *	    pointer to returned header text length
688  *	    option flags
689  */
690 
unix_text_work(MAILSTREAM * stream,MESSAGECACHE * elt,unsigned long * length,long flags)691 char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
692 		      unsigned long *length,long flags)
693 {
694   FDDATA d;
695   STRING bs;
696   unsigned char c,*s,*t,*tl,tmp[CHUNKSIZE];
697 				/* go to text position */
698   lseek (LOCAL->fd,elt->private.special.offset +
699 	 elt->private.msg.text.offset,L_SET);
700   if (flags & FT_INTERNAL) {	/* initial data OK? */
701     if (elt->private.msg.text.text.size > LOCAL->buflen) {
702       fs_give ((void **) &LOCAL->buf);
703       LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
704 				     elt->private.msg.text.text.size) + 1);
705     }
706 				/* read message */
707     read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
708 				/* got text, tie off string */
709     LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
710 				/* squeeze out CRs (in case from PC) */
711     for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
712       if (*t != '\r') *s++ = *t;
713     *s = '\0';
714     *length = s - LOCAL->buf;	/* adjust length */
715     return (char *) LOCAL->buf;
716   }
717 
718 				/* have it cached already? */
719   if (elt->private.uid != LOCAL->uid) {
720 				/* not cached, cache it now */
721     LOCAL->uid = elt->private.uid;
722 				/* is buffer big enough? */
723     if (elt->rfc822_size > LOCAL->text.size) {
724       /* excessively conservative, but the right thing is too hard to do */
725       fs_give ((void **) &LOCAL->text.data);
726       LOCAL->text.data = (unsigned char *)
727 	fs_get ((LOCAL->text.size = elt->rfc822_size) + 1);
728     }
729     d.fd = LOCAL->fd;		/* yes, set up file descriptor */
730     d.pos = elt->private.special.offset + elt->private.msg.text.offset;
731     d.chunk = tmp;		/* initial buffer chunk */
732     d.chunksize = CHUNKSIZE;	/* file chunk size */
733     INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
734     for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) {
735     case '\r':			/* carriage return seen */
736       break;
737     case '\n':
738       *s++ = '\r';		/* insert a CR */
739     default:
740       *s++ = c;			/* copy characters */
741     }
742     *s = '\0';			/* tie off buffer */
743 				/* calculate length of cached data */
744     LOCAL->textlen = s - LOCAL->text.data;
745   }
746   *length = LOCAL->textlen;	/* return from cache */
747   return (char *) LOCAL->text.data;
748 }
749 
750 /* UNIX per-message modify flag
751  * Accepts: MAIL stream
752  *	    message cache element
753  */
754 
unix_flagmsg(MAILSTREAM * stream,MESSAGECACHE * elt)755 void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
756 {
757 				/* only after finishing */
758   if (elt->valid) elt->private.dirty = LOCAL->dirty = T;
759 }
760 
761 
762 /* UNIX mail ping mailbox
763  * Accepts: MAIL stream
764  * Returns: T if stream alive, else NIL
765  */
766 
unix_ping(MAILSTREAM * stream)767 long unix_ping (MAILSTREAM *stream)
768 {
769   DOTLOCK lock;
770   struct stat sbuf;
771   long reparse;
772 				/* big no-op if not readwrite */
773   if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
774     if (stream->rdonly) {	/* does he want to give up readwrite? */
775 				/* checkpoint if we changed something */
776       if (LOCAL->dirty) unix_check (stream);
777       flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
778       close (LOCAL->ld);	/* close the readwrite lock file */
779       LOCAL->ld = -1;		/* no more readwrite lock fd */
780       unlink (LOCAL->lname);	/* delete the readwrite lock file */
781     }
782     else {			/* see if need to reparse */
783       if (!(reparse = (long) mail_parameters (NIL,GET_NETFSSTATBUG,NIL))) {
784 				/* get current mailbox size */
785 	if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
786 	else if (stat (stream->mailbox,&sbuf)) {
787 	  sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s",
788 		   strerror (errno));
789 	  MM_LOG (LOCAL->buf,ERROR);
790 	  unix_abort (stream);
791 	  return NIL;
792 	}
793 	reparse = (sbuf.st_size != LOCAL->filesize);
794       }
795 				/* parse if mailbox changed */
796       if ((LOCAL->ddirty || reparse) && unix_parse (stream,&lock,LOCK_EX)) {
797 				/* force checkpoint if double-dirty */
798 	if (LOCAL->ddirty) unix_rewrite (stream,NIL,&lock,NIL);
799 				/* unlock mailbox */
800 	else unix_unlock (LOCAL->fd,stream,&lock);
801 	mail_unlock (stream);	/* and stream */
802 	MM_NOCRITICAL (stream);	/* done with critical */
803       }
804     }
805   }
806   return LOCAL ? LONGT : NIL;	/* return if still alive */
807 }
808 
809 /* UNIX mail check mailbox
810  * Accepts: MAIL stream
811  */
812 
unix_check(MAILSTREAM * stream)813 void unix_check (MAILSTREAM *stream)
814 {
815   DOTLOCK lock;
816 				/* parse and lock mailbox */
817   if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
818       unix_parse (stream,&lock,LOCK_EX)) {
819 				/* any unsaved changes? */
820     if (LOCAL->dirty && unix_rewrite (stream,NIL,&lock,NIL)) {
821       if (!stream->silent) MM_LOG ("Checkpoint completed",NIL);
822     }
823 				/* no checkpoint needed, just unlock */
824     else unix_unlock (LOCAL->fd,stream,&lock);
825     mail_unlock (stream);	/* unlock the stream */
826     MM_NOCRITICAL (stream);	/* done with critical */
827   }
828 }
829 
830 
831 /* UNIX mail expunge mailbox
832  * Accepts: MAIL stream
833  *	    sequence to expunge if non-NIL
834  *	    expunge options
835  * Returns: T, always
836  */
837 
unix_expunge(MAILSTREAM * stream,char * sequence,long options)838 long unix_expunge (MAILSTREAM *stream,char *sequence,long options)
839 {
840   long ret;
841   unsigned long i;
842   DOTLOCK lock;
843   char *msg = NIL;
844 				/* parse and lock mailbox */
845   if (ret = (sequence ? ((options & EX_UID) ?
846 			 mail_uid_sequence (stream,sequence) :
847 			 mail_sequence (stream,sequence)) : LONGT) &&
848       LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
849       unix_parse (stream,&lock,LOCK_EX)) {
850 				/* check expunged messages if not dirty */
851     for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) {
852       MESSAGECACHE *elt = mail_elt (stream,i);
853       if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
854     }
855     if (!LOCAL->dirty) {	/* not dirty and no expunged messages */
856       unix_unlock (LOCAL->fd,stream,&lock);
857       msg = "No messages deleted, so no update needed";
858     }
859     else if (unix_rewrite (stream,&i,&lock,sequence ? LONGT : NIL)) {
860       if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
861       else msg = "Mailbox checkpointed, but no messages expunged";
862     }
863 				/* rewrite failed */
864     else unix_unlock (LOCAL->fd,stream,&lock);
865     mail_unlock (stream);	/* unlock the stream */
866     MM_NOCRITICAL (stream);	/* done with critical */
867     if (msg && !stream->silent) MM_LOG (msg,NIL);
868   }
869   else if (!stream->silent) MM_LOG("Expunge ignored on readonly mailbox",WARN);
870   return ret;
871 }
872 
873 /* UNIX mail copy message(s)
874  * Accepts: MAIL stream
875  *	    sequence
876  *	    destination mailbox
877  *	    copy options
878  * Returns: T if copy successful, else NIL
879  */
880 
unix_copy(MAILSTREAM * stream,char * sequence,char * mailbox,long options)881 long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
882 {
883   struct stat sbuf;
884   int fd;
885   char *s,file[MAILTMPLEN];
886   DOTLOCK lock;
887   time_t tp[2];
888   unsigned long i,j;
889   MESSAGECACHE *elt;
890   long ret = T;
891   mailproxycopy_t pc =
892     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
893   copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ?
894 			      NIL : mail_parameters (NIL,GET_COPYUID,NIL));
895   SEARCHSET *source = cu ? mail_newsearchset () : NIL;
896   SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
897   MAILSTREAM *tstream = NIL;
898   DRIVER *d;
899   for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL);
900        (d && strcmp (d->name,"mbox") && !(d->flags & DR_DISABLE));
901        d = d->next);		/* see if mbox driver active */
902   if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
903 	mail_sequence (stream,sequence))) return NIL;
904 				/* make sure destination is valid */
905   if (!((d && mbox_valid (mailbox) && (mailbox = "mbox")) ||
906 	unix_valid (mailbox) || !errno))
907     switch (errno) {
908     case ENOENT:		/* no such file? */
909       if (compare_cstring (mailbox,"INBOX")) {
910 	MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
911 	return NIL;
912       }
913       if (pc) return (*pc) (stream,sequence,mailbox,options);
914       unix_create (NIL,"INBOX");/* create empty INBOX */
915     case EACCES:		/* file protected */
916       sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
917       MM_LOG (LOCAL->buf,ERROR);
918       return NIL;
919     case EINVAL:
920       if (pc) return (*pc) (stream,sequence,mailbox,options);
921       sprintf (LOCAL->buf,"Invalid UNIX-format mailbox name: %.80s",mailbox);
922       MM_LOG (LOCAL->buf,ERROR);
923       return NIL;
924     default:
925       if (pc) return (*pc) (stream,sequence,mailbox,options);
926       sprintf (LOCAL->buf,"Not a UNIX-format mailbox: %.80s",mailbox);
927       MM_LOG (LOCAL->buf,ERROR);
928       return NIL;
929     }
930 
931 				/* try to open rewrite for UIDPLUS */
932   if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
933 				 OP_SILENT|OP_NOKOD)) && tstream->rdonly)
934     tstream = mail_close (tstream);
935   if (cu && !tstream) {		/* wanted a COPYUID? */
936     sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s",
937 	     mailbox);
938     MM_LOG (LOCAL->buf,WARN);
939     cu = NIL;			/* don't try to do COPYUID */
940   }
941   LOCAL->buf[0] = '\0';
942   MM_CRITICAL (stream);		/* go critical */
943   if ((fd = unix_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
944 		       (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
945 		       &lock,LOCK_EX)) < 0) {
946     MM_NOCRITICAL (stream);	/* done with critical */
947     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
948     MM_LOG (LOCAL->buf,ERROR);/* log the error */
949     return NIL;			/* failed */
950   }
951   fstat (fd,&sbuf);		/* get current file size */
952 				/* write all requested messages to mailbox */
953   for (i = 1; ret && (i <= stream->nmsgs); i++)
954     if ((elt = mail_elt (stream,i))->sequence) {
955       lseek (LOCAL->fd,elt->private.special.offset,L_SET);
956       read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
957       if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL;
958       else {			/* internal header succeeded */
959 	s = unix_header (stream,i,&j,FT_INTERNAL);
960 				/* header size, sans trailing newline */
961 	if (j && (s[j - 2] == '\n')) j--;
962 	if (write (fd,s,j) < 0) ret = NIL;
963 	else {			/* message header succeeded */
964 	  j = tstream ?		/* write UIDPLUS data if have readwrite */
965 	    unix_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) :
966 	    unix_xstatus (stream,LOCAL->buf,elt,NIL,NIL);
967 	  if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
968 	  else {		/* message status succeeded */
969 	    s = unix_text_work (stream,elt,&j,FT_INTERNAL);
970 	    if ((write (fd,s,j) < 0) || (write (fd,"\n",1) < 0)) ret = NIL;
971 	    else if (cu) {	/* need to pass back new UID? */
972 	      mail_append_set (source,mail_uid (stream,i));
973 	      mail_append_set (dest,tstream->uid_last);
974 	    }
975 	  }
976 	}
977       }
978     }
979 
980   if (!ret || fsync (fd)) {	/* force out the update */
981     sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
982     ftruncate (fd,sbuf.st_size);
983     ret = NIL;
984   }
985 				/* force UIDVALIDITY assignment now */
986   if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
987 				/* return sets if doing COPYUID */
988   if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest);
989   else {			/* flush any sets we may have built */
990     mail_free_searchset (&source);
991     mail_free_searchset (&dest);
992   }
993   tp[1] = time (0);		/* set mtime to now */
994   if (ret) tp[0] = tp[1] - 1;	/* set atime to now-1 if successful copy */
995   else tp[0] =			/* else preserve \Marked status */
996 	 ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
997 	 sbuf.st_atime : tp[1];
998   utime (file,tp);		/* set the times */
999   unix_unlock (fd,NIL,&lock);	/* unlock and close mailbox */
1000   if (tstream) {		/* update last UID if we can */
1001     UNIXLOCAL *local = (UNIXLOCAL *) tstream->local;
1002     local->dirty = T;		/* do a rewrite */
1003     local->appending = T;	/* but not at the cost of marking as old */
1004     tstream = mail_close (tstream);
1005   }
1006 				/* log the error */
1007   if (!ret) MM_LOG (LOCAL->buf,ERROR);
1008 				/* delete if requested message */
1009   else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
1010     if ((elt = mail_elt (stream,i))->sequence)
1011       elt->deleted = elt->private.dirty = LOCAL->dirty = T;
1012   MM_NOCRITICAL (stream);	/* release critical */
1013   return ret;
1014 }
1015 
1016 /* UNIX mail append message from stringstruct
1017  * Accepts: MAIL stream
1018  *	    destination mailbox
1019  *	    append callback
1020  *	    data for callback
1021  * Returns: T if append successful, else NIL
1022  */
1023 
1024 #define BUFLEN 8*MAILTMPLEN
1025 
unix_append(MAILSTREAM * stream,char * mailbox,append_t af,void * data)1026 long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
1027 {
1028   struct stat sbuf;
1029   int fd;
1030   unsigned long i;
1031   char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN];
1032   time_t tp[2];
1033   FILE *sf,*df;
1034   MESSAGECACHE elt;
1035   DOTLOCK lock;
1036   STRING *message;
1037   unsigned long uidlocation = 0;
1038   appenduid_t au = (appenduid_t)
1039     (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL :
1040      mail_parameters (NIL,GET_APPENDUID,NIL));
1041   SEARCHSET *dst = au ? mail_newsearchset () : NIL;
1042   long ret = LONGT;
1043   MAILSTREAM *tstream = NIL;
1044   if (!stream) {		/* stream specified? */
1045     stream = &unixproto;	/* no, default stream to prototype */
1046     for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i)
1047       fs_give ((void **) &stream->user_flags[i]);
1048   }
1049   if (!unix_valid (mailbox)) switch (errno) {
1050   case ENOENT:			/* no such file? */
1051     if (compare_cstring (mailbox,"INBOX")) {
1052       MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
1053       return NIL;
1054     }
1055     unix_create (NIL,"INBOX");	/* create empty INBOX */
1056   case 0:			/* merely empty file? */
1057     tstream = stream;
1058     break;
1059   case EACCES:			/* file protected */
1060     sprintf (tmp,"Can't access destination: %.80s",mailbox);
1061     MM_LOG (tmp,ERROR);
1062     return NIL;
1063   case EINVAL:
1064     sprintf (tmp,"Invalid UNIX-format mailbox name: %.80s",mailbox);
1065     MM_LOG (tmp,ERROR);
1066     return NIL;
1067   default:
1068     sprintf (tmp,"Not a UNIX-format mailbox: %.80s",mailbox);
1069     MM_LOG (tmp,ERROR);
1070     return NIL;
1071   }
1072 				/* get sniffing stream for keywords */
1073   else if (!(tstream = mail_open (NIL,mailbox,
1074 				  OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) {
1075     sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox);
1076     MM_LOG (tmp,ERROR);
1077     return NIL;
1078   }
1079 
1080 				/* get first message */
1081   if (!MM_APPEND (af) (tstream,data,&flags,&date,&message)) return NIL;
1082   if (!(sf = tmpfile ())) {	/* must have scratch file */
1083     sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ());
1084     if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) {
1085       sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
1086       MM_LOG (tmp,ERROR);
1087       return NIL;
1088     }
1089     unlink (tmp);
1090   }
1091   do {				/* parse date */
1092     if (!date) rfc822_date (date = tmp);
1093     if (!mail_parse_date (&elt,date)) {
1094       sprintf (tmp,"Bad date in append: %.80s",date);
1095       MM_LOG (tmp,ERROR);
1096     }
1097     else {			/* user wants to suppress time zones? */
1098       if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
1099 	time_t when = mail_longdate (&elt);
1100 	date = ctime (&when);	/* use traditional date */
1101       }
1102 				/* use POSIX-style date */
1103       else date = mail_cdate (tmp,&elt);
1104       if (!SIZE (message)) MM_LOG ("Append of zero-length message",ERROR);
1105       else if (!unix_collect_msg (tstream,sf,flags,date,message)) {
1106 	sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
1107 	MM_LOG (tmp,ERROR);
1108       }
1109 				/* get next message */
1110       else if (MM_APPEND (af) (tstream,data,&flags,&date,&message)) continue;
1111     }
1112     fclose (sf);		/* punt scratch file */
1113     return NIL;			/* give up */
1114   } while (message);		/* until no more messages */
1115   if (fflush (sf)) {
1116     sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
1117     MM_LOG (tmp,ERROR);
1118     fclose (sf);		/* punt scratch file */
1119     return NIL;			/* give up */
1120   }
1121   i = ftell (sf);		/* size of scratch file */
1122 				/* close sniffing stream */
1123   if (tstream != stream) tstream = mail_close (tstream);
1124 
1125   MM_CRITICAL (stream);		/* go critical */
1126 				/* try to open readwrite for UIDPLUS */
1127   if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
1128 				 OP_SILENT|OP_NOKOD)) && tstream->rdonly)
1129     tstream = mail_close (tstream);
1130   if (au && !tstream) {		/* wanted an APPENDUID? */
1131     sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox);
1132     MM_LOG (tmp,WARN);
1133     au = NIL;
1134   }
1135   if (((fd = unix_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
1136 		       (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
1137 			&lock,LOCK_EX)) < 0) ||
1138       !(df = fdopen (fd,"ab"))) {
1139     MM_NOCRITICAL (stream);	/* done with critical */
1140     sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
1141     MM_LOG (tmp,ERROR);
1142     return NIL;
1143   }
1144   fstat (fd,&sbuf);		/* get current file size */
1145   rewind (sf);
1146   tp[1] = time (0);		/* set mtime to now */
1147 				/* write all messages */
1148   if (!unix_append_msgs (tstream,sf,df,au ? dst : NIL) ||
1149       (fflush (df) == EOF) || fsync (fd)) {
1150     sprintf (buf,"Message append failed: %s",strerror (errno));
1151     MM_LOG (buf,ERROR);
1152     ftruncate (fd,sbuf.st_size);
1153     tp[0] =			/* preserve \Marked status */
1154       ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
1155       sbuf.st_atime : tp[1];
1156     ret = NIL;			/* return error */
1157   }
1158   else tp[0] = tp[1] - 1;	/* set atime to now-1 if successful copy */
1159   utime (file,tp);		/* set the times */
1160   fclose (sf);			/* done with scratch file */
1161 				/* force UIDVALIDITY assignment now */
1162   if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
1163 				/* return sets if doing APPENDUID */
1164   if (au && ret) (*au) (mailbox,tstream->uid_validity,dst);
1165   else mail_free_searchset (&dst);
1166   unix_unlock (fd,NIL,&lock);	/* unlock and close mailbox */
1167   fclose (df);			/* note that unix_unlock() released the fd */
1168   if (tstream) {		/* update last UID if we can */
1169     UNIXLOCAL *local = (UNIXLOCAL *) tstream->local;
1170     local->dirty = T;		/* do a rewrite */
1171     local->appending = T;	/* but not at the cost of marking as old */
1172     tstream = mail_close (tstream);
1173   }
1174   MM_NOCRITICAL (stream);	/* release critical */
1175   return ret;
1176 }
1177 
1178 /* Collect and write single message to append scratch file
1179  * Accepts: MAIL stream
1180  *	    scratch file
1181  *	    flags
1182  *	    date
1183  *	    message stringstruct
1184  * Returns: NIL if write error, else T
1185  */
1186 
unix_collect_msg(MAILSTREAM * stream,FILE * sf,char * flags,char * date,STRING * msg)1187 int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
1188 		     STRING *msg)
1189 {
1190   unsigned char *s,*t;
1191   unsigned long uf;
1192   long f = mail_parse_flags (stream,flags,&uf);
1193 				/* write metadata, note date ends with NL */
1194   if (fprintf (sf,"%ld %lu %s",f,SIZE (msg) + 1,date) < 0) return NIL;
1195   while (uf)			/* write user flags */
1196     if ((s = stream->user_flags[find_rightmost_bit (&uf)]) &&
1197 	(fprintf (sf," %s",s) < 0)) return NIL;
1198   if (putc ('\n',sf) == EOF) return NIL;
1199   while (SIZE (msg)) {		/* copy text to scratch file */
1200     for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s)
1201       if (!*s) *s = 0x80;	/* disallow NUL */
1202 				/* write buffered text */
1203     if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize)
1204       SETPOS (msg,GETPOS (msg) + msg->cursize);
1205     else return NIL;		/* failed */
1206   }
1207 				/* write trailing newline and return */
1208   return (putc ('\n',sf) == EOF) ? NIL : T;
1209 }
1210 
1211 /* Append messages from scratch file to mailbox
1212  * Accepts: MAIL stream
1213  *	    source file
1214  *	    destination file
1215  *	    uidset to update if non-NIL
1216  * Returns: T if success, NIL if failure
1217  */
1218 
unix_append_msgs(MAILSTREAM * stream,FILE * sf,FILE * df,SEARCHSET * set)1219 int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set)
1220 {
1221   int ti,zn,c;
1222   long f;
1223   unsigned long i,j;
1224   char *x,tmp[MAILTMPLEN];
1225   int hdrp = T;
1226 				/* get message metadata line */
1227   while (fgets (tmp,MAILTMPLEN,sf)) {
1228     if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL;
1229     f = strtol (tmp,&x,10);	/* get flags */
1230     if (!((*x++ == ' ') && isdigit (*x))) return NIL;
1231     i = strtoul (x,&x,10);	/* get message size */
1232     if ((*x++ != ' ') ||	/* build initial header */
1233 	(fprintf (df,"From %s@%s %sStatus: ",myusername(),mylocalhost(),x)<0)||
1234 	(f&fSEEN && (putc ('R',df) == EOF)) ||
1235 	(fputs ("\nX-Status: ",df) == EOF) ||
1236 	(f&fDELETED && (putc ('D',df) == EOF)) ||
1237 	(f&fFLAGGED && (putc ('F',df) == EOF)) ||
1238 	(f&fANSWERED && (putc ('A',df) == EOF)) ||
1239 	(f&fDRAFT && (putc ('T',df) == EOF)) ||
1240 	(fputs ("\nX-Keywords:",df) == EOF)) return NIL;
1241 				/* copy keywords */
1242     while ((c = getc (sf)) != '\n') switch (c) {
1243     case EOF:
1244       return NIL;
1245     default:
1246       if (putc (c,df) == EOF) return NIL;
1247     }
1248     if ((putc ('\n',df) == EOF) ||
1249 	(set && (fprintf (df,"X-UID: %lu\n",++(stream->uid_last)) < 0)))
1250       return NIL;
1251 
1252     for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) {
1253 				/* get read line length */
1254       if (i < (j = strlen (tmp))) fatal ("unix_append_msgs overrun");
1255       i -= j;			/* number of bytes left */
1256 				/* squish out CRs (note also copies NUL) */
1257       for (x = tmp; x = strchr (x,'\r'); --j) memmove (x,x+1,j-(x-tmp));
1258       if (!j) continue;		/* do nothing if line emptied */
1259 				/* start of line? */
1260       if ((c == '\n')) switch (tmp[0]) {
1261       case 'F':			/* possible "From " (case counts here) */
1262 	if ((j > 4) && (tmp[0] == 'F') && (tmp[1] == 'r') && (tmp[2] == 'o') &&
1263 	    (tmp[3] == 'm') && (tmp[4] == ' ')) {
1264 	  if (!unix_fromwidget) {
1265 	    VALID (tmp,x,ti,zn);/* conditional, only write widget if */
1266 	    if (!ti) break;	/*  it looks like a valid header */
1267 	  }			/* write the widget */
1268 	  if (putc ('>',df) == EOF) return NIL;
1269 	}
1270 	break;
1271       case 'S': case 's':	/* possible "Status:" */
1272 	if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) &&
1273 	    ((tmp[2] == 'a') || (tmp[2] == 'A')) &&
1274 	    ((tmp[3] == 't') || (tmp[3] == 'T')) &&
1275 	    ((tmp[4] == 'u') || (tmp[4] == 'U')) &&
1276 	    ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') &&
1277 	    (fputs ("X-Original-",df) == EOF)) return NIL;
1278 	break;
1279       case 'X': case 'x':	/* possible X-??? header */
1280 	if (hdrp && (tmp[1] == '-') &&
1281 				/* possible X-UID: */
1282 	    (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) &&
1283 	      ((tmp[3] == 'I') || (tmp[3] == 'i')) &&
1284 	      ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) ||
1285 				/* possible X-IMAP: */
1286 	     ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) &&
1287 	      ((tmp[3] == 'M') || (tmp[3] == 'm')) &&
1288 	      ((tmp[4] == 'A') || (tmp[4] == 'a')) &&
1289 	      ((tmp[5] == 'P') || (tmp[5] == 'p')) &&
1290 	      ((tmp[6] == ':') ||
1291 				/* or X-IMAPbase: */
1292 	       ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) &&
1293 		((tmp[7] == 'a') || (tmp[7] == 'A')) &&
1294 		((tmp[8] == 's') || (tmp[8] == 'S')) &&
1295 		((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) ||
1296 				/* possible X-Status: */
1297 	     ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) &&
1298 	      ((tmp[3] == 't') || (tmp[3] == 'T')) &&
1299 	      ((tmp[4] == 'a') || (tmp[4] == 'A')) &&
1300 	      ((tmp[5] == 't') || (tmp[5] == 'T')) &&
1301 	      ((tmp[6] == 'u') || (tmp[6] == 'U')) &&
1302 	      ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) ||
1303 				/* possible X-Keywords: */
1304 	     ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) &&
1305 	      ((tmp[3] == 'e') || (tmp[3] == 'E')) &&
1306 	      ((tmp[4] == 'y') || (tmp[4] == 'Y')) &&
1307 	      ((tmp[5] == 'w') || (tmp[5] == 'W')) &&
1308 	      ((tmp[6] == 'o') || (tmp[6] == 'O')) &&
1309 	      ((tmp[7] == 'r') || (tmp[7] == 'R')) &&
1310 	      ((tmp[8] == 'd') || (tmp[8] == 'D')) &&
1311 	      ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) &&
1312 	    (fputs ("X-Original-",df) == EOF)) return NIL;
1313       case '\n':		/* blank line */
1314 	hdrp = NIL;
1315 	break;
1316       default:			/* nothing to do */
1317 	break;
1318       }
1319 				/* just write the line */
1320       if (fwrite (tmp,1,j,df) != j) return NIL;
1321     }
1322     if (i) return NIL;		/* didn't read entire message */
1323 				/* update set */
1324     if (stream) mail_append_set (set,stream->uid_last);
1325   }
1326   return T;
1327 }
1328 
1329 /* Internal routines */
1330 
1331 
1332 /* UNIX mail abort stream
1333  * Accepts: MAIL stream
1334  */
1335 
unix_abort(MAILSTREAM * stream)1336 void unix_abort (MAILSTREAM *stream)
1337 {
1338   if (LOCAL) {			/* only if a file is open */
1339     if (LOCAL->fd >= 0) close (LOCAL->fd);
1340     if (LOCAL->ld >= 0) {	/* have a mailbox lock? */
1341       flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
1342       close (LOCAL->ld);	/* close the lock file */
1343       unlink (LOCAL->lname);	/* and delete it */
1344     }
1345     if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
1346 				/* free local text buffers */
1347     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
1348     if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
1349     if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf);
1350     if (LOCAL->line) fs_give ((void **) &LOCAL->line);
1351 				/* nuke the local data */
1352     fs_give ((void **) &stream->local);
1353     stream->dtb = NIL;		/* log out the DTB */
1354   }
1355 }
1356 
1357 /* UNIX open and lock mailbox
1358  * Accepts: file name to open/lock
1359  *	    file open mode
1360  *	    destination buffer for lock file name
1361  *	    type of locking operation (LOCK_SH or LOCK_EX)
1362  */
1363 
unix_lock(char * file,int flags,int mode,DOTLOCK * lock,int op)1364 int unix_lock (char *file,int flags,int mode,DOTLOCK *lock,int op)
1365 {
1366   int fd;
1367   blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
1368   (*bn) (BLOCK_FILELOCK,NIL);
1369 				/* try locking the easy way */
1370   if (dotlock_lock (file,lock,-1)) {
1371 				/* got dotlock file, easy open */
1372     if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
1373     else dotlock_unlock (lock);	/* open failed, free the dotlock */
1374   }
1375 				/* no dot lock file, open file now */
1376   else if ((fd = open (file,flags,mode)) >= 0) {
1377 				/* try paranoid way to make a dot lock file */
1378     if (dotlock_lock (file,lock,fd)) {
1379       close (fd);		/* get fresh fd in case of timing race */
1380       if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
1381 				/* open failed, free the dotlock */
1382       else dotlock_unlock (lock);
1383     }
1384     else flock (fd,op);		/* paranoid way failed, just flock() it */
1385   }
1386   (*bn) (BLOCK_NONE,NIL);
1387   return fd;
1388 }
1389 
1390 /* UNIX unlock and close mailbox
1391  * Accepts: file descriptor
1392  *	    (optional) mailbox stream to check atime/mtime
1393  *	    (optional) lock file name
1394  */
1395 
unix_unlock(int fd,MAILSTREAM * stream,DOTLOCK * lock)1396 void unix_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock)
1397 {
1398   if (stream) {			/* need to muck with times? */
1399     struct stat sbuf;
1400     time_t tp[2];
1401     time_t now = time (0);
1402     fstat (fd,&sbuf);		/* get file times */
1403     if (LOCAL->ld >= 0) {	/* yes, readwrite session? */
1404       tp[0] = now;		/* set atime to now */
1405 				/* set mtime to (now - 1) if necessary */
1406       tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
1407     }
1408     else if (stream->recent) {	/* readonly with recent messages */
1409       if ((sbuf.st_atime >= sbuf.st_mtime) ||
1410 	  (sbuf.st_atime >= sbuf.st_ctime))
1411 				/* keep past mtime, whack back atime */
1412 	tp[0] = (tp[1] = (sbuf.st_mtime < now) ? sbuf.st_mtime : now) - 1;
1413       else now = 0;		/* no time change needed */
1414     }
1415 				/* readonly with no recent messages */
1416     else if ((sbuf.st_atime < sbuf.st_mtime) ||
1417 	     (sbuf.st_atime < sbuf.st_ctime)) {
1418       tp[0] = now;		/* set atime to now */
1419 				/* set mtime to (now - 1) if necessary */
1420       tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
1421     }
1422     else now = 0;		/* no time change needed */
1423 				/* set the times, note change */
1424     if (now && !utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
1425   }
1426   flock (fd,LOCK_UN);		/* release flock'ers */
1427   if (!stream) close (fd);	/* close the file if no stream */
1428   dotlock_unlock (lock);	/* flush the lock file if any */
1429 }
1430 
1431 /* UNIX mail parse and lock mailbox
1432  * Accepts: MAIL stream
1433  *	    space to write lock file name
1434  *	    type of locking operation
1435  * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
1436  */
1437 
unix_parse(MAILSTREAM * stream,DOTLOCK * lock,int op)1438 int unix_parse (MAILSTREAM *stream,DOTLOCK *lock,int op)
1439 {
1440   int zn;
1441   unsigned long i,j,k,m;
1442   unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
1443   int ti = 0,retain = T;
1444   unsigned long nmsgs = stream->nmsgs;
1445   unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
1446   unsigned long recent = stream->recent;
1447   unsigned long oldnmsgs = stream->nmsgs;
1448   short silent = stream->silent;
1449   short pseudoseen = NIL;
1450   struct stat sbuf;
1451   STRING bs;
1452   FDDATA d;
1453   MESSAGECACHE *elt;
1454   mail_lock (stream);		/* guard against recursion or pingers */
1455 				/* toss out previous descriptor */
1456   if (LOCAL->fd >= 0) close (LOCAL->fd);
1457   MM_CRITICAL (stream);		/* open and lock mailbox (shared OK) */
1458   if ((LOCAL->fd = unix_lock (stream->mailbox,(LOCAL->ld >= 0) ?
1459 			      O_RDWR : O_RDONLY,
1460 			      (long)mail_parameters(NIL,GET_MBXPROTECTION,NIL),
1461 			      lock,op)) < 0) {
1462     sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
1463     MM_LOG (tmp,ERROR);
1464     unix_abort (stream);
1465     mail_unlock (stream);
1466     MM_NOCRITICAL (stream);	/* done with critical */
1467     return NIL;
1468   }
1469   fstat (LOCAL->fd,&sbuf);	/* get status */
1470 				/* validate change in size */
1471   if (sbuf.st_size < LOCAL->filesize) {
1472     sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
1473 	     (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
1474     MM_LOG (tmp,ERROR);		/* this is pretty bad */
1475     unix_unlock (LOCAL->fd,stream,lock);
1476     unix_abort (stream);
1477     mail_unlock (stream);
1478     MM_NOCRITICAL (stream);	/* done with critical */
1479     return NIL;
1480   }
1481 
1482 				/* new data? */
1483   else if (i = sbuf.st_size - LOCAL->filesize) {
1484     d.fd = LOCAL->fd;		/* yes, set up file descriptor */
1485     d.pos = LOCAL->filesize;	/* get to that position in the file */
1486     d.chunk = LOCAL->buf;	/* initial buffer chunk */
1487     d.chunksize = CHUNKSIZE;	/* file chunk size */
1488     INIT (&bs,fd_string,&d,i);	/* initialize stringstruct */
1489 				/* skip leading whitespace for broken MTAs */
1490     while (((c = CHR (&bs)) == '\n') || (c == '\r') ||
1491 	   (c == ' ') || (c == '\t')) SNX (&bs);
1492     if (SIZE (&bs)) {		/* read new data */
1493 				/* remember internal header position */
1494       j = LOCAL->filesize + GETPOS (&bs);
1495       s = unix_mbxline (stream,&bs,&i);
1496       t = NIL,zn = 0;
1497       if (i) VALID (s,t,ti,zn);	/* see if valid From line */
1498       if (!ti) {		/* someone pulled the rug from under us */
1499 	sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
1500 		 (char *) s);
1501 	MM_LOG (tmp,ERROR);
1502 	unix_unlock (LOCAL->fd,stream,lock);
1503 	unix_abort (stream);
1504 	mail_unlock (stream);
1505 				/* done with critical */
1506 	MM_NOCRITICAL (stream);
1507 	return NIL;
1508       }
1509       stream->silent = T;	/* quell main program new message events */
1510       do {			/* found a message */
1511 				/* instantiate first new message */
1512 	mail_exists (stream,++nmsgs);
1513 	(elt = mail_elt (stream,nmsgs))->valid = T;
1514 	recent++;		/* assume recent by default */
1515 	elt->recent = T;
1516 				/* note position/size of internal header */
1517 	elt->private.special.offset = j;
1518 	elt->private.msg.header.offset = elt->private.special.text.size = i;
1519 
1520 				/* generate plausible IMAPish date string */
1521 	date[2] = date[6] = date[20] = '-'; date[11] = ' ';
1522 	date[14] = date[17] = ':';
1523 				/* dd */
1524 	date[0] = t[ti - 2]; date[1] = t[ti - 1];
1525 				/* mmm */
1526 	date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
1527 				/* hh */
1528 	date[12] = t[ti + 1]; date[13] = t[ti + 2];
1529 				/* mm */
1530 	date[15] = t[ti + 4]; date[16] = t[ti + 5];
1531 	if (t[ti += 6] == ':') {/* ss */
1532 	  date[18] = t[++ti]; date[19] = t[++ti];
1533 	  ti++;			/* move to space */
1534 	}
1535 	else date[18] = date[19] = '0';
1536 				/* yy -- advance over timezone if necessary */
1537 	if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
1538 	date[7] = t[ti + 1]; date[8] = t[ti + 2];
1539 	date[9] = t[ti + 3]; date[10] = t[ti + 4];
1540 				/* zzz */
1541 	t = zn ? (t + zn + 1) : (unsigned char *) "LCL";
1542 	date[21] = *t++; date[22] = *t++; date[23] = *t++;
1543 	if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
1544 	else {			/* numeric time zone */
1545 	  date[24] = *t++; date[25] = *t++;
1546 	  date[26] = '\0'; date[20] = ' ';
1547 	}
1548 				/* set internal date */
1549 	if (!mail_parse_date (elt,date)) {
1550 	  sprintf (tmp,"Unable to parse internal date: %s",(char *) date);
1551 	  MM_LOG (tmp,WARN);
1552 	}
1553 
1554 	do {			/* look for message body */
1555 	  s = t = unix_mbxline (stream,&bs,&i);
1556 	  if (i) switch (*s) {	/* check header lines */
1557 	  case 'X':		/* possible X-???: line */
1558 	    if (s[1] == '-') {	/* must be immediately followed by hyphen */
1559 				/* X-Status: becomes Status: in S case */
1560 	      if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
1561 		  s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
1562 				/* possible X-Keywords */
1563 	      else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
1564 		       s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
1565 		       s[8] == 'd' && s[9] == 's' && s[10] == ':') {
1566 		SIZEDTEXT uf;
1567 		retain = NIL;	/* don't retain continuation */
1568 		s += 11;	/* flush leading whitespace */
1569 		while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){
1570 		  while (*s == ' ') s++;
1571 				/* find end of keyword */
1572 		  if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s);
1573 				/* got a keyword? */
1574 		  if ((k = (u - s)) && (k <= MAXUSERFLAG)) {
1575 		    uf.data = (unsigned char *) s;
1576 		    uf.size = k;
1577 		    for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j)
1578 		      if (!compare_csizedtext (stream->user_flags[j],&uf)) {
1579 			elt->user_flags |= ((long) 1) << j;
1580 			break;
1581 		      }
1582  		  }
1583 		  s = u;	/* advance to next keyword */
1584 		}
1585 		break;
1586 	      }
1587 
1588 				/* possible X-IMAP */
1589 	      else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') &&
1590 		       (s[5] == 'P') && ((m = (s[6] == ':')) ||
1591 					 ((s[6] == 'b') && (s[7] == 'a') &&
1592 					  (s[8] == 's') && (s[9] == 'e') &&
1593 					  (s[10] == ':')))) {
1594 		retain = NIL;	/* don't retain continuation */
1595 		if ((nmsgs == 1) && !stream->uid_validity) {
1596 				/* advance to data */
1597 		  s += m ? 7 : 11;
1598 				/* flush whitespace */
1599 		  while (*s == ' ') s++;
1600 		  j = 0;	/* slurp UID validity */
1601 				/* found a digit? */
1602 		  while (isdigit (*s)) {
1603 		    j *= 10;	/* yes, add it in */
1604 		    j += *s++ - '0';
1605 		  }
1606 				/* flush whitespace */
1607 		  while (*s == ' ') s++;
1608 				/* must have valid UID validity and UID last */
1609 		  if (j && isdigit (*s)) {
1610 				/* pseudo-header seen if X-IMAP */
1611 		    if (m) pseudoseen = LOCAL->pseudo = T;
1612 				/* save UID validity */
1613 		    stream->uid_validity = j;
1614 		    j = 0;	/* slurp UID last */
1615 		    while (isdigit (*s)) {
1616 		      j *= 10;	/* yes, add it in */
1617 		      j += *s++ - '0';
1618 		    }
1619 				/* save UID last */
1620 		    stream->uid_last = j;
1621 				/* process keywords */
1622 		    for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n'));
1623 			 s = u,j++) {
1624 				/* flush leading whitespace */
1625 		      while (*s == ' ') s++;
1626 		      u = strpbrk (s," \n\r");
1627 				/* got a keyword? */
1628 		      if ((j < NUSERFLAGS) && (k = (u - s)) &&
1629 			  (k <= MAXUSERFLAG)) {
1630 			if (stream->user_flags[j])
1631 			  fs_give ((void **) &stream->user_flags[j]);
1632 			stream->user_flags[j] = (char *) fs_get (k + 1);
1633 			strncpy (stream->user_flags[j],s,k);
1634 			stream->user_flags[j][k] = '\0';
1635 		      }
1636 		    }
1637 		  }
1638 		}
1639 		break;
1640 	      }
1641 
1642 				/* possible X-UID */
1643 	      else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
1644 		       s[5] == ':') {
1645 		retain = NIL;	/* don't retain continuation */
1646 				/* only believe if have a UID validity */
1647 		if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) {
1648 		  s += 6;	/* advance to UID value */
1649 				/* flush whitespace */
1650 		  while (*s == ' ') s++;
1651 		  j = 0;
1652 				/* found a digit? */
1653 		  while (isdigit (*s)) {
1654 		    j *= 10;	/* yes, add it in */
1655 		    j += *s++ - '0';
1656 		  }
1657 				/* flush remainder of line */
1658 		  while (*s != '\n') s++;
1659 				/* make sure not duplicated */
1660 		  if (elt->private.uid)
1661 		    sprintf (tmp,"Message %lu UID %lu already has UID %lu",
1662 			     pseudoseen ? elt->msgno - 1 : elt->msgno,
1663 			     j,elt->private.uid);
1664 				/* make sure UID doesn't go backwards */
1665 		  else if (j <= prevuid)
1666 		    sprintf (tmp,"Message %lu UID %lu less than %lu",
1667 			     pseudoseen ? elt->msgno - 1 : elt->msgno,
1668 			     j,prevuid + 1);
1669 #if 0	/* this is currently broken by UIDPLUS */
1670 				/* or skip by mailbox's recorded last */
1671 		  else if (j > stream->uid_last)
1672 		    sprintf (tmp,"Message %lu UID %lu greater than last %lu",
1673 			     pseudoseen ? elt->msgno - 1 : elt->msgno,
1674 			     j,stream->uid_last);
1675 #endif
1676 		  else {	/* normal UID case */
1677 		    prevuid = elt->private.uid = j;
1678 #if 1	/* temporary kludge for UIDPLUS */
1679 		    if (prevuid > stream->uid_last) {
1680 		      stream->uid_last = prevuid;
1681 		      LOCAL->ddirty = LOCAL->dirty = T;
1682 		    }
1683 #endif
1684 		    break;	/* exit this cruft */
1685 		  }
1686 		  MM_LOG (tmp,WARN);
1687 				/* invalidate UID validity */
1688 		  stream->uid_validity = 0;
1689 		  elt->private.uid = 0;
1690 		}
1691 		break;
1692 	      }
1693 	    }
1694 				/* otherwise fall into S case */
1695 
1696 	  case 'S':		/* possible Status: line */
1697 	    if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
1698 		s[4] == 'u' && s[5] == 's' && s[6] == ':') {
1699 	      retain = NIL;	/* don't retain continuation */
1700 	      s += 6;		/* advance to status flags */
1701 	      do switch (*s++) {/* parse flags */
1702 	      case 'R':		/* message read */
1703 		elt->seen = T;
1704 		break;
1705 	      case 'O':		/* message old */
1706 		if (elt->recent) {
1707 		  elt->recent = NIL;
1708 		  recent--;	/* it really wasn't recent */
1709 		}
1710 		break;
1711 	      case 'D':		/* message deleted */
1712 		elt->deleted = T;
1713 		break;
1714 	      case 'F':		/* message flagged */
1715 		elt->flagged = T;
1716 		break;
1717 	      case 'A':		/* message answered */
1718 		elt->answered = T;
1719 		break;
1720 	      case 'T':		/* message is a draft */
1721 		elt->draft = T;
1722 		break;
1723 	      default:		/* some other crap */
1724 		break;
1725 	      } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n')));
1726 	      break;		/* all done */
1727 	    }
1728 				/* otherwise fall into default case */
1729 
1730 	  default:		/* ordinary header line */
1731 	    if ((*s == 'S') || (*s == 's') ||
1732 		(((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) {
1733 	      unsigned char *e,*v;
1734 				/* must match what mail_filter() does */
1735 	      for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1);
1736 		   (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') &&
1737 		   ((c > ' ') || ((c != ' ') && (c != '\t') &&
1738 				  (c != '\r') && (c != '\n')));
1739 		   *v++ = *u++);
1740 	      *v = '\0';	/* tie off */
1741 				/* matches internal header? */
1742 	      if (!compare_cstring (tmp,"STATUS") ||
1743 		  !compare_cstring (tmp,"X-STATUS") ||
1744 		  !compare_cstring (tmp,"X-KEYWORDS") ||
1745 		  !compare_cstring (tmp,"X-UID") ||
1746 		  !compare_cstring (tmp,"X-IMAP") ||
1747 		  !compare_cstring (tmp,"X-IMAPBASE")) {
1748 		char err[MAILTMPLEN];
1749 		sprintf (err,"Discarding bogus %s header in message %lu",
1750 			 (char *) tmp,elt->msgno);
1751 		MM_LOG (err,WARN);
1752 		retain = NIL;	/* don't retain continuation */
1753 		break;		/* different case or something */
1754 	      }
1755 	    }
1756 				/* retain or non-continuation? */
1757 	    if (retain || ((*s != ' ') && (*s != '\t'))) {
1758 	      retain = T;	/* retaining continuation now */
1759 				/* line length in LF format newline */
1760 	      for (j = k = 0; j < i; ++j) if (s[j] != '\r') ++k;
1761 				/* "internal" header size */
1762 	      elt->private.spare.data += k;
1763 				/* message size */
1764 	      elt->rfc822_size += k + 1;
1765 	    }
1766 	    else {
1767 	      char err[MAILTMPLEN];
1768 	      sprintf (err,"Discarding bogus continuation in msg %lu: %.80s",
1769 		      elt->msgno,(char *) s);
1770 	      if (u = strpbrk (err,"\r\n")) *u = '\0';
1771 	      MM_LOG (err,WARN);
1772 	      break;		/* different case or something */
1773 	    }
1774 	    break;
1775 	  }
1776 	} while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n')));
1777 				/* "internal" header sans trailing newline */
1778 	if (i) elt->private.spare.data--;
1779 				/* assign a UID if none found */
1780 	if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) {
1781 	  prevuid = elt->private.uid = ++stream->uid_last;
1782 	  elt->private.dirty = T;
1783 	  LOCAL->ddirty = T;	/* force update */
1784 	}
1785 	else elt->private.dirty = elt->recent;
1786 
1787 				/* note size of header, location of text */
1788 	elt->private.msg.header.text.size =
1789 	  (elt->private.msg.text.offset =
1790 	   (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) -
1791 	     elt->private.special.text.size;
1792 	k = m = 0;		/* no previous line size yet */
1793 				/* note current position */
1794 	j = LOCAL->filesize + GETPOS (&bs);
1795 	if (i) do {		/* look for next message */
1796 	  s = unix_mbxline (stream,&bs,&i);
1797 	  if (i) {		/* got new data? */
1798 	    VALID (s,t,ti,zn);	/* yes, parse line */
1799 	    if (!ti) {		/* not a header line, add it to message */
1800 	      elt->rfc822_size += i;
1801 	      for (j = 0; j < i; ++j) switch (s[j]) {
1802 	      case '\r':	/* squeeze out CRs */
1803 		elt->rfc822_size -= 1;
1804 		break;
1805 	      case '\n':	/* LF becomes CRLF */
1806 		elt->rfc822_size += 1;
1807 		break;
1808 	      default:
1809 		break;
1810 	      }
1811 	      if ((i == 1) && (*s == '\n')) {
1812 		k = 2;
1813 		m = 1;
1814 	      }
1815 	      else if ((i == 2) && (*s == '\r') && (s[1] == '\n'))
1816 		k = m = 2;
1817 	      else k = m = 0;	/* file does not end with newline! */
1818 				/* update current position */
1819 	      j = LOCAL->filesize + GETPOS (&bs);
1820 	    }
1821 	  }
1822 	} while (i && !ti);	/* until found a header */
1823 	elt->private.msg.text.text.size = j -
1824 	  (elt->private.special.offset + elt->private.msg.text.offset);
1825 				/* flush ending blank line */
1826 	elt->private.msg.text.text.size -= m;
1827 	elt->rfc822_size -= k;
1828 				/* until end of buffer */
1829       } while (!stream->sniff && i);
1830       if (pseudoseen) {		/* flush pseudo-message if present */
1831 				/* decrement recent count */
1832 	if (mail_elt (stream,1)->recent) recent--;
1833 				/* and the exists count */
1834 	mail_exists (stream,nmsgs--);
1835 	mail_expunged(stream,1);/* fake an expunge of that message */
1836       }
1837 				/* need to start a new UID validity? */
1838       if (!stream->uid_validity) {
1839 	stream->uid_validity = time (0);
1840 				/* in case a whiner with no life */
1841 	if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL))
1842 	  stream->uid_nosticky = T;
1843 	else if (nmsgs) {	/* don't bother if empty file */
1844 				/* make dirty to restart UID epoch */
1845 	  LOCAL->ddirty = LOCAL->dirty = T;
1846 				/* need to rewrite msg 1 if not pseudo */
1847 	  if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T;
1848 	  MM_LOG ("Assigning new unique identifiers to all messages",NIL);
1849 	}
1850       }
1851       stream->nmsgs = oldnmsgs;	/* whack it back down */
1852       stream->silent = silent;	/* restore old silent setting */
1853 				/* notify upper level of new mailbox sizes */
1854       mail_exists (stream,nmsgs);
1855       mail_recent (stream,recent);
1856 				/* mark dirty so O flags are set */
1857       if (recent) LOCAL->dirty = T;
1858     }
1859   }
1860 				/* no change, don't babble if never got time */
1861   else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
1862     MM_LOG ("New mailbox modification time but apparently no changes",WARN);
1863 				/* update parsed file size and time */
1864   LOCAL->filesize = sbuf.st_size;
1865   LOCAL->filetime = sbuf.st_mtime;
1866   return T;			/* return the winnage */
1867 }
1868 
1869 /* UNIX read line from mailbox
1870  * Accepts: mail stream
1871  *	    stringstruct
1872  *	    pointer to line size
1873  * Returns: pointer to input line
1874  */
1875 
unix_mbxline(MAILSTREAM * stream,STRING * bs,unsigned long * size)1876 char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
1877 {
1878   unsigned long i,j,k,m;
1879   char *s,*t,*te;
1880   char *ret = "";
1881 				/* flush old buffer */
1882   if (LOCAL->line) fs_give ((void **) &LOCAL->line);
1883 				/* if buffer needs refreshing */
1884   if (!bs->cursize) SETPOS (bs,GETPOS (bs));
1885   if (SIZE (bs)) {		/* find newline */
1886 				/* end of fast scan */
1887     te = (t = (s = bs->curpos) + bs->cursize) - 12;
1888     while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
1889 		       (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
1890 		       (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
1891 		       (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
1892       --s;			/* back up */
1893       break;			/* exit loop */
1894     }
1895 				/* final character-at-a-time scan */
1896     while ((s < t) && (*s != '\n')) ++s;
1897 				/* difficult case if line spans buffer */
1898     if ((i = s - bs->curpos) == bs->cursize) {
1899 				/* have space in line buffer? */
1900       if (i > LOCAL->linebuflen) {
1901 	fs_give ((void **) &LOCAL->linebuf);
1902 	LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i);
1903       }
1904 				/* remember what we have so far */
1905       memcpy (LOCAL->linebuf,bs->curpos,i);
1906 				/* load next buffer */
1907       SETPOS (bs,k = GETPOS (bs) + i);
1908 				/* end of fast scan */
1909       te = (t = (s = bs->curpos) + bs->cursize) - 12;
1910 				/* fast scan in overlap buffer */
1911       while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
1912 			 (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
1913 			 (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
1914 			 (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
1915 	--s;			/* back up */
1916 	break;			/* exit loop */
1917       }
1918 
1919 				/* final character-at-a-time scan */
1920       while ((s < t) && (*s != '\n')) ++s;
1921 				/* huge line? */
1922       if ((j = s - bs->curpos) == bs->cursize) {
1923 	SETPOS (bs,GETPOS (bs) + j);
1924 				/* look for end of line (s-l-o-w!!) */
1925 	for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
1926 	SETPOS (bs,k);		/* go back to where it started */
1927       }
1928 				/* got size of data, make buffer for return */
1929       ret = LOCAL->line = (char *) fs_get (i + j + 2);
1930 				/* copy first chunk */
1931       memcpy (ret,LOCAL->linebuf,i);
1932       while (j) {		/* copy remainder */
1933 	if (!bs->cursize) SETPOS (bs,GETPOS (bs));
1934 	memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
1935 	i += k;			/* account for this much read in */
1936 	j -= k;
1937 	bs->curpos += k;	/* increment new position */
1938 	bs->cursize -= k;	/* eat that many bytes */
1939       }
1940       if (!bs->cursize) SETPOS (bs,GETPOS (bs));
1941 				/* read newline at end */
1942       if (SIZE (bs)) ret[i++] = SNX (bs);
1943       ret[i] = '\0';		/* makes debugging easier */
1944     }
1945     else {			/* this is easy */
1946       ret = bs->curpos;		/* string it at this position */
1947       bs->curpos += ++i;	/* increment new position */
1948       bs->cursize -= i;		/* eat that many bytes */
1949     }
1950     *size = i;			/* return that to user */
1951   }
1952   else *size = 0;		/* end of data, return empty */
1953   return ret;
1954 }
1955 
1956 /* UNIX make pseudo-header
1957  * Accepts: MAIL stream
1958  *	    buffer to write pseudo-header
1959  * Returns: length of pseudo-header
1960  */
1961 
unix_pseudo(MAILSTREAM * stream,char * hdr)1962 unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr)
1963 {
1964   int i;
1965   char *s,tmp[MAILTMPLEN];
1966   time_t now = time (0);
1967   rfc822_fixed_date (tmp);
1968   sprintf (hdr,"From %s %.24s\nDate: %s\nFrom: %s <%s@%.80s>\nSubject: %s\nMessage-ID: <%lu@%.80s>\nX-IMAP: %010lu %010lu",
1969 	   pseudo_from,ctime (&now),
1970 	   tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
1971 	   (unsigned long) now,mylocalhost (),stream->uid_validity,
1972 	   stream->uid_last);
1973   for (s = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i)
1974     if (stream->user_flags[i])
1975       sprintf (s += strlen (s)," %s",stream->user_flags[i]);
1976   sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n\n",pseudo_msg);
1977   return strlen (hdr);		/* return header length */
1978 }
1979 
1980 /* UNIX make status string
1981  * Accepts: MAIL stream
1982  *	    destination string to write
1983  *	    message cache entry
1984  *	    UID to write if non-zero (else use elt->private.uid)
1985  *	    non-zero flag to write UID (.LT. 0 to write UID base info too)
1986  * Returns: length of string
1987  */
1988 
unix_xstatus(MAILSTREAM * stream,char * status,MESSAGECACHE * elt,unsigned long uid,long flag)1989 unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
1990 			    unsigned long uid,long flag)
1991 {
1992   char *t,stack[64];
1993   char *s = status;
1994   unsigned long n;
1995   int pad = 50;
1996   int sticky = uid ? T : !stream->uid_nosticky;
1997   /* This used to use sprintf(), but thanks to certain cretinous C libraries
1998      with horribly slow implementations of sprintf() I had to change it to this
1999      mess.  At least it should be fast. */
2000   if ((flag < 0) && sticky) {	/* need to write X-IMAPbase: header? */
2001     *s++ = 'X'; *s++ = '-'; *s++ = 'I'; *s++ = 'M'; *s++ = 'A'; *s++ = 'P';
2002     *s++ = 'b'; *s++ = 'a'; *s++ = 's'; *s++ = 'e'; *s++ = ':'; *s++ = ' ';
2003     t = stack;
2004     n = stream->uid_validity;	/* push UID validity digits on the stack */
2005     do *t++ = (char) (n % 10) + '0';
2006     while (n /= 10);
2007 				/* pop UID validity digits from stack */
2008     while (t > stack) *s++ = *--t;
2009     *s++ = ' ';
2010     n = stream->uid_last;	/* push UID last digits on the stack */
2011     do *t++ = (char) (n % 10) + '0';
2012     while (n /= 10);
2013 				/* pop UID last digits from stack */
2014     while (t > stack) *s++ = *--t;
2015     for (n = 0; n < NUSERFLAGS; ++n) if (t = stream->user_flags[n])
2016       for (*s++ = ' '; *t; *s++ = *t++);
2017     *s++ = '\n';
2018     pad += 30;			/* increased padding if have IMAPbase */
2019   }
2020   *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
2021   *s++ = ':'; *s++ = ' ';
2022   if (elt->seen) *s++ = 'R';
2023 				/* only write O if have a UID */
2024   if (flag && (!elt->recent || !LOCAL->appending)) *s++ = 'O';
2025   *s++ = '\n';
2026   *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
2027   *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
2028   if (elt->deleted) *s++ = 'D';
2029   if (elt->flagged) *s++ = 'F';
2030   if (elt->answered) *s++ = 'A';
2031   if (elt->draft) *s++ = 'T';
2032   *s++ = '\n';
2033 
2034   if (sticky) {			/* only do this if UIDs sticky */
2035     *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
2036     *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
2037     if (n = elt->user_flags) do {
2038       *s++ = ' ';
2039       for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++);
2040     } while (n);
2041     n = s - status;		/* get size of stuff so far */
2042 				/* pad X-Keywords to make size constant */
2043     if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' ';
2044     *s++ = '\n';
2045     if (flag) {			/* want to include UID? */
2046       t = stack;
2047 				/* push UID digits on the stack */
2048       n = uid ? uid : elt->private.uid;
2049       do *t++ = (char) (n % 10) + '0';
2050       while (n /= 10);
2051       *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
2052       *s++ = ' ';
2053 				/* pop UID from stack */
2054       while (t > stack) *s++ = *--t;
2055       *s++ = '\n';
2056     }
2057   }
2058   *s++ = '\n'; *s = '\0';	/* end of extended message status */
2059   return s - status;		/* return size of resulting string */
2060 }
2061 
2062 /* Rewrite mailbox file
2063  * Accepts: MAIL stream, must be critical and locked
2064  *	    return pointer to number of expunged messages if want expunge
2065  *	    lock file name
2066  *	    expunge sequence, not deleted flag
2067  * Returns: T if success and mailbox unlocked, NIL if failure
2068  */
2069 
2070 #define OVERFLOWBUFLEN 8192	/* initial overflow buffer length */
2071 
unix_rewrite(MAILSTREAM * stream,unsigned long * nexp,DOTLOCK * lock,long flags)2072 long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
2073 		   long flags)
2074 {
2075   MESSAGECACHE *elt;
2076   UNIXFILE f;
2077   char *s;
2078   time_t tp[2];
2079   long ret,flag;
2080   unsigned long i,j;
2081   unsigned long recent = stream->recent;
2082   unsigned long size = LOCAL->pseudo ? unix_pseudo (stream,LOCAL->buf) : 0;
2083   if (nexp) *nexp = 0;		/* initially nothing expunged */
2084 				/* calculate size of mailbox after rewrite */
2085   for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) {
2086     elt = mail_elt (stream,i);	/* get cache */
2087     if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) {
2088 				/* add RFC822 size of this message */
2089       size += elt->private.special.text.size + elt->private.spare.data +
2090 	unix_xstatus (stream,LOCAL->buf,elt,NIL,flag) +
2091 	  elt->private.msg.text.text.size + 1;
2092       flag = 1;			/* only count X-IMAPbase once */
2093     }
2094   }
2095 				/* no messages, has a life, and no pseudo */
2096   if (!size && !mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) {
2097     LOCAL->pseudo = T;		/* so make a pseudo-message now */
2098     size = unix_pseudo (stream,LOCAL->buf);
2099   }
2100 				/* extend the file as necessary */
2101   if (ret = unix_extend (stream,size)) {
2102     /* Set up buffered I/O file structure
2103      * curpos	current position being written through buffering
2104      * filepos	current position being written physically to the disk
2105      * bufpos	current position being written in the buffer
2106      * protect	current maximum position that can be written to the disk
2107      *		before buffering is forced
2108      * The code tries to buffer so that that disk is written in multiples of
2109      * OVERBLOWBUFLEN bytes.
2110      */
2111     f.stream = stream;		/* note mail stream */
2112     f.curpos = f.filepos = 0;	/* start of file */
2113     f.protect = stream->nmsgs ?	/* initial protection pointer */
2114     mail_elt (stream,1)->private.special.offset : 8192;
2115     f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN);
2116 
2117     if (LOCAL->pseudo)		/* update pseudo-header */
2118       unix_write (&f,LOCAL->buf,unix_pseudo (stream,LOCAL->buf));
2119 				/* loop through all messages */
2120     for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) {
2121       elt = mail_elt (stream,i);/* get cache */
2122 				/* expunge this message? */
2123       if (nexp && elt->deleted && (flags ? elt->sequence : T)) {
2124 				/* one less recent message */
2125 	if (elt->recent) --recent;
2126 	mail_expunged(stream,i);/* notify upper levels */
2127 	++*nexp;		/* count up one more expunged message */
2128       }
2129       else {			/* preserve this message */
2130 	i++;			/* advance to next message */
2131 	if ((flag < 0) ||	/* need to rewrite message? */
2132 	    elt->private.dirty || (f.curpos != elt->private.special.offset) ||
2133 	    (elt->private.msg.header.text.size !=
2134 	     (elt->private.spare.data +
2135 	      unix_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) {
2136 	  unsigned long newoffset = f.curpos;
2137 				/* yes, seek to internal header */
2138 	  lseek (LOCAL->fd,elt->private.special.offset,L_SET);
2139 	  read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
2140 				/* see if need to squeeze out a CR */
2141 	  if (LOCAL->buf[elt->private.special.text.size - 2] == '\r') {
2142 	    LOCAL->buf[--elt->private.special.text.size - 1] = '\n';
2143 	    --size;		/* squeezed out a CR from PC */
2144 	  }
2145 				/* protection pointer moves to RFC822 header */
2146 	  f.protect = elt->private.special.offset +
2147 	    elt->private.msg.header.offset;
2148 				/* write internal header */
2149 	  unix_write (&f,LOCAL->buf,elt->private.special.text.size);
2150 				/* get RFC822 header */
2151 	  s = unix_header (stream,elt->msgno,&j,FT_INTERNAL);
2152 				/* in case this got decremented */
2153 	  elt->private.msg.header.offset = elt->private.special.text.size;
2154 				/* header size, sans trailing newline */
2155 	  if ((j < 2) || (s[j - 2] == '\n')) j--;
2156 				/* this can happen if CRs were squeezed */
2157 	  if (j < elt->private.spare.data) {
2158 				/* so fix up counts */
2159 	    size -= elt->private.spare.data - j;
2160 	    elt->private.spare.data = j;
2161 	  }
2162 	  else if (j != elt->private.spare.data)
2163 	    fatal ("header size inconsistent");
2164 				/* protection pointer moves to RFC822 text */
2165 	  f.protect = elt->private.special.offset +
2166 	    elt->private.msg.text.offset;
2167 	  unix_write (&f,s,j);	/* write RFC822 header */
2168 				/* write status and UID */
2169 	  unix_write (&f,LOCAL->buf,
2170 		      j = unix_xstatus (stream,LOCAL->buf,elt,NIL,flag));
2171 	  flag = 1;		/* only write X-IMAPbase once */
2172 				/* new file header size */
2173 	  elt->private.msg.header.text.size = elt->private.spare.data + j;
2174 
2175 				/* did text move? */
2176 	  if (f.curpos != f.protect) {
2177 				/* get message text */
2178 	    s = unix_text_work (stream,elt,&j,FT_INTERNAL);
2179 				/* this can happen if CRs were squeezed */
2180 	    if (j < elt->private.msg.text.text.size) {
2181 				/* so fix up counts */
2182 	      size -= elt->private.msg.text.text.size - j;
2183 	      elt->private.msg.text.text.size = j;
2184 	    }
2185 				/* can't happen it says here */
2186 	    else if (j > elt->private.msg.text.text.size)
2187 	      fatal ("text size inconsistent");
2188 				/* new text offset, status/UID may change it */
2189 	    elt->private.msg.text.offset = f.curpos - newoffset;
2190 				/* protection pointer moves to next message */
2191 	    f.protect = (i <= stream->nmsgs) ?
2192 	      mail_elt (stream,i)->private.special.offset : (f.curpos + j + 1);
2193 	    unix_write (&f,s,j);/* write text */
2194 				/* write trailing newline */
2195 	    unix_write (&f,"\n",1);
2196 	  }
2197 	  else {		/* tie off header and status */
2198 	    unix_write (&f,NIL,NIL);
2199 				/* protection pointer moves to next message */
2200 	    f.protect = (i <= stream->nmsgs) ?
2201 	      mail_elt (stream,i)->private.special.offset : size;
2202 				/* locate end of message text */
2203 	    j = f.filepos + elt->private.msg.text.text.size;
2204 				/* trailing newline already there? */
2205 	    if (f.protect == (j + 1)) f.curpos = f.filepos = f.protect;
2206 	    else {		/* trailing newline missing, write it */
2207 	      f.curpos = f.filepos = j;
2208 	      unix_write (&f,"\n",1);
2209 	    }
2210 	  }
2211 				/* new internal header offset */
2212 	  elt->private.special.offset = newoffset;
2213 	  elt->private.dirty =NIL;/* message is now clean */
2214 	}
2215 	else {			/* no need to rewrite this message */
2216 				/* tie off previous message if needed */
2217 	  unix_write (&f,NIL,NIL);
2218 				/* protection pointer moves to next message */
2219 	  f.protect = (i <= stream->nmsgs) ?
2220 	    mail_elt (stream,i)->private.special.offset : size;
2221 				/* locate end of message text */
2222 	  j = f.filepos + elt->private.special.text.size +
2223 	    elt->private.msg.header.text.size +
2224 	      elt->private.msg.text.text.size;
2225 				/* trailing newline already there? */
2226 	  if (f.protect == (j + 1)) f.curpos = f.filepos = f.protect;
2227 	  else {		/* trailing newline missing, write it */
2228 	    f.curpos = f.filepos = j;
2229 	    unix_write (&f,"\n",1);
2230 	  }
2231 	}
2232       }
2233     }
2234 
2235     unix_write (&f,NIL,NIL);	/* tie off final message */
2236     if (size != f.filepos) fatal ("file size inconsistent");
2237     fs_give ((void **) &f.buf);	/* free buffer */
2238 				/* make sure tied off */
2239     ftruncate (LOCAL->fd,LOCAL->filesize = size);
2240     fsync (LOCAL->fd);		/* make sure the updates take */
2241     if (size && (flag < 0)) fatal ("lost UID base information");
2242 				/* no longer dirty */
2243     LOCAL->ddirty = LOCAL->dirty = NIL;
2244   				/* notify upper level of new mailbox sizes */
2245     mail_exists (stream,stream->nmsgs);
2246     mail_recent (stream,recent);
2247 				/* set atime to now, mtime a second earlier */
2248     tp[1] = (tp[0] = time (0)) - 1;
2249 				/* set the times, note change */
2250     if (!utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
2251     close (LOCAL->fd);		/* close and reopen file */
2252     if ((LOCAL->fd = open (stream->mailbox,O_RDWR,
2253 			   (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
2254 	< 0) {
2255       sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
2256       MM_LOG (LOCAL->buf,ERROR);
2257       unix_abort (stream);
2258     }
2259     dotlock_unlock (lock);	/* flush the lock file */
2260   }
2261   return ret;			/* return state from algorithm */
2262 }
2263 
2264 /* Extend UNIX mailbox file
2265  * Accepts: MAIL stream
2266  *	    new desired size
2267  * Return: T if success, else NIL
2268  */
2269 
unix_extend(MAILSTREAM * stream,unsigned long size)2270 long unix_extend (MAILSTREAM *stream,unsigned long size)
2271 {
2272   unsigned long i = (size > LOCAL->filesize) ? size - LOCAL->filesize : 0;
2273   if (i) {			/* does the mailbox need to grow? */
2274     if (i > LOCAL->buflen) {	/* make sure have enough space */
2275 				/* this user won the lottery all right */
2276       fs_give ((void **) &LOCAL->buf);
2277       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
2278     }
2279     memset (LOCAL->buf,'\0',i);	/* get a block of nulls */
2280     while (T) {			/* until write successful or punt */
2281       lseek (LOCAL->fd,LOCAL->filesize,L_SET);
2282       if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break;
2283       else {
2284 	long e = errno;		/* note error before doing ftruncate */
2285 	ftruncate (LOCAL->fd,LOCAL->filesize);
2286 	if (MM_DISKERROR (stream,e,NIL)) {
2287 	  fsync (LOCAL->fd);	/* user chose to punt */
2288 	  sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e));
2289 	  if (!stream->silent) MM_LOG (LOCAL->buf,ERROR);
2290 	  return NIL;
2291 	}
2292       }
2293     }
2294   }
2295   return LONGT;
2296 }
2297 
2298 /* Write data to buffered file
2299  * Accepts: buffered file pointer
2300  *	    file data or NIL to indicate "flush buffer"
2301  *	    date size (ignored for "flush buffer")
2302  * Does not return until success
2303  */
2304 
unix_write(UNIXFILE * f,char * buf,unsigned long size)2305 void unix_write (UNIXFILE *f,char *buf,unsigned long size)
2306 {
2307   unsigned long i,j,k;
2308   if (buf) {			/* doing buffered write? */
2309     i = f->bufpos - f->buf;	/* yes, get size of current buffer data */
2310 				/* yes, have space in current buffer chunk? */
2311     if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) {
2312 				/* yes, fill up buffer as much as we can */
2313       memcpy (f->bufpos,buf,k = min (j,size));
2314       f->bufpos += k;		/* new buffer position */
2315       f->curpos += k;		/* new current position */
2316       if (j -= k) return;	/* all done if still have buffer free space */
2317       buf += k;			/* full, get new unwritten data pointer */
2318       size -= k;		/* new data size */
2319       i += k;			/* new buffer data size */
2320     }
2321     /* This chunk of the buffer is full.  See if can make some space by
2322      * writing to the disk, if there's enough unprotected space to do so.
2323      * Try to fill out any unaligned chunk, along with any subsequent full
2324      * chunks that will fit in unprotected space.
2325      */
2326 				/* any unprotected space we can write to? */
2327     if (j = min (i,f->protect - f->filepos)) {
2328 				/* yes, filepos not at chunk boundary? */
2329       if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j))
2330 	j -= k;			/* yes, and can write out partial chunk */
2331       else k = 0;		/* no partial chunk to write */
2332 				/* if at least a chunk free, write that too */
2333       if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN);
2334       if (k) {			/* write data if there is anything we can */
2335 	unix_phys_write (f,f->buf,k);
2336 				/* slide buffer */
2337 	if (i -= k) memmove (f->buf,f->buf + k,i);
2338 	f->bufpos = f->buf + i;	/* new end of buffer */
2339       }
2340     }
2341 
2342     /* Have flushed the buffer as best as possible.  All done if no more
2343      * data to write.  Otherwise, if the buffer is empty AND if the unwritten
2344      * data is larger than a chunk AND the unprotected space is also larger
2345      * than a chunk, then write as many chunks as we can directly from the
2346      * data.  Buffer the rest, expanding the buffer as needed.
2347      */
2348     if (size) {			/* have more data that we need to buffer? */
2349 				/* can write any of it to disk instead? */
2350       if ((f->bufpos == f->buf) &&
2351 	  ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) {
2352 				/* write as much as we can right now */
2353 	unix_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN));
2354 	buf += j;		/* new data pointer */
2355 	size -= j;		/* new data size */
2356 	f->curpos += j;		/* advance current pointer */
2357       }
2358       if (size) {		/* still have data that we need to buffer? */
2359 				/* yes, need to expand the buffer? */
2360 	if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) {
2361 				/* note current position in buffer */
2362 	  j = f->bufpos - f->buf;
2363 	  i += OVERFLOWBUFLEN;	/* yes, grow another chunk */
2364 	  fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN));
2365 				/* in case buffer relocated */
2366 	  f->bufpos = f->buf + j;
2367 	}
2368 				/* buffer remaining data */
2369 	memcpy (f->bufpos,buf,size);
2370 	f->bufpos += size;	/* new end of buffer */
2371 	f->curpos += size;	/* advance current pointer */
2372       }
2373     }
2374   }
2375   else {			/* flush buffer to disk */
2376     unix_phys_write (f,f->buf,i = f->bufpos - f->buf);
2377     f->bufpos = f->buf;		/* reset buffer */
2378 				/* update positions */
2379     f->curpos = f->protect = f->filepos;
2380   }
2381 }
2382 
2383 /* Physical disk write
2384  * Accepts: buffered file pointer
2385  *	    buffer address
2386  *	    buffer size
2387  * Does not return until success
2388  */
2389 
unix_phys_write(UNIXFILE * f,char * buf,size_t size)2390 void unix_phys_write (UNIXFILE *f,char *buf,size_t size)
2391 {
2392   MAILSTREAM *stream = f->stream;
2393 				/* write data at desired position */
2394   while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) ||
2395 		  (write (LOCAL->fd,buf,size) < 0))) {
2396     int e;
2397     char tmp[MAILTMPLEN];
2398     sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno));
2399     MM_LOG (tmp,ERROR);
2400     MM_DISKERROR (NIL,e,T);	/* serious problem, must retry */
2401   }
2402   f->filepos += size;		/* update file position */
2403 }
2404 
2405 /* MBOX mail routines */
2406 
2407 
2408 /* Driver dispatch used by MAIL */
2409 
2410 DRIVER mboxdriver = {
2411   "mbox",			/* driver name */
2412 				/* driver flags */
2413   DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY,
2414   (DRIVER *) NIL,		/* next driver */
2415   mbox_valid,			/* mailbox is valid for us */
2416   unix_parameters,		/* manipulate parameters */
2417   unix_scan,			/* scan mailboxes */
2418   unix_list,			/* find mailboxes */
2419   unix_lsub,			/* find subscribed mailboxes */
2420   NIL,				/* subscribe to mailbox */
2421   NIL,				/* unsubscribe from mailbox */
2422   mbox_create,			/* create mailbox */
2423   mbox_delete,			/* delete mailbox */
2424   mbox_rename,			/* rename mailbox */
2425   mbox_status,			/* status of mailbox */
2426   mbox_open,			/* open mailbox */
2427   unix_close,			/* close mailbox */
2428   NIL,				/* fetch message "fast" attributes */
2429   NIL,				/* fetch message flags */
2430   NIL,				/* fetch overview */
2431   NIL,				/* fetch message structure */
2432   unix_header,			/* fetch message header */
2433   unix_text,			/* fetch message body */
2434   NIL,				/* fetch partial message text */
2435   NIL,				/* unique identifier */
2436   NIL,				/* message number */
2437   NIL,				/* modify flags */
2438   unix_flagmsg,			/* per-message modify flags */
2439   NIL,				/* search for message based on criteria */
2440   NIL,				/* sort messages */
2441   NIL,				/* thread messages */
2442   mbox_ping,			/* ping mailbox to see if still alive */
2443   mbox_check,			/* check for new messages */
2444   mbox_expunge,			/* expunge deleted messages */
2445   unix_copy,			/* copy messages to another mailbox */
2446   mbox_append,			/* append string message to mailbox */
2447   NIL				/* garbage collect stream */
2448 };
2449 
2450 				/* prototype stream */
2451 MAILSTREAM mboxproto = {&mboxdriver};
2452 
2453 /* MBOX mail validate mailbox
2454  * Accepts: mailbox name
2455  * Returns: our driver if name is valid, NIL otherwise
2456  */
2457 
mbox_valid(char * name)2458 DRIVER *mbox_valid (char *name)
2459 {
2460 				/* only INBOX, mbox must exist */
2461   if (!compare_cstring (name,"INBOX") && (unix_valid ("mbox") || !errno) &&
2462       (unix_valid (sysinbox()) || !errno || (errno == ENOENT)))
2463     return &mboxdriver;
2464   return NIL;			/* can't win (yet, anyway) */
2465 }
2466 
2467 /* MBOX mail create mailbox
2468  * Accepts: MAIL stream
2469  *	    mailbox name to create
2470  * Returns: T on success, NIL on failure
2471  */
2472 
mbox_create(MAILSTREAM * stream,char * mailbox)2473 long mbox_create (MAILSTREAM *stream,char *mailbox)
2474 {
2475   char tmp[MAILTMPLEN];
2476   if (!compare_cstring (mailbox,"INBOX")) return unix_create (NIL,"mbox");
2477   sprintf (tmp,"Can't create non-INBOX name as mbox: %.80s",mailbox);
2478   MM_LOG (tmp,ERROR);
2479   return NIL;
2480 }
2481 
2482 
2483 /* MBOX mail delete mailbox
2484  * Accepts: MAIL stream
2485  *	    mailbox name to delete
2486  * Returns: T on success, NIL on failure
2487  */
2488 
mbox_delete(MAILSTREAM * stream,char * mailbox)2489 long mbox_delete (MAILSTREAM *stream,char *mailbox)
2490 {
2491   return mbox_rename (stream,mailbox,NIL);
2492 }
2493 
2494 
2495 /* MBOX mail rename mailbox
2496  * Accepts: MAIL stream
2497  *	    old mailbox name
2498  *	    new mailbox name (or NIL for delete)
2499  * Returns: T on success, NIL on failure
2500  */
2501 
mbox_rename(MAILSTREAM * stream,char * old,char * newname)2502 long mbox_rename (MAILSTREAM *stream,char *old,char *newname)
2503 {
2504   char tmp[MAILTMPLEN];
2505   long ret = unix_rename (stream,"~/mbox",newname);
2506 				/* recreate file if renamed INBOX */
2507   if (ret) unix_create (NIL,"mbox");
2508   else MM_LOG (tmp,ERROR);	/* log error */
2509   return ret;			/* return success */
2510 }
2511 
2512 /* MBOX Mail status
2513  * Accepts: mail stream
2514  *	    mailbox name
2515  *	    status flags
2516  * Returns: T on success, NIL on failure
2517  */
2518 
mbox_status(MAILSTREAM * stream,char * mbx,long flags)2519 long mbox_status (MAILSTREAM *stream,char *mbx,long flags)
2520 {
2521   MAILSTATUS status;
2522   unsigned long i;
2523   MAILSTREAM *tstream = NIL;
2524   MAILSTREAM *systream = NIL;
2525 				/* make temporary stream (unless this mbx) */
2526   if (!stream && !(stream = tstream =
2527 		   mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
2528   status.flags = flags;		/* return status values */
2529   status.messages = stream->nmsgs;
2530   status.recent = stream->recent;
2531   if (flags & SA_UNSEEN)	/* must search to get unseen messages */
2532     for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
2533       if (!mail_elt (stream,i)->seen) status.unseen++;
2534   status.uidnext = stream->uid_last + 1;
2535   status.uidvalidity = stream->uid_validity;
2536   if (!status.recent &&		/* calculate post-snarf results */
2537       (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
2538     status.messages += systream->nmsgs;
2539     status.recent += systream->recent;
2540     if (flags & SA_UNSEEN)	/* must search to get unseen messages */
2541       for (i = 1; i <= systream->nmsgs; i++)
2542 	if (!mail_elt (systream,i)->seen) status.unseen++;
2543 				/* kludge but probably good enough */
2544     status.uidnext += systream->nmsgs;
2545   }
2546   MM_STATUS(stream,mbx,&status);/* pass status to main program */
2547   if (tstream) mail_close (tstream);
2548   if (systream) mail_close (systream);
2549   return T;			/* success */
2550 }
2551 
2552 /* MBOX mail open
2553  * Accepts: stream to open
2554  * Returns: stream on success, NIL on failure
2555  */
2556 
mbox_open(MAILSTREAM * stream)2557 MAILSTREAM *mbox_open (MAILSTREAM *stream)
2558 {
2559   unsigned long i = 1;
2560   unsigned long recent = 0;
2561 				/* return prototype for OP_PROTOTYPE call */
2562   if (!stream) return &mboxproto;
2563 				/* change mailbox file name */
2564   fs_give ((void **) &stream->mailbox);
2565   stream->mailbox = cpystr ("mbox");
2566 				/* open mailbox, snarf new mail */
2567   if (!(unix_open (stream) && mbox_ping (stream))) return NIL;
2568   stream->inbox = T;		/* mark that this is an INBOX */
2569 				/* notify upper level of mailbox sizes */
2570   mail_exists (stream,stream->nmsgs);
2571   while (i <= stream->nmsgs) if (mail_elt (stream,i++)->recent) ++recent;
2572   mail_recent (stream,recent);	/* including recent messages */
2573   return stream;
2574 }
2575 
2576 /* MBOX mail ping mailbox
2577  * Accepts: MAIL stream
2578  * Returns: T if stream alive, else NIL
2579  * No-op for readonly files, since read/writer can expunge it from under us!
2580  */
2581 
2582 static int snarfed = 0;		/* number of snarfs */
2583 
mbox_ping(MAILSTREAM * stream)2584 long mbox_ping (MAILSTREAM *stream)
2585 {
2586   int sfd;
2587   unsigned long size;
2588   struct stat sbuf;
2589   char *s;
2590   DOTLOCK lock,lockx;
2591 				/* time to try snarf and sysinbox non-empty? */
2592   if (LOCAL && !stream->rdonly && !stream->lock &&
2593       (time (0) >= (LOCAL->lastsnarf +
2594 		    (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
2595       !stat (sysinbox (),&sbuf) && sbuf.st_size) {
2596     MM_CRITICAL (stream);	/* yes, go critical */
2597 				/* open and lock sysinbox */
2598     if ((sfd = unix_lock (sysinbox (),O_RDWR,
2599 			  (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
2600 			  &lockx,LOCK_EX)) >= 0) {
2601 				/* locked sysinbox in good format? */
2602       if (fstat (sfd,&sbuf) || !(size = sbuf.st_size) ||
2603 	  !unix_isvalid_fd (sfd)) {
2604 	sprintf (LOCAL->buf,"Mail drop %s is not in standard Unix format",
2605 		 sysinbox ());
2606 	MM_LOG (LOCAL->buf,ERROR);
2607       }
2608 				/* sysinbox good, parse and excl-lock mbox */
2609       else if (unix_parse (stream,&lock,LOCK_EX)) {
2610 	lseek (sfd,0,L_SET);	/* read entire sysinbox into memory */
2611 	read (sfd,s = (char *) fs_get (size + 1),size);
2612 	s[size] = '\0';		/* tie it off */
2613 				/* append to end of mbox */
2614 	lseek (LOCAL->fd,LOCAL->filesize,L_SET);
2615 
2616 				/* copy to mbox */
2617 	if ((write (LOCAL->fd,s,size) < 0) || fsync (LOCAL->fd)) {
2618 	  sprintf (LOCAL->buf,"New mail move failed: %s",strerror (errno));
2619 	  MM_LOG (LOCAL->buf,WARN);
2620 				/* revert mbox to previous size */
2621 	  ftruncate (LOCAL->fd,LOCAL->filesize);
2622 	}
2623 				/* sysinbox better not have changed */
2624 	else if (fstat (sfd,&sbuf) || (size != sbuf.st_size)) {
2625 	  sprintf (LOCAL->buf,"Mail drop %s lock failure, old=%lu now=%lu",
2626 		   sysinbox (),size,(unsigned long) sbuf.st_size);
2627 	  MM_LOG (LOCAL->buf,ERROR);
2628 				/* revert mbox to previous size */
2629 	  ftruncate (LOCAL->fd,LOCAL->filesize);
2630 	  /* Believe it or not, a Singaporean government system actually had
2631 	   * symlinks from /var/mail/user to ~user/mbox.  To compound this
2632 	   * error, they used an SVR4 system; BSD and OSF locks would have
2633 	   * prevented it but not SVR4 locks.
2634 	   */
2635 	  if (!fstat (sfd,&sbuf) && (size == sbuf.st_size))
2636 	    syslog (LOG_ALERT,"File %s and %s are the same file!",
2637 		    sysinbox (),stream->mailbox);
2638 	}
2639 	else {			/* data copied OK */
2640 	  ftruncate (sfd,0);	/* truncate sysinbox to zero bytes */
2641 	  if (!snarfed++) {	/* have we snarfed before? */
2642 				/* syslog if server, else user log */
2643 	    sprintf (LOCAL->buf,"Moved %lu bytes of new mail to %s from %s",
2644 		     size,stream->mailbox,sysinbox ());
2645 	    if (strcmp ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
2646 			"unknown"))
2647 	      syslog (LOG_INFO,"%s host= %s",LOCAL->buf,tcp_clienthost ());
2648 	    else MM_LOG (LOCAL->buf,WARN);
2649 	  }
2650 	}
2651 				/* done with sysinbox text */
2652 	fs_give ((void **) &s);
2653 				/* all done with mbox */
2654 	unix_unlock (LOCAL->fd,stream,&lock);
2655 	mail_unlock (stream);	/* unlock the stream */
2656 	MM_NOCRITICAL (stream);	/* done with critical */
2657       }
2658 				/* all done with sysinbox */
2659       unix_unlock (sfd,NIL,&lockx);
2660     }
2661     MM_NOCRITICAL (stream);	/* done with critical */
2662     LOCAL->lastsnarf = time (0);/* note time of last snarf */
2663   }
2664   return unix_ping (stream);	/* do the unix routine now */
2665 }
2666 
2667 /* MBOX mail check mailbox
2668  * Accepts: MAIL stream
2669  */
2670 
mbox_check(MAILSTREAM * stream)2671 void mbox_check (MAILSTREAM *stream)
2672 {
2673 				/* do local ping, then do unix routine */
2674   if (mbox_ping (stream)) unix_check (stream);
2675 }
2676 
2677 
2678 /* MBOX mail expunge mailbox
2679  * Accepts: MAIL stream
2680  *	    sequence to expunge if non-NIL
2681  *	    expunge options
2682  * Returns: T, always
2683  */
2684 
mbox_expunge(MAILSTREAM * stream,char * sequence,long options)2685 long mbox_expunge (MAILSTREAM *stream,char *sequence,long options)
2686 {
2687   long ret = unix_expunge (stream,sequence,options);
2688   mbox_ping (stream);		/* do local ping */
2689   return ret;
2690 }
2691 
2692 
2693 /* MBOX mail append message from stringstruct
2694  * Accepts: MAIL stream
2695  *	    destination mailbox
2696  *	    append callback
2697  *	    data for callback
2698  * Returns: T if append successful, else NIL
2699  */
2700 
mbox_append(MAILSTREAM * stream,char * mailbox,append_t af,void * data)2701 long mbox_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
2702 {
2703   char tmp[MAILTMPLEN];
2704   if (mbox_valid (mailbox)) return unix_append (stream,"mbox",af,data);
2705   sprintf (tmp,"Can't append to that name: %.80s",mailbox);
2706   MM_LOG (tmp,ERROR);
2707   return NIL;
2708 }
2709