1 /* ========================================================================
2 * Copyright 2008-2011 Mark Crispin
3 * ========================================================================
7 * Program: MH mail routines
9 * Author(s): Mark Crispin
11 * Date: 23 February 1992
12 * Last Edited: 8 April 2011
14 * Previous versions of this file were
16 * Copyright 1988-2007 University of Washington
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
22 * http://www.apache.org/licenses/LICENSE-2.0
31 extern int errno
; /* just in case */
43 /* Build parameters */
45 #define MHINBOX "#mhinbox" /* corresponds to namespace in env_unix.c */
46 #define MHINBOXDIR "inbox"
47 #define MHPROFILE ".mh_profile"
49 #define MHSEQUENCE ".mh_sequence"
50 #define MHSEQUENCES ".mh_sequences"
54 /* mh_load_message() flags */
56 #define MLM_HEADER 0x1 /* load message text */
57 #define MLM_TEXT 0x2 /* load message text */
59 /* MH I/O stream local data */
61 typedef struct mh_local
{
62 char *dir
; /* spool directory name */
63 unsigned char buf
[CHUNKSIZE
]; /* temporary buffer */
64 unsigned long cachedtexts
; /* total size of all cached texts */
65 time_t scantime
; /* last time directory scanned */
69 /* Convenient access to local data */
71 #define LOCAL ((MHLOCAL *) stream->local)
74 /* Function prototypes */
76 DRIVER
*mh_valid (char *name
);
77 int mh_isvalid (char *name
,char *tmp
,long synonly
);
78 int mh_namevalid (char *name
);
79 char *mh_path (char *tmp
);
80 void *mh_parameters (long function
,void *value
);
81 long mh_dirfmttest (char *name
);
82 void mh_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
83 void mh_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
84 void mh_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
85 void mh_list_work (MAILSTREAM
*stream
,char *dir
,char *pat
,long level
);
86 long mh_subscribe (MAILSTREAM
*stream
,char *mailbox
);
87 long mh_unsubscribe (MAILSTREAM
*stream
,char *mailbox
);
88 long mh_create (MAILSTREAM
*stream
,char *mailbox
);
89 long mh_delete (MAILSTREAM
*stream
,char *mailbox
);
90 long mh_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
91 MAILSTREAM
*mh_open (MAILSTREAM
*stream
);
92 void mh_close (MAILSTREAM
*stream
,long options
);
93 void mh_fast (MAILSTREAM
*stream
,char *sequence
,long flags
);
94 void mh_load_message (MAILSTREAM
*stream
,unsigned long msgno
,long flags
);
95 char *mh_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
97 long mh_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
98 long mh_ping (MAILSTREAM
*stream
);
99 void mh_check (MAILSTREAM
*stream
);
100 long mh_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
101 long mh_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,
103 long mh_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
105 int mh_select (struct direct
*name
);
106 int mh_numsort (const void *d1
,const void *d2
);
107 char *mh_file (char *dst
,char *name
);
108 long mh_canonicalize (char *pattern
,char *ref
,char *pat
);
109 void mh_setdate (char *file
,MESSAGECACHE
*elt
);
111 /* MH mail routines */
114 /* Driver dispatch used by MAIL */
117 "mh", /* driver name */
119 DR_MAIL
|DR_LOCAL
|DR_NOFAST
|DR_NAMESPACE
|DR_NOSTICKY
|DR_DIRFMT
,
120 (DRIVER
*) NIL
, /* next driver */
121 mh_valid
, /* mailbox is valid for us */
122 mh_parameters
, /* manipulate parameters */
123 mh_scan
, /* scan mailboxes */
124 mh_list
, /* find mailboxes */
125 mh_lsub
, /* find subscribed mailboxes */
126 mh_subscribe
, /* subscribe to mailbox */
127 mh_unsubscribe
, /* unsubscribe from mailbox */
128 mh_create
, /* create mailbox */
129 mh_delete
, /* delete mailbox */
130 mh_rename
, /* rename mailbox */
131 mail_status_default
, /* status of mailbox */
132 mh_open
, /* open mailbox */
133 mh_close
, /* close mailbox */
134 mh_fast
, /* fetch message "fast" attributes */
135 NIL
, /* fetch message flags */
136 NIL
, /* fetch overview */
137 NIL
, /* fetch message envelopes */
138 mh_header
, /* fetch message header */
139 mh_text
, /* fetch message body */
140 NIL
, /* fetch partial message text */
141 NIL
, /* unique identifier */
142 NIL
, /* message number */
143 NIL
, /* modify flags */
144 NIL
, /* per-message modify flags */
145 NIL
, /* search for message based on criteria */
146 NIL
, /* sort messages */
147 NIL
, /* thread messages */
148 mh_ping
, /* ping mailbox to see if still alive */
149 mh_check
, /* check for new messages */
150 mh_expunge
, /* expunge deleted messages */
151 mh_copy
, /* copy messages to another mailbox */
152 mh_append
, /* append string message to mailbox */
153 NIL
/* garbage collect stream */
156 /* prototype stream */
157 MAILSTREAM mhproto
= {&mhdriver
};
160 static char *mh_profile
= NIL
; /* holds MH profile */
161 static char *mh_pathname
= NIL
; /* holds MH path name */
162 static long mh_once
= 0; /* already snarled once */
163 static long mh_allow_inbox
=NIL
;/* allow INBOX as well as MHINBOX */
165 /* MH mail validate mailbox
166 * Accepts: mailbox name
167 * Returns: our driver if name is valid, NIL otherwise
170 DRIVER
*mh_valid (char *name
)
172 char tmp
[MAILTMPLEN
];
173 return mh_isvalid (name
,tmp
,T
) ? &mhdriver
: NIL
;
177 /* MH mail test for valid mailbox
178 * Accepts: mailbox name
179 * temporary buffer to use
180 * syntax only test flag
181 * Returns: T if valid, NIL otherwise
184 int mh_isvalid (char *name
,char *tmp
,long synonly
)
187 char *s
,*t
,altname
[MAILTMPLEN
];
190 errno
= NIL
; /* zap any error condition */
192 if ((mh_allow_inbox
&& !compare_cstring (name
,"INBOX")) ||
193 !compare_cstring (name
,MHINBOX
) ||
194 ((name
[0] == '#') && ((name
[1] == 'm') || (name
[1] == 'M')) &&
195 ((name
[2] == 'h') || (name
[2] == 'H')) && (name
[3] == '/') && name
[4])){
196 if (mh_path (tmp
)) /* validate name if INBOX or not synonly */
197 ret
= (synonly
&& compare_cstring (name
,"INBOX")) ?
198 T
: ((stat (mh_file (tmp
,name
),&sbuf
) == 0) &&
199 (sbuf
.st_mode
& S_IFMT
) == S_IFDIR
);
200 else if (!mh_once
++) { /* only report error once */
201 sprintf (tmp
,"%.900s not found, mh format names disabled",mh_profile
);
205 /* see if non-NS name within mh hierarchy */
206 else if ((name
[0] != '#') && (s
= mh_path (tmp
)) && (i
= strlen (s
)) &&
207 (t
= mailboxfile (tmp
,name
)) && !strncmp (t
,s
,i
) &&
208 (tmp
[i
] == '/') && tmp
[i
+1]) {
209 sprintf (altname
,"#mh%.900s",tmp
+i
);
210 /* can't do synonly here! */
211 ret
= mh_isvalid (altname
,tmp
,NIL
);
213 else errno
= EINVAL
; /* bogus name */
217 /* MH mail test for valid mailbox
218 * Accepts: mailbox name
219 * Returns: T if valid, NIL otherwise
222 int mh_namevalid (char *name
)
225 if (name
[0] == '#' && (name
[1] == 'm' || name
[1] == 'M') &&
226 (name
[2] == 'h' || name
[2] == 'H') && name
[3] == '/')
227 for (s
= name
; s
&& *s
;) { /* make sure no all-digit nodes */
228 if (isdigit (*s
)) s
++; /* digit, check this node further... */
229 else if (*s
== '/') break;/* all digit node, barf */
230 /* non-digit, skip to next node or return */
231 else if (!((s
= strchr (s
+1,'/')) && *++s
)) return T
;
233 return NIL
; /* all numeric or empty node */
237 * Accepts: temporary buffer
238 * Returns: MH path or NIL if MH disabled
241 char *mh_path (char *tmp
)
246 if (!mh_profile
) { /* build mh_profile and mh_pathname now */
247 sprintf (tmp
,"%s/%s",myhomedir (),MHPROFILE
);
248 if ((fd
= open (mh_profile
= cpystr (tmp
),O_RDONLY
,NIL
)) >= 0) {
249 fstat (fd
,&sbuf
); /* yes, get size and read file */
250 read (fd
,(t
= (char *) fs_get (sbuf
.st_size
+ 1)),sbuf
.st_size
);
251 close (fd
); /* don't need the file any more */
252 t
[sbuf
.st_size
] = '\0'; /* tie it off */
253 /* parse profile file */
254 for (s
= strtok_r (t
,"\r\n",&r
); s
&& *s
; s
= strtok_r (NIL
,"\r\n",&r
)) {
255 /* found space in line? */
256 if (v
= strpbrk (s
," \t")) {
257 *v
++ = '\0'; /* tie off, is keyword "Path:"? */
258 if (!compare_cstring (s
,"Path:")) {
259 /* skip whitespace */
260 while ((*v
== ' ') || (*v
== '\t')) ++v
;
262 if (*v
== '/') s
= v
;
263 else sprintf (s
= tmp
,"%s/%s",myhomedir (),v
);
265 mh_pathname
= cpystr (s
);
266 break; /* don't need to look at rest of file */
270 fs_give ((void **) &t
); /* flush profile text */
271 if (!mh_pathname
) { /* default path if not in the profile */
272 sprintf (tmp
,"%s/%s",myhomedir (),MHPATH
);
273 mh_pathname
= cpystr (tmp
);
280 /* MH manipulate driver parameters
281 * Accepts: function code
282 * function-dependent value
283 * Returns: function-dependent return value
286 void *mh_parameters (long function
,void *value
)
289 switch ((int) function
) {
291 if (value
) ret
= mh_file ((char *) value
,"INBOX");
294 ret
= (void *) mh_dirfmttest
;
297 if (mh_profile
) fs_give ((void **) &mh_profile
);
298 mh_profile
= cpystr ((char *) value
);
300 ret
= (void *) mh_profile
;
303 if (mh_pathname
) fs_give ((void **) &mh_pathname
);
304 mh_pathname
= cpystr ((char *) value
);
306 ret
= (void *) mh_pathname
;
308 case SET_MHALLOWINBOX
:
309 mh_allow_inbox
= value
? T
: NIL
;
310 case GET_MHALLOWINBOX
:
311 ret
= (void *) (mh_allow_inbox
? VOIDT
: NIL
);
317 /* MH test for directory format internal node
318 * Accepts: candidate node name
319 * Returns: T if internal name, NIL otherwise
322 long mh_dirfmttest (char *s
)
325 /* sequence(s) file is an internal name */
326 if (strcmp (s
,MHSEQUENCE
) && strcmp (s
,MHSEQUENCES
)) {
327 if (*s
== MHCOMMA
) ++s
; /* else comma + all numeric name */
328 /* success if all-numeric */
329 while (c
= *s
++) if (!isdigit (c
)) return NIL
;
335 * Accepts: mail stream
341 void mh_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
343 char *s
,test
[MAILTMPLEN
],file
[MAILTMPLEN
];
345 if (!pat
|| !*pat
) { /* empty pattern? */
346 if (mh_canonicalize (test
,ref
,"*")) {
347 /* tie off name at root */
348 if (s
= strchr (test
,'/')) *++s
= '\0';
350 mm_list (stream
,'/',test
,LATT_NOSELECT
);
353 /* get canonical form of name */
354 else if (mh_canonicalize (test
,ref
,pat
)) {
355 if (contents
) { /* maybe I'll implement this someday */
356 mm_log ("Scan not valid for mh mailboxes",ERROR
);
359 if (test
[3] == '/') { /* looking down levels? */
360 /* yes, found any wildcards? */
361 if (s
= strpbrk (test
,"%*")) {
362 /* yes, copy name up to that point */
363 strncpy (file
,test
+4,i
= s
- (test
+4));
364 file
[i
] = '\0'; /* tie off */
366 else strcpy (file
,test
+4);/* use just that name then */
367 /* find directory name */
368 if (s
= strrchr (file
,'/')) {
369 *s
= '\0'; /* found, tie off at that point */
373 mh_list_work (stream
,s
,test
,0);
375 /* always an INBOX */
376 if (!compare_cstring (test
,MHINBOX
))
377 mm_list (stream
,NIL
,MHINBOX
,LATT_NOINFERIORS
);
382 * Accepts: mail stream
387 void mh_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
389 mh_scan (stream
,ref
,pat
,NIL
);
393 /* MH list subscribed mailboxes
394 * Accepts: mail stream
399 void mh_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
402 char *s
,test
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
403 /* get canonical form of name */
404 if (mh_canonicalize (test
,ref
,pat
) && (s
= sm_read (tmp
,&sdb
))) {
405 do if (pmatch_full (s
,test
,'/')) mm_lsub (stream
,'/',s
,NIL
);
406 while (s
= sm_read (tmp
,&sdb
)); /* until no more subscriptions */
410 /* MH list mailboxes worker routine
411 * Accepts: mail stream
412 * directory name to search
417 void mh_list_work (MAILSTREAM
*stream
,char *dir
,char *pat
,long level
)
422 char *cp
,*np
,curdir
[MAILTMPLEN
],name
[MAILTMPLEN
];
423 /* build MH name to search */
424 if (dir
) sprintf (name
,"#mh/%s/",dir
);
425 else strcpy (name
,"#mh/");
426 /* make directory name, punt if bogus */
427 if (!mh_file (curdir
,name
)) return;
428 cp
= curdir
+ strlen (curdir
);/* end of directory name */
429 np
= name
+ strlen (name
); /* end of MH name */
430 if (dp
= opendir (curdir
)) { /* open directory */
431 while (d
= readdir (dp
)) /* scan, ignore . and numeric names */
432 if ((d
->d_name
[0] != '.') && !mh_select (d
)) {
433 strcpy (cp
,d
->d_name
); /* make directory name */
434 if (!stat (curdir
,&sbuf
) && ((sbuf
.st_mode
& S_IFMT
) == S_IFDIR
)) {
435 strcpy (np
,d
->d_name
);/* make mh name of directory name */
436 /* yes, an MH name if full match */
437 if (pmatch_full (name
,pat
,'/')) mm_list (stream
,'/',name
,NIL
);
438 /* check if should recurse */
439 if (dmatch (name
,pat
,'/') &&
440 (level
< (long) mail_parameters (NIL
,GET_LISTMAXLEVEL
,NIL
)))
441 mh_list_work (stream
,name
+4,pat
,level
+1);
444 closedir (dp
); /* all done, flush directory */
448 /* MH mail subscribe to mailbox
449 * Accepts: mail stream
450 * mailbox to add to subscription list
451 * Returns: T on success, NIL on failure
454 long mh_subscribe (MAILSTREAM
*stream
,char *mailbox
)
456 return sm_subscribe (mailbox
);
460 /* MH mail unsubscribe to mailbox
461 * Accepts: mail stream
462 * mailbox to delete from subscription list
463 * Returns: T on success, NIL on failure
466 long mh_unsubscribe (MAILSTREAM
*stream
,char *mailbox
)
468 return sm_unsubscribe (mailbox
);
471 /* MH mail create mailbox
472 * Accepts: mail stream
473 * mailbox name to create
474 * Returns: T on success, NIL on failure
477 long mh_create (MAILSTREAM
*stream
,char *mailbox
)
479 char tmp
[MAILTMPLEN
];
480 if (!mh_namevalid (mailbox
)) /* validate name */
481 sprintf (tmp
,"Can't create mailbox %.80s: invalid MH-format name",mailbox
);
482 /* must not already exist */
483 else if (mh_isvalid (mailbox
,tmp
,NIL
))
484 sprintf (tmp
,"Can't create mailbox %.80s: mailbox already exists",mailbox
);
485 else if (!mh_path (tmp
)) return NIL
;
487 else if (!(mh_file (tmp
,mailbox
) &&
488 dummy_create_path (stream
,strcat (tmp
,"/"),
489 get_dir_protection (mailbox
))))
490 sprintf (tmp
,"Can't create mailbox %.80s: %s",mailbox
,strerror (errno
));
491 else return LONGT
; /* success */
496 /* MH mail delete mailbox
497 * mailbox name to delete
498 * Returns: T on success, NIL on failure
501 long mh_delete (MAILSTREAM
*stream
,char *mailbox
)
506 char tmp
[MAILTMPLEN
];
507 /* is mailbox valid? */
508 if (!mh_isvalid (mailbox
,tmp
,NIL
)) {
509 sprintf (tmp
,"Can't delete mailbox %.80s: no such mailbox",mailbox
);
513 /* get name of directory */
514 i
= strlen (mh_file (tmp
,mailbox
));
515 if (dirp
= opendir (tmp
)) { /* open directory */
516 tmp
[i
++] = '/'; /* now apply trailing delimiter */
517 /* massacre all mh owned files */
518 while (d
= readdir (dirp
)) if (mh_dirfmttest (d
->d_name
)) {
519 strcpy (tmp
+ i
,d
->d_name
);
520 unlink (tmp
); /* sayonara */
522 closedir (dirp
); /* flush directory */
524 /* try to remove the directory */
525 if (rmdir (mh_file (tmp
,mailbox
))) {
526 sprintf (tmp
,"Can't delete mailbox %.80s: %s",mailbox
,strerror (errno
));
529 return T
; /* return success */
532 /* MH mail rename mailbox
533 * Accepts: MH mail stream
536 * Returns: T on success, NIL on failure
539 long mh_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
541 char c
,*s
,tmp
[MAILTMPLEN
],tmp1
[MAILTMPLEN
];
543 /* old mailbox name must be valid */
544 if (!mh_isvalid (old
,tmp
,NIL
))
545 sprintf (tmp
,"Can't rename mailbox %.80s: no such mailbox",old
);
546 else if (!mh_namevalid (newname
))
547 sprintf (tmp
,"Can't rename to mailbox %.80s: invalid MH-format name",
549 /* new mailbox name must not be valid */
550 else if (mh_isvalid (newname
,tmp
,NIL
))
551 sprintf (tmp
,"Can't rename to mailbox %.80s: destination already exists",
553 /* success if can rename the directory */
554 else { /* found superior to destination name? */
555 if (s
= strrchr (mh_file (tmp1
,newname
),'/')) {
556 c
= *++s
; /* remember first character of inferior */
557 *s
= '\0'; /* tie off to get just superior */
558 /* name doesn't exist, create it */
559 if ((stat (tmp1
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) &&
560 !dummy_create_path (stream
,tmp1
,get_dir_protection (newname
)))
562 *s
= c
; /* restore full name */
564 if (!rename (mh_file (tmp
,old
),tmp1
)) return T
;
565 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %s",
566 old
,newname
,strerror (errno
));
568 mm_log (tmp
,ERROR
); /* something failed */
573 * Accepts: stream to open
574 * Returns: stream on success, NIL on failure
577 MAILSTREAM
*mh_open (MAILSTREAM
*stream
)
579 char tmp
[MAILTMPLEN
];
580 if (!stream
) return &mhproto
; /* return prototype for OP_PROTOTYPE call */
581 if (stream
->local
) fatal ("mh recycle stream");
582 stream
->local
= fs_get (sizeof (MHLOCAL
));
583 /* INBOXness is one of the following:
584 * #mhinbox (case-independent)
585 * #mh/inbox (mh is case-independent, inbox is case-dependent)
586 * INBOX (case-independent
588 stream
->inbox
= /* note if an INBOX or not */
589 (!compare_cstring (stream
->mailbox
,MHINBOX
) ||
590 ((stream
->mailbox
[0] == '#') &&
591 ((stream
->mailbox
[1] == 'm') || (stream
->mailbox
[1] == 'M')) &&
592 ((stream
->mailbox
[2] == 'h') || (stream
->mailbox
[2] == 'H')) &&
593 (stream
->mailbox
[3] == '/') && !strcmp (stream
->mailbox
+4,MHINBOXDIR
)) ||
594 !compare_cstring (stream
->mailbox
,"INBOX")) ? T
: NIL
;
595 mh_file (tmp
,stream
->mailbox
);/* get directory name */
596 LOCAL
->dir
= cpystr (tmp
); /* copy directory name for later */
597 LOCAL
->scantime
= 0; /* not scanned yet */
598 LOCAL
->cachedtexts
= 0; /* no cached texts */
599 stream
->sequence
++; /* bump sequence number */
601 stream
->nmsgs
= stream
->recent
= 0;
602 if (!mh_ping (stream
)) return NIL
;
603 if (!(stream
->nmsgs
|| stream
->silent
))
604 mm_log ("Mailbox is empty",(long) NIL
);
605 return stream
; /* return stream to caller */
609 * Accepts: MAIL stream
613 void mh_close (MAILSTREAM
*stream
,long options
)
615 if (LOCAL
) { /* only if a file is open */
616 int silent
= stream
->silent
;
617 stream
->silent
= T
; /* note this stream is dying */
618 if (options
& CL_EXPUNGE
) mh_expunge (stream
,NIL
,NIL
);
619 if (LOCAL
->dir
) fs_give ((void **) &LOCAL
->dir
);
620 /* nuke the local data */
621 fs_give ((void **) &stream
->local
);
622 stream
->dtb
= NIL
; /* log out the DTB */
623 stream
->silent
= silent
; /* reset silent state */
628 /* MH mail fetch fast information
629 * Accepts: MAIL stream
634 void mh_fast (MAILSTREAM
*stream
,char *sequence
,long flags
)
638 /* set up metadata for all messages */
639 if (stream
&& LOCAL
&& ((flags
& FT_UID
) ?
640 mail_uid_sequence (stream
,sequence
) :
641 mail_sequence (stream
,sequence
)))
642 for (i
= 1; i
<= stream
->nmsgs
; i
++)
643 if ((elt
= mail_elt (stream
,i
))->sequence
&&
644 !(elt
->day
&& elt
->rfc822_size
)) mh_load_message (stream
,i
,NIL
);
647 /* MH load message into cache
648 * Accepts: MAIL stream
653 void mh_load_message (MAILSTREAM
*stream
,unsigned long msgno
,long flags
)
655 unsigned long i
,j
,nlseen
;
662 elt
= mail_elt (stream
,msgno
);/* get elt */
663 /* build message file name */
664 sprintf (LOCAL
->buf
,"%s/%lu",LOCAL
->dir
,elt
->private.uid
);
665 /* anything we need not currently cached? */
666 if ((!elt
->day
|| !elt
->rfc822_size
||
667 ((flags
& MLM_HEADER
) && !elt
->private.msg
.header
.text
.data
) ||
668 ((flags
& MLM_TEXT
) && !elt
->private.msg
.text
.text
.data
)) &&
669 ((fd
= open (LOCAL
->buf
,O_RDONLY
,NIL
)) >= 0)) {
670 fstat (fd
,&sbuf
); /* get file metadata */
671 d
.fd
= fd
; /* set up file descriptor */
672 d
.pos
= 0; /* start of file */
673 d
.chunk
= LOCAL
->buf
;
674 d
.chunksize
= CHUNKSIZE
;
675 INIT (&bs
,fd_string
,&d
,sbuf
.st_size
);
676 if (!elt
->day
) { /* set internaldate to file date */
677 struct tm
*tm
= gmtime (&sbuf
.st_mtime
);
678 elt
->day
= tm
->tm_mday
; elt
->month
= tm
->tm_mon
+ 1;
679 elt
->year
= tm
->tm_year
+ 1900 - BASEYEAR
;
680 elt
->hours
= tm
->tm_hour
; elt
->minutes
= tm
->tm_min
;
681 elt
->seconds
= tm
->tm_sec
;
682 elt
->zhours
= 0; elt
->zminutes
= 0;
685 if (!elt
->rfc822_size
) { /* know message size yet? */
686 for (i
= 0, j
= SIZE (&bs
), nlseen
= 0; j
--; ) switch (SNX (&bs
)) {
687 case '\015': /* unlikely carriage return */
688 if (!j
|| (CHR (&bs
) != '\012')) {
689 i
++; /* ugh, raw CR */
693 SNX (&bs
); /* eat the line feed, drop in */
695 case '\012': /* line feed? */
696 i
+= 2; /* count a CRLF */
697 /* header size known yet? */
698 if (!elt
->private.msg
.header
.text
.size
&& nlseen
) {
699 /* note position in file */
700 elt
->private.special
.text
.size
= GETPOS (&bs
);
701 /* and CRLF-adjusted size */
702 elt
->private.msg
.header
.text
.size
= i
;
704 nlseen
= T
; /* note newline seen */
706 default: /* ordinary character */
711 SETPOS (&bs
,0); /* restore old position */
712 elt
->rfc822_size
= i
; /* note that we have size now */
713 /* header is entire message if no delimiter */
714 if (!elt
->private.msg
.header
.text
.size
)
715 elt
->private.msg
.header
.text
.size
= elt
->rfc822_size
;
716 /* text is remainder of message */
717 elt
->private.msg
.text
.text
.size
=
718 elt
->rfc822_size
- elt
->private.msg
.header
.text
.size
;
720 /* need to load cache with message data? */
721 if (((flags
& MLM_HEADER
) && !elt
->private.msg
.header
.text
.data
) ||
722 ((flags
& MLM_TEXT
) && !elt
->private.msg
.text
.text
.data
)) {
723 /* purge cache if too big */
724 if (LOCAL
->cachedtexts
> max (stream
->nmsgs
* 4096,2097152)) {
725 /* just can't keep that much */
726 mail_gc (stream
,GC_TEXTS
);
727 LOCAL
->cachedtexts
= 0;
730 if ((flags
& MLM_HEADER
) && !elt
->private.msg
.header
.text
.data
) {
731 t
= elt
->private.msg
.header
.text
.data
=
732 (unsigned char *) fs_get (elt
->private.msg
.header
.text
.size
+ 1);
733 LOCAL
->cachedtexts
+= elt
->private.msg
.header
.text
.size
;
734 /* read in message header */
735 for (i
= 0; i
< elt
->private.msg
.header
.text
.size
; i
++)
736 switch (c
= SNX (&bs
)) {
737 case '\015': /* unlikely carriage return */
739 if ((CHR (&bs
) == '\012')) {
744 case '\012': /* line feed? */
751 *t
= '\0'; /* tie off string */
752 if ((t
- elt
->private.msg
.header
.text
.data
) !=
753 elt
->private.msg
.header
.text
.size
) fatal ("mh hdr size mismatch");
755 if ((flags
& MLM_TEXT
) && !elt
->private.msg
.text
.text
.data
) {
756 t
= elt
->private.msg
.text
.text
.data
=
757 (unsigned char *) fs_get (elt
->private.msg
.text
.text
.size
+ 1);
758 SETPOS (&bs
,elt
->private.special
.text
.size
);
759 LOCAL
->cachedtexts
+= elt
->private.msg
.text
.text
.size
;
760 /* read in message text */
761 for (i
= 0; i
< elt
->private.msg
.text
.text
.size
; i
++)
762 switch (c
= SNX (&bs
)) {
763 case '\015': /* unlikely carriage return */
765 if ((CHR (&bs
) == '\012')) {
770 case '\012': /* line feed? */
777 *t
= '\0'; /* tie off string */
778 if ((t
- elt
->private.msg
.text
.text
.data
) !=
779 elt
->private.msg
.text
.text
.size
) fatal ("mh txt size mismatch");
782 close (fd
); /* flush message file */
786 /* MH mail fetch message header
787 * Accepts: MAIL stream
789 * pointer to returned header text length
791 * Returns: message header in RFC822 format
794 char *mh_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
798 *length
= 0; /* default to empty */
799 if (flags
& FT_UID
) return "";/* UID call "impossible" */
800 elt
= mail_elt (stream
,msgno
);/* get elt */
801 if (!elt
->private.msg
.header
.text
.data
)
802 mh_load_message (stream
,msgno
,MLM_HEADER
);
803 *length
= elt
->private.msg
.header
.text
.size
;
804 return (char *) elt
->private.msg
.header
.text
.data
;
808 /* MH mail fetch message text (body only)
809 * Accepts: MAIL stream
811 * pointer to returned stringstruct
813 * Returns: T on success, NIL on failure
816 long mh_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
819 /* UID call "impossible" */
820 if (flags
& FT_UID
) return NIL
;
821 elt
= mail_elt (stream
,msgno
);/* get elt */
822 /* snarf message if don't have it yet */
823 if (!elt
->private.msg
.text
.text
.data
) {
824 mh_load_message (stream
,msgno
,MLM_TEXT
);
825 if (!elt
->private.msg
.text
.text
.data
) return NIL
;
827 if (!(flags
& FT_PEEK
)) { /* mark as seen */
828 mail_elt (stream
,msgno
)->seen
= T
;
829 mm_flags (stream
,msgno
);
831 INIT (bs
,mail_string
,elt
->private.msg
.text
.text
.data
,
832 elt
->private.msg
.text
.text
.size
);
836 /* MH mail ping mailbox
837 * Accepts: MAIL stream
838 * Returns: T if stream alive, else NIL
841 long mh_ping (MAILSTREAM
*stream
)
843 MAILSTREAM
*sysibx
= NIL
;
844 MESSAGECACHE
*elt
,*selt
;
846 char *s
,tmp
[MAILTMPLEN
];
849 unsigned long old
= stream
->uid_last
;
850 long nmsgs
= stream
->nmsgs
;
851 long recent
= stream
->recent
;
852 int silent
= stream
->silent
;
853 if (stat (LOCAL
->dir
,&sbuf
)) {/* directory exists? */
854 if (stream
->inbox
&& /* no, create if INBOX */
855 dummy_create_path (stream
,strcat (mh_file (tmp
,MHINBOX
),"/"),
856 get_dir_protection ("INBOX"))) return T
;
857 sprintf (tmp
,"Can't open mailbox %.80s: no such mailbox",stream
->mailbox
);
861 stream
->silent
= T
; /* don't pass up mm_exists() events yet */
862 if (sbuf
.st_ctime
!= LOCAL
->scantime
) {
863 struct direct
**names
= NIL
;
864 long nfiles
= scandir (LOCAL
->dir
,&names
,mh_select
,mh_numsort
);
865 if (nfiles
< 0) nfiles
= 0; /* in case error */
866 /* note scanned now */
867 LOCAL
->scantime
= sbuf
.st_ctime
;
869 for (i
= 0; i
< nfiles
; ++i
) {
870 /* if newly seen, add to list */
871 if ((j
= atoi (names
[i
]->d_name
)) > old
) {
872 mail_exists (stream
,++nmsgs
);
873 stream
->uid_last
= (elt
= mail_elt (stream
,nmsgs
))->private.uid
= j
;
874 elt
->valid
= T
; /* note valid flags */
875 if (old
) { /* other than the first pass? */
876 elt
->recent
= T
; /* yup, mark as recent */
877 recent
++; /* bump recent count */
879 else { /* see if already read */
880 sprintf (tmp
,"%s/%s",LOCAL
->dir
,names
[i
]->d_name
);
881 if (!stat (tmp
,&sbuf
) && (sbuf
.st_atime
> sbuf
.st_mtime
))
885 fs_give ((void **) &names
[i
]);
888 if (s
= (void *) names
) fs_give ((void **) &s
);
891 /* if INBOX, snarf from system INBOX */
892 if (stream
->inbox
&& strcmp (sysinbox (),stream
->mailbox
)) {
893 old
= stream
->uid_last
;
894 mm_critical (stream
); /* go critical */
895 /* see if anything in system inbox */
896 if (!stat (sysinbox (),&sbuf
) && sbuf
.st_size
&&
897 (sysibx
= mail_open (sysibx
,sysinbox (),OP_SILENT
)) &&
898 !sysibx
->rdonly
&& (r
= sysibx
->nmsgs
)) {
899 for (i
= 1; i
<= r
; ++i
) {/* for each message in sysinbox mailbox */
900 /* build file name we will use */
901 sprintf (LOCAL
->buf
,"%s/%lu",LOCAL
->dir
,++old
);
902 /* snarf message from Berkeley mailbox */
903 selt
= mail_elt (sysibx
,i
);
904 if (((fd
= open (LOCAL
->buf
,O_WRONLY
|O_CREAT
|O_EXCL
,
905 (long) mail_parameters (NIL
,GET_MBXPROTECTION
,NIL
)))
907 (s
= mail_fetchheader_full (sysibx
,i
,NIL
,&j
,FT_INTERNAL
)) &&
908 (write (fd
,s
,j
) == j
) &&
909 (s
= mail_fetchtext_full (sysibx
,i
,&j
,FT_INTERNAL
|FT_PEEK
)) &&
910 (write (fd
,s
,j
) == j
) && !fsync (fd
) && !close (fd
)) {
911 /* swell the cache */
912 mail_exists (stream
,++nmsgs
);
913 stream
->uid_last
= /* create new elt, note its file number */
914 (elt
= mail_elt (stream
,nmsgs
))->private.uid
= old
;
915 recent
++; /* bump recent count */
916 /* set up initial flags and date */
917 elt
->valid
= elt
->recent
= T
;
918 elt
->seen
= selt
->seen
;
919 elt
->deleted
= selt
->deleted
;
920 elt
->flagged
= selt
->flagged
;
921 elt
->answered
= selt
->answered
;
922 elt
->draft
= selt
->draft
;
923 elt
->day
= selt
->day
;elt
->month
= selt
->month
;elt
->year
= selt
->year
;
924 elt
->hours
= selt
->hours
;elt
->minutes
= selt
->minutes
;
925 elt
->seconds
= selt
->seconds
;
926 elt
->zhours
= selt
->zhours
; elt
->zminutes
= selt
->zminutes
;
927 elt
->zoccident
= selt
->zoccident
;
928 mh_setdate (LOCAL
->buf
,elt
);
929 sprintf (tmp
,"%lu",i
);/* delete it from the sysinbox */
930 mail_flag (sysibx
,tmp
,"\\Deleted",ST_SET
);
933 else { /* failed to snarf */
934 if (fd
) { /* did it ever get opened? */
935 close (fd
); /* close descriptor */
936 unlink (LOCAL
->buf
);/* flush this file */
938 sprintf (tmp
,"Message copy to MH mailbox failed: %.80s",
941 r
= 0; /* stop the snarf in its tracks */
944 /* update scan time */
945 if (!stat (LOCAL
->dir
,&sbuf
)) LOCAL
->scantime
= sbuf
.st_ctime
;
946 mail_expunge (sysibx
); /* now expunge all those messages */
948 if (sysibx
) mail_close (sysibx
);
949 mm_nocritical (stream
); /* release critical */
951 stream
->silent
= silent
; /* can pass up events now */
952 mail_exists (stream
,nmsgs
); /* notify upper level of mailbox size */
953 mail_recent (stream
,recent
);
954 return T
; /* return that we are alive */
957 /* MH mail check mailbox
958 * Accepts: MAIL stream
961 void mh_check (MAILSTREAM
*stream
)
963 /* Perhaps in the future this will preserve flags */
964 if (mh_ping (stream
)) mm_log ("Check completed",(long) NIL
);
968 /* MH mail expunge mailbox
969 * Accepts: MAIL stream
970 * sequence to expunge if non-NIL
975 long mh_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
981 unsigned long recent
= stream
->recent
;
982 if (ret
= sequence
? ((options
& EX_UID
) ?
983 mail_uid_sequence (stream
,sequence
) :
984 mail_sequence (stream
,sequence
)) : LONGT
) {
985 mm_critical (stream
); /* go critical */
986 while (i
<= stream
->nmsgs
) {/* for each message */
987 elt
= mail_elt (stream
,i
);/* if deleted, need to trash it */
988 if (elt
->deleted
&& (sequence
? elt
->sequence
: T
)) {
989 sprintf (LOCAL
->buf
,"%s/%lu",LOCAL
->dir
,elt
->private.uid
);
990 if (unlink (LOCAL
->buf
)) {/* try to delete the message */
991 sprintf (LOCAL
->buf
,"Expunge of message %lu failed, aborted: %s",i
,
993 mm_log (LOCAL
->buf
,(long) NIL
);
997 LOCAL
->cachedtexts
-= ((elt
->private.msg
.header
.text
.data
?
998 elt
->private.msg
.header
.text
.size
: 0) +
999 (elt
->private.msg
.text
.text
.data
?
1000 elt
->private.msg
.text
.text
.size
: 0));
1001 mail_gc_msg (&elt
->private.msg
,GC_ENV
| GC_TEXTS
);
1002 /* if recent, note one less recent message */
1003 if (elt
->recent
) --recent
;
1004 /* notify upper levels */
1005 mail_expunged (stream
,i
);
1006 n
++; /* count up one more expunged message */
1008 else i
++; /* otherwise try next message */
1010 if (n
) { /* output the news if any expunged */
1011 sprintf (LOCAL
->buf
,"Expunged %lu messages",n
);
1012 mm_log (LOCAL
->buf
,(long) NIL
);
1014 else mm_log ("No messages deleted, so no update needed",(long) NIL
);
1015 mm_nocritical (stream
); /* release critical */
1016 /* notify upper level of new mailbox size */
1017 mail_exists (stream
,stream
->nmsgs
);
1018 mail_recent (stream
,recent
);
1023 /* MH mail copy message(s)
1024 * Accepts: MAIL stream
1026 * destination mailbox
1028 * Returns: T if copy successful, else NIL
1031 long mh_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
1039 char flags
[MAILTMPLEN
],date
[MAILTMPLEN
];
1040 appenduid_t au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
);
1042 /* copy the messages */
1043 if ((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
1044 mail_sequence (stream
,sequence
))
1045 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1046 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1047 sprintf (LOCAL
->buf
,"%s/%lu",LOCAL
->dir
,elt
->private.uid
);
1048 if ((fd
= open (LOCAL
->buf
,O_RDONLY
,NIL
)) < 0) return NIL
;
1049 fstat (fd
,&sbuf
); /* get size of message */
1050 if (!elt
->day
) { /* set internaldate to file date if needed */
1051 struct tm
*tm
= gmtime (&sbuf
.st_mtime
);
1052 elt
->day
= tm
->tm_mday
; elt
->month
= tm
->tm_mon
+ 1;
1053 elt
->year
= tm
->tm_year
+ 1900 - BASEYEAR
;
1054 elt
->hours
= tm
->tm_hour
; elt
->minutes
= tm
->tm_min
;
1055 elt
->seconds
= tm
->tm_sec
;
1056 elt
->zhours
= 0; elt
->zminutes
= 0;
1058 d
.fd
= fd
; /* set up file descriptor */
1059 d
.pos
= 0; /* start of file */
1060 d
.chunk
= LOCAL
->buf
;
1061 d
.chunksize
= CHUNKSIZE
;
1062 /* kludge; mh_append would just strip CRs */
1063 INIT (&st
,fd_string
,&d
,sbuf
.st_size
);
1064 /* init flag string */
1065 flags
[0] = flags
[1] = '\0';
1066 if (elt
->seen
) strcat (flags
," \\Seen");
1067 if (elt
->deleted
) strcat (flags
," \\Deleted");
1068 if (elt
->flagged
) strcat (flags
," \\Flagged");
1069 if (elt
->answered
) strcat (flags
," \\Answered");
1070 if (elt
->draft
) strcat (flags
," \\Draft");
1071 flags
[0] = '('; /* open list */
1072 strcat (flags
,")"); /* close list */
1073 mail_date (date
,elt
); /* generate internal date */
1074 if (au
) mail_parameters (NIL
,SET_APPENDUID
,NIL
);
1075 if ((ret
= mail_append_full (NIL
,mailbox
,flags
,date
,&st
)) &&
1076 (options
& CP_MOVE
)) elt
->deleted
= T
;
1077 if (au
) mail_parameters (NIL
,SET_APPENDUID
,(void *) au
);
1080 if (ret
&& mail_parameters (NIL
,GET_COPYUID
,NIL
))
1081 mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN
);
1082 return ret
; /* return success */
1085 /* MH mail append message from stringstruct
1086 * Accepts: MAIL stream
1087 * destination mailbox
1090 * Returns: T if append successful, else NIL
1093 long mh_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1095 struct direct
**names
= NIL
;
1097 char c
,*flags
,*date
,*s
,tmp
[MAILTMPLEN
];
1101 long i
,size
,last
,nfiles
;
1103 /* default stream to prototype */
1104 if (!stream
) stream
= &mhproto
;
1105 /* make sure valid mailbox */
1106 if (!mh_isvalid (mailbox
,tmp
,NIL
)) switch (errno
) {
1107 case ENOENT
: /* no such file? */
1108 if (!((!compare_cstring (mailbox
,MHINBOX
) ||
1109 !compare_cstring (mailbox
,"INBOX")) &&
1110 (mh_file (tmp
,MHINBOX
) &&
1111 dummy_create_path (stream
,strcat (tmp
,"/"),
1112 get_dir_protection (mailbox
))))) {
1113 mm_notify (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
1117 case 0: /* merely empty file? */
1120 sprintf (tmp
,"Invalid MH-format mailbox name: %.80s",mailbox
);
1124 sprintf (tmp
,"Not a MH-format mailbox: %.80s",mailbox
);
1128 /* get first message */
1129 if (!(*af
) (stream
,data
,&flags
,&date
,&message
)) return NIL
;
1130 if ((nfiles
= scandir (tmp
,&names
,mh_select
,mh_numsort
)) > 0) {
1131 /* largest number */
1132 last
= atoi (names
[nfiles
-1]->d_name
);
1133 for (i
= 0; i
< nfiles
; ++i
) /* free directory */
1134 fs_give ((void **) &names
[i
]);
1136 else last
= 0; /* no messages here yet */
1137 if (s
= (void *) names
) fs_give ((void **) &s
);
1139 mm_critical (stream
); /* go critical */
1141 if (!SIZE (message
)) { /* guard against zero-length */
1142 mm_log ("Append of zero-length message",ERROR
);
1146 if (date
) { /* want to preserve date? */
1147 /* yes, parse date into an elt */
1148 if (!mail_parse_date (&elt
,date
)) {
1149 sprintf (tmp
,"Bad date in append: %.80s",date
);
1155 mh_file (tmp
,mailbox
); /* build file name we will use */
1156 sprintf (tmp
+ strlen (tmp
),"/%ld",++last
);
1157 if (((fd
= open (tmp
,O_WRONLY
|O_CREAT
|O_EXCL
,
1158 (long)mail_parameters (NIL
,GET_MBXPROTECTION
,NIL
))) < 0)||
1159 !(df
= fdopen (fd
,"ab"))) {
1160 sprintf (tmp
,"Can't open append message: %s",strerror (errno
));
1165 /* copy the data w/o CR's */
1166 for (size
= 0,i
= SIZE (message
); i
&& ret
; --i
)
1167 if (((c
= SNX (message
)) != '\015') && (putc (c
,df
) == EOF
)) ret
= NIL
;
1168 /* close the file */
1169 if (!ret
|| fclose (df
)) {
1170 unlink (tmp
); /* delete message */
1171 sprintf (tmp
,"Message append failed: %s",strerror (errno
));
1175 if (ret
) { /* set the date for this message */
1176 if (date
) mh_setdate (tmp
,&elt
);
1177 /* get next message */
1178 if (!(*af
) (stream
,data
,&flags
,&date
,&message
)) ret
= NIL
;
1180 } while (ret
&& message
);
1181 mm_nocritical (stream
); /* release critical */
1182 if (ret
&& mail_parameters (NIL
,GET_APPENDUID
,NIL
))
1183 mm_log ("Can not return meaningful APPENDUID with this mailbox format",
1188 /* Internal routines */
1191 /* MH file name selection test
1192 * Accepts: candidate directory entry
1193 * Returns: T to use file name, NIL to skip it
1196 int mh_select (struct direct
*name
)
1199 char *s
= name
->d_name
;
1200 while (c
= *s
++) if (!isdigit (c
)) return NIL
;
1205 /* MH file name comparison
1206 * Accepts: first candidate directory entry
1207 * second candidate directory entry
1208 * Returns: negative if d1 < d2, 0 if d1 == d2, positive if d1 > d2
1211 int mh_numsort (const void *d1
,const void *d2
)
1213 return atoi ((*(struct direct
**) d1
)->d_name
) -
1214 atoi ((*(struct direct
**) d2
)->d_name
);
1218 /* MH mail build file name
1219 * Accepts: destination string
1221 * Returns: destination
1224 char *mh_file (char *dst
,char *name
)
1227 char *path
= mh_path (dst
);
1228 if (!path
) fatal ("No mh path in mh_file()!");
1229 /* INBOX becomes "inbox" in the MH path */
1230 if (!compare_cstring (name
,MHINBOX
) || !compare_cstring (name
,"INBOX"))
1231 sprintf (dst
,"%.900s/%.80s",path
,MHINBOXDIR
);
1232 /* #mh names skip past prefix */
1233 else if (*name
== '#') sprintf (dst
,"%.100s/%.900s",path
,name
+ 4);
1234 else mailboxfile (dst
,name
); /* all other names */
1235 /* tie off unnecessary trailing / */
1236 if ((s
= strrchr (dst
,'/')) && !s
[1] && (s
[-1] == '/')) *s
= '\0';
1240 /* MH canonicalize name
1241 * Accepts: buffer to write name
1244 * Returns: T if success, NIL if failure
1247 long mh_canonicalize (char *pattern
,char *ref
,char *pat
)
1250 char *s
,tmp
[MAILTMPLEN
];
1251 if (ref
&& *ref
) { /* have a reference */
1252 strcpy (pattern
,ref
); /* copy reference to pattern */
1253 /* # overrides mailbox field in reference */
1254 if (*pat
== '#') strcpy (pattern
,pat
);
1255 /* pattern starts, reference ends, with / */
1256 else if ((*pat
== '/') && (pattern
[strlen (pattern
) - 1] == '/'))
1257 strcat (pattern
,pat
+ 1); /* append, omitting one of the period */
1258 else strcat (pattern
,pat
); /* anything else is just appended */
1260 else strcpy (pattern
,pat
); /* just have basic name */
1261 if (mh_isvalid (pattern
,tmp
,T
)) {
1262 /* count wildcards */
1263 for (i
= 0, s
= pattern
; *s
; *s
++) if ((*s
== '*') || (*s
== '%')) ++i
;
1264 /* success if not too many */
1265 if (i
<= MAXWILDCARDS
) return LONGT
;
1266 mm_log ("Excessive wildcards in LIST/LSUB",ERROR
);
1271 /* Set date for message
1272 * Accepts: file name
1273 * elt containing date
1276 void mh_setdate (char *file
,MESSAGECACHE
*elt
)
1279 tp
[0] = time (0); /* atime is now */
1280 tp
[1] = mail_longdate (elt
); /* modification time */
1281 utime (file
,tp
); /* set the times */