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 (const struct direct
*name
);
106 int mh_numsort (const struct direct
**d1
,const struct direct
**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 */
154 NIL
/* renew stream */
157 /* prototype stream */
158 MAILSTREAM mhproto
= {&mhdriver
};
161 static char *mh_profile
= NIL
; /* holds MH profile */
162 static char *mh_pathname
= NIL
; /* holds MH path name */
163 static long mh_once
= 0; /* already snarled once */
164 static long mh_allow_inbox
=NIL
;/* allow INBOX as well as MHINBOX */
166 /* MH mail validate mailbox
167 * Accepts: mailbox name
168 * Returns: our driver if name is valid, NIL otherwise
171 DRIVER
*mh_valid (char *name
)
173 char tmp
[MAILTMPLEN
];
174 return mh_isvalid (name
,tmp
,T
) ? &mhdriver
: NIL
;
178 /* MH mail test for valid mailbox
179 * Accepts: mailbox name
180 * temporary buffer to use
181 * syntax only test flag
182 * Returns: T if valid, NIL otherwise
185 int mh_isvalid (char *name
,char *tmp
,long synonly
)
188 char *s
,*t
,altname
[MAILTMPLEN
];
191 errno
= NIL
; /* zap any error condition */
193 if ((mh_allow_inbox
&& !compare_cstring (name
,"INBOX")) ||
194 !compare_cstring (name
,MHINBOX
) ||
195 ((name
[0] == '#') && ((name
[1] == 'm') || (name
[1] == 'M')) &&
196 ((name
[2] == 'h') || (name
[2] == 'H')) && (name
[3] == '/') && name
[4])){
197 if (mh_path (tmp
)) /* validate name if INBOX or not synonly */
198 ret
= (synonly
&& compare_cstring (name
,"INBOX")) ?
199 T
: ((stat (mh_file (tmp
,name
),&sbuf
) == 0) &&
200 (sbuf
.st_mode
& S_IFMT
) == S_IFDIR
);
201 else if (!mh_once
++) { /* only report error once */
202 sprintf (tmp
,"%.900s not found, mh format names disabled",mh_profile
);
206 /* see if non-NS name within mh hierarchy */
207 else if ((name
[0] != '#') && (s
= mh_path (tmp
)) && (i
= strlen (s
)) &&
208 (t
= mailboxfile (tmp
,name
)) && !strncmp (t
,s
,i
) &&
209 (tmp
[i
] == '/') && tmp
[i
+1]) {
210 sprintf (altname
,"#mh%.900s",tmp
+i
);
211 /* can't do synonly here! */
212 ret
= mh_isvalid (altname
,tmp
,NIL
);
214 else errno
= EINVAL
; /* bogus name */
218 /* MH mail test for valid mailbox
219 * Accepts: mailbox name
220 * Returns: T if valid, NIL otherwise
223 int mh_namevalid (char *name
)
226 if (name
[0] == '#' && (name
[1] == 'm' || name
[1] == 'M') &&
227 (name
[2] == 'h' || name
[2] == 'H') && name
[3] == '/')
228 for (s
= name
; s
&& *s
;) { /* make sure no all-digit nodes */
229 if (isdigit (*s
)) s
++; /* digit, check this node further... */
230 else if (*s
== '/') break;/* all digit node, barf */
231 /* non-digit, skip to next node or return */
232 else if (!((s
= strchr (s
+1,'/')) && *++s
)) return T
;
234 return NIL
; /* all numeric or empty node */
238 * Accepts: temporary buffer
239 * Returns: MH path or NIL if MH disabled
242 char *mh_path (char *tmp
)
247 if (!mh_profile
) { /* build mh_profile and mh_pathname now */
248 sprintf (tmp
,"%s/%s",myhomedir (),MHPROFILE
);
249 if ((fd
= open (mh_profile
= cpystr (tmp
),O_RDONLY
,NIL
)) >= 0) {
250 fstat (fd
,&sbuf
); /* yes, get size and read file */
251 read (fd
,(t
= (char *) fs_get (sbuf
.st_size
+ 1)),sbuf
.st_size
);
252 close (fd
); /* don't need the file any more */
253 t
[sbuf
.st_size
] = '\0'; /* tie it off */
254 /* parse profile file */
255 for (s
= strtok_r (t
,"\r\n",&r
); s
&& *s
; s
= strtok_r (NIL
,"\r\n",&r
)) {
256 /* found space in line? */
257 if ((v
= strpbrk (s
," \t")) != NULL
) {
258 *v
++ = '\0'; /* tie off, is keyword "Path:"? */
259 if (!compare_cstring (s
,"Path:")) {
260 /* skip whitespace */
261 while ((*v
== ' ') || (*v
== '\t')) ++v
;
263 if (*v
== '/') s
= v
;
264 else sprintf (s
= tmp
,"%s/%s",myhomedir (),v
);
266 mh_pathname
= cpystr (s
);
267 break; /* don't need to look at rest of file */
271 fs_give ((void **) &t
); /* flush profile text */
272 if (!mh_pathname
) { /* default path if not in the profile */
273 sprintf (tmp
,"%s/%s",myhomedir (),MHPATH
);
274 mh_pathname
= cpystr (tmp
);
281 /* MH manipulate driver parameters
282 * Accepts: function code
283 * function-dependent value
284 * Returns: function-dependent return value
287 void *mh_parameters (long function
,void *value
)
290 switch ((int) function
) {
292 if (value
) ret
= mh_file ((char *) value
,"INBOX");
295 ret
= (void *) mh_dirfmttest
;
298 if (mh_profile
) fs_give ((void **) &mh_profile
);
299 mh_profile
= cpystr ((char *) value
);
301 ret
= (void *) mh_profile
;
304 if (mh_pathname
) fs_give ((void **) &mh_pathname
);
305 mh_pathname
= cpystr ((char *) value
);
307 ret
= (void *) mh_pathname
;
309 case SET_MHALLOWINBOX
:
310 mh_allow_inbox
= value
? T
: NIL
;
311 case GET_MHALLOWINBOX
:
312 ret
= (void *) (mh_allow_inbox
? VOIDT
: NIL
);
318 /* MH test for directory format internal node
319 * Accepts: candidate node name
320 * Returns: T if internal name, NIL otherwise
323 long mh_dirfmttest (char *s
)
326 /* sequence(s) file is an internal name */
327 if (strcmp (s
,MHSEQUENCE
) && strcmp (s
,MHSEQUENCES
)) {
328 if (*s
== MHCOMMA
) ++s
; /* else comma + all numeric name */
329 /* success if all-numeric */
330 while ((c
= *s
++) != '\0') if (!isdigit (c
)) return NIL
;
336 * Accepts: mail stream
342 void mh_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
344 char *s
,test
[MAILTMPLEN
],file
[MAILTMPLEN
];
346 if (!pat
|| !*pat
) { /* empty pattern? */
347 if (mh_canonicalize (test
,ref
,"*")) {
348 /* tie off name at root */
349 if ((s
= strchr (test
,'/')) != NULL
) *++s
= '\0';
351 mm_list (stream
,'/',test
,LATT_NOSELECT
);
354 /* get canonical form of name */
355 else if (mh_canonicalize (test
,ref
,pat
)) {
356 if (contents
) { /* maybe I'll implement this someday */
357 mm_log ("Scan not valid for mh mailboxes",ERROR
);
360 if (test
[3] == '/') { /* looking down levels? */
361 /* yes, found any wildcards? */
362 if ((s
= strpbrk (test
,"%*")) != NULL
) {
363 /* yes, copy name up to that point */
364 strncpy (file
,test
+4,i
= s
- (test
+4));
365 file
[i
] = '\0'; /* tie off */
367 else strcpy (file
,test
+4);/* use just that name then */
368 /* find directory name */
369 if ((s
= strrchr (file
,'/')) != NULL
) {
370 *s
= '\0'; /* found, tie off at that point */
374 mh_list_work (stream
,s
,test
,0);
376 /* always an INBOX */
377 if (!compare_cstring (test
,MHINBOX
))
378 mm_list (stream
,NIL
,MHINBOX
,LATT_NOINFERIORS
);
383 * Accepts: mail stream
388 void mh_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
390 mh_scan (stream
,ref
,pat
,NIL
);
394 /* MH list subscribed mailboxes
395 * Accepts: mail stream
400 void mh_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
403 char *s
,test
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
404 /* get canonical form of name */
405 if (mh_canonicalize (test
,ref
,pat
) && (s
= sm_read (tmp
,&sdb
))) {
406 do if (pmatch_full (s
,test
,'/')) mm_lsub (stream
,'/',s
,NIL
);
407 while ((s
= sm_read (tmp
,&sdb
)) != NULL
); /* until no more subscriptions */
411 /* MH list mailboxes worker routine
412 * Accepts: mail stream
413 * directory name to search
418 void mh_list_work (MAILSTREAM
*stream
,char *dir
,char *pat
,long level
)
423 char *cp
,*np
,curdir
[MAILTMPLEN
],name
[MAILTMPLEN
];
424 /* build MH name to search */
425 if (dir
) sprintf (name
,"#mh/%s/",dir
);
426 else strcpy (name
,"#mh/");
427 /* make directory name, punt if bogus */
428 if (!mh_file (curdir
,name
)) return;
429 cp
= curdir
+ strlen (curdir
);/* end of directory name */
430 np
= name
+ strlen (name
); /* end of MH name */
431 if ((dp
= opendir (curdir
)) != NULL
) { /* open directory */
432 while ((d
= readdir (dp
)) != NULL
) /* scan, ignore . and numeric names */
433 if ((d
->d_name
[0] != '.') && !mh_select (d
)) {
434 strcpy (cp
,d
->d_name
); /* make directory name */
435 if (!stat (curdir
,&sbuf
) && ((sbuf
.st_mode
& S_IFMT
) == S_IFDIR
)) {
436 strcpy (np
,d
->d_name
);/* make mh name of directory name */
437 /* yes, an MH name if full match */
438 if (pmatch_full (name
,pat
,'/')) mm_list (stream
,'/',name
,NIL
);
439 /* check if should recurse */
440 if (dmatch (name
,pat
,'/') &&
441 (level
< (long) mail_parameters (NIL
,GET_LISTMAXLEVEL
,NIL
)))
442 mh_list_work (stream
,name
+4,pat
,level
+1);
445 closedir (dp
); /* all done, flush directory */
449 /* MH mail subscribe to mailbox
450 * Accepts: mail stream
451 * mailbox to add to subscription list
452 * Returns: T on success, NIL on failure
455 long mh_subscribe (MAILSTREAM
*stream
,char *mailbox
)
457 return sm_subscribe (mailbox
);
461 /* MH mail unsubscribe to mailbox
462 * Accepts: mail stream
463 * mailbox to delete from subscription list
464 * Returns: T on success, NIL on failure
467 long mh_unsubscribe (MAILSTREAM
*stream
,char *mailbox
)
469 return sm_unsubscribe (mailbox
);
472 /* MH mail create mailbox
473 * Accepts: mail stream
474 * mailbox name to create
475 * Returns: T on success, NIL on failure
478 long mh_create (MAILSTREAM
*stream
,char *mailbox
)
480 char tmp
[MAILTMPLEN
];
481 if (!mh_namevalid (mailbox
)) /* validate name */
482 sprintf (tmp
,"Can't create mailbox %.80s: invalid MH-format name",mailbox
);
483 /* must not already exist */
484 else if (mh_isvalid (mailbox
,tmp
,NIL
))
485 sprintf (tmp
,"Can't create mailbox %.80s: mailbox already exists",mailbox
);
486 else if (!mh_path (tmp
)) return NIL
;
488 else if (!(mh_file (tmp
,mailbox
) &&
489 dummy_create_path (stream
,strcat (tmp
,"/"),
490 get_dir_protection (mailbox
))))
491 sprintf (tmp
,"Can't create mailbox %.80s: %s",mailbox
,strerror (errno
));
492 else return LONGT
; /* success */
497 /* MH mail delete mailbox
498 * mailbox name to delete
499 * Returns: T on success, NIL on failure
502 long mh_delete (MAILSTREAM
*stream
,char *mailbox
)
507 char tmp
[MAILTMPLEN
];
508 /* is mailbox valid? */
509 if (!mh_isvalid (mailbox
,tmp
,NIL
)) {
510 sprintf (tmp
,"Can't delete mailbox %.80s: no such mailbox",mailbox
);
514 /* get name of directory */
515 i
= strlen (mh_file (tmp
,mailbox
));
516 if ((dirp
= opendir (tmp
)) != NULL
) { /* open directory */
517 tmp
[i
++] = '/'; /* now apply trailing delimiter */
518 /* massacre all mh owned files */
519 while ((d
= readdir (dirp
)) != NULL
) if (mh_dirfmttest (d
->d_name
)) {
520 strcpy (tmp
+ i
,d
->d_name
);
521 unlink (tmp
); /* sayonara */
523 closedir (dirp
); /* flush directory */
525 /* try to remove the directory */
526 if (rmdir (mh_file (tmp
,mailbox
))) {
527 sprintf (tmp
,"Can't delete mailbox %.80s: %s",mailbox
,strerror (errno
));
530 return T
; /* return success */
533 /* MH mail rename mailbox
534 * Accepts: MH mail stream
537 * Returns: T on success, NIL on failure
540 long mh_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
542 char c
,*s
,tmp
[MAILTMPLEN
],tmp1
[MAILTMPLEN
];
544 /* old mailbox name must be valid */
545 if (!mh_isvalid (old
,tmp
,NIL
))
546 sprintf (tmp
,"Can't rename mailbox %.80s: no such mailbox",old
);
547 else if (!mh_namevalid (newname
))
548 sprintf (tmp
,"Can't rename to mailbox %.80s: invalid MH-format name",
550 /* new mailbox name must not be valid */
551 else if (mh_isvalid (newname
,tmp
,NIL
))
552 sprintf (tmp
,"Can't rename to mailbox %.80s: destination already exists",
554 /* success if can rename the directory */
555 else { /* found superior to destination name? */
556 if ((s
= strrchr (mh_file (tmp1
,newname
),'/')) != NULL
) {
557 c
= *++s
; /* remember first character of inferior */
558 *s
= '\0'; /* tie off to get just superior */
559 /* name doesn't exist, create it */
560 if ((stat (tmp1
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) &&
561 !dummy_create_path (stream
,tmp1
,get_dir_protection (newname
)))
563 *s
= c
; /* restore full name */
565 if (!rename (mh_file (tmp
,old
),tmp1
)) return T
;
566 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %s",
567 old
,newname
,strerror (errno
));
569 mm_log (tmp
,ERROR
); /* something failed */
574 * Accepts: stream to open
575 * Returns: stream on success, NIL on failure
578 MAILSTREAM
*mh_open (MAILSTREAM
*stream
)
580 char tmp
[MAILTMPLEN
];
581 if (!stream
) return &mhproto
; /* return prototype for OP_PROTOTYPE call */
582 if (stream
->local
) fatal ("mh recycle stream");
583 stream
->local
= fs_get (sizeof (MHLOCAL
));
584 /* INBOXness is one of the following:
585 * #mhinbox (case-independent)
586 * #mh/inbox (mh is case-independent, inbox is case-dependent)
587 * INBOX (case-independent
589 stream
->inbox
= /* note if an INBOX or not */
590 (!compare_cstring (stream
->mailbox
,MHINBOX
) ||
591 ((stream
->mailbox
[0] == '#') &&
592 ((stream
->mailbox
[1] == 'm') || (stream
->mailbox
[1] == 'M')) &&
593 ((stream
->mailbox
[2] == 'h') || (stream
->mailbox
[2] == 'H')) &&
594 (stream
->mailbox
[3] == '/') && !strcmp (stream
->mailbox
+4,MHINBOXDIR
)) ||
595 !compare_cstring (stream
->mailbox
,"INBOX")) ? T
: NIL
;
596 mh_file (tmp
,stream
->mailbox
);/* get directory name */
597 LOCAL
->dir
= cpystr (tmp
); /* copy directory name for later */
598 LOCAL
->scantime
= 0; /* not scanned yet */
599 LOCAL
->cachedtexts
= 0; /* no cached texts */
600 stream
->sequence
++; /* bump sequence number */
602 stream
->nmsgs
= stream
->recent
= 0;
603 if (!mh_ping (stream
)) return NIL
;
604 if (!(stream
->nmsgs
|| stream
->silent
))
605 mm_log ("Mailbox is empty",(long) NIL
);
606 return stream
; /* return stream to caller */
610 * Accepts: MAIL stream
614 void mh_close (MAILSTREAM
*stream
,long options
)
616 if (LOCAL
) { /* only if a file is open */
617 int silent
= stream
->silent
;
618 stream
->silent
= T
; /* note this stream is dying */
619 if (options
& CL_EXPUNGE
) mh_expunge (stream
,NIL
,NIL
);
620 if (LOCAL
->dir
) fs_give ((void **) &LOCAL
->dir
);
621 /* nuke the local data */
622 fs_give ((void **) &stream
->local
);
623 stream
->dtb
= NIL
; /* log out the DTB */
624 stream
->silent
= silent
; /* reset silent state */
629 /* MH mail fetch fast information
630 * Accepts: MAIL stream
635 void mh_fast (MAILSTREAM
*stream
,char *sequence
,long flags
)
639 /* set up metadata for all messages */
640 if (stream
&& LOCAL
&& ((flags
& FT_UID
) ?
641 mail_uid_sequence (stream
,sequence
) :
642 mail_sequence (stream
,sequence
)))
643 for (i
= 1; i
<= stream
->nmsgs
; i
++)
644 if ((elt
= mail_elt (stream
,i
))->sequence
&&
645 !(elt
->day
&& elt
->rfc822_size
)) mh_load_message (stream
,i
,NIL
);
648 /* MH load message into cache
649 * Accepts: MAIL stream
654 void mh_load_message (MAILSTREAM
*stream
,unsigned long msgno
,long flags
)
656 unsigned long i
,j
,nlseen
;
663 elt
= mail_elt (stream
,msgno
);/* get elt */
664 /* build message file name */
665 sprintf (LOCAL
->buf
,"%s/%lu",LOCAL
->dir
,elt
->private.uid
);
666 /* anything we need not currently cached? */
667 if ((!elt
->day
|| !elt
->rfc822_size
||
668 ((flags
& MLM_HEADER
) && !elt
->private.msg
.header
.text
.data
) ||
669 ((flags
& MLM_TEXT
) && !elt
->private.msg
.text
.text
.data
)) &&
670 ((fd
= open (LOCAL
->buf
,O_RDONLY
,NIL
)) >= 0)) {
671 fstat (fd
,&sbuf
); /* get file metadata */
672 d
.fd
= fd
; /* set up file descriptor */
673 d
.pos
= 0; /* start of file */
674 d
.chunk
= LOCAL
->buf
;
675 d
.chunksize
= CHUNKSIZE
;
676 INIT (&bs
,fd_string
,&d
,sbuf
.st_size
);
677 if (!elt
->day
) { /* set internaldate to file date */
678 struct tm
*tm
= gmtime (&sbuf
.st_mtime
);
679 elt
->day
= tm
->tm_mday
; elt
->month
= tm
->tm_mon
+ 1;
680 elt
->year
= tm
->tm_year
+ 1900 - BASEYEAR
;
681 elt
->hours
= tm
->tm_hour
; elt
->minutes
= tm
->tm_min
;
682 elt
->seconds
= tm
->tm_sec
;
683 elt
->zhours
= 0; elt
->zminutes
= 0;
686 if (!elt
->rfc822_size
) { /* know message size yet? */
687 for (i
= 0, j
= SIZE (&bs
), nlseen
= 0; j
--; ) switch (SNX (&bs
)) {
688 case '\015': /* unlikely carriage return */
689 if (!j
|| (CHR (&bs
) != '\012')) {
690 i
++; /* ugh, raw CR */
694 SNX (&bs
); /* eat the line feed, drop in */
696 case '\012': /* line feed? */
697 i
+= 2; /* count a CRLF */
698 /* header size known yet? */
699 if (!elt
->private.msg
.header
.text
.size
&& nlseen
) {
700 /* note position in file */
701 elt
->private.special
.text
.size
= GETPOS (&bs
);
702 /* and CRLF-adjusted size */
703 elt
->private.msg
.header
.text
.size
= i
;
705 nlseen
= T
; /* note newline seen */
707 default: /* ordinary character */
712 SETPOS (&bs
,0); /* restore old position */
713 elt
->rfc822_size
= i
; /* note that we have size now */
714 /* header is entire message if no delimiter */
715 if (!elt
->private.msg
.header
.text
.size
)
716 elt
->private.msg
.header
.text
.size
= elt
->rfc822_size
;
717 /* text is remainder of message */
718 elt
->private.msg
.text
.text
.size
=
719 elt
->rfc822_size
- elt
->private.msg
.header
.text
.size
;
721 /* need to load cache with message data? */
722 if (((flags
& MLM_HEADER
) && !elt
->private.msg
.header
.text
.data
) ||
723 ((flags
& MLM_TEXT
) && !elt
->private.msg
.text
.text
.data
)) {
724 /* purge cache if too big */
725 if (LOCAL
->cachedtexts
> max (stream
->nmsgs
* 4096,2097152)) {
726 /* just can't keep that much */
727 mail_gc (stream
,GC_TEXTS
);
728 LOCAL
->cachedtexts
= 0;
731 if ((flags
& MLM_HEADER
) && !elt
->private.msg
.header
.text
.data
) {
732 t
= elt
->private.msg
.header
.text
.data
=
733 (unsigned char *) fs_get (elt
->private.msg
.header
.text
.size
+ 1);
734 LOCAL
->cachedtexts
+= elt
->private.msg
.header
.text
.size
;
735 /* read in message header */
736 for (i
= 0; i
< elt
->private.msg
.header
.text
.size
; i
++)
737 switch (c
= SNX (&bs
)) {
738 case '\015': /* unlikely carriage return */
740 if (CHR (&bs
) == '\012') {
745 case '\012': /* line feed? */
752 *t
= '\0'; /* tie off string */
753 if ((t
- elt
->private.msg
.header
.text
.data
) !=
754 elt
->private.msg
.header
.text
.size
) fatal ("mh hdr size mismatch");
756 if ((flags
& MLM_TEXT
) && !elt
->private.msg
.text
.text
.data
) {
757 t
= elt
->private.msg
.text
.text
.data
=
758 (unsigned char *) fs_get (elt
->private.msg
.text
.text
.size
+ 1);
759 SETPOS (&bs
,elt
->private.special
.text
.size
);
760 LOCAL
->cachedtexts
+= elt
->private.msg
.text
.text
.size
;
761 /* read in message text */
762 for (i
= 0; i
< elt
->private.msg
.text
.text
.size
; i
++)
763 switch (c
= SNX (&bs
)) {
764 case '\015': /* unlikely carriage return */
766 if (CHR (&bs
) == '\012') {
771 case '\012': /* line feed? */
778 *t
= '\0'; /* tie off string */
779 if ((t
- elt
->private.msg
.text
.text
.data
) !=
780 elt
->private.msg
.text
.text
.size
) fatal ("mh txt size mismatch");
783 close (fd
); /* flush message file */
787 /* MH mail fetch message header
788 * Accepts: MAIL stream
790 * pointer to returned header text length
792 * Returns: message header in RFC822 format
795 char *mh_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
799 *length
= 0; /* default to empty */
800 if (flags
& FT_UID
) return "";/* UID call "impossible" */
801 elt
= mail_elt (stream
,msgno
);/* get elt */
802 if (!elt
->private.msg
.header
.text
.data
)
803 mh_load_message (stream
,msgno
,MLM_HEADER
);
804 *length
= elt
->private.msg
.header
.text
.size
;
805 return (char *) elt
->private.msg
.header
.text
.data
;
809 /* MH mail fetch message text (body only)
810 * Accepts: MAIL stream
812 * pointer to returned stringstruct
814 * Returns: T on success, NIL on failure
817 long mh_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
820 /* UID call "impossible" */
821 if (flags
& FT_UID
) return NIL
;
822 elt
= mail_elt (stream
,msgno
);/* get elt */
823 /* snarf message if don't have it yet */
824 if (!elt
->private.msg
.text
.text
.data
) {
825 mh_load_message (stream
,msgno
,MLM_TEXT
);
826 if (!elt
->private.msg
.text
.text
.data
) return NIL
;
828 if (!(flags
& FT_PEEK
)) { /* mark as seen */
829 mail_elt (stream
,msgno
)->seen
= T
;
830 mm_flags (stream
,msgno
);
832 INIT (bs
,mail_string
,elt
->private.msg
.text
.text
.data
,
833 elt
->private.msg
.text
.text
.size
);
837 /* MH mail ping mailbox
838 * Accepts: MAIL stream
839 * Returns: T if stream alive, else NIL
842 long mh_ping (MAILSTREAM
*stream
)
844 MAILSTREAM
*sysibx
= NIL
;
845 MESSAGECACHE
*elt
,*selt
;
847 char *s
,tmp
[MAILTMPLEN
];
850 unsigned long old
= stream
->uid_last
;
851 long nmsgs
= stream
->nmsgs
;
852 long recent
= stream
->recent
;
853 int silent
= stream
->silent
;
854 if (stat (LOCAL
->dir
,&sbuf
)) {/* directory exists? */
855 if (stream
->inbox
&& /* no, create if INBOX */
856 dummy_create_path (stream
,strcat (mh_file (tmp
,MHINBOX
),"/"),
857 get_dir_protection ("INBOX"))) return T
;
858 sprintf (tmp
,"Can't open mailbox %.80s: no such mailbox",stream
->mailbox
);
862 stream
->silent
= T
; /* don't pass up mm_exists() events yet */
863 if (sbuf
.st_ctime
!= LOCAL
->scantime
) {
864 struct direct
**names
= NIL
;
865 long nfiles
= scandir (LOCAL
->dir
,&names
,mh_select
,mh_numsort
);
866 if (nfiles
< 0) nfiles
= 0; /* in case error */
867 /* note scanned now */
868 LOCAL
->scantime
= sbuf
.st_ctime
;
870 for (i
= 0; i
< nfiles
; ++i
) {
871 /* if newly seen, add to list */
872 if ((j
= atoi (names
[i
]->d_name
)) > old
) {
873 mail_exists (stream
,++nmsgs
);
874 stream
->uid_last
= (elt
= mail_elt (stream
,nmsgs
))->private.uid
= j
;
875 elt
->valid
= T
; /* note valid flags */
876 if (old
) { /* other than the first pass? */
877 elt
->recent
= T
; /* yup, mark as recent */
878 recent
++; /* bump recent count */
880 else { /* see if already read */
881 sprintf (tmp
,"%s/%s",LOCAL
->dir
,names
[i
]->d_name
);
882 if (!stat (tmp
,&sbuf
) && (sbuf
.st_atime
> sbuf
.st_mtime
))
886 fs_give ((void **) &names
[i
]);
889 if ((s
= (void *) names
) != NULL
) fs_give ((void **) &s
);
892 /* if INBOX, snarf from system INBOX */
893 if (stream
->inbox
&& strcmp (sysinbox (),stream
->mailbox
)) {
894 old
= stream
->uid_last
;
895 mm_critical (stream
); /* go critical */
896 /* see if anything in system inbox */
897 if (!stat (sysinbox (),&sbuf
) && sbuf
.st_size
&&
898 (sysibx
= mail_open (sysibx
,sysinbox (),OP_SILENT
)) &&
899 !sysibx
->rdonly
&& (r
= sysibx
->nmsgs
)) {
900 for (i
= 1; i
<= r
; ++i
) {/* for each message in sysinbox mailbox */
901 /* build file name we will use */
902 sprintf (LOCAL
->buf
,"%s/%lu",LOCAL
->dir
,++old
);
903 /* snarf message from Berkeley mailbox */
904 selt
= mail_elt (sysibx
,i
);
905 if (((fd
= open (LOCAL
->buf
,O_WRONLY
|O_CREAT
|O_EXCL
,
906 (long) mail_parameters (NIL
,GET_MBXPROTECTION
,NIL
)))
908 (s
= mail_fetchheader_full (sysibx
,i
,NIL
,&j
,FT_INTERNAL
)) &&
909 (write (fd
,s
,j
) == j
) &&
910 (s
= mail_fetchtext_full (sysibx
,i
,&j
,FT_INTERNAL
|FT_PEEK
)) &&
911 (write (fd
,s
,j
) == j
) && !fsync (fd
) && !close (fd
)) {
912 /* swell the cache */
913 mail_exists (stream
,++nmsgs
);
914 stream
->uid_last
= /* create new elt, note its file number */
915 (elt
= mail_elt (stream
,nmsgs
))->private.uid
= old
;
916 recent
++; /* bump recent count */
917 /* set up initial flags and date */
918 elt
->valid
= elt
->recent
= T
;
919 elt
->seen
= selt
->seen
;
920 elt
->deleted
= selt
->deleted
;
921 elt
->flagged
= selt
->flagged
;
922 elt
->answered
= selt
->answered
;
923 elt
->draft
= selt
->draft
;
924 elt
->day
= selt
->day
;elt
->month
= selt
->month
;elt
->year
= selt
->year
;
925 elt
->hours
= selt
->hours
;elt
->minutes
= selt
->minutes
;
926 elt
->seconds
= selt
->seconds
;
927 elt
->zhours
= selt
->zhours
; elt
->zminutes
= selt
->zminutes
;
928 elt
->zoccident
= selt
->zoccident
;
929 mh_setdate (LOCAL
->buf
,elt
);
930 sprintf (tmp
,"%lu",i
);/* delete it from the sysinbox */
931 mail_flag (sysibx
,tmp
,"\\Deleted",ST_SET
);
934 else { /* failed to snarf */
935 if (fd
) { /* did it ever get opened? */
936 close (fd
); /* close descriptor */
937 unlink (LOCAL
->buf
);/* flush this file */
939 sprintf (tmp
,"Message copy to MH mailbox failed: %.80s",
942 r
= 0; /* stop the snarf in its tracks */
945 /* update scan time */
946 if (!stat (LOCAL
->dir
,&sbuf
)) LOCAL
->scantime
= sbuf
.st_ctime
;
947 mail_expunge (sysibx
); /* now expunge all those messages */
949 if (sysibx
) mail_close (sysibx
);
950 mm_nocritical (stream
); /* release critical */
952 stream
->silent
= silent
; /* can pass up events now */
953 mail_exists (stream
,nmsgs
); /* notify upper level of mailbox size */
954 mail_recent (stream
,recent
);
955 return T
; /* return that we are alive */
958 /* MH mail check mailbox
959 * Accepts: MAIL stream
962 void mh_check (MAILSTREAM
*stream
)
964 /* Perhaps in the future this will preserve flags */
965 if (mh_ping (stream
)) mm_log ("Check completed",(long) NIL
);
969 /* MH mail expunge mailbox
970 * Accepts: MAIL stream
971 * sequence to expunge if non-NIL
976 long mh_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
982 unsigned long recent
= stream
->recent
;
983 if ((ret
= sequence
? ((options
& EX_UID
) ?
984 mail_uid_sequence (stream
,sequence
) :
985 mail_sequence (stream
,sequence
)) : LONGT
) != 0L) {
986 mm_critical (stream
); /* go critical */
987 while (i
<= stream
->nmsgs
) {/* for each message */
988 elt
= mail_elt (stream
,i
);/* if deleted, need to trash it */
989 if (elt
->deleted
&& (sequence
? elt
->sequence
: T
)) {
990 sprintf (LOCAL
->buf
,"%s/%lu",LOCAL
->dir
,elt
->private.uid
);
991 if (unlink (LOCAL
->buf
)) {/* try to delete the message */
992 sprintf (LOCAL
->buf
,"Expunge of message %lu failed, aborted: %s",i
,
994 mm_log (LOCAL
->buf
,(long) NIL
);
998 LOCAL
->cachedtexts
-= ((elt
->private.msg
.header
.text
.data
?
999 elt
->private.msg
.header
.text
.size
: 0) +
1000 (elt
->private.msg
.text
.text
.data
?
1001 elt
->private.msg
.text
.text
.size
: 0));
1002 mail_gc_msg (&elt
->private.msg
,GC_ENV
| GC_TEXTS
);
1003 /* if recent, note one less recent message */
1004 if (elt
->recent
) --recent
;
1005 /* notify upper levels */
1006 mail_expunged (stream
,i
);
1007 n
++; /* count up one more expunged message */
1009 else i
++; /* otherwise try next message */
1011 if (n
) { /* output the news if any expunged */
1012 sprintf (LOCAL
->buf
,"Expunged %lu messages",n
);
1013 mm_log (LOCAL
->buf
,(long) NIL
);
1015 else mm_log ("No messages deleted, so no update needed",(long) NIL
);
1016 mm_nocritical (stream
); /* release critical */
1017 /* notify upper level of new mailbox size */
1018 mail_exists (stream
,stream
->nmsgs
);
1019 mail_recent (stream
,recent
);
1024 /* MH mail copy message(s)
1025 * Accepts: MAIL stream
1027 * destination mailbox
1029 * Returns: T if copy successful, else NIL
1032 long mh_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
1040 char flags
[MAILTMPLEN
],date
[MAILTMPLEN
];
1041 appenduid_t au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
);
1043 /* copy the messages */
1044 if ((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
1045 mail_sequence (stream
,sequence
))
1046 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1047 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1048 sprintf (LOCAL
->buf
,"%s/%lu",LOCAL
->dir
,elt
->private.uid
);
1049 if ((fd
= open (LOCAL
->buf
,O_RDONLY
,NIL
)) < 0) return NIL
;
1050 fstat (fd
,&sbuf
); /* get size of message */
1051 if (!elt
->day
) { /* set internaldate to file date if needed */
1052 struct tm
*tm
= gmtime (&sbuf
.st_mtime
);
1053 elt
->day
= tm
->tm_mday
; elt
->month
= tm
->tm_mon
+ 1;
1054 elt
->year
= tm
->tm_year
+ 1900 - BASEYEAR
;
1055 elt
->hours
= tm
->tm_hour
; elt
->minutes
= tm
->tm_min
;
1056 elt
->seconds
= tm
->tm_sec
;
1057 elt
->zhours
= 0; elt
->zminutes
= 0;
1059 d
.fd
= fd
; /* set up file descriptor */
1060 d
.pos
= 0; /* start of file */
1061 d
.chunk
= LOCAL
->buf
;
1062 d
.chunksize
= CHUNKSIZE
;
1063 /* kludge; mh_append would just strip CRs */
1064 INIT (&st
,fd_string
,&d
,sbuf
.st_size
);
1065 /* init flag string */
1066 flags
[0] = flags
[1] = '\0';
1067 if (elt
->seen
) strcat (flags
," \\Seen");
1068 if (elt
->deleted
) strcat (flags
," \\Deleted");
1069 if (elt
->flagged
) strcat (flags
," \\Flagged");
1070 if (elt
->answered
) strcat (flags
," \\Answered");
1071 if (elt
->draft
) strcat (flags
," \\Draft");
1072 flags
[0] = '('; /* open list */
1073 strcat (flags
,")"); /* close list */
1074 mail_date (date
,elt
); /* generate internal date */
1075 if (au
) mail_parameters (NIL
,SET_APPENDUID
,NIL
);
1076 if ((ret
= mail_append_full (NIL
,mailbox
,flags
,date
,&st
)) &&
1077 (options
& CP_MOVE
)) elt
->deleted
= T
;
1078 if (au
) mail_parameters (NIL
,SET_APPENDUID
,(void *) au
);
1081 if (ret
&& mail_parameters (NIL
,GET_COPYUID
,NIL
))
1082 mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN
);
1083 return ret
; /* return success */
1086 /* MH mail append message from stringstruct
1087 * Accepts: MAIL stream
1088 * destination mailbox
1091 * Returns: T if append successful, else NIL
1094 long mh_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1096 struct direct
**names
= NIL
;
1098 char c
,*flags
,*date
,*s
,tmp
[MAILTMPLEN
];
1102 long i
,size
,last
,nfiles
;
1104 /* default stream to prototype */
1105 if (!stream
) stream
= &mhproto
;
1106 /* make sure valid mailbox */
1107 if (!mh_isvalid (mailbox
,tmp
,NIL
)) switch (errno
) {
1108 case ENOENT
: /* no such file? */
1109 if (!((!compare_cstring (mailbox
,MHINBOX
) ||
1110 !compare_cstring (mailbox
,"INBOX")) &&
1111 (mh_file (tmp
,MHINBOX
) &&
1112 dummy_create_path (stream
,strcat (tmp
,"/"),
1113 get_dir_protection (mailbox
))))) {
1114 mm_notify (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
1118 case 0: /* merely empty file? */
1121 sprintf (tmp
,"Invalid MH-format mailbox name: %.80s",mailbox
);
1125 sprintf (tmp
,"Not a MH-format mailbox: %.80s",mailbox
);
1129 /* get first message */
1130 if (!(*af
) (stream
,data
,&flags
,&date
,&message
)) return NIL
;
1131 if ((nfiles
= scandir (tmp
,&names
,mh_select
,mh_numsort
)) > 0) {
1132 /* largest number */
1133 last
= atoi (names
[nfiles
-1]->d_name
);
1134 for (i
= 0; i
< nfiles
; ++i
) /* free directory */
1135 fs_give ((void **) &names
[i
]);
1137 else last
= 0; /* no messages here yet */
1138 if ((s
= (void *) names
) != NULL
) fs_give ((void **) &s
);
1140 mm_critical (stream
); /* go critical */
1142 if (!SIZE (message
)) { /* guard against zero-length */
1143 mm_log ("Append of zero-length message",ERROR
);
1147 if (date
) { /* want to preserve date? */
1148 /* yes, parse date into an elt */
1149 if (!mail_parse_date (&elt
,date
)) {
1150 sprintf (tmp
,"Bad date in append: %.80s",date
);
1156 mh_file (tmp
,mailbox
); /* build file name we will use */
1157 sprintf (tmp
+ strlen (tmp
),"/%ld",++last
);
1158 if (((fd
= open (tmp
,O_WRONLY
|O_CREAT
|O_EXCL
,
1159 (long)mail_parameters (NIL
,GET_MBXPROTECTION
,NIL
))) < 0)||
1160 !(df
= fdopen (fd
,"ab"))) {
1161 sprintf (tmp
,"Can't open append message: %s",strerror (errno
));
1166 /* copy the data w/o CR's */
1167 for (size
= 0,i
= SIZE (message
); i
&& ret
; --i
)
1168 if (((c
= SNX (message
)) != '\015') && (putc (c
,df
) == EOF
)) ret
= NIL
;
1169 /* close the file */
1170 if (!ret
|| fclose (df
)) {
1171 unlink (tmp
); /* delete message */
1172 sprintf (tmp
,"Message append failed: %s",strerror (errno
));
1176 if (ret
) { /* set the date for this message */
1177 if (date
) mh_setdate (tmp
,&elt
);
1178 /* get next message */
1179 if (!(*af
) (stream
,data
,&flags
,&date
,&message
)) ret
= NIL
;
1181 } while (ret
&& message
);
1182 mm_nocritical (stream
); /* release critical */
1183 if (ret
&& mail_parameters (NIL
,GET_APPENDUID
,NIL
))
1184 mm_log ("Can not return meaningful APPENDUID with this mailbox format",
1189 /* Internal routines */
1192 /* MH file name selection test
1193 * Accepts: candidate directory entry
1194 * Returns: T to use file name, NIL to skip it
1197 int mh_select (const struct direct
*name
)
1200 char *s
= (char *) name
->d_name
;
1201 while ((c
= *s
++) != '\0') if (!isdigit (c
)) return NIL
;
1206 /* MH file name comparison
1207 * Accepts: first candidate directory entry
1208 * second candidate directory entry
1209 * Returns: negative if d1 < d2, 0 if d1 == d2, positive if d1 > d2
1212 int mh_numsort (const struct direct
**d1
,const struct direct
**d2
)
1214 return atoi ((*(struct direct
**) d1
)->d_name
) -
1215 atoi ((*(struct direct
**) d2
)->d_name
);
1219 /* MH mail build file name
1220 * Accepts: destination string
1222 * Returns: destination
1225 char *mh_file (char *dst
,char *name
)
1228 char *path
= mh_path (dst
);
1229 if (!path
) fatal ("No mh path in mh_file()!");
1230 /* INBOX becomes "inbox" in the MH path */
1231 if (!compare_cstring (name
,MHINBOX
) || !compare_cstring (name
,"INBOX"))
1232 sprintf (dst
,"%.900s/%.80s",path
,MHINBOXDIR
);
1233 /* #mh names skip past prefix */
1234 else if (*name
== '#') sprintf (dst
,"%.100s/%.900s",path
,name
+ 4);
1235 else mailboxfile (dst
,name
); /* all other names */
1236 /* tie off unnecessary trailing / */
1237 if ((s
= strrchr (dst
,'/')) && !s
[1] && (s
[-1] == '/')) *s
= '\0';
1241 /* MH canonicalize name
1242 * Accepts: buffer to write name
1245 * Returns: T if success, NIL if failure
1248 long mh_canonicalize (char *pattern
,char *ref
,char *pat
)
1251 char *s
,tmp
[MAILTMPLEN
];
1252 if (ref
&& *ref
) { /* have a reference */
1253 strcpy (pattern
,ref
); /* copy reference to pattern */
1254 /* # overrides mailbox field in reference */
1255 if (*pat
== '#') strcpy (pattern
,pat
);
1256 /* pattern starts, reference ends, with / */
1257 else if ((*pat
== '/') && (pattern
[strlen (pattern
) - 1] == '/'))
1258 strcat (pattern
,pat
+ 1); /* append, omitting one of the period */
1259 else strcat (pattern
,pat
); /* anything else is just appended */
1261 else strcpy (pattern
,pat
); /* just have basic name */
1262 if (mh_isvalid (pattern
,tmp
,T
)) {
1263 /* count wildcards */
1264 for (i
= 0, s
= pattern
; *s
; s
++) if ((*s
== '*') || (*s
== '%')) ++i
;
1265 /* success if not too many */
1266 if (i
<= MAXWILDCARDS
) return LONGT
;
1267 mm_log ("Excessive wildcards in LIST/LSUB",ERROR
);
1272 /* Set date for message
1273 * Accepts: file name
1274 * elt containing date
1277 void mh_setdate (char *file
,MESSAGECACHE
*elt
)
1280 tp
[0] = time (0); /* atime is now */
1281 tp
[1] = mail_longdate (elt
); /* modification time */
1282 utime (file
,tp
); /* set the times */