* Do not define _BSD_SOURCE but define _DEFAULT_SOURCE instead.
[alpine.git] / imap / src / osdep / unix / tenex.c
blob8815fee19d8c88de7990d034f9dbe6b02df469b4
1 /* ========================================================================
2 * Copyright 1988-2007 University of Washington
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
15 * Program: Tenex mail routines
17 * Author: Mark Crispin
18 * Networks and Distributed Computing
19 * Computing & Communications
20 * University of Washington
21 * Administration Building, AG-44
22 * Seattle, WA 98195
23 * Internet: MRC@CAC.Washington.EDU
25 * Date: 22 May 1990
26 * Last Edited: 11 October 2007
30 /* FILE TIME SEMANTICS
32 * The atime is the last read time of the file.
33 * The mtime is the last flags update time of the file.
34 * The ctime is the last write time of the file.
36 * TEXT SIZE SEMANTICS
38 * Most of the text sizes are in internal (LF-only) form, except for the
39 * msg.text size. Beware.
42 #include <stdio.h>
43 #include <ctype.h>
44 #include <errno.h>
45 extern int errno; /* just in case */
46 #include "mail.h"
47 #include "osdep.h"
48 #include <sys/stat.h>
49 #include "misc.h"
50 #include "dummy.h"
52 /* TENEX I/O stream local data */
54 typedef struct tenex_local {
55 unsigned int shouldcheck: 1; /* if ping should do a check instead */
56 unsigned int mustcheck: 1; /* if ping must do a check instead */
57 int fd; /* file descriptor for I/O */
58 off_t filesize; /* file size parsed */
59 time_t filetime; /* last file time */
60 time_t lastsnarf; /* local snarf time */
61 unsigned char *buf; /* temporary buffer */
62 unsigned long buflen; /* current size of temporary buffer */
63 unsigned long uid; /* current text uid */
64 SIZEDTEXT text; /* current text */
65 } TENEXLOCAL;
68 /* Convenient access to local data */
70 #define LOCAL ((TENEXLOCAL *) stream->local)
73 /* Function prototypes */
75 DRIVER *tenex_valid (char *name);
76 int tenex_isvalid (char *name,char *tmp);
77 void *tenex_parameters (long function,void *value);
78 void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
79 void tenex_list (MAILSTREAM *stream,char *ref,char *pat);
80 void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat);
81 long tenex_create (MAILSTREAM *stream,char *mailbox);
82 long tenex_delete (MAILSTREAM *stream,char *mailbox);
83 long tenex_rename (MAILSTREAM *stream,char *old,char *newname);
84 long tenex_status (MAILSTREAM *stream,char *mbx,long flags);
85 MAILSTREAM *tenex_open (MAILSTREAM *stream);
86 void tenex_close (MAILSTREAM *stream,long options);
87 void tenex_fast (MAILSTREAM *stream,char *sequence,long flags);
88 void tenex_flags (MAILSTREAM *stream,char *sequence,long flags);
89 char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
90 unsigned long *length,long flags);
91 long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
92 void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
93 void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
94 long tenex_ping (MAILSTREAM *stream);
95 void tenex_check (MAILSTREAM *stream);
96 void tenex_snarf (MAILSTREAM *stream);
97 long tenex_expunge (MAILSTREAM *stream,char *sequence,long options);
98 long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
99 long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
101 unsigned long tenex_size (MAILSTREAM *stream,unsigned long m);
102 char *tenex_file (char *dst,char *name);
103 long tenex_parse (MAILSTREAM *stream);
104 MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno);
105 void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
106 void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,
107 long syncflag);
108 unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
109 unsigned long *size);
111 /* Tenex mail routines */
114 /* Driver dispatch used by MAIL */
116 DRIVER tenexdriver = {
117 "tenex", /* driver name */
118 DR_LOCAL|DR_MAIL|DR_NOSTICKY|DR_LOCKING,
119 /* driver flags */
120 (DRIVER *) NIL, /* next driver */
121 tenex_valid, /* mailbox is valid for us */
122 tenex_parameters, /* manipulate parameters */
123 tenex_scan, /* scan mailboxes */
124 tenex_list, /* list mailboxes */
125 tenex_lsub, /* list subscribed mailboxes */
126 NIL, /* subscribe to mailbox */
127 NIL, /* unsubscribe from mailbox */
128 dummy_create, /* create mailbox */
129 tenex_delete, /* delete mailbox */
130 tenex_rename, /* rename mailbox */
131 tenex_status, /* status of mailbox */
132 tenex_open, /* open mailbox */
133 tenex_close, /* close mailbox */
134 tenex_fast, /* fetch message "fast" attributes */
135 tenex_flags, /* fetch message flags */
136 NIL, /* fetch overview */
137 NIL, /* fetch message envelopes */
138 tenex_header, /* fetch message header */
139 tenex_text, /* fetch message body */
140 NIL, /* fetch partial message text */
141 NIL, /* unique identifier */
142 NIL, /* message number */
143 tenex_flag, /* modify flags */
144 tenex_flagmsg, /* per-message modify flags */
145 NIL, /* search for message based on criteria */
146 NIL, /* sort messages */
147 NIL, /* thread messages */
148 tenex_ping, /* ping mailbox to see if still alive */
149 tenex_check, /* check for new messages */
150 tenex_expunge, /* expunge deleted messages */
151 tenex_copy, /* copy messages to another mailbox */
152 tenex_append, /* append string message to mailbox */
153 NIL /* garbage collect stream */
156 /* prototype stream */
157 MAILSTREAM tenexproto = {&tenexdriver};
159 /* Tenex mail validate mailbox
160 * Accepts: mailbox name
161 * Returns: our driver if name is valid, NIL otherwise
164 DRIVER *tenex_valid (char *name)
166 char tmp[MAILTMPLEN];
167 return tenex_isvalid (name,tmp) ? &tenexdriver : NIL;
171 /* Tenex mail test for valid mailbox
172 * Accepts: mailbox name
173 * Returns: T if valid, NIL otherwise
176 int tenex_isvalid (char *name,char *tmp)
178 int fd;
179 int ret = NIL;
180 char *s,file[MAILTMPLEN];
181 struct stat sbuf;
182 time_t tp[2];
183 errno = EINVAL; /* assume invalid argument */
184 /* if file, get its status */
185 if ((s = tenex_file (file,name)) && !stat (s,&sbuf)) {
186 if (!sbuf.st_size) { /* allow empty file if INBOX */
187 if ((s = mailboxfile (tmp,name)) && !*s) ret = T;
188 else errno = 0; /* empty file */
190 else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
191 memset (tmp,'\0',MAILTMPLEN);
192 if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\012')) &&
193 (s[-1] != '\015')) { /* valid format? */
194 *s = '\0'; /* tie off header */
195 /* must begin with dd-mmm-yy" */
196 ret = (((tmp[2] == '-' && tmp[6] == '-') ||
197 (tmp[1] == '-' && tmp[5] == '-')) &&
198 (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
200 else errno = -1; /* bogus format */
201 close (fd); /* close the file */
202 /* \Marked status? */
203 if (sbuf.st_ctime > sbuf.st_atime) {
204 tp[0] = sbuf.st_atime; /* preserve atime and mtime */
205 tp[1] = sbuf.st_mtime;
206 utime (file,tp); /* set the times */
210 /* in case INBOX but not tenex format */
211 else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
212 return ret; /* return what we should */
215 /* Tenex manipulate driver parameters
216 * Accepts: function code
217 * function-dependent value
218 * Returns: function-dependent return value
221 void *tenex_parameters (long function,void *value)
223 void *ret = NIL;
224 switch ((int) function) {
225 case GET_INBOXPATH:
226 if (value) ret = tenex_file ((char *) value,"INBOX");
227 break;
229 return ret;
233 /* Tenex mail scan mailboxes
234 * Accepts: mail stream
235 * reference
236 * pattern to search
237 * string to scan
240 void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
242 if (stream) dummy_scan (NIL,ref,pat,contents);
246 /* Tenex mail list mailboxes
247 * Accepts: mail stream
248 * reference
249 * pattern to search
252 void tenex_list (MAILSTREAM *stream,char *ref,char *pat)
254 if (stream) dummy_list (NIL,ref,pat);
258 /* Tenex mail list subscribed mailboxes
259 * Accepts: mail stream
260 * reference
261 * pattern to search
264 void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat)
266 if (stream) dummy_lsub (NIL,ref,pat);
269 /* Tenex mail delete mailbox
270 * Accepts: MAIL stream
271 * mailbox name to delete
272 * Returns: T on success, NIL on failure
275 long tenex_delete (MAILSTREAM *stream,char *mailbox)
277 return tenex_rename (stream,mailbox,NIL);
281 /* Tenex mail rename mailbox
282 * Accepts: MAIL stream
283 * old mailbox name
284 * new mailbox name (or NIL for delete)
285 * Returns: T on success, NIL on failure
288 long tenex_rename (MAILSTREAM *stream,char *old,char *newname)
290 long ret = T;
291 char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
292 int fd,ld;
293 struct stat sbuf;
294 if (!tenex_file (file,old) ||
295 (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
296 ((s = strrchr (tmp,'/')) && !s[1])))) {
297 if(newname)
298 sprintf (tmp,
299 "Can't rename mailbox %.80s to %.80s: invalid name",
300 old,newname);
301 else
302 sprintf (tmp,
303 "Can't delete mailbox %.80s: invalid name", old);
304 MM_LOG (tmp,ERROR);
305 return NIL;
307 else if ((fd = open (file,O_RDWR,NIL)) < 0) {
308 sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
309 MM_LOG (tmp,ERROR);
310 return NIL;
312 /* get exclusive parse/append permission */
313 if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) {
314 MM_LOG ("Unable to lock rename mailbox",ERROR);
315 return NIL;
317 /* lock out other users */
318 if (flock (fd,LOCK_EX|LOCK_NB)) {
319 close (fd); /* couldn't lock, give up on it then */
320 sprintf (tmp,"Mailbox %.80s is in use by another process",old);
321 MM_LOG (tmp,ERROR);
322 unlockfd (ld,lock); /* release exclusive parse/append permission */
323 return NIL;
326 if (newname) { /* want rename? */
327 if ((s = strrchr (tmp,'/')) != NULL) {/* found superior to destination name? */
328 c = *++s; /* remember first character of inferior */
329 *s = '\0'; /* tie off to get just superior */
330 /* name doesn't exist, create it */
331 if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
332 !dummy_create_path (stream,tmp,get_dir_protection (newname)))
333 ret = NIL;
334 else *s = c; /* restore full name */
336 /* rename the file */
337 if (ret && rename (file,tmp)) {
338 sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
339 strerror (errno));
340 MM_LOG (tmp,ERROR);
341 ret = NIL; /* set failure */
344 else if (unlink (file)) {
345 sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
346 MM_LOG (tmp,ERROR);
347 ret = NIL; /* set failure */
349 flock (fd,LOCK_UN); /* release lock on the file */
350 close (fd); /* close the file */
351 unlockfd (ld,lock); /* release exclusive parse/append permission */
352 /* recreate file if renamed INBOX */
353 if (ret && !compare_cstring (old,"INBOX")) dummy_create (NIL,"mail.txt");
354 return ret; /* return success */
357 /* Tenex Mail status
358 * Accepts: mail stream
359 * mailbox name
360 * status flags
361 * Returns: T on success, NIL on failure
364 long tenex_status (MAILSTREAM *stream,char *mbx,long flags)
366 MAILSTATUS status;
367 unsigned long i;
368 MAILSTREAM *tstream = NIL;
369 MAILSTREAM *systream = NIL;
370 /* make temporary stream (unless this mbx) */
371 if (!stream && !(stream = tstream =
372 mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
373 status.flags = flags; /* return status values */
374 status.messages = stream->nmsgs;
375 status.recent = stream->recent;
376 if (flags & SA_UNSEEN) /* must search to get unseen messages */
377 for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
378 if (!mail_elt (stream,i)->seen) status.unseen++;
379 status.uidnext = stream->uid_last + 1;
380 status.uidvalidity = stream->uid_validity;
381 /* calculate post-snarf results */
382 if (!status.recent && stream->inbox &&
383 (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
384 status.messages += systream->nmsgs;
385 status.recent += systream->recent;
386 if (flags & SA_UNSEEN) /* must search to get unseen messages */
387 for (i = 1; i <= systream->nmsgs; i++)
388 if (!mail_elt (systream,i)->seen) status.unseen++;
389 /* kludge but probably good enough */
390 status.uidnext += systream->nmsgs;
392 MM_STATUS(stream,mbx,&status);/* pass status to main program */
393 if (tstream) mail_close (tstream);
394 if (systream) mail_close (systream);
395 return T; /* success */
398 /* Tenex mail open
399 * Accepts: stream to open
400 * Returns: stream on success, NIL on failure
403 MAILSTREAM *tenex_open (MAILSTREAM *stream)
405 int fd,ld;
406 char tmp[MAILTMPLEN];
407 blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
408 /* return prototype for OP_PROTOTYPE call */
409 if (!stream) return user_flags (&tenexproto);
410 if (stream->local) fatal ("tenex recycle stream");
411 user_flags (stream); /* set up user flags */
412 /* canonicalize the mailbox name */
413 if (!tenex_file (tmp,stream->mailbox)) {
414 sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
415 MM_LOG (tmp,ERROR);
417 if (stream->rdonly ||
418 (fd = open (tmp,O_RDWR,NIL)) < 0) {
419 if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
420 sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
421 MM_LOG (tmp,ERROR);
422 return NIL;
424 else if (!stream->rdonly) { /* got it, but readonly */
425 MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
426 stream->rdonly = T;
429 stream->local = fs_get (sizeof (TENEXLOCAL));
430 LOCAL->buf = (char *) fs_get (CHUNKSIZE);
431 LOCAL->buflen = CHUNKSIZE - 1;
432 LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
433 LOCAL->text.size = CHUNKSIZE - 1;
435 /* note if an INBOX or not */
436 stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
437 LOCAL->fd = fd; /* bind the file */
438 /* flush old name */
439 fs_give ((void **) &stream->mailbox);
440 /* save canonical name */
441 stream->mailbox = cpystr (tmp);
442 /* get shared parse permission */
443 if ((ld = lockfd (fd,tmp,LOCK_SH)) < 0) {
444 MM_LOG ("Unable to lock open mailbox",ERROR);
445 return NIL;
447 (*bn) (BLOCK_FILELOCK,NIL);
448 flock (LOCAL->fd,LOCK_SH); /* lock the file */
449 (*bn) (BLOCK_NONE,NIL);
450 unlockfd (ld,tmp); /* release shared parse permission */
451 LOCAL->filesize = 0; /* initialize parsed file size */
452 /* time not set up yet */
453 LOCAL->lastsnarf = LOCAL->filetime = 0;
454 LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
455 stream->sequence++; /* bump sequence number */
456 /* parse mailbox */
457 stream->nmsgs = stream->recent = 0;
458 if (tenex_ping (stream) && !stream->nmsgs)
459 MM_LOG ("Mailbox is empty",(long) NIL);
460 if (!LOCAL) return NIL; /* failure if stream died */
461 stream->perm_seen = stream->perm_deleted =
462 stream->perm_flagged = stream->perm_answered = stream->perm_draft =
463 stream->rdonly ? NIL : T;
464 stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
465 return stream; /* return stream to caller */
468 /* Tenex mail close
469 * Accepts: MAIL stream
470 * close options
473 void tenex_close (MAILSTREAM *stream,long options)
475 if (stream && LOCAL) { /* only if a file is open */
476 int silent = stream->silent;
477 stream->silent = T; /* note this stream is dying */
478 if (options & CL_EXPUNGE) tenex_expunge (stream,NIL,NIL);
479 stream->silent = silent; /* restore previous status */
480 flock (LOCAL->fd,LOCK_UN); /* unlock local file */
481 close (LOCAL->fd); /* close the local file */
482 /* free local text buffer */
483 if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
484 if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
485 /* nuke the local data */
486 fs_give ((void **) &stream->local);
487 stream->dtb = NIL; /* log out the DTB */
491 /* Tenex mail fetch fast data
492 * Accepts: MAIL stream
493 * sequence
494 * option flags
497 void tenex_fast (MAILSTREAM *stream,char *sequence,long flags)
499 STRING bs;
500 MESSAGECACHE *elt;
501 unsigned long i;
502 if (stream && LOCAL &&
503 ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
504 mail_sequence (stream,sequence)))
505 for (i = 1; i <= stream->nmsgs; i++)
506 if ((elt = mail_elt (stream,i))->sequence) {
507 if (!elt->rfc822_size) { /* have header size yet? */
508 lseek (LOCAL->fd,elt->private.special.offset +
509 elt->private.special.text.size,L_SET);
510 /* resize bigbuf if necessary */
511 if (LOCAL->buflen < elt->private.msg.full.text.size) {
512 fs_give ((void **) &LOCAL->buf);
513 LOCAL->buflen = elt->private.msg.full.text.size;
514 LOCAL->buf = (char *) fs_get (LOCAL->buflen + 1);
516 /* tie off string */
517 LOCAL->buf[elt->private.msg.full.text.size] = '\0';
518 /* read in the message */
519 read (LOCAL->fd,LOCAL->buf,elt->private.msg.full.text.size);
520 INIT (&bs,mail_string,(void *) LOCAL->buf,
521 elt->private.msg.full.text.size);
522 /* calculate its CRLF size */
523 elt->rfc822_size = strcrlflen (&bs);
525 tenex_elt (stream,i); /* get current flags from file */
530 /* Tenex mail fetch flags
531 * Accepts: MAIL stream
532 * sequence
533 * option flags
534 * Sniffs at file to get flags
537 void tenex_flags (MAILSTREAM *stream,char *sequence,long flags)
539 unsigned long i;
540 if (stream && LOCAL &&
541 ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
542 mail_sequence (stream,sequence)))
543 for (i = 1; i <= stream->nmsgs; i++)
544 if (mail_elt (stream,i)->sequence) tenex_elt (stream,i);
547 /* TENEX mail fetch message header
548 * Accepts: MAIL stream
549 * message # to fetch
550 * pointer to returned header text length
551 * option flags
552 * Returns: message header in RFC822 format
555 char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
556 unsigned long *length,long flags)
558 char *s;
559 unsigned long i;
560 *length = 0; /* default to empty */
561 if (flags & FT_UID) return "";/* UID call "impossible" */
562 /* get to header position */
563 lseek (LOCAL->fd,tenex_hdrpos (stream,msgno,&i),L_SET);
564 if (flags & FT_INTERNAL) {
565 if (i > LOCAL->buflen) { /* resize if not enough space */
566 fs_give ((void **) &LOCAL->buf);
567 LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
569 /* slurp the data */
570 read (LOCAL->fd,LOCAL->buf,*length = i);
572 else {
573 s = (char *) fs_get (i + 1);/* get readin buffer */
574 s[i] = '\0'; /* tie off string */
575 read (LOCAL->fd,s,i); /* slurp the data */
576 /* make CRLF copy of string */
577 *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,i);
578 fs_give ((void **) &s); /* free readin buffer */
580 return (char *) LOCAL->buf;
583 /* TENEX mail fetch message text (body only)
584 * Accepts: MAIL stream
585 * message # to fetch
586 * pointer to returned stringstruct
587 * option flags
588 * Returns: T, always
591 long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
593 char *s;
594 unsigned long i,j;
595 MESSAGECACHE *elt;
596 /* UID call "impossible" */
597 if (flags & FT_UID) return NIL;
598 /* get message status */
599 elt = tenex_elt (stream,msgno);
600 /* if message not seen */
601 if (!(flags & FT_PEEK) && !elt->seen) {
602 elt->seen = T; /* mark message as seen */
603 /* recalculate status */
604 tenex_update_status (stream,msgno,T);
605 MM_FLAGS (stream,msgno);
607 if (flags & FT_INTERNAL) { /* if internal representation wanted */
608 /* find header position */
609 i = tenex_hdrpos (stream,msgno,&j);
610 if (i > LOCAL->buflen) { /* resize if not enough space */
611 fs_give ((void **) &LOCAL->buf);
612 LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
614 /* go to text position */
615 lseek (LOCAL->fd,i + j,L_SET);
616 /* slurp the data */
617 read (LOCAL->fd,LOCAL->buf,i);
618 /* set up stringstruct for internal */
619 INIT (bs,mail_string,LOCAL->buf,i);
621 else { /* normal form, previous text cached? */
622 if (elt->private.uid == LOCAL->uid)
623 i = elt->private.msg.text.text.size;
624 else { /* not cached, cache it now */
625 LOCAL->uid = elt->private.uid;
626 /* find header position */
627 i = tenex_hdrpos (stream,msgno,&j);
628 /* go to text position */
629 lseek (LOCAL->fd,i + j,L_SET);
630 s = (char *) fs_get ((i = tenex_size (stream,msgno) - j) + 1);
631 s[i] = '\0'; /* tie off string */
632 read (LOCAL->fd,s,i); /* slurp the data */
633 /* make CRLF copy of string */
634 i = elt->private.msg.text.text.size =
635 strcrlfcpy (&LOCAL->text.data,&LOCAL->text.size,s,i);
636 fs_give ((void **) &s); /* free readin buffer */
638 /* set up stringstruct */
639 INIT (bs,mail_string,LOCAL->text.data,i);
641 return T; /* success */
644 /* Tenex mail modify flags
645 * Accepts: MAIL stream
646 * sequence
647 * flag(s)
648 * option flags
651 void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
653 time_t tp[2];
654 struct stat sbuf;
655 if (!stream->rdonly) { /* make sure the update takes */
656 fsync (LOCAL->fd);
657 fstat (LOCAL->fd,&sbuf); /* get current write time */
658 tp[1] = LOCAL->filetime = sbuf.st_mtime;
659 tp[0] = time (0); /* make sure read comes after all that */
660 utime (stream->mailbox,tp);
665 /* Tenex mail per-message modify flags
666 * Accepts: MAIL stream
667 * message cache element
670 void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
672 struct stat sbuf;
673 /* maybe need to do a checkpoint? */
674 if (LOCAL->filetime && !LOCAL->shouldcheck) {
675 fstat (LOCAL->fd,&sbuf); /* get current write time */
676 if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
677 LOCAL->filetime = 0; /* don't do this test for any other messages */
679 /* recalculate status */
680 tenex_update_status (stream,elt->msgno,NIL);
683 /* Tenex mail ping mailbox
684 * Accepts: MAIL stream
685 * Returns: T if stream still alive, NIL if not
688 long tenex_ping (MAILSTREAM *stream)
690 unsigned long i = 1;
691 long r = T;
692 int ld;
693 char lock[MAILTMPLEN];
694 struct stat sbuf;
695 if (stream && LOCAL) { /* only if stream already open */
696 fstat (LOCAL->fd,&sbuf); /* get current file poop */
697 if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
698 (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
699 /* check for changed message status */
700 if (LOCAL->mustcheck || LOCAL->shouldcheck) {
701 LOCAL->filetime = sbuf.st_mtime;
702 if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
703 MM_NOTIFY (stream,"[CHECK] Checking for flag updates",NIL);
704 while (i <= stream->nmsgs) tenex_elt (stream,i++);
705 LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
707 /* get shared parse/append permission */
708 if ((sbuf.st_size != LOCAL->filesize) &&
709 ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
710 /* parse resulting mailbox */
711 r = (tenex_parse (stream)) ? T : NIL;
712 unlockfd (ld,lock); /* release shared parse/append permission */
714 if (LOCAL) { /* stream must still be alive */
715 /* snarf if this is a read-write inbox */
716 if (stream->inbox && !stream->rdonly) {
717 tenex_snarf (stream);
718 fstat (LOCAL->fd,&sbuf);/* see if file changed now */
719 if ((sbuf.st_size != LOCAL->filesize) &&
720 ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
721 /* parse resulting mailbox */
722 r = (tenex_parse (stream)) ? T : NIL;
723 unlockfd (ld,lock); /* release shared parse/append permission */
728 return r; /* return result of the parse */
732 /* Tenex mail check mailbox (reparses status too)
733 * Accepts: MAIL stream
736 void tenex_check (MAILSTREAM *stream)
738 /* mark that a check is desired */
739 if (LOCAL) LOCAL->mustcheck = T;
740 if (tenex_ping (stream)) MM_LOG ("Check completed",(long) NIL);
743 /* Tenex mail snarf messages from system inbox
744 * Accepts: MAIL stream
747 void tenex_snarf (MAILSTREAM *stream)
749 unsigned long i = 0;
750 unsigned long j,r,hdrlen,txtlen;
751 struct stat sbuf;
752 char *hdr,*txt,lock[MAILTMPLEN],tmp[MAILTMPLEN];
753 MESSAGECACHE *elt;
754 MAILSTREAM *sysibx = NIL;
755 int ld;
756 /* give up if can't get exclusive permission */
757 if ((time (0) >= (LOCAL->lastsnarf +
758 (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
759 strcmp (sysinbox (),stream->mailbox) &&
760 ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) {
761 MM_CRITICAL (stream); /* go critical */
762 /* sizes match and anything in sysinbox? */
763 if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
764 !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) &&
765 (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
766 (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
767 /* yes, go to end of file in our mailbox */
768 lseek (LOCAL->fd,sbuf.st_size,L_SET);
769 /* for each message in sysibx mailbox */
770 while (r && (++i <= sysibx->nmsgs)) {
771 /* snarf message from system INBOX */
772 hdr = cpystr (mail_fetchheader_full(sysibx,i,NIL,&hdrlen,FT_INTERNAL));
773 txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_INTERNAL|FT_PEEK);
774 /* if have a message */
775 if ((j = hdrlen + txtlen) != 0L) {
776 /* calculate header line */
777 mail_date (LOCAL->buf,elt = mail_elt (sysibx,i));
778 sprintf (LOCAL->buf + strlen (LOCAL->buf),
779 ",%lu;0000000000%02o\n",j,(unsigned)
780 ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
781 (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
782 (fDRAFT * elt->draft)));
783 /* copy message */
784 if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) ||
785 (write (LOCAL->fd,hdr,hdrlen) < 0) ||
786 (write (LOCAL->fd,txt,txtlen) < 0)) r = 0;
788 fs_give ((void **) &hdr);
791 /* make sure all the updates take */
792 if (fsync (LOCAL->fd)) r = 0;
793 if (r) { /* delete all the messages we copied */
794 if (r == 1) strcpy (tmp,"1");
795 else sprintf (tmp,"1:%lu",r);
796 mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
797 mail_expunge (sysibx); /* now expunge all those messages */
799 else {
800 sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno));
801 MM_LOG (LOCAL->buf,WARN);
802 ftruncate (LOCAL->fd,sbuf.st_size);
804 fstat (LOCAL->fd,&sbuf); /* yes, get current file size */
805 LOCAL->filetime = sbuf.st_mtime;
807 if (sysibx) mail_close (sysibx);
808 MM_NOCRITICAL (stream); /* release critical */
809 unlockfd (ld,lock); /* release exclusive parse/append permission */
810 LOCAL->lastsnarf = time (0);/* note time of last snarf */
814 /* Tenex mail expunge mailbox
815 * Accepts: MAIL stream
816 * sequence to expunge if non-NIL
817 * expunge options
818 * Returns: T, always
821 long tenex_expunge (MAILSTREAM *stream,char *sequence,long options)
823 long ret;
824 time_t tp[2];
825 struct stat sbuf;
826 off_t pos = 0;
827 int ld;
828 unsigned long i = 1;
829 unsigned long j,k,m,recent;
830 unsigned long n = 0;
831 unsigned long delta = 0;
832 char lock[MAILTMPLEN];
833 MESSAGECACHE *elt;
834 blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
835 if (!(ret = (sequence ? ((options & EX_UID) ?
836 mail_uid_sequence (stream,sequence) :
837 mail_sequence (stream,sequence)) : LONGT) &&
838 tenex_ping (stream))); /* parse sequence if given, ping stream */
839 else if (stream->rdonly) MM_LOG ("Expunge ignored on readonly mailbox",WARN);
840 else {
841 if (LOCAL->filetime && !LOCAL->shouldcheck) {
842 fstat (LOCAL->fd,&sbuf); /* get current write time */
843 if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
845 /* The cretins who designed flock() created a window of vulnerability in
846 * upgrading locks from shared to exclusive or downgrading from exclusive
847 * to shared. Rather than maintain the lock at shared status at a minimum,
848 * flock() actually *releases* the former lock. Obviously they never talked
849 * to any database guys. Fortunately, we have the parse/append permission
850 * lock. If we require this lock before going exclusive on the mailbox,
851 * another process can not sneak in and steal the exclusive mailbox lock on
852 * us, because it will block on trying to get parse/append permission first.
854 /* get exclusive parse/append permission */
855 if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0)
856 MM_LOG ("Unable to lock expunge mailbox",ERROR);
857 /* make sure see any newly-arrived messages */
858 else if (!tenex_parse (stream));
859 /* get exclusive access */
860 else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
861 (*bn) (BLOCK_FILELOCK,NIL);
862 flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
863 (*bn) (BLOCK_NONE,NIL);
864 MM_LOG ("Can't expunge because mailbox is in use by another process",
865 ERROR);
866 unlockfd (ld,lock); /* release exclusive parse/append permission */
869 else {
870 MM_CRITICAL (stream); /* go critical */
871 recent = stream->recent; /* get recent now that pinged and locked */
872 /* for each message */
873 while (i <= stream->nmsgs) {
874 /* get cache element */
875 elt = tenex_elt (stream,i);
876 /* number of bytes to smash or preserve */
877 k = elt->private.special.text.size + tenex_size (stream,i);
878 /* if need to expunge this message */
879 if (elt->deleted && (sequence ? elt->sequence : T)) {
880 /* if recent, note one less recent message */
881 if (elt->recent) --recent;
882 delta += k; /* number of bytes to delete */
883 /* notify upper levels */
884 mail_expunged (stream,i);
885 n++; /* count up one more expunged message */
887 else if (i++ && delta) {/* preserved message */
888 /* first byte to preserve */
889 j = elt->private.special.offset;
890 do { /* read from source position */
891 m = min (k,LOCAL->buflen);
892 lseek (LOCAL->fd,j,L_SET);
893 read (LOCAL->fd,LOCAL->buf,m);
894 pos = j - delta; /* write to destination position */
895 lseek (LOCAL->fd,pos,L_SET);
896 while (T) {
897 lseek (LOCAL->fd,pos,L_SET);
898 if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
899 MM_NOTIFY (stream,strerror (errno),WARN);
900 MM_DISKERROR (stream,errno,T);
902 pos += m; /* new position */
903 j += m; /* next chunk, perhaps */
904 } while (k -= m); /* until done */
905 /* note the new address of this text */
906 elt->private.special.offset -= delta;
908 /* preserved but no deleted messages */
909 else pos = elt->private.special.offset + k;
912 if (n) { /* truncate file after last message */
913 if (pos != (LOCAL->filesize -= delta)) {
914 sprintf (LOCAL->buf,
915 "Calculated size mismatch %lu != %lu, delta = %lu",
916 (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
917 MM_LOG (LOCAL->buf,WARN);
918 LOCAL->filesize = pos;/* fix it then */
920 ftruncate (LOCAL->fd,LOCAL->filesize);
921 sprintf (LOCAL->buf,"Expunged %lu messages",n);
922 /* output the news */
923 MM_LOG (LOCAL->buf,(long) NIL);
925 else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
926 fsync (LOCAL->fd); /* force disk update */
927 fstat (LOCAL->fd,&sbuf); /* get new write time */
928 tp[1] = LOCAL->filetime = sbuf.st_mtime;
929 tp[0] = time (0); /* reset atime to now */
930 utime (stream->mailbox,tp);
931 MM_NOCRITICAL (stream); /* release critical */
932 /* notify upper level of new mailbox size */
933 mail_exists (stream,stream->nmsgs);
934 mail_recent (stream,recent);
935 (*bn) (BLOCK_FILELOCK,NIL);
936 flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
937 (*bn) (BLOCK_NONE,NIL);
938 unlockfd (ld,lock); /* release exclusive parse/append permission */
941 return LONGT;
944 /* Tenex mail copy message(s)
945 * Accepts: MAIL stream
946 * sequence
947 * destination mailbox
948 * copy options
949 * Returns: T if success, NIL if failed
952 long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
954 struct stat sbuf;
955 time_t tp[2];
956 MESSAGECACHE *elt;
957 unsigned long i,j,k;
958 long ret = LONGT;
959 int fd,ld;
960 char file[MAILTMPLEN],lock[MAILTMPLEN];
961 mailproxycopy_t pc =
962 (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
963 /* make sure valid mailbox */
964 if (!tenex_isvalid (mailbox,LOCAL->buf)) switch (errno) {
965 case ENOENT: /* no such file? */
966 MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
967 return NIL;
968 case 0: /* merely empty file? */
969 break;
970 case EACCES: /* file protected */
971 sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
972 MM_LOG (LOCAL->buf,ERROR);
973 return NIL;
974 case EINVAL:
975 if (pc) return (*pc) (stream,sequence,mailbox,options);
976 sprintf (LOCAL->buf,"Invalid Tenex-format mailbox name: %.80s",mailbox);
977 MM_LOG (LOCAL->buf,ERROR);
978 return NIL;
979 default:
980 if (pc) return (*pc) (stream,sequence,mailbox,options);
981 sprintf (LOCAL->buf,"Not a Tenex-format mailbox: %.80s",mailbox);
982 MM_LOG (LOCAL->buf,ERROR);
983 return NIL;
985 if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
986 mail_sequence (stream,sequence))) return NIL;
987 /* got file? */
988 if ((fd = open (tenex_file(file,mailbox),O_RDWR,NIL)) < 0) {
989 sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
990 MM_LOG (LOCAL->buf,ERROR);
991 return NIL;
993 MM_CRITICAL (stream); /* go critical */
994 /* get exclusive parse/append permission */
995 if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
996 MM_LOG ("Unable to lock copy mailbox",ERROR);
997 MM_NOCRITICAL (stream);
998 return NIL;
1000 fstat (fd,&sbuf); /* get current file size */
1001 lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
1003 /* for each requested message */
1004 for (i = 1; ret && (i <= stream->nmsgs); i++)
1005 if ((elt = mail_elt (stream,i))->sequence) {
1006 lseek (LOCAL->fd,elt->private.special.offset,L_SET);
1007 /* number of bytes to copy */
1008 k = elt->private.special.text.size + tenex_size (stream,i);
1009 do { /* read from source position */
1010 j = min (k,LOCAL->buflen);
1011 read (LOCAL->fd,LOCAL->buf,j);
1012 if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
1013 } while (ret && (k -= j));/* until done */
1015 /* make sure all the updates take */
1016 if (!(ret && (ret = !fsync (fd)))) {
1017 sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
1018 MM_LOG (LOCAL->buf,ERROR);
1019 ftruncate (fd,sbuf.st_size);
1021 if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
1022 /* else preserve \Marked status */
1023 else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
1024 tp[1] = sbuf.st_mtime; /* preserve mtime */
1025 utime (file,tp); /* set the times */
1026 close (fd); /* close the file */
1027 unlockfd (ld,lock); /* release exclusive parse/append permission */
1028 MM_NOCRITICAL (stream); /* release critical */
1029 /* delete all requested messages */
1030 if (ret && (options & CP_MOVE)) {
1031 for (i = 1; i <= stream->nmsgs; i++)
1032 if ((elt = tenex_elt (stream,i))->sequence) {
1033 elt->deleted = T; /* mark message deleted */
1034 /* recalculate status */
1035 tenex_update_status (stream,i,NIL);
1037 if (!stream->rdonly) { /* make sure the update takes */
1038 fsync (LOCAL->fd);
1039 fstat (LOCAL->fd,&sbuf); /* get current write time */
1040 tp[1] = LOCAL->filetime = sbuf.st_mtime;
1041 tp[0] = time (0); /* make sure atime remains greater */
1042 utime (stream->mailbox,tp);
1045 if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
1046 MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN);
1047 return ret;
1050 /* Tenex mail append message from stringstruct
1051 * Accepts: MAIL stream
1052 * destination mailbox
1053 * append callback
1054 * data for callback
1055 * Returns: T if append successful, else NIL
1058 long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
1060 struct stat sbuf;
1061 int fd,ld,c;
1062 char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
1063 time_t tp[2];
1064 FILE *df;
1065 MESSAGECACHE elt;
1066 long f;
1067 unsigned long i,j,uf,size;
1068 STRING *message;
1069 long ret = LONGT;
1070 /* default stream to prototype */
1071 if (!stream) stream = user_flags (&tenexproto);
1072 /* make sure valid mailbox */
1073 if (!tenex_isvalid (mailbox,tmp)) switch (errno) {
1074 case ENOENT: /* no such file? */
1075 if (!compare_cstring (mailbox,"INBOX")) dummy_create (NIL,"mail.txt");
1076 else {
1077 MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
1078 return NIL;
1080 /* falls through */
1081 case 0: /* merely empty file? */
1082 break;
1083 case EACCES: /* file protected */
1084 sprintf (tmp,"Can't access destination: %.80s",mailbox);
1085 MM_LOG (tmp,ERROR);
1086 return NIL;
1087 case EINVAL:
1088 sprintf (tmp,"Invalid TENEX-format mailbox name: %.80s",mailbox);
1089 MM_LOG (tmp,ERROR);
1090 return NIL;
1091 default:
1092 sprintf (tmp,"Not a TENEX-format mailbox: %.80s",mailbox);
1093 MM_LOG (tmp,ERROR);
1094 return NIL;
1096 /* get first message */
1097 if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL;
1099 /* open destination mailbox */
1100 if (((fd = open (tenex_file (file,mailbox),O_WRONLY|O_APPEND,NIL)) < 0) ||
1101 !(df = fdopen (fd,"ab"))) {
1102 sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
1103 MM_LOG (tmp,ERROR);
1104 return NIL;
1106 /* get parse/append permission */
1107 if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
1108 MM_LOG ("Unable to lock append mailbox",ERROR);
1109 close (fd);
1110 return NIL;
1112 MM_CRITICAL (stream); /* go critical */
1113 fstat (fd,&sbuf); /* get current file size */
1114 errno = 0;
1115 do { /* parse flags */
1116 if (!SIZE (message)) { /* guard against zero-length */
1117 MM_LOG ("Append of zero-length message",ERROR);
1118 ret = NIL;
1119 break;
1121 f = mail_parse_flags (stream,flags,&i);
1122 /* reverse bits (dontcha wish we had CIRC?) */
1123 for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
1124 if (date) { /* parse date if given */
1125 if (!mail_parse_date (&elt,date)) {
1126 sprintf (tmp,"Bad date in append: %.80s",date);
1127 MM_LOG (tmp,ERROR);
1128 ret = NIL; /* mark failure */
1129 break;
1131 mail_date (tmp,&elt); /* write preseved date */
1133 else internal_date (tmp); /* get current date in IMAP format */
1134 i = GETPOS (message); /* remember current position */
1135 for (j = SIZE (message), size = 0; j; --j)
1136 if (SNX (message) != '\015') ++size;
1137 SETPOS (message,i); /* restore position */
1138 /* write header */
1139 if (fprintf (df,"%s,%lu;%010lo%02lo\n",tmp,size,uf,(unsigned long) f) < 0)
1140 ret = NIL;
1141 else { /* write message */
1142 while (size) if ((c = 0xff & SNX (message)) != '\015') {
1143 if (putc (c,df) != EOF) --size;
1144 else break;
1146 /* get next message */
1147 if (size || !MM_APPEND (af) (stream,data,&flags,&date,&message))
1148 ret = NIL;
1150 } while (ret && message);
1151 /* if error... */
1152 if (!ret || (fflush (df) == EOF)) {
1153 ftruncate (fd,sbuf.st_size);/* revert file */
1154 close (fd); /* make sure fclose() doesn't corrupt us */
1155 if (errno) {
1156 sprintf (tmp,"Message append failed: %s",strerror (errno));
1157 MM_LOG (tmp,ERROR);
1159 ret = NIL;
1161 if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
1162 /* else preserve \Marked status */
1163 else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
1164 tp[1] = sbuf.st_mtime; /* preserve mtime */
1165 utime (file,tp); /* set the times */
1166 fclose (df); /* close the file */
1167 unlockfd (ld,lock); /* release exclusive parse/append permission */
1168 MM_NOCRITICAL (stream); /* release critical */
1169 if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
1170 MM_LOG ("Can not return meaningful APPENDUID with this mailbox format",
1171 WARN);
1172 return ret;
1175 /* Internal routines */
1178 /* Tenex mail return internal message size in bytes
1179 * Accepts: MAIL stream
1180 * message #
1181 * Returns: internal size of message
1184 unsigned long tenex_size (MAILSTREAM *stream,unsigned long m)
1186 MESSAGECACHE *elt = mail_elt (stream,m);
1187 return ((m < stream->nmsgs) ? mail_elt (stream,m+1)->private.special.offset :
1188 LOCAL->filesize) -
1189 (elt->private.special.offset + elt->private.special.text.size);
1193 /* Tenex mail generate file string
1194 * Accepts: temporary buffer to write into
1195 * mailbox name string
1196 * Returns: local file string or NIL if failure
1199 char *tenex_file (char *dst,char *name)
1201 char tmp[MAILTMPLEN];
1202 char *s = mailboxfile (dst,name);
1203 /* return our standard inbox */
1204 return (s && !*s) ? mailboxfile (dst,tenex_isvalid ("~/INBOX",tmp) ?
1205 "~/INBOX" : "mail.txt") : s;
1208 /* Tenex mail parse mailbox
1209 * Accepts: MAIL stream
1210 * Returns: T if parse OK
1211 * NIL if failure, stream aborted
1214 long tenex_parse (MAILSTREAM *stream)
1216 struct stat sbuf;
1217 MESSAGECACHE *elt = NIL;
1218 unsigned char c,*s,*t,*x;
1219 char tmp[MAILTMPLEN];
1220 unsigned long i,j;
1221 long curpos = LOCAL->filesize;
1222 long nmsgs = stream->nmsgs;
1223 long recent = stream->recent;
1224 short added = NIL;
1225 short silent = stream->silent;
1226 fstat (LOCAL->fd,&sbuf); /* get status */
1227 if (sbuf.st_size < curpos) { /* sanity check */
1228 sprintf (tmp,"Mailbox shrank from %lu to %lu!",
1229 (unsigned long) curpos,(unsigned long) sbuf.st_size);
1230 MM_LOG (tmp,ERROR);
1231 tenex_close (stream,NIL);
1232 return NIL;
1234 stream->silent = T; /* don't pass up exists events yet */
1235 while (sbuf.st_size - curpos){/* while there is stuff to parse */
1236 /* get to that position in the file */
1237 lseek (LOCAL->fd,curpos,L_SET);
1238 if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
1239 sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
1240 (unsigned long) curpos,(unsigned long) sbuf.st_size,
1241 i ? strerror (errno) : "no data read");
1242 MM_LOG (tmp,ERROR);
1243 tenex_close (stream,NIL);
1244 return NIL;
1246 LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
1247 if (!(s = strchr (LOCAL->buf,'\012'))) {
1248 sprintf (tmp,"Unable to find newline at %lu in %lu bytes, text: %s",
1249 (unsigned long) curpos,i,(char *) LOCAL->buf);
1250 MM_LOG (tmp,ERROR);
1251 tenex_close (stream,NIL);
1252 return NIL;
1254 *s = '\0'; /* tie off header line */
1255 i = (s + 1) - LOCAL->buf; /* note start of text offset */
1256 if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
1257 sprintf (tmp,"Unable to parse internal header at %lu: %s",
1258 (unsigned long) curpos,(char *) LOCAL->buf);
1259 MM_LOG (tmp,ERROR);
1260 tenex_close (stream,NIL);
1261 return NIL;
1263 *s++ = '\0'; *t++ = '\0'; /* tie off fields */
1265 added = T; /* note that a new message was added */
1266 /* swell the cache */
1267 mail_exists (stream,++nmsgs);
1268 /* instantiate an elt for this message */
1269 (elt = mail_elt (stream,nmsgs))->valid = T;
1270 elt->private.uid = ++stream->uid_last;
1271 /* note file offset of header */
1272 elt->private.special.offset = curpos;
1273 /* in case error */
1274 elt->private.special.text.size = 0;
1275 /* header size not known yet */
1276 elt->private.msg.header.text.size = 0;
1277 x = s; /* parse the header components */
1278 if (mail_parse_date (elt,LOCAL->buf) &&
1279 (elt->private.msg.full.text.size = strtoul (s,(char **) &s,10)) &&
1280 (!(s && *s)) && isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
1281 isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
1282 isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
1283 isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
1284 elt->private.special.text.size = i;
1285 else { /* oops */
1286 sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
1287 curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
1288 MM_LOG (tmp,ERROR);
1289 tenex_close (stream,NIL);
1290 return NIL;
1292 /* make sure didn't run off end of file */
1293 if ((curpos += (elt->private.msg.full.text.size + i)) > sbuf.st_size) {
1294 sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
1295 elt->private.special.offset,(unsigned long) curpos,
1296 (unsigned long) sbuf.st_size);
1297 MM_LOG (tmp,ERROR);
1298 tenex_close (stream,NIL);
1299 return NIL;
1301 c = t[10]; /* remember first system flags byte */
1302 t[10] = '\0'; /* tie off flags */
1303 j = strtoul (t,NIL,8); /* get user flags value */
1304 t[10] = c; /* restore first system flags byte */
1305 /* set up all valid user flags (reversed!) */
1306 while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
1307 stream->user_flags[i]) elt->user_flags |= 1 << i;
1308 /* calculate system flags */
1309 if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
1310 if (j & fDELETED) elt->deleted = T;
1311 if (j & fFLAGGED) elt->flagged = T;
1312 if (j & fANSWERED) elt->answered = T;
1313 if (j & fDRAFT) elt->draft = T;
1314 if (!(j & fOLD)) { /* newly arrived message? */
1315 elt->recent = T;
1316 recent++; /* count up a new recent message */
1317 /* mark it as old */
1318 tenex_update_status (stream,nmsgs,NIL);
1321 fsync (LOCAL->fd); /* make sure all the fOLD flags take */
1322 /* update parsed file size and time */
1323 LOCAL->filesize = sbuf.st_size;
1324 fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
1325 LOCAL->filetime = sbuf.st_mtime;
1326 if (added && !stream->rdonly){/* make sure atime updated */
1327 time_t tp[2];
1328 tp[0] = time (0);
1329 tp[1] = LOCAL->filetime;
1330 utime (stream->mailbox,tp);
1332 stream->silent = silent; /* can pass up events now */
1333 mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
1334 mail_recent (stream,recent); /* and of change in recent messages */
1335 return LONGT; /* return the winnage */
1338 /* Tenex get cache element with status updating from file
1339 * Accepts: MAIL stream
1340 * message number
1341 * Returns: cache element
1344 MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno)
1346 MESSAGECACHE *elt = mail_elt (stream,msgno);
1347 struct { /* old flags */
1348 unsigned int seen : 1;
1349 unsigned int deleted : 1;
1350 unsigned int flagged : 1;
1351 unsigned int answered : 1;
1352 unsigned int draft : 1;
1353 unsigned long user_flags;
1354 } old;
1355 old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
1356 old.answered = elt->answered; old.draft = elt->draft;
1357 old.user_flags = elt->user_flags;
1358 tenex_read_flags (stream,elt);
1359 if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
1360 (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
1361 (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
1362 MM_FLAGS (stream,msgno); /* let top level know */
1363 return elt;
1366 /* Tenex read flags from file
1367 * Accepts: MAIL stream
1368 * Returns: cache element
1371 void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
1373 unsigned long i,j;
1374 /* noop if readonly and have valid flags */
1375 if (stream->rdonly && elt->valid) return;
1376 /* set the seek pointer */
1377 lseek (LOCAL->fd,(off_t) elt->private.special.offset +
1378 elt->private.special.text.size - 13,L_SET);
1379 /* read the new flags */
1380 if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
1381 sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
1382 fatal (LOCAL->buf);
1384 /* calculate system flags */
1385 i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
1386 elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
1387 elt->flagged = i & fFLAGGED ? T : NIL;
1388 elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
1389 LOCAL->buf[10] = '\0'; /* tie off flags */
1390 j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
1391 /* set up all valid user flags (reversed!) */
1392 while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
1393 stream->user_flags[i]) elt->user_flags |= 1 << i;
1394 elt->valid = T; /* have valid flags now */
1397 /* Tenex update status string
1398 * Accepts: MAIL stream
1399 * message number
1400 * flag saying whether or not to sync
1403 void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
1405 time_t tp[2];
1406 struct stat sbuf;
1407 MESSAGECACHE *elt = mail_elt (stream,msgno);
1408 unsigned long j,k = 0;
1409 /* readonly */
1410 if (stream->rdonly || !elt->valid) tenex_read_flags (stream,elt);
1411 else { /* readwrite */
1412 j = elt->user_flags; /* get user flags */
1413 /* reverse bits (dontcha wish we had CIRC?) */
1414 while (j) k |= 1 << (29 - find_rightmost_bit (&j));
1415 /* print new flag string */
1416 sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
1417 (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
1418 (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
1419 (fDRAFT * elt->draft)));
1420 /* get to that place in the file */
1421 lseek (LOCAL->fd,(off_t) elt->private.special.offset +
1422 elt->private.special.text.size - 13,L_SET);
1423 /* write new flags */
1424 write (LOCAL->fd,LOCAL->buf,12);
1425 if (syncflag) { /* sync if requested */
1426 fsync (LOCAL->fd);
1427 fstat (LOCAL->fd,&sbuf); /* get new write time */
1428 tp[1] = LOCAL->filetime = sbuf.st_mtime;
1429 tp[0] = time (0); /* make sure read is later */
1430 utime (stream->mailbox,tp);
1435 /* Tenex locate header for a message
1436 * Accepts: MAIL stream
1437 * message number
1438 * pointer to returned header size
1439 * Returns: position of header in file
1442 unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
1443 unsigned long *size)
1445 unsigned long siz;
1446 long i = 0;
1447 char c = '\0';
1448 char *s = NIL;
1449 MESSAGECACHE *elt = tenex_elt (stream,msgno);
1450 unsigned long ret = elt->private.special.offset +
1451 elt->private.special.text.size;
1452 unsigned long msiz = tenex_size (stream,msgno);
1453 /* is header size known? */
1454 if (!(*size = elt->private.msg.header.text.size)) {
1455 lseek (LOCAL->fd,ret,L_SET);/* get to header position */
1456 /* search message for LF LF */
1457 for (siz = 0; siz < msiz; siz++) {
1458 if (--i <= 0) /* read another buffer as necessary */
1459 read (LOCAL->fd,s = LOCAL->buf,i = min (msiz-siz,(long) MAILTMPLEN));
1460 /* two newline sequence? */
1461 if ((c == '\012') && (*s == '\012')) {
1462 /* yes, note for later */
1463 elt->private.msg.header.text.size = (*size = siz + 1);
1465 return ret; /* return to caller */
1467 else c = *s++; /* next character */
1469 /* header consumes entire message */
1470 elt->private.msg.header.text.size = *size = msiz;
1472 return ret;