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