Add support for tab-completion when selecting by rule
[alpine.git] / contrib / carmel / c-client / carmel2.c
blob4f71a187e11e3392862c7007495a07268ff6f9c2
1 #define TESTING
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
7 Baha'i World Centre
8 Data Processing
9 Haifa, Israel
10 Internet: lgl@cac.washington.edu or laurence@bwc.org
11 September 1992
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
24 + Expunge is fast
25 + Check point is very fast
26 + Memory usage is much lower
27 + Search of message headers is fast
28 Disadvantages:
29 - Fetching a large message is slow
30 - Searching the message bodies is slow
31 - Sorting the mailbox is slow
33 Index File Format
34 -----------------
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______________________________
42 Zxxxxxx
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
99 SUPPORT FUNCTIONS
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 ----------------------------------------------------------------------*/
139 #include <stdio.h>
140 #include <ctype.h>
141 #include <pwd.h>
142 #include <netdb.h>
143 #include <errno.h>
144 extern int errno; /* just in case */
145 #include "mail.h"
146 #include "osdep.h"
147 #include <sys/dir.h>
148 #include <sys/file.h>
149 #include <sys/stat.h>
150 #include <sys/time.h>
151 #include "carmel2.h"
152 #include "rfc822.h"
153 #include "misc.h"
156 char *strindex2(), *strchr();
158 #ifdef ANSI
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 **);
205 #else
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();
252 #endif
255 /*------ Driver dispatch used by MAIL -------*/
257 DRIVER carmel2driver = {
258 "carmel2",
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 */
270 carmel2_create,
271 carmel2_delete,
272 carmel2_rename,
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
310 Args: - mailbox name
312 Returns: our driver if name is valid, otherwise calls valid in next driver
313 ---*/
315 DRIVER *carmel2_valid (name)
316 char *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
332 ----*/
334 carmel2_isvalid (name)
335 char *name;
337 char *carmel_index_file;
338 struct stat sbuf;
339 int fd;
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)
345 return;
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))
356 return(0);
358 fd = open(carmel_index_file, O_RDONLY);
359 if(fd < 0)
360 return(0);
362 if(read(fd, carmel_20k_buf, 200) <= 0)
363 return(0);
365 close(fd);
367 if(strncmp(carmel_20k_buf, carmel2_s_o_f, strlen(carmel2_s_o_f)))
368 return(0);
370 return(1);
375 /*----------------------------------------------------------------------
376 Set parameters for the carmel driver.
378 Currently does nothing
379 ----*/
380 void *
381 carmel2_parameters()
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
396 This needs testing
397 ----*/
399 void
400 carmel2_find (stream, pat)
401 MAILSTREAM *stream;
402 char *pat;
404 char tmp[MAILTMPLEN];
405 struct direct **namelist, **n;
406 int num;
407 extern int alphasort();
408 struct carmel_mb_name *parsed_name;
409 struct passwd *pw;
411 parsed_name = carmel_parse_mb_name(pat, '2');
412 if(parsed_name == NULL)
413 return;
415 if(parsed_name->user == NULL) {
416 sprintf(tmp, "%s/%s", myhomedir(), CARMEL2_INDEX_DIR);
417 } else {
418 pw = getpwnam(parsed_name->user);
419 if(pw == NULL) {
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);
424 return;
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);
433 if(num <= 0) {
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); */
438 return;
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);
445 } else {
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,
449 (*n)->d_name);
451 mm_mailbox(tmp);
452 free(*n);
454 free(namelist);
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
463 ---*/
464 void
465 carmel2_find_all (stream, pat)
466 MAILSTREAM *stream;
467 char *pat;
469 carmel2_find(stream, pat);
474 /*----------------------------------------------------------------------
475 Carmel2 mail find list of bboards; always NULL, no bboards
476 ----*/
477 void carmel2_find_bboards (stream,pat)
478 MAILSTREAM *stream;
479 char *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
488 ----*/
489 void carmel2_find_all_bboards (stream,pat)
490 MAILSTREAM *stream;
491 char *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
501 ----*/
502 static int
503 carmel2_sift_files(dir)
504 struct direct *dir;
506 if(dir->d_name[0] == '.')
507 return(0);
508 else
509 return(1);
514 /*----------------------------------------------------------------------
515 Dummy subscribe/unsubscribe routines.
516 Carmel driver does support this. Maybe it should support the UNIX
517 sm sometime
518 ----*/
519 long carmel2_subscribe() {}
520 long carmel2_unsubscribe() {}
521 long carmel2_subscribe_bboard() {}
522 long carmel2_unsubscribe_bboard() {}
526 /*----------------------------------------------------------------------
527 ----*/
528 long
529 carmel2_create(stream, mailbox)
530 MAILSTREAM *stream;
531 char *mailbox;
537 /*----------------------------------------------------------------------
538 ----*/
539 long
540 carmel2_delete(stream, mailbox)
541 MAILSTREAM *stream;
542 char *mailbox;
548 /*----------------------------------------------------------------------
549 ----*/
550 long
551 carmel2_rename(stream, orig_mailbox, new_mailbox)
552 MAILSTREAM *stream;
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
566 ----*/
568 MAILSTREAM *
569 carmel2_open (stream)
570 MAILSTREAM *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 */
578 if (LOCAL) {
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;
597 stream->msgno = -1;
598 stream->env = NULL;
599 stream->body = NULL;
600 stream->scache = 1;
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)
607 return(NULL);
609 mail_exists (stream,stream->nmsgs);
610 mail_recent (stream,stream->recent);
612 return(stream);
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
624 0 if it succeeds
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
638 deemed corrupt.
639 ---*/
640 carmel_open2(stream, file)
641 MAILSTREAM *stream;
642 char *file;
644 long file_pos, recent;
645 MESSAGECACHE *mc;
646 struct stat sb;
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);
651 return(-1);
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);
660 return(-1);
663 recent = 0;
664 stream->nmsgs = 0;
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);
673 return(-1);
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);
680 return(-1);
683 /* Bug, this doesn't really do the recent correctly */
685 stream->recent = recent;
686 return(0);
691 /*----------------------------------------------------------------------
692 Carmel mail close
694 Args: stream -- stream to close
696 ----*/
698 void
699 carmel2_close (stream)
700 MAILSTREAM *stream;
702 if (LOCAL) { /* only if a file is open */
703 carmel2_check (stream); /* dump final checkpoint */
704 if(LOCAL->msg_buf)
705 fs_give ((void **) &LOCAL->msg_buf);
706 if(LOCAL->stdio_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
721 ----*/
722 void
723 carmel2_fetchfast (stream, sequence)
724 MAILSTREAM *stream;
725 char *sequence;
727 return;
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
737 ----*/
738 void
739 carmel2_fetchflags(stream, sequence)
740 MAILSTREAM *stream;
741 char *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
759 data is small.
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)
764 ----*/
766 ENVELOPE *
767 carmel2_fetchstructure (stream, msgno, body)
768 MAILSTREAM *stream;
769 long msgno;
770 BODY **body;
772 MESSAGECACHE *mc;
773 ENVELOPE **env;
774 BODY **b;
776 env = &stream->env;
777 b = &stream->body;
779 if (msgno != stream->msgno){
780 /* flush old poop if a different message */
781 mail_free_envelope (env);
782 mail_free_body (b);
784 stream->msgno = msgno;
786 mc = MC(msgno);
788 if(*env == NULL) {
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) {
804 case 'F':
805 (*env)->from = carmel2_parse_address(carmel_20k_buf,
806 (*env)->from,
807 mylocalhost());
808 break;
809 case 'T':
810 (*env)->to = carmel2_parse_address(carmel_20k_buf,
811 (*env)->to,
812 mylocalhost());
813 break;
814 case 'C':
815 (*env)->cc = carmel2_parse_address(carmel_20k_buf,
816 (*env)->cc,
817 mylocalhost());
818 break;
819 case 'B':
820 (*env)->bcc = carmel2_parse_address(carmel_20k_buf,
821 (*env)->bcc,
822 mylocalhost());
823 break;
824 case 'E':
825 (*env)->sender = carmel2_parse_address(carmel_20k_buf,
826 (*env)->sender,
827 mylocalhost());
828 break;
829 case 'R':
830 (*env)->reply_to = carmel2_parse_address(carmel_20k_buf,
831 (*env)->reply_to,
832 mylocalhost());
833 break;
834 case 'H':
835 (*env)->return_path =carmel2_parse_address(carmel_20k_buf,
836 (*env)->return_path,
837 mylocalhost());
838 break;
839 case 'J':
840 (*env)->subject = cpystr(carmel_20k_buf+1);
841 break;
842 case 'I':
843 (*env)->message_id = cpystr(carmel_20k_buf+1);
844 break;
845 case 'L':
846 (*env)->in_reply_to = cpystr(carmel_20k_buf+1);
847 break;
848 case 'N':
849 (*env)->newsgroups = cpystr(carmel_20k_buf+1);
850 break;
851 case 'r':
852 (*env)->remail = cpystr(carmel_20k_buf+1);
853 break;
854 default:
855 break;
860 if(body != NULL) {
861 if(*b == NULL) {
862 /* Have to fetch the body structure too */
863 *b = carmel2_bodystruct(stream, mc);
865 *body = *b;
868 return(*env); /* return the envelope */
874 /*----------------------------------------------------------------------
875 Carmel mail fetch message header
877 Args: stream --
878 msgno
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.
884 ----*/
886 char *
887 carmel2_fetchheader (stream, msgno)
888 MAILSTREAM *stream;
889 long msgno;
891 char *header;
892 MESSAGECACHE *mc;
894 mc = MC(msgno);
895 header = carmel2_readmsg(stream, 1, 0, mc->data2);
897 return(header == NULL ? "" : header);
902 /*----------------------------------------------------------------------
903 Carmel mail fetch message text (only)
905 Args: stream --
906 msgno
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.
912 ----*/
914 char *
915 carmel2_fetchtext(stream, msgno)
916 MAILSTREAM *stream;
917 long msgno;
919 MESSAGECACHE *mc;
920 char *m;
922 mc = MC(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);
931 return(m);
936 /*----------------------------------------------------------------------
937 Fetch MIME body parts
939 Args: stream
940 msgno
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
945 ---*/
947 char *
948 carmel2_fetchbody (stream, msgno, section, len)
949 MAILSTREAM *stream;
950 long msgno;
951 char *section;
952 unsigned long *len;
954 char *base;
955 BODY *b;
956 PART *part;
957 int part_num;
958 long offset;
959 MESSAGECACHE *mc;
961 if (carmel2_fetchstructure(stream, msgno, &b) == NULL || b == NULL)
962 return(NULL);
964 if(section == NULL || *section == '\0')
965 return(NULL);
967 part_num = strtol(section, &section, 10);
968 if(part_num <= 0)
969 return(NULL);
971 base = carmel2_fetchtext(stream, msgno);
972 if(base == NULL)
973 base = "";
974 offset = 0;
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));
980 if (!part)
981 return (NIL); /* bad specifier */
983 /* Found part, get ready to go further */
984 b = &part->body;
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? */
996 if(*section == 0) {
997 break;
998 } else {
999 switch (b->type) {
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, &section,10)) > 0)
1008 break; /* Found the part */
1010 default: /* bogus subpart specification */
1011 return(NULL);
1014 } while (part_num);
1016 mc = MC(msgno);
1017 /* lose if body bogus */
1018 if ((!b) || b->type == TYPEMULTIPART)
1019 return(NULL);
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
1035 * sequence
1036 * flag(s)
1039 void
1040 carmel2_setflag (stream,sequence,flag)
1041 MAILSTREAM *stream;
1042 char *sequence;
1043 char *flag;
1045 MESSAGECACHE *elt;
1046 long i;
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++){
1052 elt = MC(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
1060 really changed */
1061 LOCAL->dirty = T;
1068 /*----------------------------------------------------------------------
1069 * Carmel mail clear flag
1070 * Accepts: MAIL stream
1071 * sequence
1072 * flag(s)
1075 void
1076 carmel2_clearflag (stream,sequence,flag)
1077 MAILSTREAM *stream;
1078 char *sequence;
1079 char *flag;
1081 MESSAGECACHE *elt;
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++) {
1088 elt = MC(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
1097 really changed */
1098 LOCAL->dirty = T; /* mark stream as dirty */
1105 /* Carmel mail search for messages
1106 * Accepts: MAIL stream
1107 * search criteria
1110 void
1111 carmel2_search (stream,criteria)
1112 MAILSTREAM *stream;
1113 char *criteria;
1115 long i,n;
1116 char *d;
1117 search_t f;
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;
1130 break;
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);
1138 break;
1139 case 'C': /* possible CC */
1140 if (!strcmp (criteria+1,"C"))
1141 f = carmel2_search_string (carmel2_search_cc,&d,&n);
1142 break;
1143 case 'D': /* possible DELETED */
1144 if (!strcmp (criteria+1,"ELETED")) f = carmel2_search_deleted;
1145 break;
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);
1150 break;
1151 case 'K': /* possible KEYWORD */
1152 if (!strcmp (criteria+1,"EYWORD"))
1153 f = carmel2_search_flag (carmel2_search_keyword,&d);
1154 break;
1155 case 'N': /* possible NEW */
1156 if (!strcmp (criteria+1,"EW")) f = carmel2_search_new;
1157 break;
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);
1163 break;
1164 case 'R': /* possible RECENT */
1165 if (!strcmp (criteria+1,"ECENT")) f = carmel2_search_recent;
1166 break;
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);
1173 break;
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);
1179 break;
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;
1189 break;
1190 default: /* we will barf below */
1191 break;
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);
1196 return;
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
1218 long
1219 carmel2_ping (stream)
1220 MAILSTREAM *stream;
1222 struct stat sb;
1223 char path[1000], *mailfile;
1224 #ifdef TESTING
1225 char debug_buf[1000];
1226 #endif
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;
1233 return NIL;
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) {
1240 #ifdef TESTING
1241 mm_log("!!!!! Carmel index grew", NIL);
1242 #endif
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;
1253 return NIL;
1255 #endif
1256 if(carmel2_parse_mail(stream, LOCAL->index_size) < 0) {
1257 mm_log("Mailbox corrupted. Mailbox closed", ERROR);
1258 stream->readonly = 1;
1259 return NIL;
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);
1268 return NIL;
1271 #ifdef TESTING
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,
1276 LOCAL->carmel);
1277 mm_log(debug_buf, NIL);
1278 #endif
1280 if(!stream->readonly &&
1281 ((LOCAL->carmel &&
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());
1287 #ifdef TESTING
1288 sprintf(debug_buf, "!!!!! Checking spool mail\"%s\"", path);
1289 mm_log(debug_buf, NIL);
1290 #endif
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);
1297 return(T);
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),
1304 &sb);
1305 LOCAL->index_size = sb.st_size;
1309 return T;
1316 /*----------------------------------------------------------------------
1317 This checks for new mail and checkpoints the mail file
1319 Args -- The stream to check point
1321 ----*/
1322 void
1323 carmel2_check(stream)
1324 MAILSTREAM *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.
1335 ----*/
1336 carmel2_check2(stream)
1337 MAILSTREAM *stream;
1339 int msgno;
1340 MESSAGECACHE *mc;
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) */
1347 if(!LOCAL->dirty)
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++) {
1357 mc = MC(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);
1374 return(1);
1381 /*----------------------------------------------------------------------
1382 Carmel2 mail expunge mailbox
1384 Args: stream -- mail stream to expunge
1386 ----*/
1388 void carmel2_expunge (stream)
1389 MAILSTREAM *stream;
1391 char *index_file;
1392 long msgno, new_msgno;
1393 FILE *new_index;
1394 MESSAGECACHE *mc, *new_mc;
1395 ENVELOPE *envelope;
1396 int save_e;
1397 struct stat sb;
1399 if (stream->readonly) {
1400 if(!stream->silent)
1401 mm_log ("Expunge ignored on readonly mailbox",NIL);
1402 return;
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) {
1413 goto fail;
1415 if(fprintf(new_index, carmel2_s_o_f) == EOF)
1416 goto fail;
1418 new_msgno = 1;
1419 for(msgno = 1; msgno <= stream->nmsgs; msgno++) {
1420 mc = MC(msgno);
1421 if(mc->deleted) {
1422 mm_expunged(stream, new_msgno);
1423 continue;
1426 if(msgno != new_msgno) {
1427 new_mc = MC(new_msgno);
1428 *new_mc = *mc;
1429 new_mc->msgno = new_msgno;
1430 mc = new_mc;
1433 envelope = carmel2_fetchstructure(stream, msgno, NULL);
1434 if(envelope == NULL)
1435 goto fail;
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)
1441 goto fail;
1442 new_msgno++;
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 ---*/
1450 unlink(index_file);
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);
1466 return;
1468 fail:
1469 save_e = errno;
1470 if(new_index != NULL)
1471 fclose(new_index);
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);
1477 return;
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
1490 ----*/
1491 long
1492 carmel2_copy(stream, sequence, mailbox)
1493 MAILSTREAM *stream;
1494 char *sequence;
1495 char *mailbox;
1497 ENVELOPE *e;
1498 MESSAGECACHE *mc, new_mc;
1499 long msgno, file_no, file_pos;
1500 int fail;
1501 char *file_name, *line;
1502 FILE *dest_stream;
1503 struct stat sb;
1505 if (!mail_sequence (stream,sequence)) /* get sequence to do */
1506 return(NIL);
1508 /*--- Get the file open (creating if needed) first ----*/
1509 file_name = (*LOCAL->calc_paths)(CalcPathCarmel2Index, mailbox, 0, 0);
1510 if(file_name == NULL)
1511 return(NIL);
1513 if(stat(file_name, &sb) < 0) {
1514 mm_notify (stream,"[TRYCREATE] Must create mailbox before copy", NIL);
1515 return(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);
1523 return(NIL);
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",
1532 mailbox);
1533 mm_log(carmel_error_buf, ERROR);
1534 fclose(dest_stream);
1535 return(NIL);
1539 /*--- Get the destination Carmel index open ----*/
1540 file_pos = ftell(dest_stream);
1541 fail = 0;
1544 for(msgno = 1; msgno <= stream->nmsgs; msgno++) {
1545 mc = MC(msgno);
1546 if(!mc->sequence)
1547 continue;
1549 new_mc = *mc;
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) {
1554 fail = 1;
1555 break;
1559 e = carmel2_fetchstructure(stream, msgno, NULL);
1560 if(carmel2_write_index(e, &new_mc, dest_stream) < 0) {
1561 fail = 1;
1562 break;
1565 if(LOCAL->carmel && LOCAL->aux_copy != NULL) {
1566 if((*LOCAL->aux_copy)(stream->local, mailbox, e, &new_mc)) {
1567 fail = 1;
1568 break;
1573 if(fail) {
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
1593 ----*/
1595 long
1596 carmel2_move (stream,sequence,mailbox)
1597 MAILSTREAM *stream;
1598 char *sequence;
1599 char *mailbox;
1601 long i;
1602 MESSAGECACHE *elt;
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++)
1608 elt = MC(i);
1609 if (elt->sequence) {
1610 elt->deleted = T; /* mark message deleted */
1611 LOCAL->dirty = T; /* mark mailbox as dirty */
1613 return T;
1618 /*----------------------------------------------------------------------
1619 * Carmel2 garbage collect stream
1620 * Accepts: Mail stream
1621 * garbage collection flags
1624 void carmel2_gc (stream, gcflags)
1625 MAILSTREAM *stream;
1626 long gcflags;
1628 /* No garbage collection in Carmel2 driver, not much to collect */
1633 /*----------------------------------------------------------------------
1634 Handle MessageCache for carmel2 mail driver
1636 Args: stream --
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
1642 that has to go on.
1643 ----*/
1644 void *
1645 carmel2_cache(stream, msgno, op)
1646 MAILSTREAM *stream;
1647 long msgno;
1648 long op;
1650 /* It's a carmel driver if first 6 letters of name are carmel */
1651 if(stream->dtb == NULL)
1652 return(0);
1654 if(struncmp2(stream->dtb->name, "carmel", 6) == 0) {
1655 if(op == CH_MAKEELT)
1656 return(MC(msgno));
1657 else
1658 return(0);
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
1663 mm_cache().
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
1677 ----*/
1678 long
1679 carmel2_append(stream, mailbox, flags, date, message)
1680 MAILSTREAM *stream;
1681 char *mailbox, *flags, *date;
1682 STRING *message;
1684 CARMEL2LOCAL local;
1686 /*---- A fake local data structure to pass to other functions---*/
1687 local.calc_paths = carmel2_calc_paths;
1688 local.carmel = 0;
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.
1707 ----*/
1708 static BODY *
1709 carmel2_bodystruct(stream, mc)
1710 MESSAGECACHE *mc;
1711 MAILSTREAM *stream;
1713 char *header, *text, *file, *p;
1714 ENVELOPE *e_place_holder;
1715 BODY *b;
1716 STRING string_struct;
1718 header = carmel2_readmsg(stream, 1, mc->rfc822_size, mc->data2);
1719 if(header == NULL)
1720 return(NULL);
1722 text = carmel2_readmsg(stream, 0, mc->rfc822_size, mc->data2);
1723 if(text == NULL)
1724 return(NULL);
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);
1732 #ifdef BWC
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)
1739 break;
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");
1752 #endif
1754 return(b);
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
1766 ----*/
1767 static ADDRESS *
1768 carmel2_parse_address(line, addr, localhost)
1769 char *line, *localhost;
1770 ADDRESS *addr;
1772 ADDRESS *a;
1774 if(addr == NULL) {
1775 addr = mail_newaddr();
1776 a = addr;
1777 } else {
1778 for(a = addr; a!= NULL && a->next != NULL; a = a->next);
1779 a->next = mail_newaddr();
1780 a = a->next;
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 */
1791 return(addr);
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
1806 ----*/
1807 static char *
1808 carmel2_parse_addr_field(string)
1809 char **string;
1811 char *p, end, *start;
1813 start = p = *string;
1814 while(*p > '\001') /* Find end of sub string or string */
1815 p++;
1816 if((p - *string) == 0) {
1817 if(*p) p++;
1818 *string = p;
1819 return(NULL); /* If nothing found return nothing */
1821 end = *p; /* Save terminating character (^A or \0) */
1822 *p = '\0';
1823 if(end) /* If not end of string, get ready for next call */
1824 p++;
1825 *string = p; /* Return pointer to next substring */
1826 return(cpystr(start));
1832 /*----------------------------------------------------------------------
1833 Write an entry into a carmel2 index
1835 Args: e -- Envelope
1836 mc -- Message Cache element
1837 stream -- File stream to write to
1839 Returns: 0 if OK, -1 if failed
1840 ----*/
1841 carmel2_write_index(e, mc, stream)
1842 ENVELOPE *e;
1843 MESSAGECACHE *mc;
1844 FILE *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)
1851 goto blah;
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)
1858 goto blah;
1859 if(fprintf(stream, "Z%d\n", mc->rfc822_size) == EOF)
1860 goto blah;
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)
1865 goto blah;
1866 if(fprintf(stream, "Svmail\n") == EOF)
1867 goto blah;
1868 if(fprintf(stream, "P%d\n",mc->data2) == EOF)
1869 goto blah;
1870 if(carmel2_index_address(e->to, 'T', stream) < 0)
1871 goto blah;
1872 if(carmel2_index_address(e->from, 'F', stream) < 0)
1873 goto blah;
1874 if(carmel2_index_address(e->cc, 'C', stream) < 0)
1875 goto blah;
1876 if(carmel2_index_address(e->bcc, 'B', stream) < 0)
1877 goto blah;
1878 #ifdef HAVE_RESENT
1879 if(carmel2_index_address(e->resent_to, 't', stream) < 0)
1880 goto blah;
1881 if(carmel2_index_address(e->resent_from, 'f', stream) < 0)
1882 goto blah;
1883 if(carmel2_index_address(e->resent_cc, 'c', stream) < 0)
1884 goto blah;
1885 if(carmel2_index_address(e->resent_bcc, 'b', stream) < 0)
1886 goto blah;
1887 #endif
1888 if(carmel2_index_address(e->return_path, 'H', stream) < 0)
1889 goto blah;
1890 if(carmel2_index_address(e->sender, 'E', stream) < 0)
1891 goto blah;
1892 if(carmel2_index_address(e->reply_to, 'R', stream) < 0)
1893 goto blah;
1894 if(e->in_reply_to != NULL)
1895 if(fprintf(stream, "L%s\n", e->in_reply_to) == EOF)
1896 goto blah;
1897 if(e->remail != NULL)
1898 if(fprintf(stream, "r%s\n", e->remail) == EOF)
1899 goto blah;
1900 if(e->message_id != NULL)
1901 if(fprintf(stream, "I%s\n", e->message_id) == EOF)
1902 goto blah;
1903 if(e->newsgroups != NULL)
1904 if(fprintf(stream, "N%s\n", e->newsgroups) == EOF)
1905 goto blah;
1906 if(e->subject != NULL)
1907 if(fprintf(stream, "J%s\n", e->subject) == EOF)
1908 goto blah;
1910 /*--- figure out and write the offset ---*/
1911 f_end = ftell(stream);
1912 if(fseek(stream, f_start, 0) < 0)
1913 goto blah;
1914 if(fprintf(stream, "%s\007\001%10d\n", carmel2_s_o_m, f_end - f_start)==EOF)
1915 goto blah;
1916 if(fseek(stream, f_end, 0) < 0)
1917 goto blah;
1919 return(0);
1921 blah:
1922 /* Index entry is a bit of a mess now. Maybe we should try to truncate */
1923 return(-1);
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
1936 ---*/
1937 static
1938 carmel2_index_address(addr, field, stream)
1939 ADDRESS *addr;
1940 int field;
1941 FILE *stream;
1943 ADDRESS *a;
1945 for(a = addr; a != NULL; a = a->next) {
1946 if(fprintf(stream, "%c%s\001%s\001%s\001%s\n",
1947 field,
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)
1952 return(-1);
1954 return(0);
1959 /*----------------------------------------------------------------------
1960 Real work of reading mail data files
1962 Args: stream
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.
1985 ----*/
1987 char *
1988 carmel2_readmsg(stream, header_only, file_size, file_num)
1989 MAILSTREAM *stream;
1990 int header_only;
1991 int file_num;
1992 long file_size;
1994 FILE *msg_stream;
1995 char *p, *p_end, *file_name;
1996 int past_header, not_eof;
1997 long max_read;
1998 struct stat st;
1999 #define DEBUGDEBUG 1
2000 #ifdef DEBUGDEBUG
2001 char debug_buf[500];
2002 #endif
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 */
2008 #ifdef DEBUGDEBUG
2009 sprintf(debug_buf, "READ RQ:\"%s\" HV:\"%s\" RQ_HD:%d HV_TXT:%d\n",
2010 file_name,
2011 LOCAL->buffered_file == NULL ? "" : LOCAL->buffered_file,
2012 header_only, LOCAL->buffered_header_and_text);
2013 mm_log(debug_buf, NIL);
2014 #endif
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) {
2023 if(header_only) {
2024 #ifdef DEBUGDEBUG
2025 mm_log("....Returning buffered header\n", NIL);
2026 #endif
2027 return(LOCAL->msg_buf);
2028 } else {
2029 #ifdef DEBUGDEBUG
2030 mm_log("....Returning buffered text\n", NIL);
2031 #endif
2032 return(LOCAL->msg_buf_text_start);
2035 } else {
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));
2043 #ifdef DEBUGDEBUG
2044 mm_log("....Reading file\n", NIL);
2045 #endif
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)
2054 return(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
2065 the header.
2067 max_read = (file_size * 11) / 10;
2068 past_header = 0;
2069 p = LOCAL->msg_buf;
2070 if(header_only) {
2071 max_read = min(max_read, CARMEL_MAX_HEADER);
2072 } else if(LOCAL->msg_buf_text_offset > 0) {
2073 past_header = 1;
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;
2079 if(max_read == 0)
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
2085 strangest cases.
2087 do {
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);
2096 past_header = 0;
2097 p = LOCAL->msg_buf;
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) {
2104 *p++ = '\r';
2105 *p++ = '\n';
2106 *p++ = '\0';
2107 past_header = 1;
2108 LOCAL->msg_buf_text_offset = ftell(msg_stream);
2109 LOCAL->msg_buf_text_start = p;
2110 if(header_only)
2111 goto done;
2112 else
2113 continue;
2115 p += strlen(p) - 1;
2116 *p++ = '\r';
2117 *p++ = '\n';
2119 *p = '\0';
2120 if(!not_eof)
2121 goto done;
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);
2131 } while (1);
2132 done:
2133 if(p == p_end)
2134 mm_log("Message truncated. It's is too large", WARN);
2136 fclose(msg_stream);
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
2153 -1 if it fails
2154 ----*/
2155 static int
2156 carmel2_parse_mail(stream, file_pos)
2157 MAILSTREAM *stream;
2158 long file_pos;
2160 MESSAGECACHE *mc;
2161 long nmsgs, next, last_line_read;
2162 int found;
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)
2169 goto done_reading;
2170 if(strncmp(carmel_20k_buf, carmel2_s_o_m, carmel2_s_o_m_len) != 0)
2171 goto bomb;
2173 while(1) {
2174 if(strlen(carmel_20k_buf) != carmel2_s_o_m_len + 13)
2175 goto bomb;
2177 nmsgs++;
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)
2185 goto done_reading;
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';
2192 mc->recent = 0;
2194 /*---- Get the rest of the other interesting entries -----*/
2195 found = 0;
2196 while(fgets(carmel_20k_buf,sizeof(carmel_20k_buf),
2197 LOCAL->index_stream) != NULL &&
2198 found < 3 &&
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);
2202 found++;
2203 } else if(*carmel_20k_buf == 'D') {
2204 carmel2_parse_date(mc, carmel_20k_buf+1);
2205 found++;
2206 } else if(*carmel_20k_buf == 'P') {
2207 mc->data2 = atoi(carmel_20k_buf+1);
2208 found++;
2211 /*-------- Now find the next index entry ---------*/
2212 last_line_read = ftell(LOCAL->index_stream);
2213 file_pos += next;
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)
2217 break;
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);
2221 do {
2222 file_pos = ftell(LOCAL->index_stream);
2223 if(fgets(carmel_20k_buf, sizeof(carmel_20k_buf),
2224 LOCAL->index_stream) == NULL)
2225 goto done_reading;
2226 }while(strncmp(carmel_20k_buf,carmel2_s_o_m,carmel2_s_o_m_len)!=0);
2229 done_reading:
2230 if(stream->nmsgs != nmsgs) {
2231 stream->nmsgs = nmsgs;
2232 mm_exists(stream, nmsgs);
2234 return(0);
2236 bomb:
2237 return(-1);
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) && \
2267 (x-s >= 27) && \
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
2357 Result:
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.
2374 ----*/
2375 void
2376 carmel2_spool_mail(stream, spool, mailbox, clear_spool_file)
2377 MAILSTREAM *stream;
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;
2385 ENVELOPE *envelope;
2386 BODY *b;
2387 MESSAGECACHE *mc;
2388 STRING string_struct;
2389 #ifdef BWC
2390 int is_wide;
2391 #endif
2393 /*--- Get the locks set and files open-----*/
2394 fd = carmel2_bezerk_lock(spool, mailbox);
2395 if(fd < 0) {
2396 return;
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)
2426 goto backout;
2427 file_pos = ftell(spool_stream);
2428 p = LOCAL->msg_buf;
2429 in_header = 1;
2430 byte_count = 0L;
2431 #ifdef BWC
2432 is_wide = 0;
2433 #endif
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
2440 ---*/
2441 eof = fgets(carmel_20k_buf, sizeof(carmel_20k_buf), spool_stream);
2442 while(eof != NULL){
2443 if(VALID(carmel_20k_buf, from_p, from_i1, from_i2))
2444 break;
2446 if(in_header) {
2447 #ifdef BWC
2448 is_wide |= carmel_match_glyph_wide(carmel_20k_buf);
2449 #endif
2450 if(*carmel_20k_buf == '\n') {
2451 /* Encountered first blank line, end of header */
2452 in_header = 0;
2453 *p = '\0';
2454 } else {
2455 if(p - LOCAL->msg_buf + strlen(carmel_20k_buf) >
2456 LOCAL->msg_buf_size){
2457 /* out of room in buffer, end it */
2458 in_header = 0;
2459 *p = '\0';
2460 } else {
2461 strcpy(p, carmel_20k_buf);
2462 p +=strlen(p);
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)
2473 goto backout;
2474 byte_count -= 1;
2475 } else {
2476 if(fputs(carmel_20k_buf, data_stream) == EOF)
2477 goto backout;
2479 eof = fgets(carmel_20k_buf, sizeof(carmel_20k_buf), spool_stream);
2481 fclose(data_stream);
2482 #ifdef BWC
2483 if(is_wide) {
2484 sprintf(carmel_path_buf, "%s.wid",
2485 (*LOCAL->calc_paths)(CalcPathCarmel2Data,stream->mailbox,n)
2487 rename((*LOCAL->calc_paths)(CalcPathCarmel2Data,stream->mailbox,n),
2488 carmel_path_buf);
2490 #endif
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;
2502 mc->data2 = n;
2503 mc->data1 = ftell(LOCAL->index_stream);
2504 mc->recent = 1;
2506 /*----- Now add the message to the Carmel2 index ------*/
2507 if(carmel2_write_index(envelope, mc, LOCAL->index_stream) < 0)
2508 goto backout;
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 */
2515 goto backout;
2519 /*---- All went well, let the user know -----*/
2520 stream->nmsgs++;
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) {
2529 unlink(spool);
2530 close(creat(spool, 0600));
2532 carmel2_bezerk_unlock(fd, spool);
2533 return;
2535 backout:
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,
2553 ----*/
2554 static
2555 carmel2_copy_msg_file(stream, orig_file_number, new_mailbox)
2556 MAILSTREAM *stream;
2557 int orig_file_number;
2558 char *new_mailbox;
2560 char *file_name;
2561 int wide, e, new_file_num, n, orig_fd, new_fd;
2563 /*---- Open the original file ----*/
2564 wide = 0;
2565 file_name = (*LOCAL->calc_paths)(CalcPathCarmel2Data,
2566 new_mailbox,orig_file_number);
2567 if(file_name == NULL)
2568 return(-1);
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);
2574 if(orig_fd < 0)
2575 goto bomb;
2576 wide = 1;
2577 } else {
2578 goto bomb;
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)
2584 goto bomb;
2585 file_name = (*LOCAL->calc_paths)(CalcPathCarmel2Data,
2586 new_mailbox, new_file_num);
2587 if(wide)
2588 strcat(file_name, ".wid");
2589 new_fd = open(file_name, O_WRONLY | O_CREAT, 0600);
2590 if(new_fd < 0) {
2591 goto bomb;
2594 /*---- Copy the bits ------*/
2595 e = 0;
2596 while((n = read(orig_fd, carmel_20k_buf, sizeof(carmel_20k_buf))) >0) {
2597 if(write(new_fd, carmel_20k_buf, n) < 0) {
2598 e = errno;
2599 break;
2603 /*---- Close the streams and handle any errors ----*/
2604 close(orig_fd);
2605 close(new_fd);
2607 if(e == 0)
2608 return(new_file_num); /* All is OK */
2610 /*--- something didn't go right ---*/
2611 bomb:
2612 unlink(file_name);
2613 sprintf(carmel_error_buf, "Error copying message to %s: %s",
2614 new_mailbox, strerror(errno));
2615 mm_log(carmel_error_buf, ERROR);
2616 return(-1);
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
2659 process is started.
2660 ----*/
2662 carmel2_lock(local, file, write)
2663 CARMEL2LOCAL *local;
2664 char *file;
2665 int write;
2667 char *hitch, lock[CARMEL_PATHBUF_SIZE], error_mess[200], *l_path;
2668 struct stat sb;
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);
2677 timer = -1;
2679 /*----- Get the lock file and hitch names -----*/
2680 l_path = (*local->calc_paths)(write ? CalcPathCarmel2WriteLock:
2681 CalcPathCarmel2ReadLock,
2682 file, 0);
2683 if(l_path == NULL)
2684 return(-1);
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);
2689 l = strlen(hitch);
2691 do {
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);
2700 if(hitch_fd < 0) {
2701 sprintf(error_mess, "Error creating lock file \"%s\": %s",
2702 hitch, strerror(errno));
2703 mm_log(error_mess, WARN);
2704 return(-1);
2706 close(hitch_fd);
2708 /*----- Got a hitching post, now try link -----*/
2709 link_result = link(hitch, lock);
2710 stat(lock, &sb);
2711 unlink(hitch);
2712 if(link_result == 0 && sb.st_nlink == 2) {
2713 /*------ Got the lock! ------*/
2714 stat(lock, &sb);
2715 if(write)
2716 local->write_lock_mod_time = sb.st_mtime;
2717 else
2718 local->read_lock_mod_time = sb.st_mtime;
2719 return(0);
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 */
2726 continue;
2727 } else {
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 -------*/
2738 if(!(timer % 15)) {
2739 sprintf(error_mess,
2740 "Please wait. Mailbox %s is locked for %d more seconds",
2741 file, timer);
2742 mm_log(error_mess, WARN);
2744 timer--;
2745 sleep(1);
2746 } while(timer > 0);
2748 return(-1);
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
2760 Nothing is returned
2761 ----*/
2762 void
2763 carmel2_unlock(local, mailbox, write)
2764 CARMEL2LOCAL *local;
2765 char *mailbox;
2766 int write;
2768 char lock[CARMEL_PATHBUF_SIZE];
2769 struct stat sb;
2771 strcpy(lock, (*local->calc_paths)(write ? CalcPathCarmel2WriteLock:
2772 CalcPathCarmel2ReadLock,
2773 mailbox, 0));
2775 if(stat(lock, &sb) < 0)
2776 return; /* Hmmm... lock already broken */
2778 if(sb.st_mtime !=
2779 (write ? local->write_lock_mod_time : local->read_lock_mod_time))
2780 return; /* Hmmm... not our lock */
2782 unlink(lock);
2787 /*----------------------------------------------------------------------
2788 Keep the mod date on the lock file fresh
2790 Args: stream --
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
2796 ----*/
2797 static int
2798 carmel2_update_lock(local, file, write)
2799 CARMEL2LOCAL *local;
2800 char *file;
2801 int write;
2803 char lock[CARMEL_PATHBUF_SIZE];
2804 struct timeval tp[2];
2805 struct stat sb;
2807 strcpy(lock, (*local->calc_paths)(write ? CalcPathCarmel2WriteLock:
2808 CalcPathCarmel2ReadLock,
2809 file, 0));
2811 if(stat(lock, &sb) < 0) {
2812 /* Lock file stolen, oh oh */
2813 return(-1);
2816 if(sb.st_mtime !=
2817 (write ? local->write_lock_mod_time : local->read_lock_mod_time)) {
2818 /* Not our lock anymore , oh oh */
2819 return(-1);
2822 gettimeofday (&tp[0],NIL); /* set atime to now */
2823 gettimeofday (&tp[1],NIL); /* set mtime to now */
2824 utimes(lock, tp);
2826 if(write)
2827 local->write_lock_mod_time = tp[1].tv_sec;
2828 else
2829 local->read_lock_mod_time = tp[1].tv_sec;
2830 return(0);
2835 /*----------------------------------------------------------------------
2836 Berkeley open and lock mailbox
2838 This is mostly ripped off from the Bezerk driver
2839 ----*/
2841 static int
2842 carmel2_bezerk_lock (spool, file)
2843 char *spool, *file;
2845 int fd,ld,j;
2846 int i = BEZERKLOCKTIMEOUT * 60 - 1;
2847 struct timeval tp;
2848 struct stat sb;
2849 char *hitch, *lock;
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 */
2855 #ifdef NFSKLUDGE
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);
2888 #else
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);
2898 break;
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 */
2902 break;
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 */
2907 break;
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 */
2913 #endif
2914 if ((ld < 0) && *lock) { /* if failed to make lock file and retry OK */
2915 if (!(i%15)) {
2916 sprintf (carmel_20k_buf,"Mailbox %s is locked, will override in %d seconds...",
2917 file,i);
2918 mm_log (carmel_20k_buf, WARN);
2920 sleep (1); /* wait 1 second before next try */
2922 } while (i-- && ld < 0 && *lock);
2923 /* open file */
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 */
2930 return(fd);
2935 /*----------------------------------------------------------------------
2936 Berkeley unlock and close mailbox
2937 ----*/
2938 static void
2939 carmel2_bezerk_unlock (fd, spool)
2940 int fd;
2941 char *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
2959 0 if OK
2960 ----*/
2962 carmel2_check_dir(dir)
2963 char *dir;
2965 struct stat sb;
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);
2973 return(-1);
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);
2978 return(-1);
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);
2984 return(-1);
2986 return(0);
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
2998 ----*/
2999 static
3000 carmel2_new_data_file(local, mailbox)
3001 CARMEL2LOCAL *local;
3002 char *mailbox;
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);
3009 if(path == NULL)
3010 return(-1);
3012 fd = open(path, O_RDWR|O_CREAT, 0666);
3013 if(fd < 0) {
3014 sprintf(e_buf, "Error getting next number of mail file: %s",
3015 strerror(errno));
3016 mm_log(e_buf, ERROR);
3017 return(-1);
3020 bytes_read = read(fd, num_buf, sizeof(num_buf));
3021 if(bytes_read < 6) {
3022 num = 100000;
3023 } else {
3024 num = atoi(num_buf) + 1;
3025 if(num >= 999999)
3026 num = 100000;
3028 sprintf(num_buf, "%-6d\n", num);
3029 lseek(fd, 0, 0);
3030 write(fd, num_buf, strlen(num_buf));
3031 close(fd);
3032 return(num);
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
3043 the .MAXNAME file.
3045 Bug: This is untested!
3046 ----*/
3047 static char *
3048 carmel2_calc_paths(operation, mailbox, data_file_num)
3049 int operation;
3050 char *mailbox;
3051 int data_file_num;
3053 static char path[CARMEL_PATHBUF_SIZE], num[20];
3054 char *p, *home_dir;
3055 struct carmel_mb_name *parsed_name;
3056 struct passwd *pw;
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);
3062 return(NULL);
3066 if(parsed_name->user != NULL) {
3067 /*---- has a user in mailbox name -----*/
3068 pw = getpwnam(parsed_name->user);
3069 if(pw == NULL) {
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);
3075 return(0);
3077 home_dir = pw->pw_dir;
3078 } else {
3079 home_dir = myhomedir();
3081 mailbox = parsed_name->mailbox;
3083 switch(operation) {
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);
3091 break;
3093 case CalcPathCarmel2MAXNAME:
3094 sprintf(path, "%s/%s", home_dir, CARMEL2_MAXFILE);
3095 break;
3097 case CalcPathCarmel2WriteLock:
3098 sprintf(path, "%s/%s/.%s.wl", home_dir, CARMEL2_INDEX_DIR, mailbox);
3099 break;
3101 case CalcPathCarmel2ReadLock:
3102 sprintf(path, "%s/%s/.%s.rl", home_dir, CARMEL2_INDEX_DIR, mailbox);
3103 break;
3106 carmel_free_mb_name(parsed_name);
3107 return(path);
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
3117 ----*/
3118 void
3119 carmel2_parse_bezerk_status(mc, header)
3120 MESSAGECACHE *mc;
3121 char *header;
3123 register char *p;
3124 for(p = header; *p; p++) {
3125 if(*p != '\n')
3126 continue;
3127 p++;
3128 if(*p != 'S' && *p != 'X')
3129 continue;
3130 if(strncmp(p, "Status: ", 8) == 0 || strncmp(p,"X-Status: ",10)== 0) {
3131 mc->recent = 1;
3132 for(p += *p == 'X' ? 10: 8; *p && *p != '\n'; p++)
3133 switch(*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
3153 ----*/
3154 static short
3155 carmel2_getflags (stream, flag)
3156 MAILSTREAM *stream;
3157 char *flag;
3159 char *t, tmp[100];
3160 short f = 0;
3161 short i,j;
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);
3166 return NIL;
3168 /* copy the flag string w/o list construct */
3169 strncpy (tmp,flag+i,(j = strlen (flag) - (2*i)));
3170 tmp[j] = '\0';
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 */
3179 break;
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 */
3184 break;
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 */
3189 break;
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 */
3194 break;
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 */
3199 break;
3200 default: /* unknown */
3201 i = 0;
3202 break;
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);
3208 return NIL;
3211 else { /* no user flags yet */
3212 mm_log ("Unknown flag",ERROR);
3213 return NIL;
3217 return f;
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)
3245 ----*/
3246 static MESSAGECACHE *
3247 carmel2_new_mc(stream, num)
3248 MAILSTREAM *stream;
3249 int num;
3251 MESSAGECACHE *mc;
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 *));
3257 else
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;
3265 mc = MC(num);
3267 mc->user_flags = 0;
3268 mc->lockcount = 0;
3269 mc->seen = 0;
3270 mc->deleted = 0;
3271 mc->flagged = 0;
3272 mc->answered = 0;
3273 mc->recent = 0;
3274 mc->searched = 0;
3275 mc->sequence = 0;
3276 mc->spare = 0;
3277 mc->spare2 = 0;
3278 mc->spare3 = 0;
3279 mc->msgno = num;
3281 /* Don't set the date, the size and the extra data,
3282 assume they will be set
3284 return(mc);
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
3305 - Unlock the index
3307 BUG: This needs some locking and some error messages
3309 ----*/
3310 carmel2_append2(stream, local, mailbox, flags, date, message)
3311 char *mailbox, *flags, *date;
3312 CARMEL2LOCAL *local;
3313 STRING *message;
3314 MAILSTREAM *stream;
3316 char *index_name, *data_name, *p, c, *header_string;
3317 ENVELOPE *envelope;
3318 BODY *b;
3319 MESSAGECACHE mc;
3320 FILE *index_file, *data_file;
3321 struct stat sb;
3322 int last_was_crlf, saved_errno;
3323 STRING string_struct;
3324 long size;
3325 short flagbits;
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.",
3331 mailbox);
3332 mm_log(carmel_error_buf, ERROR);
3333 return(NIL);
3336 /*----- Get the file name of carmel2 index -----*/
3337 index_name = (*local->calc_paths)(CalcPathCarmel2Index, mailbox, 0);
3338 if(index_name == NULL) {
3339 saved_errno = 0;
3340 goto bomb;
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);
3347 return(NIL);
3350 index_file = fopen(index_name, "a");
3351 if(index_file == NULL)
3352 goto bomb;
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;
3363 mc.user_flags = 0;
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 */
3369 goto bomb;
3371 data_file = fopen(data_name, "w");
3372 if(data_file == NULL)
3373 goto bomb;
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--){
3380 c = SNX(message);
3381 if(c == '\r' && size > 1) {
3382 /* Turn CRLF into LF for UNIX */
3383 c = SNX(message);
3384 size--;
3385 if(c != '\n') {
3386 if(fputc('\r', data_file) < 0 || fputc(c, data_file) < 0)
3387 goto bomb;
3388 if(header_string != NULL) {
3389 *header_string++ = '\r';
3390 *header_string++ = c;
3392 } else {
3393 if(fputc('\n', data_file) < 0)
3394 goto bomb;
3395 if(header_string != NULL) {
3396 if(last_was_crlf) {
3397 *header_string = '\0';
3398 header_string = NULL;
3399 } else {
3400 *header_string++ = '\r';
3401 *header_string++ = '\n';
3404 last_was_crlf = 1;
3406 } else {
3407 if(fputc(c, data_file) == EOF)
3408 goto bomb;
3409 if(header_string != NULL)
3410 *header_string++ = c;
3411 last_was_crlf = 0;
3414 if(fclose(data_file) == EOF)
3415 goto bomb;
3416 data_file = NULL;
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);
3426 #ifdef BWC
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);
3433 break;
3436 #endif
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)
3447 goto bomb;
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)
3456 goto bomb;
3457 carmel2_unlock(local, mailbox, WRITE_LOCK);
3458 return(T);
3460 bomb:
3461 saved_errno = errno;
3462 if(index_file != NULL) {
3463 fclose(index_file);
3465 if(data_file != NULL) {
3466 fclose(data_file);
3467 unlink(data_name);
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);
3475 return(NIL);
3481 /* Search support routines
3482 * Accepts: MAIL stream
3483 * message number
3484 * pointer to additional data
3485 * pointer to temporary buffer
3486 * Returns: T if search matches, else NIL
3489 static char
3490 carmel2_search_all (stream,msgno,d,n)
3491 MAILSTREAM *stream;
3492 long msgno;
3493 char *d;
3494 long n;
3496 return T; /* ALL always succeeds */
3500 static char
3501 carmel2_search_answered (stream,msgno,d,n)
3502 MAILSTREAM *stream;
3503 long msgno;
3504 char *d;
3505 long n;
3507 return MC(msgno)->answered ? T : NIL;
3511 static char
3512 carmel2_search_deleted (stream,msgno,d,n)
3513 MAILSTREAM *stream;
3514 long msgno;
3515 char *d;
3516 long n;
3518 return MC(msgno)->deleted ? T : NIL;
3522 static char
3523 carmel2_search_flagged (stream,msgno,d,n)
3524 MAILSTREAM *stream;
3525 long msgno;
3526 char *d;
3527 long n;
3529 return MC(msgno)->flagged ? T : NIL;
3533 static char
3534 carmel2_search_keyword (stream,msgno,d,n)
3535 MAILSTREAM *stream;
3536 long msgno;
3537 char *d;
3538 long n;
3540 return NIL; /* keywords not supported yet */
3544 static char
3545 carmel2_search_new (stream,msgno,d,n)
3546 MAILSTREAM *stream;
3547 long msgno;
3548 char *d;
3549 long n;
3551 MESSAGECACHE *elt = MC(msgno);
3552 return (elt->recent && !elt->seen) ? T : NIL;
3555 static char
3556 carmel2_search_old (stream,msgno,d,n)
3557 MAILSTREAM *stream;
3558 long msgno;
3559 char *d;
3560 long n;
3562 return MC(msgno)->recent ? NIL : T;
3566 static char
3567 carmel2_search_recent (stream,msgno,d,n)
3568 MAILSTREAM *stream;
3569 long msgno;
3570 char *d;
3571 long n;
3573 return MC(msgno)->recent ? T : NIL;
3577 static char
3578 carmel2_search_seen (stream,msgno,d,n)
3579 MAILSTREAM *stream;
3580 long msgno;
3581 char *d;
3582 long n;
3584 return MC(msgno)->seen ? T : NIL;
3588 static char
3589 carmel2_search_unanswered (stream,msgno,d,n)
3590 MAILSTREAM *stream;
3591 long msgno;
3592 char *d;
3593 long n;
3595 return MC(msgno)->answered ? NIL : T;
3599 static char
3600 carmel2_search_undeleted (stream,msgno,d,n)
3601 MAILSTREAM *stream;
3602 long msgno;
3603 char *d;
3604 long n;
3606 return MC(msgno)->deleted ? NIL : T;
3610 static char
3611 carmel2_search_unflagged (stream,msgno,d,n)
3612 MAILSTREAM *stream;
3613 long msgno;
3614 char *d;
3615 long n;
3617 return MC(msgno)->flagged ? NIL : T;
3621 static char
3622 carmel2_search_unkeyword (stream,msgno,d,n)
3623 MAILSTREAM *stream;
3624 long msgno;
3625 char *d;
3626 long n;
3628 return T; /* keywords not supported yet */
3632 static char
3633 carmel2_search_unseen (stream,msgno,d,n)
3634 MAILSTREAM *stream;
3635 long msgno;
3636 char *d;
3637 long n;
3639 return MC(msgno)->seen ? NIL : T;
3642 static char
3643 carmel2_search_before (stream,msgno,d,n)
3644 MAILSTREAM *stream;
3645 long msgno;
3646 char *d;
3647 long n;
3649 return (char) (carmel2_msgdate (stream,msgno) < n);
3653 static char
3654 carmel2_search_on (stream,msgno,d,n)
3655 MAILSTREAM *stream;
3656 long msgno;
3657 char *d;
3658 long n;
3660 return (char) (carmel2_msgdate (stream,msgno) == n);
3664 static char
3665 carmel2_search_since (stream,msgno,d,n)
3666 MAILSTREAM *stream;
3667 long msgno;
3668 char *d;
3669 long n;
3671 /* everybody interprets "since" as .GE. */
3672 return (char) (carmel2_msgdate (stream,msgno) >= n);
3676 static unsigned long
3677 carmel2_msgdate (stream,msgno)
3678 MAILSTREAM *stream;
3679 long msgno;
3681 struct stat sbuf;
3682 struct tm *tm;
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
3691 ---*/
3692 static char
3693 carmel2_search_body (stream,msgno, pat, pat_len)
3694 MAILSTREAM *stream;
3695 long msgno,pat_len;
3696 char *pat;
3698 char *t = carmel2_fetchtext(stream, msgno);
3699 return (t && search (t,strlen(t), pat, pat_len));
3703 /*----------------------------------------------------------------------
3704 Search the subject field
3705 ----*/
3706 static char
3707 carmel2_search_subject (stream,msgno, pat, pat_len)
3708 MAILSTREAM *stream;
3709 long msgno, pat_len;
3710 char *pat;
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
3719 ---*/
3720 static char
3721 carmel2_search_text (stream, msgno, pat, pat_len)
3722 MAILSTREAM *stream;
3723 long msgno, pat_len;
3724 char *pat;
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
3734 ---*/
3735 static char
3736 carmel2_search_bcc (stream,msgno,d,n)
3737 MAILSTREAM *stream;
3738 long msgno;
3739 char *d;
3740 long 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);
3750 static char
3751 carmel2_search_cc (stream,msgno,d,n)
3752 MAILSTREAM *stream;
3753 long msgno;
3754 char *d;
3755 long 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);
3765 static char
3766 carmel2_search_from (stream,msgno,d,n)
3767 MAILSTREAM *stream;
3768 long msgno;
3769 char *d;
3770 long 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);
3780 static char
3781 carmel2_search_to (stream,msgno, pat, pat_len)
3782 MAILSTREAM *stream;
3783 long msgno;
3784 char *pat;
3785 long 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 */
3797 /* Parse a date
3798 * Accepts: function to return
3799 * pointer to date integer to return
3800 * Returns: function to return
3803 static search_t
3804 carmel2_search_date (f, n)
3805 search_t f;
3806 long *n;
3808 long i;
3809 char *s;
3810 MESSAGECACHE elt;
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;
3816 /* Parse a flag
3817 * Accepts: function to return
3818 * pointer to string to return
3819 * Returns: function to return
3822 static search_t
3823 carmel2_search_flag (f,d)
3824 search_t f;
3825 char **d;
3827 /* get a keyword, return if OK */
3828 return (*d = strtok (NIL," ")) ? f : NIL;
3832 /* Parse a string
3833 * Accepts: function to return
3834 * pointer to string to return
3835 * pointer to string length to return
3836 * Returns: function to return
3839 static search_t
3840 carmel2_search_string (f,d,n)
3841 search_t f;
3842 char **d;
3843 long *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 */
3849 case ' ':
3850 return NIL;
3851 case '"': /* quoted string */
3852 if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
3853 return NIL;
3854 break;
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 */
3861 break;
3862 default: /* atomic string */
3863 *n = strlen (*d = strtok (c," "));
3864 break;
3866 return f;
3868 else return NIL;
3876 /*----------------------------------------------------------------------
3877 Some stuff to help out with the date parsing
3878 ---*/
3879 char *xdays2[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL};
3881 char *
3882 month_abbrev2(month_num)
3883 int 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)
3888 return("xxx");
3889 return(xmonths[month_num - 1]);
3892 struct time_zone_names {
3893 char *name;
3894 int hours;
3895 int minutes;
3896 } tz_names[] = {
3897 {"GMT", 0, 0},
3898 {"PST", -8, 0},
3899 {"PDT", -7, 0},
3900 {"MST", -7, 0},
3901 {"MDT", -6, 0},
3902 {"CST", -6, 0},
3903 {"CDT", -5, 0},
3904 {"EST", -5, 0},
3905 {"EDT", -4, 0},
3906 {"JST", 9, 0},
3907 {"IST", 2, 0},
3908 {"IDT", 3, 0},
3909 {NULL, 0, 0}};
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.
3928 ----*/
3929 void
3930 carmel2_rfc822_date(mc, given_date)
3931 char *given_date;
3932 MESSAGECACHE *mc;
3934 char *p, **i, *q;
3935 int month, year;
3936 struct time_zone_names *tz;
3938 mc->seconds = 0;
3939 mc->minutes = 0;
3940 mc->hours = 30;
3941 mc->day = 0;
3942 mc->month = 0;
3943 mc->year = 0;
3944 mc->zhours = 0;
3945 mc->zminutes = 0;
3947 if(given_date == NULL)
3948 return;
3950 p = given_date;
3952 if(*p != 'D' && strncmp(p, "Date:",5))
3953 while(*p) {
3954 if(*p == '\n' && *(p+1) == 'D' && strncmp(p+1, "Date:", 5) == 0)
3955 break;
3956 p++;
3958 if(!*p)
3959 return;
3961 p += 6; /* Skip "\nDate: " */
3962 while(isspace(*p))
3963 p++;
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 */
3968 break;
3969 if(*i != NULL) {
3970 /* Started with week day .. buz over it*/
3971 while(*p && !isspace(*p) && *p != ',')
3972 p++;
3973 while(*p && (isspace(*p) || *p == ','))
3974 p++;
3976 if(isdigit(*p)) {
3977 mc->day = atoi(p);
3978 while(*p && isdigit(*p))
3979 p++;
3980 while(*p && (*p == '-' || *p == ',' || isspace(*p)))
3981 p++;
3983 for(month = 1; month <= 12; month++)
3984 if(struncmp2(p, month_abbrev2(month), 3) == 0)
3985 break;
3986 if(month < 13) {
3987 mc->month = month;
3990 /* Move over month, (or whatever is there) */
3991 while(*p && !isspace(*p) && *p != ',' && *p != '-')
3992 p++;
3993 while(*p && (isspace(*p) || *p == ',' || *p == '-'))
3994 p++;
3996 /* Check again for day */
3997 if(isdigit(*p) && mc->day == -1) {
3998 mc->day = atoi(p);
3999 while(*p && isdigit(*p))
4000 p++;
4001 while(*p && (*p == '-' || *p == ',' || isspace(*p)))
4002 p++;
4005 /*-- Check for time --*/
4006 for(q = p; *q && isdigit(*q); q++);
4007 if(*q == ':') {
4008 /* It's the time (out of place) */
4009 mc->hours = atoi(p);
4010 while(*p && *p != ':' && !isspace(*p))
4011 p++;
4012 if(*p == ':') {
4013 mc->minutes = atoi(p);
4014 while(*p && *p != ':' && !isspace(*p))
4015 p++;
4016 if(*p == ':') {
4017 mc->seconds = atoi(p);
4018 while(*p && !isspace(*p))
4019 p++;
4022 while(*p && isspace(*p))
4023 p++;
4026 /* Get the year 0-50 is 2000-2050; 50-100 is 1950-1999 and
4027 101-9999 is 101-9999 */
4028 if(isdigit(*p)) {
4029 year = atoi(p);
4030 if(year < 50)
4031 year += 2000;
4032 else if(year < 100)
4033 year += 1900;
4034 mc->year = year - 1969;
4035 while(*p && isdigit(*p))
4036 p++;
4037 while(*p && (*p == '-' || *p == ',' || isspace(*p)))
4038 p++;
4039 } else {
4040 /* Something weird, skip it and try to resynch */
4041 while(*p && !isspace(*p) && *p != ',' && *p != '-')
4042 p++;
4043 while(*p && (isspace(*p) || *p == ',' || *p == '-'))
4044 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))
4052 p++;
4053 if(*p == ':') {
4054 p++;
4055 mc->minutes = atoi(p);
4056 while(*p && *p != ':' && !isspace(*p))
4057 p++;
4058 if(*p == ':') {
4059 p++;
4060 mc->seconds = atoi(p);
4061 while(*p && !isspace(*p) && *p != '+' && *p != '-')
4062 p++;
4066 while(*p && isspace(*p))
4067 p++;
4070 /*-- The time zone --*/
4071 if(*p) {
4072 if(*p == '+' || *p == '-') {
4073 char tmp[3];
4074 mc->zoccident = (*p == '+' ? 1 : 0);
4075 p++;
4076 tmp[0] = *p++;
4077 tmp[1] = *p++;
4078 tmp[2] = '\0';
4079 mc->zhours = atoi(tmp);
4080 tmp[0] = *p++;
4081 tmp[1] = *p++;
4082 tmp[2] = '\0';
4083 mc->zminutes *= atoi(tmp);
4084 } else {
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;
4089 mc->zoccident = 1;
4090 } else {
4091 mc->zhours = -tz->hours;
4092 mc->zoccident = 0;
4094 mc->zminutes = tz->minutes;
4095 break;
4100 if(mc->hours == 30)
4101 mc->hours = 0;
4106 /*----------------------------------------------------------------------
4107 Print the date from the MESSAGECACHE into the string
4108 ----*/
4109 static void
4110 carmel2_date2string(string, mc)
4111 char *string;
4112 MESSAGECACHE *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"
4129 ----*/
4130 static void
4131 carmel2_parse_date(mc, string)
4132 MESSAGECACHE *mc;
4133 char *string;
4135 int n;
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);
4144 if(n < 0) {
4145 mc->zoccident = 0;
4146 mc->zhours = -n;
4147 } else {
4148 mc->zoccident = 1;
4149 mc->zhours = n;
4151 mc->zminutes = next_num(&string);
4156 /*----------------------------------------------------------------------
4157 Read the next number out of string and return it, advancing the string
4158 ----*/
4159 static
4160 next_num(string)
4161 char **string;
4163 int n;
4164 char *p;
4166 if(string == NULL)
4167 return(0);
4169 p = *string;
4170 n = atoi(p);
4171 while(*p > '\001')
4172 p++;
4173 if(*p)
4174 *string = p+1;
4175 else
4176 *string = NULL;
4177 return(n);
4181 /*----------------------------------------------------------------------
4182 Take a (slightly ugly) FQ mailbox and and return the prettier
4183 last part of it
4184 ----*/
4185 char *
4186 carmel_pretty_mailbox(mailbox)
4187 char *mailbox;
4189 char *pretty_mb;
4191 for(pretty_mb = mailbox + strlen(mailbox) - 1;
4192 *pretty_mb != '#' && pretty_mb > mailbox;
4193 pretty_mb--)
4195 if(*pretty_mb == '#')
4196 pretty_mb++;
4197 return(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.
4208 ----*/
4209 struct carmel_mb_name *
4210 carmel_parse_mb_name(fq_name, given_version)
4211 char *fq_name;
4212 char 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 */
4219 p = fq_name;
4220 version[0] = given_version;
4221 version[1] = '\0';
4222 } else {
4223 if(fq_name[7] == CARMEL_NAME_CHAR) {
4224 version[0] = '\0';
4225 p = fq_name + 8;
4226 } else if(fq_name[8] == CARMEL_NAME_CHAR) {
4227 version[0] = fq_name[7];
4228 version[1] = '\0';
4229 p = fq_name + 9;
4230 } else {
4231 return(NULL);
4235 if(given_version != version[0])
4236 return(NULL);
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';
4250 p = q + 1;
4251 } else {
4252 parsed_name->user = NULL;
4255 parsed_name->mailbox = cpystr(p);
4257 return(parsed_name);
4261 void
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
4286 ---*/
4287 strucmp2(o, r)
4288 register char *r, *o;
4290 if(r == NULL && o == NULL)
4291 return(0);
4292 if(o == NULL)
4293 return(1);
4294 if(r == NULL)
4295 return(-1);
4297 while(*o && *r && (isupper(*o) ? tolower(*o) : *o) ==
4298 (isupper(*r) ? tolower(*r) : *r)) {
4299 o++, 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
4314 ----*/
4315 struncmp2(o, r, n)
4316 register char *r, *o;
4317 register int n;
4319 if(r == NULL && o == NULL)
4320 return(0);
4321 if(o == NULL)
4322 return(1);
4323 if(r == NULL)
4324 return(-1);
4326 n--;
4327 while(n && *o && *r &&
4328 (isupper(*o)? tolower(*o): *o) == (isupper(*r)? tolower(*r): *r)) {
4329 o++; r++; n--;
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.
4341 ----*/
4342 char *
4343 strindex2(buffer, ch)
4344 char *buffer;
4345 int 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 **/
4351 if(*buffer == ch)
4352 return(buffer);
4353 while (*buffer++ != '\0');
4355 return(NULL);
4359 /*======================================================================
4362 xopen(file, mode)
4363 char *file, *mode;
4367 xclose(stream)
4368 FILE *stream;
4371 xread() {}
4373 xwrite() {}
4375 xlseek() {}
4377 #ifdef BWC
4378 carmel_match_glyph_wide(string)
4379 char *string;
4381 extern char *ptspecials;
4382 char *s;
4384 if(struncmp2(string, "content-type", 12))
4385 return(0); /* Nope */
4386 string += 12;
4387 while(*string && isspace(*string)) string++;
4388 if(*string != ':')
4389 return(0);
4390 string++;
4391 while(*string && isspace(*string)) string++;
4392 s = string;
4393 string = rfc822_parse_word(string, ptspecials);
4394 if(string == NULL)
4395 return(0);
4396 if(struncmp2(s, "text", 4))
4397 return(0);
4398 while(*string && isspace(*string)) string++;
4399 if(*string != '/')
4400 return;
4401 string++;
4402 while(*string && isspace(*string)) string++;
4403 s = string;
4404 string = rfc822_parse_word(string, ptspecials);
4405 if(string == NULL)
4406 return(0);
4407 if(struncmp2(s, "x-bwc-glyph-wide", 16))
4408 return(0);
4409 return(1);
4411 #endif