1 /* ========================================================================
2  * Copyright 2008-2011 Mark Crispin
3  * ========================================================================
4  */
5 
6 /*
7  * Program:	MH mail routines
8  *
9  * Author(s):	Mark Crispin
10  *
11  * Date:	23 February 1992
12  * Last Edited:	8 April 2011
13  *
14  * Previous versions of this file were
15  *
16  * Copyright 1988-2007 University of Washington
17  *
18  * Licensed under the Apache License, Version 2.0 (the "License");
19  * you may not use this file except in compliance with the License.
20  * You may obtain a copy of the License at
21  *
22  *     http://www.apache.org/licenses/LICENSE-2.0
23  *
24  */
25 
26 
27 #include <stdio.h>
28 #include <ctype.h>
29 #include <errno.h>
30 extern int errno;		/* just in case */
31 #include "mail.h"
32 #include "osdep.h"
33 #include <pwd.h>
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 #include "misc.h"
37 #include "dummy.h"
38 #include "fdstring.h"
39 
40 
41 /* Build parameters */
42 
43 #define MHINBOX "#mhinbox"	/* corresponds to namespace in env_unix.c */
44 #define MHINBOXDIR "inbox"
45 #define MHPROFILE ".mh_profile"
46 #define MHCOMMA ','
47 #define MHSEQUENCE ".mh_sequence"
48 #define MHSEQUENCES ".mh_sequences"
49 #define MHPATH "Mail"
50 
51 
52 /* mh_load_message() flags */
53 
54 #define MLM_HEADER 0x1		/* load message text */
55 #define MLM_TEXT 0x2		/* load message text */
56 
57 /* MH I/O stream local data */
58 
59 typedef struct mh_local {
60   char *dir;			/* spool directory name */
61   unsigned char buf[CHUNKSIZE];	/* temporary buffer */
62   unsigned long cachedtexts;	/* total size of all cached texts */
63   time_t scantime;		/* last time directory scanned */
64 } MHLOCAL;
65 
66 
67 /* Convenient access to local data */
68 
69 #define LOCAL ((MHLOCAL *) stream->local)
70 
71 
72 /* Function prototypes */
73 
74 DRIVER *mh_valid (char *name);
75 int mh_isvalid (char *name,char *tmp,long synonly);
76 int mh_namevalid (char *name);
77 char *mh_path (char *tmp);
78 void *mh_parameters (long function,void *value);
79 long mh_dirfmttest (char *name);
80 void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
81 void mh_list (MAILSTREAM *stream,char *ref,char *pat);
82 void mh_lsub (MAILSTREAM *stream,char *ref,char *pat);
83 void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level);
84 long mh_subscribe (MAILSTREAM *stream,char *mailbox);
85 long mh_unsubscribe (MAILSTREAM *stream,char *mailbox);
86 long mh_create (MAILSTREAM *stream,char *mailbox);
87 long mh_delete (MAILSTREAM *stream,char *mailbox);
88 long mh_rename (MAILSTREAM *stream,char *old,char *newname);
89 MAILSTREAM *mh_open (MAILSTREAM *stream);
90 void mh_close (MAILSTREAM *stream,long options);
91 void mh_fast (MAILSTREAM *stream,char *sequence,long flags);
92 void mh_load_message (MAILSTREAM *stream,unsigned long msgno,long flags);
93 char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
94 		 long flags);
95 long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
96 long mh_ping (MAILSTREAM *stream);
97 void mh_check (MAILSTREAM *stream);
98 long mh_expunge (MAILSTREAM *stream,char *sequence,long options);
99 long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
100 	      long options);
101 long mh_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
102 
103 int mh_select (struct direct *name);
104 int mh_numsort (const void *d1,const void *d2);
105 char *mh_file (char *dst,char *name);
106 long mh_canonicalize (char *pattern,char *ref,char *pat);
107 void mh_setdate (char *file,MESSAGECACHE *elt);
108 
109 /* MH mail routines */
110 
111 
112 /* Driver dispatch used by MAIL */
113 
114 DRIVER mhdriver = {
115   "mh",				/* driver name */
116 				/* driver flags */
117   DR_MAIL|DR_LOCAL|DR_NOFAST|DR_NAMESPACE|DR_NOSTICKY|DR_DIRFMT,
118   (DRIVER *) NIL,		/* next driver */
119   mh_valid,			/* mailbox is valid for us */
120   mh_parameters,		/* manipulate parameters */
121   mh_scan,			/* scan mailboxes */
122   mh_list,			/* find mailboxes */
123   mh_lsub,			/* find subscribed mailboxes */
124   mh_subscribe,			/* subscribe to mailbox */
125   mh_unsubscribe,		/* unsubscribe from mailbox */
126   mh_create,			/* create mailbox */
127   mh_delete,			/* delete mailbox */
128   mh_rename,			/* rename mailbox */
129   mail_status_default,		/* status of mailbox */
130   mh_open,			/* open mailbox */
131   mh_close,			/* close mailbox */
132   mh_fast,			/* fetch message "fast" attributes */
133   NIL,				/* fetch message flags */
134   NIL,				/* fetch overview */
135   NIL,				/* fetch message envelopes */
136   mh_header,			/* fetch message header */
137   mh_text,			/* fetch message body */
138   NIL,				/* fetch partial message text */
139   NIL,				/* unique identifier */
140   NIL,				/* message number */
141   NIL,				/* modify flags */
142   NIL,				/* per-message modify flags */
143   NIL,				/* search for message based on criteria */
144   NIL,				/* sort messages */
145   NIL,				/* thread messages */
146   mh_ping,			/* ping mailbox to see if still alive */
147   mh_check,			/* check for new messages */
148   mh_expunge,			/* expunge deleted messages */
149   mh_copy,			/* copy messages to another mailbox */
150   mh_append,			/* append string message to mailbox */
151   NIL,				/* garbage collect stream */
152   NIL				/* renew stream */
153 };
154 
155 				/* prototype stream */
156 MAILSTREAM mhproto = {&mhdriver};
157 
158 
159 static char *mh_profile = NIL;	/* holds MH profile */
160 static char *mh_pathname = NIL;	/* holds MH path name */
161 static long mh_once = 0;	/* already snarled once */
162 static long mh_allow_inbox =NIL;/* allow INBOX as well as MHINBOX */
163 
164 /* MH mail validate mailbox
165  * Accepts: mailbox name
166  * Returns: our driver if name is valid, NIL otherwise
167  */
168 
mh_valid(char * name)169 DRIVER *mh_valid (char *name)
170 {
171   char tmp[MAILTMPLEN];
172   return mh_isvalid (name,tmp,T) ? &mhdriver : NIL;
173 }
174 
175 
176 /* MH mail test for valid mailbox
177  * Accepts: mailbox name
178  *	    temporary buffer to use
179  *	    syntax only test flag
180  * Returns: T if valid, NIL otherwise
181  */
182 
mh_isvalid(char * name,char * tmp,long synonly)183 int mh_isvalid (char *name,char *tmp,long synonly)
184 {
185   struct stat sbuf;
186   char *s,*t,altname[MAILTMPLEN];
187   unsigned long i;
188   int ret = NIL;
189   errno = NIL;			/* zap any error condition */
190 				/* mh name? */
191   if ((mh_allow_inbox && !compare_cstring (name,"INBOX")) ||
192       !compare_cstring (name,MHINBOX) ||
193       ((name[0] == '#') && ((name[1] == 'm') || (name[1] == 'M')) &&
194        ((name[2] == 'h') || (name[2] == 'H')) && (name[3] == '/') && name[4])){
195     if (mh_path (tmp))		/* validate name if INBOX or not synonly */
196       ret = (synonly && compare_cstring (name,"INBOX")) ?
197 	T : ((stat (mh_file (tmp,name),&sbuf) == 0) &&
198 	     (sbuf.st_mode & S_IFMT) == S_IFDIR);
199     else if (!mh_once++) {	/* only report error once */
200       sprintf (tmp,"%.900s not found, mh format names disabled",mh_profile);
201       mm_log (tmp,WARN);
202     }
203   }
204 				/* see if non-NS name within mh hierarchy */
205   else if ((name[0] != '#') && (s = mh_path (tmp)) && (i = strlen (s)) &&
206 	   (t = mailboxfile (tmp,name)) && !strncmp (t,s,i) &&
207 	   (tmp[i] == '/') && tmp[i+1]) {
208     sprintf (altname,"#mh%.900s",tmp+i);
209 				/* can't do synonly here! */
210     ret = mh_isvalid (altname,tmp,NIL);
211   }
212   else errno = EINVAL;		/* bogus name */
213   return ret;
214 }
215 
216 /* MH mail test for valid mailbox
217  * Accepts: mailbox name
218  * Returns: T if valid, NIL otherwise
219  */
220 
mh_namevalid(char * name)221 int mh_namevalid (char *name)
222 {
223   char *s;
224   if (name[0] == '#' && (name[1] == 'm' || name[1] == 'M') &&
225       (name[2] == 'h' || name[2] == 'H') && name[3] == '/')
226     for (s = name; s && *s;) {	/* make sure no all-digit nodes */
227       if (isdigit (*s)) s++;	/* digit, check this node further... */
228       else if (*s == '/') break;/* all digit node, barf */
229 				/* non-digit, skip to next node or return */
230       else if (!((s = strchr (s+1,'/')) && *++s)) return T;
231     }
232   return NIL;			/* all numeric or empty node */
233 }
234 
235 /* Return MH path
236  * Accepts: temporary buffer
237  * Returns: MH path or NIL if MH disabled
238  */
239 
mh_path(char * tmp)240 char *mh_path (char *tmp)
241 {
242   char *s,*t,*v,*r;
243   int fd;
244   struct stat sbuf;
245   if (!mh_profile) {		/* build mh_profile and mh_pathname now */
246     sprintf (tmp,"%s/%s",myhomedir (),MHPROFILE);
247     if ((fd = open (mh_profile = cpystr (tmp),O_RDONLY,NIL)) >= 0) {
248       fstat (fd,&sbuf);		/* yes, get size and read file */
249       read (fd,(t = (char *) fs_get (sbuf.st_size + 1)),sbuf.st_size);
250       close (fd);		/* don't need the file any more */
251       t[sbuf.st_size] = '\0';	/* tie it off */
252 				/* parse profile file */
253       for (s = strtok_r (t,"\r\n",&r); s && *s; s = strtok_r (NIL,"\r\n",&r)) {
254 				/* found space in line? */
255 	if ((v = strpbrk (s," \t")) != NULL) {
256 	  *v++ = '\0';		/* tie off, is keyword "Path:"? */
257 	  if (!compare_cstring (s,"Path:")) {
258 				/* skip whitespace */
259 	    while ((*v == ' ') || (*v == '\t')) ++v;
260 				/* absolute path? */
261 	    if (*v == '/') s = v;
262 	    else sprintf (s = tmp,"%s/%s",myhomedir (),v);
263 				/* copy name */
264 	    mh_pathname = cpystr (s);
265 	    break;		/* don't need to look at rest of file */
266 	  }
267 	}
268       }
269       fs_give ((void **) &t);	/* flush profile text */
270       if (!mh_pathname) {	/* default path if not in the profile */
271 	sprintf (tmp,"%s/%s",myhomedir (),MHPATH);
272 	mh_pathname = cpystr (tmp);
273       }
274     }
275   }
276   return mh_pathname;
277 }
278 
279 /* MH manipulate driver parameters
280  * Accepts: function code
281  *	    function-dependent value
282  * Returns: function-dependent return value
283  */
284 
mh_parameters(long function,void * value)285 void *mh_parameters (long function,void *value)
286 {
287   void *ret = NIL;
288   switch ((int) function) {
289   case GET_INBOXPATH:
290     if (value) ret = mh_file ((char *) value,"INBOX");
291     break;
292   case GET_DIRFMTTEST:
293     ret = (void *) mh_dirfmttest;
294     break;
295   case SET_MHPROFILE:
296     if (mh_profile) fs_give ((void **) &mh_profile);
297     mh_profile = cpystr ((char *) value);
298   case GET_MHPROFILE:
299     ret = (void *) mh_profile;
300     break;
301   case SET_MHPATH:
302     if (mh_pathname) fs_give ((void **) &mh_pathname);
303     mh_pathname = cpystr ((char *) value);
304   case GET_MHPATH:
305     ret = (void *) mh_pathname;
306     break;
307   case SET_MHALLOWINBOX:
308     mh_allow_inbox = value ? T : NIL;
309   case GET_MHALLOWINBOX:
310     ret = (void *) (mh_allow_inbox ? VOIDT : NIL);
311   }
312   return ret;
313 }
314 
315 
316 /* MH test for directory format internal node
317  * Accepts: candidate node name
318  * Returns: T if internal name, NIL otherwise
319  */
320 
mh_dirfmttest(char * s)321 long mh_dirfmttest (char *s)
322 {
323   int c;
324 				/* sequence(s) file is an internal name */
325   if (strcmp (s,MHSEQUENCE) && strcmp (s,MHSEQUENCES)) {
326     if (*s == MHCOMMA) ++s;	/* else comma + all numeric name */
327 				/* success if all-numeric */
328     while ((c = *s++) != '\0') if (!isdigit (c)) return NIL;
329   }
330   return LONGT;
331 }
332 
333 /* MH scan mailboxes
334  * Accepts: mail stream
335  *	    reference
336  *	    pattern to search
337  *	    string to scan
338  */
339 
mh_scan(MAILSTREAM * stream,char * ref,char * pat,char * contents)340 void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
341 {
342   char *s,test[MAILTMPLEN],file[MAILTMPLEN];
343   long i = 0;
344   if (!pat || !*pat) {		/* empty pattern? */
345     if (mh_canonicalize (test,ref,"*")) {
346 				/* tie off name at root */
347       if ((s = strchr (test,'/')) != NULL) *++s = '\0';
348       else test[0] = '\0';
349       mm_list (stream,'/',test,LATT_NOSELECT);
350     }
351   }
352 				/* get canonical form of name */
353   else if (mh_canonicalize (test,ref,pat)) {
354     if (contents) {		/* maybe I'll implement this someday */
355       mm_log ("Scan not valid for mh mailboxes",ERROR);
356       return;
357     }
358     if (test[3] == '/') {	/* looking down levels? */
359 				/* yes, found any wildcards? */
360       if ((s = strpbrk (test,"%*")) != NULL) {
361 				/* yes, copy name up to that point */
362 	strncpy (file,test+4,i = s - (test+4));
363 	file[i] = '\0';		/* tie off */
364       }
365       else strcpy (file,test+4);/* use just that name then */
366 				/* find directory name */
367       if ((s = strrchr (file,'/')) != NULL) {
368 	*s = '\0';		/* found, tie off at that point */
369 	s = file;
370       }
371 				/* do the work */
372       mh_list_work (stream,s,test,0);
373     }
374 				/* always an INBOX */
375     if (!compare_cstring (test,MHINBOX))
376       mm_list (stream,NIL,MHINBOX,LATT_NOINFERIORS);
377   }
378 }
379 
380 /* MH list mailboxes
381  * Accepts: mail stream
382  *	    reference
383  *	    pattern to search
384  */
385 
mh_list(MAILSTREAM * stream,char * ref,char * pat)386 void mh_list (MAILSTREAM *stream,char *ref,char *pat)
387 {
388   mh_scan (stream,ref,pat,NIL);
389 }
390 
391 
392 /* MH list subscribed mailboxes
393  * Accepts: mail stream
394  *	    reference
395  *	    pattern to search
396  */
397 
mh_lsub(MAILSTREAM * stream,char * ref,char * pat)398 void mh_lsub (MAILSTREAM *stream,char *ref,char *pat)
399 {
400   void *sdb = NIL;
401   char *s,test[MAILTMPLEN],tmp[MAILTMPLEN];
402 				/* get canonical form of name */
403   if (mh_canonicalize (test,ref,pat) && (s = sm_read (tmp,&sdb))) {
404     do if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,NIL);
405     while ((s = sm_read (tmp,&sdb)) != NULL); /* until no more subscriptions */
406   }
407 }
408 
409 /* MH list mailboxes worker routine
410  * Accepts: mail stream
411  *	    directory name to search
412  *	    search pattern
413  *	    search level
414  */
415 
mh_list_work(MAILSTREAM * stream,char * dir,char * pat,long level)416 void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level)
417 {
418   DIR *dp;
419   struct direct *d;
420   struct stat sbuf;
421   char *cp,*np,curdir[MAILTMPLEN],name[MAILTMPLEN];
422 				/* build MH name to search */
423   if (dir) sprintf (name,"#mh/%s/",dir);
424   else strcpy (name,"#mh/");
425 				/* make directory name, punt if bogus */
426   if (!mh_file (curdir,name)) return;
427   cp = curdir + strlen (curdir);/* end of directory name */
428   np = name + strlen (name);	/* end of MH name */
429   if ((dp = opendir (curdir)) != NULL) {	/* open directory */
430     while ((d = readdir (dp)) != NULL)	/* scan, ignore . and numeric names */
431       if ((d->d_name[0] != '.') && !mh_select (d)) {
432 	strcpy (cp,d->d_name);	/* make directory name */
433 	if (!stat (curdir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
434 	  strcpy (np,d->d_name);/* make mh name of directory name */
435 				/* yes, an MH name if full match */
436 	  if (pmatch_full (name,pat,'/')) mm_list (stream,'/',name,NIL);
437 				/* check if should recurse */
438 	  if (dmatch (name,pat,'/') &&
439 	      (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
440 	    mh_list_work (stream,name+4,pat,level+1);
441 	}
442       }
443     closedir (dp);		/* all done, flush directory */
444   }
445 }
446 
447 /* MH mail subscribe to mailbox
448  * Accepts: mail stream
449  *	    mailbox to add to subscription list
450  * Returns: T on success, NIL on failure
451  */
452 
mh_subscribe(MAILSTREAM * stream,char * mailbox)453 long mh_subscribe (MAILSTREAM *stream,char *mailbox)
454 {
455   return sm_subscribe (mailbox);
456 }
457 
458 
459 /* MH mail unsubscribe to mailbox
460  * Accepts: mail stream
461  *	    mailbox to delete from subscription list
462  * Returns: T on success, NIL on failure
463  */
464 
mh_unsubscribe(MAILSTREAM * stream,char * mailbox)465 long mh_unsubscribe (MAILSTREAM *stream,char *mailbox)
466 {
467   return sm_unsubscribe (mailbox);
468 }
469 
470 /* MH mail create mailbox
471  * Accepts: mail stream
472  *	    mailbox name to create
473  * Returns: T on success, NIL on failure
474  */
475 
mh_create(MAILSTREAM * stream,char * mailbox)476 long mh_create (MAILSTREAM *stream,char *mailbox)
477 {
478   char tmp[MAILTMPLEN];
479   if (!mh_namevalid (mailbox))	/* validate name */
480     sprintf (tmp,"Can't create mailbox %.80s: invalid MH-format name",mailbox);
481 				/* must not already exist */
482   else if (mh_isvalid (mailbox,tmp,NIL))
483     sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
484   else if (!mh_path (tmp)) return NIL;
485 				/* try to make it */
486   else if (!(mh_file (tmp,mailbox) &&
487 	     dummy_create_path (stream,strcat (tmp,"/"),
488 				get_dir_protection (mailbox))))
489     sprintf (tmp,"Can't create mailbox %.80s: %s",mailbox,strerror (errno));
490   else return LONGT;		/* success */
491   mm_log (tmp,ERROR);
492   return NIL;
493 }
494 
495 /* MH mail delete mailbox
496  *	    mailbox name to delete
497  * Returns: T on success, NIL on failure
498  */
499 
mh_delete(MAILSTREAM * stream,char * mailbox)500 long mh_delete (MAILSTREAM *stream,char *mailbox)
501 {
502   DIR *dirp;
503   struct direct *d;
504   int i;
505   char tmp[MAILTMPLEN];
506 				/* is mailbox valid? */
507   if (!mh_isvalid (mailbox,tmp,NIL)) {
508     sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
509     mm_log (tmp,ERROR);
510     return NIL;
511   }
512 				/* get name of directory */
513   i = strlen (mh_file (tmp,mailbox));
514   if ((dirp = opendir (tmp)) != NULL) {	/* open directory */
515     tmp[i++] = '/';		/* now apply trailing delimiter */
516 				/* massacre all mh owned files */
517     while ((d = readdir (dirp)) != NULL) if (mh_dirfmttest (d->d_name)) {
518       strcpy (tmp + i,d->d_name);
519       unlink (tmp);		/* sayonara */
520     }
521     closedir (dirp);		/* flush directory */
522   }
523 				/* try to remove the directory */
524   if (rmdir (mh_file (tmp,mailbox))) {
525     sprintf (tmp,"Can't delete mailbox %.80s: %s",mailbox,strerror (errno));
526     mm_log (tmp,WARN);
527   }
528   return T;			/* return success */
529 }
530 
531 /* MH mail rename mailbox
532  * Accepts: MH mail stream
533  *	    old mailbox name
534  *	    new mailbox name
535  * Returns: T on success, NIL on failure
536  */
537 
mh_rename(MAILSTREAM * stream,char * old,char * newname)538 long mh_rename (MAILSTREAM *stream,char *old,char *newname)
539 {
540   char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
541   struct stat sbuf;
542 				/* old mailbox name must be valid */
543   if (!mh_isvalid (old,tmp,NIL))
544     sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
545   else if (!mh_namevalid (newname))
546     sprintf (tmp,"Can't rename to mailbox %.80s: invalid MH-format name",
547 	     newname);
548 				/* new mailbox name must not be valid */
549   else if (mh_isvalid (newname,tmp,NIL))
550     sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
551 	     newname);
552 				/* success if can rename the directory */
553   else {			/* found superior to destination name? */
554     if ((s = strrchr (mh_file (tmp1,newname),'/')) != NULL) {
555       c = *++s;			/* remember first character of inferior */
556       *s = '\0';		/* tie off to get just superior */
557 				/* name doesn't exist, create it */
558       if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
559 	  !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
560 	return NIL;
561       *s = c;			/* restore full name */
562     }
563     if (!rename (mh_file (tmp,old),tmp1)) return T;
564     sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",
565 	     old,newname,strerror (errno));
566   }
567   mm_log (tmp,ERROR);		/* something failed */
568   return NIL;
569 }
570 
571 /* MH mail open
572  * Accepts: stream to open
573  * Returns: stream on success, NIL on failure
574  */
575 
mh_open(MAILSTREAM * stream)576 MAILSTREAM *mh_open (MAILSTREAM *stream)
577 {
578   char tmp[MAILTMPLEN];
579   if (!stream) return &mhproto;	/* return prototype for OP_PROTOTYPE call */
580   if (stream->local) fatal ("mh recycle stream");
581   stream->local = fs_get (sizeof (MHLOCAL));
582   /* INBOXness is one of the following:
583    * #mhinbox (case-independent)
584    * #mh/inbox (mh is case-independent, inbox is case-dependent)
585    * INBOX (case-independent
586    */
587   stream->inbox =		/* note if an INBOX or not */
588     (!compare_cstring (stream->mailbox,MHINBOX) ||
589      ((stream->mailbox[0] == '#') &&
590       ((stream->mailbox[1] == 'm') || (stream->mailbox[1] == 'M')) &&
591       ((stream->mailbox[2] == 'h') || (stream->mailbox[2] == 'H')) &&
592       (stream->mailbox[3] == '/') && !strcmp (stream->mailbox+4,MHINBOXDIR)) ||
593      !compare_cstring (stream->mailbox,"INBOX")) ? T : NIL;
594   mh_file (tmp,stream->mailbox);/* get directory name */
595   LOCAL->dir = cpystr (tmp);	/* copy directory name for later */
596   LOCAL->scantime = 0;		/* not scanned yet */
597   LOCAL->cachedtexts = 0;	/* no cached texts */
598   stream->sequence++;		/* bump sequence number */
599 				/* parse mailbox */
600   stream->nmsgs = stream->recent = 0;
601   if (!mh_ping (stream)) return NIL;
602   if (!(stream->nmsgs || stream->silent))
603     mm_log ("Mailbox is empty",(long) NIL);
604   return stream;		/* return stream to caller */
605 }
606 
607 /* MH mail close
608  * Accepts: MAIL stream
609  *	    close options
610  */
611 
mh_close(MAILSTREAM * stream,long options)612 void mh_close (MAILSTREAM *stream,long options)
613 {
614   if (LOCAL) {			/* only if a file is open */
615     int silent = stream->silent;
616     stream->silent = T;		/* note this stream is dying */
617     if (options & CL_EXPUNGE) mh_expunge (stream,NIL,NIL);
618     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
619 				/* nuke the local data */
620     fs_give ((void **) &stream->local);
621     stream->dtb = NIL;		/* log out the DTB */
622     stream->silent = silent;	/* reset silent state */
623   }
624 }
625 
626 
627 /* MH mail fetch fast information
628  * Accepts: MAIL stream
629  *	    sequence
630  *	    option flags
631  */
632 
mh_fast(MAILSTREAM * stream,char * sequence,long flags)633 void mh_fast (MAILSTREAM *stream,char *sequence,long flags)
634 {
635   MESSAGECACHE *elt;
636   unsigned long i;
637 				/* set up metadata for all messages */
638   if (stream && LOCAL && ((flags & FT_UID) ?
639 			  mail_uid_sequence (stream,sequence) :
640 			  mail_sequence (stream,sequence)))
641     for (i = 1; i <= stream->nmsgs; i++)
642       if ((elt = mail_elt (stream,i))->sequence &&
643 	  !(elt->day && elt->rfc822_size)) mh_load_message (stream,i,NIL);
644 }
645 
646 /* MH load message into cache
647  * Accepts: MAIL stream
648  *	    message #
649  *	    option flags
650  */
651 
mh_load_message(MAILSTREAM * stream,unsigned long msgno,long flags)652 void mh_load_message (MAILSTREAM *stream,unsigned long msgno,long flags)
653 {
654   unsigned long i,j,nlseen;
655   int fd;
656   unsigned char c,*t;
657   struct stat sbuf;
658   MESSAGECACHE *elt;
659   FDDATA d;
660   STRING bs;
661   elt = mail_elt (stream,msgno);/* get elt */
662 				/* build message file name */
663   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
664 				/* anything we need not currently cached? */
665   if ((!elt->day || !elt->rfc822_size ||
666        ((flags & MLM_HEADER) && !elt->private.msg.header.text.data) ||
667        ((flags & MLM_TEXT) && !elt->private.msg.text.text.data)) &&
668       ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
669     fstat (fd,&sbuf);		/* get file metadata */
670     d.fd = fd;			/* set up file descriptor */
671     d.pos = 0;			/* start of file */
672     d.chunk = LOCAL->buf;
673     d.chunksize = CHUNKSIZE;
674     INIT (&bs,fd_string,&d,sbuf.st_size);
675     if (!elt->day) {		/* set internaldate to file date */
676       struct tm *tm = gmtime (&sbuf.st_mtime);
677       elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
678       elt->year = tm->tm_year + 1900 - BASEYEAR;
679       elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
680       elt->seconds = tm->tm_sec;
681       elt->zhours = 0; elt->zminutes = 0;
682     }
683 
684     if (!elt->rfc822_size) {	/* know message size yet? */
685       for (i = 0, j = SIZE (&bs), nlseen = 0; j--; ) switch (SNX (&bs)) {
686       case '\015':		/* unlikely carriage return */
687 	if (!j || (CHR (&bs) != '\012')) {
688 	  i++;			/* ugh, raw CR */
689 	  nlseen = NIL;
690 	  break;
691 	}
692 	SNX (&bs);		/* eat the line feed, drop in */
693 	--j;
694       case '\012':		/* line feed? */
695 	i += 2;			/* count a CRLF */
696 				/* header size known yet? */
697 	if (!elt->private.msg.header.text.size && nlseen) {
698 				/* note position in file */
699 	  elt->private.special.text.size = GETPOS (&bs);
700 				/* and CRLF-adjusted size */
701 	  elt->private.msg.header.text.size = i;
702 	}
703 	nlseen = T;		/* note newline seen */
704 	break;
705       default:			/* ordinary character */
706 	i++;
707 	nlseen = NIL;
708 	break;
709       }
710       SETPOS (&bs,0);		/* restore old position */
711       elt->rfc822_size = i;	/* note that we have size now */
712 				/* header is entire message if no delimiter */
713       if (!elt->private.msg.header.text.size)
714 	elt->private.msg.header.text.size = elt->rfc822_size;
715 				/* text is remainder of message */
716       elt->private.msg.text.text.size =
717 	elt->rfc822_size - elt->private.msg.header.text.size;
718     }
719 				/* need to load cache with message data? */
720     if (((flags & MLM_HEADER) && !elt->private.msg.header.text.data) ||
721 	((flags & MLM_TEXT) && !elt->private.msg.text.text.data)) {
722 				/* purge cache if too big */
723       if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) {
724 				/* just can't keep that much */
725 	mail_gc (stream,GC_TEXTS);
726 	LOCAL->cachedtexts = 0;
727       }
728 
729       if ((flags & MLM_HEADER) && !elt->private.msg.header.text.data) {
730 	t = elt->private.msg.header.text.data =
731 	  (unsigned char *) fs_get (elt->private.msg.header.text.size + 1);
732 	LOCAL->cachedtexts += elt->private.msg.header.text.size;
733 				/* read in message header */
734 	for (i = 0; i < elt->private.msg.header.text.size; i++)
735 	  switch (c = SNX (&bs)) {
736 	  case '\015':		/* unlikely carriage return */
737 	    *t++ = c;
738 	    if (CHR (&bs) == '\012') {
739 	      *t++ = SNX (&bs);
740 	      i++;
741 	    }
742 	    break;
743 	  case '\012':		/* line feed? */
744 	    *t++ = '\015';
745 	    i++;
746 	  default:
747 	    *t++ = c;
748 	    break;
749 	  }
750 	*t = '\0';		/* tie off string */
751 	if ((t - elt->private.msg.header.text.data) !=
752 	    elt->private.msg.header.text.size) fatal ("mh hdr size mismatch");
753       }
754       if ((flags & MLM_TEXT) && !elt->private.msg.text.text.data) {
755 	t = elt->private.msg.text.text.data =
756 	  (unsigned char *) fs_get (elt->private.msg.text.text.size + 1);
757 	SETPOS (&bs,elt->private.special.text.size);
758 	LOCAL->cachedtexts += elt->private.msg.text.text.size;
759 				/* read in message text */
760 	for (i = 0; i < elt->private.msg.text.text.size; i++)
761 	  switch (c = SNX (&bs)) {
762 	  case '\015':		/* unlikely carriage return */
763 	    *t++ = c;
764 	    if (CHR (&bs) == '\012') {
765 	      *t++ = SNX (&bs);
766 	      i++;
767 	    }
768 	    break;
769 	  case '\012':		/* line feed? */
770 	    *t++ = '\015';
771 	    i++;
772 	  default:
773 	    *t++ = c;
774 	    break;
775 	  }
776 	*t = '\0';		/* tie off string */
777 	if ((t - elt->private.msg.text.text.data) !=
778 	    elt->private.msg.text.text.size) fatal ("mh txt size mismatch");
779       }
780     }
781     close (fd);			/* flush message file */
782   }
783 }
784 
785 /* MH mail fetch message header
786  * Accepts: MAIL stream
787  *	    message # to fetch
788  *	    pointer to returned header text length
789  *	    option flags
790  * Returns: message header in RFC822 format
791  */
792 
mh_header(MAILSTREAM * stream,unsigned long msgno,unsigned long * length,long flags)793 char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
794 		 long flags)
795 {
796   MESSAGECACHE *elt;
797   *length = 0;			/* default to empty */
798   if (flags & FT_UID) return "";/* UID call "impossible" */
799   elt = mail_elt (stream,msgno);/* get elt */
800   if (!elt->private.msg.header.text.data)
801     mh_load_message (stream,msgno,MLM_HEADER);
802   *length = elt->private.msg.header.text.size;
803   return (char *) elt->private.msg.header.text.data;
804 }
805 
806 
807 /* MH mail fetch message text (body only)
808  * Accepts: MAIL stream
809  *	    message # to fetch
810  *	    pointer to returned stringstruct
811  *	    option flags
812  * Returns: T on success, NIL on failure
813  */
814 
mh_text(MAILSTREAM * stream,unsigned long msgno,STRING * bs,long flags)815 long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
816 {
817   MESSAGECACHE *elt;
818 				/* UID call "impossible" */
819   if (flags & FT_UID) return NIL;
820   elt = mail_elt (stream,msgno);/* get elt */
821 				/* snarf message if don't have it yet */
822   if (!elt->private.msg.text.text.data) {
823     mh_load_message (stream,msgno,MLM_TEXT);
824     if (!elt->private.msg.text.text.data) return NIL;
825   }
826   if (!(flags & FT_PEEK)) {	/* mark as seen */
827     mail_elt (stream,msgno)->seen = T;
828     mm_flags (stream,msgno);
829   }
830   INIT (bs,mail_string,elt->private.msg.text.text.data,
831 	elt->private.msg.text.text.size);
832   return T;
833 }
834 
835 /* MH mail ping mailbox
836  * Accepts: MAIL stream
837  * Returns: T if stream alive, else NIL
838  */
839 
mh_ping(MAILSTREAM * stream)840 long mh_ping (MAILSTREAM *stream)
841 {
842   MAILSTREAM *sysibx = NIL;
843   MESSAGECACHE *elt,*selt;
844   struct stat sbuf;
845   char *s,tmp[MAILTMPLEN];
846   int fd;
847   unsigned long i,j,r;
848   unsigned long old = stream->uid_last;
849   long nmsgs = stream->nmsgs;
850   long recent = stream->recent;
851   int silent = stream->silent;
852   if (stat (LOCAL->dir,&sbuf)) {/* directory exists? */
853     if (stream->inbox &&	/* no, create if INBOX */
854 	dummy_create_path (stream,strcat (mh_file (tmp,MHINBOX),"/"),
855 			   get_dir_protection ("INBOX"))) return T;
856     sprintf (tmp,"Can't open mailbox %.80s: no such mailbox",stream->mailbox);
857     mm_log (tmp,ERROR);
858     return NIL;
859   }
860   stream->silent = T;		/* don't pass up mm_exists() events yet */
861   if (sbuf.st_ctime != LOCAL->scantime) {
862     struct direct **names = NIL;
863     long nfiles = scandir (LOCAL->dir,&names,mh_select,mh_numsort);
864     if (nfiles < 0) nfiles = 0;	/* in case error */
865 				/* note scanned now */
866     LOCAL->scantime = sbuf.st_ctime;
867 				/* scan directory */
868     for (i = 0; i < nfiles; ++i) {
869 				/* if newly seen, add to list */
870       if ((j = atoi (names[i]->d_name)) > old) {
871 	mail_exists (stream,++nmsgs);
872 	stream->uid_last = (elt = mail_elt (stream,nmsgs))->private.uid = j;
873 	elt->valid = T;		/* note valid flags */
874 	if (old) {		/* other than the first pass? */
875 	  elt->recent = T;	/* yup, mark as recent */
876 	  recent++;		/* bump recent count */
877 	}
878 	else {			/* see if already read */
879 	  sprintf (tmp,"%s/%s",LOCAL->dir,names[i]->d_name);
880 	  if (!stat (tmp,&sbuf) && (sbuf.st_atime > sbuf.st_mtime))
881 	    elt->seen = T;
882 	}
883       }
884       fs_give ((void **) &names[i]);
885     }
886 				/* free directory */
887     if ((s = (void *) names) != NULL) fs_give ((void **) &s);
888   }
889 
890 				/* if INBOX, snarf from system INBOX  */
891   if (stream->inbox && strcmp (sysinbox (),stream->mailbox)) {
892     old = stream->uid_last;
893     mm_critical (stream);	/* go critical */
894 				/* see if anything in system inbox */
895     if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
896 	(sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
897 	!sysibx->rdonly && (r = sysibx->nmsgs)) {
898       for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */
899 				/* build file name we will use */
900 	sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,++old);
901 				/* snarf message from Berkeley mailbox */
902 	selt = mail_elt (sysibx,i);
903 	if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL,
904 			 (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
905 	     >= 0) &&
906 	    (s = mail_fetchheader_full (sysibx,i,NIL,&j,FT_INTERNAL)) &&
907 	    (write (fd,s,j) == j) &&
908 	    (s = mail_fetchtext_full (sysibx,i,&j,FT_INTERNAL|FT_PEEK)) &&
909 	    (write (fd,s,j) == j) && !fsync (fd) && !close (fd)) {
910 				/* swell the cache */
911 	  mail_exists (stream,++nmsgs);
912 	  stream->uid_last =	/* create new elt, note its file number */
913 	    (elt = mail_elt (stream,nmsgs))->private.uid = old;
914 	  recent++;		/* bump recent count */
915 				/* set up initial flags and date */
916 	  elt->valid = elt->recent = T;
917 	  elt->seen = selt->seen;
918 	  elt->deleted = selt->deleted;
919 	  elt->flagged = selt->flagged;
920 	  elt->answered = selt->answered;
921 	  elt->draft = selt->draft;
922 	  elt->day = selt->day;elt->month = selt->month;elt->year = selt->year;
923 	  elt->hours = selt->hours;elt->minutes = selt->minutes;
924 	  elt->seconds = selt->seconds;
925 	  elt->zhours = selt->zhours; elt->zminutes = selt->zminutes;
926 	  elt->zoccident = selt->zoccident;
927 	  mh_setdate (LOCAL->buf,elt);
928 	  sprintf (tmp,"%lu",i);/* delete it from the sysinbox */
929 	  mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
930 	}
931 
932 	else {			/* failed to snarf */
933 	  if (fd) {		/* did it ever get opened? */
934 	    close (fd);		/* close descriptor */
935 	    unlink (LOCAL->buf);/* flush this file */
936 	  }
937 	  sprintf (tmp,"Message copy to MH mailbox failed: %.80s",
938 		   strerror (errno));
939 	  mm_log (tmp,ERROR);
940 	  r = 0;		/* stop the snarf in its tracks */
941 	}
942       }
943 				/* update scan time */
944       if (!stat (LOCAL->dir,&sbuf)) LOCAL->scantime = sbuf.st_ctime;
945       mail_expunge (sysibx);	/* now expunge all those messages */
946     }
947     if (sysibx) mail_close (sysibx);
948     mm_nocritical (stream);	/* release critical */
949   }
950   stream->silent = silent;	/* can pass up events now */
951   mail_exists (stream,nmsgs);	/* notify upper level of mailbox size */
952   mail_recent (stream,recent);
953   return T;			/* return that we are alive */
954 }
955 
956 /* MH mail check mailbox
957  * Accepts: MAIL stream
958  */
959 
mh_check(MAILSTREAM * stream)960 void mh_check (MAILSTREAM *stream)
961 {
962   /* Perhaps in the future this will preserve flags */
963   if (mh_ping (stream)) mm_log ("Check completed",(long) NIL);
964 }
965 
966 
967 /* MH mail expunge mailbox
968  * Accepts: MAIL stream
969  *	    sequence to expunge if non-NIL
970  *	    expunge options
971  * Returns: T, always
972  */
973 
mh_expunge(MAILSTREAM * stream,char * sequence,long options)974 long mh_expunge (MAILSTREAM *stream,char *sequence,long options)
975 {
976   long ret;
977   MESSAGECACHE *elt;
978   unsigned long i = 1;
979   unsigned long n = 0;
980   unsigned long recent = stream->recent;
981   if ((ret = sequence ? ((options & EX_UID) ?
982 			mail_uid_sequence (stream,sequence) :
983 			mail_sequence (stream,sequence)) : LONGT) != 0L) {
984     mm_critical (stream);	/* go critical */
985     while (i <= stream->nmsgs) {/* for each message */
986       elt = mail_elt (stream,i);/* if deleted, need to trash it */
987       if (elt->deleted && (sequence ? elt->sequence : T)) {
988 	sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
989 	if (unlink (LOCAL->buf)) {/* try to delete the message */
990 	  sprintf (LOCAL->buf,"Expunge of message %lu failed, aborted: %s",i,
991 		   strerror (errno));
992 	  mm_log (LOCAL->buf,(long) NIL);
993 	  break;
994 	}
995 				/* note uncached */
996 	LOCAL->cachedtexts -= ((elt->private.msg.header.text.data ?
997 				elt->private.msg.header.text.size : 0) +
998 			       (elt->private.msg.text.text.data ?
999 				elt->private.msg.text.text.size : 0));
1000 	mail_gc_msg (&elt->private.msg,GC_ENV | GC_TEXTS);
1001 				/* if recent, note one less recent message */
1002 	if (elt->recent) --recent;
1003 				/* notify upper levels */
1004 	mail_expunged (stream,i);
1005 	n++;			/* count up one more expunged message */
1006       }
1007       else i++;			/* otherwise try next message */
1008     }
1009     if (n) {			/* output the news if any expunged */
1010       sprintf (LOCAL->buf,"Expunged %lu messages",n);
1011       mm_log (LOCAL->buf,(long) NIL);
1012     }
1013     else mm_log ("No messages deleted, so no update needed",(long) NIL);
1014     mm_nocritical (stream);	/* release critical */
1015 				/* notify upper level of new mailbox size */
1016     mail_exists (stream,stream->nmsgs);
1017     mail_recent (stream,recent);
1018   }
1019   return ret;
1020 }
1021 
1022 /* MH mail copy message(s)
1023  * Accepts: MAIL stream
1024  *	    sequence
1025  *	    destination mailbox
1026  *	    copy options
1027  * Returns: T if copy successful, else NIL
1028  */
1029 
mh_copy(MAILSTREAM * stream,char * sequence,char * mailbox,long options)1030 long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
1031 {
1032   FDDATA d;
1033   STRING st;
1034   MESSAGECACHE *elt;
1035   struct stat sbuf;
1036   int fd;
1037   unsigned long i;
1038   char flags[MAILTMPLEN],date[MAILTMPLEN];
1039   appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
1040   long ret = NIL;
1041 				/* copy the messages */
1042   if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
1043       mail_sequence (stream,sequence))
1044     for (i = 1; i <= stream->nmsgs; i++)
1045       if ((elt = mail_elt (stream,i))->sequence) {
1046 	sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
1047 	if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) < 0) return NIL;
1048 	fstat (fd,&sbuf);	/* get size of message */
1049 	if (!elt->day) {	/* set internaldate to file date if needed */
1050 	  struct tm *tm = gmtime (&sbuf.st_mtime);
1051 	  elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
1052 	  elt->year = tm->tm_year + 1900 - BASEYEAR;
1053 	  elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
1054 	  elt->seconds = tm->tm_sec;
1055 	  elt->zhours = 0; elt->zminutes = 0;
1056 	}
1057 	d.fd = fd;		/* set up file descriptor */
1058 	d.pos = 0;		/* start of file */
1059 	d.chunk = LOCAL->buf;
1060 	d.chunksize = CHUNKSIZE;
1061 				/* kludge; mh_append would just strip CRs */
1062 	INIT (&st,fd_string,&d,sbuf.st_size);
1063 				/* init flag string */
1064 	flags[0] = flags[1] = '\0';
1065 	if (elt->seen) strcat (flags," \\Seen");
1066 	if (elt->deleted) strcat (flags," \\Deleted");
1067 	if (elt->flagged) strcat (flags," \\Flagged");
1068 	if (elt->answered) strcat (flags," \\Answered");
1069 	if (elt->draft) strcat (flags," \\Draft");
1070 	flags[0] = '(';		/* open list */
1071 	strcat (flags,")");	/* close list */
1072 	mail_date (date,elt);	/* generate internal date */
1073 	if (au) mail_parameters (NIL,SET_APPENDUID,NIL);
1074 	if ((ret = mail_append_full (NIL,mailbox,flags,date,&st)) &&
1075 	    (options & CP_MOVE)) elt->deleted = T;
1076 	if (au) mail_parameters (NIL,SET_APPENDUID,(void *) au);
1077 	close (fd);
1078       }
1079   if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
1080     mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
1081   return ret;			/* return success */
1082 }
1083 
1084 /* MH mail append message from stringstruct
1085  * Accepts: MAIL stream
1086  *	    destination mailbox
1087  *	    append callback
1088  *	    data for callback
1089  * Returns: T if append successful, else NIL
1090  */
1091 
mh_append(MAILSTREAM * stream,char * mailbox,append_t af,void * data)1092 long mh_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
1093 {
1094   struct direct **names = NIL;
1095   int fd;
1096   char c,*flags,*date,*s,tmp[MAILTMPLEN];
1097   STRING *message;
1098   MESSAGECACHE elt;
1099   FILE *df;
1100   long i,size,last,nfiles;
1101   long ret = LONGT;
1102 				/* default stream to prototype */
1103   if (!stream) stream = &mhproto;
1104 				/* make sure valid mailbox */
1105   if (!mh_isvalid (mailbox,tmp,NIL)) switch (errno) {
1106   case ENOENT:			/* no such file? */
1107     if (!((!compare_cstring (mailbox,MHINBOX) ||
1108 	   !compare_cstring (mailbox,"INBOX")) &&
1109 	  (mh_file (tmp,MHINBOX) &&
1110 	   dummy_create_path (stream,strcat (tmp,"/"),
1111 			      get_dir_protection (mailbox))))) {
1112       mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
1113       return NIL;
1114     }
1115 				/* falls through */
1116   case 0:			/* merely empty file? */
1117     break;
1118   case EINVAL:
1119     sprintf (tmp,"Invalid MH-format mailbox name: %.80s",mailbox);
1120     mm_log (tmp,ERROR);
1121     return NIL;
1122   default:
1123     sprintf (tmp,"Not a MH-format mailbox: %.80s",mailbox);
1124     mm_log (tmp,ERROR);
1125     return NIL;
1126   }
1127 				/* get first message */
1128   if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
1129   if ((nfiles = scandir (tmp,&names,mh_select,mh_numsort)) > 0) {
1130 				/* largest number */
1131     last = atoi (names[nfiles-1]->d_name);
1132     for (i = 0; i < nfiles; ++i) /* free directory */
1133       fs_give ((void **) &names[i]);
1134   }
1135   else last = 0;		/* no messages here yet */
1136   if ((s = (void *) names) != NULL) fs_give ((void **) &s);
1137 
1138   mm_critical (stream);		/* go critical */
1139   do {
1140     if (!SIZE (message)) {	/* guard against zero-length */
1141       mm_log ("Append of zero-length message",ERROR);
1142       ret = NIL;
1143       break;
1144     }
1145     if (date) {			/* want to preserve date? */
1146 				/* yes, parse date into an elt */
1147       if (!mail_parse_date (&elt,date)) {
1148 	sprintf (tmp,"Bad date in append: %.80s",date);
1149 	mm_log (tmp,ERROR);
1150 	ret = NIL;
1151 	break;
1152       }
1153     }
1154     mh_file (tmp,mailbox);	/* build file name we will use */
1155     sprintf (tmp + strlen (tmp),"/%ld",++last);
1156     if (((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL,
1157 		     (long)mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0)||
1158 	!(df = fdopen (fd,"ab"))) {
1159       sprintf (tmp,"Can't open append message: %s",strerror (errno));
1160       mm_log (tmp,ERROR);
1161       ret = NIL;
1162       break;
1163     }
1164 				/* copy the data w/o CR's */
1165     for (size = 0,i = SIZE (message); i && ret; --i)
1166       if (((c = SNX (message)) != '\015') && (putc (c,df) == EOF)) ret = NIL;
1167 				/* close the file */
1168     if (!ret || fclose (df)) {
1169       unlink (tmp);		/* delete message */
1170       sprintf (tmp,"Message append failed: %s",strerror (errno));
1171       mm_log (tmp,ERROR);
1172       ret = NIL;
1173     }
1174     if (ret) {			/* set the date for this message */
1175       if (date) mh_setdate (tmp,&elt);
1176 				/* get next message */
1177       if (!(*af) (stream,data,&flags,&date,&message)) ret = NIL;
1178     }
1179   } while (ret && message);
1180   mm_nocritical (stream);	/* release critical */
1181   if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
1182     mm_log ("Can not return meaningful APPENDUID with this mailbox format",
1183 	    WARN);
1184   return ret;
1185 }
1186 
1187 /* Internal routines */
1188 
1189 
1190 /* MH file name selection test
1191  * Accepts: candidate directory entry
1192  * Returns: T to use file name, NIL to skip it
1193  */
1194 
mh_select(struct direct * name)1195 int mh_select (struct direct *name)
1196 {
1197   char c;
1198   char *s = name->d_name;
1199   while ((c = *s++) != '\0') if (!isdigit (c)) return NIL;
1200   return T;
1201 }
1202 
1203 
1204 /* MH file name comparison
1205  * Accepts: first candidate directory entry
1206  *	    second candidate directory entry
1207  * Returns: negative if d1 < d2, 0 if d1 == d2, positive if d1 > d2
1208  */
1209 
mh_numsort(const void * d1,const void * d2)1210 int mh_numsort (const void *d1,const void *d2)
1211 {
1212   return atoi ((*(struct direct **) d1)->d_name) -
1213     atoi ((*(struct direct **) d2)->d_name);
1214 }
1215 
1216 
1217 /* MH mail build file name
1218  * Accepts: destination string
1219  *          source
1220  * Returns: destination
1221  */
1222 
mh_file(char * dst,char * name)1223 char *mh_file (char *dst,char *name)
1224 {
1225   char *s;
1226   char *path = mh_path (dst);
1227   if (!path) fatal ("No mh path in mh_file()!");
1228 				/* INBOX becomes "inbox" in the MH path */
1229   if (!compare_cstring (name,MHINBOX) || !compare_cstring (name,"INBOX"))
1230     sprintf (dst,"%.900s/%.80s",path,MHINBOXDIR);
1231 				/* #mh names skip past prefix */
1232   else if (*name == '#') sprintf (dst,"%.100s/%.900s",path,name + 4);
1233   else mailboxfile (dst,name);	/* all other names */
1234 				/* tie off unnecessary trailing / */
1235   if ((s = strrchr (dst,'/')) && !s[1] && (s[-1] == '/')) *s = '\0';
1236   return dst;
1237 }
1238 
1239 /* MH canonicalize name
1240  * Accepts: buffer to write name
1241  *	    reference
1242  *	    pattern
1243  * Returns: T if success, NIL if failure
1244  */
1245 
mh_canonicalize(char * pattern,char * ref,char * pat)1246 long mh_canonicalize (char *pattern,char *ref,char *pat)
1247 {
1248   unsigned long i;
1249   char *s,tmp[MAILTMPLEN];
1250   if (ref && *ref) {		/* have a reference */
1251     strcpy (pattern,ref);	/* copy reference to pattern */
1252 				/* # overrides mailbox field in reference */
1253     if (*pat == '#') strcpy (pattern,pat);
1254 				/* pattern starts, reference ends, with / */
1255     else if ((*pat == '/') && (pattern[strlen (pattern) - 1] == '/'))
1256       strcat (pattern,pat + 1);	/* append, omitting one of the period */
1257     else strcat (pattern,pat);	/* anything else is just appended */
1258   }
1259   else strcpy (pattern,pat);	/* just have basic name */
1260   if (mh_isvalid (pattern,tmp,T)) {
1261 				/* count wildcards */
1262     for (i = 0, s = pattern; *s; s++) if ((*s == '*') || (*s == '%')) ++i;
1263 				/* success if not too many */
1264     if (i <= MAXWILDCARDS) return LONGT;
1265     mm_log ("Excessive wildcards in LIST/LSUB",ERROR);
1266   }
1267   return NIL;
1268 }
1269 
1270 /* Set date for message
1271  * Accepts: file name
1272  *	    elt containing date
1273  */
1274 
mh_setdate(char * file,MESSAGECACHE * elt)1275 void mh_setdate (char *file,MESSAGECACHE *elt)
1276 {
1277   time_t tp[2];
1278   tp[0] = time (0);		/* atime is now */
1279   tp[1] = mail_longdate (elt);	/* modification time */
1280   utime (file,tp);		/* set the times */
1281 }
1282