2 /*----------------------------------------------------------------------
4 T H E C A R M E L M A I L F I L E D R I V E R
6 Author(s): Laurence Lundblade
10 Internet: lgl@cac.washington.edu or laurence@bwc.org
13 Last edited: Aug 31, 1994
15 The Carmel2 mail file stores messages in individual files and
16 implements folders or mailboxes with index files that contain
17 references to the files a nd a full c-client envelope in an easily
18 parsed form. It was written as a needed part of the pod mail file
19 driver with hopes that it might be useful otherwise some day. It has
20 only been run with the carmel driver.
22 Advantages over Berkeley format and driver:
23 + Opening mail folder is very fast
25 + Check point is very fast
26 + Memory usage is much lower
27 + Search of message headers is fast
29 - Fetching a large message is slow
30 - Searching the message bodies is slow
31 - Sorting the mailbox is slow
36 The first line of the file is always:
37 "\254\312--CARMEL2-MAIL-FILE-INDEX--\n"
39 It is followed by index entries which are of the format:
40 ---START-OF-MESSAGE---\007\001nnnnnnnnnn
41 Ufrads______________________________
43 D..... <Many fields here, labeled by the first letter in the line>
44 <Fields may be repeated>
47 The index is almost an ASCII file. With the first version of this
48 driver it is not advisable to edit this file, lest the byte counts get
49 disrupted. In the future it will be editable. The file starts with
50 two binary bytes so the file(1) utility will report it as "data" to
51 discourage would be hackers from messing with it. The ^G is also in
52 each index entry for the same reason. If you are reading this source you
53 probably know enough to edit the index file without destroying it.
54 Other parts of the format are designed for the easiest possible
55 parsing. The idea was to have a file format that was reasonable to
56 fiddle for debugging, to discourage inexperienced folks for fiddling
57 it and to be efficient for use by the program.
60 Routines and data structures
61 ----------------------------
63 C-CLIENT INTERFACE FUNCTIONS
64 carmel2_valid - check to see if a mailbox is valid for carmel2 mail files
65 carmel2_isvalid - actual work of checking
66 carmel2_find - generate list of carmel2 mailboxes
67 carmel2_sift_files - select mailboxes out of list, used with scandir
68 carmel2_find_bboars - dummy routine, doesn't do anything
70 carmel2_open - initial phase of opening a mailbox
71 carmel2_open2 - real work of opening mailbox, shared with carmel driver
72 carmel2_close - close a mail stream
74 carmel2_fetchfast - fetch "fast" message info, noop for this driver
75 carmel2_fetchflags - fetch the flags, a noop for this driver
76 carmel2_fetchstructure - fetch and envelope and possibly body
78 carmel2_fetchheader - fetch the text header of the message
79 carmel2_fetchtext - fetch the text of the message (no header included)
80 carmel2_fetchbody - fetch the text of a body part of a message
82 carmel2_setflag - Set a flag for a message sequence
83 carmel2_clearflag - Clear a flag for a message sequence
85 carmel2_search - Invoke the search facilities
87 carmel2_ping - Check for new mail and see if still alive
88 carmel2_check - Checkpoint the message statuses to the disk
89 carmel2_expunge - Delete all the messages marked for delete
91 carmel2_copy - Copy a message to another mailbox
92 carmel2_move - Move a message to another mailbox
94 carmel_gc - Garbage collection, a noop for this driver
95 carmel_cache - The required c-client cache handler, doesn't do much
97 carmel2_append - Append a message to a mail folder
100 carmel_bodystruct - Fetch the body structure for a carmel message
101 carmel_parse_address - Parse the address out of a carmel index
102 carmel_parse_addr_field - Parse individual address field out of carmel index
103 carmel2_write_index - Write an entry into a carmel index
104 carmel2_index_address - Write an address into a carmel index
106 carmel_readmsg - Read a message file into memory, header text or both
107 carmel_spool_mail - Get new mail out of spoold mail file
108 carmel_copy_msg_file - Make copy of message file when copying (unused)
110 carmel2_lock - Lock a carmel index for read or write
111 carmel2_unlock - Unlock a carmel index
112 carmel2_update_lock - Touch lock mod time, marking it as active
113 carmel2_bezerk_lock - Lock the spool mail file Berkeley style
114 carmel2_bezerk_unlock - Unlock the spool mail file
116 carmel2_check_dir - Check that directory exists and is writeable
117 carmel2_new_data_file - Get file number for new message file
118 carmel2_calc_paths - Calculate path names for carmel driver
120 carmel_parse_bezerk_status - Parse the "Status: OR" field in mail headers
121 carmel2_getflags - Turn the named flags into a bit mask
122 carmel_new_mc - Get pointer to new MESSAGECACHE, allocating if needed
124 carmel_append2 - The real work of append a message to a mailbox
126 carmel2_search-* - A bunch of search support routines
128 month_abbrev2 - Returns three letter month abbreviation given a name
129 carmel2_rfc822_date - Parse a date string, into MESSAGECACHE structure
130 carmel2_date2string - Generate a date string from MESSAGECACHE
131 carmel2_parse_date - Parse date out of a carmel index
132 next_num - Called to parse dates
134 strucmp2 - Case insensitive strcmp()
135 struncmp2 - Case insensitive strncmp()
137 ----------------------------------------------------------------------*/
144 extern int errno
; /* just in case */
148 #include <sys/file.h>
149 #include <sys/stat.h>
150 #include <sys/time.h>
156 char *strindex2(), *strchr();
159 static int carmel2_sift_files(struct direct
*);
160 static BODY
*carmel2_bodystruct(MAILSTREAM
*, MESSAGECACHE
*);
161 static ADDRESS
*carmel2_parse_address(char *, ADDRESS
*, char *);
162 static char *carmel2_parse_addr_field(char **);
163 static int carmel2_index_address(ADDRESS
*, int , FILE *);
164 static int carmel2_parse_mail(MAILSTREAM
*, long);
165 void carmel2_spool_mail(MAILSTREAM
*, char *, char *, int);
166 static int carmel2_copy_msg_file(MAILSTREAM
*, int, char *);
167 static int carmel2_bezerk_lock(char *, char *);
168 static void carmel2_bezerk_unlock(int, char *);
169 static int carmel2_new_data_file(CARMEL2LOCAL
*, char *);
170 static char *carmel2_calc_paths(int, char *, int);
171 static short carmel2_getflags(MAILSTREAM
*, char *);
172 static MESSAGECACHE
*carmel2_new_mc(MAILSTREAM
*, int);
173 static char carmel2_search_all(MAILSTREAM
*, long, char *, long);
174 static char carmel2_search_answered(MAILSTREAM
*, long, char *, long);
175 static char carmel2_search_deleted(MAILSTREAM
*, long, char *, long);
176 static char carmel2_search_flagged(MAILSTREAM
*, long, char *, long);
177 static char carmel2_search_keyword(MAILSTREAM
*, long, char *, long);
178 static char carmel2_search_new(MAILSTREAM
*, long, char *, long);
179 static char carmel2_search_old(MAILSTREAM
*, long, char *, long);
180 static char carmel2_search_recent(MAILSTREAM
*, long, char *, long);
181 static char carmel2_search_seen(MAILSTREAM
*, long, char *, long);
182 static char carmel2_search_unanswered(MAILSTREAM
*, long, char *, long);
183 static char carmel2_search_undeleted(MAILSTREAM
*, long, char *, long);
184 static char carmel2_search_unflagged(MAILSTREAM
*, long, char *, long);
185 static char carmel2_search_unkeyword(MAILSTREAM
*, long, char *, long);
186 static char carmel2_search_unseen(MAILSTREAM
*, long, char *, long);
187 static char carmel2_search_before(MAILSTREAM
*, long, char *, long);
188 static char carmel2_search_on(MAILSTREAM
*, long, char *, long);
189 static char carmel2_search_since(MAILSTREAM
*, long, char *, long);
190 static unsigned long carmel2_msgdate(MAILSTREAM
*, long);
191 static char carmel2_search_body(MAILSTREAM
*, long, char *, long);
192 static char carmel2_search_subject(MAILSTREAM
*, long, char *, long);
193 static char carmel2_search_text(MAILSTREAM
*, long, char *, long);
194 static char carmel2_search_bcc(MAILSTREAM
*, long, char *, long);
195 static char carmel2_search_cc(MAILSTREAM
*, long, char *, long);
196 static char carmel2_search_from(MAILSTREAM
*, long, char *, long);
197 static char carmel2_search_to(MAILSTREAM
*, long, char *, long);
198 typedef char (*search_t
) ();
199 static search_t
carmel2_search_date(search_t
, long *);
200 static search_t
carmel2_search_flag(search_t
, char **);
201 static search_t
carmel2_search_string(search_t
, char **, long *);
202 static void carmel2_date2string(char *, MESSAGECACHE
*);
203 static void carmel2_parse_date(MESSAGECACHE
*, char *);
204 static int next_num(char **);
206 static int carmel2_sift_files();
207 static BODY
*carmel2_bodystruct();
208 static ADDRESS
*carmel2_parse_address();
209 static char *carmel2_parse_addr_field();
210 static int carmel2_index_address();
211 static int carmel2_parse_mail();
212 void carmel2_spool_mail();
213 static int carmel2_copy_msg_file();
214 static int carmel2_bezerk_lock();
215 static void carmel2_bezerk_unlock();
216 static int carmel2_new_data_file();
217 static char *carmel2_calc_paths();
218 static short carmel2_getflags();
219 static MESSAGECACHE
*carmel2_new_mc();
220 static char carmel2_search_all();
221 static char carmel2_search_answered();
222 static char carmel2_search_deleted();
223 static char carmel2_search_flagged();
224 static char carmel2_search_keyword();
225 static char carmel2_search_new();
226 static char carmel2_search_old();
227 static char carmel2_search_recent();
228 static char carmel2_search_seen();
229 static char carmel2_search_unanswered();
230 static char carmel2_search_undeleted();
231 static char carmel2_search_unflagged();
232 static char carmel2_search_unkeyword();
233 static char carmel2_search_unseen();
234 static char carmel2_search_before();
235 static char carmel2_search_on();
236 static char carmel2_search_since();
237 static unsigned long carmel2_msgdate();
238 static char carmel2_search_body();
239 static char carmel2_search_subject();
240 static char carmel2_search_text();
241 static char carmel2_search_bcc();
242 static char carmel2_search_cc();
243 static char carmel2_search_from();
244 static char carmel2_search_to();
245 typedef char (*search_t
) ();
246 static search_t
carmel2_search_date();
247 static search_t
carmel2_search_flag();
248 static search_t
carmel2_search_string();
249 static void carmel2_date2string();
250 static void carmel2_parse_date();
251 static int next_num();
255 /*------ Driver dispatch used by MAIL -------*/
257 DRIVER carmel2driver
= {
259 (DRIVER
*) NIL
, /* next driver */
260 carmel2_valid
, /* mailbox is valid for us */
261 carmel2_parameters
, /* manipulate parameters */
262 carmel2_find
, /* find mailboxes */
263 carmel2_find_bboards
, /* find bboards */
264 carmel2_find_all
, /* find all mailboxes */
265 carmel2_find_bboards
, /* find all bboards ; just a noop here */
266 carmel2_subscribe
, /* subscribe to mailbox */
267 carmel2_unsubscribe
, /* unsubscribe from mailbox */
268 carmel2_subscribe_bboard
, /* subscribe to bboard */
269 carmel2_unsubscribe_bboard
, /* unsubscribe from bboard */
273 carmel2_open
, /* open mailbox */
274 carmel2_close
, /* close mailbox */
275 carmel2_fetchfast
, /* fetch message "fast" attributes */
276 carmel2_fetchflags
, /* fetch message flags */
277 carmel2_fetchstructure
, /* fetch message envelopes */
278 carmel2_fetchheader
, /* fetch message header only */
279 carmel2_fetchtext
, /* fetch message body only */
280 carmel2_fetchbody
, /* fetch message body section */
281 carmel2_setflag
, /* set message flag */
282 carmel2_clearflag
, /* clear message flag */
283 carmel2_search
, /* search for message based on criteria */
284 carmel2_ping
, /* ping mailbox to see if still alive */
285 carmel2_check
, /* check for new messages */
286 carmel2_expunge
, /* expunge deleted messages */
287 carmel2_copy
, /* copy messages to another mailbox */
288 carmel2_move
, /* move messages to another mailbox */
289 carmel2_append
, /* Append message to a mailbox */
290 carmel2_gc
, /* garbage collect stream */
293 MAILSTREAM carmel2proto
={&carmel2driver
};/*HACK for default_driver in pine.c*/
295 /*-- Some string constants used in carmel2 indexes --*/
296 char *carmel2_s_o_m
= "---START-OF-MESSAGE---";
297 int carmel2_s_o_m_len
= 22;
298 char *carmel2_s_o_f
= "\254\312--CARMEL2-MAIL-FILE-INDEX--\n";
302 /*-- Buffers used for various reasons, also used by carmel driver --*/
303 char carmel_20k_buf
[20000], carmel_path_buf
[CARMEL_PATHBUF_SIZE
],
304 carmel_error_buf
[200];
307 /*----------------------------------------------------------------------
308 Carmel mail validate mailbox
312 Returns: our driver if name is valid, otherwise calls valid in next driver
315 DRIVER
*carmel2_valid (name
)
318 return (carmel2_isvalid (name
) ? &carmel2driver
: NIL
);
323 /*----------------------------------------------------------------------
324 Open the mailbox and see if it's the correct format
326 Args: name -- name of the mailbox, not fully qualified path
328 Returns: 0 if is is not valid, 1 if it is valid
330 The file must be a regular file and start with the string in the
331 variable carmel2_s_o_f. It has a magic number of sorts
334 carmel2_isvalid (name
)
337 char *carmel_index_file
;
340 struct carmel_mb_name
*parsed_name
;
342 /*---- Must match FQN name of carmel mailboxes ----*/
343 parsed_name
= carmel_parse_mb_name(name
, '2');
344 if(parsed_name
== NULL
)
346 carmel_free_mb_name(parsed_name
);
348 carmel_index_file
= carmel2_calc_paths(CalcPathCarmel2Index
, name
, 0);
349 if(carmel_index_file
== NULL
)
350 return(0); /* Will get two error messages here, one from dummy driver */
352 if(stat(carmel_index_file
, &sbuf
) < 0)
353 return(1); /* If the names match and it doesn't exist, it's valid */
355 if(!(sbuf
.st_mode
& S_IFREG
))
358 fd
= open(carmel_index_file
, O_RDONLY
);
362 if(read(fd
, carmel_20k_buf
, 200) <= 0)
367 if(strncmp(carmel_20k_buf
, carmel2_s_o_f
, strlen(carmel2_s_o_f
)))
375 /*----------------------------------------------------------------------
376 Set parameters for the carmel driver.
378 Currently does nothing
388 /*----------------------------------------------------------------------
389 Carmel mail find list of mailboxes
391 Args: stream -- mail stream to find mailboxes for
392 pat -- wildcard pattern to match (currently unused)
394 Returns nothing, the results are passed back by calls to mm_log
400 carmel2_find (stream
, pat
)
404 char tmp
[MAILTMPLEN
];
405 struct direct
**namelist
, **n
;
407 extern int alphasort();
408 struct carmel_mb_name
*parsed_name
;
411 parsed_name
= carmel_parse_mb_name(pat
, '2');
412 if(parsed_name
== NULL
)
415 if(parsed_name
->user
== NULL
) {
416 sprintf(tmp
, "%s/%s", myhomedir(), CARMEL2_INDEX_DIR
);
418 pw
= getpwnam(parsed_name
->user
);
420 sprintf(carmel_error_buf
,
421 "Error accessing mailbox \"%s\". No such user name \"%s\"\n",
422 pat
, parsed_name
->user
);
423 mm_log(carmel_error_buf
, ERROR
);
426 sprintf(tmp
, "%s/%s", pw
->pw_dir
, CARMEL2_INDEX_DIR
);
429 sprintf(tmp
, "%s/%s", myhomedir(), CARMEL2_INDEX_DIR
);
431 num
= scandir(tmp
, &namelist
, carmel2_sift_files
, alphasort
);
434 /* Seems this error message should not be logged
435 sprintf(carmel_error_buf, "Error finding mailboxes \"%s\": %s",
436 pat, strerror(errno));
437 mm_log(carmel_error_buf, ERROR); */
441 for(n
= namelist
; num
> 0; num
--, n
++) {
442 if(parsed_name
->user
== NULL
) {
443 sprintf(tmp
, "%s%s%c%s", CARMEL_NAME_PREFIX
, parsed_name
->version
,
444 CARMEL_NAME_CHAR
, (*n
)->d_name
);
446 sprintf(tmp
, "%s%s%c%s%c%s", CARMEL_NAME_PREFIX
,
447 parsed_name
->version
, CARMEL_NAME_CHAR
,
448 parsed_name
->user
, CARMEL_NAME_CHAR
,
455 carmel_free_mb_name(parsed_name
);
460 /*----------------------------------------------------------------------
461 Find_all is the same as find for the carmel2 format -- no notion
462 of subscribed and unsubscribed
465 carmel2_find_all (stream
, pat
)
469 carmel2_find(stream
, pat
);
474 /*----------------------------------------------------------------------
475 Carmel2 mail find list of bboards; always NULL, no bboards
477 void carmel2_find_bboards (stream
,pat
)
481 /* Always a no-op, Carmel2 file format doesn't do news */
486 /*----------------------------------------------------------------------
487 Carmel2 mail find list of bboards; always NULL, no bboards
489 void carmel2_find_all_bboards (stream
,pat
)
493 /* Always a no-op, Carmel2 file format doesn't do news */
498 /*----------------------------------------------------------------------
499 This is used by scandir to determine which files in the directory
500 are treated as mail files
503 carmel2_sift_files(dir
)
506 if(dir
->d_name
[0] == '.')
514 /*----------------------------------------------------------------------
515 Dummy subscribe/unsubscribe routines.
516 Carmel driver does support this. Maybe it should support the UNIX
519 long carmel2_subscribe() {}
520 long carmel2_unsubscribe() {}
521 long carmel2_subscribe_bboard() {}
522 long carmel2_unsubscribe_bboard() {}
526 /*----------------------------------------------------------------------
529 carmel2_create(stream
, mailbox
)
537 /*----------------------------------------------------------------------
540 carmel2_delete(stream
, mailbox
)
548 /*----------------------------------------------------------------------
551 carmel2_rename(stream
, orig_mailbox
, new_mailbox
)
553 char *orig_mailbox
, *new_mailbox
;
558 /*----------------------------------------------------------------------
559 Open a carmel2 mail folder/stream
561 Args: stream -- stream to by fully opened
563 Returns: it's argument or NULL of the open fails
565 This needs testing and more code, see pod_open for examples
569 carmel2_open (stream
)
572 char tmp
[MAILTMPLEN
];
573 struct hostent
*host_name
;
575 if(!stream
) return &carmel2proto
; /* Must be a OP_PROTOTYPE call */
577 /* close old file if stream being recycled */
579 carmel2_close (stream
); /* dump and save the changes */
580 stream
->dtb
= &carmel2driver
; /* reattach this driver */
581 mail_free_cache (stream
); /* clean up cache */
584 mailcache
= carmel2_cache
;
586 /* Allocate local stream */
587 stream
->local
= fs_get (sizeof (CARMEL2LOCAL
));
589 stream
->readonly
= 1; /* Read-only till more work is done */
591 LOCAL
->msg_buf
= NULL
;
592 LOCAL
->msg_buf_size
= 0;
593 LOCAL
->buffered_file
= NULL
;
594 LOCAL
->msg_buf_text_start
= NULL
;
595 LOCAL
->msg_buf_text_offset
= 0;
596 LOCAL
->stdio_buf
= NULL
;
601 LOCAL
->calc_paths
= carmel2_calc_paths
;
602 LOCAL
->aux_copy
= NULL
;
603 LOCAL
->new_file_on_copy
= 1;
605 if(carmel_open2(stream
,
606 (*(LOCAL
->calc_paths
))(CalcPathCarmel2Index
, stream
->mailbox
,0)) < 0)
609 mail_exists (stream
,stream
->nmsgs
);
610 mail_recent (stream
,stream
->recent
);
617 /*----------------------------------------------------------------------
618 Do the real work of opening a Carmel folder.
620 Args: stream -- The mail stream being opened
621 file -- Path name of the actual Carmel index file
623 Returns: -1 if the open fails
626 This is shared between the Carmel driver and the Pod driver.
628 Here, the status, size and date (fast info) of a message entry
629 is read out of the index file into the MESSAGECACHE structures.
630 To make the reading efficient these items are at the beginning
631 of each entry and there is a byte offset to the next entry.
632 If the byte offset is wrong (as detected by looking for the
633 start of message string) then the index is read line by line
634 until it synchs up. This allows manual editing of the index.
635 However, the first two lines of an index entry cannot be
636 edited because mail_check() writes them in place. If these
637 lines have been edited it is detected here and the folder is
640 carmel_open2(stream
, file
)
644 long file_pos
, recent
;
648 if(stat(file
, &sb
) < 0) {
649 sprintf(carmel_error_buf
, "Can't open mailbox: %s", strerror(errno
));
650 mm_log(carmel_error_buf
, ERROR
);
654 LOCAL
->index_stream
= fopen(file
, stream
->readonly
? "r" : "r+");
655 LOCAL
->stdio_buf
= fs_get(CARMEL2_INDEX_BUF_SIZE
);
656 setbuffer(LOCAL
->index_stream
, LOCAL
->stdio_buf
, CARMEL2_INDEX_BUF_SIZE
);
657 if(LOCAL
->index_stream
== NULL
) {
658 sprintf(carmel_error_buf
, "Can't open mailbox: %s", strerror(errno
));
659 mm_log(carmel_error_buf
, ERROR
);
665 LOCAL
->cache_size
= 0;
666 LOCAL
->mc_blocks
= NULL
;
667 LOCAL
->index_size
= sb
.st_size
;
669 /*---- Read line with magic number, which we already checked ------*/
670 if(fgets(carmel_20k_buf
,sizeof(carmel_20k_buf
),LOCAL
->index_stream
)==NULL
){
671 fclose(LOCAL
->index_stream
);
672 mm_log("Mailbox is corrupt. Open failed", ERROR
);
676 file_pos
= ftell(LOCAL
->index_stream
);
677 if(carmel2_parse_mail(stream
, file_pos
) < 0) {
678 fclose(LOCAL
->index_stream
);
679 mm_log("Mailbox is corrupt. Open failed", ERROR
);
683 /* Bug, this doesn't really do the recent correctly */
685 stream
->recent
= recent
;
691 /*----------------------------------------------------------------------
694 Args: stream -- stream to close
699 carmel2_close (stream
)
702 if (LOCAL
) { /* only if a file is open */
703 carmel2_check (stream
); /* dump final checkpoint */
705 fs_give ((void **) &LOCAL
->msg_buf
);
707 fs_give ((void **) &LOCAL
->stdio_buf
);
708 /* nuke the local data */
709 fs_give ((void **) &stream
->local
);
710 stream
->dtb
= NIL
; /* log out the DTB */
716 /*----------------------------------------------------------------------
717 Carmel mail fetch fast information.
719 This is a no-op because the data is always available as it is read in to
720 the message cache blocks when the folder is opened
723 carmel2_fetchfast (stream
, sequence
)
732 /*----------------------------------------------------------------------
733 Carmel2 mail fetch flags.
735 This is a no-op because the data is always available as it is read in to
736 the message cache blocks when the folder is opened
739 carmel2_fetchflags(stream
, sequence
)
743 return; /* no-op for local mail */
748 /*----------------------------------------------------------------------
749 Carmel mail fetch message structure
750 Args: stream -- stream to get structure for
751 msgno -- Message number to fetch data for
752 body -- Pointer to place to return body structure, may be NULL
754 If the request is the for the same msgno as last time, the saved copy
755 of the envelope and/or body structure is returned.
757 To get the envelope the Carmel index file itself must be read and parsed,
758 but this is fast because it is easy to parse (by design) and the amount of
761 To get the body, the whole message is read into memory and then parsed
762 by routines called from here. Doing this for a large message can be slow,
763 so it is best not to request the body if it is not needed. (body == NULL)
767 carmel2_fetchstructure (stream
, msgno
, body
)
779 if (msgno
!= stream
->msgno
){
780 /* flush old poop if a different message */
781 mail_free_envelope (env
);
784 stream
->msgno
= msgno
;
789 *env
= mail_newenvelope();
791 fseek(LOCAL
->index_stream
, mc
->data1
, 0);
792 fgets(carmel_20k_buf
, sizeof(carmel_20k_buf
), LOCAL
->index_stream
);
793 if(strncmp(carmel_20k_buf
, carmel2_s_o_m
, carmel2_s_o_m_len
))
794 return(NULL
); /* Oh ooo */
796 carmel2_date2string(carmel_20k_buf
, mc
);
797 (*env
)->date
= cpystr(carmel_20k_buf
);
799 while(fgets(carmel_20k_buf
, sizeof(carmel_20k_buf
),
800 LOCAL
->index_stream
) != NULL
&&
801 strncmp(carmel_20k_buf
, carmel2_s_o_m
, carmel2_s_o_m_len
)) {
802 carmel_20k_buf
[strlen(carmel_20k_buf
) - 1] = '\0';
803 switch(*carmel_20k_buf
) {
805 (*env
)->from
= carmel2_parse_address(carmel_20k_buf
,
810 (*env
)->to
= carmel2_parse_address(carmel_20k_buf
,
815 (*env
)->cc
= carmel2_parse_address(carmel_20k_buf
,
820 (*env
)->bcc
= carmel2_parse_address(carmel_20k_buf
,
825 (*env
)->sender
= carmel2_parse_address(carmel_20k_buf
,
830 (*env
)->reply_to
= carmel2_parse_address(carmel_20k_buf
,
835 (*env
)->return_path
=carmel2_parse_address(carmel_20k_buf
,
840 (*env
)->subject
= cpystr(carmel_20k_buf
+1);
843 (*env
)->message_id
= cpystr(carmel_20k_buf
+1);
846 (*env
)->in_reply_to
= cpystr(carmel_20k_buf
+1);
849 (*env
)->newsgroups
= cpystr(carmel_20k_buf
+1);
852 (*env
)->remail
= cpystr(carmel_20k_buf
+1);
862 /* Have to fetch the body structure too */
863 *b
= carmel2_bodystruct(stream
, mc
);
868 return(*env
); /* return the envelope */
874 /*----------------------------------------------------------------------
875 Carmel mail fetch message header
880 Returns: pointer to text of mail header. Returned string is null terminated
881 and consists of internal storage. It will be valid till the next
882 call to mail_fetchheader() or mail_fetchtext(). An empty
883 string is returned if the fetch fails.
887 carmel2_fetchheader (stream
, msgno
)
895 header
= carmel2_readmsg(stream
, 1, 0, mc
->data2
);
897 return(header
== NULL
? "" : header
);
902 /*----------------------------------------------------------------------
903 Carmel mail fetch message text (only)
908 Returns: pointer to text of mail message. Returned string is null terminated
909 and consists of internal storage. It will be valid till the next
910 call to mail_fetchheader() or mail_fetchtext(). An empty
911 string is returned if the fetch fails.
915 carmel2_fetchtext(stream
, msgno
)
924 if (!mc
->seen
) { /* if message not seen before */
925 mc
->seen
= T
; /* mark as seen */
926 LOCAL
->dirty
= T
; /* and that stream is now dirty */
929 m
= carmel2_readmsg(stream
, 0, mc
->rfc822_size
, mc
->data2
);
936 /*----------------------------------------------------------------------
937 Fetch MIME body parts
941 section -- string section number spec. i.e. "1.3.4"
942 len -- pointer to return len in
944 Returns: The contents of the body part, or NULL
948 carmel2_fetchbody (stream
, msgno
, section
, len
)
961 if (carmel2_fetchstructure(stream
, msgno
, &b
) == NULL
|| b
== NULL
)
964 if(section
== NULL
|| *section
== '\0')
967 part_num
= strtol(section
, §ion
, 10);
971 base
= carmel2_fetchtext(stream
, msgno
);
976 do { /* until find desired body part */
977 if (b
->type
== TYPEMULTIPART
) {
978 part
= b
->contents
.part
; /* yes, find desired part */
979 while (--part_num
&& (part
= part
->next
));
981 return (NIL
); /* bad specifier */
983 /* Found part, get ready to go further */
985 if(b
->type
== TYPEMULTIPART
&& *section
== '\0')
986 return(NULL
); /* Ran out of section numbers, needed more */
988 offset
= part
->offset
; /* and its offset */
990 } else if (part_num
!= 1) {
991 return NIL
; /* Not Multipart, better be part 1 */
994 /* need to go down further? */
1000 case TYPEMESSAGE
: /* embedded message, calculate new base */
1001 offset
= b
->contents
.msg
.offset
;
1002 b
= b
->contents
.msg
.body
;
1003 /* got its body, drop into multipart case*/
1005 case TYPEMULTIPART
: /* multipart, get next section */
1006 if ((*section
++ == '.') &&
1007 (part_num
= strtol (section
, §ion
,10)) > 0)
1008 break; /* Found the part */
1010 default: /* bogus subpart specification */
1017 /* lose if body bogus */
1018 if ((!b
) || b
->type
== TYPEMULTIPART
)
1021 if (!mc
->seen
) { /* if message not seen before */
1022 mc
->seen
= T
; /* mark as seen */
1023 LOCAL
->dirty
= T
; /* and that stream is now dirty */
1026 *len
= b
->size
.ibytes
;
1027 return(base
+ offset
);
1032 /*----------------------------------------------------------------------
1033 * Carmel mail set flag
1034 * Accepts: MAIL stream
1040 carmel2_setflag (stream
,sequence
,flag
)
1047 short f
= carmel2_getflags (stream
,flag
);
1048 if (!f
) return; /* no-op if no flags to modify */
1049 /* get sequence and loop on it */
1050 if (mail_sequence (stream
,sequence
))
1051 for (i
= 1; i
<= stream
->nmsgs
; i
++){
1053 if (elt
->sequence
) {
1054 /* set all requested flags */
1055 if (f
&fSEEN
) elt
->seen
= T
;
1056 if (f
&fDELETED
) elt
->deleted
= T
;
1057 if (f
&fFLAGGED
) elt
->flagged
= T
;
1058 if (f
&fANSWERED
) elt
->answered
= T
;
1059 /* Could be more creative about keeping track to see if something
1068 /*----------------------------------------------------------------------
1069 * Carmel mail clear flag
1070 * Accepts: MAIL stream
1076 carmel2_clearflag (stream
,sequence
,flag
)
1082 long i
= stream
->nmsgs
;
1083 short f
= carmel2_getflags (stream
,flag
);
1084 if (!f
) return; /* no-op if no flags to modify */
1085 /* get sequence and loop on it */
1086 if (mail_sequence (stream
,sequence
))
1087 for(i
= 1; i
<= stream
->nmsgs
; i
++) {
1089 if (elt
->sequence
) {
1090 /* clear all requested flags */
1091 if (f
&fSEEN
) elt
->seen
= NIL
;
1092 if (f
&fDELETED
) elt
->deleted
= NIL
;
1093 if (f
&fFLAGGED
) elt
->flagged
= NIL
;
1094 if (f
&fANSWERED
) elt
->answered
= NIL
;
1095 /* clearing either seen or deleted does this */
1096 /* Could be more creative about keeping track to see if something
1098 LOCAL
->dirty
= T
; /* mark stream as dirty */
1105 /* Carmel mail search for messages
1106 * Accepts: MAIL stream
1111 carmel2_search (stream
,criteria
)
1119 /* initially all searched */
1120 for (i
= 1; i
<= stream
->nmsgs
; ++i
) mail_elt (stream
,i
)->searched
= T
;
1121 /* get first criterion */
1122 if (criteria
&& (criteria
= strtok (criteria
," "))) {
1123 /* for each criterion */
1124 for (; criteria
; (criteria
= strtok (NIL
," "))) {
1125 f
= NIL
; d
= NIL
; n
= 0; /* init then scan the criterion */
1126 switch (*ucase (criteria
)) {
1127 case 'A': /* possible ALL, ANSWERED */
1128 if (!strcmp (criteria
+1,"LL")) f
= carmel2_search_all
;
1129 else if (!strcmp (criteria
+1,"NSWERED")) f
= carmel2_search_answered
;
1131 case 'B': /* possible BCC, BEFORE, BODY */
1132 if (!strcmp (criteria
+1,"CC"))
1133 f
= carmel2_search_string (carmel2_search_bcc
,&d
,&n
);
1134 else if (!strcmp (criteria
+1,"EFORE"))
1135 f
= carmel2_search_date (carmel2_search_before
,&n
);
1136 else if (!strcmp (criteria
+1,"ODY"))
1137 f
= carmel2_search_string (carmel2_search_body
,&d
,&n
);
1139 case 'C': /* possible CC */
1140 if (!strcmp (criteria
+1,"C"))
1141 f
= carmel2_search_string (carmel2_search_cc
,&d
,&n
);
1143 case 'D': /* possible DELETED */
1144 if (!strcmp (criteria
+1,"ELETED")) f
= carmel2_search_deleted
;
1146 case 'F': /* possible FLAGGED, FROM */
1147 if (!strcmp (criteria
+1,"LAGGED")) f
= carmel2_search_flagged
;
1148 else if (!strcmp (criteria
+1,"ROM"))
1149 f
= carmel2_search_string (carmel2_search_from
,&d
,&n
);
1151 case 'K': /* possible KEYWORD */
1152 if (!strcmp (criteria
+1,"EYWORD"))
1153 f
= carmel2_search_flag (carmel2_search_keyword
,&d
);
1155 case 'N': /* possible NEW */
1156 if (!strcmp (criteria
+1,"EW")) f
= carmel2_search_new
;
1159 case 'O': /* possible OLD, ON */
1160 if (!strcmp (criteria
+1,"LD")) f
= carmel2_search_old
;
1161 else if (!strcmp (criteria
+1,"N"))
1162 f
= carmel2_search_date (carmel2_search_on
,&n
);
1164 case 'R': /* possible RECENT */
1165 if (!strcmp (criteria
+1,"ECENT")) f
= carmel2_search_recent
;
1167 case 'S': /* possible SEEN, SINCE, SUBJECT */
1168 if (!strcmp (criteria
+1,"EEN")) f
= carmel2_search_seen
;
1169 else if (!strcmp (criteria
+1,"INCE"))
1170 f
= carmel2_search_date (carmel2_search_since
,&n
);
1171 else if (!strcmp (criteria
+1,"UBJECT"))
1172 f
= carmel2_search_string (carmel2_search_subject
,&d
,&n
);
1174 case 'T': /* possible TEXT, TO */
1175 if (!strcmp (criteria
+1,"EXT"))
1176 f
= carmel2_search_string (carmel2_search_text
,&d
,&n
);
1177 else if (!strcmp (criteria
+1,"O"))
1178 f
= carmel2_search_string (carmel2_search_to
,&d
,&n
);
1180 case 'U': /* possible UN* */
1181 if (criteria
[1] == 'N') {
1182 if (!strcmp (criteria
+2,"ANSWERED")) f
= carmel2_search_unanswered
;
1183 else if (!strcmp (criteria
+2,"DELETED")) f
= carmel2_search_undeleted
;
1184 else if (!strcmp (criteria
+2,"FLAGGED")) f
= carmel2_search_unflagged
;
1185 else if (!strcmp (criteria
+2,"KEYWORD"))
1186 f
= carmel2_search_flag (carmel2_search_unkeyword
,&d
);
1187 else if (!strcmp (criteria
+2,"SEEN")) f
= carmel2_search_unseen
;
1190 default: /* we will barf below */
1193 if (!f
) { /* if can't determine any criteria */
1194 sprintf(carmel_error_buf
,"Unknown search criterion: %s",criteria
);
1195 mm_log (carmel_error_buf
,ERROR
);
1198 /* run the search criterion */
1199 for (i
= 1; i
<= stream
->nmsgs
; ++i
)
1200 if (mail_elt (stream
,i
)->searched
&& !(*f
) (stream
,i
,d
,n
))
1201 mail_elt (stream
,i
)->searched
= NIL
;
1203 /* report search results to main program */
1204 for (i
= 1; i
<= stream
->nmsgs
; ++i
)
1205 if (mail_elt (stream
,i
)->searched
) mail_searched (stream
,i
);
1212 /*----------------------------------------------------------------------
1213 * Carmel mail ping mailbox
1214 * Accepts: MAIL stream
1215 * Returns: T if stream alive, else NIL
1219 carmel2_ping (stream
)
1223 char path
[1000], *mailfile
;
1225 char debug_buf
[1000];
1228 if(!stream
->readonly
&&
1229 carmel2_update_lock(stream
->local
, stream
->mailbox
, READ_LOCK
) < 0) {
1230 /* Yuck! Someone messed up our lock file, this stream is hosed */
1231 mm_log("Mailbox updated unexpectedly! Mailbox closed", ERROR
);
1232 stream
->readonly
= 1;
1236 /*--- First check to see if the Carmel index grew ----*/
1237 /* BUG, will this really work on NFS since the file is held open? */
1238 stat((*LOCAL
->calc_paths
)(CalcPathCarmel2Index
, stream
->mailbox
, 0), &sb
);
1239 if(sb
.st_size
> LOCAL
->index_size
) {
1241 mm_log("!!!!! Carmel index grew", NIL
);
1243 /* Pull in the new mail.... */
1244 #ifdef MAY_NEED_FOR_NFS
1245 /*---- First close and reopen the mail file -----*/
1246 fclose(LOCAL
->index_stream
);
1247 LOCAL
->index_stream
= fopen((*LOCAL
->calc_paths
)(CalcPathCarmel2Index
,
1248 stream
->mailbox
, 0),
1249 stream
->readonly
? "r" : "r+");
1250 if(LOCAL
->index_stream
== NULL
) {
1251 mm_log("Mailbox stolen. Mailbox closed", ERROR
);
1252 stream
->readonly
= 1;
1256 if(carmel2_parse_mail(stream
, LOCAL
->index_size
) < 0) {
1257 mm_log("Mailbox corrupted. Mailbox closed", ERROR
);
1258 stream
->readonly
= 1;
1261 LOCAL
->index_size
= sb
.st_size
;
1264 if(sb
.st_size
< LOCAL
->index_size
) {
1265 /* Something bad happened, mail box shrunk on us, shutdown */
1266 stream
->readonly
= 1;
1267 mm_log("Mailbox shrank unexpectedly! Mailbox closed", ERROR
);
1272 sprintf(debug_buf
, "!!!!! Mailbox:\"%s\" pretty_mailbox:\"%s\"",
1273 stream
->mailbox
, carmel_pretty_mailbox(stream
->mailbox
));
1274 mm_log(debug_buf
, NIL
);
1275 sprintf(debug_buf
, "!!!! Readonly:%d, Carmel:%d", stream
->readonly
,
1277 mm_log(debug_buf
, NIL
);
1280 if(!stream
->readonly
&&
1282 strcmp(carmel_pretty_mailbox(stream
->mailbox
), "MAIL") == 0) ||
1283 strucmp2(carmel_pretty_mailbox(stream
->mailbox
), "inbox") == 0)) {
1284 /* If it's the inbox we've got to check the spool file */
1285 mailfile
= getenv("MAIL") == NULL
? MAILFILE
: getenv("MAIL");
1286 sprintf(path
, mailfile
, myhomedir());
1288 sprintf(debug_buf
, "!!!!! Checking spool mail\"%s\"", path
);
1289 mm_log(debug_buf
, NIL
);
1291 if(stat(path
, &sb
) < 0)
1292 return(T
); /* No new mail...*/
1293 if(sb
.st_size
> 0) {
1294 mm_critical(stream
);
1295 if(carmel2_lock(stream
->local
, stream
->mailbox
, WRITE_LOCK
) < 0) {
1296 mm_nocritical(stream
);
1299 mm_log("!!!! Inbox locked, sucking in mail", NIL
);
1300 carmel2_spool_mail(stream
, path
, stream
->mailbox
, 1); /*go get it*/
1301 carmel2_unlock(stream
->local
, stream
->mailbox
, WRITE_LOCK
);
1302 mm_nocritical(stream
);
1303 stat((*LOCAL
->calc_paths
)(CalcPathCarmel2Index
, stream
->mailbox
,0),
1305 LOCAL
->index_size
= sb
.st_size
;
1316 /*----------------------------------------------------------------------
1317 This checks for new mail and checkpoints the mail file
1319 Args -- The stream to check point
1323 carmel2_check(stream
)
1326 (void)carmel2_check2(stream
);
1331 /*----------------------------------------------------------------------
1332 Do actual work of a check on carmel2 index, returning status
1334 Returns: 0 if no checkpoint was done, 1 if it was done.
1336 carmel2_check2(stream
)
1342 if(stream
->readonly
|| LOCAL
== NULL
)
1343 return(0); /* Nothing to do in readonly or closed case */
1345 carmel2_ping(stream
); /* check for new mail (Ping always checks) */
1348 return(0); /* Nothing to do */
1350 mm_critical(stream
);
1351 if(carmel2_lock(stream
->local
, stream
->mailbox
, WRITE_LOCK
) < 0) {
1352 mm_nocritical(stream
);
1353 return(0); /* Couldn't get a write lock, nothing we can do */
1356 for(msgno
= 1; msgno
<= stream
->nmsgs
; msgno
++) {
1358 fseek(LOCAL
->index_stream
, mc
->data1
+ carmel2_s_o_m_len
+ 13, 0);
1359 if(fprintf(LOCAL
->index_stream
,
1360 "U%c%c%c%c%c____________________________\n",
1361 mc
->flagged
? 'F' : 'f',
1362 mc
->recent
? 'R' : 'r',
1363 mc
->answered
? 'A' : 'a',
1364 mc
->deleted
? 'D' : 'd',
1365 mc
->seen
? 'S' : 's') == EOF
) {
1366 /* BUG .. Need some error check here */
1369 fflush(LOCAL
->index_stream
);
1371 carmel2_unlock(stream
->local
, stream
->mailbox
, WRITE_LOCK
);
1372 mm_nocritical(stream
);
1373 mm_log("Check completed", NIL
);
1381 /*----------------------------------------------------------------------
1382 Carmel2 mail expunge mailbox
1384 Args: stream -- mail stream to expunge
1388 void carmel2_expunge (stream
)
1392 long msgno
, new_msgno
;
1394 MESSAGECACHE
*mc
, *new_mc
;
1399 if (stream
->readonly
) {
1401 mm_log ("Expunge ignored on readonly mailbox",NIL
);
1405 mm_critical(stream
);
1406 carmel2_lock(stream
->local
, stream
->mailbox
, WRITE_LOCK
);
1408 strcpy(carmel_path_buf
,
1409 (*LOCAL
->calc_paths
)(CalcPathCarmel2Expunge
, stream
->mailbox
, 0));
1411 new_index
= fopen(carmel_path_buf
, "w");
1412 if(new_index
== NULL
) {
1415 if(fprintf(new_index
, carmel2_s_o_f
) == EOF
)
1419 for(msgno
= 1; msgno
<= stream
->nmsgs
; msgno
++) {
1422 mm_expunged(stream
, new_msgno
);
1426 if(msgno
!= new_msgno
) {
1427 new_mc
= MC(new_msgno
);
1429 new_mc
->msgno
= new_msgno
;
1433 envelope
= carmel2_fetchstructure(stream
, msgno
, NULL
);
1434 if(envelope
== NULL
)
1437 /* get this after envelope to offset is still valid in old file */
1438 mc
->data1
= ftell(new_index
);
1440 if(carmel2_write_index(envelope
, mc
, new_index
) < 0)
1445 index_file
= (*LOCAL
->calc_paths
)(CalcPathCarmel2Index
, stream
->mailbox
, 0);
1447 /*--- Close it to make sure bits are really on disk across NFS. ---*/
1448 if(fclose(new_index
) != EOF
) {
1449 /*--- copy of index was a success, rename it ---*/
1451 link(carmel_path_buf
, index_file
);
1453 /*--- Save the mail index size ----*/
1454 stat(index_file
, &sb
);
1455 LOCAL
->index_size
= sb
.st_size
;
1457 stream
->nmsgs
= new_msgno
- 1;
1458 mm_exists(stream
, stream
->nmsgs
);
1460 fclose(LOCAL
->index_stream
);
1461 LOCAL
->index_stream
= fopen(index_file
, "r+");
1463 unlink(carmel_path_buf
);
1464 carmel2_unlock(stream
->local
, stream
->mailbox
, WRITE_LOCK
);
1465 mm_nocritical(stream
);
1470 if(new_index
!= NULL
)
1472 unlink(carmel_path_buf
);
1473 sprintf(carmel_error_buf
, "Expunge failed: %s", strerror(save_e
));
1474 mm_log(carmel_error_buf
, WARN
);
1475 carmel2_unlock(stream
->local
, stream
->mailbox
, WRITE_LOCK
);
1476 mm_nocritical(stream
);
1482 /*----------------------------------------------------------------------
1483 Carmel2 mail copy message(s)
1485 Args: stream - mail stream
1486 sequence - message sequence
1487 mailbox - destination mailbox, FQN
1489 Returns: T if copy successful, else NIL
1492 carmel2_copy(stream
, sequence
, mailbox
)
1498 MESSAGECACHE
*mc
, new_mc
;
1499 long msgno
, file_no
, file_pos
;
1501 char *file_name
, *line
;
1505 if (!mail_sequence (stream
,sequence
)) /* get sequence to do */
1508 /*--- Get the file open (creating if needed) first ----*/
1509 file_name
= (*LOCAL
->calc_paths
)(CalcPathCarmel2Index
, mailbox
, 0, 0);
1510 if(file_name
== NULL
)
1513 if(stat(file_name
, &sb
) < 0) {
1514 mm_notify (stream
,"[TRYCREATE] Must create mailbox before copy", NIL
);
1518 dest_stream
= fopen(file_name
, "a+");
1519 if(dest_stream
== NULL
) {
1520 sprintf(carmel_error_buf
, "Can't copy message to %s: %s",
1521 mailbox
, strerror(errno
));
1522 mm_log(carmel_error_buf
, ERROR
);
1526 mm_critical(stream
);
1528 if(carmel2_lock(stream
->local
, mailbox
, WRITE_LOCK
) < 0) {
1529 mm_nocritical(stream
);
1530 sprintf(carmel_error_buf
,
1531 "Mailbox %s locked, can't copy messages to it",
1533 mm_log(carmel_error_buf
, ERROR
);
1534 fclose(dest_stream
);
1539 /*--- Get the destination Carmel index open ----*/
1540 file_pos
= ftell(dest_stream
);
1544 for(msgno
= 1; msgno
<= stream
->nmsgs
; msgno
++) {
1551 if(LOCAL
->new_file_on_copy
) {
1552 new_mc
.data2
= carmel2_copy_msg_file(stream
, mc
->data2
, mailbox
);
1553 if((long)new_mc
.data2
< 0) {
1559 e
= carmel2_fetchstructure(stream
, msgno
, NULL
);
1560 if(carmel2_write_index(e
, &new_mc
, dest_stream
) < 0) {
1565 if(LOCAL
->carmel
&& LOCAL
->aux_copy
!= NULL
) {
1566 if((*LOCAL
->aux_copy
)(stream
->local
, mailbox
, e
, &new_mc
)) {
1574 ftruncate(fileno(dest_stream
), file_pos
);
1577 fclose(dest_stream
);
1579 carmel2_unlock(stream
->local
, mailbox
, WRITE_LOCK
);
1581 mm_nocritical(stream
);
1583 return(fail
? NIL
: T
);
1588 /*----------------------------------------------------------------------
1589 Carmel2 mail move message(s)
1592 Returns: T if move successful, else NIL
1596 carmel2_move (stream
,sequence
,mailbox
)
1604 if (!(mail_sequence (stream
,sequence
) &&
1605 carmel2_copy (stream
,sequence
,mailbox
))) return NIL
;
1606 /* delete all requested messages */
1607 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1609 if (elt
->sequence
) {
1610 elt
->deleted
= T
; /* mark message deleted */
1611 LOCAL
->dirty
= T
; /* mark mailbox as dirty */
1618 /*----------------------------------------------------------------------
1619 * Carmel2 garbage collect stream
1620 * Accepts: Mail stream
1621 * garbage collection flags
1624 void carmel2_gc (stream
, gcflags
)
1628 /* No garbage collection in Carmel2 driver, not much to collect */
1633 /*----------------------------------------------------------------------
1634 Handle MessageCache for carmel2 mail driver
1637 msgno -- message number
1638 op -- operation code
1640 The carmel2 format keeps MESSAGECACHE entries in core for all messages
1641 in the open mail folder so there isn't any cache flushing and rereading
1645 carmel2_cache(stream
, msgno
, op
)
1650 /* It's a carmel driver if first 6 letters of name are carmel */
1651 if(stream
->dtb
== NULL
)
1654 if(struncmp2(stream
->dtb
->name
, "carmel", 6) == 0) {
1655 if(op
== CH_MAKEELT
)
1661 /* Not a carmel2 or carmel driver, call the standard function. This works
1662 as long as there is only one other driver since we know it must be
1665 return(mm_cache(stream
, msgno
, op
));
1670 /*----------------------------------------------------------------------
1671 Append a message to a mailbox
1673 Args: mailbox -- The message to append the mail folder too
1674 message -- Message header and text in rfc822 \r\n format to append
1676 Returns: T if all went OK, NIL if not
1679 carmel2_append(stream
, mailbox
, flags
, date
, message
)
1681 char *mailbox
, *flags
, *date
;
1686 /*---- A fake local data structure to pass to other functions---*/
1687 local
.calc_paths
= carmel2_calc_paths
;
1689 local
.aux_copy
= NULL
;
1691 return(carmel2_append2(stream
, &local
, mailbox
, flags
, date
, message
));
1697 /*----------------------------------------------------------------------
1698 Fetch the body structure for a camel message
1700 Args: stream -- MAILSTREAM
1701 mc -- The incore message cache entry for the message
1703 Returns: body structure
1705 Note, the envelope that results from the parse here is ignored. Only
1706 the envelope from the index is actually used in the Carmel2 format.
1709 carmel2_bodystruct(stream
, mc
)
1713 char *header
, *text
, *file
, *p
;
1714 ENVELOPE
*e_place_holder
;
1716 STRING string_struct
;
1718 header
= carmel2_readmsg(stream
, 1, mc
->rfc822_size
, mc
->data2
);
1722 text
= carmel2_readmsg(stream
, 0, mc
->rfc822_size
, mc
->data2
);
1726 INIT(&string_struct
, mail_string
, (void *)text
, strlen(text
));
1727 rfc822_parse_msg(&e_place_holder
, &b
, header
, strlen(header
),
1728 &string_struct
, mylocalhost(), carmel_20k_buf
);
1730 mail_free_envelope(&e_place_holder
);
1733 /* If there's no content type in the header and it's the carmel
1734 driver at the BWC set the type X-BWC-Glyph
1736 for(p
= header
; *p
; p
++)
1737 if(*p
=='\n' && (*(p
+1)=='C' || *(p
+1)=='c') &&
1738 struncmp2(p
+1,"content-type:", 13) == 0)
1741 if(!*p
&& LOCAL
->carmel
&& /* Carmel, not Carmel2 */
1742 b
->type
== TYPETEXT
&& /* Type text */
1743 (b
->subtype
== NULL
|| strcmp(b
->subtype
,"PLAIN") == 0) &&
1744 ((int)mc
->year
) + 1969 < 1994) {
1745 /* Most mail in a pod mail store is in the BWC-Glyph character
1746 set, but there is no tag in the data on disk, so we fake it here.
1747 We expect after 1994 all mail generated in BWC-Glyph format
1748 will have proper tags!
1750 b
->subtype
= cpystr("X-BWC-Glyph");
1759 /*----------------------------------------------------------------------
1760 Parse an address in a Carmel2 format mail index
1762 Args: line -- The line from the index to parse
1763 addr -- The address list to add to (if there is one)
1765 Returns: address list
1768 carmel2_parse_address(line
, addr
, localhost
)
1769 char *line
, *localhost
;
1775 addr
= mail_newaddr();
1778 for(a
= addr
; a
!= NULL
&& a
->next
!= NULL
; a
= a
->next
);
1779 a
->next
= mail_newaddr();
1783 line
++; /* Skip the tag chacater */
1784 a
->personal
= carmel2_parse_addr_field(&line
);
1785 a
->mailbox
= carmel2_parse_addr_field(&line
);
1786 a
->host
= carmel2_parse_addr_field(&line
);
1787 a
->adl
= carmel2_parse_addr_field(&line
);
1788 /* if(a->host == NULL)
1789 a->host = cpystr(localhost); */ /* host can't be NULL */
1790 /* Yes it can for Internet group syntax */
1796 /*----------------------------------------------------------------------
1797 Parse the next address field out of a carmel address index entry
1799 Args: string -- pointer to pointer to string
1801 Returns: field in malloced string or NULL
1803 Input string is a bunch of substrings separated by ^A. This function scans
1804 for the next ^A or end of string, cuts it out and returns it. The original
1805 strings passed in is mangled
1808 carmel2_parse_addr_field(string
)
1811 char *p
, end
, *start
;
1813 start
= p
= *string
;
1814 while(*p
> '\001') /* Find end of sub string or string */
1816 if((p
- *string
) == 0) {
1819 return(NULL
); /* If nothing found return nothing */
1821 end
= *p
; /* Save terminating character (^A or \0) */
1823 if(end
) /* If not end of string, get ready for next call */
1825 *string
= p
; /* Return pointer to next substring */
1826 return(cpystr(start
));
1832 /*----------------------------------------------------------------------
1833 Write an entry into a carmel2 index
1836 mc -- Message Cache element
1837 stream -- File stream to write to
1839 Returns: 0 if OK, -1 if failed
1841 carmel2_write_index(e
, mc
, stream
)
1846 long f_start
, f_end
;
1848 f_start
= ftell(stream
);
1850 if(fprintf(stream
, "%s\007\001xxxxxxxxxx\n", carmel2_s_o_m
) == EOF
)
1852 if(fprintf(stream
, "U%c%c%c%c%c____________________________\n",
1853 mc
->flagged
? 'F' : 'f',
1854 mc
->recent
? 'R' : 'r',
1855 mc
->answered
? 'A' : 'a',
1856 mc
->deleted
? 'D' : 'd',
1857 mc
->seen
? 'S' : 's') == EOF
)
1859 if(fprintf(stream
, "Z%d\n", mc
->rfc822_size
) == EOF
)
1861 if(fprintf(stream
, "D%d\001%d\001%d\001%d\001%d\001%d\001%d\001%d\n",
1862 mc
->year
+1969, mc
->month
, mc
->day
, mc
->hours
, mc
->minutes
,
1863 mc
->seconds
, mc
->zhours
* (mc
->zoccident
? 1 : -1),
1864 mc
->zminutes
) == EOF
)
1866 if(fprintf(stream
, "Svmail\n") == EOF
)
1868 if(fprintf(stream
, "P%d\n",mc
->data2
) == EOF
)
1870 if(carmel2_index_address(e
->to
, 'T', stream
) < 0)
1872 if(carmel2_index_address(e
->from
, 'F', stream
) < 0)
1874 if(carmel2_index_address(e
->cc
, 'C', stream
) < 0)
1876 if(carmel2_index_address(e
->bcc
, 'B', stream
) < 0)
1879 if(carmel2_index_address(e
->resent_to
, 't', stream
) < 0)
1881 if(carmel2_index_address(e
->resent_from
, 'f', stream
) < 0)
1883 if(carmel2_index_address(e
->resent_cc
, 'c', stream
) < 0)
1885 if(carmel2_index_address(e
->resent_bcc
, 'b', stream
) < 0)
1888 if(carmel2_index_address(e
->return_path
, 'H', stream
) < 0)
1890 if(carmel2_index_address(e
->sender
, 'E', stream
) < 0)
1892 if(carmel2_index_address(e
->reply_to
, 'R', stream
) < 0)
1894 if(e
->in_reply_to
!= NULL
)
1895 if(fprintf(stream
, "L%s\n", e
->in_reply_to
) == EOF
)
1897 if(e
->remail
!= NULL
)
1898 if(fprintf(stream
, "r%s\n", e
->remail
) == EOF
)
1900 if(e
->message_id
!= NULL
)
1901 if(fprintf(stream
, "I%s\n", e
->message_id
) == EOF
)
1903 if(e
->newsgroups
!= NULL
)
1904 if(fprintf(stream
, "N%s\n", e
->newsgroups
) == EOF
)
1906 if(e
->subject
!= NULL
)
1907 if(fprintf(stream
, "J%s\n", e
->subject
) == EOF
)
1910 /*--- figure out and write the offset ---*/
1911 f_end
= ftell(stream
);
1912 if(fseek(stream
, f_start
, 0) < 0)
1914 if(fprintf(stream
, "%s\007\001%10d\n", carmel2_s_o_m
, f_end
- f_start
)==EOF
)
1916 if(fseek(stream
, f_end
, 0) < 0)
1922 /* Index entry is a bit of a mess now. Maybe we should try to truncate */
1928 /*----------------------------------------------------------------------
1929 Write an address entry into a carmel2 index
1931 Args: addr -- addresslist
1932 field -- Field character specifier
1933 stream -- File stream to write to
1935 Returns 0 if OK, -1 on error writing
1938 carmel2_index_address(addr
, field
, stream
)
1945 for(a
= addr
; a
!= NULL
; a
= a
->next
) {
1946 if(fprintf(stream
, "%c%s\001%s\001%s\001%s\n",
1948 a
->personal
== NULL
? "" : a
->personal
,
1949 a
->mailbox
== NULL
? "" : a
->mailbox
,
1950 a
->host
== NULL
? "" : a
->host
,
1951 a
->adl
== NULL
? "" : a
->adl
) == EOF
)
1959 /*----------------------------------------------------------------------
1960 Real work of reading mail data files
1963 header_only -- return header if set, text if not
1964 file_size -- The file size if known (saves a stat)
1965 file -- name of the file to read
1967 Returns buffer with text stored in internal buffer. The Carmel2 format
1968 buffers the text of the current message and header in an internal
1969 buffer. The buffer never shrinks and is expanded as needed, up to a
1970 maximum. The text in the buffer is in CRLF format and is read in line
1971 by line using stdio. It is believed this will be efficient on whatever
1972 machine it is running on and will not use too much memory. (There's
1973 some extra memory used for buffering in stdio.) If a request is made
1974 first for only the header, then only the header will be read in. This
1975 is a big efficiency win when the file is large and only the header is
1976 needed. (In the Carmel2 format the header is genera lly not used, and
1977 when it is it is with the body to do a MIME parse, but the pod format
1978 does read the headers in to create the Carmel2 index.) An estimate is
1979 made of the size needed to expand the file to convert the line ends
1980 from LF to CRLF. The estimate alloca tes 10% extra space. If it
1981 doesn't fit, the whole buffer will be expanded by 50% and the whole
1982 read done over. When the header is read in a 30K buffer is allocated
1983 initially (if the buffer is smaller than that initially). The 50%
1984 increase is applied to the buffer when reading only the header.
1988 carmel2_readmsg(stream
, header_only
, file_size
, file_num
)
1995 char *p
, *p_end
, *file_name
;
1996 int past_header
, not_eof
;
1999 #define DEBUGDEBUG 1
2001 char debug_buf
[500];
2004 file_name
= (*LOCAL
->calc_paths
)(CalcPathCarmel2Data
,
2005 stream
->mailbox
, file_num
);
2006 if(file_name
== NULL
)
2007 return(NULL
); /* Just in case; should never be invalid if open */
2009 sprintf(debug_buf
, "READ RQ:\"%s\" HV:\"%s\" RQ_HD:%d HV_TXT:%d\n",
2011 LOCAL
->buffered_file
== NULL
? "" : LOCAL
->buffered_file
,
2012 header_only
, LOCAL
->buffered_header_and_text
);
2013 mm_log(debug_buf
, NIL
);
2016 /*---- Check out what we have read already -----*/
2017 if(LOCAL
->buffered_file
!= NULL
&&
2018 strcmp(LOCAL
->buffered_file
, file_name
) == 0) {
2019 /* The file is the same. Now have we read in the part
2020 that is wanted? If so return it.
2022 if(header_only
|| LOCAL
->buffered_header_and_text
) {
2025 mm_log("....Returning buffered header\n", NIL
);
2027 return(LOCAL
->msg_buf
);
2030 mm_log("....Returning buffered text\n", NIL
);
2032 return(LOCAL
->msg_buf_text_start
);
2036 /*-- Different file than last time, reset a few things --*/
2037 LOCAL
->buffered_header_and_text
= 0;
2038 LOCAL
->msg_buf_text_offset
= 0L;
2039 if(LOCAL
->buffered_file
!= NULL
)
2040 fs_give((void **)&(LOCAL
->buffered_file
));
2044 mm_log("....Reading file\n", NIL
);
2047 /*----- Open the file ------*/
2048 /* Would actually be more efficient not to use stdio here */
2049 msg_stream
= fopen(file_name
, "r");
2050 if(msg_stream
== NULL
) {
2051 strcat(file_name
, ".wid");
2052 msg_stream
= fopen(file_name
, "r");
2053 if(msg_stream
== NULL
)
2057 /*---- Check the size of the file ----*/
2058 if(file_size
== 0 && stat(file_name
, &st
) >= 0)
2059 file_size
= st
.st_size
;
2062 /* ------Pick an amount to read -------
2063 Assume the header is less than MAX_HEADER. We don't want to
2064 allocate buffer for the whole message if we are just getting
2067 max_read
= (file_size
* 11) / 10;
2071 max_read
= min(max_read
, CARMEL_MAX_HEADER
);
2072 } else if(LOCAL
->msg_buf_text_offset
> 0) {
2074 p
= LOCAL
->msg_buf_text_start
;
2075 fseek(msg_stream
, LOCAL
->msg_buf_text_offset
, 0);
2077 if(max_read
> CARMEL_MAXMESSAGESIZE
)
2078 max_read
= CARMEL_MAXMESSAGESIZE
;
2080 max_read
= 1; /* Forces buffer allocation below */
2083 /*----- Loop (re)reading the whole file 'till it fits ---*/
2084 /* This will fit the first time for all but the
2088 /*---- Make sure buffer is the right size ----*/
2089 if(LOCAL
->msg_buf_size
< max_read
) {
2090 /* Buffer not big, enough start whole read over */
2091 if(LOCAL
->msg_buf
!= NULL
)
2092 fs_give((void **)&(LOCAL
->msg_buf
));
2093 LOCAL
->msg_buf
= fs_get(max_read
);
2094 LOCAL
->msg_buf_size
= max_read
;
2095 fseek(msg_stream
, 0, 0);
2100 p_end
= LOCAL
->msg_buf
+ LOCAL
->msg_buf_size
- 3;
2102 while(p
< p_end
&& (not_eof
=(fgets(p
, p_end
-p
, msg_stream
) != NULL
))){
2103 if(*p
== '\n' && !past_header
) {
2108 LOCAL
->msg_buf_text_offset
= ftell(msg_stream
);
2109 LOCAL
->msg_buf_text_start
= p
;
2123 /* If we're here, the buffer wasn't big enough, which
2124 is due to a message with most lines less than 10 characters
2125 (the 10% addition for turning LF to CRLF wasn't enough).
2126 Increase it by 50% and try again, till we hit
2127 the largest message we can read
2129 max_read
= min(CARMEL_MAXMESSAGESIZE
, (max_read
* 15) / 10);
2130 fseek(msg_stream
, 0, 0);
2134 mm_log("Message truncated. It's is too large", WARN
);
2138 /*---- Save the name of the file buffered file ----*/
2139 LOCAL
->buffered_file
= cpystr(file_name
);
2140 LOCAL
->buffered_header_and_text
|= !header_only
;
2142 return(header_only
? LOCAL
->msg_buf
: LOCAL
->msg_buf_text_start
);
2146 /*----------------------------------------------------------------------
2147 Parse basic/quick entries in a Carmel mailbox
2149 Args: stream -- stream for mailbox
2150 file_pos -- File position in the index to start parsing at
2152 Returns: 0 if parse succeeds
2156 carmel2_parse_mail(stream
, file_pos
)
2161 long nmsgs
, next
, last_line_read
;
2164 nmsgs
= stream
->nmsgs
;
2166 /*---- Get the start-of-message record ------*/
2167 fseek(LOCAL
->index_stream
, file_pos
, 0);
2168 if(fgets(carmel_20k_buf
,sizeof(carmel_20k_buf
),LOCAL
->index_stream
)==NULL
)
2170 if(strncmp(carmel_20k_buf
, carmel2_s_o_m
, carmel2_s_o_m_len
) != 0)
2174 if(strlen(carmel_20k_buf
) != carmel2_s_o_m_len
+ 13)
2178 next
= atol(carmel_20k_buf
+24);
2179 mc
= carmel2_new_mc(stream
, nmsgs
);
2180 mc
->data1
= file_pos
;
2182 /*-- Get the status line, It must be the first line in the entry ----*/
2183 if(fgets(carmel_20k_buf
,sizeof(carmel_20k_buf
),
2184 LOCAL
->index_stream
) == NULL
)
2186 if(*carmel_20k_buf
!= 'U' || strlen(carmel_20k_buf
) != 35)
2187 goto bomb
; /* Invalid format! */
2188 mc
->flagged
= carmel_20k_buf
[1] == 'F';
2189 mc
->answered
= carmel_20k_buf
[3] == 'A';
2190 mc
->deleted
= carmel_20k_buf
[4] == 'D';
2191 mc
->seen
= carmel_20k_buf
[5] == 'S';
2194 /*---- Get the rest of the other interesting entries -----*/
2196 while(fgets(carmel_20k_buf
,sizeof(carmel_20k_buf
),
2197 LOCAL
->index_stream
) != NULL
&&
2199 strncmp(carmel_20k_buf
, carmel2_s_o_m
, carmel2_s_o_m_len
))
2200 if (*carmel_20k_buf
== 'Z') {
2201 mc
->rfc822_size
= atol(carmel_20k_buf
+1);
2203 } else if(*carmel_20k_buf
== 'D') {
2204 carmel2_parse_date(mc
, carmel_20k_buf
+1);
2206 } else if(*carmel_20k_buf
== 'P') {
2207 mc
->data2
= atoi(carmel_20k_buf
+1);
2211 /*-------- Now find the next index entry ---------*/
2212 last_line_read
= ftell(LOCAL
->index_stream
);
2214 fseek(LOCAL
->index_stream
, file_pos
, 0); /* try the offset first */
2215 if(fgets(carmel_20k_buf
, sizeof(carmel_20k_buf
),
2216 LOCAL
->index_stream
) == NULL
)
2218 if(strncmp(carmel_20k_buf
, carmel2_s_o_m
, carmel2_s_o_m_len
) != 0){
2219 /*-- Didn't match what was seeked to, back off and read lines --*/
2220 fseek(LOCAL
->index_stream
, last_line_read
, 0);
2222 file_pos
= ftell(LOCAL
->index_stream
);
2223 if(fgets(carmel_20k_buf
, sizeof(carmel_20k_buf
),
2224 LOCAL
->index_stream
) == NULL
)
2226 }while(strncmp(carmel_20k_buf
,carmel2_s_o_m
,carmel2_s_o_m_len
)!=0);
2230 if(stream
->nmsgs
!= nmsgs
) {
2231 stream
->nmsgs
= nmsgs
;
2232 mm_exists(stream
, nmsgs
);
2242 /* This killer macro is from bezerk.h. It's only needed at sites that don't
2243 * escape "From " lines with ">From " unless absolutely necessary (The UW).
2246 /* Validate line known to start with ``F''
2247 * Accepts: pointer to candidate string to validate as a From header
2248 * return pointer to end of date/time field
2249 * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
2250 * return pointer to offset from t of time zone (if non-zero)
2251 * Returns: T if valid From string, t,ti,zn set; else NIL
2254 #define VALID(s,x,ti,zn) \
2255 (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && (s[4] == ' ') && \
2256 (x = strchr (s+5,'\n')) && \
2257 ((x-s < 41) || ((ti = ((x[-2] == ' ') ? -14 : (x[-3] == ' ') ? -15 : \
2258 (x[-4] == ' ') ? -16 : (x[-5] == ' ') ? -17 : \
2259 (x[-6] == ' ') ? -18 : (x[-7] == ' ') ? -19 : \
2260 (x[-8] == ' ') ? -20 : (x[-9] == ' ') ? -21 : \
2261 (x[-10]== ' ') ? -22 : (x[-11]== ' ') ? -23 : 0)) && \
2262 (x[ti] == ' ') && (x[ti+1] == 'r') && (x[ti+2] == 'e') && \
2263 (x[ti+3] == 'm') && (x[ti+4] == 'o') && (x[ti+5] == 't') && \
2264 (x[ti+6] == 'e') && (x[ti+7] == ' ') && (x[ti+8] == 'f') && \
2265 (x[ti+9] == 'r') && (x[ti+10]== 'o') && (x[ti+11]== 'm') && \
2266 (x += ti)) || T) && \
2268 ((x[ti = -5] == ' ') ? ((x[-8] == ':') ? !(zn = 0) : \
2269 ((x[ti = zn = -9] == ' ') || \
2270 ((x[ti = zn = -11] == ' ') && \
2271 ((x[-10] == '+') || (x[-10] == '-'))))) : \
2272 ((x[zn = -4] == ' ') ? (x[ti = -9] == ' ') : \
2273 ((x[zn = -6] == ' ') && ((x[-5] == '+') || (x[-5] == '-')) && \
2274 (x[ti = -11] == ' ')))) && \
2275 (x[ti - 3] == ':') && (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
2276 (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && (x[ti - 11] == ' ')
2279 /* You are not expected to understand this macro, but read the next page if
2280 * you are not faint of heart.
2282 * Known formats to the VALID macro are:
2283 * From user Wed Dec 2 05:53 1992
2284 * BSD From user Wed Dec 2 05:53:22 1992
2285 * SysV From user Wed Dec 2 05:53 PST 1992
2286 * rn From user Wed Dec 2 05:53:22 PST 1992
2287 * From user Wed Dec 2 05:53 -0700 1992
2288 * From user Wed Dec 2 05:53:22 -0700 1992
2289 * From user Wed Dec 2 05:53 1992 PST
2290 * From user Wed Dec 2 05:53:22 1992 PST
2291 * From user Wed Dec 2 05:53 1992 -0700
2292 * Solaris From user Wed Dec 2 05:53:22 1992 -0700
2294 * Plus all of the above with `` remote from xxx'' after it. Thank you very
2295 * much, smail and Solaris, for making my life considerably more complicated.
2299 * What? You want to understand the VALID macro anyway? Alright, since you
2300 * insist. Actually, it isn't really all that difficult, provided that you
2301 * take it step by step.
2303 * Line 1 Validates that the 2-5th characters are ``rom ''.
2304 * Line 2 Sets x to point to the end of the line.
2305 * Lines 3-12 First checks to see if the line is at least 41 characters long.
2306 * If so, it scans backwards up to 10 characters (the UUCP system
2307 * name length limit due to old versions of UNIX) to find a space.
2308 * If one is found, it backs up 12 characters from that point, and
2309 * sees if the string from there is `` remote from''. If so, it
2310 * sets x to that position. The ``|| T'' is there so the parse
2311 * will still continue.
2312 * Line 13 Makes sure that there are at least 27 characters in the line.
2313 * Lines 14-17 Checks if the date/time ends with the year. If so, It sees if
2314 * there is a colon 3 characters further back; this would mean
2315 * that there is no timezone field and zn is set to 0 and ti is
2316 * left in front of the year. If not, then it expects there to
2317 * either to be a space four characters back for a three-letter
2318 * timezone, or a space six characters back followed by a + or -
2319 * for a numeric timezone. If a timezone is found, both zn and
2320 * ti are the offset of the space immediately before it.
2321 * Lines 18-20 Are the failure case for a date/time not ending with a year in
2322 * line 14. If there is a space four characters back, it is a
2323 * three-letter timezone; there must be a space for the year nine
2324 * characters back. Otherwise, there must be a space six
2325 * characters back and a + or - five characters back to indicate a
2326 * numeric timezone and a space eleven characters back to indicate
2327 * a year. zn and ti are set appropriately.
2328 * Line 21 Make sure that the string before ti is of the form hh:mm or
2329 * hh:mm:ss. There must be a colon three characters back, and a
2330 * space six or nine characters back (depending upon whether or
2331 * not the character six characters back is a colon). ti is set
2332 * to be the offset of the space immediately before the time.
2333 * Line 22 Make sure the string before ti is of the form www mmm dd.
2334 * There must be a space three characters back (in front of the
2335 * day), one seven characters back (in front of the month), and
2336 * one eleven characters back (in front of the day of week).
2338 * Why a macro? It gets invoked a *lot* in a tight loop. On some of the
2339 * newer pipelined machines it is faster being open-coded than it would be if
2340 * subroutines are called.
2342 * Why does it scan backwards from the end of the line, instead of doing the
2343 * much easier forward scan? There is no deterministic way to parse the
2344 * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to
2345 * see if unquoted spaces were possible. They are, and I've encountered enough
2346 * evil mail to be totally unwilling to trust that ``it will never happen''.
2350 /*----------------------------------------------------------------------
2351 Get the new mail out of the spooled mail file
2353 Args: stream -- The inbox stream to add mail too
2354 spool -- The path name of the spool file
2355 mailbox -- Name user sees for this, used only for error reporting
2359 - Lock the spool mail file with bezerk_lock
2360 - Get the carmel2 index open and remember where we started in it
2361 - Make buffer big enough for biggest header we'll mess with
2362 - Loop reading all the message out of the spool file:
2363 - Get a new data file for the message and open it
2364 - Read the header of the message into the big buffer while...
2365 - writing the message into the new data file
2366 - finish writing the text of the message into the data file
2367 - If all went well bump the message count and say it exists
2368 - Parse the header of the message to get an envelope, date and size
2369 - Write an entry into the carmel2 index
2370 - Unlink and create (to zero) the spool file and unlock it
2372 The carmel inbox should be locked when this is called and mm_critical
2373 should be called around this function.
2376 carmel2_spool_mail(stream
, spool
, mailbox
, clear_spool_file
)
2378 char *spool
, *mailbox
;
2379 int clear_spool_file
;
2381 char *p
, *eof
, *from_p
;
2382 int n
, size
, fd
, in_header
, from_i1
, from_i2
;
2383 long file_pos
, index_file_pos
, byte_count
, start_of_append
;
2384 FILE *spool_stream
, *data_stream
;
2388 STRING string_struct
;
2393 /*--- Get the locks set and files open-----*/
2394 fd
= carmel2_bezerk_lock(spool
, mailbox
);
2398 spool_stream
= fdopen(fd
, "r");
2399 fseek(LOCAL
->index_stream
, 0L, 2);
2400 start_of_append
= ftell(LOCAL
->index_stream
);
2402 /*--- Make buffer big enough for largest allowable header ----*/
2403 if(LOCAL
->msg_buf_size
< CARMEL_MAX_HEADER
) {
2404 if(LOCAL
->msg_buf
!= NULL
)
2405 fs_give((void **)&(LOCAL
->msg_buf
));
2406 LOCAL
->msg_buf_size
= CARMEL_MAX_HEADER
;
2407 LOCAL
->msg_buf
= fs_get(CARMEL_MAX_HEADER
);
2409 LOCAL
->buffered_header_and_text
= 0;
2410 LOCAL
->msg_buf_text_offset
= 0L;
2411 if(LOCAL
->buffered_file
!= NULL
)
2412 fs_give((void **)&(LOCAL
->buffered_file
));
2415 /*---- Read (and ignore) the first line with the "From " in it ----*/
2416 eof
= fgets(carmel_20k_buf
, sizeof(carmel_20k_buf
), spool_stream
);
2418 /*----- Loop getting messages ----*/
2419 while(eof
!= NULL
) {
2421 /*----- get a data file for the new message ----*/
2422 n
= carmel2_new_data_file(stream
->local
, stream
->mailbox
);
2423 data_stream
= fopen((*LOCAL
->calc_paths
)(CalcPathCarmel2Data
,
2424 stream
->mailbox
, n
),"w");
2425 if(data_stream
== NULL
)
2427 file_pos
= ftell(spool_stream
);
2436 /*--------------------------------------------------------------------
2437 Read the message in line by line, writing it out to the
2438 new data file. Also acculamuate a copy of the header in
2439 a buffer for parsing
2441 eof
= fgets(carmel_20k_buf
, sizeof(carmel_20k_buf
), spool_stream
);
2443 if(VALID(carmel_20k_buf
, from_p
, from_i1
, from_i2
))
2448 is_wide
|= carmel_match_glyph_wide(carmel_20k_buf
);
2450 if(*carmel_20k_buf
== '\n') {
2451 /* Encountered first blank line, end of header */
2455 if(p
- LOCAL
->msg_buf
+ strlen(carmel_20k_buf
) >
2456 LOCAL
->msg_buf_size
){
2457 /* out of room in buffer, end it */
2461 strcpy(p
, carmel_20k_buf
);
2467 /*----- Write the message into the file -----*/
2468 byte_count
+= strlen(carmel_20k_buf
);
2469 if(carmel_20k_buf
[0] == '>' && carmel_20k_buf
[1] == 'F' &&
2470 carmel_20k_buf
[2] == 'r' && carmel_20k_buf
[3] == 'o' &&
2471 carmel_20k_buf
[4] == 'm' && carmel_20k_buf
[5] == ' ') {
2472 if(fputs(carmel_20k_buf
+ 1, data_stream
) == EOF
)
2476 if(fputs(carmel_20k_buf
, data_stream
) == EOF
)
2479 eof
= fgets(carmel_20k_buf
, sizeof(carmel_20k_buf
), spool_stream
);
2481 fclose(data_stream
);
2484 sprintf(carmel_path_buf
, "%s.wid",
2485 (*LOCAL
->calc_paths
)(CalcPathCarmel2Data
,stream
->mailbox
,n
)
2487 rename((*LOCAL
->calc_paths
)(CalcPathCarmel2Data
,stream
->mailbox
,n
),
2492 /*---- get a new MESSAGECACHE to store it in -----*/
2493 mc
= carmel2_new_mc(stream
, stream
->nmsgs
+ 1);
2495 /*------ Parse the message header ------*/
2496 INIT(&string_struct
, mail_string
, (void *)"", 0);
2497 rfc822_parse_msg(&envelope
, &b
, LOCAL
->msg_buf
, strlen(LOCAL
->msg_buf
),
2498 &string_struct
, mylocalhost(), carmel_20k_buf
);
2499 carmel2_parse_bezerk_status(mc
, LOCAL
->msg_buf
);
2500 carmel2_rfc822_date(mc
, LOCAL
->msg_buf
);
2501 mc
->rfc822_size
= byte_count
;
2503 mc
->data1
= ftell(LOCAL
->index_stream
);
2506 /*----- Now add the message to the Carmel2 index ------*/
2507 if(carmel2_write_index(envelope
, mc
, LOCAL
->index_stream
) < 0)
2510 /*----- Write message into auxiliary index (plain carmel) ----*/
2511 if(LOCAL
->carmel
&& LOCAL
->aux_copy
!= NULL
) {
2512 if((*LOCAL
->aux_copy
)(stream
->local
, mailbox
, envelope
, mc
)) {
2513 /* BUG - this error may leave things half done, but will
2514 only result in duplicated mail */
2519 /*---- All went well, let the user know -----*/
2521 mm_exists(stream
, stream
->nmsgs
);
2523 mail_free_envelope(&envelope
);
2526 fflush(LOCAL
->index_stream
); /* Force new index entries onto disk */
2527 fclose(spool_stream
);
2528 if(clear_spool_file
) {
2530 close(creat(spool
, 0600));
2532 carmel2_bezerk_unlock(fd
, spool
);
2536 sprintf(carmel_error_buf
, "Error incorporating new mail into \"%s\": %s",
2537 carmel_parse_mb_name(mailbox
,'\0')->mailbox
, strerror(errno
));
2538 /* bug in above call to parse_mb -- should have version passed in */
2539 mm_log(carmel_error_buf
, ERROR
);
2540 fflush(LOCAL
->index_stream
);
2541 ftruncate(fileno(LOCAL
->index_stream
), start_of_append
);
2542 carmel2_bezerk_unlock(fd
, spool
);
2547 /*----------------------------------------------------------------------
2548 Copy the actual data file when copying a message
2550 Returns: -1 for failure
2551 otherwise the number of the new file,
2555 carmel2_copy_msg_file(stream
, orig_file_number
, new_mailbox
)
2557 int orig_file_number
;
2561 int wide
, e
, new_file_num
, n
, orig_fd
, new_fd
;
2563 /*---- Open the original file ----*/
2565 file_name
= (*LOCAL
->calc_paths
)(CalcPathCarmel2Data
,
2566 new_mailbox
,orig_file_number
);
2567 if(file_name
== NULL
)
2570 orig_fd
= open(file_name
, O_RDONLY
);
2571 if(orig_fd
< 0 && LOCAL
->carmel
) {
2572 strcat(file_name
, ".wid");
2573 orig_fd
= open(file_name
, O_RDONLY
);
2581 /*----- Open and create the new file ------*/
2582 new_file_num
= carmel2_new_data_file(stream
->local
, new_mailbox
);
2583 if(new_file_num
< 0)
2585 file_name
= (*LOCAL
->calc_paths
)(CalcPathCarmel2Data
,
2586 new_mailbox
, new_file_num
);
2588 strcat(file_name
, ".wid");
2589 new_fd
= open(file_name
, O_WRONLY
| O_CREAT
, 0600);
2594 /*---- Copy the bits ------*/
2596 while((n
= read(orig_fd
, carmel_20k_buf
, sizeof(carmel_20k_buf
))) >0) {
2597 if(write(new_fd
, carmel_20k_buf
, n
) < 0) {
2603 /*---- Close the streams and handle any errors ----*/
2608 return(new_file_num
); /* All is OK */
2610 /*--- something didn't go right ---*/
2613 sprintf(carmel_error_buf
, "Error copying message to %s: %s",
2614 new_mailbox
, strerror(errno
));
2615 mm_log(carmel_error_buf
, ERROR
);
2624 /*----------------------------------------------------------------------
2625 Locking for Carmel and Pod formats
2627 Args: stream -- Mail stream we're operating on
2628 file -- Mail file name
2629 write -- Flag indicating we want write access
2631 Returns: -1 if lock fails, 0 if it succeeds
2633 - There are two locks. Plain locks and write locks. They are created
2634 about the same way, but have different names. The time out on the write
2635 locks is much shorter, because it should never be held for very long.
2637 - Hitching (links in file system) post locking is used so it will work
2638 across NFS. Flock() could be used as it has two advantages. First it
2639 is more efficient, second the locks will dissolve automatically when the
2640 process dies. The efficiency is not of great concern, and the
2641 process should not (theoretically) die unless it crashes due to a bug
2642 or it is abnormally terminated. The advantage of locking across NFS
2643 is considered greater than the advantages of flock().
2645 - The mod time of the lock file is updated every time mail_check()
2646 or mail_ping() is called and the mod time on the lock file is recorded.
2647 This is so it can be determined that the lock file is current.
2649 - When a read lock file over 30 or a write lock over 5 minutes old is
2650 encountered it is assumed the lock is old and should be overridden
2651 (because the process crashed or was killed).
2653 - Every time the mod time on the lock file is updated (on calls to
2654 mail_check() and mail_ping()), the mod time of the lock file is
2655 checked against the record of what it was last set to. If the mod times
2656 don't match the lock has been broken and overridden. Then the original
2657 Pine should go into read-only mode.... This is only likely to happen if
2658 someone suspends (^Z's) the process for more than 30 minutes, and another
2662 carmel2_lock(local
, file
, write
)
2663 CARMEL2LOCAL
*local
;
2667 char *hitch
, lock
[CARMEL_PATHBUF_SIZE
], error_mess
[200], *l_path
;
2669 int n
, link_result
, hitch_fd
, timer_set
, l
;
2670 long override
, timer
;
2672 /* Set the length of time for override. It is 5 minutes for a
2673 write lock (no process should have it for that long) and
2674 30 minutes for a read lock, that's 30 minutes since the
2675 lock file was last touched. */
2676 override
= 60 * (write
? 5 : 30);
2679 /*----- Get the lock file and hitch names -----*/
2680 l_path
= (*local
->calc_paths
)(write
? CalcPathCarmel2WriteLock
:
2681 CalcPathCarmel2ReadLock
,
2685 strcpy(lock
, l_path
);
2686 /* copy lock file into bufferl call it hitch, unique thing added below */
2687 hitch
= carmel_path_buf
;
2688 hitch
= strcpy(hitch
, lock
);
2692 /*--- First create hitching post ----*/
2693 for(n
= time(0) % 6400; ; n
+= 10007) {
2694 /* Get random file name, that's not too long */
2695 sprintf(hitch
+ l
, "_%c%c", '0' + (n
% 80) , '0' + (n
/80));
2696 if(stat(hitch
, &sb
) < 0)
2697 break; /* Got a name that doesn't exist */
2699 hitch_fd
= open(hitch
, O_CREAT
, 0666);
2701 sprintf(error_mess
, "Error creating lock file \"%s\": %s",
2702 hitch
, strerror(errno
));
2703 mm_log(error_mess
, WARN
);
2708 /*----- Got a hitching post, now try link -----*/
2709 link_result
= link(hitch
, lock
);
2712 if(link_result
== 0 && sb
.st_nlink
== 2) {
2713 /*------ Got the lock! ------*/
2716 local
->write_lock_mod_time
= sb
.st_mtime
;
2718 local
->read_lock_mod_time
= sb
.st_mtime
;
2722 /*----- Check and override if lock is too old -----*/
2723 if(sb
.st_mtime
+ override
< time(0)) {
2724 unlink(lock
); /* Lock is old, override it */
2725 timer
= 100; /* Get us around the loop again */
2728 if(timer
< 0) /* timer not set */
2729 timer
= sb
.st_mtime
+ override
- time(0);
2732 /*----- Will user wait till time for override? -----*/
2733 if(!write
|| timer
> 5 * 60) {
2734 return(-1); /* Not more that 5 minutes */
2737 /*----- Try again, and tell user we're trying -------*/
2740 "Please wait. Mailbox %s is locked for %d more seconds",
2742 mm_log(error_mess
, WARN
);
2753 /*----------------------------------------------------------------------
2754 Unlock a carmel mail stream
2756 Args: stream -- The mailstream that is locked
2757 mailbox -- FQN of mailbox to lock ( e.g. #carmel#foo )
2758 write -- flag to set if it is a write lock
2763 carmel2_unlock(local
, mailbox
, write
)
2764 CARMEL2LOCAL
*local
;
2768 char lock
[CARMEL_PATHBUF_SIZE
];
2771 strcpy(lock
, (*local
->calc_paths
)(write
? CalcPathCarmel2WriteLock
:
2772 CalcPathCarmel2ReadLock
,
2775 if(stat(lock
, &sb
) < 0)
2776 return; /* Hmmm... lock already broken */
2779 (write
? local
->write_lock_mod_time
: local
->read_lock_mod_time
))
2780 return; /* Hmmm... not our lock */
2787 /*----------------------------------------------------------------------
2788 Keep the mod date on the lock file fresh
2791 file -- the name of the mailbox the lock is for
2792 write -- set if this is a write lock
2794 Returns: 0 if update was OK
2795 -1 if something bad happened, like the lock was stolen
2798 carmel2_update_lock(local
, file
, write
)
2799 CARMEL2LOCAL
*local
;
2803 char lock
[CARMEL_PATHBUF_SIZE
];
2804 struct timeval tp
[2];
2807 strcpy(lock
, (*local
->calc_paths
)(write
? CalcPathCarmel2WriteLock
:
2808 CalcPathCarmel2ReadLock
,
2811 if(stat(lock
, &sb
) < 0) {
2812 /* Lock file stolen, oh oh */
2817 (write
? local
->write_lock_mod_time
: local
->read_lock_mod_time
)) {
2818 /* Not our lock anymore , oh oh */
2822 gettimeofday (&tp
[0],NIL
); /* set atime to now */
2823 gettimeofday (&tp
[1],NIL
); /* set mtime to now */
2827 local
->write_lock_mod_time
= tp
[1].tv_sec
;
2829 local
->read_lock_mod_time
= tp
[1].tv_sec
;
2835 /*----------------------------------------------------------------------
2836 Berkeley open and lock mailbox
2838 This is mostly ripped off from the Bezerk driver
2842 carmel2_bezerk_lock (spool
, file
)
2846 int i
= BEZERKLOCKTIMEOUT
* 60 - 1;
2851 lock
= carmel_path_buf
;
2852 sprintf(lock
, "%s.lock", spool
);
2853 do { /* until OK or out of tries */
2854 gettimeofday (&tp
,NIL
); /* get the time now */
2856 /* SUN-OS had an NFS, As kludgy as an albatross;
2857 * And everywhere that it was installed, It was a total loss. -- MRC 9/25/91
2859 /* build hitching post file name */
2860 hitch
= carmel_20k_buf
;
2861 sprintf(hitch
, "%s.%d.%d.",carmel_path_buf
,time (0),getpid ());
2862 j
= strlen (hitch
); /* append local host name */
2863 gethostname (hitch
+ j
,(MAILTMPLEN
- j
) - 1);
2864 /* try to get hitching-post file */
2865 if ((ld
= open (hitch
, O_WRONLY
|O_CREAT
|O_EXCL
,0666)) < 0) {
2866 /* prot fail & non-ex, don't use lock files */
2867 if ((errno
== EACCES
) && (stat (hitch
, &sb
))) *lock
= '\0';
2868 else { /* otherwise something strange is going on */
2869 sprintf (carmel_20k_buf
,"Error creating %s: %s",lock
,strerror (errno
));
2870 mm_log (carmel_20k_buf
, WARN
); /* this is probably not good */
2871 /* don't use lock files if not one of these */
2872 if ((errno
!= EEXIST
) && (errno
!= EACCES
)) *lock
= '\0';
2875 else { /* got a hitching-post */
2876 chmod (hitch
,0666); /* make sure others can break the lock */
2877 close (ld
); /* close the hitching-post */
2878 link (hitch
,lock
); /* tie hitching-post to lock, ignore failure */
2879 stat (hitch
, &sb
); /* get its data */
2880 unlink (hitch
); /* flush hitching post */
2881 /* If link count .ne. 2, hitch failed. Set ld to -1 as if open() failed
2882 so we try again. If extant lock file and time now is .gt. file time
2883 plus timeout interval, flush the lock so can win next time around. */
2884 if ((ld
= (sb
.st_nlink
!= 2) ? -1 : 0) && (!stat (lock
,&sb
)) &&
2885 (tp
.tv_sec
> sb
.st_ctime
+ BEZERKLOCKTIMEOUT
* 60)) unlink (lock
);
2889 /* This works on modern Unix systems which are not afflicted with NFS mail.
2890 * "Modern" means that O_EXCL works. I think that NFS mail is a terrible
2891 * idea -- that's what IMAP is for -- but some people insist upon losing...
2893 /* try to get the lock */
2894 if ((ld
= open (lock
,O_WRONLY
|O_CREAT
|O_EXCL
,0666)) < 0) switch (errno
) {
2895 case EEXIST
: /* if extant and old, grab it for ourselves */
2896 if ((!stat (lock
,&sb
)) && tp
.tv_sec
> sb
.st_ctime
+ LOCKTIMEOUT
* 60)
2897 ld
= open (lock
,O_WRONLY
|O_CREAT
,0666);
2899 case EACCES
: /* protection fail, ignore if non-ex or old */
2900 if (stat (lock
,&sb
) || (tp
.tv_sec
> sb
.st_ctime
+ LOCKTIMEOUT
* 60))
2901 *lock
= '\0'; /* assume no world write mail spool dir */
2903 default: /* some other failure */
2904 sprintf (tmp
,"Error creating %s: %s",lock
,strerror (errno
));
2905 mm_log (tmp
,WARN
); /* this is probably not good */
2906 *lock
= '\0'; /* don't use lock files */
2909 if (ld
>= 0) { /* if made a lock file */
2910 chmod (tmp
,0666); /* make sure others can break the lock */
2911 close (ld
); /* close the lock file */
2914 if ((ld
< 0) && *lock
) { /* if failed to make lock file and retry OK */
2916 sprintf (carmel_20k_buf
,"Mailbox %s is locked, will override in %d seconds...",
2918 mm_log (carmel_20k_buf
, WARN
);
2920 sleep (1); /* wait 1 second before next try */
2922 } while (i
-- && ld
< 0 && *lock
);
2924 if ((fd
= open (spool
, O_RDONLY
)) >= 0) flock (fd
, LOCK_SH
);
2925 else { /* open failed */
2926 j
= errno
; /* preserve error code */
2927 if (*lock
) unlink (lock
); /* flush the lock file if any */
2928 errno
= j
; /* restore error code */
2935 /*----------------------------------------------------------------------
2936 Berkeley unlock and close mailbox
2939 carmel2_bezerk_unlock (fd
, spool
)
2943 sprintf(carmel_path_buf
, "%s.lock", spool
);
2945 flock (fd
, LOCK_UN
); /* release flock'ers */
2946 close (fd
); /* close the file */
2947 /* flush the lock file if any */
2948 if (*carmel_path_buf
) unlink (carmel_path_buf
);
2953 /*----------------------------------------------------------------------
2954 Make sure directory exists and is writable
2956 Args: dir - directory to check, should be full path
2958 Result: returns -1 if not OK along with mm_logging a message
2962 carmel2_check_dir(dir
)
2966 char error_mess
[150];
2968 if(stat(dir
, &sb
) < 0) {
2969 if(mkdir(dir
, 0700) < 0) {
2970 sprintf(error_mess
, "Error creating directory %-.30s %s",
2971 dir
, strerror(errno
));
2972 mm_log(error_mess
, WARN
);
2975 } else if(!(sb
.st_mode
& S_IFDIR
)) {
2976 sprintf(error_mess
, "Warning: %s is not a directory",dir
);
2977 mm_log(error_mess
, WARN
);
2980 } else if(access(dir
, W_OK
) != 0) {
2981 sprintf(error_mess
, "Warning: unable to write to %-.30s %s",
2982 dir
, strerror(errno
));
2983 mm_log(error_mess
, WARN
);
2991 /*----------------------------------------------------------------------
2992 Return the number for the next message file
2994 Args: stream -- the Mail stream for the new data file
2995 mailbox -- The FQN of mailbox data file is for
2997 Returns: file number or -1
3000 carmel2_new_data_file(local
, mailbox
)
3001 CARMEL2LOCAL
*local
;
3004 char *path
, num_buf
[50], e_buf
[100];
3005 int fd
, num
, bytes_read
;
3007 /*---- Get the full path of the .MAXNAME file for index ----*/
3008 path
= (*local
->calc_paths
)(CalcPathCarmel2MAXNAME
, mailbox
, 0);
3012 fd
= open(path
, O_RDWR
|O_CREAT
, 0666);
3014 sprintf(e_buf
, "Error getting next number of mail file: %s",
3016 mm_log(e_buf
, ERROR
);
3020 bytes_read
= read(fd
, num_buf
, sizeof(num_buf
));
3021 if(bytes_read
< 6) {
3024 num
= atoi(num_buf
) + 1;
3028 sprintf(num_buf
, "%-6d\n", num
);
3030 write(fd
, num_buf
, strlen(num_buf
));
3037 /*----------------------------------------------------------------------
3038 Do all the file name generation for carmel2 driver
3040 The mailbox passed in is in either the format: #carmel2#folder or
3041 #carmel2#user%folder
3042 This generates that absolute paths for an index, or a data file or
3045 Bug: This is untested!
3048 carmel2_calc_paths(operation
, mailbox
, data_file_num
)
3053 static char path
[CARMEL_PATHBUF_SIZE
], num
[20];
3055 struct carmel_mb_name
*parsed_name
;
3058 parsed_name
= carmel_parse_mb_name(mailbox
,'2');
3060 if(parsed_name
== NULL
) {
3061 mm_log("Internal error (bug). Invalid Carmel folder name",ERROR
);
3066 if(parsed_name
->user
!= NULL
) {
3067 /*---- has a user in mailbox name -----*/
3068 pw
= getpwnam(parsed_name
->user
);
3070 sprintf(carmel_error_buf
,
3071 "Error accessing mailbox \"%s\". No such user name \"%s\"\n",
3072 mailbox
, parsed_name
->user
);
3073 mm_log(carmel_error_buf
, ERROR
);
3074 carmel_free_mb_name(parsed_name
);
3077 home_dir
= pw
->pw_dir
;
3079 home_dir
= myhomedir();
3081 mailbox
= parsed_name
->mailbox
;
3085 case CalcPathCarmel2Data
:
3086 sprintf(path
, "%s/%s/%d", home_dir
, CARMEL2_MSG_DIR
, data_file_num
);
3089 case CalcPathCarmel2Index
:
3090 sprintf(path
, "%s/%s/%s", home_dir
, CARMEL2_INDEX_DIR
, mailbox
);
3093 case CalcPathCarmel2MAXNAME
:
3094 sprintf(path
, "%s/%s", home_dir
, CARMEL2_MAXFILE
);
3097 case CalcPathCarmel2WriteLock
:
3098 sprintf(path
, "%s/%s/.%s.wl", home_dir
, CARMEL2_INDEX_DIR
, mailbox
);
3101 case CalcPathCarmel2ReadLock
:
3102 sprintf(path
, "%s/%s/.%s.rl", home_dir
, CARMEL2_INDEX_DIR
, mailbox
);
3106 carmel_free_mb_name(parsed_name
);
3112 /*----------------------------------------------------------------------
3113 Find and parse the status line in a mail header
3115 Args: mc -- the message cache where status is to be stored
3116 header -- the message header to parsen
3119 carmel2_parse_bezerk_status(mc
, header
)
3124 for(p
= header
; *p
; p
++) {
3128 if(*p
!= 'S' && *p
!= 'X')
3130 if(strncmp(p
, "Status: ", 8) == 0 || strncmp(p
,"X-Status: ",10)== 0) {
3132 for(p
+= *p
== 'X' ? 10: 8; *p
&& *p
!= '\n'; p
++)
3134 case 'R': mc
->seen
= 1; break;
3135 case 'O': mc
->recent
= 0; break;
3136 case 'D': mc
->deleted
= 1; break;
3137 case 'F': mc
->flagged
= 1; break;
3138 case 'A': mc
->flagged
= 1; break;
3146 /*----------------------------------------------------------------------
3147 Turn a string of describing flags into a bit mask
3149 Args: stream -- mail stream, unused
3150 flag -- string flag list
3152 Returns: returns short with bits set; bits are defined in carmel2.h
3155 carmel2_getflags (stream
, flag
)
3162 if (flag
&& *flag
) { /* no-op if no flag string */
3163 /* check if a list and make sure valid */
3164 if ((i
= (*flag
== '(')) ^ (flag
[strlen (flag
)-1] == ')')) {
3165 mm_log ("Bad flag list",ERROR
);
3168 /* copy the flag string w/o list construct */
3169 strncpy (tmp
,flag
+i
,(j
= strlen (flag
) - (2*i
)));
3171 t
= ucase (tmp
); /* uppercase only from now on */
3173 while (*t
) { /* parse the flags */
3174 if (*t
== '\\') { /* system flag? */
3175 switch (*++t
) { /* dispatch based on first character */
3176 case 'S': /* possible \Seen flag */
3177 if (t
[1] == 'E' && t
[2] == 'E' && t
[3] == 'N') i
= fSEEN
;
3178 t
+= 4; /* skip past flag name */
3180 case 'D': /* possible \Deleted flag */
3181 if (t
[1] == 'E' && t
[2] == 'L' && t
[3] == 'E' && t
[4] == 'T' &&
3182 t
[5] == 'E' && t
[6] == 'D') i
= fDELETED
;
3183 t
+= 7; /* skip past flag name */
3185 case 'F': /* possible \Flagged flag */
3186 if (t
[1] == 'L' && t
[2] == 'A' && t
[3] == 'G' && t
[4] == 'G' &&
3187 t
[5] == 'E' && t
[6] == 'D') i
= fFLAGGED
;
3188 t
+= 7; /* skip past flag name */
3190 case 'A': /* possible \Answered flag */
3191 if (t
[1] == 'N' && t
[2] == 'S' && t
[3] == 'W' && t
[4] == 'E' &&
3192 t
[5] == 'R' && t
[6] == 'E' && t
[7] == 'D') i
= fANSWERED
;
3193 t
+= 8; /* skip past flag name */
3195 case 'R': /* possible \Answered flag */
3196 if (t
[1] == 'E' && t
[2] == 'C' && t
[3] == 'E' && t
[4] == 'N' &&
3197 t
[5] == 'T') i
= fRECENT
;
3198 t
+= 6; /* skip past flag name */
3200 default: /* unknown */
3204 /* add flag to flags list */
3205 if (i
&& ((*t
== '\0') || (*t
++ == ' '))) f
|= i
;
3206 else { /* bitch about bogus flag */
3207 mm_log ("Unknown system flag",ERROR
);
3211 else { /* no user flags yet */
3212 mm_log ("Unknown flag",ERROR
);
3221 /*----------------------------------------------------------------------
3222 Get a pointer to a MESSAGECACHE entry, allocating if needed
3224 Args: stream -- message stream
3225 num -- Message number to allocate on for
3227 Returns: The MESSAGECACHE entry
3229 The message caches are allocated in blocks of 256 to save space taken up by
3230 pointers in a linked list and allocation overhead. The mc_blocks
3231 data structure is an array that points to each block. The macro MC()
3232 defined in carmel.h returns a pointer to a MESSAGECACHE given
3233 a message number. This function here can be called when a MESSAGECACHE
3234 is needed to a message number that might be new. It allocates a new
3235 block if needed and clears the MESSAGECACHE returned.
3237 The MESSAGECACHE entries are currently about 28 bytes which implies 28Kb
3238 must be used per 1000 messages. If memory is limited this driver will be
3239 limited in the number of messages it can handle, and the limit is due to
3240 the fact that these MESSAGECACHEs must be in core.
3242 It might be possible to reduce the size of each entry by a few bytes if
3243 the message numbers were reduced to a short, and the mostly unused keywords
3244 were removed. It would also be nice to add a day of the week (3 bits)
3246 static MESSAGECACHE
*
3247 carmel2_new_mc(stream
, num
)
3253 /* Make sure we've got a cache_entry */
3254 if(num
>= LOCAL
->cache_size
) {
3255 if(LOCAL
->mc_blocks
== NULL
)
3256 LOCAL
->mc_blocks
=(MESSAGECACHE
**)fs_get(sizeof(MESSAGECACHE
*));
3258 fs_resize((void **)&(LOCAL
->mc_blocks
), ((num
>>8) + 1) *
3259 sizeof(MESSAGECACHE
*));
3260 LOCAL
->mc_blocks
[num
>> 8] = (MESSAGECACHE
*)
3261 fs_get(256 * sizeof(MESSAGECACHE
));
3262 LOCAL
->cache_size
= ((num
>> 8) + 1) * 256;
3281 /* Don't set the date, the size and the extra data,
3282 assume they will be set
3289 /*----------------------------------------------------------------------
3290 Do the real work of appending a message to a mailbox
3292 Args: local -- The carmel2 local data structure, (a some what fake incomplete
3293 one, set up for the purposes here)
3294 mailbox -- Name of mailbox to append to
3295 message -- The rfc822 format of the message with \r\n's
3297 Returns: T if all went OK, NIL if it did not
3299 - Make sure index exists or can be created
3300 - lock index for write
3301 - get a data file name
3302 - Put the text in the file
3303 - Parse the string to get envelope and message cache
3304 - Write the entry into the index
3307 BUG: This needs some locking and some error messages
3310 carmel2_append2(stream
, local
, mailbox
, flags
, date
, message
)
3311 char *mailbox
, *flags
, *date
;
3312 CARMEL2LOCAL
*local
;
3316 char *index_name
, *data_name
, *p
, c
, *header_string
;
3320 FILE *index_file
, *data_file
;
3322 int last_was_crlf
, saved_errno
;
3323 STRING string_struct
;
3327 /*------ Lock the mailbox for write ------*/
3328 if(carmel2_lock(local
, mailbox
, WRITE_LOCK
) < 0) {
3329 sprintf(carmel_error_buf
,
3330 "Mailbox \"%s\" is locked. Can't append to it.",
3332 mm_log(carmel_error_buf
, ERROR
);
3336 /*----- Get the file name of carmel2 index -----*/
3337 index_name
= (*local
->calc_paths
)(CalcPathCarmel2Index
, mailbox
, 0);
3338 if(index_name
== NULL
) {
3343 /*------ See if it exists already or not ------*/
3344 if(stat(index_name
, &sb
) < 0) {
3345 mm_notify (stream
,"[TRYCREATE] Must create mailbox before copy", NIL
);
3346 carmel2_unlock(local
, mailbox
, WRITE_LOCK
);
3350 index_file
= fopen(index_name
, "a");
3351 if(index_file
== NULL
)
3354 mc
.data2
= carmel2_new_data_file(local
, mailbox
);
3356 flagbits
= carmel2_getflags(NULL
, flags
);
3358 if(flagbits
& fSEEN
) mc
.seen
= T
;
3359 if(flagbits
& fDELETED
) mc
.deleted
= T
;
3360 if(flagbits
& fFLAGGED
) mc
.flagged
= T
;
3361 if(flagbits
& fANSWERED
) mc
.answered
= T
;
3362 if(flagbits
& fRECENT
) mc
.recent
= T
;
3365 /*----- Open the data file -----*/
3366 data_name
= (*local
->calc_paths
)(CalcPathCarmel2Data
, mailbox
, mc
.data2
);
3367 if(data_name
== NULL
) {
3368 errno
= 0; /* Don't generate an error message at all */
3371 data_file
= fopen(data_name
, "w");
3372 if(data_file
== NULL
)
3375 /*--- accumulate header as we go for later parsing ---*/
3376 header_string
= carmel_20k_buf
;
3378 /*------ Write the message to the file, and get header in a string -----*/
3379 for(size
= SIZE(message
); size
> 0; size
--){
3381 if(c
== '\r' && size
> 1) {
3382 /* Turn CRLF into LF for UNIX */
3386 if(fputc('\r', data_file
) < 0 || fputc(c
, data_file
) < 0)
3388 if(header_string
!= NULL
) {
3389 *header_string
++ = '\r';
3390 *header_string
++ = c
;
3393 if(fputc('\n', data_file
) < 0)
3395 if(header_string
!= NULL
) {
3397 *header_string
= '\0';
3398 header_string
= NULL
;
3400 *header_string
++ = '\r';
3401 *header_string
++ = '\n';
3407 if(fputc(c
, data_file
) == EOF
)
3409 if(header_string
!= NULL
)
3410 *header_string
++ = c
;
3414 if(fclose(data_file
) == EOF
)
3419 /*----Get the size that we actually wrote -----*/
3420 stat(data_name
, &sb
);
3421 mc
.rfc822_size
= sb
.st_size
;
3423 /* This blows the nice tight memory usage for the carmel2 driver :-( */
3424 header_string
= cpystr(carmel_20k_buf
);
3427 /*--- For MIME type x-bwc-glyph-wide, store in a nnnn.wid file ----*/
3428 for(p
= header_string
; *p
; p
++) {
3429 if((p
== header_string
&& carmel_match_glyph_wide(p
)) ||
3430 (*p
== '\r' && *(p
+1) == '\n' && carmel_match_glyph_wide(p
+2))) {
3431 sprintf(carmel_path_buf
, "%s.wid", data_name
);
3432 rename(data_name
, carmel_path_buf
);
3438 /*------ Parse the message to get envelope and message cache ----*/
3439 INIT(&string_struct
, mail_string
, (void *)"", 0);
3440 rfc822_parse_msg(&envelope
, &b
, header_string
, strlen(header_string
),
3441 &string_struct
, mylocalhost(), carmel_20k_buf
);
3442 carmel2_parse_bezerk_status(&mc
, header_string
);
3443 carmel2_rfc822_date(&mc
, header_string
);
3445 /*------ Write the entry into the index ------*/
3446 if(carmel2_write_index(envelope
, &mc
, index_file
) < 0)
3449 if(local
->aux_copy
!= NULL
) /* Write carmel index if needed */
3450 (*local
->aux_copy
)(local
, mailbox
, envelope
, &mc
);
3452 mail_free_envelope(&envelope
);
3453 fs_give((void **)&header_string
);
3455 if(fclose(index_file
) == EOF
)
3457 carmel2_unlock(local
, mailbox
, WRITE_LOCK
);
3461 saved_errno
= errno
;
3462 if(index_file
!= NULL
) {
3465 if(data_file
!= NULL
) {
3469 carmel2_unlock(local
, mailbox
, WRITE_LOCK
);
3470 if(saved_errno
!= 0) {
3471 sprintf(carmel_error_buf
,"Message append failed: %s",
3472 strerror(saved_errno
));
3473 mm_log(carmel_error_buf
, ERROR
);
3481 /* Search support routines
3482 * Accepts: MAIL stream
3484 * pointer to additional data
3485 * pointer to temporary buffer
3486 * Returns: T if search matches, else NIL
3490 carmel2_search_all (stream
,msgno
,d
,n
)
3496 return T
; /* ALL always succeeds */
3501 carmel2_search_answered (stream
,msgno
,d
,n
)
3507 return MC(msgno
)->answered
? T
: NIL
;
3512 carmel2_search_deleted (stream
,msgno
,d
,n
)
3518 return MC(msgno
)->deleted
? T
: NIL
;
3523 carmel2_search_flagged (stream
,msgno
,d
,n
)
3529 return MC(msgno
)->flagged
? T
: NIL
;
3534 carmel2_search_keyword (stream
,msgno
,d
,n
)
3540 return NIL
; /* keywords not supported yet */
3545 carmel2_search_new (stream
,msgno
,d
,n
)
3551 MESSAGECACHE
*elt
= MC(msgno
);
3552 return (elt
->recent
&& !elt
->seen
) ? T
: NIL
;
3556 carmel2_search_old (stream
,msgno
,d
,n
)
3562 return MC(msgno
)->recent
? NIL
: T
;
3567 carmel2_search_recent (stream
,msgno
,d
,n
)
3573 return MC(msgno
)->recent
? T
: NIL
;
3578 carmel2_search_seen (stream
,msgno
,d
,n
)
3584 return MC(msgno
)->seen
? T
: NIL
;
3589 carmel2_search_unanswered (stream
,msgno
,d
,n
)
3595 return MC(msgno
)->answered
? NIL
: T
;
3600 carmel2_search_undeleted (stream
,msgno
,d
,n
)
3606 return MC(msgno
)->deleted
? NIL
: T
;
3611 carmel2_search_unflagged (stream
,msgno
,d
,n
)
3617 return MC(msgno
)->flagged
? NIL
: T
;
3622 carmel2_search_unkeyword (stream
,msgno
,d
,n
)
3628 return T
; /* keywords not supported yet */
3633 carmel2_search_unseen (stream
,msgno
,d
,n
)
3639 return MC(msgno
)->seen
? NIL
: T
;
3643 carmel2_search_before (stream
,msgno
,d
,n
)
3649 return (char) (carmel2_msgdate (stream
,msgno
) < n
);
3654 carmel2_search_on (stream
,msgno
,d
,n
)
3660 return (char) (carmel2_msgdate (stream
,msgno
) == n
);
3665 carmel2_search_since (stream
,msgno
,d
,n
)
3671 /* everybody interprets "since" as .GE. */
3672 return (char) (carmel2_msgdate (stream
,msgno
) >= n
);
3676 static unsigned long
3677 carmel2_msgdate (stream
,msgno
)
3683 MESSAGECACHE
*elt
= MC(msgno
);
3685 return (long) (elt
->year
<< 9) + (elt
->month
<< 5) + elt
->day
;
3688 /*----------------------------------------------------------------------
3689 Search only the body of the text.
3690 BUG, probably need to unencode before searching
3693 carmel2_search_body (stream
,msgno
, pat
, pat_len
)
3698 char *t
= carmel2_fetchtext(stream
, msgno
);
3699 return (t
&& search (t
,strlen(t
), pat
, pat_len
));
3703 /*----------------------------------------------------------------------
3704 Search the subject field
3707 carmel2_search_subject (stream
,msgno
, pat
, pat_len
)
3709 long msgno
, pat_len
;
3712 char *t
= carmel2_fetchstructure (stream
,msgno
,NULL
)->subject
;
3713 return t
? search (t
, strlen(t
), pat
, pat_len
) : NIL
;
3717 /*----------------------------------------------------------------------
3718 Search the full header and body text of the message
3721 carmel2_search_text (stream
, msgno
, pat
, pat_len
)
3723 long msgno
, pat_len
;
3726 char *t
= carmel2_fetchheader(stream
,msgno
);
3727 return (t
&& search(t
, strlen(t
), pat
, pat_len
)) ||
3728 carmel2_search_body(stream
,msgno
, pat
, pat_len
);
3732 /*----------------------------------------------------------------------
3733 Search the Bcc field
3736 carmel2_search_bcc (stream
,msgno
,d
,n
)
3742 carmel_20k_buf
[0] = '\0';
3743 /* get text for address */
3744 rfc822_write_address (carmel_20k_buf
,
3745 carmel2_fetchstructure (stream
,msgno
,NULL
)->bcc
);
3746 return search (carmel_20k_buf
, strlen(carmel_20k_buf
),d
,n
);
3751 carmel2_search_cc (stream
,msgno
,d
,n
)
3757 carmel_20k_buf
[0] = '\0';
3758 /* get text for address */
3759 rfc822_write_address (carmel_20k_buf
,
3760 carmel2_fetchstructure (stream
, msgno
, NULL
)->cc
);
3761 return search (carmel_20k_buf
, strlen(carmel_20k_buf
),d
,n
);
3766 carmel2_search_from (stream
,msgno
,d
,n
)
3772 carmel_20k_buf
[0] = '\0';
3773 /* get text for address */
3774 rfc822_write_address (carmel_20k_buf
,
3775 carmel2_fetchstructure (stream
,msgno
,NULL
)->from
);
3776 return search (carmel_20k_buf
, strlen(carmel_20k_buf
),d
,n
);
3781 carmel2_search_to (stream
,msgno
, pat
, pat_len
)
3787 carmel_20k_buf
[0] = '\0';
3788 /* get text for address */
3789 rfc822_write_address (carmel_20k_buf
,
3790 carmel2_fetchstructure (stream
, msgno
, NULL
)->to
);
3791 return(search(carmel_20k_buf
,strlen(carmel_20k_buf
), pat
, pat_len
));
3794 /* Search parsers */
3798 * Accepts: function to return
3799 * pointer to date integer to return
3800 * Returns: function to return
3804 carmel2_search_date (f
, n
)
3811 /* parse the date and return fn if OK */
3812 return (carmel2_search_string (f
,&s
,&i
) && mail_parse_date (&elt
,s
) &&
3813 (*n
= (elt
.year
<< 9) + (elt
.month
<< 5) + elt
.day
)) ? f
: NIL
;
3817 * Accepts: function to return
3818 * pointer to string to return
3819 * Returns: function to return
3823 carmel2_search_flag (f
,d
)
3827 /* get a keyword, return if OK */
3828 return (*d
= strtok (NIL
," ")) ? f
: NIL
;
3833 * Accepts: function to return
3834 * pointer to string to return
3835 * pointer to string length to return
3836 * Returns: function to return
3840 carmel2_search_string (f
,d
,n
)
3845 char *c
= strtok (NIL
,""); /* remainder of criteria */
3846 if (c
) { /* better be an argument */
3847 switch (*c
) { /* see what the argument is */
3848 case '\0': /* catch bogons */
3851 case '"': /* quoted string */
3852 if (!(strchr (c
+1,'"') && (*d
= strtok (c
,"\"")) && (*n
= strlen (*d
))))
3855 case '{': /* literal string */
3856 *n
= strtol (c
+1,&c
,10); /* get its length */
3857 if (*c
++ != '}' || *c
++ != '\015' || *c
++ != '\012' ||
3858 *n
> strlen (*d
= c
)) return NIL
;
3859 c
[*n
] = '\255'; /* write new delimiter */
3860 strtok (c
,"\255"); /* reset the strtok mechanism */
3862 default: /* atomic string */
3863 *n
= strlen (*d
= strtok (c
," "));
3876 /*----------------------------------------------------------------------
3877 Some stuff to help out with the date parsing
3879 char *xdays2
[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
};
3882 month_abbrev2(month_num
)
3885 static char *xmonths
[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
3886 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL
};
3887 if(month_num
< 1 || month_num
> 12)
3889 return(xmonths
[month_num
- 1]);
3892 struct time_zone_names
{
3911 /*----------------------------------------------------------------------
3912 Parse a date string into into a structure
3914 Args: mc -- message cache to with structure to receive data
3915 given_date -- full header with date string somewhere to be found
3917 This parses a number of date formats and produces a canonical date in
3918 a structure. The basic format is:
3920 dd mm yy hh:mm:ss.t tz
3922 It will also handle:
3923 ww dd mm yy hh:mm:ss.t tz mm dd yy hh:mm:ss.t tz
3924 ww dd mm hh:mm:ss.t yy tz mm dd hh:mm:ss.t yy tz
3926 It knows many abbreviations for timezones, but is not complete.
3927 In general absolute time zones in hours +/- GMT are best.
3930 carmel2_rfc822_date(mc
, given_date
)
3936 struct time_zone_names
*tz
;
3947 if(given_date
== NULL
)
3952 if(*p
!= 'D' && strncmp(p
, "Date:",5))
3954 if(*p
== '\n' && *(p
+1) == 'D' && strncmp(p
+1, "Date:", 5) == 0)
3961 p
+= 6; /* Skip "\nDate: " */
3965 /* Start with month, weekday or day ? */
3966 for(i
= xdays2
; *i
!= NULL
; i
++)
3967 if(struncmp2(p
, *i
, 3) == 0) /* Match first 3 letters */
3970 /* Started with week day .. buz over it*/
3971 while(*p
&& !isspace(*p
) && *p
!= ',')
3973 while(*p
&& (isspace(*p
) || *p
== ','))
3978 while(*p
&& isdigit(*p
))
3980 while(*p
&& (*p
== '-' || *p
== ',' || isspace(*p
)))
3983 for(month
= 1; month
<= 12; month
++)
3984 if(struncmp2(p
, month_abbrev2(month
), 3) == 0)
3990 /* Move over month, (or whatever is there) */
3991 while(*p
&& !isspace(*p
) && *p
!= ',' && *p
!= '-')
3993 while(*p
&& (isspace(*p
) || *p
== ',' || *p
== '-'))
3996 /* Check again for day */
3997 if(isdigit(*p
) && mc
->day
== -1) {
3999 while(*p
&& isdigit(*p
))
4001 while(*p
&& (*p
== '-' || *p
== ',' || isspace(*p
)))
4005 /*-- Check for time --*/
4006 for(q
= p
; *q
&& isdigit(*q
); q
++);
4008 /* It's the time (out of place) */
4009 mc
->hours
= atoi(p
);
4010 while(*p
&& *p
!= ':' && !isspace(*p
))
4013 mc
->minutes
= atoi(p
);
4014 while(*p
&& *p
!= ':' && !isspace(*p
))
4017 mc
->seconds
= atoi(p
);
4018 while(*p
&& !isspace(*p
))
4022 while(*p
&& isspace(*p
))
4026 /* Get the year 0-50 is 2000-2050; 50-100 is 1950-1999 and
4027 101-9999 is 101-9999 */
4034 mc
->year
= year
- 1969;
4035 while(*p
&& isdigit(*p
))
4037 while(*p
&& (*p
== '-' || *p
== ',' || isspace(*p
)))
4040 /* Something weird, skip it and try to resynch */
4041 while(*p
&& !isspace(*p
) && *p
!= ',' && *p
!= '-')
4043 while(*p
&& (isspace(*p
) || *p
== ',' || *p
== '-'))
4047 /*-- Now get hours minutes, seconds and ignore tenths --*/
4048 for(q
= p
; *q
&& isdigit(*q
); q
++);
4049 if(*q
== ':' && mc
->hours
== 30) {
4050 mc
->hours
= atoi(p
);
4051 while(*p
&& *p
!= ':' && !isspace(*p
))
4055 mc
->minutes
= atoi(p
);
4056 while(*p
&& *p
!= ':' && !isspace(*p
))
4060 mc
->seconds
= atoi(p
);
4061 while(*p
&& !isspace(*p
) && *p
!= '+' && *p
!= '-')
4066 while(*p
&& isspace(*p
))
4070 /*-- The time zone --*/
4072 if(*p
== '+' || *p
== '-') {
4074 mc
->zoccident
= (*p
== '+' ? 1 : 0);
4079 mc
->zhours
= atoi(tmp
);
4083 mc
->zminutes
*= atoi(tmp
);
4085 for(tz
= tz_names
; tz
->name
!= NULL
; tz
++) {
4086 if(struncmp2(p
, tz
->name
, strlen(tz
->name
)) ==0) {
4087 if(tz
->hours
>= 0) {
4088 mc
->zhours
= tz
->hours
;
4091 mc
->zhours
= -tz
->hours
;
4094 mc
->zminutes
= tz
->minutes
;
4106 /*----------------------------------------------------------------------
4107 Print the date from the MESSAGECACHE into the string
4110 carmel2_date2string(string
, mc
)
4114 sprintf(string
, "%d %s %d %d:%02d:%02d %s%04d",
4115 mc
->day
, month_abbrev2(mc
->month
), 1969+mc
->year
,
4116 mc
->hours
, mc
->minutes
, mc
->seconds
, mc
->zoccident
? "-" : "",
4117 mc
->zhours
* 100 + mc
->zminutes
);
4122 /*----------------------------------------------------------------------
4123 Read the date into a structure from line in Carmel index
4125 Args: mc -- The structure to contain the date (and other things)
4126 string -- String to be parsed. Format is:
4127 "yyyy^Amm^Add^Ahh^Amm^Ass^Azh^Azm"
4131 carmel2_parse_date(mc
, string
)
4136 mc
->year
= next_num(&string
) - 1969;
4137 mc
->month
= next_num(&string
);
4138 mc
->day
= next_num(&string
);
4139 mc
->hours
= next_num(&string
);
4140 mc
->minutes
= next_num(&string
);
4141 mc
->seconds
= next_num(&string
);
4143 n
= next_num(&string
);
4151 mc
->zminutes
= next_num(&string
);
4156 /*----------------------------------------------------------------------
4157 Read the next number out of string and return it, advancing the string
4181 /*----------------------------------------------------------------------
4182 Take a (slightly ugly) FQ mailbox and and return the prettier
4186 carmel_pretty_mailbox(mailbox
)
4191 for(pretty_mb
= mailbox
+ strlen(mailbox
) - 1;
4192 *pretty_mb
!= '#' && pretty_mb
> mailbox
;
4195 if(*pretty_mb
== '#')
4200 /*----------------------------------------------------------------------
4201 Parse a carmel mailbox name into its parts
4203 Args: fq_name: The name to parse
4204 given_version: The version that must match; currently either \0 or '2'
4206 Returns: NULL if not a valid carmel name, version of name and given_version
4207 do not match, or a malloced structure if it is.
4209 struct carmel_mb_name
*
4210 carmel_parse_mb_name(fq_name
, given_version
)
4214 char *p
, *q
, version
[2];
4215 struct carmel_mb_name
*parsed_name
;
4217 if(struncmp2(fq_name
, CARMEL_NAME_PREFIX
, strlen(CARMEL_NAME_PREFIX
))!= 0){
4218 return(0); /* BUG -- we won't work with non-FQN names for now */
4220 version
[0] = given_version
;
4223 if(fq_name
[7] == CARMEL_NAME_CHAR
) {
4226 } else if(fq_name
[8] == CARMEL_NAME_CHAR
) {
4227 version
[0] = fq_name
[7];
4235 if(given_version
!= version
[0])
4238 parsed_name
=(struct carmel_mb_name
*)fs_get(sizeof(struct carmel_mb_name
));
4239 parsed_name
->version
[0] = version
[0];
4240 parsed_name
->version
[1] = version
[1];
4242 /*---- Find second # if there is one ---*/
4243 for(q
= p
; *q
&& *q
!= CARMEL_NAME_CHAR
; q
++);
4245 if(*q
== CARMEL_NAME_CHAR
) {
4246 /*----- There is a user name -----*/
4247 parsed_name
->user
= fs_get((q
- p
) + 1);
4248 strncpy(parsed_name
->user
, p
, q
- p
);
4249 parsed_name
->user
[q
- p
] = '\0';
4252 parsed_name
->user
= NULL
;
4255 parsed_name
->mailbox
= cpystr(p
);
4257 return(parsed_name
);
4262 carmel_free_mb_name(mb_name
)
4263 struct carmel_mb_name
*mb_name
;
4265 if(mb_name
->user
!= NULL
)
4266 fs_give((void **)(&(mb_name
->user
)));
4267 fs_give((void **)(&(mb_name
->mailbox
)));
4268 fs_give((void **)&mb_name
);
4280 /*--------------------------------------------------
4281 A case insensitive strcmp()
4283 Args: o, r -- The two strings to compare
4285 Result: integer indicating which is greater
4288 register char *r
, *o
;
4290 if(r
== NULL
&& o
== NULL
)
4297 while(*o
&& *r
&& (isupper(*o
) ? tolower(*o
) : *o
) ==
4298 (isupper(*r
) ? tolower(*r
) : *r
)) {
4301 return((isupper(*o
) ? tolower(*o
): *o
)-(isupper(*r
) ? tolower(*r
) : *r
));
4306 /*----------------------------------------------------------------------
4307 A case insensitive strncmp()
4309 Args: o, r -- The two strings to compare
4310 n -- length to stop comparing strings at
4312 Result: integer indicating which is greater
4316 register char *r
, *o
;
4319 if(r
== NULL
&& o
== NULL
)
4327 while(n
&& *o
&& *r
&&
4328 (isupper(*o
)? tolower(*o
): *o
) == (isupper(*r
)? tolower(*r
): *r
)) {
4331 return((isupper(*o
)? tolower(*o
): *o
) - (isupper(*r
)? tolower(*r
): *r
));
4334 /*----------------------------------------------------------------------
4335 A replacement for strchr or index ...
4337 ....so we don't have to worry if it's there or not. We bring our own.
4338 If we really care about efficiency and think the local one is more
4339 efficient the local one can be used, but most of the things that take
4340 a long time are in the c-client and not in pine.
4343 strindex2(buffer
, ch
)
4347 /** Returns a pointer to the first occurance of the character
4348 'ch' in the specified string or NULL if it doesn't occur **/
4353 while (*buffer
++ != '\0');
4359 /*======================================================================
4378 carmel_match_glyph_wide(string
)
4381 extern char *ptspecials
;
4384 if(struncmp2(string
, "content-type", 12))
4385 return(0); /* Nope */
4387 while(*string
&& isspace(*string
)) string
++;
4391 while(*string
&& isspace(*string
)) string
++;
4393 string
= rfc822_parse_word(string
, ptspecials
);
4396 if(struncmp2(s
, "text", 4))
4398 while(*string
&& isspace(*string
)) string
++;
4402 while(*string
&& isspace(*string
)) string
++;
4404 string
= rfc822_parse_word(string
, ptspecials
);
4407 if(struncmp2(s
, "x-bwc-glyph-wide", 16))