diff options
Diffstat (limited to 'imap/src/osdep/amiga')
38 files changed, 21358 insertions, 0 deletions
diff --git a/imap/src/osdep/amiga/Makefile b/imap/src/osdep/amiga/Makefile new file mode 100644 index 00000000..1f08e97e --- /dev/null +++ b/imap/src/osdep/amiga/Makefile @@ -0,0 +1,231 @@ +# ======================================================================== +# Copyright 1988-2006 University of Washington +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# +# ======================================================================== + + +# Program: C client makefile for Amiga +# +# Author: Mark Crispin +# Networks and Distributed Computing +# Computing & Communications +# University of Washington +# Administration Building, AG-44 +# Seattle, WA 98195 +# Internet: MRC@CAC.Washington.EDU +# +# Date: 11 May 1989 +# Last Edited: 5 November 2006 + + +# Command line build parameters + +EXTRAAUTHENTICATORS= +EXTRADRIVERS=mbox +PASSWDTYPE=std + + +# Build parameters normally set by the individual port + +AMICFLAGS=-O -DNO_INLINE_STDARG -Dunix +AMILDFLAGS=/pine/libc.a -lamiga -lauto +CHECKPW=std +LOGINPW=std +ACTIVEFILE=/UULib/News/Active +SPOOLDIR=/usr/spool +MAILSPOOL=/AmiTCP/Mail +NEWSSPOOL=/UUNews +MD5PWD="/etc/cram-md5.pwd" + + +# Default formats for creating new mailboxes and for empty mailboxes in the +# default namespace; must be set to the associated driver's prototype. +# +# The CREATEPROTO is the default format for new mailbox creation. +# The EMPTYPROTO is the default format for handling zero-byte files. +# +# Normally, this is set by the individual port. +# +# NOTE: namespace formats (e.g. mh and news) can not be set as a default format +# since they do not exist in the default namespace. Also, it is meaningless to +# set certain other formats (e.g. mbx, mx, and mix) as the EMPTYPROTO since +# these formats can never be empty files. + +CREATEPROTO=unixproto +EMPTYPROTO=unixproto + + +# Commands possibly overriden by the individual port + +ARRC=ar rc +CC=cc +LN=cp +RANLIB=ranlib +RM=rm -f + + +# Standard distribution build parameters + +DEFAULTAUTHENTICATORS=ext md5 pla log +DEFAULTDRIVERS=imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile +CHUNKSIZE=65536 + + +# Normally no need to change any of these + +ARCHIVE=c-client.a +BINARIES=mail.o misc.o newsrc.o smanager.o osdep.o utf8.o utf8aux.o \ + dummy.o pseudo.o netmsg.o flstring.o fdstring.o \ + rfc822.o nntp.o smtp.o imap4r1.o pop3.o \ + unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o +CFLAGS=$(BASECFLAGS) $(EXTRACFLAGS) +MAKE=make +MV=mv +SHELL=/bin/sh + + +# Primary build command + +BUILDOPTIONS= EXTRACFLAGS=$(EXTRACFLAGS) EXTRALDFLAGS=$(EXTRALDFLAGS)\ + EXTRADRIVERS=$(EXTRADRIVERS) EXTRAAUTHENTICATORS=$(EXTRAAUTHENTICATORS)\ + PASSWDTYPE=$(PASSWDTYPE) +BUILD=$(MAKE) build $(BUILDOPTIONS) $(SPECIALS) + + +# Here if no make argument established + +missing: osdep.h + $(MAKE) $(ARCHIVE) CC=`cat CCTYPE` CFLAGS="`cat CFLAGS`" + +osdep.h: + @echo You must specify what type of system + @false + + +# Current ports + +ami: # AmigaDOS + $(BUILD) OS=$@ \ + BASECFLAGS="-DOLD $(AMICFLAGS)" \ + BASELDFLAGS="$(AMILDFLAGS) -lamitcp000" \ + CC=gcc + +am2: # AmigaDOS with a 68020+ + $(BUILD) OS=ami \ + BASECFLAGS="-DOLD -m68020 $(AMICFLAGS)" \ + BASELDFLAGS="$(AMILDFLAGS) -lamitcp" \ + CC=gcc + +amn: # AmigaDOS with a 680x0 using "new" socket library + $(BUILD) OS=ami \ + BASELDFLAGS="$(AMILDFLAGS) -lnewamitcp000" \ + CC=gcc + +ama: # AmigaDOS using AS225R2 + $(BUILD) OS=ami \ + MAILSPOOL=/INet/Mail \ + BASECFLAGS="-m68020 $(AMICFLAGS)" \ + BASELDFLAGS="$(AMILDFLAGS) -las225r2" \ + CC=gcc + +# Build it! + +build: clean once ckp$(PASSWDTYPE) $(EXTRAAUTHENTICATORS) $(ARCHIVE) + +$(ARCHIVE): $(BINARIES) + $(RM) $(ARCHIVE) || true + $(ARRC) $(ARCHIVE) $(BINARIES) + $(RANLIB) $(ARCHIVE) + +# Cleanup + +clean: + $(RM) *.o linkage.[ch] auths.c $(ARCHIVE) osdep.* *TYPE *FLAGS || true + + +# Dependencies + +dummy.o: mail.h misc.h osdep.h dummy.h +fdstring.o: mail.h misc.h osdep.h fdstring.h +flstring.o: mail.h misc.h osdep.h flstring.h +imap4r1.o: mail.h misc.h osdep.h imap4r1.h rfc822.h +mail.o: mail.h misc.h osdep.h rfc822.h linkage.h +mbx.o: mail.h misc.h osdep.h dummy.h +mh.o: mail.h misc.h osdep.h mh.h dummy.h +mix.o: mail.h misc.h osdep.h dummy.h +mx.o: mail.h misc.h osdep.h mx.h dummy.h +misc.o: mail.h misc.h osdep.h +mmdf.o: mail.h misc.h osdep.h pseudo.h dummy.h +mtx.o: mail.h misc.h osdep.h dummy.h +netmsg.o: mail.h misc.h osdep.h netmsg.h +news.o: mail.h misc.h osdep.h +newsrc.o: mail.h misc.h osdep.h newsrc.h +nntp.o: mail.h misc.h osdep.h netmsg.h smtp.h nntp.h rfc822.h +phile.o: mail.h misc.h osdep.h rfc822.h dummy.h +pseudo.o: pseudo.h +pop3.o: mail.h misc.h osdep.h pop3.h rfc822.h +smanager.o: mail.h misc.h osdep.h +smtp.o: mail.h misc.h osdep.h smtp.h rfc822.h +rfc822.o: mail.h misc.h osdep.h rfc822.h +tenex.o: mail.h misc.h osdep.h dummy.h +unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h +utf8.o: mail.h misc.h osdep.h utf8.h +utf8aux.o: mail.h misc.h osdep.h utf8.h + + +# OS-dependent + +osdep.o:mail.h misc.h env.h fs.h ftl.h nl.h tcp.h \ + osdep.h env_ami.h tcp_ami.h \ + osdep.c env_ami.c fs_ami.c ftl_ami.c nl_ami.c tcp_ami.c \ + auths.c gethstid.c \ + gr_waitp.c \ + auth_log.c auth_md5.c auth_pla.c \ + pmatch.c scandir.c \ + tz_bsd.c \ + write.c \ + strerror.c strpbrk.c strstr.c strtok.c strtoul.c \ + OSCFLAGS + $(CC) $(CFLAGS) `cat OSCFLAGS` -c osdep.c + +osdep.c: osdepbas.c osdepckp.c osdeplog.c osdepssl.c + $(RM) osdep.c || true + cat osdepbas.c osdepckp.c osdeplog.c osdepssl.c > osdep.c + + +# Once-only environment setup + +once: + @echo Once-only environment setup... + ./drivers $(EXTRADRIVERS) $(DEFAULTDRIVERS) dummy + ./mkauths $(EXTRAAUTHENTICATORS) $(DEFAULTAUTHENTICATORS) + echo $(CC) > CCTYPE + echo $(CFLAGS) -DCHUNKSIZE=$(CHUNKSIZE) > CFLAGS + echo -DCREATEPROTO=$(CREATEPROTO) -DEMPTYPROTO=$(EMPTYPROTO) \ + -DMD5ENABLE=\"$(MD5PWD)\" -DMAILSPOOL=\"$(MAILSPOOL)\" \ + -DACTIVEFILE=\"$(ACTIVEFILE)\" -DNEWSSPOOL=\"$(NEWSSPOOL)\" \ + -DANONYMOUSHOME=\"$(MAILSPOOL)/anonymous\" > OSCFLAGS + echo $(BASELDFLAGS) $(EXTRALDFLAGS) > LDFLAGS + $(LN) os_$(OS).h osdep.h + $(LN) os_$(OS).c osdepbas.c + $(LN) log_$(LOGINPW).c osdeplog.c + $(LN) ssl_none.c osdepssl.c + + +# Password checkers + +ckpstd: # Port standard + $(LN) ckp_$(CHECKPW).c osdepckp.c + + +# A monument to a hack of long ago and far away... + +love: + @echo not war? diff --git a/imap/src/osdep/amiga/ckp_std.c b/imap/src/osdep/amiga/ckp_std.c new file mode 100644 index 00000000..36255cc1 --- /dev/null +++ b/imap/src/osdep/amiga/ckp_std.c @@ -0,0 +1,42 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Standard check password + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + +/* Check password + * Accepts: login passwd struct + * password string + * argument count + * argument vector + * Returns: passwd struct if password validated, NIL otherwise + */ + +struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[]) +{ + return (pw->pw_passwd && pw->pw_passwd[0] && pw->pw_passwd[1] && + !strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) ? + pw : NIL; +} diff --git a/imap/src/osdep/amiga/drivers b/imap/src/osdep/amiga/drivers new file mode 100755 index 00000000..bd49365f --- /dev/null +++ b/imap/src/osdep/amiga/drivers @@ -0,0 +1,36 @@ +#!/bin/sh +# ======================================================================== +# Copyright 1988-2006 University of Washington +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# +# ======================================================================== + +# Program: Driver Linkage Generator +# +# Author: Mark Crispin +# Networks and Distributed Computing +# Computing & Communications +# University of Washington +# Administration Building, AG-44 +# Seattle, WA 98195 +# Internet: MRC@CAC.Washington.EDU +# +# Date: 11 October 1989 +# Last Edited: 30 August 2006 + + +# Erase old driver linkage +rm -f linkage.[ch] + +# Now define the new list +for driver + do + echo "extern DRIVER "$driver"driver;" >> linkage.h + echo " mail_link (&"$driver"driver); /* link in the $driver driver */" | cat >> linkage.c +done diff --git a/imap/src/osdep/amiga/dummy.c b/imap/src/osdep/amiga/dummy.c new file mode 100644 index 00000000..b003a0ba --- /dev/null +++ b/imap/src/osdep/amiga/dummy.c @@ -0,0 +1,809 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Dummy routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 9 May 1991 + * Last Edited: 1 June 2007 + */ + + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include "osdep.h" +#include <pwd.h> +#include <sys/stat.h> +#include "dummy.h" +#include "misc.h" + +/* Function prototypes */ + +DRIVER *dummy_valid (char *name); +void *dummy_parameters (long function,void *value); +void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents, + long level); +long dummy_listed (MAILSTREAM *stream,char delimiter,char *name, + long attributes,char *contents); +long dummy_subscribe (MAILSTREAM *stream,char *mailbox); +MAILSTREAM *dummy_open (MAILSTREAM *stream); +void dummy_close (MAILSTREAM *stream,long options); +long dummy_ping (MAILSTREAM *stream); +void dummy_check (MAILSTREAM *stream); +long dummy_expunge (MAILSTREAM *stream,char *sequence,long options); +long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +/* Dummy routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER dummydriver = { + "dummy", /* driver name */ + DR_LOCAL|DR_MAIL, /* driver flags */ + (DRIVER *) NIL, /* next driver */ + dummy_valid, /* mailbox is valid for us */ + dummy_parameters, /* manipulate parameters */ + dummy_scan, /* scan mailboxes */ + dummy_list, /* list mailboxes */ + dummy_lsub, /* list subscribed mailboxes */ + dummy_subscribe, /* subscribe to mailbox */ + NIL, /* unsubscribe from mailbox */ + dummy_create, /* create mailbox */ + dummy_delete, /* delete mailbox */ + dummy_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + dummy_open, /* open mailbox */ + dummy_close, /* close mailbox */ + NIL, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message structure */ + NIL, /* fetch header */ + NIL, /* fetch text */ + NIL, /* fetch message data */ + NIL, /* unique identifier */ + NIL, /* message number from UID */ + NIL, /* modify flags */ + NIL, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + dummy_ping, /* ping mailbox to see if still alive */ + dummy_check, /* check for new messages */ + dummy_expunge, /* expunge deleted messages */ + dummy_copy, /* copy messages to another mailbox */ + dummy_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM dummyproto = {&dummydriver}; + +/* Dummy validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *dummy_valid (char *name) +{ + char *s,tmp[MAILTMPLEN]; + struct stat sbuf; + /* must be valid local mailbox */ + if (name && *name && (*name != '{') && (s = mailboxfile (tmp,name))) { + /* indeterminate clearbox INBOX */ + if (!*s) return &dummydriver; + else if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) { + case S_IFREG: + case S_IFDIR: + return &dummydriver; + } + /* blackbox INBOX does not exist yet */ + else if (!compare_cstring (name,"INBOX")) return &dummydriver; + } + return NIL; +} + + +/* Dummy manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *dummy_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = dummy_file ((char *) value,"INBOX"); + break; + } + return ret; +} + +/* Dummy scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + DRIVER *drivers; + char *s,test[MAILTMPLEN],file[MAILTMPLEN]; + long i; + if (!pat || !*pat) { /* empty pattern? */ + if (dummy_canonicalize (test,ref,"*")) { + /* tie off name at root */ + if (s = strchr (test,'/')) *++s = '\0'; + else test[0] = '\0'; + dummy_listed (stream,'/',test,LATT_NOSELECT,NIL); + } + } + /* get canonical form of name */ + else if (dummy_canonicalize (test,ref,pat)) { + /* found any wildcards? */ + if (s = strpbrk (test,"%*")) { + /* yes, copy name up to that point */ + strncpy (file,test,i = s - test); + file[i] = '\0'; /* tie off */ + } + else strcpy (file,test); /* use just that name then */ + if (s = strrchr (file,'/')){/* find directory name */ + *++s = '\0'; /* found, tie off at that point */ + s = file; + } + /* silly case */ + else if ((file[0] == '~') || (file[0] == '#')) s = file; + /* do the work */ + dummy_list_work (stream,s,test,contents,0); + /* always an INBOX */ + if (pmatch ("INBOX",ucase (test))) { + /* done if have a dirfmt INBOX */ + for (drivers = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL); + drivers && !(!(drivers->flags & DR_DISABLE) && + (drivers->flags & DR_DIRFMT) && + (*drivers->valid) ("INBOX")); drivers = drivers->next); + /* list INBOX appropriately */ + dummy_listed (stream,drivers ? '/' : NIL,"INBOX", + drivers ? NIL : LATT_NOINFERIORS,contents); + } + } +} + + +/* Dummy list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void dummy_list (MAILSTREAM *stream,char *ref,char *pat) +{ + dummy_scan (stream,ref,pat,NIL); +} + +/* Dummy list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + void *sdb = NIL; + char *s,*t,test[MAILTMPLEN],tmp[MAILTMPLEN]; + int showuppers = pat[strlen (pat) - 1] == '%'; + /* get canonical form of name */ + if (dummy_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) do + if (*s != '{') { + if (!compare_cstring (s,"INBOX") && + pmatch ("INBOX",ucase (strcpy (tmp,test)))) + mm_lsub (stream,NIL,s,LATT_NOINFERIORS); + else if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,NIL); + else while (showuppers && (t = strrchr (s,'/'))) { + *t = '\0'; /* tie off the name */ + if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,LATT_NOSELECT); + } + } + while (s = sm_read (&sdb)); /* until no more subscriptions */ +} + + +/* Dummy subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long dummy_subscribe (MAILSTREAM *stream,char *mailbox) +{ + char *s,tmp[MAILTMPLEN]; + struct stat sbuf; + /* must be valid local mailbox */ + if ((s = mailboxfile (tmp,mailbox)) && *s && !stat (s,&sbuf)) + switch (sbuf.st_mode & S_IFMT) { + case S_IFDIR: /* allow but snarl */ + sprintf (tmp,"CLIENT BUG DETECTED: subscribe of non-mailbox directory %.80s", + mailbox); + MM_NOTIFY (stream,tmp,WARN); + case S_IFREG: + return sm_subscribe (mailbox); + } + sprintf (tmp,"Can't subscribe %.80s: not a mailbox",mailbox); + MM_LOG (tmp,ERROR); + return NIL; +} + +/* Dummy list mailboxes worker routine + * Accepts: mail stream + * directory name to search + * search pattern + * string to scan + * search level + */ + +void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents, + long level) +{ + DRIVER *drivers; + dirfmttest_t dt; + DIR *dp; + struct direct *d; + struct stat sbuf; + char tmp[MAILTMPLEN],path[MAILTMPLEN]; + size_t len = 0; + /* punt if bogus name */ + if (!mailboxdir (tmp,dir,NIL)) return; + if (dp = opendir (tmp)) { /* do nothing if can't open directory */ + /* see if a non-namespace directory format */ + for (drivers = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL), dt = NIL; + dir && !dt && drivers; drivers = drivers->next) + if (!(drivers->flags & DR_DISABLE) && (drivers->flags & DR_DIRFMT) && + (*drivers->valid) (dir)) + dt = mail_parameters ((*drivers->open) (NIL),GET_DIRFMTTEST,NIL); + /* list it if at top-level */ + if (!level && dir && pmatch_full (dir,pat,'/') && !pmatch (dir,"INBOX")) + dummy_listed (stream,'/',dir,dt ? NIL : LATT_NOSELECT,contents); + + /* scan directory, ignore . and .. */ + if (!dir || dir[(len = strlen (dir)) - 1] == '/') while (d = readdir (dp)) + if ((!(dt && (*dt) (d->d_name))) && + ((d->d_name[0] != '.') || + (((long) mail_parameters (NIL,GET_HIDEDOTFILES,NIL)) ? NIL : + (d->d_name[1] && (((d->d_name[1] != '.') || d->d_name[2]))))) && + ((len + strlen (d->d_name)) <= NETMAXMBX)) { + /* see if name is useful */ + if (dir) sprintf (tmp,"%s%s",dir,d->d_name); + else strcpy (tmp,d->d_name); + /* make sure useful and can get info */ + if ((pmatch_full (strcpy (path,tmp),pat,'/') || + pmatch_full (strcat (path,"/"),pat,'/') || + dmatch (path,pat,'/')) && + mailboxdir (path,dir,"x") && (len = strlen (path)) && + strcpy (path+len-1,d->d_name) && !stat (path,&sbuf)) { + /* only interested in file type */ + switch (sbuf.st_mode & S_IFMT) { + case S_IFDIR: /* directory? */ + /* form with trailing / */ + sprintf (path,"%s/",tmp); + /* skip listing if INBOX */ + if (!pmatch (tmp,"INBOX")) { + if (pmatch_full (tmp,pat,'/')) { + if (!dummy_listed (stream,'/',tmp,LATT_NOSELECT,contents)) + break; + } + /* try again with trailing / */ + else if (pmatch_full (path,pat,'/') && + !dummy_listed (stream,'/',path,LATT_NOSELECT,contents)) + break; + } + if (dmatch (path,pat,'/') && + (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL))) + dummy_list_work (stream,path,pat,contents,level+1); + break; + case S_IFREG: /* ordinary name */ + /* Must use ctime for systems that don't update mtime properly */ + if (pmatch_full (tmp,pat,'/') && compare_cstring (tmp,"INBOX")) + dummy_listed (stream,'/',tmp,LATT_NOINFERIORS + + ((sbuf.st_size && (sbuf.st_atime < sbuf.st_ctime))? + LATT_MARKED : LATT_UNMARKED),contents); + break; + } + } + } + closedir (dp); /* all done, flush directory */ + } +} + +/* Scan file for contents + * Accepts: driver to use + * file name + * desired contents + * length of contents + * size of file + * Returns: NIL if contents not found, T if found + */ + +long scan_contents (DRIVER *dtb,char *name,char *contents, + unsigned long csiz,unsigned long fsiz) +{ + scancontents_t sc = dtb ? + (scancontents_t) (*dtb->parameters) (GET_SCANCONTENTS,NIL) : NIL; + return (*(sc ? sc : dummy_scan_contents)) (name,contents,csiz,fsiz); +} + + +/* Scan file for contents + * Accepts: file name + * desired contents + * length of contents + * size of file + * Returns: NIL if contents not found, T if found + */ + +#define BUFSIZE 4*MAILTMPLEN + +long dummy_scan_contents (char *name,char *contents,unsigned long csiz, + unsigned long fsiz) +{ + int fd; + unsigned long ssiz,bsiz; + char *buf; + /* forget it if can't select or open */ + if ((fd = open (name,O_RDONLY,NIL)) >= 0) { + /* get buffer including slop */ + buf = (char *) fs_get (BUFSIZE + (ssiz = 4 * ((csiz / 4) + 1)) + 1); + memset (buf,'\0',ssiz); /* no slop area the first time */ + while (fsiz) { /* until end of file */ + read (fd,buf+ssiz,bsiz = min (fsiz,BUFSIZE)); + if (search ((unsigned char *) buf,bsiz+ssiz, + (unsigned char *) contents,csiz)) break; + memcpy (buf,buf+BUFSIZE,ssiz); + fsiz -= bsiz; /* note that we read that much */ + } + fs_give ((void **) &buf); /* flush buffer */ + close (fd); /* finished with file */ + if (fsiz) return T; /* found */ + } + return NIL; /* not found */ +} + +/* Mailbox found + * Accepts: MAIL stream + * hierarchy delimiter + * mailbox name + * attributes + * contents to search before calling mm_list() + * Returns: NIL if should abort hierarchy search, else T (currently always) + */ + +long dummy_listed (MAILSTREAM *stream,char delimiter,char *name, + long attributes,char *contents) +{ + DRIVER *d; + DIR *dp; + struct direct *dr; + dirfmttest_t dt; + unsigned long csiz; + struct stat sbuf; + int nochild; + char *s,tmp[MAILTMPLEN]; + if (!(attributes & LATT_NOINFERIORS) && mailboxdir (tmp,name,NIL) && + (dp = opendir (tmp))) { /* if not \NoInferiors */ + /* locate dirfmttest if any */ + for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL), dt = NIL; + !dt && d; d = d->next) + if (!(d->flags & DR_DISABLE) && (d->flags & DR_DIRFMT) && + (*d->valid) (name)) + dt = mail_parameters ((*d->open) (NIL),GET_DIRFMTTEST,NIL); + /* scan directory for children */ + for (nochild = T; nochild && (dr = readdir (dp)); ) + if ((!(dt && (*dt) (dr->d_name))) && + ((dr->d_name[0] != '.') || + (((long) mail_parameters (NIL,GET_HIDEDOTFILES,NIL)) ? NIL : + (dr->d_name[1] && ((dr->d_name[1] != '.') || dr->d_name[2]))))) + nochild = NIL; + attributes |= nochild ? LATT_HASNOCHILDREN : LATT_HASCHILDREN; + closedir (dp); /* all done, flush directory */ + } + d = NIL; /* don't \NoSelect dir if it has a driver */ + if ((attributes & LATT_NOSELECT) && (d = mail_valid (NIL,name,NIL)) && + (d != &dummydriver)) attributes &= ~LATT_NOSELECT; + if (!contents || /* notify main program */ + (!(attributes & LATT_NOSELECT) && (csiz = strlen (contents)) && + (s = mailboxfile (tmp,name)) && + (*s || (s = mail_parameters (NIL,GET_INBOXPATH,tmp))) && + !stat (s,&sbuf) && (d || (csiz <= sbuf.st_size)) && + SAFE_SCAN_CONTENTS (d,tmp,contents,csiz,sbuf.st_size))) + mm_list (stream,delimiter,name,attributes); + return T; +} + +/* Dummy create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long dummy_create (MAILSTREAM *stream,char *mailbox) +{ + char *s,tmp[MAILTMPLEN]; + long ret = NIL; + /* validate name */ + if (!(compare_cstring (mailbox,"INBOX") && (s = dummy_file (tmp,mailbox)))) { + sprintf (tmp,"Can't create %.80s: invalid name",mailbox); + MM_LOG (tmp,ERROR); + } + /* create the name, done if made directory */ + else if ((ret = dummy_create_path (stream,tmp,get_dir_protection(mailbox)))&& + (s = strrchr (s,'/')) && !s[1]) return T; + return ret ? set_mbx_protections (mailbox,tmp) : NIL; +} + +/* Dummy create path + * Accepts: mail stream + * path name to create + * directory mode + * Returns: T on success, NIL on failure + */ + +long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode) +{ + struct stat sbuf; + char c,*s,tmp[MAILTMPLEN]; + int fd; + long ret = NIL; + char *t = strrchr (path,'/'); + int wantdir = t && !t[1]; + int mask = umask (0); + if (wantdir) *t = '\0'; /* flush trailing delimiter for directory */ + if (s = strrchr (path,'/')) { /* found superior to this name? */ + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (path,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,path,dirmode)) { + umask (mask); /* restore mask */ + return NIL; + } + *s = c; /* restore full name */ + } + if (wantdir) { /* want to create directory? */ + ret = !mkdir (path,(int) dirmode); + *t = '/'; /* restore directory delimiter */ + } + /* create file */ + else if ((fd = open (path,O_WRONLY|O_CREAT|O_EXCL, + (long) mail_parameters(NIL,GET_MBXPROTECTION,NIL))) >=0) + ret = !close (fd); + if (!ret) { /* error? */ + sprintf (tmp,"Can't create mailbox node %.80s: %.80s",path,strerror (errno)); + MM_LOG (tmp,ERROR); + } + umask (mask); /* restore mask */ + return ret; /* return status */ +} + +/* Dummy delete mailbox + * Accepts: mail stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long dummy_delete (MAILSTREAM *stream,char *mailbox) +{ + struct stat sbuf; + char *s,tmp[MAILTMPLEN]; + if (!(s = dummy_file (tmp,mailbox))) { + sprintf (tmp,"Can't delete - invalid name: %.80s",s); + MM_LOG (tmp,ERROR); + } + /* no trailing / (workaround BSD kernel bug) */ + if ((s = strrchr (tmp,'/')) && !s[1]) *s = '\0'; + if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) == S_IFDIR) ? + rmdir (tmp) : unlink (tmp)) { + sprintf (tmp,"Can't delete mailbox %.80s: %.80s",mailbox,strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + return T; /* return success */ +} + +/* Mail rename mailbox + * Accepts: mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long dummy_rename (MAILSTREAM *stream,char *old,char *newname) +{ + struct stat sbuf; + char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN]; + /* no trailing / allowed */ + if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) || + stat (oldname,&sbuf) || ((s = strrchr (s,'/')) && !s[1] && + ((sbuf.st_mode & S_IFMT) != S_IFDIR))) { + sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname); + MM_LOG (mbx,ERROR); + return NIL; + } + if (s) { /* found a directory delimiter? */ + if (!s[1]) *s = '\0'; /* ignore trailing delimiter */ + else { /* found superior to destination name? */ + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (mbx,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create (stream,mbx)) return NIL; + *s = c; /* restore full name */ + } + } + /* rename of non-ex INBOX creates dest */ + if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf)) + return dummy_create (NIL,mbx); + if (rename (oldname,mbx)) { + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname, + strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + return T; /* return success */ +} + +/* Dummy open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *dummy_open (MAILSTREAM *stream) +{ + int fd; + char err[MAILTMPLEN],tmp[MAILTMPLEN]; + struct stat sbuf; + /* OP_PROTOTYPE call */ + if (!stream) return &dummyproto; + err[0] = '\0'; /* no error message yet */ + /* can we open the file? */ + if (!dummy_file (tmp,stream->mailbox)) + sprintf (err,"Can't open this name: %.80s",stream->mailbox); + else if ((fd = open (tmp,O_RDONLY,NIL)) < 0) { + /* no, error unless INBOX */ + if (compare_cstring (stream->mailbox,"INBOX")) + sprintf (err,"%.80s: %.80s",strerror (errno),stream->mailbox); + } + else { /* file had better be empty then */ + fstat (fd,&sbuf); /* sniff at its size */ + close (fd); + if ((sbuf.st_mode & S_IFMT) != S_IFREG) + sprintf (err,"Can't open %.80s: not a selectable mailbox", + stream->mailbox); + else if (sbuf.st_size) /* bogus format if non-empty */ + sprintf (err,"Can't open %.80s (file %.80s): not in valid mailbox format", + stream->mailbox,tmp); + } + if (err[0]) { /* if an error happened */ + MM_LOG (err,stream->silent ? WARN : ERROR); + return NIL; + } + else if (!stream->silent) { /* only if silence not requested */ + mail_exists (stream,0); /* say there are 0 messages */ + mail_recent (stream,0); /* and certainly no recent ones! */ + stream->uid_validity = time (0); + } + stream->inbox = T; /* note that it's an INBOX */ + return stream; /* return success */ +} + + +/* Dummy close + * Accepts: MAIL stream + * options + */ + +void dummy_close (MAILSTREAM *stream,long options) +{ + /* return silently */ +} + +/* Dummy ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +long dummy_ping (MAILSTREAM *stream) +{ + MAILSTREAM *test; + if (time (0) >= /* time to do another test? */ + ((time_t) (stream->gensym + + (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) { + /* has mailbox format changed? */ + if ((test = mail_open (NIL,stream->mailbox,OP_PROTOTYPE)) && + (test->dtb != stream->dtb) && + (test = mail_open (NIL,stream->mailbox,NIL))) { + /* preserve some resources */ + test->original_mailbox = stream->original_mailbox; + stream->original_mailbox = NIL; + test->sparep = stream->sparep; + stream->sparep = NIL; + test->sequence = stream->sequence; + mail_close ((MAILSTREAM *) /* flush resources used by dummy stream */ + memcpy (fs_get (sizeof (MAILSTREAM)),stream, + sizeof (MAILSTREAM))); + /* swap the streams */ + memcpy (stream,test,sizeof (MAILSTREAM)); + fs_give ((void **) &test);/* flush test now that copied */ + /* make sure application knows */ + mail_exists (stream,stream->recent = stream->nmsgs); + } + /* still hasn't changed */ + else stream->gensym = time (0); + } + return T; +} + + +/* Dummy check mailbox + * Accepts: MAIL stream + * No-op for readonly files, since read/writer can expunge it from under us! + */ + +void dummy_check (MAILSTREAM *stream) +{ + dummy_ping (stream); /* invoke ping */ +} + + +/* Dummy expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T, always + */ + +long dummy_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + return LONGT; +} + +/* Dummy copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * options + * Returns: T if copy successful, else NIL + */ + +long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy"); + return NIL; +} + + +/* Dummy append message string + * Accepts: mail stream + * destination mailbox + * append callback function + * data for callback + * Returns: T on success, NIL on failure + */ + +long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + struct stat sbuf; + int fd = -1; + int e; + char tmp[MAILTMPLEN]; + MAILSTREAM *ts = default_proto (T); + /* append to INBOX? */ + if (!compare_cstring (mailbox,"INBOX")) { + /* yes, if no empty proto try creating */ + if (!ts && !(*(ts = default_proto (NIL))->dtb->create) (ts,"INBOX")) + ts = NIL; + } + else if (dummy_file (tmp,mailbox) && ((fd = open (tmp,O_RDONLY,NIL)) < 0)) { + if ((e = errno) == ENOENT) /* failed, was it no such file? */ + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); + sprintf (tmp,"%.80s: %.80s",strerror (e),mailbox); + MM_LOG (tmp,ERROR); /* pass up error */ + return NIL; /* always fails */ + } + else if (fd >= 0) { /* found file? */ + fstat (fd,&sbuf); /* get its size */ + close (fd); /* toss out the fd */ + if (sbuf.st_size) ts = NIL; /* non-empty file? */ + } + if (ts) return (*ts->dtb->append) (stream,mailbox,af,data); + sprintf (tmp,"Indeterminate mailbox format: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; +} + +/* Dummy mail generate file string + * Accepts: temporary buffer to write into + * mailbox name string + * Returns: local file string or NIL if failure + */ + +char *dummy_file (char *dst,char *name) +{ + char *s = mailboxfile (dst,name); + /* return our standard inbox */ + return (s && !*s) ? strcpy (dst,sysinbox ()) : s; +} + + +/* Dummy canonicalize name + * Accepts: buffer to write name + * reference + * pattern + * Returns: T if success, NIL if failure + */ + +long dummy_canonicalize (char *tmp,char *ref,char *pat) +{ + unsigned long i; + char *s; + if (ref) { /* preliminary reference check */ + if (*ref == '{') return NIL;/* remote reference not allowed */ + else if (!*ref) ref = NIL; /* treat empty reference as no reference */ + } + switch (*pat) { + case '#': /* namespace name */ + if (mailboxfile (tmp,pat)) strcpy (tmp,pat); + else return NIL; /* unknown namespace */ + break; + case '{': /* remote names not allowed */ + return NIL; + case '/': /* rooted name */ + case '~': /* home directory name */ + if (!ref || (*ref != '#')) {/* non-namespace reference? */ + strcpy (tmp,pat); /* yes, ignore */ + break; + } + /* fall through */ + default: /* apply reference for all other names */ + if (!ref) strcpy (tmp,pat); /* just copy if no namespace */ + else if ((*ref != '#') || mailboxfile (tmp,ref)) { + /* wants root of name? */ + if (*pat == '/') strcpy (strchr (strcpy (tmp,ref),'/'),pat); + /* otherwise just append */ + else sprintf (tmp,"%s%s",ref,pat); + } + else return NIL; /* unknown namespace */ + } + /* count wildcards */ + for (i = 0, s = tmp; *s; *s++) if ((*s == '*') || (*s == '%')) ++i; + if (i > MAXWILDCARDS) { /* ridiculous wildcarding? */ + MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR); + return NIL; + } + return T; +} diff --git a/imap/src/osdep/amiga/dummy.h b/imap/src/osdep/amiga/dummy.h new file mode 100644 index 00000000..32650e06 --- /dev/null +++ b/imap/src/osdep/amiga/dummy.h @@ -0,0 +1,43 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Dummy routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 9 May 1991 + * Last Edited: 30 August 2006 + */ + +/* Exported function prototypes */ + +void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void dummy_list (MAILSTREAM *stream,char *ref,char *pat); +void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat); +long scan_contents (DRIVER *dtb,char *name,char *contents, + unsigned long csiz,unsigned long fsiz); +long dummy_scan_contents (char *name,char *contents,unsigned long csiz, + unsigned long fsiz); +long dummy_create (MAILSTREAM *stream,char *mailbox); +long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode); +long dummy_delete (MAILSTREAM *stream,char *mailbox); +long dummy_rename (MAILSTREAM *stream,char *old,char *newname); +char *dummy_file (char *dst,char *name); +long dummy_canonicalize (char *tmp,char *ref,char *pat); diff --git a/imap/src/osdep/amiga/env_ami.c b/imap/src/osdep/amiga/env_ami.c new file mode 100644 index 00000000..843757ed --- /dev/null +++ b/imap/src/osdep/amiga/env_ami.c @@ -0,0 +1,1282 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Amiga environment routines + * + * Author: Mark Crispin + * UW Technology + * Seattle, WA 98195 + * Internet: MRC@Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 15 May 2008 + */ + +#include <grp.h> +#include <signal.h> +#include <sys/wait.h> + +/* c-client environment parameters */ + +static char *myUserName = NIL; /* user name */ +static char *myHomeDir = NIL; /* home directory name */ +static char *myMailboxDir = NIL;/* mailbox directory name */ +static char *myLocalHost = NIL; /* local host name */ +static char *myNewsrc = NIL; /* newsrc file name */ +static char *mailsubdir = NIL; /* mail subdirectory name */ +static char *sysInbox = NIL; /* system inbox name */ +static char *newsActive = NIL; /* news active file */ +static char *newsSpool = NIL; /* news spool */ + /* anonymous home directory */ +static char *anonymousHome = NIL; +static char *ftpHome = NIL; /* ftp export home directory */ +static char *publicHome = NIL; /* public home directory */ +static char *sharedHome = NIL; /* shared home directory */ +static short anonymous = NIL; /* is anonymous */ +static short restrictBox = NIL; /* is a restricted box */ +static short has_no_life = NIL; /* is a cretin with no life */ + /* block environment init */ +static short block_env_init = NIL; +static short hideDotFiles = NIL;/* hide files whose names start with . */ + /* advertise filesystem root */ +static short advertisetheworld = NIL; + /* disable automatic shared namespaces */ +static short noautomaticsharedns = NIL; +static short no822tztext = NIL; /* disable RFC [2]822 timezone text */ +static short netfsstatbug = NIL;/* compensate for broken stat() on network + * filesystems (AFS and old NFS). Don't do + * this unless you really have to! + */ + /* 1 = disable plaintext, 2 = if not SSL */ +static long disablePlaintext = NIL; +static long list_max_level = 20;/* maximum level of list recursion */ + /* default file protection */ +static long mbx_protection = 0600; + /* default directory protection */ +static long dir_protection = 0700; + /* default lock file protection */ +static long lock_protection = MANDATORYLOCKPROT; + /* default ftp file protection */ +static long ftp_protection = 0644; +static long ftp_dir_protection = 0755; + /* default public file protection */ +static long public_protection = 0666; +static long public_dir_protection = 0777; + /* default shared file protection */ +static long shared_protection = 0660; +static long shared_dir_protection = 0770; +static long locktimeout = 5; /* default lock timeout */ + /* default prototypes */ +static MAILSTREAM *createProto = NIL; +static MAILSTREAM *appendProto = NIL; + /* default user flags */ +static char *userFlags[NUSERFLAGS] = {NIL}; +static NAMESPACE *nslist[3]; /* namespace list */ +static int logtry = 3; /* number of server login tries */ + /* block notification */ +static blocknotify_t mailblocknotify = mm_blocknotify; + +/* Amiga namespaces */ + + /* personal mh namespace */ +static NAMESPACE nsmhf = {"#mh/",'/',NIL,NIL}; +static NAMESPACE nsmh = {"#mhinbox",NIL,NIL,&nsmhf}; + /* home namespace */ +static NAMESPACE nshome = {"",'/',NIL,&nsmh}; + /* Amiga other user namespace */ +static NAMESPACE nsamigaother = {"~",'/',NIL,NIL}; + /* public (anonymous OK) namespace */ +static NAMESPACE nspublic = {"#public/",'/',NIL,NIL}; + /* netnews namespace */ +static NAMESPACE nsnews = {"#news.",'.',NIL,&nspublic}; + /* FTP export namespace */ +static NAMESPACE nsftp = {"#ftp/",'/',NIL,&nsnews}; + /* shared (no anonymous) namespace */ +static NAMESPACE nsshared = {"#shared/",'/',NIL,&nsftp}; + /* world namespace */ +static NAMESPACE nsworld = {"/",'/',NIL,&nsshared}; + +#include "write.c" /* include safe writing routines */ +#include "pmatch.c" /* include wildcard pattern matcher */ + +/* Get all authenticators */ + +#include "auths.c" + +/* Environment manipulate parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *env_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_NAMESPACE: + ret = (void *) nslist; + break; + case SET_USERNAME: + if (myUserName) fs_give ((void **) &myUserName); + myUserName = cpystr ((char *) value); + case GET_USERNAME: + ret = (void *) myUserName; + break; + case SET_HOMEDIR: + if (myHomeDir) fs_give ((void **) &myHomeDir); + myHomeDir = cpystr ((char *) value); + case GET_HOMEDIR: + ret = (void *) myHomeDir; + break; + case SET_LOCALHOST: + if (myLocalHost) fs_give ((void **) &myLocalHost); + myLocalHost = cpystr ((char *) value); + case GET_LOCALHOST: + ret = (void *) myLocalHost; + break; + case SET_NEWSRC: + if (myNewsrc) fs_give ((void **) &myNewsrc); + myNewsrc = cpystr ((char *) value); + case GET_NEWSRC: + ret = (void *) myNewsrc; + break; + case SET_NEWSACTIVE: + if (newsActive) fs_give ((void **) &newsActive); + newsActive = cpystr ((char *) value); + case GET_NEWSACTIVE: + ret = (void *) newsActive; + break; + case SET_NEWSSPOOL: + if (newsSpool) fs_give ((void **) &newsSpool); + newsSpool = cpystr ((char *) value); + case GET_NEWSSPOOL: + ret = (void *) newsSpool; + break; + + case SET_ANONYMOUSHOME: + if (anonymousHome) fs_give ((void **) &anonymousHome); + anonymousHome = cpystr ((char *) value); + case GET_ANONYMOUSHOME: + if (!anonymousHome) anonymousHome = cpystr (ANONYMOUSHOME); + ret = (void *) anonymousHome; + break; + case SET_FTPHOME: + if (ftpHome) fs_give ((void **) &ftpHome); + ftpHome = cpystr ((char *) value); + case GET_FTPHOME: + ret = (void *) ftpHome; + break; + case SET_PUBLICHOME: + if (publicHome) fs_give ((void **) &publicHome); + publicHome = cpystr ((char *) value); + case GET_PUBLICHOME: + ret = (void *) publicHome; + break; + case SET_SHAREDHOME: + if (sharedHome) fs_give ((void **) &sharedHome); + sharedHome = cpystr ((char *) value); + case GET_SHAREDHOME: + ret = (void *) sharedHome; + break; + case SET_SYSINBOX: + if (sysInbox) fs_give ((void **) &sysInbox); + sysInbox = cpystr ((char *) value); + case GET_SYSINBOX: + ret = (void *) sysInbox; + break; + case SET_LISTMAXLEVEL: + list_max_level = (long) value; + case GET_LISTMAXLEVEL: + ret = (void *) list_max_level; + break; + + case SET_MBXPROTECTION: + mbx_protection = (long) value; + case GET_MBXPROTECTION: + ret = (void *) mbx_protection; + break; + case SET_DIRPROTECTION: + dir_protection = (long) value; + case GET_DIRPROTECTION: + ret = (void *) dir_protection; + break; + case SET_LOCKPROTECTION: + lock_protection = (long) value; + case GET_LOCKPROTECTION: + ret = (void *) lock_protection; + break; + case SET_FTPPROTECTION: + ftp_protection = (long) value; + case GET_FTPPROTECTION: + ret = (void *) ftp_protection; + break; + case SET_PUBLICPROTECTION: + public_protection = (long) value; + case GET_PUBLICPROTECTION: + ret = (void *) public_protection; + break; + case SET_SHAREDPROTECTION: + shared_protection = (long) value; + case GET_SHAREDPROTECTION: + ret = (void *) shared_protection; + break; + case SET_FTPDIRPROTECTION: + ftp_dir_protection = (long) value; + case GET_FTPDIRPROTECTION: + ret = (void *) ftp_dir_protection; + break; + case SET_PUBLICDIRPROTECTION: + public_dir_protection = (long) value; + case GET_PUBLICDIRPROTECTION: + ret = (void *) public_dir_protection; + break; + case SET_SHAREDDIRPROTECTION: + shared_dir_protection = (long) value; + case GET_SHAREDDIRPROTECTION: + ret = (void *) shared_dir_protection; + break; + + case SET_LOCKTIMEOUT: + locktimeout = (long) value; + case GET_LOCKTIMEOUT: + ret = (void *) locktimeout; + break; + case SET_HIDEDOTFILES: + hideDotFiles = value ? T : NIL; + case GET_HIDEDOTFILES: + ret = (void *) (hideDotFiles ? VOIDT : NIL); + break; + case SET_DISABLEPLAINTEXT: + disablePlaintext = (long) value; + case GET_DISABLEPLAINTEXT: + ret = (void *) disablePlaintext; + break; + case SET_ADVERTISETHEWORLD: + advertisetheworld = value ? T : NIL; + case GET_ADVERTISETHEWORLD: + ret = (void *) (advertisetheworld ? VOIDT : NIL); + break; + case SET_DISABLEAUTOSHAREDNS: + noautomaticsharedns = value ? T : NIL; + case GET_DISABLEAUTOSHAREDNS: + ret = (void *) (noautomaticsharedns ? VOIDT : NIL); + break; + case SET_DISABLE822TZTEXT: + no822tztext = value ? T : NIL; + case GET_DISABLE822TZTEXT: + ret = (void *) (no822tztext ? VOIDT : NIL); + break; + case SET_USERHASNOLIFE: + has_no_life = value ? T : NIL; + case GET_USERHASNOLIFE: + ret = (void *) (has_no_life ? VOIDT : NIL); + break; + case SET_NETFSSTATBUG: + netfsstatbug = value ? T : NIL; + case GET_NETFSSTATBUG: + ret = (void *) (netfsstatbug ? VOIDT : NIL); + break; + case SET_BLOCKENVINIT: + block_env_init = value ? T : NIL; + case GET_BLOCKENVINIT: + ret = (void *) (block_env_init ? VOIDT : NIL); + break; + case SET_BLOCKNOTIFY: + mailblocknotify = (blocknotify_t) value; + case GET_BLOCKNOTIFY: + ret = (void *) mailblocknotify; + break; + } + return ret; +} + +/* Write current time + * Accepts: destination string + * optional format of day-of-week prefix + * format of date and time + * flag whether to append symbolic timezone + */ + +static void do_date (char *date,char *prefix,char *fmt,int suffix) +{ + time_t tn = time (0); + struct tm *t = gmtime (&tn); + int zone = t->tm_hour * 60 + t->tm_min; + int julian = t->tm_yday; + t = localtime (&tn); /* get local time now */ + /* minus UTC minutes since midnight */ + zone = t->tm_hour * 60 + t->tm_min - zone; + /* julian can be one of: + * 36x local time is December 31, UTC is January 1, offset -24 hours + * 1 local time is 1 day ahead of UTC, offset +24 hours + * 0 local time is same day as UTC, no offset + * -1 local time is 1 day behind UTC, offset -24 hours + * -36x local time is January 1, UTC is December 31, offset +24 hours + */ + if (julian = t->tm_yday -julian) + zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60; + if (prefix) { /* want day of week? */ + sprintf (date,prefix,days[t->tm_wday]); + date += strlen (date); /* make next sprintf append */ + } + /* output the date */ + sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900, + t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60); + /* append timezone suffix if desired */ + if (suffix) rfc822_timezone (date,(void *) t); +} + +/* Write current time in RFC 822 format + * Accepts: destination string + */ + +void rfc822_date (char *date) +{ + do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d", + no822tztext ? NIL : T); +} + + +/* Write current time in fixed-width RFC 822 format + * Accepts: destination string + */ + +void rfc822_fixed_date (char *date) +{ + do_date (date,NIL,"%02d %s %4d %02d:%02d:%02d %+03d%02d",NIL); +} + + +/* Write current time in internal format + * Accepts: destination string + */ + +void internal_date (char *date) +{ + do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL); +} + +/* Initialize server + * Accepts: server name for syslog or NIL + * /etc/services service name or NIL + * alternate /etc/services service name or NIL + * clock interrupt handler + * kiss-of-death interrupt handler + * hangup interrupt handler + * termination interrupt handler + */ + +void server_init (char *server,char *service,char *sslservice, + void *clkint,void *kodint,void *hupint,void *trmint, + void *staint) +{ + int onceonly = server && service && sslservice; + if (onceonly) { /* set server name in syslog */ + int mask; + openlog (myServerName = cpystr (server),LOG_PID,syslog_facility); + fclose (stderr); /* possibly save a process ID */ + + switch (mask = umask (022)){/* check old umask */ + case 0: /* definitely unreasonable */ + case 022: /* don't need to change it */ + break; + default: /* already was a reasonable value */ + umask (mask); /* so change it back */ + } + } + arm_signal (SIGALRM,clkint); /* prepare for clock interrupt */ + arm_signal (SIGUSR2,kodint); /* prepare for Kiss Of Death */ + arm_signal (SIGHUP,hupint); /* prepare for hangup */ + arm_signal (SIGPIPE,hupint); /* alternative hangup */ + arm_signal (SIGTERM,trmint); /* prepare for termination */ + /* status dump */ + if (staint) arm_signal (SIGUSR1,staint); + if (onceonly) { /* set up network and maybe SSL */ + long port; + struct servent *sv; + /* Use SSL if SSL service, or if server starts with "s" and not service */ + if (((port = tcp_serverport ()) >= 0)) { + if ((sv = getservbyname (service,"tcp")) && (port == ntohs (sv->s_port))) + syslog (LOG_DEBUG,"%s service init from %s",service,tcp_clientaddr ()); + else if ((sv = getservbyname (sslservice,"tcp")) && + (port == ntohs (sv->s_port))) { + syslog (LOG_DEBUG,"%s SSL service init from %s",sslservice, + tcp_clientaddr ()); + ssl_server_init (server); + } + else { /* not service or SSL service port */ + syslog (LOG_DEBUG,"port %ld service init from %s",port, + tcp_clientaddr ()); + if (*server == 's') ssl_server_init (server); + } + } + } +} + +/* Wait for stdin input + * Accepts: timeout in seconds + * Returns: T if have input on stdin, else NIL + */ + +long server_input_wait (long seconds) +{ + fd_set rfd,efd; + struct timeval tmo; + FD_ZERO (&rfd); + FD_ZERO (&efd); + FD_SET (0,&rfd); + FD_SET (0,&efd); + tmo.tv_sec = seconds; tmo.tv_usec = 0; + return select (1,&rfd,0,&efd,&tmo) ? LONGT : NIL; +} + +/* Return Amiga password entry for user name + * Accepts: user name string + * Returns: password entry + * + * Tries all-lowercase form of user name if given user name fails + */ + +static struct passwd *pwuser (unsigned char *user) +{ + unsigned char *s; + struct passwd *pw = getpwnam (user); + if (!pw) { /* failed, see if any uppercase characters */ + for (s = user; *s && ((*s < 'A') || (*s > 'Z')); s++); + if (*s) { /* yes, try all lowercase form */ + pw = getpwnam (s = lcase (cpystr (user))); + fs_give ((void **) &s); + } + } + return pw; +} + + +/* Validate password for user name + * Accepts: user name string + * password string + * argument count + * argument vector + * Returns: password entry if validated + * + * Tries password+1 if password fails and starts with space + */ + +static struct passwd *valpwd (char *user,char *pwd,int argc,char *argv[]) +{ + char *s; + struct passwd *pw; + struct passwd *ret = NIL; + if (auth_md5.server) { /* using CRAM-MD5 authentication? */ + if (s = auth_md5_pwd (user)) { + if (!strcmp (s,pwd) || ((*pwd == ' ') && pwd[1] && !strcmp (s,pwd+1))) + ret = pwuser (user); /* validated, get passwd entry for user */ + memset (s,0,strlen (s)); /* erase sensitive information */ + fs_give ((void **) &s); + } + } + else if (pw = pwuser (user)) {/* can get user? */ + s = cpystr (pw->pw_name); /* copy returned name in case we need it */ + if (*pwd && !(ret = checkpw (pw,pwd,argc,argv)) && + (*pwd == ' ') && pwd[1] && (ret = pwuser (s))) + ret = checkpw (pw,pwd+1,argc,argv); + fs_give ((void **) &s); /* don't need copy of name any more */ + } + return ret; +} + +/* Server log in + * Accepts: user name string + * password string + * authenticating user name string + * argument count + * argument vector + * Returns: T if password validated, NIL otherwise + */ + +long server_login (char *user,char *pwd,char *authuser,int argc,char *argv[]) +{ + struct passwd *pw = NIL; + int level = LOG_NOTICE; + char *err = "failed"; + /* cretins still haven't given up */ + if ((strlen (user) >= NETMAXUSER) || + (authuser && (strlen (authuser) >= NETMAXUSER))) { + level = LOG_ALERT; /* escalate this alert */ + err = "SYSTEM BREAK-IN ATTEMPT"; + logtry = 0; /* render this session useless */ + } + else if (logtry-- <= 0) err = "excessive login failures"; + else if (disablePlaintext) err = "disabled"; + else if (!(authuser && *authuser)) pw = valpwd (user,pwd,argc,argv); + else if (valpwd (authuser,pwd,argc,argv)) pw = pwuser (user); + if (pw && pw_login (pw,authuser,pw->pw_name,NIL,argc,argv)) return T; + syslog (level|LOG_AUTH,"Login %s user=%.64s auth=%.64s host=%.80s",err, + user,(authuser && *authuser) ? authuser : user,tcp_clienthost ()); + sleep (3); /* slow down possible cracker */ + return NIL; +} + +/* Authenticated server log in + * Accepts: user name string + * authenticating user name string + * argument count + * argument vector + * Returns: T if password validated, NIL otherwise + */ + +long authserver_login (char *user,char *authuser,int argc,char *argv[]) +{ + return pw_login (pwuser (user),authuser,user,NIL,argc,argv); +} + + +/* Log in as anonymous daemon + * Accepts: argument count + * argument vector + * Returns: T if successful, NIL if error + */ + +long anonymous_login (int argc,char *argv[]) +{ + /* log in Mr. A. N. Onymous */ + return pw_login (getpwnam (ANONYMOUSUSER),NIL,NIL, + (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL), + argc,argv); +} + +/* Finish log in and environment initialization + * Accepts: passwd struct for loginpw() + * optional authentication user name + * user name (NIL for anonymous) + * home directory (NIL to use directory from passwd struct) + * argument count + * argument vector + * Returns: T if successful, NIL if error + */ + +long pw_login (struct passwd *pw,char *auser,char *user,char *home,int argc, + char *argv[]) +{ + struct group *gr; + char **t; + long ret = NIL; + if (pw && pw->pw_uid) { /* must have passwd struct for non-UID 0 */ + /* make safe copies of user and home */ + if (user) user = cpystr (pw->pw_name); + home = cpystr (home ? home : pw->pw_dir); + /* authorization ID .NE. authentication ID? */ + if (user && auser && *auser && compare_cstring (auser,user)) { + /* scan list of mail administrators */ + if ((gr = getgrnam (ADMINGROUP)) && (t = gr->gr_mem)) while (*t && !ret) + if (!compare_cstring (auser,*t++)) + ret = pw_login (pw,NIL,user,home,argc,argv); + syslog (LOG_NOTICE|LOG_AUTH,"%s %.80s override of user=%.80s host=%.80s", + ret ? "Admin" : "Failed",auser,user,tcp_clienthost ()); + } + /* normal login */ + else if (((pw->pw_uid == geteuid ()) || loginpw (pw,argc,argv)) && + (ret = env_init (user,home))) chdir (myhomedir ()); + fs_give ((void **) &home); /* clean up */ + if (user) fs_give ((void **) &user); + } + return ret; /* return status */ +} + +/* Initialize environment + * Accepts: user name (NIL for anonymous) + * home directory name + * Returns: T, always + */ + +long env_init (char *user,char *home) +{ + extern MAILSTREAM CREATEPROTO; + extern MAILSTREAM EMPTYPROTO; + struct passwd *pw; + struct stat sbuf; + char tmp[MAILTMPLEN]; + /* don't init if blocked */ + if (block_env_init) return LONGT; + if (myUserName) fatal ("env_init called twice!"); + /* set up user name */ + myUserName = cpystr (user ? user : ANONYMOUSUSER); + if (user) { /* remember user name and home directory */ + nslist[0] = &nshome; /* home namespace */ + nslist[1] = &nsamigaother; + nslist[2] = advertisetheworld ? &nsworld : &nsshared; + } + else { /* anonymous user */ + nslist[0] = nslist[1] = NIL,nslist[2] = &nsftp; + sprintf (tmp,"%s/INBOX", + home = (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL)); + sysInbox = cpystr (tmp); /* make system INBOX */ + anonymous = T; /* flag as anonymous */ + } + myHomeDir = cpystr (home); /* set home directory */ + if (!noautomaticsharedns) { + /* #ftp namespace */ + if (!ftpHome && (pw = getpwnam ("ftp"))) ftpHome = cpystr (pw->pw_dir); + /* #public namespace */ + if (!publicHome && (pw = getpwnam ("imappublic"))) + publicHome = cpystr (pw->pw_dir); + /* #shared namespace */ + if (!anonymous && !sharedHome && (pw = getpwnam ("imapshared"))) + sharedHome = cpystr (pw->pw_dir); + } + if (!myLocalHost) mylocalhost (); + if (!myNewsrc) myNewsrc = cpystr(strcat (strcpy (tmp,myHomeDir),"/.newsrc")); + if (!newsActive) newsActive = cpystr (ACTIVEFILE); + if (!newsSpool) newsSpool = cpystr (NEWSSPOOL); + /* force default prototype to be set */ + if (!createProto) createProto = &CREATEPROTO; + if (!appendProto) appendProto = &EMPTYPROTO; + /* re-do open action to get flags */ + (*createProto->dtb->open) (NIL); + endpwent (); /* close pw database */ + return T; +} + +/* Return my user name + * Accepts: pointer to optional flags + * Returns: my user name + */ + +char *myusername_full (unsigned long *flags) +{ + struct passwd *pw; + struct stat sbuf; + char *s; + unsigned long euid; + char *ret = UNLOGGEDUSER; + /* no user name yet and not root? */ + if (!myUserName && (euid = geteuid ())) { + /* yes, look up getlogin() user name or EUID */ + if (((s = (char *) getlogin ()) && *s && (strlen (s) < NETMAXUSER) && + (pw = getpwnam (s)) && (pw->pw_uid == euid)) || + (pw = getpwuid (euid))) { + if (block_env_init) { /* don't env_init if blocked */ + if (flags) *flags = MU_LOGGEDIN; + return pw->pw_name; + } + env_init (pw->pw_name, + ((s = getenv ("HOME")) && *s && (strlen (s) < NETMAXMBX) && + !stat (s,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) ? + s : pw->pw_dir); + } + else fatal ("Unable to look up user name"); + } + if (myUserName) { /* logged in? */ + if (flags) *flags = anonymous ? MU_ANONYMOUS : MU_LOGGEDIN; + ret = myUserName; /* return user name */ + } + else if (flags) *flags = MU_NOTLOGGEDIN; + return ret; +} +/* Return my local host name + * Returns: my local host name + */ + +char *mylocalhost () +{ + char tmp[MAILTMPLEN]; + struct hostent *host_name; + if (!myLocalHost) myLocalHost = cpystr (gethostname (tmp,MAILTMPLEN-1) ? + "random-pc" : tcp_canonical (tmp)); + return myLocalHost; +} + +/* Return my home directory name + * Returns: my home directory name + */ + +char *myhomedir () +{ + if (!myHomeDir) myusername ();/* initialize if first time */ + return myHomeDir ? myHomeDir : ""; +} + + +/* Return my home mailbox name + * Returns: my home directory name + */ + +static char *mymailboxdir () +{ + char *home = myhomedir (); + if (!myMailboxDir && home) { /* initialize if first time */ + if (mailsubdir) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"%s/%s",home,mailsubdir); + myMailboxDir = cpystr (tmp);/* use pre-defined subdirectory of home */ + } + else myMailboxDir = cpystr (home); + } + return myMailboxDir ? myMailboxDir : ""; +} + + +/* Return system standard INBOX + * Accepts: buffer string + */ + +char *sysinbox () +{ + char tmp[MAILTMPLEN]; + if (!sysInbox) { /* initialize if first time */ + sprintf (tmp,"%s/%s",MAILSPOOL,myusername ()); + sysInbox = cpystr (tmp); /* system inbox is from mail spool */ + } + return sysInbox; +} + +/* Return mailbox directory name + * Accepts: destination buffer + * directory prefix + * name in directory + * Returns: file name or NIL if error + */ + +char *mailboxdir (char *dst,char *dir,char *name) +{ + char tmp[MAILTMPLEN]; + if (dir || name) { /* if either argument provided */ + if (dir) { + if (strlen (dir) > NETMAXMBX) return NIL; + strcpy (tmp,dir); /* write directory prefix */ + } + else tmp[0] = '\0'; /* otherwise null string */ + if (name) { + if (strlen (name) > NETMAXMBX) return NIL; + strcat (tmp,name); /* write name in directory */ + } + /* validate name, return its name */ + if (!mailboxfile (dst,tmp)) return NIL; + } + /* no arguments, wants mailbox directory */ + else strcpy (dst,mymailboxdir ()); + return dst; /* return the name */ +} + +/* Return mailbox file name + * Accepts: destination buffer + * mailbox name + * Returns: file name or empty string for driver-selected INBOX or NIL if error + */ + +char *mailboxfile (char *dst,char *name) +{ + struct passwd *pw; + char *s; + if (!name || !*name || (*name == '{') || (strlen (name) > NETMAXMBX) || + ((anonymous || restrictBox || (*name == '#')) && + (strstr (name,"..") || strstr (name,"//") || strstr (name,"/~")))) + dst = NIL; /* invalid name */ + else switch (*name) { /* determine mailbox type based upon name */ + case '#': /* namespace name */ + /* #ftp/ namespace */ + if (((name[1] == 'f') || (name[1] == 'F')) && + ((name[2] == 't') || (name[2] == 'T')) && + ((name[3] == 'p') || (name[3] == 'P')) && + (name[4] == '/') && ftpHome) sprintf (dst,"%s/%s",ftpHome,name+5); + /* #public/ and #shared/ namespaces */ + else if ((((name[1] == 'p') || (name[1] == 'P')) && + ((name[2] == 'u') || (name[2] == 'U')) && + ((name[3] == 'b') || (name[3] == 'B')) && + ((name[4] == 'l') || (name[4] == 'L')) && + ((name[5] == 'i') || (name[5] == 'I')) && + ((name[6] == 'c') || (name[6] == 'C')) && + (name[7] == '/') && (s = publicHome)) || + (!anonymous && ((name[1] == 's') || (name[1] == 'S')) && + ((name[2] == 'h') || (name[2] == 'H')) && + ((name[3] == 'a') || (name[3] == 'A')) && + ((name[4] == 'r') || (name[4] == 'R')) && + ((name[5] == 'e') || (name[5] == 'E')) && + ((name[6] == 'd') || (name[6] == 'D')) && + (name[7] == '/') && (s = sharedHome))) + sprintf (dst,"%s/%s",s,compare_cstring (name,"INBOX") ? name : "INBOX"); + else dst = NIL; /* unknown namespace */ + break; + + case '/': /* root access */ + if (anonymous) dst = NIL; /* anonymous forbidden to do this */ + else if ((restrictBox & RESTRICTROOT) && strcmp (name,sysinbox ())) + dst = NIL; /* restricted and not access to sysinbox */ + else strcpy (dst,name); /* unrestricted, copy root name */ + break; + case '~': /* other user access */ + /* bad syntax or anonymous can't win */ + if (!*++name || anonymous) dst = NIL; + /* ~/ equivalent to ordinary name */ + else if (*name == '/') sprintf (dst,"%s/%s",mymailboxdir (),name+1); + /* other user forbidden if restricted */ + else if (restrictBox & RESTRICTOTHERUSER) dst = NIL; + else { /* clear box other user */ + /* copy user name */ + for (s = dst; *name && (*name != '/'); *s++ = *name++); + *s++ = '\0'; /* tie off user name, look up in passwd file */ + if ((pw = getpwnam (dst)) && pw->pw_dir) { + if (*name) name++; /* skip past the slash */ + /* canonicalize case of INBOX */ + if (!compare_cstring (name,"INBOX")) name = "INBOX"; + /* remove trailing / from directory */ + if ((s = strrchr (pw->pw_dir,'/')) && !s[1]) *s = '\0'; + /* don't allow ~root/ if restricted root */ + if ((restrictBox & RESTRICTROOT) && !*pw->pw_dir) dst = NIL; + /* build final name w/ subdir if needed */ + else if (mailsubdir) sprintf (dst,"%s/%s/%s",pw->pw_dir,mailsubdir,name); + else sprintf (dst,"%s/%s",pw->pw_dir,name); + } + else dst = NIL; /* no such user */ + } + break; + case 'I': case 'i': /* possible INBOX */ + if (!compare_cstring (name+1,"NBOX")) { + /* if anonymous, use INBOX in mailbox dir */ + if (anonymous) sprintf (dst,"%s/INBOX",mymailboxdir ()); + else *dst = '\0'; /* otherwise driver selects the name */ + break; + } + /* drop into to ordinary name case */ + default: /* ordinary name is easy */ + sprintf (dst,"%s/%s",mymailboxdir (),name); + break; + } + return dst; /* return final name */ +} + +/* Dot-lock file locker + * Accepts: file name to lock + * destination buffer for lock file name + * open file description on file name to lock + * Returns: T if success, NIL if failure + */ + +long dotlock_lock (char *file,DOTLOCK *base,int fd) +{ + int i = locktimeout * 60; + int j,mask,retry,pi[2],po[2]; + char *s,tmp[MAILTMPLEN]; + struct stat sb; + /* flush absurd file name */ + if (strlen (file) > 512) return NIL; + /* build lock filename */ + sprintf (base->lock,"%s.lock",file); + /* assume no pipe */ + base->pipei = base->pipeo = -1; + do { /* make sure not symlink */ + if (!(j = chk_notsymlink (base->lock,&sb))) return NIL; + /* time out if file older than 5 minutes */ + if ((j > 0) && ((time (0)) >= (sb.st_ctime + locktimeout * 60))) i = 0; + /* try to create the lock */ + if ((j = open (name,O_WRONLY|O_CREAT|O_EXCL,(int) lock_protection)) >= 0) { + close (i); /* make the file, now close it */ + chmod (base->lock,(int) lock_protection); + return LONGT; + } + if (errno == EEXIST) { /* already locked? */ + retry = -1; /* can try again */ + if (!(i%15)) { /* time to notify? */ + sprintf (tmp,"Mailbox %.80s is locked, will override in %d seconds...", + file,i); + mm_log (tmp,WARN); + } + sleep (1); /* wait 1 second before next try */ + } + else retry = i = 0; /* hard failure, no more retries */ + } while (i--); /* until out of retries */ + if (retry < 0) { /* still returning retry after locktimeout? */ + if (!(j = chk_notsymlink (base->lock,&sb))) return NIL; + if ((j > 0) && ((time (0)) < (sb.st_ctime + locktimeout * 60))) { + sprintf (tmp,"Mailbox vulnerable - seizing %ld second old lock", + (long) (time (0) - sb.st_ctime)); + mm_log (tmp,WARN); + } + mask = umask (0); + unlink (base->lock); /* try to remove the old file */ + /* seize the lock */ + if ((i = open (base->lock,O_WRONLY|O_CREAT,(int) lock_protection)) >= 0) { + close (i); /* don't need descriptor any more */ + sprintf (tmp,"Mailbox %.80s lock overridden",file); + mm_log (tmp,NIL); + chmod (base->lock,(int) lock_protection); + umask (mask) /* restore old umask */ + return LONGT; + } + umask (mask) /* restore old umask */ + } + + if (fd >= 0) switch (errno) { + case EACCES: /* protection failure? */ + /* make command pipes */ + if (!stat (LOCKPGM,&sb) && (pipe (pi) >= 0)) { + /* if input pipes usable create output pipes */ + if ((pi[0] < FD_SETSIZE) && (pi[1] < FD_SETSIZE) && (pipe (po) >= 0)) { + /* make sure output pipes are usable */ + if ((po[0] >= FD_SETSIZE) || (po[1] >= FD_SETSIZE)); + /* all is good, make inferior process */ + else if (!(j = fork ())) { + if (!fork ()) { /* make grandchild so it's inherited by init */ + long cf; /* don't change caller vars in case vfork() */ + char *argv[4],arg[20]; + /* prepare argument vector */ + sprintf (arg,"%d",fd); + argv[0] = LOCKPGM; argv[1] = arg; + argv[2] = file; argv[3] = NIL; + /* set parent's I/O to my O/I */ + dup2 (pi[1],1); dup2 (pi[1],2); dup2 (po[0],0); + /* close all unnecessary descriptors */ + for (cf = max (20,max (max (pi[0],pi[1]),max(po[0],po[1]))); + cf >= 3; --cf) if (cf != fd) close (cf); + /* be our own process group */ + setpgrp (0,getpid ()); + /* now run it */ + _exit (execv (argv[0],argv)); + } + _exit (1); /* child is done */ + } + else if (j > 0) { /* parent process */ + fd_set rfd; + struct timeval tmo; + FD_ZERO (&rfd); + FD_SET (pi[0],&rfd); + tmo.tv_sec = locktimeout * 60; + grim_pid_reap (j,NIL);/* reap child; grandchild now owned by init */ + /* read response from locking program */ + if (select (pi[0]+1,&rfd,0,0,&tmo) && + (read (pi[0],tmp,1) == 1) && (tmp[0] == '+')) { + /* success, record pipes */ + base->pipei = pi[0]; base->pipeo = po[1]; + /* close child's side of the pipes */ + close (pi[1]); close (po[0]); + return LONGT; + } + } + close (po[0]); close (po[1]); + } + close (pi[0]); close (pi[1]); + } + /* find directory/file delimiter */ + if (s = strrchr (base->lock,'/')) { + *s = '\0'; /* tie off at directory */ + sprintf(tmp, /* generate default message */ + "Mailbox vulnerable - directory %.80s must have 1777 protection", + base->lock); + /* definitely not 1777 if can't stat */ + mask = stat (base->lock,&sb) ? 0 : (sb.st_mode & 1777); + *s = '/'; /* restore lock name */ + if (mask != 1777) { /* default warning if not 1777 */ + MM_LOG (tmp,WARN); + break; + } + } + default: + sprintf (tmp,"Mailbox vulnerable - error creating %.80s: %s", + base->lock,strerror (errno)); + mm_log (tmp,WARN); /* this is probably not good */ + break; + } + base->lock[0] = '\0'; /* don't use lock files */ + return NIL; +} + +/* Dot-lock file unlocker + * Accepts: lock file name + * Returns: T if success, NIL if failure + */ + +long dotlock_unlock (DOTLOCK *base) +{ + long ret = LONGT; + if (base && base->lock[0]) { + if (base->pipei >= 0) { /* if running through a pipe unlocker */ + ret = (write (base->pipeo,"+",1) == 1); + /* nuke the pipes */ + close (base->pipei); close (base->pipeo); + } + else ret = !unlink (base->lock); + } + return ret; +} + +/* Lock file name + * Accepts: scratch buffer + * file name + * type of locking operation (LOCK_SH or LOCK_EX) + * pointer to return PID of locker + * Returns: file descriptor of lock or negative if error + */ + +int lockname (char *lock,char *fname,int op,long *pid) +{ + struct stat sbuf; + *pid = 0; /* no locker PID */ + return stat (fname,&sbuf) ? -1 : lock_work (lock,&sbuf,op,pid); +} + + +/* Lock file descriptor + * Accepts: file descriptor + * lock file name buffer + * type of locking operation (LOCK_SH or LOCK_EX) + * Returns: file descriptor of lock or negative if error + */ + +int lockfd (int fd,char *lock,int op) +{ + struct stat sbuf; + return fstat (fd,&sbuf) ? -1 : lock_work (lock,&sbuf,op,NIL); +} + +/* Lock file name worker + * Accepts: lock file name + * pointer to stat() buffer + * type of locking operation (LOCK_SH or LOCK_EX) + * pointer to return PID of locker + * Returns: file descriptor of lock or negative if error + */ + +int lock_work (char *lock,void *sb,int op,long *pid) +{ + struct stat lsb,fsb; + struct stat *sbuf = (struct stat *) sb; + char tmp[MAILTMPLEN]; + long i; + int fd; + int mask = umask (0); + if (pid) *pid = 0; /* initialize return PID */ + /* make temporary lock file name */ + sprintf (lock,"%s/.%lx.%lx","/tmp", + (unsigned long) sbuf->st_dev,(unsigned long) sbuf->st_ino); + while (T) { /* until get a good lock */ + do switch ((int) chk_notsymlink (lock,&lsb)) { + case 1: /* exists just once */ + if (((fd = open (lock,O_RDWR,lock_protection)) >= 0) || + (errno != ENOENT) || (chk_notsymlink (lock,&lsb) >= 0)) break; + case -1: /* name doesn't exist */ + fd = open (lock,O_RDWR|O_CREAT|O_EXCL,lock_protection); + break; + default: /* multiple hard links */ + mm_log ("hard link to lock name",ERROR); + syslog (LOG_CRIT,"SECURITY PROBLEM: hard link to lock name: %.80s",lock); + case 0: /* symlink (already did syslog) */ + umask (mask); /* restore old mask */ + return -1; /* fail: no lock file */ + } while ((fd < 0) && (errno == EEXIST)); + if (fd < 0) { /* failed to get file descriptor */ + syslog (LOG_INFO,"Mailbox lock file %s open failure: %s",lock, + strerror (errno)); + if (stat ("/tmp",&lsb)) + syslog (LOG_CRIT,"SYSTEM ERROR: no /tmp: %s",strerror (errno)); + else if ((lsb.st_mode & 01777) != 01777) + mm_log ("Can't lock for write: /tmp must have 1777 protection",WARN); + umask (mask); /* restore old mask */ + return -1; /* fail: can't open lock file */ + } + + /* non-blocking form */ + if (op & LOCK_NB) i = flock (fd,op); + else { /* blocking form */ + (*mailblocknotify) (BLOCK_FILELOCK,NIL); + i = flock (fd,op); + (*mailblocknotify) (BLOCK_NONE,NIL); + } + if (i) { /* failed, get other process' PID */ + if (pid && !fstat (fd,&fsb) && (i = min (fsb.st_size,MAILTMPLEN-1)) && + (read (fd,tmp,i) == i) && !(tmp[i] = 0) && ((i = atol (tmp)) > 0)) + *pid = i; + close (fd); /* failed, give up on lock */ + umask (mask); /* restore old mask */ + return -1; /* fail: can't lock */ + } + /* make sure this lock is good for us */ + if (!lstat (lock,&lsb) && ((lsb.st_mode & S_IFMT) != S_IFLNK) && + !fstat (fd,&fsb) && (lsb.st_dev == fsb.st_dev) && + (lsb.st_ino == fsb.st_ino) && (fsb.st_nlink == 1)) break; + close (fd); /* lock not right, drop fd and try again */ + } + /* make sure mode OK (don't use fchmod()) */ + chmod (lock,(int) lock_protection); + umask (mask); /* restore old mask */ + return fd; /* success */ +} + +/* Check to make sure not a symlink + * Accepts: file name + * stat buffer + * Returns: -1 if doesn't exist, NIL if symlink, else number of hard links + */ + +long chk_notsymlink (char *name,void *sb) +{ + struct stat *sbuf = (struct stat *) sb; + /* name exists? */ + if (lstat (name,sbuf)) return -1; + /* forbid symbolic link */ + if ((sbuf->st_mode & S_IFMT) == S_IFLNK) { + mm_log ("symbolic link on lock name",ERROR); + syslog (LOG_CRIT,"SECURITY PROBLEM: symbolic link on lock name: %.80s", + name); + return NIL; + } + return (long) sbuf->st_nlink; /* return number of hard links */ +} + + +/* Unlock file descriptor + * Accepts: file descriptor + * lock file name from lockfd() + */ + +void unlockfd (int fd,char *lock) +{ + /* delete the file if no sharers */ + if (!flock (fd,LOCK_EX|LOCK_NB)) unlink (lock); + flock (fd,LOCK_UN); /* unlock it */ + close (fd); /* close it */ +} + +/* Set proper file protection for mailbox + * Accepts: mailbox name + * actual file path name + * Returns: T, always + */ + +long set_mbx_protections (char *mailbox,char *path) +{ + struct stat sbuf; + int mode = (int) mbx_protection; + if (*mailbox == '#') { /* possible namespace? */ + if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) && + ((mailbox[2] == 't') || (mailbox[2] == 'T')) && + ((mailbox[3] == 'p') || (mailbox[3] == 'P')) && + (mailbox[4] == '/')) mode = (int) ftp_protection; + else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) && + ((mailbox[2] == 'u') || (mailbox[2] == 'U')) && + ((mailbox[3] == 'b') || (mailbox[3] == 'B')) && + ((mailbox[4] == 'l') || (mailbox[4] == 'L')) && + ((mailbox[5] == 'i') || (mailbox[5] == 'I')) && + ((mailbox[6] == 'c') || (mailbox[6] == 'C')) && + (mailbox[7] == '/')) mode = (int) public_protection; + else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) && + ((mailbox[2] == 'h') || (mailbox[2] == 'H')) && + ((mailbox[3] == 'a') || (mailbox[3] == 'A')) && + ((mailbox[4] == 'r') || (mailbox[4] == 'R')) && + ((mailbox[5] == 'e') || (mailbox[5] == 'E')) && + ((mailbox[6] == 'd') || (mailbox[6] == 'D')) && + (mailbox[7] == '/')) mode = (int) shared_protection; + } + /* if a directory */ + if (!stat (path,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) { + /* set owner search if allow read or write */ + if (mode & 0600) mode |= 0100; + if (mode & 060) mode |= 010;/* set group search if allow read or write */ + if (mode & 06) mode |= 01; /* set world search if allow read or write */ + /* preserve directory SGID bit */ + if (sbuf.st_mode & S_ISGID) mode |= S_ISGID; + } + chmod (path,mode); /* set the new protection, ignore failure */ + return LONGT; +} + +/* Get proper directory protection + * Accepts: mailbox name + * Returns: directory mode, always + */ + +long get_dir_protection (char *mailbox) +{ + if (*mailbox == '#') { /* possible namespace? */ + if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) && + ((mailbox[2] == 't') || (mailbox[2] == 'T')) && + ((mailbox[3] == 'p') || (mailbox[3] == 'P')) && + (mailbox[4] == '/')) return ftp_dir_protection; + else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) && + ((mailbox[2] == 'u') || (mailbox[2] == 'U')) && + ((mailbox[3] == 'b') || (mailbox[3] == 'B')) && + ((mailbox[4] == 'l') || (mailbox[4] == 'L')) && + ((mailbox[5] == 'i') || (mailbox[5] == 'I')) && + ((mailbox[6] == 'c') || (mailbox[6] == 'C')) && + (mailbox[7] == '/')) return public_dir_protection; + else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) && + ((mailbox[2] == 'h') || (mailbox[2] == 'H')) && + ((mailbox[3] == 'a') || (mailbox[3] == 'A')) && + ((mailbox[4] == 'r') || (mailbox[4] == 'R')) && + ((mailbox[5] == 'e') || (mailbox[5] == 'E')) && + ((mailbox[6] == 'd') || (mailbox[6] == 'D')) && + (mailbox[7] == '/')) return shared_dir_protection; + } + return dir_protection; +} + +/* Determine default prototype stream to user + * Accepts: type (NIL for create, T for append) + * Returns: default prototype stream + */ + +MAILSTREAM *default_proto (long type) +{ + myusername (); /* make sure initialized */ + /* return default driver's prototype */ + return type ? appendProto : createProto; +} + + +/* Set up user flags for stream + * Accepts: MAIL stream + * Returns: MAIL stream with user flags set up + */ + +MAILSTREAM *user_flags (MAILSTREAM *stream) +{ + int i; + myusername (); /* make sure initialized */ + for (i = 0; i < NUSERFLAGS && userFlags[i]; ++i) + if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (userFlags[i]); + return stream; +} + + +/* Return nth user flag + * Accepts: user flag number + * Returns: flag + */ + +char *default_user_flag (unsigned long i) +{ + myusername (); /* make sure initialized */ + return userFlags[i]; +} + +/* Default block notify routine + * Accepts: reason for calling + * data + * Returns: data + */ + +void *mm_blocknotify (int reason,void *data) +{ + void *ret = data; + switch (reason) { + case BLOCK_SENSITIVE: /* entering sensitive code */ + ret = (void *) alarm (0); + break; + case BLOCK_NONSENSITIVE: /* exiting sensitive code */ + if ((unsigned int) data) alarm ((unsigned int) data); + break; + default: /* ignore all other reasons */ + break; + } + return ret; +} diff --git a/imap/src/osdep/amiga/env_ami.h b/imap/src/osdep/amiga/env_ami.h new file mode 100644 index 00000000..365e5128 --- /dev/null +++ b/imap/src/osdep/amiga/env_ami.h @@ -0,0 +1,95 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UNIX environment routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + + +typedef struct dotlock_base { + char lock[MAILTMPLEN]; + int pipei; + int pipeo; +} DOTLOCK; + + +/* Bits that can be set in restrictBox */ + +#define RESTRICTROOT 0x1 /* restricted box doesn't allow root */ +#define RESTRICTOTHERUSER 0x2 /* restricted box doesn't allow other user */ + +/* Subscription definitions for UNIX */ + +#define SUBSCRIPTIONFILE(t) sprintf (t,"%s/.mailboxlist",myhomedir ()) +#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s/.mlbxlsttmp",myhomedir ()) + + +/* dorc() options */ + +#define SYSCONFIG "/etc/c-client.cf" + + +/* Special users */ + +#define ANONYMOUSUSER "nobody" /* anonymous user */ +#define UNLOGGEDUSER "root" /* unlogged-in user */ +#define ADMINGROUP "mailadm" /* mail administrator group */ + +/* Function prototypes */ + +#include "env.h" + +void rfc822_fixed_date (char *date); +long env_init (char *user,char *home); +char *myusername_full (unsigned long *flags); +#define MU_LOGGEDIN 0 +#define MU_NOTLOGGEDIN 1 +#define MU_ANONYMOUS 2 +#define myusername() \ + myusername_full (NIL) +char *sysinbox (); +char *mailboxdir (char *dst,char *dir,char *name); +long dotlock_lock (char *file,DOTLOCK *base,int fd); +long dotlock_unlock (DOTLOCK *base); +int lockname (char *lock,char *fname,int op,long *pid); +int lockfd (int fd,char *lock,int op); +int lock_work (char *lock,void *sbuf,int op,long *pid); +long chk_notsymlink (char *name,void *sbuf); +void unlockfd (int fd,char *lock); +long set_mbx_protections (char *mailbox,char *path); +long get_dir_protection (char *mailbox); +MAILSTREAM *user_flags (MAILSTREAM *stream); +char *default_user_flag (unsigned long i); +void dorc (char *file,long flag); +long path_create (MAILSTREAM *stream,char *mailbox); +void grim_pid_reap_status (int pid,int killreq,void *status); +#define grim_pid_reap(pid,killreq) \ + grim_pid_reap_status (pid,killreq,NIL) +long safe_write (int fd,char *buf,long nbytes); +void *arm_signal (int sig,void *action); +struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[]); +long loginpw (struct passwd *pw,int argc,char *argv[]); +long pw_login (struct passwd *pw,char *auser,char *user,char *home,int argc, + char *argv[]); +void *mm_blocknotify (int reason,void *data); diff --git a/imap/src/osdep/amiga/fdstring.c b/imap/src/osdep/amiga/fdstring.c new file mode 100644 index 00000000..7a491f7d --- /dev/null +++ b/imap/src/osdep/amiga/fdstring.c @@ -0,0 +1,99 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: File descriptor string routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 15 April 1997 + * Last Edited: 4 April 2007 + */ + +#include "mail.h" +#include "osdep.h" +#include "misc.h" +#include "fdstring.h" + +/* String driver for fd stringstructs */ + +static void fd_string_init (STRING *s,void *data,unsigned long size); +static char fd_string_next (STRING *s); +static void fd_string_setpos (STRING *s,unsigned long i); + +STRINGDRIVER fd_string = { + fd_string_init, /* initialize string structure */ + fd_string_next, /* get next byte in string structure */ + fd_string_setpos /* set position in string structure */ +}; + + +/* Initialize string structure for fd stringstruct + * Accepts: string structure + * pointer to string + * size of string + */ + +static void fd_string_init (STRING *s,void *data,unsigned long size) +{ + FDDATA *d = (FDDATA *) data; + /* note fd */ + s->data = (void *) (unsigned long) d->fd; + s->data1 = d->pos; /* note file offset */ + s->size = size; /* note size */ + s->curpos = s->chunk = d->chunk; + s->chunksize = (unsigned long) d->chunksize; + s->offset = 0; /* initial position */ + /* and size of data */ + s->cursize = min (s->chunksize,size); + /* move to that position in the file */ + lseek (d->fd,d->pos,L_SET); + read (d->fd,s->chunk,(size_t) s->cursize); +} + +/* Get next character from fd stringstruct + * Accepts: string structure + * Returns: character, string structure chunk refreshed + */ + +static char fd_string_next (STRING *s) +{ + char c = *s->curpos++; /* get next byte */ + SETPOS (s,GETPOS (s)); /* move to next chunk */ + return c; /* return the byte */ +} + + +/* Set string pointer position for fd stringstruct + * Accepts: string structure + * new position + */ + +static void fd_string_setpos (STRING *s,unsigned long i) +{ + if (i > s->size) i = s->size; /* don't permit setting beyond EOF */ + s->offset = i; /* set new offset */ + s->curpos = s->chunk; /* reset position */ + /* set size of data */ + if (s->cursize = min (s->chunksize,SIZE (s))) { + /* move to that position in the file */ + lseek ((long) s->data,s->data1 + s->offset,L_SET); + read ((long) s->data,s->curpos,(size_t) s->cursize); + } +} diff --git a/imap/src/osdep/amiga/fdstring.h b/imap/src/osdep/amiga/fdstring.h new file mode 100644 index 00000000..d0a021bf --- /dev/null +++ b/imap/src/osdep/amiga/fdstring.h @@ -0,0 +1,39 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: File descriptor string routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 15 April 1997 + * Last Edited: 30 August 2006 + */ + +/* Driver-dependent data passed to init method */ + +typedef struct fd_data { + int fd; /* file descriptor */ + unsigned long pos; /* initial position */ + char *chunk; /* I/O buffer chunk */ + unsigned long chunksize; /* I/O buffer chunk length */ +} FDDATA; + + +extern STRINGDRIVER fd_string; diff --git a/imap/src/osdep/amiga/fs_ami.c b/imap/src/osdep/amiga/fs_ami.c new file mode 100644 index 00000000..b433e1b4 --- /dev/null +++ b/imap/src/osdep/amiga/fs_ami.c @@ -0,0 +1,71 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Free storage management routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + +/* Get a block of free storage + * Accepts: size of desired block + * Returns: free storage block + */ + +void *fs_get (size_t size) +{ + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + void *data = (*bn) (BLOCK_SENSITIVE,NIL); + void *block = malloc (size ? size : (size_t) 1); + if (!block) fatal ("Out of memory"); + (*bn) (BLOCK_NONSENSITIVE,data); + return (block); +} + + +/* Resize a block of free storage + * Accepts: ** pointer to current block + * new size + */ + +void fs_resize (void **block,size_t size) +{ + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + void *data = (*bn) (BLOCK_SENSITIVE,NIL); + if (!(*block = realloc (*block,size ? size : (size_t) 1))) + fatal ("Can't resize memory"); + (*bn) (BLOCK_NONSENSITIVE,data); +} + + +/* Return a block of free storage + * Accepts: ** pointer to free storage block + */ + +void fs_give (void **block) +{ + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + void *data = (*bn) (BLOCK_SENSITIVE,NIL); + free (*block); + *block = NIL; + (*bn) (BLOCK_NONSENSITIVE,data); +} diff --git a/imap/src/osdep/amiga/ftl_ami.c b/imap/src/osdep/amiga/ftl_ami.c new file mode 100644 index 00000000..9e65ef55 --- /dev/null +++ b/imap/src/osdep/amiga/ftl_ami.c @@ -0,0 +1,38 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: DOS/VMS/TOPS-20 crash management routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + + +/* Report a fatal error + * Accepts: string to output + */ + +void fatal (char *string) +{ + mm_fatal (string); /* pass up the string */ + abort (); /* die horribly */ +} diff --git a/imap/src/osdep/amiga/gethstid.c b/imap/src/osdep/amiga/gethstid.c new file mode 100644 index 00000000..01516976 --- /dev/null +++ b/imap/src/osdep/amiga/gethstid.c @@ -0,0 +1,38 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Get host ID emulator + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 3 May 1995 + * Last Edited: 30 August 2006 + */ + + +/* Emulator for BSD gethostid() call + * Returns: unique identifier for this machine + */ + +long gethostid (void) +{ + /* No gethostid() here, so just fake it and hope things turn out okay. */ + return 0xdeadface; +} diff --git a/imap/src/osdep/amiga/gr_waitp.c b/imap/src/osdep/amiga/gr_waitp.c new file mode 100644 index 00000000..8b230f9d --- /dev/null +++ b/imap/src/osdep/amiga/gr_waitp.c @@ -0,0 +1,39 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UNIX Grim PID Reaper -- waitpid() version + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 30 November 1993 + * Last Edited: 30 August 2006 + */ + +/* Grim PID reaper + * Accepts: process ID + * kill request flag + * status return value + */ + +void grim_pid_reap_status (int pid,int killreq,void *status) +{ + if (killreq) kill(pid,SIGHUP);/* kill if not already dead */ + while ((waitpid (pid,status,NIL) < 0) && (errno != ECHILD)); +} diff --git a/imap/src/osdep/amiga/log_std.c b/imap/src/osdep/amiga/log_std.c new file mode 100644 index 00000000..a36fa88f --- /dev/null +++ b/imap/src/osdep/amiga/log_std.c @@ -0,0 +1,44 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Standard login + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + +/* Log in + * Accepts: login passwd struct + * argument count + * argument vector + * Returns: T if success, NIL otherwise + */ + +long loginpw (struct passwd *pw,int argc,char *argv[]) +{ + uid_t uid = pw->pw_uid; + char *name = cpystr (pw->pw_name); + long ret = !(setgid (pw->pw_gid) || initgroups (name,pw->pw_gid) || + setuid (uid)); + fs_give ((void **) &name); + return ret; +} diff --git a/imap/src/osdep/amiga/mbx.c b/imap/src/osdep/amiga/mbx.c new file mode 100644 index 00000000..1ece5d8d --- /dev/null +++ b/imap/src/osdep/amiga/mbx.c @@ -0,0 +1,1855 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: MBX mail routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 3 October 1995 + * Last Edited: 11 October 2007 + */ + + +/* FILE TIME SEMANTICS + * + * The atime is the last read time of the file. + * The mtime is the last flags update time of the file. + * The ctime is the last write time of the file. + */ + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include "osdep.h" +#include <pwd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include "misc.h" +#include "dummy.h" +#include "fdstring.h" + + +/* Build parameters */ + +#define HDRSIZE 2048 + + +/* Kludge to make Cygwin happy */ + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +/* MBX I/O stream local data */ + +typedef struct mbx_local { + unsigned int flagcheck: 1; /* if ping should sweep for flags */ + unsigned int expok: 1; /* if expunging OK in ping */ + unsigned int expunged : 1; /* if one or more expunged messages */ + int fd; /* file descriptor for I/O */ + int ld; /* lock file descriptor */ + int ffuserflag; /* first free user flag */ + off_t filesize; /* file size parsed */ + time_t filetime; /* last file time */ + time_t lastsnarf; /* last snarf time */ + unsigned long lastpid; /* PID of last writer */ + unsigned char *buf; /* temporary buffer */ + unsigned long buflen; /* current size of temporary buffer */ + char lock[MAILTMPLEN]; /* buffer to write lock name */ +} MBXLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((MBXLOCAL *) stream->local) + +/* Function prototypes */ + +DRIVER *mbx_valid (char *name); +int mbx_isvalid (MAILSTREAM **stream,char *name,char *tmp,int *ld,char *lock, + long flags); +void *mbx_parameters (long function,void *value); +void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void mbx_list (MAILSTREAM *stream,char *ref,char *pat); +void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat); +long mbx_create (MAILSTREAM *stream,char *mailbox); +long mbx_delete (MAILSTREAM *stream,char *mailbox); +long mbx_rename (MAILSTREAM *stream,char *old,char *newname); +long mbx_status (MAILSTREAM *stream,char *mbx,long flags); +MAILSTREAM *mbx_open (MAILSTREAM *stream); +void mbx_close (MAILSTREAM *stream,long options); +void mbx_abort (MAILSTREAM *stream); +void mbx_flags (MAILSTREAM *stream,char *sequence,long flags); +char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags); +long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); +void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); +long mbx_ping (MAILSTREAM *stream); +void mbx_check (MAILSTREAM *stream); +long mbx_expunge (MAILSTREAM *stream,char *sequence,long options); +void mbx_snarf (MAILSTREAM *stream); +long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +char *mbx_file (char *dst,char *name); +long mbx_parse (MAILSTREAM *stream); +MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok); +unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt); +void mbx_update_header (MAILSTREAM *stream); +void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags); +unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno, + unsigned long *size,char **hdr); +unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed, + long flags); +long mbx_flaglock (MAILSTREAM *stream); + +/* MBX mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER mbxdriver = { + "mbx", /* driver name */ + DR_LOCAL|DR_MAIL|DR_CRLF|DR_LOCKING, + /* driver flags */ + (DRIVER *) NIL, /* next driver */ + mbx_valid, /* mailbox is valid for us */ + mbx_parameters, /* manipulate parameters */ + mbx_scan, /* scan mailboxes */ + mbx_list, /* list mailboxes */ + mbx_lsub, /* list subscribed mailboxes */ + NIL, /* subscribe to mailbox */ + NIL, /* unsubscribe from mailbox */ + mbx_create, /* create mailbox */ + mbx_delete, /* delete mailbox */ + mbx_rename, /* rename mailbox */ + mbx_status, /* status of mailbox */ + mbx_open, /* open mailbox */ + mbx_close, /* close mailbox */ + mbx_flags, /* fetch message "fast" attributes */ + mbx_flags, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + mbx_header, /* fetch message header */ + mbx_text, /* fetch message body */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + mbx_flag, /* modify flags */ + mbx_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + mbx_ping, /* ping mailbox to see if still alive */ + mbx_check, /* check for new messages */ + mbx_expunge, /* expunge deleted messages */ + mbx_copy, /* copy messages to another mailbox */ + mbx_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM mbxproto = {&mbxdriver}; + +/* MBX mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *mbx_valid (char *name) +{ + char tmp[MAILTMPLEN]; + int fd = mbx_isvalid (NIL,name,tmp,NIL,NIL,NIL); + if (fd < 0) return NIL; + close (fd); /* don't need the fd now */ + return &mbxdriver; +} + + +/* MBX mail test for valid mailbox + * Accepts: returned stream with valid mailbox keywords + * mailbox name + * scratch buffer + * returned lock fd + * returned lock name + * RW flags or NIL for readonly + * Returns: file descriptor if valid, NIL otherwise + */ + +#define MBXISVALIDNOUID 0x1 /* RW, don't do UID action */ +#define MBXISVALIDUID 0x2 /* RW, do UID action */ + +int mbx_isvalid (MAILSTREAM **stream,char *name,char *tmp,int *ld,char *lock, + long flags) +{ + int fd,upd; + int ret = -1; + unsigned long i; + long j,k; + off_t pos; + char c,*s,*t,hdr[HDRSIZE]; + struct stat sbuf; + time_t tp[2]; + int error = EINVAL; /* assume invalid argument */ + if (ld) *ld = -1; /* initially no lock */ + if ((s = mbx_file (tmp,name)) && !stat (s,&sbuf) && + ((fd = open (tmp,(flags ? O_RDWR : O_RDONLY)|O_BINARY,NIL)) >= 0)) { + error = -1; /* bogus format */ + /* I love cretinous C compilers -- don't you? */ + if (read (fd,hdr,HDRSIZE) == HDRSIZE) + if ((hdr[0] == '*') && (hdr[1] == 'm') && (hdr[2] == 'b') && + (hdr[3] == 'x') && (hdr[4] == '*') && (hdr[5] == '\015') && + (hdr[6] == '\012') && isxdigit (hdr[7]) && isxdigit (hdr[8])) + if (isxdigit (hdr[9]) && isxdigit (hdr[10]) && isxdigit (hdr[11]) && + isxdigit (hdr[12]) && isxdigit (hdr[13]) && isxdigit (hdr[14]) && + isxdigit (c = hdr[15]) && isxdigit (hdr[16])) + if (isxdigit (hdr[17]) && isxdigit (hdr[18]) && + isxdigit (hdr[19]) && isxdigit (hdr[20]) && + isxdigit (hdr[21]) && isxdigit (hdr[22]) && + (hdr[23] == '\015') && (hdr[24] == '\012')) { + ret = fd; /* mbx format */ + + if (stream) { /* lock if making mini-stream */ + if (flock (fd,LOCK_SH) || + (flags && ((*ld = lockfd (fd,lock,LOCK_EX)) < 0))) ret = -1; + /* reread data now that locked */ + else if (lseek (fd,0,L_SET) || + (read (fd,hdr,HDRSIZE) != HDRSIZE)) ret = -1; + else { + *stream = (MAILSTREAM *) memset (fs_get (sizeof (MAILSTREAM)), + 0,sizeof (MAILSTREAM)); + hdr[15] = '\0'; /* tie off UIDVALIDITY */ + (*stream)->uid_validity = strtoul (hdr+7,NIL,16); + hdr[15] = c; /* now get UIDLAST */ + (*stream)->uid_last = strtoul (hdr+15,NIL,16); + /* parse user flags */ + for (i = 0, s = hdr + 25; + (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s); + i++, s = t + 2) { + *t = '\0'; /* tie off flag */ + if (strlen (s) <= MAXUSERFLAG) + (*stream)->user_flags[i] = cpystr (s); + } + /* make sure have true UIDLAST */ + if (flags & MBXISVALIDUID) { + for (upd = NIL,pos = 2048, k = 0; pos < sbuf.st_size; + pos += (j + k)) { + /* read header for this message */ + lseek (fd,pos,L_SET); + if ((j = read (fd,hdr,64)) >= 0) { + hdr[j] = '\0'; + if ((s = strchr (hdr,'\015')) && (s[1] == '\012')) { + *s = '\0'; + k = s + 2 - hdr; + if ((s = strchr (hdr,',')) && + (j = strtol (s+1,&s,10)) && (*s == ';') && + (s = strchr (s+1,'-'))) { + /* get UID if there is any */ + i = strtoul (++s,&t,16); + if (!*t && (t == (s + 8)) && + (i <= (*stream)->uid_last)) { + if (!i) { + lseek (fd,pos + s - hdr,L_SET); + sprintf (hdr,"%08lx",++(*stream)->uid_last); + write (fd,hdr,8); + upd = T; + } + continue; + } + } + } + ret = -1; /* error, give up */ + *stream = mail_close (*stream); + pos = sbuf.st_size + 1; + j = k = 0; + } + } + + if (upd) { /* need to update hdr with new UIDLAST? */ + lseek (fd,15,L_SET); + sprintf (hdr,"%08lx",(*stream)->uid_last); + write (fd,hdr,8); + } + } + } + } + } + if (ret != fd) close (fd); /* close the file */ + else lseek (fd,0,L_SET); /* else rewind to start */ + /* \Marked status? */ + if (sbuf.st_ctime > sbuf.st_atime) { + tp[0] = sbuf.st_atime; /* preserve atime and mtime */ + tp[1] = sbuf.st_mtime; + utime (tmp,tp); /* set the times */ + } + } + /* in case INBOX but not mbx format */ + else if (((error = errno) == ENOENT) && !compare_cstring (name,"INBOX")) + error = -1; + if ((ret < 0) && ld && (*ld >= 0)) { + unlockfd (*ld,lock); + *ld = -1; + } + errno = error; /* return as last error */ + return ret; /* return what we should */ +} + +/* MBX manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *mbx_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = mbx_file ((char *) value,"INBOX"); + break; + case SET_ONETIMEEXPUNGEATPING: + if (value) ((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T; + case GET_ONETIMEEXPUNGEATPING: + if (value) ret = (void *) + (((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL); + break; + } + return ret; +} + + +/* MBX mail scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + if (stream) dummy_scan (NIL,ref,pat,contents); +} + + +/* MBX mail list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mbx_list (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_list (NIL,ref,pat); +} + + +/* MBX mail list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_lsub (NIL,ref,pat); +} + +/* MBX mail create mailbox + * Accepts: MAIL stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long mbx_create (MAILSTREAM *stream,char *mailbox) +{ + char *s,*t,mbx[MAILTMPLEN],tmp[HDRSIZE]; + long ret = NIL; + int i,fd; + if (!(s = mbx_file (mbx,mailbox))) { + sprintf (mbx,"Can't create %.80s: invalid name",mailbox); + MM_LOG (mbx,ERROR); + } + /* create underlying file */ + else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) { + /* done if made directory */ + if ((s = strrchr (s,'/')) && !s[1]) return T; + if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) { + sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno)); + MM_LOG (tmp,ERROR); + unlink (mbx); /* delete the file */ + } + else { + memset (tmp,'\0',HDRSIZE);/* initialize header */ + sprintf (s = tmp,"*mbx*\015\012%08lx00000000\015\012", + (unsigned long) time (0)); + for (i = 0; i < NUSERFLAGS; ++i) { + t = (stream && stream->user_flags[i]) ? stream->user_flags[i] : + ((t = default_user_flag (i)) ? t : ""); + sprintf (s += strlen (s),"%s\015\012",t); + } + if (write (fd,tmp,HDRSIZE) != HDRSIZE) { + sprintf (tmp,"Can't initialize mailbox node %.80s: %s", + mbx,strerror (errno)); + MM_LOG (tmp,ERROR); + unlink (mbx); /* delete the file */ + } + else ret = T; /* success */ + close (fd); /* close file */ + } + } + /* set proper protections */ + return ret ? set_mbx_protections (mailbox,mbx) : NIL; +} + + +/* MBX mail delete mailbox + * Accepts: MAIL stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long mbx_delete (MAILSTREAM *stream,char *mailbox) +{ + return mbx_rename (stream,mailbox,NIL); +} + +/* MBX mail rename mailbox + * Accepts: MAIL stream + * old mailbox name + * new mailbox name (or NIL for delete) + * Returns: T on success, NIL on failure + */ + +long mbx_rename (MAILSTREAM *stream,char *old,char *newname) +{ + long ret = LONGT; + char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN]; + int fd,ld; + struct stat sbuf; + if (!mbx_file (file,old) || + (newname && (!((s = mailboxfile (tmp,newname)) && *s) || + ((s = strrchr (tmp,'/')) && !s[1])))) { + sprintf (tmp,newname ? + "Can't rename mailbox %.80s to %.80s: invalid name" : + "Can't delete mailbox %.80s: invalid name", + old,newname); + MM_LOG (tmp,ERROR); + return NIL; + } + else if ((fd = open (file,O_RDWR|O_BINARY,NIL)) < 0) { + sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get parse/append permission */ + if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) { + MM_LOG ("Unable to lock rename mailbox",ERROR); + return NIL; + } + /* lock out other users */ + if (flock (fd,LOCK_EX|LOCK_NB)) { + close (fd); /* couldn't lock, give up on it then */ + sprintf (tmp,"Mailbox %.80s is in use by another process",old); + MM_LOG (tmp,ERROR); + unlockfd (ld,lock); /* release exclusive parse/append permission */ + return NIL; + } + + if (newname) { /* want rename? */ + /* found superior to destination name? */ + if (s = strrchr (tmp,'/')) { + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* superior name doesn't exist, create it */ + if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,tmp,get_dir_protection (newname))) + ret = NIL; + else *s = c; /* restore full name */ + } + /* rename the file */ + if (ret && rename (file,tmp)) { + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname, + strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; /* set failure */ + } + } + else if (unlink (file)) { + sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; /* set failure */ + } + flock (fd,LOCK_UN); /* release lock on the file */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + close (fd); /* close the file */ + /* recreate file if renamed INBOX */ + if (ret && !compare_cstring (old,"INBOX")) mbx_create (NIL,"INBOX"); + return ret; /* return success */ +} + +/* MBX Mail status + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long mbx_status (MAILSTREAM *stream,char *mbx,long flags) +{ + MAILSTATUS status; + unsigned long i; + MAILSTREAM *tstream = NIL; + MAILSTREAM *systream = NIL; + /* make temporary stream (unless this mbx) */ + if (!stream && !(stream = tstream = + mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) + return NIL; + status.flags = flags; /* return status values */ + status.messages = stream->nmsgs; + status.recent = stream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++) + if (!mail_elt (stream,i)->seen) status.unseen++; + status.uidnext = stream->uid_last + 1; + status.uidvalidity = stream->uid_validity; + /* calculate post-snarf results */ + if (!status.recent && stream->inbox && + (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) { + status.messages += systream->nmsgs; + status.recent += systream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1; i <= systream->nmsgs; i++) + if (!mail_elt (systream,i)->seen) status.unseen++; + /* kludge but probably good enough */ + status.uidnext += systream->nmsgs; + } + MM_STATUS(stream,mbx,&status);/* pass status to main program */ + if (tstream) mail_close (tstream); + if (systream) mail_close (systream); + return T; /* success */ +} + +/* MBX mail open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *mbx_open (MAILSTREAM *stream) +{ + int fd,ld; + short silent; + char tmp[MAILTMPLEN]; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return user_flags (&mbxproto); + if (stream->local) fatal ("mbx recycle stream"); + /* canonicalize the mailbox name */ + if (!mbx_file (tmp,stream->mailbox)) { + sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox); + MM_LOG (tmp,ERROR); + } + if (stream->rdonly || + (fd = open (tmp,O_RDWR|O_BINARY,NIL)) < 0) { + if ((fd = open (tmp,O_RDONLY|O_BINARY,NIL)) < 0) { + sprintf (tmp,"Can't open mailbox: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + else if (!stream->rdonly) { /* got it, but readonly */ + MM_LOG ("Can't get write access to mailbox, access is readonly",WARN); + stream->rdonly = T; + } + } + + stream->local = memset (fs_get (sizeof (MBXLOCAL)),NIL,sizeof (MBXLOCAL)); + LOCAL->fd = fd; /* bind the file */ + LOCAL->ld = -1; /* no flaglock */ + LOCAL->buf = (char *) fs_get (CHUNKSIZE); + LOCAL->buflen = CHUNKSIZE - 1; + /* note if an INBOX or not */ + stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr (tmp); + /* get parse/append permission */ + if ((ld = lockfd (LOCAL->fd,tmp,LOCK_EX)) < 0) { + MM_LOG ("Unable to lock open mailbox",ERROR); + return NIL; + } + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH); /* lock the file */ + (*bn) (BLOCK_NONE,NIL); + unlockfd (ld,tmp); /* release shared parse permission */ + LOCAL->filesize = HDRSIZE; /* initialize parsed file size */ + /* time not set up yet */ + LOCAL->lastsnarf = LOCAL->filetime = 0; + LOCAL->expok = LOCAL->flagcheck = NIL; + stream->sequence++; /* bump sequence number */ + /* parse mailbox */ + stream->nmsgs = stream->recent = 0; + silent = stream->silent; /* defer events */ + stream->silent = T; + if (mbx_ping (stream) && !stream->nmsgs) + MM_LOG ("Mailbox is empty",(long) NIL); + stream->silent = silent; /* now notify upper level */ + mail_exists (stream,stream->nmsgs); + mail_recent (stream,stream->recent); + if (!LOCAL) return NIL; /* failure if stream died */ + stream->perm_seen = stream->perm_deleted = stream->perm_flagged = + stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T; + stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff; + stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? + NIL : T; /* can we create new user flags? */ + return stream; /* return stream to caller */ +} + +/* MBX mail close + * Accepts: MAIL stream + * close options + */ + +void mbx_close (MAILSTREAM *stream,long options) +{ + if (stream && LOCAL) { /* only if a file is open */ + int silent = stream->silent; + stream->silent = T; /* note this stream is dying */ + /* do an expunge if requested */ + if (options & CL_EXPUNGE) mbx_expunge (stream,NIL,NIL); + else { /* otherwise do a checkpoint to purge */ + LOCAL->expok = T; /* possible expunged messages */ + mbx_ping (stream); + } + stream->silent = silent; /* restore previous status */ + mbx_abort (stream); + } +} + + +/* MBX mail abort stream + * Accepts: MAIL stream + */ + +void mbx_abort (MAILSTREAM *stream) +{ + if (stream && LOCAL) { /* only if a file is open */ + flock (LOCAL->fd,LOCK_UN); /* unlock local file */ + close (LOCAL->fd); /* close the local file */ + /* free local text buffer */ + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + + +/* MBX mail fetch flags + * Accepts: MAIL stream + * sequence + * option flags + * Sniffs at file to see if some other process changed the flags + */ + +void mbx_flags (MAILSTREAM *stream,char *sequence,long flags) +{ + MESSAGECACHE *elt; + unsigned long i; + if (mbx_ping (stream) && /* ping mailbox, get new status for messages */ + ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence && !elt->valid) + mbx_elt (stream,i,NIL); +} + +/* MBX mail fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + +char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags) +{ + unsigned long i; + char *s; + *length = 0; /* default to empty */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + /* get header position, possibly header */ + i = mbx_hdrpos (stream,msgno,length,&s); + if (!s) { /* mbx_hdrpos() returned header? */ + lseek (LOCAL->fd,i,L_SET); /* no, get to header position */ + /* is buffer big enough? */ + if (*length > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1); + } + /* slurp the data */ + read (LOCAL->fd,s = LOCAL->buf,*length); + } + s[*length] = '\0'; /* tie off string */ + return s; +} + +/* MBX mail fetch message text (body only) + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: T on success, NIL on failure + */ + +long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + FDDATA d; + unsigned long i,j; + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + /* get message status */ + elt = mbx_elt (stream,msgno,NIL); + /* if message not seen */ + if (!(flags & FT_PEEK) && !elt->seen && mbx_flaglock (stream)) { + elt->seen = T; /* mark message as seen */ + /* recalculate status */ + mbx_update_status (stream,msgno,NIL); + MM_FLAGS (stream,msgno); + /* update flags */ + mbx_flag (stream,NIL,NIL,NIL); + } + if (!LOCAL) return NIL; /* mbx_flaglock() could have aborted */ + /* find header position */ + i = mbx_hdrpos (stream,msgno,&j,NIL); + d.fd = LOCAL->fd; /* set up file descriptor */ + d.pos = i + j; + d.chunk = LOCAL->buf; /* initial buffer chunk */ + d.chunksize = CHUNKSIZE; + INIT (bs,fd_string,&d,elt->rfc822_size - j); + return LONGT; /* success */ +} + +/* MBX mail modify flags + * Accepts: MAIL stream + * sequence + * flag(s) + * option flags + * Unlocks flag lock + */ + +void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) +{ + time_t tp[2]; + struct stat sbuf; + unsigned long oldpid = LOCAL->lastpid; + /* make sure the update takes */ + if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld >= 0)) { + fsync (LOCAL->fd); + fstat (LOCAL->fd,&sbuf); /* get current write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + /* we are the last flag updater */ + LOCAL->lastpid = (unsigned long) getpid (); + /* update header if needed */ + if (((LOCAL->ffuserflag < NUSERFLAGS) && + stream->user_flags[LOCAL->ffuserflag]) || (oldpid != LOCAL->lastpid)) + mbx_update_header (stream); + tp[0] = time (0); /* make sure read comes after all that */ + utime (stream->mailbox,tp); + } + if (LOCAL->ld >= 0) { /* unlock now */ + unlockfd (LOCAL->ld,LOCAL->lock); + LOCAL->ld = -1; + } +} + + +/* MBX mail per-message modify flags + * Accepts: MAIL stream + * message cache element + */ + +void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + if (mbx_flaglock (stream)) mbx_update_status (stream,elt->msgno,NIL); +} + +/* MBX mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream still alive, NIL if not + */ + +long mbx_ping (MAILSTREAM *stream) +{ + unsigned long i,pos; + long ret = NIL; + int ld; + char lock[MAILTMPLEN]; + MESSAGECACHE *elt; + struct stat sbuf; + if (stream && LOCAL) { /* only if stream already open */ + int snarf = stream->inbox && !stream->rdonly; + ret = LONGT; /* assume OK */ + fstat (LOCAL->fd,&sbuf); /* get current file poop */ + /* allow expunge if permitted at ping */ + if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T; + /* if external modification */ + if (LOCAL->filetime && (LOCAL->filetime < sbuf.st_mtime)) + LOCAL->flagcheck = T; /* upgrade to flag checking */ + /* new mail or flagcheck handling needed? */ + if (((sbuf.st_size - LOCAL->filesize) || LOCAL->flagcheck || + !stream->nmsgs || snarf) && + ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) { + /* reparse header if not flagchecking */ + if (!LOCAL->flagcheck) ret = mbx_parse (stream); + /* sweep mailbox for changed message status */ + else if (ret = mbx_parse (stream)) { + unsigned long recent = 0; + LOCAL->filetime = sbuf.st_mtime; + for (i = 1; i <= stream->nmsgs; ) + if (elt = mbx_elt (stream,i,LOCAL->expok)) { + if (elt->recent) ++recent; + ++i; + } + mail_recent (stream,recent); + LOCAL->flagcheck = NIL; /* got all the updates */ + } + /* always reparse header at least */ + if (ret && snarf) { /* snarf new messages if still OK */ + mbx_snarf (stream); + /* parse snarfed messages */ + ret = mbx_parse (stream); + } + unlockfd (ld,lock); /* release shared parse/append permission */ + } + if (ret) { /* must still be alive */ + if (!LOCAL->expunged) /* look for holes if none known yet */ + for (i = 1, pos = HDRSIZE; + !LOCAL->expunged && (i <= stream->nmsgs); + i++, pos += elt->private.special.text.size + elt->rfc822_size) + if ((elt = mail_elt (stream,i))->private.special.offset != pos) + LOCAL->expunged = T;/* found a hole */ + /* burp any holes */ + if (LOCAL->expunged && !stream->rdonly) { + if (mbx_rewrite (stream,&i,NIL)) fatal ("expunge on check"); + if (i) { /* any space reclaimed? */ + LOCAL->expunged = NIL;/* no more pending expunge */ + sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",i); + MM_LOG (LOCAL->buf,(long) NIL); + } + } + LOCAL->expok = NIL; /* no more expok */ + } + } + return ret; /* return result of the parse */ +} + +/* MBX mail check mailbox (reparses status too) + * Accepts: MAIL stream + */ + +void mbx_check (MAILSTREAM *stream) +{ + if (LOCAL) LOCAL->expok = T; /* mark that a check is desired */ + if (mbx_ping (stream)) MM_LOG ("Check completed",(long) NIL); +} + + +/* MBX mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T if success, NIL if failure + */ + +long mbx_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret; + unsigned long nexp,reclaimed; + if (ret = sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : LONGT) { + if (!mbx_ping (stream)); /* do nothing if stream dead */ + else if (stream->rdonly) /* won't do on readonly files! */ + MM_LOG ("Expunge ignored on readonly mailbox",WARN); + /* if expunged any messages */ + else if (nexp = mbx_rewrite (stream,&reclaimed,sequence ? -1 : 1)) { + sprintf (LOCAL->buf,"Expunged %lu messages",nexp); + MM_LOG (LOCAL->buf,(long) NIL); + } + else if (reclaimed) { /* or if any prior expunged space reclaimed */ + sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed); + MM_LOG (LOCAL->buf,(long) NIL); + } + else MM_LOG ("No messages deleted, so no update needed",(long) NIL); + } + return ret; +} + +/* MBX mail snarf messages from system inbox + * Accepts: MAIL stream, already locked + */ + +void mbx_snarf (MAILSTREAM *stream) +{ + unsigned long i = 0; + unsigned long j,r,hdrlen,txtlen; + struct stat sbuf; + char *hdr,*txt,tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + MAILSTREAM *sysibx = NIL; + /* give up if can't get exclusive permission */ + if ((time (0) >= (LOCAL->lastsnarf + + (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) && + strcmp (sysinbox (),stream->mailbox)) { + MM_CRITICAL (stream); /* go critical */ + /* sizes match and anything in sysinbox? */ + if (!stat (sysinbox (),&sbuf) && sbuf.st_size && + !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) && + (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) && + (!sysibx->rdonly) && (r = sysibx->nmsgs)) { + /* yes, go to end of file in our mailbox */ + lseek (LOCAL->fd,sbuf.st_size,L_SET); + /* for each message in sysibx mailbox */ + while (r && (++i <= sysibx->nmsgs)) { + /* snarf message from system INBOX */ + hdr = cpystr (mail_fetchheader_full (sysibx,i,NIL,&hdrlen,NIL)); + txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_PEEK); + /* if have a message */ + if (j = hdrlen + txtlen) { + /* build header line */ + mail_date (LOCAL->buf,elt = mail_elt (sysibx,i)); + sprintf (LOCAL->buf + strlen (LOCAL->buf), + ",%lu;00000000%04x-00000000\015\012",j,(unsigned) + ((fSEEN * elt->seen) + + (fDELETED * elt->deleted) + (fFLAGGED * elt->flagged) + + (fANSWERED * elt->answered) + (fDRAFT * elt->draft))); + /* copy message */ + if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) || + (write (LOCAL->fd,hdr,hdrlen) < 0) || + (write (LOCAL->fd,txt,txtlen) < 0)) r = 0; + } + fs_give ((void **) &hdr); + } + + /* make sure all the updates take */ + if (fsync (LOCAL->fd)) r = 0; + if (r) { /* delete all the messages we copied */ + if (r == 1) strcpy (tmp,"1"); + else sprintf (tmp,"1:%lu",r); + mail_setflag (sysibx,tmp,"\\Deleted"); + mail_expunge (sysibx); /* now expunge all those messages */ + } + else { + sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno)); + MM_LOG (LOCAL->buf,WARN); + ftruncate (LOCAL->fd,sbuf.st_size); + } + fstat (LOCAL->fd,&sbuf); /* yes, get current file size */ + LOCAL->filetime = sbuf.st_mtime; + } + if (sysibx) mail_close (sysibx); + MM_NOCRITICAL (stream); /* release critical */ + LOCAL->lastsnarf = time (0);/* note time of last snarf */ + } +} + +/* MBX mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if success, NIL if failed + */ + +long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + struct stat sbuf; + time_t tp[2]; + MESSAGECACHE *elt; + unsigned long i,j,k,m; + long ret = LONGT; + int fd,ld; + char *s,*t,file[MAILTMPLEN],lock[MAILTMPLEN]; + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL); + SEARCHSET *source = cu ? mail_newsearchset () : NIL; + SEARCHSET *dest = cu ? mail_newsearchset () : NIL; + MAILSTREAM *dstream = NIL; + if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) return NIL; + /* make sure valid mailbox */ + if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock, + cu ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0) + switch (errno) { + case ENOENT: /* no such file? */ + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); + return NIL; + case EACCES: /* file protected */ + sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + case EINVAL: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Invalid MBX-format mailbox name: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + default: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Not a MBX-format mailbox: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + MM_CRITICAL (stream); /* go critical */ + fstat (fd,&sbuf); /* get current file size */ + lseek (fd,sbuf.st_size,L_SET);/* move to end of file */ + + /* for each requested message */ + for (i = 1; ret && (i <= stream->nmsgs); i++) + if ((elt = mail_elt (stream,i))->sequence) { + lseek (LOCAL->fd,elt->private.special.offset + + elt->private.special.text.size,L_SET); + mail_date(LOCAL->buf,elt);/* build target header */ + /* get target keyword mask */ + for (j = elt->user_flags, k = 0; j; ) + if (s = stream->user_flags[find_rightmost_bit (&j)]) + for (m = 0; (m < NUSERFLAGS) && (t = dstream->user_flags[m]); m++) + if (!compare_cstring (s,t) && (k |= 1 << m)) break; + sprintf (LOCAL->buf+strlen(LOCAL->buf),",%lu;%08lx%04x-%08lx\015\012", + elt->rfc822_size,k,(unsigned) + ((fSEEN * elt->seen) + (fDELETED * elt->deleted) + + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + + (fDRAFT * elt->draft)),cu ? ++dstream->uid_last : 0); + /* write target header */ + if (ret = (write (fd,LOCAL->buf,strlen (LOCAL->buf)) > 0)) { + for (k = elt->rfc822_size; ret && (j = min (k,LOCAL->buflen)); k -= j){ + read (LOCAL->fd,LOCAL->buf,j); + ret = write (fd,LOCAL->buf,j) >= 0; + } + if (cu) { /* need to pass back new UID? */ + mail_append_set (source,mail_uid (stream,i)); + mail_append_set (dest,dstream->uid_last); + } + } + } + + /* make sure all the updates take */ + if (!(ret && (ret = !fsync (fd)))) { + sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + ftruncate (fd,sbuf.st_size); + } + if (cu && ret) { /* return sets if doing COPYUID */ + (*cu) (stream,mailbox,dstream->uid_validity,source,dest); + lseek (fd,15,L_SET); /* update UIDLAST */ + sprintf (LOCAL->buf,"%08lx",dstream->uid_last); + write (fd,LOCAL->buf,8); + } + else { /* flush any sets we may have built */ + mail_free_searchset (&source); + mail_free_searchset (&dest); + } + if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */ + /* else preserve \Marked status */ + else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0); + tp[1] = sbuf.st_mtime; /* preserve mtime */ + utime (file,tp); /* set the times */ + close (fd); /* close the file */ + MM_NOCRITICAL (stream); /* release critical */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + /* delete all requested messages */ + if (ret && (options & CP_MOVE) && mbx_flaglock (stream)) { + for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) { + /* mark message deleted */ + mbx_elt (stream,i,NIL)->deleted = T; + /* recalculate status */ + mbx_update_status (stream,i,NIL); + } + /* update flags */ + mbx_flag (stream,NIL,NIL,NIL); + } + if (dstream != stream) mail_close (dstream); + return ret; +} + +/* MBX mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + struct stat sbuf; + int fd,ld; + char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN]; + time_t tp[2]; + FILE *df; + MESSAGECACHE elt; + long f; + unsigned long i,uf; + STRING *message; + long ret = NIL; + MAILSTREAM *dstream = NIL; + appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); + SEARCHSET *dst = au ? mail_newsearchset () : NIL; + /* make sure valid mailbox */ + if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock, + au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0) + switch (errno) { + case ENOENT: /* no such file? */ + if (compare_cstring (mailbox,"INBOX")) { + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); + return NIL; + } + /* can create INBOX here */ + mbx_create (dstream = stream ? stream : user_flags (&mbxproto),"INBOX"); + if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock, + au ? MBXISVALIDUID : MBXISVALIDNOUID)) >= 0) + break; + case EACCES: /* file protected */ + sprintf (tmp,"Can't access destination: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + case EINVAL: + sprintf (tmp,"Invalid MBX-format mailbox name: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + default: + sprintf (tmp,"Not a MBX-format mailbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + + /* get first message */ + if (!MM_APPEND (af) (dstream,data,&flags,&date,&message)) close (fd); + else if (!(df = fdopen (fd,"r+b"))) { + MM_LOG ("Unable to reopen append mailbox",ERROR); + close (fd); + } + else { + MM_CRITICAL (dstream); /* go critical */ + fstat (fd,&sbuf); /* get current file size */ + fseek (df,sbuf.st_size,SEEK_SET); + errno = 0; + for (ret = LONGT; ret && message; ) { + if (!SIZE (message)) { /* guard against zero-length */ + MM_LOG ("Append of zero-length message",ERROR); + ret = NIL; + break; + } + f = mail_parse_flags (dstream,flags,&uf); + if (date) { /* parse date if given */ + if (!mail_parse_date (&elt,date)) { + sprintf (tmp,"Bad date in append: %.80s",date); + MM_LOG (tmp,ERROR); + ret = NIL; /* mark failure */ + break; + } + mail_date (tmp,&elt); /* write preseved date */ + } + else internal_date (tmp); /* get current date in IMAP format */ + /* write header */ + if (fprintf (df,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp,i = SIZE (message), + uf,(unsigned long) f,au ? ++dstream->uid_last : 0) < 0) + ret = NIL; + else { /* write message */ + size_t j; + if (!message->cursize) SETPOS (message,GETPOS (message)); + while (i && (j = fwrite (message->curpos,1,message->cursize,df))) { + i -= j; + SETPOS (message,GETPOS (message) + j); + } + /* get next message */ + if (i || !MM_APPEND (af) (dstream,data,&flags,&date,&message)) + ret = NIL; + else if (au) mail_append_set (dst,dstream->uid_last); + } + } + + /* if error... */ + if (!ret || (fflush (df) == EOF)) { + /* revert file */ + ftruncate (fd,sbuf.st_size); + close (fd); /* make sure fclose() doesn't corrupt us */ + if (errno) { + sprintf (tmp,"Message append failed: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + } + ret = NIL; + } + if (au && ret) { /* return sets if doing APPENDUID */ + (*au) (mailbox,dstream->uid_validity,dst); + fseek (df,15,SEEK_SET); /* update UIDLAST */ + fprintf (df,"%08lx",dstream->uid_last); + } + else mail_free_searchset (&dst); + /* set atime to now-1 if successful copy */ + if (ret) tp[0] = time (0) - 1; + /* else preserve \Marked status */ + else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0); + tp[1] = sbuf.st_mtime; /* preserve mtime */ + utime (file,tp); /* set the times */ + fclose (df); /* close the file */ + MM_NOCRITICAL (dstream); /* release critical */ + } + unlockfd (ld,lock); /* release exclusive parse/append permission */ + if (dstream != stream) mail_close (dstream); + return ret; +} + +/* Internal routines */ + + +/* MBX mail generate file string + * Accepts: temporary buffer to write into + * mailbox name string + * Returns: local file string or NIL if failure + */ + +char *mbx_file (char *dst,char *name) +{ + char *s = mailboxfile (dst,name); + return (s && !*s) ? mailboxfile (dst,"~/INBOX") : s; +} + +/* MBX mail parse mailbox + * Accepts: MAIL stream + * Returns: T if parse OK + * NIL if failure, stream aborted + */ + +long mbx_parse (MAILSTREAM *stream) +{ + struct stat sbuf; + MESSAGECACHE *elt = NIL; + unsigned char c,*s,*t,*x; + char tmp[MAILTMPLEN]; + unsigned long i,j,k,m; + off_t curpos = LOCAL->filesize; + unsigned long nmsgs = stream->nmsgs; + unsigned long recent = stream->recent; + unsigned long lastuid = 0; + short dirty = NIL; + short added = NIL; + short silent = stream->silent; + short uidwarn = T; + fstat (LOCAL->fd,&sbuf); /* get status */ + if (sbuf.st_size < curpos) { /* sanity check */ + sprintf (tmp,"Mailbox shrank from %lu to %lu!", + (unsigned long) curpos,(unsigned long) sbuf.st_size); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + lseek (LOCAL->fd,0,L_SET); /* rewind file */ + /* read internal header */ + read (LOCAL->fd,LOCAL->buf,HDRSIZE); + LOCAL->buf[HDRSIZE] = '\0'; /* tie off header */ + c = LOCAL->buf[15]; /* save first character of last UID */ + LOCAL->buf[15] = '\0'; + /* parse UID validity */ + stream->uid_validity = strtoul (LOCAL->buf + 7,NIL,16); + LOCAL->buf[15] = c; /* restore first character of last UID */ + /* parse last UID */ + i = strtoul (LOCAL->buf + 15,NIL,16); + stream->uid_last = stream->rdonly ? max (i,stream->uid_last) : i; + /* parse user flags */ + for (i = 0, s = LOCAL->buf + 25; + (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s); + i++, s = t + 2) { + *t = '\0'; /* tie off flag */ + if (!stream->user_flags[i] && (strlen (s) <= MAXUSERFLAG)) + stream->user_flags[i] = cpystr (s); + } + LOCAL->ffuserflag = (int) i; /* first free user flag */ + + /* get current last flag updater PID */ + i = (isxdigit (LOCAL->buf[HDRSIZE-10]) && isxdigit (LOCAL->buf[HDRSIZE-9]) && + isxdigit (LOCAL->buf[HDRSIZE-8]) && isxdigit (LOCAL->buf[HDRSIZE-7]) && + isxdigit (LOCAL->buf[HDRSIZE-6]) && isxdigit (LOCAL->buf[HDRSIZE-5]) && + isxdigit (LOCAL->buf[HDRSIZE-4]) && isxdigit (LOCAL->buf[HDRSIZE-3]) && + (LOCAL->buf[HDRSIZE-2] == '\015') && (LOCAL->buf[HDRSIZE-1] == '\012'))? + strtoul (LOCAL->buf + HDRSIZE - 8,NIL,16) : 0; + /* set flagcheck if lastpid changed */ + if (LOCAL->lastpid && (LOCAL->lastpid != i)) LOCAL->flagcheck = T; + LOCAL->lastpid = i; /* set as last PID */ + stream->silent = T; /* don't pass up exists events yet */ + while (sbuf.st_size - curpos){/* while there is stuff to parse */ + /* get to that position in the file */ + lseek (LOCAL->fd,curpos,L_SET); + if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) { + sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s", + (unsigned long) curpos,(unsigned long) sbuf.st_size, + i ? strerror (errno) : "no data read"); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + LOCAL->buf[i] = '\0'; /* tie off buffer just in case */ + if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) { + sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %.80s", + (unsigned long) curpos,i,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + *s = '\0'; /* tie off header line */ + i = (s + 2) - LOCAL->buf; /* note start of text offset */ + if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) { + sprintf (tmp,"Unable to parse internal header at %lu: %.80s", + (unsigned long) curpos,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + if (!(isxdigit (t[1]) && isxdigit (t[2]) && isxdigit (t[3]) && + isxdigit (t[4]) && isxdigit (t[5]) && isxdigit (t[6]) && + isxdigit (t[7]) && isxdigit (t[8]) && isxdigit (t[9]) && + isxdigit (t[10]) && isxdigit (t[11]) && isxdigit (t[12]))) { + sprintf (tmp,"Unable to parse message flags at %lu: %.80s", + (unsigned long) curpos,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + if ((t[13] != '-') || t[22] || + !(isxdigit (t[14]) && isxdigit (t[15]) && isxdigit (t[16]) && + isxdigit (t[17]) && isxdigit (t[18]) && isxdigit (t[19]) && + isxdigit (t[20]) && isxdigit (t[21]))) { + sprintf (tmp,"Unable to parse message UID at %lu: %.80s", + (unsigned long) curpos,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + + *s++ = '\0'; *t++ = '\0'; /* break up fields */ + /* get message size */ + if (!(j = strtoul (s,(char **) &x,10)) && (!(x && *x))) { + sprintf (tmp,"Unable to parse message size at %lu: %.80s,%.80s;%.80s", + (unsigned long) curpos,(char *) LOCAL->buf,(char *) s, + (char *) t); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + /* make sure didn't run off end of file */ + if (((off_t) (curpos + i + j)) > sbuf.st_size) { + sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)", + (unsigned long) curpos,(unsigned long) (curpos + i + j), + (unsigned long) sbuf.st_size); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + /* parse UID */ + if ((m = strtoul (t+13,NIL,16)) && + ((m <= lastuid) || (m > stream->uid_last))) { + if (uidwarn) { + sprintf (tmp,"Invalid UID %08lx in message %lu, rebuilding UIDs", + m,nmsgs+1); + MM_LOG (tmp,WARN); + uidwarn = NIL; + /* restart UID validity */ + stream->uid_validity = time (0); + } + m = 0; /* lose this UID */ + dirty = T; /* mark dirty, set new lastuid */ + stream->uid_last = lastuid; + } + + t[12] = '\0'; /* parse system flags */ + if ((k = strtoul (t+8,NIL,16)) & fEXPUNGED) { + if (m) lastuid = m; /* expunge message, update last UID seen */ + else { /* no UID assigned? */ + lastuid = ++stream->uid_last; + dirty = T; + } + } + else { /* not expunged, swell the cache */ + added = T; /* note that a new message was added */ + mail_exists (stream,++nmsgs); + /* instantiate an elt for this message */ + (elt = mail_elt (stream,nmsgs))->valid = T; + /* parse the date */ + if (!mail_parse_date (elt,LOCAL->buf)) { + sprintf (tmp,"Unable to parse message date at %lu: %.80s", + (unsigned long) curpos,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + mbx_abort (stream); + return NIL; + } + /* note file offset of header */ + elt->private.special.offset = curpos; + /* and internal header size */ + elt->private.special.text.size = i; + /* header size not known yet */ + elt->private.msg.header.text.size = 0; + elt->rfc822_size = j; /* note message size */ + /* calculate system flags */ + if (k & fSEEN) elt->seen = T; + if (k & fDELETED) elt->deleted = T; + if (k & fFLAGGED) elt->flagged = T; + if (k & fANSWERED) elt->answered = T; + if (k & fDRAFT) elt->draft = T; + t[8] = '\0'; /* get user flags value */ + elt->user_flags = strtoul (t,NIL,16); + /* UID already assigned? */ + if (!(elt->private.uid = m) || !(k & fOLD)) { + elt->recent = T; /* no, mark as recent */ + ++recent; /* count up a new recent message */ + dirty = T; /* and must rewrite header */ + /* assign new UID */ + if (!elt->private.uid) elt->private.uid = ++stream->uid_last; + mbx_update_status (stream,elt->msgno,NIL); + } + /* update last parsed UID */ + lastuid = elt->private.uid; + } + curpos += i + j; /* update position */ + } + + if (dirty && !stream->rdonly){/* update header */ + mbx_update_header (stream); + fsync (LOCAL->fd); /* make sure all the UID updates take */ + } + /* update parsed file size and time */ + LOCAL->filesize = sbuf.st_size; + fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */ + LOCAL->filetime = sbuf.st_mtime; + if (added && !stream->rdonly){/* make sure atime updated */ + time_t tp[2]; + tp[0] = time (0); + tp[1] = LOCAL->filetime; + utime (stream->mailbox,tp); + } + stream->silent = silent; /* can pass up events now */ + mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */ + mail_recent (stream,recent); /* and of change in recent messages */ + return LONGT; /* return the winnage */ +} + +/* MBX get cache element with status updating from file + * Accepts: MAIL stream + * message number + * expunge OK flag + * Returns: cache element + */ + +MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok) +{ + MESSAGECACHE *elt = mail_elt (stream,msgno); + struct { /* old flags */ + unsigned int seen : 1; + unsigned int deleted : 1; + unsigned int flagged : 1; + unsigned int answered : 1; + unsigned int draft : 1; + unsigned long user_flags; + } old; + old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged; + old.answered = elt->answered; old.draft = elt->draft; + old.user_flags = elt->user_flags; + /* get new flags */ + if (mbx_read_flags (stream,elt) && expok) { + mail_expunged (stream,elt->msgno); + return NIL; /* return this message was expunged */ + } + if ((old.seen != elt->seen) || (old.deleted != elt->deleted) || + (old.flagged != elt->flagged) || (old.answered != elt->answered) || + (old.draft != elt->draft) || (old.user_flags != elt->user_flags)) + MM_FLAGS (stream,msgno); /* let top level know */ + return elt; +} + +/* MBX read flags from file + * Accepts: MAIL stream + * cache element + * Returns: non-NIL if message expunged + */ + +unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + unsigned long i; + struct stat sbuf; + fstat (LOCAL->fd,&sbuf); /* get status */ + /* make sure file size is good */ + if (sbuf.st_size < LOCAL->filesize) { + sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag read!", + (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size); + fatal (LOCAL->buf); + } + /* set the seek pointer */ + lseek (LOCAL->fd,(off_t) elt->private.special.offset + + elt->private.special.text.size - 24,L_SET); + /* read the new flags */ + if (read (LOCAL->fd,LOCAL->buf,14) < 0) { + sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno)); + fatal (LOCAL->buf); + } + if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) { + LOCAL->buf[14] = '\0'; /* tie off buffer for error message */ + sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s", + elt->msgno,elt->private.special.offset, + elt->private.special.text.size,(char *) LOCAL->buf); + fatal (LOCAL->buf+50); + } + LOCAL->buf[13] = '\0'; /* tie off buffer */ + /* calculate system flags */ + i = strtoul (LOCAL->buf+9,NIL,16); + elt->seen = i & fSEEN ? T : NIL; + elt->deleted = i & fDELETED ? T : NIL; + elt->flagged = i & fFLAGGED ? T : NIL; + elt->answered = i & fANSWERED ? T : NIL; + elt->draft = i & fDRAFT ? T : NIL; + LOCAL->expunged |= i & fEXPUNGED ? T : NIL; + LOCAL->buf[9] = '\0'; /* tie off flags */ + /* get user flags value */ + elt->user_flags = strtoul (LOCAL->buf+1,NIL,16); + elt->valid = T; /* have valid flags now */ + return i & fEXPUNGED; +} + +/* MBX update header + * Accepts: MAIL stream + */ + +#ifndef CYGKLUDGEOFFSET +#define CYGKLUDGEOFFSET 0 +#endif + +void mbx_update_header (MAILSTREAM *stream) +{ + int i; + char *s = LOCAL->buf; + memset (s,'\0',HDRSIZE); /* initialize header */ + sprintf (s,"*mbx*\015\012%08lx%08lx\015\012", + stream->uid_validity,stream->uid_last); + for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i) + sprintf (s += strlen (s),"%s\015\012",stream->user_flags[i]); + LOCAL->ffuserflag = i; /* first free user flag */ + /* can we create more user flags? */ + stream->kwd_create = (i < NUSERFLAGS) ? T : NIL; + /* write reserved lines */ + while (i++ < NUSERFLAGS) strcat (s,"\015\012"); + sprintf (LOCAL->buf + HDRSIZE - 10,"%08lx\015\012",LOCAL->lastpid); + while (T) { /* rewind file */ + lseek (LOCAL->fd,CYGKLUDGEOFFSET,L_SET); + /* write new header */ + if (write (LOCAL->fd,LOCAL->buf + CYGKLUDGEOFFSET, + HDRSIZE - CYGKLUDGEOFFSET) > 0) break; + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } +} + +/* MBX update status string + * Accepts: MAIL stream + * message number + * flags + */ + +void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags) +{ + struct stat sbuf; + MESSAGECACHE *elt = mail_elt (stream,msgno); + /* readonly */ + if (stream->rdonly || !elt->valid) mbx_read_flags (stream,elt); + else { /* readwrite */ + fstat (LOCAL->fd,&sbuf); /* get status */ + /* make sure file size is good */ + if (sbuf.st_size < LOCAL->filesize) { + sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag update!", + (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size); + fatal (LOCAL->buf); + } + /* set the seek pointer */ + lseek (LOCAL->fd,(off_t) elt->private.special.offset + + elt->private.special.text.size - 24,L_SET); + /* read the new flags */ + if (read (LOCAL->fd,LOCAL->buf,14) < 0) { + sprintf (LOCAL->buf,"Unable to read old status: %s",strerror (errno)); + fatal (LOCAL->buf); + } + if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) { + LOCAL->buf[14] = '\0'; /* tie off buffer for error message */ + sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s", + elt->msgno,elt->private.special.offset, + elt->private.special.text.size,(char *) LOCAL->buf); + fatal (LOCAL->buf+50); + } + /* print new flag string */ + sprintf (LOCAL->buf,"%08lx%04x-%08lx",elt->user_flags,(unsigned) + (((elt->deleted && flags) ? + fEXPUNGED : (strtoul (LOCAL->buf+9,NIL,16)) & fEXPUNGED) + + (fSEEN * elt->seen) + (fDELETED * elt->deleted) + + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + + (fDRAFT * elt->draft) + fOLD),elt->private.uid); + while (T) { /* get to that place in the file */ + lseek (LOCAL->fd,(off_t) elt->private.special.offset + + elt->private.special.text.size - 23,L_SET); + /* write new flags and UID */ + if (write (LOCAL->fd,LOCAL->buf,21) > 0) break; + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } + } +} + +/* MBX locate header for a message + * Accepts: MAIL stream + * message number + * pointer to returned header size + * pointer to possible returned header + * Returns: position of header in file + */ + +#define HDRBUFLEN 16384 /* good enough for most headers */ +#define SLOP 4 /* CR LF CR LF */ + +unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno, + unsigned long *size,char **hdr) +{ + unsigned long siz,done; + long i; + unsigned char *s,*t,*te; + MESSAGECACHE *elt = mail_elt (stream,msgno); + unsigned long ret = elt->private.special.offset + + elt->private.special.text.size; + if (hdr) *hdr = NIL; /* assume no header returned */ + /* is header size known? */ + if (*size = elt->private.msg.header.text.size) return ret; + /* paranoia check */ + if (LOCAL->buflen < (HDRBUFLEN + SLOP)) + fatal ("LOCAL->buf smaller than HDRBUFLEN"); + lseek (LOCAL->fd,ret,L_SET); /* get to header position */ + /* read HDRBUFLEN chunks with 4 byte slop */ + for (done = siz = 0, s = LOCAL->buf; + (i = min ((long) (elt->rfc822_size - done),(long) HDRBUFLEN)) && + (read (LOCAL->fd,s,i) == i); + done += i, siz += (t - LOCAL->buf) - SLOP, s = LOCAL->buf + SLOP) { + te = (t = s + i) - 12; /* calculate end of fast scan */ + /* fast scan for CR */ + for (s = LOCAL->buf; s < te;) + if (((*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') || + (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') || + (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') || + (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015')) && + (*s == '\012') && (*++s == '\015') && (*++s == '\012')) { + *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf); + if (hdr) *hdr = LOCAL->buf; + return ret; + } + for (te = t - 3; (s < te);) /* final character-at-a-time scan */ + if ((*s++ == '\015') && (*s == '\012') && (*++s == '\015') && + (*++s == '\012')) { + *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf); + if (hdr) *hdr = LOCAL->buf; + return ret; + } + if (i <= SLOP) break; /* end of data */ + /* slide over last 4 bytes */ + memmove (LOCAL->buf,t - SLOP,SLOP); + hdr = NIL; /* can't return header this way */ + } + /* not found: header consumes entire message */ + elt->private.msg.header.text.size = *size = elt->rfc822_size; + if (hdr) *hdr = LOCAL->buf; /* possibly return header too */ + return ret; +} + +/* MBX mail rewrite mailbox + * Accepts: MAIL stream + * pointer to return reclaimed size + * flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence) + * Returns: number of expunged messages + */ + +unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed, + long flags) +{ + time_t tp[2]; + struct stat sbuf; + off_t pos,ppos; + int ld; + unsigned long i,j,k,m,delta; + unsigned long n = *reclaimed = 0; + unsigned long recent = 0; + char lock[MAILTMPLEN]; + MESSAGECACHE *elt; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + /* The cretins who designed flock() created a window of vulnerability in + * upgrading locks from shared to exclusive or downgrading from exclusive + * to shared. Rather than maintain the lock at shared status at a minimum, + * flock() actually *releases* the former lock. Obviously they never talked + * to any database guys. Fortunately, we have the parse/append permission + * lock. If we require this lock before going exclusive on the mailbox, + * another process can not sneak in and steal the exclusive mailbox lock on + * us, because it will block on trying to get parse/append permission first. + */ + /* get parse/append permission */ + if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0) { + MM_LOG ("Unable to lock mailbox for rewrite",ERROR); + return 0; + } + fstat (LOCAL->fd,&sbuf); /* get current write time */ + if (LOCAL->filetime && !LOCAL->flagcheck && + (LOCAL->filetime < sbuf.st_mtime)) LOCAL->flagcheck = T; + if (!mbx_parse (stream)) { /* make sure see any newly-arrived messages */ + unlockfd (ld,lock); /* failed?? */ + return 0; + } + if (LOCAL->flagcheck) { /* sweep flags if need flagcheck */ + LOCAL->filetime = sbuf.st_mtime; + for (i = 1; i <= stream->nmsgs; ++i) mbx_elt (stream,i,NIL); + LOCAL->flagcheck = NIL; + } + + /* get exclusive access */ + if (!flock (LOCAL->fd,LOCK_EX|LOCK_NB)) { + MM_CRITICAL (stream); /* go critical */ + for (i = 1,delta = 0,pos = ppos = HDRSIZE; i <= stream->nmsgs; ) { + /* note if message not at predicted location */ + if (m = (elt = mbx_elt (stream,i,NIL))->private.special.offset - ppos) { + ppos = elt->private.special.offset; + *reclaimed += m; /* note reclaimed message space */ + delta += m; /* and as expunge delta */ + } + /* number of bytes to smash or preserve */ + ppos += (k = elt->private.special.text.size + elt->rfc822_size); + /* if need to expunge this message*/ + if (flags && elt->deleted && ((flags > 0) || elt->sequence)) { + delta += k; /* number of bytes to delete */ + mail_expunged(stream,i);/* notify upper levels */ + n++; /* count up one more expunged message */ + } + else { /* preserved message */ + i++; /* count this message */ + if (elt->recent) ++recent; + if (delta) { /* moved, note first byte to preserve */ + j = elt->private.special.offset; + do { /* read from source position */ + m = min (k,LOCAL->buflen); + lseek (LOCAL->fd,j,L_SET); + read (LOCAL->fd,LOCAL->buf,m); + pos = j - delta; /* write to destination position */ + while (T) { + lseek (LOCAL->fd,pos,L_SET); + if (write (LOCAL->fd,LOCAL->buf,m) > 0) break; + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } + pos += m; /* new position */ + j += m; /* next chunk, perhaps */ + } while (k -= m); /* until done */ + /* note the new address of this text */ + elt->private.special.offset -= delta; + } + /* preserved but no deleted messages yet */ + else pos = elt->private.special.offset + k; + } + } + /* deltaed file size match position? */ + if (m = (LOCAL->filesize -= delta) - pos) { + *reclaimed += m; /* probably an fEXPUNGED msg */ + LOCAL->filesize = pos; /* set correct size */ + } + /* truncate file after last message */ + ftruncate (LOCAL->fd,LOCAL->filesize); + fsync (LOCAL->fd); /* force disk update */ + MM_NOCRITICAL (stream); /* release critical */ + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH); /* allow sharers again */ + (*bn) (BLOCK_NONE,NIL); + } + + else { /* can't get exclusive */ + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH); /* recover previous shared mailbox lock */ + (*bn) (BLOCK_NONE,NIL); + /* do hide-expunge when shared */ + if (flags) for (i = 1; i <= stream->nmsgs; ) { + if (elt = mbx_elt (stream,i,T)) { + /* make the message invisible */ + if (elt->deleted && ((flags > 0) || elt->sequence)) { + mbx_update_status (stream,elt->msgno,LONGT); + /* notify upper levels */ + mail_expunged (stream,i); + n++; /* count up one more expunged message */ + } + else { + i++; /* preserved message */ + if (elt->recent) ++recent; + } + } + else n++; /* count up one more expunged message */ + } + fsync (LOCAL->fd); /* force disk update */ + } + fstat (LOCAL->fd,&sbuf); /* get new write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* reset atime to now */ + utime (stream->mailbox,tp); + unlockfd (ld,lock); /* release exclusive parse/append permission */ + /* notify upper level of new mailbox size */ + mail_exists (stream,stream->nmsgs); + mail_recent (stream,recent); + return n; /* return number of expunged messages */ +} + +/* MBX mail lock for flag updating + * Accepts: stream + * Returns: T if successful, NIL if failure + */ + +long mbx_flaglock (MAILSTREAM *stream) +{ + struct stat sbuf; + unsigned long i; + int ld; + char lock[MAILTMPLEN]; + /* no-op if readonly or already locked */ + if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld < 0)) { + /* lock now */ + if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0) return NIL; + if (!LOCAL->flagcheck) { /* don't do this if flagcheck already needed */ + if (LOCAL->filetime) { /* know previous time? */ + fstat (LOCAL->fd,&sbuf);/* get current write time */ + if (LOCAL->filetime < sbuf.st_mtime) LOCAL->flagcheck = T; + LOCAL->filetime = 0; /* don't do this test for any other messages */ + } + if (!mbx_parse (stream)) {/* parse mailbox */ + unlockfd (ld,lock); /* shouldn't happen */ + return NIL; + } + if (LOCAL->flagcheck) /* invalidate cache if flagcheck */ + for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->valid = NIL; + } + LOCAL->ld = ld; /* copy to stream for subsequent calls */ + memcpy (LOCAL->lock,lock,MAILTMPLEN); + } + return LONGT; +} diff --git a/imap/src/osdep/amiga/mh.c b/imap/src/osdep/amiga/mh.c new file mode 100644 index 00000000..0226b7af --- /dev/null +++ b/imap/src/osdep/amiga/mh.c @@ -0,0 +1,1283 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: MH mail routines + * + * Author(s): Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 23 February 1992 + * Last Edited: 11 October 2007 + */ + + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include "osdep.h" +#include <pwd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include "misc.h" +#include "dummy.h" +#include "fdstring.h" + + +/* Build parameters */ + +#define MHINBOX "#mhinbox" /* corresponds to namespace in env_unix.c */ +#define MHINBOXDIR "inbox" +#define MHPROFILE ".mh_profile" +#define MHCOMMA ',' +#define MHSEQUENCE ".mh_sequence" +#define MHSEQUENCES ".mh_sequences" +#define MHPATH "Mail" + + +/* mh_load_message() flags */ + +#define MLM_HEADER 0x1 /* load message text */ +#define MLM_TEXT 0x2 /* load message text */ + +/* MH I/O stream local data */ + +typedef struct mh_local { + char *dir; /* spool directory name */ + unsigned char buf[CHUNKSIZE]; /* temporary buffer */ + unsigned long cachedtexts; /* total size of all cached texts */ + time_t scantime; /* last time directory scanned */ +} MHLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((MHLOCAL *) stream->local) + + +/* Function prototypes */ + +DRIVER *mh_valid (char *name); +int mh_isvalid (char *name,char *tmp,long synonly); +int mh_namevalid (char *name); +char *mh_path (char *tmp); +void *mh_parameters (long function,void *value); +long mh_dirfmttest (char *name); +void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void mh_list (MAILSTREAM *stream,char *ref,char *pat); +void mh_lsub (MAILSTREAM *stream,char *ref,char *pat); +void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level); +long mh_subscribe (MAILSTREAM *stream,char *mailbox); +long mh_unsubscribe (MAILSTREAM *stream,char *mailbox); +long mh_create (MAILSTREAM *stream,char *mailbox); +long mh_delete (MAILSTREAM *stream,char *mailbox); +long mh_rename (MAILSTREAM *stream,char *old,char *newname); +MAILSTREAM *mh_open (MAILSTREAM *stream); +void mh_close (MAILSTREAM *stream,long options); +void mh_fast (MAILSTREAM *stream,char *sequence,long flags); +void mh_load_message (MAILSTREAM *stream,unsigned long msgno,long flags); +char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags); +long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +long mh_ping (MAILSTREAM *stream); +void mh_check (MAILSTREAM *stream); +long mh_expunge (MAILSTREAM *stream,char *sequence,long options); +long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox, + long options); +long mh_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +int mh_select (struct direct *name); +int mh_numsort (const void *d1,const void *d2); +char *mh_file (char *dst,char *name); +long mh_canonicalize (char *pattern,char *ref,char *pat); +void mh_setdate (char *file,MESSAGECACHE *elt); + +/* MH mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER mhdriver = { + "mh", /* driver name */ + /* driver flags */ + DR_MAIL|DR_LOCAL|DR_NOFAST|DR_NAMESPACE|DR_NOSTICKY|DR_DIRFMT, + (DRIVER *) NIL, /* next driver */ + mh_valid, /* mailbox is valid for us */ + mh_parameters, /* manipulate parameters */ + mh_scan, /* scan mailboxes */ + mh_list, /* find mailboxes */ + mh_lsub, /* find subscribed mailboxes */ + mh_subscribe, /* subscribe to mailbox */ + mh_unsubscribe, /* unsubscribe from mailbox */ + mh_create, /* create mailbox */ + mh_delete, /* delete mailbox */ + mh_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + mh_open, /* open mailbox */ + mh_close, /* close mailbox */ + mh_fast, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + mh_header, /* fetch message header */ + mh_text, /* fetch message body */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + NIL, /* modify flags */ + NIL, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + mh_ping, /* ping mailbox to see if still alive */ + mh_check, /* check for new messages */ + mh_expunge, /* expunge deleted messages */ + mh_copy, /* copy messages to another mailbox */ + mh_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM mhproto = {&mhdriver}; + + +static char *mh_profile = NIL; /* holds MH profile */ +static char *mh_pathname = NIL; /* holds MH path name */ +static long mh_once = 0; /* already snarled once */ +static long mh_allow_inbox =NIL;/* allow INBOX as well as MHINBOX */ + +/* MH mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *mh_valid (char *name) +{ + char tmp[MAILTMPLEN]; + return mh_isvalid (name,tmp,T) ? &mhdriver : NIL; +} + + +/* MH mail test for valid mailbox + * Accepts: mailbox name + * temporary buffer to use + * syntax only test flag + * Returns: T if valid, NIL otherwise + */ + +int mh_isvalid (char *name,char *tmp,long synonly) +{ + struct stat sbuf; + char *s,*t,altname[MAILTMPLEN]; + unsigned long i; + int ret = NIL; + errno = NIL; /* zap any error condition */ + /* mh name? */ + if ((mh_allow_inbox && !compare_cstring (name,"INBOX")) || + !compare_cstring (name,MHINBOX) || + ((name[0] == '#') && ((name[1] == 'm') || (name[1] == 'M')) && + ((name[2] == 'h') || (name[2] == 'H')) && (name[3] == '/') && name[4])){ + if (mh_path (tmp)) /* validate name if INBOX or not synonly */ + ret = (synonly && compare_cstring (name,"INBOX")) ? + T : ((stat (mh_file (tmp,name),&sbuf) == 0) && + (sbuf.st_mode & S_IFMT) == S_IFDIR); + else if (!mh_once++) { /* only report error once */ + sprintf (tmp,"%.900s not found, mh format names disabled",mh_profile); + mm_log (tmp,WARN); + } + } + /* see if non-NS name within mh hierarchy */ + else if ((name[0] != '#') && (s = mh_path (tmp)) && (i = strlen (s)) && + (t = mailboxfile (tmp,name)) && !strncmp (t,s,i) && + (tmp[i] == '/') && tmp[i+1]) { + sprintf (altname,"#mh%.900s",tmp+i); + /* can't do synonly here! */ + ret = mh_isvalid (altname,tmp,NIL); + } + else errno = EINVAL; /* bogus name */ + return ret; +} + +/* MH mail test for valid mailbox + * Accepts: mailbox name + * Returns: T if valid, NIL otherwise + */ + +int mh_namevalid (char *name) +{ + char *s; + if (name[0] == '#' && (name[1] == 'm' || name[1] == 'M') && + (name[2] == 'h' || name[2] == 'H') && name[3] == '/') + for (s = name; s && *s;) { /* make sure no all-digit nodes */ + if (isdigit (*s)) s++; /* digit, check this node further... */ + else if (*s == '/') break;/* all digit node, barf */ + /* non-digit, skip to next node or return */ + else if (!((s = strchr (s+1,'/')) && *++s)) return T; + } + return NIL; /* all numeric or empty node */ +} + +/* Return MH path + * Accepts: temporary buffer + * Returns: MH path or NIL if MH disabled + */ + +char *mh_path (char *tmp) +{ + char *s,*t,*v,*r; + int fd; + struct stat sbuf; + if (!mh_profile) { /* build mh_profile and mh_pathname now */ + sprintf (tmp,"%s/%s",myhomedir (),MHPROFILE); + if ((fd = open (mh_profile = cpystr (tmp),O_RDONLY,NIL)) >= 0) { + fstat (fd,&sbuf); /* yes, get size and read file */ + read (fd,(t = (char *) fs_get (sbuf.st_size + 1)),sbuf.st_size); + close (fd); /* don't need the file any more */ + t[sbuf.st_size] = '\0'; /* tie it off */ + /* parse profile file */ + for (s = strtok_r (t,"\r\n",&r); s && *s; s = strtok_r (NIL,"\r\n",&r)) { + /* found space in line? */ + if (v = strpbrk (s," \t")) { + *v++ = '\0'; /* tie off, is keyword "Path:"? */ + if (!compare_cstring (s,"Path:")) { + /* skip whitespace */ + while ((*v == ' ') || (*v == '\t')) ++v; + /* absolute path? */ + if (*v == '/') s = v; + else sprintf (s = tmp,"%s/%s",myhomedir (),v); + /* copy name */ + mh_pathname = cpystr (s); + break; /* don't need to look at rest of file */ + } + } + } + fs_give ((void **) &t); /* flush profile text */ + if (!mh_pathname) { /* default path if not in the profile */ + sprintf (tmp,"%s/%s",myhomedir (),MHPATH); + mh_pathname = cpystr (tmp); + } + } + } + return mh_pathname; +} + +/* MH manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *mh_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = mh_file ((char *) value,"INBOX"); + break; + case GET_DIRFMTTEST: + ret = (void *) mh_dirfmttest; + break; + case SET_MHPROFILE: + if (mh_profile) fs_give ((void **) &mh_profile); + mh_profile = cpystr ((char *) value); + case GET_MHPROFILE: + ret = (void *) mh_profile; + break; + case SET_MHPATH: + if (mh_pathname) fs_give ((void **) &mh_pathname); + mh_pathname = cpystr ((char *) value); + case GET_MHPATH: + ret = (void *) mh_pathname; + break; + case SET_MHALLOWINBOX: + mh_allow_inbox = value ? T : NIL; + case GET_MHALLOWINBOX: + ret = (void *) (mh_allow_inbox ? VOIDT : NIL); + } + return ret; +} + + +/* MH test for directory format internal node + * Accepts: candidate node name + * Returns: T if internal name, NIL otherwise + */ + +long mh_dirfmttest (char *s) +{ + int c; + /* sequence(s) file is an internal name */ + if (strcmp (s,MHSEQUENCE) && strcmp (s,MHSEQUENCES)) { + if (*s == MHCOMMA) ++s; /* else comma + all numeric name */ + /* success if all-numeric */ + while (c = *s++) if (!isdigit (c)) return NIL; + } + return LONGT; +} + +/* MH scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + char *s,test[MAILTMPLEN],file[MAILTMPLEN]; + long i = 0; + if (!pat || !*pat) { /* empty pattern? */ + if (mh_canonicalize (test,ref,"*")) { + /* tie off name at root */ + if (s = strchr (test,'/')) *++s = '\0'; + else test[0] = '\0'; + mm_list (stream,'/',test,LATT_NOSELECT); + } + } + /* get canonical form of name */ + else if (mh_canonicalize (test,ref,pat)) { + if (contents) { /* maybe I'll implement this someday */ + mm_log ("Scan not valid for mh mailboxes",ERROR); + return; + } + if (test[3] == '/') { /* looking down levels? */ + /* yes, found any wildcards? */ + if (s = strpbrk (test,"%*")) { + /* yes, copy name up to that point */ + strncpy (file,test+4,i = s - (test+4)); + file[i] = '\0'; /* tie off */ + } + else strcpy (file,test+4);/* use just that name then */ + /* find directory name */ + if (s = strrchr (file,'/')) { + *s = '\0'; /* found, tie off at that point */ + s = file; + } + /* do the work */ + mh_list_work (stream,s,test,0); + } + /* always an INBOX */ + if (!compare_cstring (test,MHINBOX)) + mm_list (stream,NIL,MHINBOX,LATT_NOINFERIORS); + } +} + +/* MH list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mh_list (MAILSTREAM *stream,char *ref,char *pat) +{ + mh_scan (stream,ref,pat,NIL); +} + + +/* MH list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mh_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + void *sdb = NIL; + char *s,test[MAILTMPLEN]; + /* get canonical form of name */ + if (mh_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) { + do if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,NIL); + while (s = sm_read (&sdb)); /* until no more subscriptions */ + } +} + +/* MH list mailboxes worker routine + * Accepts: mail stream + * directory name to search + * search pattern + * search level + */ + +void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level) +{ + DIR *dp; + struct direct *d; + struct stat sbuf; + char *cp,*np,curdir[MAILTMPLEN],name[MAILTMPLEN]; + /* build MH name to search */ + if (dir) sprintf (name,"#mh/%s/",dir); + else strcpy (name,"#mh/"); + /* make directory name, punt if bogus */ + if (!mh_file (curdir,name)) return; + cp = curdir + strlen (curdir);/* end of directory name */ + np = name + strlen (name); /* end of MH name */ + if (dp = opendir (curdir)) { /* open directory */ + while (d = readdir (dp)) /* scan, ignore . and numeric names */ + if ((d->d_name[0] != '.') && !mh_select (d)) { + strcpy (cp,d->d_name); /* make directory name */ + if (!stat (curdir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) { + strcpy (np,d->d_name);/* make mh name of directory name */ + /* yes, an MH name if full match */ + if (pmatch_full (name,pat,'/')) mm_list (stream,'/',name,NIL); + /* check if should recurse */ + if (dmatch (name,pat,'/') && + (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL))) + mh_list_work (stream,name+4,pat,level+1); + } + } + closedir (dp); /* all done, flush directory */ + } +} + +/* MH mail subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long mh_subscribe (MAILSTREAM *stream,char *mailbox) +{ + return sm_subscribe (mailbox); +} + + +/* MH mail unsubscribe to mailbox + * Accepts: mail stream + * mailbox to delete from subscription list + * Returns: T on success, NIL on failure + */ + +long mh_unsubscribe (MAILSTREAM *stream,char *mailbox) +{ + return sm_unsubscribe (mailbox); +} + +/* MH mail create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long mh_create (MAILSTREAM *stream,char *mailbox) +{ + char tmp[MAILTMPLEN]; + if (!mh_namevalid (mailbox)) /* validate name */ + sprintf (tmp,"Can't create mailbox %.80s: invalid MH-format name",mailbox); + /* must not already exist */ + else if (mh_isvalid (mailbox,tmp,NIL)) + sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox); + else if (!mh_path (tmp)) return NIL; + /* try to make it */ + else if (!(mh_file (tmp,mailbox) && + dummy_create_path (stream,strcat (tmp,"/"), + get_dir_protection (mailbox)))) + sprintf (tmp,"Can't create mailbox %.80s: %s",mailbox,strerror (errno)); + else return LONGT; /* success */ + mm_log (tmp,ERROR); + return NIL; +} + +/* MH mail delete mailbox + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long mh_delete (MAILSTREAM *stream,char *mailbox) +{ + DIR *dirp; + struct direct *d; + int i; + char tmp[MAILTMPLEN]; + /* is mailbox valid? */ + if (!mh_isvalid (mailbox,tmp,NIL)) { + sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox); + mm_log (tmp,ERROR); + return NIL; + } + /* get name of directory */ + i = strlen (mh_file (tmp,mailbox)); + if (dirp = opendir (tmp)) { /* open directory */ + tmp[i++] = '/'; /* now apply trailing delimiter */ + /* massacre all mh owned files */ + while (d = readdir (dirp)) if (mh_dirfmttest (d->d_name)) { + strcpy (tmp + i,d->d_name); + unlink (tmp); /* sayonara */ + } + closedir (dirp); /* flush directory */ + } + /* try to remove the directory */ + if (rmdir (mh_file (tmp,mailbox))) { + sprintf (tmp,"Can't delete mailbox %.80s: %s",mailbox,strerror (errno)); + mm_log (tmp,WARN); + } + return T; /* return success */ +} + +/* MH mail rename mailbox + * Accepts: MH mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long mh_rename (MAILSTREAM *stream,char *old,char *newname) +{ + char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN]; + struct stat sbuf; + /* old mailbox name must be valid */ + if (!mh_isvalid (old,tmp,NIL)) + sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old); + else if (!mh_namevalid (newname)) + sprintf (tmp,"Can't rename to mailbox %.80s: invalid MH-format name", + newname); + /* new mailbox name must not be valid */ + else if (mh_isvalid (newname,tmp,NIL)) + sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists", + newname); + /* success if can rename the directory */ + else { /* found superior to destination name? */ + if (s = strrchr (mh_file (tmp1,newname),'/')) { + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,tmp1,get_dir_protection (newname))) + return NIL; + *s = c; /* restore full name */ + } + if (!rename (mh_file (tmp,old),tmp1)) return T; + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s", + old,newname,strerror (errno)); + } + mm_log (tmp,ERROR); /* something failed */ + return NIL; +} + +/* MH mail open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *mh_open (MAILSTREAM *stream) +{ + char tmp[MAILTMPLEN]; + if (!stream) return &mhproto; /* return prototype for OP_PROTOTYPE call */ + if (stream->local) fatal ("mh recycle stream"); + stream->local = fs_get (sizeof (MHLOCAL)); + /* INBOXness is one of the following: + * #mhinbox (case-independent) + * #mh/inbox (mh is case-independent, inbox is case-dependent) + * INBOX (case-independent + */ + stream->inbox = /* note if an INBOX or not */ + (!compare_cstring (stream->mailbox,MHINBOX) || + ((stream->mailbox[0] == '#') && + ((stream->mailbox[1] == 'm') || (stream->mailbox[1] == 'M')) && + ((stream->mailbox[2] == 'h') || (stream->mailbox[2] == 'H')) && + (stream->mailbox[3] == '/') && !strcmp (stream->mailbox+4,MHINBOXDIR)) || + !compare_cstring (stream->mailbox,"INBOX")) ? T : NIL; + mh_file (tmp,stream->mailbox);/* get directory name */ + LOCAL->dir = cpystr (tmp); /* copy directory name for later */ + LOCAL->scantime = 0; /* not scanned yet */ + LOCAL->cachedtexts = 0; /* no cached texts */ + stream->sequence++; /* bump sequence number */ + /* parse mailbox */ + stream->nmsgs = stream->recent = 0; + if (!mh_ping (stream)) return NIL; + if (!(stream->nmsgs || stream->silent)) + mm_log ("Mailbox is empty",(long) NIL); + return stream; /* return stream to caller */ +} + +/* MH mail close + * Accepts: MAIL stream + * close options + */ + +void mh_close (MAILSTREAM *stream,long options) +{ + if (LOCAL) { /* only if a file is open */ + int silent = stream->silent; + stream->silent = T; /* note this stream is dying */ + if (options & CL_EXPUNGE) mh_expunge (stream,NIL,NIL); + if (LOCAL->dir) fs_give ((void **) &LOCAL->dir); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + stream->silent = silent; /* reset silent state */ + } +} + + +/* MH mail fetch fast information + * Accepts: MAIL stream + * sequence + * option flags + */ + +void mh_fast (MAILSTREAM *stream,char *sequence,long flags) +{ + MESSAGECACHE *elt; + unsigned long i; + /* set up metadata for all messages */ + if (stream && LOCAL && ((flags & FT_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence && + !(elt->day && elt->rfc822_size)) mh_load_message (stream,i,NIL); +} + +/* MH load message into cache + * Accepts: MAIL stream + * message # + * option flags + */ + +void mh_load_message (MAILSTREAM *stream,unsigned long msgno,long flags) +{ + unsigned long i,j,nlseen; + int fd; + unsigned char c,*t; + struct stat sbuf; + MESSAGECACHE *elt; + FDDATA d; + STRING bs; + elt = mail_elt (stream,msgno);/* get elt */ + /* build message file name */ + sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid); + /* anything we need not currently cached? */ + if ((!elt->day || !elt->rfc822_size || + ((flags & MLM_HEADER) && !elt->private.msg.header.text.data) || + ((flags & MLM_TEXT) && !elt->private.msg.text.text.data)) && + ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) { + fstat (fd,&sbuf); /* get file metadata */ + d.fd = fd; /* set up file descriptor */ + d.pos = 0; /* start of file */ + d.chunk = LOCAL->buf; + d.chunksize = CHUNKSIZE; + INIT (&bs,fd_string,&d,sbuf.st_size); + if (!elt->day) { /* set internaldate to file date */ + struct tm *tm = gmtime (&sbuf.st_mtime); + elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1; + elt->year = tm->tm_year + 1900 - BASEYEAR; + elt->hours = tm->tm_hour; elt->minutes = tm->tm_min; + elt->seconds = tm->tm_sec; + elt->zhours = 0; elt->zminutes = 0; + } + + if (!elt->rfc822_size) { /* know message size yet? */ + for (i = 0, j = SIZE (&bs), nlseen = 0; j--; ) switch (SNX (&bs)) { + case '\015': /* unlikely carriage return */ + if (!j || (CHR (&bs) != '\012')) { + i++; /* ugh, raw CR */ + nlseen = NIL; + break; + } + SNX (&bs); /* eat the line feed, drop in */ + --j; + case '\012': /* line feed? */ + i += 2; /* count a CRLF */ + /* header size known yet? */ + if (!elt->private.msg.header.text.size && nlseen) { + /* note position in file */ + elt->private.special.text.size = GETPOS (&bs); + /* and CRLF-adjusted size */ + elt->private.msg.header.text.size = i; + } + nlseen = T; /* note newline seen */ + break; + default: /* ordinary chararacter */ + i++; + nlseen = NIL; + break; + } + SETPOS (&bs,0); /* restore old position */ + elt->rfc822_size = i; /* note that we have size now */ + /* header is entire message if no delimiter */ + if (!elt->private.msg.header.text.size) + elt->private.msg.header.text.size = elt->rfc822_size; + /* text is remainder of message */ + elt->private.msg.text.text.size = + elt->rfc822_size - elt->private.msg.header.text.size; + } + /* need to load cache with message data? */ + if (((flags & MLM_HEADER) && !elt->private.msg.header.text.data) || + ((flags & MLM_TEXT) && !elt->private.msg.text.text.data)) { + /* purge cache if too big */ + if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) { + /* just can't keep that much */ + mail_gc (stream,GC_TEXTS); + LOCAL->cachedtexts = 0; + } + + if ((flags & MLM_HEADER) && !elt->private.msg.header.text.data) { + t = elt->private.msg.header.text.data = + (unsigned char *) fs_get (elt->private.msg.header.text.size + 1); + LOCAL->cachedtexts += elt->private.msg.header.text.size; + /* read in message header */ + for (i = 0; i < elt->private.msg.header.text.size; i++) + switch (c = SNX (&bs)) { + case '\015': /* unlikely carriage return */ + *t++ = c; + if ((CHR (&bs) == '\012')) { + *t++ = SNX (&bs); + i++; + } + break; + case '\012': /* line feed? */ + *t++ = '\015'; + i++; + default: + *t++ = c; + break; + } + *t = '\0'; /* tie off string */ + if ((t - elt->private.msg.header.text.data) != + elt->private.msg.header.text.size) fatal ("mh hdr size mismatch"); + } + if ((flags & MLM_TEXT) && !elt->private.msg.text.text.data) { + t = elt->private.msg.text.text.data = + (unsigned char *) fs_get (elt->private.msg.text.text.size + 1); + SETPOS (&bs,elt->private.special.text.size); + LOCAL->cachedtexts += elt->private.msg.text.text.size; + /* read in message text */ + for (i = 0; i < elt->private.msg.text.text.size; i++) + switch (c = SNX (&bs)) { + case '\015': /* unlikely carriage return */ + *t++ = c; + if ((CHR (&bs) == '\012')) { + *t++ = SNX (&bs); + i++; + } + break; + case '\012': /* line feed? */ + *t++ = '\015'; + i++; + default: + *t++ = c; + break; + } + *t = '\0'; /* tie off string */ + if ((t - elt->private.msg.text.text.data) != + elt->private.msg.text.text.size) fatal ("mh txt size mismatch"); + } + } + close (fd); /* flush message file */ + } +} + +/* MH mail fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + +char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags) +{ + MESSAGECACHE *elt; + *length = 0; /* default to empty */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + elt = mail_elt (stream,msgno);/* get elt */ + if (!elt->private.msg.header.text.data) + mh_load_message (stream,msgno,MLM_HEADER); + *length = elt->private.msg.header.text.size; + return (char *) elt->private.msg.header.text.data; +} + + +/* MH mail fetch message text (body only) + * Accepts: MAIL stream + * message # to fetch + * pointer to returned stringstruct + * option flags + * Returns: T on success, NIL on failure + */ + +long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + elt = mail_elt (stream,msgno);/* get elt */ + /* snarf message if don't have it yet */ + if (!elt->private.msg.text.text.data) { + mh_load_message (stream,msgno,MLM_TEXT); + if (!elt->private.msg.text.text.data) return NIL; + } + if (!(flags & FT_PEEK)) { /* mark as seen */ + mail_elt (stream,msgno)->seen = T; + mm_flags (stream,msgno); + } + INIT (bs,mail_string,elt->private.msg.text.text.data, + elt->private.msg.text.text.size); + return T; +} + +/* MH mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +long mh_ping (MAILSTREAM *stream) +{ + MAILSTREAM *sysibx = NIL; + MESSAGECACHE *elt,*selt; + struct stat sbuf; + char *s,tmp[MAILTMPLEN]; + int fd; + unsigned long i,j,r; + unsigned long old = stream->uid_last; + long nmsgs = stream->nmsgs; + long recent = stream->recent; + int silent = stream->silent; + if (stat (LOCAL->dir,&sbuf)) {/* directory exists? */ + if (stream->inbox && /* no, create if INBOX */ + dummy_create_path (stream,strcat (mh_file (tmp,MHINBOX),"/"), + get_dir_protection ("INBOX"))) return T; + sprintf (tmp,"Can't open mailbox %.80s: no such mailbox",stream->mailbox); + mm_log (tmp,ERROR); + return NIL; + } + stream->silent = T; /* don't pass up mm_exists() events yet */ + if (sbuf.st_ctime != LOCAL->scantime) { + struct direct **names = NIL; + long nfiles = scandir (LOCAL->dir,&names,mh_select,mh_numsort); + if (nfiles < 0) nfiles = 0; /* in case error */ + /* note scanned now */ + LOCAL->scantime = sbuf.st_ctime; + /* scan directory */ + for (i = 0; i < nfiles; ++i) { + /* if newly seen, add to list */ + if ((j = atoi (names[i]->d_name)) > old) { + mail_exists (stream,++nmsgs); + stream->uid_last = (elt = mail_elt (stream,nmsgs))->private.uid = j; + elt->valid = T; /* note valid flags */ + if (old) { /* other than the first pass? */ + elt->recent = T; /* yup, mark as recent */ + recent++; /* bump recent count */ + } + else { /* see if already read */ + sprintf (tmp,"%s/%s",LOCAL->dir,names[i]->d_name); + if (!stat (tmp,&sbuf) && (sbuf.st_atime > sbuf.st_mtime)) + elt->seen = T; + } + } + fs_give ((void **) &names[i]); + } + /* free directory */ + if (s = (void *) names) fs_give ((void **) &s); + } + + /* if INBOX, snarf from system INBOX */ + if (stream->inbox && strcmp (sysinbox (),stream->mailbox)) { + old = stream->uid_last; + mm_critical (stream); /* go critical */ + /* see if anything in system inbox */ + if (!stat (sysinbox (),&sbuf) && sbuf.st_size && + (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) && + !sysibx->rdonly && (r = sysibx->nmsgs)) { + for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */ + /* build file name we will use */ + sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,++old); + /* snarf message from Berkeley mailbox */ + selt = mail_elt (sysibx,i); + if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) + >= 0) && + (s = mail_fetchheader_full (sysibx,i,NIL,&j,FT_INTERNAL)) && + (write (fd,s,j) == j) && + (s = mail_fetchtext_full (sysibx,i,&j,FT_INTERNAL|FT_PEEK)) && + (write (fd,s,j) == j) && !fsync (fd) && !close (fd)) { + /* swell the cache */ + mail_exists (stream,++nmsgs); + stream->uid_last = /* create new elt, note its file number */ + (elt = mail_elt (stream,nmsgs))->private.uid = old; + recent++; /* bump recent count */ + /* set up initial flags and date */ + elt->valid = elt->recent = T; + elt->seen = selt->seen; + elt->deleted = selt->deleted; + elt->flagged = selt->flagged; + elt->answered = selt->answered; + elt->draft = selt->draft; + elt->day = selt->day;elt->month = selt->month;elt->year = selt->year; + elt->hours = selt->hours;elt->minutes = selt->minutes; + elt->seconds = selt->seconds; + elt->zhours = selt->zhours; elt->zminutes = selt->zminutes; + elt->zoccident = selt->zoccident; + mh_setdate (LOCAL->buf,elt); + sprintf (tmp,"%lu",i);/* delete it from the sysinbox */ + mail_flag (sysibx,tmp,"\\Deleted",ST_SET); + } + + else { /* failed to snarf */ + if (fd) { /* did it ever get opened? */ + close (fd); /* close descriptor */ + unlink (LOCAL->buf);/* flush this file */ + } + sprintf (tmp,"Message copy to MH mailbox failed: %.80s", + s,strerror (errno)); + mm_log (tmp,ERROR); + r = 0; /* stop the snarf in its tracks */ + } + } + /* update scan time */ + if (!stat (LOCAL->dir,&sbuf)) LOCAL->scantime = sbuf.st_ctime; + mail_expunge (sysibx); /* now expunge all those messages */ + } + if (sysibx) mail_close (sysibx); + mm_nocritical (stream); /* release critical */ + } + stream->silent = silent; /* can pass up events now */ + mail_exists (stream,nmsgs); /* notify upper level of mailbox size */ + mail_recent (stream,recent); + return T; /* return that we are alive */ +} + +/* MH mail check mailbox + * Accepts: MAIL stream + */ + +void mh_check (MAILSTREAM *stream) +{ + /* Perhaps in the future this will preserve flags */ + if (mh_ping (stream)) mm_log ("Check completed",(long) NIL); +} + + +/* MH mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T, always + */ + +long mh_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret; + MESSAGECACHE *elt; + unsigned long i = 1; + unsigned long n = 0; + unsigned long recent = stream->recent; + if (ret = sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : LONGT) { + mm_critical (stream); /* go critical */ + while (i <= stream->nmsgs) {/* for each message */ + elt = mail_elt (stream,i);/* if deleted, need to trash it */ + if (elt->deleted && (sequence ? elt->sequence : T)) { + sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid); + if (unlink (LOCAL->buf)) {/* try to delete the message */ + sprintf (LOCAL->buf,"Expunge of message %lu failed, aborted: %s",i, + strerror (errno)); + mm_log (LOCAL->buf,(long) NIL); + break; + } + /* note uncached */ + LOCAL->cachedtexts -= ((elt->private.msg.header.text.data ? + elt->private.msg.header.text.size : 0) + + (elt->private.msg.text.text.data ? + elt->private.msg.text.text.size : 0)); + mail_gc_msg (&elt->private.msg,GC_ENV | GC_TEXTS); + /* if recent, note one less recent message */ + if (elt->recent) --recent; + /* notify upper levels */ + mail_expunged (stream,i); + n++; /* count up one more expunged message */ + } + else i++; /* otherwise try next message */ + } + if (n) { /* output the news if any expunged */ + sprintf (LOCAL->buf,"Expunged %lu messages",n); + mm_log (LOCAL->buf,(long) NIL); + } + else mm_log ("No messages deleted, so no update needed",(long) NIL); + mm_nocritical (stream); /* release critical */ + /* notify upper level of new mailbox size */ + mail_exists (stream,stream->nmsgs); + mail_recent (stream,recent); + } + return ret; +} + +/* MH mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if copy successful, else NIL + */ + +long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + FDDATA d; + STRING st; + MESSAGECACHE *elt; + struct stat sbuf; + int fd; + unsigned long i; + char flags[MAILTMPLEN],date[MAILTMPLEN]; + appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); + long ret = NIL; + /* copy the messages */ + if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) { + sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid); + if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) < 0) return NIL; + fstat (fd,&sbuf); /* get size of message */ + if (!elt->day) { /* set internaldate to file date if needed */ + struct tm *tm = gmtime (&sbuf.st_mtime); + elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1; + elt->year = tm->tm_year + 1900 - BASEYEAR; + elt->hours = tm->tm_hour; elt->minutes = tm->tm_min; + elt->seconds = tm->tm_sec; + elt->zhours = 0; elt->zminutes = 0; + } + d.fd = fd; /* set up file descriptor */ + d.pos = 0; /* start of file */ + d.chunk = LOCAL->buf; + d.chunksize = CHUNKSIZE; + /* kludge; mh_append would just strip CRs */ + INIT (&st,fd_string,&d,sbuf.st_size); + /* init flag string */ + flags[0] = flags[1] = '\0'; + if (elt->seen) strcat (flags," \\Seen"); + if (elt->deleted) strcat (flags," \\Deleted"); + if (elt->flagged) strcat (flags," \\Flagged"); + if (elt->answered) strcat (flags," \\Answered"); + if (elt->draft) strcat (flags," \\Draft"); + flags[0] = '('; /* open list */ + strcat (flags,")"); /* close list */ + mail_date (date,elt); /* generate internal date */ + if (au) mail_parameters (NIL,SET_APPENDUID,NIL); + if ((ret = mail_append_full (NIL,mailbox,flags,date,&st)) && + (options & CP_MOVE)) elt->deleted = T; + if (au) mail_parameters (NIL,SET_APPENDUID,(void *) au); + close (fd); + } + if (ret && mail_parameters (NIL,GET_COPYUID,NIL)) + mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN); + return ret; /* return success */ +} + +/* MH mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long mh_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + struct direct **names = NIL; + int fd; + char c,*flags,*date,*s,tmp[MAILTMPLEN]; + STRING *message; + MESSAGECACHE elt; + FILE *df; + long i,size,last,nfiles; + long ret = LONGT; + /* default stream to prototype */ + if (!stream) stream = &mhproto; + /* make sure valid mailbox */ + if (!mh_isvalid (mailbox,tmp,NIL)) switch (errno) { + case ENOENT: /* no such file? */ + if (!((!compare_cstring (mailbox,MHINBOX) || + !compare_cstring (mailbox,"INBOX")) && + (mh_file (tmp,MHINBOX) && + dummy_create_path (stream,strcat (tmp,"/"), + get_dir_protection (mailbox))))) { + mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL); + return NIL; + } + /* falls through */ + case 0: /* merely empty file? */ + break; + case EINVAL: + sprintf (tmp,"Invalid MH-format mailbox name: %.80s",mailbox); + mm_log (tmp,ERROR); + return NIL; + default: + sprintf (tmp,"Not a MH-format mailbox: %.80s",mailbox); + mm_log (tmp,ERROR); + return NIL; + } + /* get first message */ + if (!(*af) (stream,data,&flags,&date,&message)) return NIL; + if ((nfiles = scandir (tmp,&names,mh_select,mh_numsort)) > 0) { + /* largest number */ + last = atoi (names[nfiles-1]->d_name); + for (i = 0; i < nfiles; ++i) /* free directory */ + fs_give ((void **) &names[i]); + } + else last = 0; /* no messages here yet */ + if (s = (void *) names) fs_give ((void **) &s); + + mm_critical (stream); /* go critical */ + do { + if (!SIZE (message)) { /* guard against zero-length */ + mm_log ("Append of zero-length message",ERROR); + ret = NIL; + break; + } + if (date) { /* want to preserve date? */ + /* yes, parse date into an elt */ + if (!mail_parse_date (&elt,date)) { + sprintf (tmp,"Bad date in append: %.80s",date); + mm_log (tmp,ERROR); + ret = NIL; + break; + } + } + mh_file (tmp,mailbox); /* build file name we will use */ + sprintf (tmp + strlen (tmp),"/%ld",++last); + if (((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL, + (long)mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0)|| + !(df = fdopen (fd,"ab"))) { + sprintf (tmp,"Can't open append message: %s",strerror (errno)); + mm_log (tmp,ERROR); + ret = NIL; + break; + } + /* copy the data w/o CR's */ + for (size = 0,i = SIZE (message); i && ret; --i) + if (((c = SNX (message)) != '\015') && (putc (c,df) == EOF)) ret = NIL; + /* close the file */ + if (!ret || fclose (df)) { + unlink (tmp); /* delete message */ + sprintf (tmp,"Message append failed: %s",strerror (errno)); + mm_log (tmp,ERROR); + ret = NIL; + } + if (ret) { /* set the date for this message */ + if (date) mh_setdate (tmp,&elt); + /* get next message */ + if (!(*af) (stream,data,&flags,&date,&message)) ret = NIL; + } + } while (ret && message); + mm_nocritical (stream); /* release critical */ + if (ret && mail_parameters (NIL,GET_APPENDUID,NIL)) + mm_log ("Can not return meaningful APPENDUID with this mailbox format", + WARN); + return ret; +} + +/* Internal routines */ + + +/* MH file name selection test + * Accepts: candidate directory entry + * Returns: T to use file name, NIL to skip it + */ + +int mh_select (struct direct *name) +{ + char c; + char *s = name->d_name; + while (c = *s++) if (!isdigit (c)) return NIL; + return T; +} + + +/* MH file name comparision + * Accepts: first candidate directory entry + * second candidate directory entry + * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2 + */ + +int mh_numsort (const void *d1,const void *d2) +{ + return atoi ((*(struct direct **) d1)->d_name) - + atoi ((*(struct direct **) d2)->d_name); +} + + +/* MH mail build file name + * Accepts: destination string + * source + * Returns: destination + */ + +char *mh_file (char *dst,char *name) +{ + char *s; + char *path = mh_path (dst); + if (!path) fatal ("No mh path in mh_file()!"); + /* INBOX becomes "inbox" in the MH path */ + if (!compare_cstring (name,MHINBOX) || !compare_cstring (name,"INBOX")) + sprintf (dst,"%.900s/%.80s",path,MHINBOXDIR); + /* #mh names skip past prefix */ + else if (*name == '#') sprintf (dst,"%.100s/%.900s",path,name + 4); + else mailboxfile (dst,name); /* all other names */ + /* tie off unnecessary trailing / */ + if ((s = strrchr (dst,'/')) && !s[1] && (s[-1] == '/')) *s = '\0'; + return dst; +} + +/* MH canonicalize name + * Accepts: buffer to write name + * reference + * pattern + * Returns: T if success, NIL if failure + */ + +long mh_canonicalize (char *pattern,char *ref,char *pat) +{ + unsigned long i; + char *s,tmp[MAILTMPLEN]; + if (ref && *ref) { /* have a reference */ + strcpy (pattern,ref); /* copy reference to pattern */ + /* # overrides mailbox field in reference */ + if (*pat == '#') strcpy (pattern,pat); + /* pattern starts, reference ends, with / */ + else if ((*pat == '/') && (pattern[strlen (pattern) - 1] == '/')) + strcat (pattern,pat + 1); /* append, omitting one of the period */ + else strcat (pattern,pat); /* anything else is just appended */ + } + else strcpy (pattern,pat); /* just have basic name */ + if (mh_isvalid (pattern,tmp,T)) { + /* count wildcards */ + for (i = 0, s = pattern; *s; *s++) if ((*s == '*') || (*s == '%')) ++i; + /* success if not too many */ + if (i <= MAXWILDCARDS) return LONGT; + mm_log ("Excessive wildcards in LIST/LSUB",ERROR); + } + return NIL; +} + +/* Set date for message + * Accepts: file name + * elt containing date + */ + +void mh_setdate (char *file,MESSAGECACHE *elt) +{ + time_t tp[2]; + tp[0] = time (0); /* atime is now */ + tp[1] = mail_longdate (elt); /* modification time */ + utime (file,tp); /* set the times */ +} diff --git a/imap/src/osdep/amiga/mix.c b/imap/src/osdep/amiga/mix.c new file mode 100644 index 00000000..fbf4a023 --- /dev/null +++ b/imap/src/osdep/amiga/mix.c @@ -0,0 +1,2834 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: MIX mail routines + * + * Author(s): Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@Washington.EDU + * + * Date: 1 March 2006 + * Last Edited: 7 May 2008 + */ + + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include "osdep.h" +#include <pwd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include "misc.h" +#include "dummy.h" +#include "fdstring.h" + +/* MIX definitions */ + +#define MEGABYTE (1024*1024) + +#define MIXDATAROLL MEGABYTE /* size at which we roll to a new file */ + + +/* MIX files */ + +#define MIXNAME ".mix" /* prefix for all MIX file names */ +#define MIXMETA "meta" /* suffix for metadata */ +#define MIXINDEX "index" /* suffix for index */ +#define MIXSTATUS "status" /* suffix for status */ +#define MIXSORTCACHE "sortcache"/* suffix for sortcache */ +#define METAMAX (MEGABYTE-1) /* maximum metadata file size (sanity check) */ + + +/* MIX file formats */ + + /* sequence format (all but msg files) */ +#define SEQFMT "S%08lx\015\012" + /* metadata file format */ +#define MTAFMT "V%08lx\015\012L%08lx\015\012N%08lx\015\012" + /* index file record format */ +#define IXRFMT ":%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:%08lx:%08lx:%08lx:%08lx:\015\012" + /* status file record format */ +#define STRFMT ":%08lx:%08lx:%04x:%08lx:\015\012" + /* message file header format */ +#define MSRFMT "%s%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:\015\012" +#define MSGTOK ":msg:" +#define MSGTSZ (sizeof(MSGTOK)-1) + /* sortcache file record format */ +#define SCRFMT ":%08lx:%08lx:%08lx:%08lx:%08lx:%c%08lx:%08lx:%08lx:\015\012" + +/* MIX I/O stream local data */ + +typedef struct mix_local { + unsigned long curmsg; /* current message file number */ + unsigned long newmsg; /* current new message file number */ + time_t lastsnarf; /* last snarf time */ + int msgfd; /* file description of current msg file */ + int mfd; /* file descriptor of open metadata */ + unsigned long metaseq; /* metadata sequence */ + char *index; /* mailbox index name */ + unsigned long indexseq; /* index sequence */ + char *status; /* mailbox status name */ + unsigned long statusseq; /* status sequence */ + char *sortcache; /* mailbox sortcache name */ + unsigned long sortcacheseq; /* sortcache sequence */ + unsigned char *buf; /* temporary buffer */ + unsigned long buflen; /* current size of temporary buffer */ + unsigned int expok : 1; /* non-zero if expunge reports OK */ + unsigned int internal : 1; /* internally opened, do not validate */ +} MIXLOCAL; + + +#define MIXBURP struct mix_burp + +MIXBURP { + unsigned long fileno; /* message file number */ + char *name; /* message file name */ + SEARCHSET *tail; /* tail of ranges */ + SEARCHSET set; /* set of retained ranges */ + MIXBURP *next; /* next file to burp */ +}; + + +/* Convenient access to local data */ + +#define LOCAL ((MIXLOCAL *) stream->local) + +/* Function prototypes */ + +DRIVER *mix_valid (char *name); +long mix_isvalid (char *name,char *meta); +void *mix_parameters (long function,void *value); +long mix_dirfmttest (char *name); +void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +long mix_scan_contents (char *name,char *contents,unsigned long csiz, + unsigned long fsiz); +void mix_list (MAILSTREAM *stream,char *ref,char *pat); +void mix_lsub (MAILSTREAM *stream,char *ref,char *pat); +long mix_subscribe (MAILSTREAM *stream,char *mailbox); +long mix_unsubscribe (MAILSTREAM *stream,char *mailbox); +long mix_create (MAILSTREAM *stream,char *mailbox); +long mix_delete (MAILSTREAM *stream,char *mailbox); +long mix_rename (MAILSTREAM *stream,char *old,char *newname); +int mix_rselect (struct direct *name); +MAILSTREAM *mix_open (MAILSTREAM *stream); +void mix_close (MAILSTREAM *stream,long options); +void mix_abort (MAILSTREAM *stream); +char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags); +long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); +unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags); +THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags); +long mix_ping (MAILSTREAM *stream); +void mix_check (MAILSTREAM *stream); +long mix_expunge (MAILSTREAM *stream,char *sequence,long options); +int mix_select (struct direct *name); +int mix_msgfsort (const void *d1,const void *d2); +long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size); +long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed); +long mix_burp_check (SEARCHSET *set,size_t size,char *file); +long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox, + long options); +long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); +long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt, + STRING *msg,SEARCHSET *set,unsigned long seq); + +FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags); +char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq); +long mix_meta_update (MAILSTREAM *stream); +long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag); +long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag); +FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size, + unsigned long newsize); +FILE *mix_sortcache_open (MAILSTREAM *stream); +long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache); +char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type); +unsigned long mix_read_sequence (FILE *f); +char *mix_dir (char *dst,char *name); +char *mix_file (char *dst,char *dir,char *name); +char *mix_file_data (char *dst,char *dir,unsigned long data); +unsigned long mix_modseq (unsigned long oldseq); + +/* MIX mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER mixdriver = { + "mix", /* driver name */ + /* driver flags */ + DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT|DR_MODSEQ, + (DRIVER *) NIL, /* next driver */ + mix_valid, /* mailbox is valid for us */ + mix_parameters, /* manipulate parameters */ + mix_scan, /* scan mailboxes */ + mix_list, /* find mailboxes */ + mix_lsub, /* find subscribed mailboxes */ + mix_subscribe, /* subscribe to mailbox */ + mix_unsubscribe, /* unsubscribe from mailbox */ + mix_create, /* create mailbox */ + mix_delete, /* delete mailbox */ + mix_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + mix_open, /* open mailbox */ + mix_close, /* close mailbox */ + NIL, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + mix_header, /* fetch message header only */ + mix_text, /* fetch message body only */ + NIL, /* fetch partial message test */ + NIL, /* unique identifier */ + NIL, /* message number */ + mix_flag, /* modify flags */ + NIL, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + mix_sort, /* sort messages */ + mix_thread, /* thread messages */ + mix_ping, /* ping mailbox to see if still alive */ + mix_check, /* check for new messages */ + mix_expunge, /* expunge deleted messages */ + mix_copy, /* copy messages to another mailbox */ + mix_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM mixproto = {&mixdriver}; + +/* MIX mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *mix_valid (char *name) +{ + char tmp[MAILTMPLEN]; + return mix_isvalid (name,tmp) ? &mixdriver : NIL; +} + + +/* MIX mail test for valid mailbox + * Accepts: mailbox name + * buffer to return meta name + * Returns: T if valid, NIL otherwise, metadata name written in both cases + */ + +long mix_isvalid (char *name,char *meta) +{ + char dir[MAILTMPLEN]; + struct stat sbuf; + /* validate name as directory */ + if (!(errno = ((strlen (name) > NETMAXMBX) ? ENAMETOOLONG : NIL)) && + *mix_dir (dir,name) && mix_file (meta,dir,MIXMETA) && + !stat (dir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) { + /* name is directory; is it mix? */ + if (!stat (meta,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG)) + return LONGT; + else errno = NIL; /* directory but not mix */ + } + return NIL; +} + +/* MIX manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *mix_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = mailboxfile ((char *) value,"~/INBOX"); + break; + case GET_DIRFMTTEST: + ret = (void *) mix_dirfmttest; + break; + case GET_SCANCONTENTS: + ret = (void *) mix_scan_contents; + break; + case SET_ONETIMEEXPUNGEATPING: + if (value) ((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T; + case GET_ONETIMEEXPUNGEATPING: + if (value) ret = (void *) + (((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL); + break; + } + return ret; +} + + +/* MIX test for directory format internal node + * Accepts: candidate node name + * Returns: T if internal name, NIL otherwise + */ + +long mix_dirfmttest (char *name) +{ + /* belongs to MIX if starts with .mix */ + return strncmp (name,MIXNAME,sizeof (MIXNAME) - 1) ? NIL : LONGT; +} + +/* MIX mail scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + if (stream) dummy_scan (NIL,ref,pat,contents); +} + + +/* MIX scan mailbox for contents + * Accepts: mailbox name + * desired contents + * contents size + * file size (ignored) + * Returns: NIL if contents not found, T if found + */ + +long mix_scan_contents (char *name,char *contents,unsigned long csiz, + unsigned long fsiz) +{ + long i,nfiles; + void *a; + char *s; + long ret = NIL; + size_t namelen = strlen (name); + struct stat sbuf; + struct direct **names = NIL; + if ((nfiles = scandir (name,&names,mix_select,mix_msgfsort)) > 0) + for (i = 0; i < nfiles; ++i) { + if (!ret) { + sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2), + "%s/%s",name,names[i]->d_name); + if (!stat (s,&sbuf) && (csiz <= sbuf.st_size)) + ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size); + fs_give ((void **) &s); + } + fs_give ((void **) &names[i]); + } + /* free directory list */ + if (a = (void *) names) fs_give ((void **) &a); + return ret; +} + +/* MIX list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mix_list (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_list (NIL,ref,pat); +} + + +/* MIX list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mix_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_lsub (NIL,ref,pat); +} + +/* MIX mail subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long mix_subscribe (MAILSTREAM *stream,char *mailbox) +{ + return sm_subscribe (mailbox); +} + + +/* MIX mail unsubscribe to mailbox + * Accepts: mail stream + * mailbox to delete from subscription list + * Returns: T on success, NIL on failure + */ + +long mix_unsubscribe (MAILSTREAM *stream,char *mailbox) +{ + return sm_unsubscribe (mailbox); +} + +/* MIX mail create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long mix_create (MAILSTREAM *stream,char *mailbox) +{ + DRIVER *test; + FILE *f; + int c,i; + char *t,tmp[MAILTMPLEN],file[MAILTMPLEN]; + char *s = strrchr (mailbox,'/'); + unsigned long now = time (NIL); + long ret = NIL; + /* always create \NoSelect if trailing / */ + if (s && !s[1]) return dummy_create (stream,mailbox); + /* validate name */ + if (mix_dirfmttest (s ? s + 1 : mailbox)) + sprintf(tmp,"Can't create mailbox %.80s: invalid MIX-format name",mailbox); + /* must not already exist */ + else if ((test = mail_valid (NIL,mailbox,NIL)) && + strcmp (test->name,"dummy")) + sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox); + /* create directory and metadata */ + else if (!dummy_create_path (stream, + mix_file (file,mix_dir (tmp,mailbox),MIXMETA), + get_dir_protection (mailbox))) + sprintf (tmp,"Can't create mailbox %.80s: %.80s",mailbox,strerror (errno)); + else if (!(f = fopen (file,"w"))) + sprintf (tmp,"Can't re-open metadata %.80s: %.80s",mailbox, + strerror (errno)); + else { /* success, write initial metadata */ + fprintf (f,SEQFMT,now); + fprintf (f,MTAFMT,now,0,now); + for (i = 0, c = 'K'; (i < NUSERFLAGS) && + (t = (stream && stream->user_flags[i]) ? stream->user_flags[i] : + default_user_flag (i)) && *t; ++i) { + putc (c,f); /* write another keyword */ + fputs (t,f); + c = ' '; /* delimiter is now space */ + } + fclose (f); + set_mbx_protections (mailbox,file); + /* point to suffix */ + s = file + strlen (file) - (sizeof (MIXMETA) - 1); + strcpy (s,MIXINDEX); /* create index */ + if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) + sprintf (tmp,"Can't create mix mailbox index: %.80s",strerror (errno)); + else { + set_mbx_protections (mailbox,file); + strcpy (s,MIXSTATUS); /* create status */ + if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) + sprintf (tmp,"Can't create mix mailbox status: %.80s", + strerror (errno)); + else { + set_mbx_protections (mailbox,file); + sprintf (s,"%08lx",now);/* message file */ + if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) + sprintf (tmp,"Can't create mix mailbox data: %.80s", + strerror (errno)); + else { + set_mbx_protections (mailbox,file); + ret = LONGT; /* declare success at this point */ + } + } + } + } + if (!ret) MM_LOG (tmp,ERROR); /* some error */ + return ret; +} + +/* MIX mail delete mailbox + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long mix_delete (MAILSTREAM *stream,char *mailbox) +{ + DIR *dirp; + struct direct *d; + int fd = -1; + char *s,tmp[MAILTMPLEN]; + if (!mix_isvalid (mailbox,tmp)) + sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox); + else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB)) + sprintf (tmp,"Can't lock mailbox for delete: %.80s",mailbox); + /* delete metadata */ + else if (unlink (tmp)) sprintf (tmp,"Can't delete mailbox %.80s index: %80s", + mailbox,strerror (errno)); + else { + close (fd); /* close descriptor on deleted metadata */ + /* get directory name */ + *(s = strrchr (tmp,'/')) = '\0'; + if (dirp = opendir (tmp)) { /* open directory */ + *s++ = '/'; /* restore delimiter */ + /* massacre messages */ + while (d = readdir (dirp)) if (mix_dirfmttest (d->d_name)) { + strcpy (s,d->d_name); /* make path */ + unlink (tmp); /* sayonara */ + } + closedir (dirp); /* flush directory */ + *(s = strrchr (tmp,'/')) = '\0'; + if (rmdir (tmp)) { /* try to remove the directory */ + sprintf (tmp,"Can't delete name %.80s: %.80s", + mailbox,strerror (errno)); + MM_LOG (tmp,WARN); + } + } + return T; /* always success */ + } + if (fd >= 0) close (fd); /* close any descriptor on metadata */ + MM_LOG (tmp,ERROR); /* something failed */ + return NIL; +} + +/* MIX mail rename mailbox + * Accepts: MIX mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long mix_rename (MAILSTREAM *stream,char *old,char *newname) +{ + char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN]; + struct stat sbuf; + int fd = -1; + if (!mix_isvalid (old,tmp)) + sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old); + else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB)) + sprintf (tmp,"Can't lock mailbox for rename: %.80s",old); + else if (mix_dirfmttest ((s = strrchr (newname,'/')) ? s + 1 : newname)) + sprintf (tmp,"Can't rename to mailbox %.80s: invalid MIX-format name", + newname); + /* new mailbox name must not be valid */ + else if (mix_isvalid (newname,tmp)) + sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists", + newname); + else { + mix_dir (tmp,old); /* build old directory name */ + mix_dir (tmp1,newname); /* and new directory name */ + /* easy if not INBOX */ + if (compare_cstring (old,"INBOX")) { + /* found superior to destination name? */ + if (s = strrchr (tmp1,'/')) { + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,tmp1,get_dir_protection (newname))) + return NIL; + *s = c; /* restore full name */ + } + if (!rename (tmp,tmp1)) { + close (fd); /* close descriptor on metadata */ + return LONGT; + } + } + + /* RFC 3501 requires this */ + else if (dummy_create_path (stream,strcat (tmp1,"/"), + get_dir_protection (newname))) { + void *a; + int i,n,lasterror; + char *src,*dst; + struct direct **names = NIL; + size_t srcl = strlen (tmp); + size_t dstl = strlen (tmp1); + /* rename each mix file to new directory */ + for (i = lasterror = 0,n = scandir (tmp,&names,mix_rselect,alphasort); + i < n; ++i) { + size_t len = strlen (names[i]->d_name); + sprintf (src = (char *) fs_get (srcl + len + 2),"%s/%s", + tmp,names[i]->d_name); + sprintf (dst = (char *) fs_get (dstl + len + 1),"%s%s", + tmp1,names[i]->d_name); + if (rename (src,dst)) lasterror = errno; + fs_give ((void **) &src); + fs_give ((void **) &dst); + fs_give ((void **) &names[i]); + } + /* free directory list */ + if (a = (void *) names) fs_give ((void **) &a); + if (lasterror) errno = lasterror; + else { + close (fd); /* close descriptor on metadata */ + return mix_create (NIL,"INBOX"); + } + } + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s", + old,newname,strerror (errno)); + } + if (fd >= 0) close (fd); /* close any descriptor on metadata */ + MM_LOG (tmp,ERROR); /* something failed */ + return NIL; +} + + +/* MIX test for mix name + * Accepts: candidate directory name + * Returns: T if mix file name, NIL otherwise + */ + +int mix_rselect (struct direct *name) +{ + return mix_dirfmttest (name->d_name); +} + +/* MIX mail open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *mix_open (MAILSTREAM *stream) +{ + short silent; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return user_flags (&mixproto); + if (stream->local) fatal ("mix recycle stream"); + stream->local = memset (fs_get (sizeof (MIXLOCAL)),0,sizeof (MIXLOCAL)); + /* note if an INBOX or not */ + stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); + /* make temporary buffer */ + LOCAL->buf = (char *) fs_get (CHUNKSIZE); + LOCAL->buflen = CHUNKSIZE - 1; + /* set stream->mailbox to be directory name */ + mix_dir (LOCAL->buf,stream->mailbox); + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr (LOCAL->buf); + LOCAL->msgfd = -1; /* currently no file open */ + if (!(((!stream->rdonly && /* open metadata file */ + ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA), + O_RDWR,NIL)) >= 0)) || + ((stream->rdonly = T) && + ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA), + O_RDONLY,NIL)) >= 0))) && + !flock (LOCAL->mfd,LOCK_SH))) { + MM_LOG ("Error opening mix metadata file",ERROR); + mix_abort (stream); + stream = NIL; /* open fails */ + } + else { /* metadata open, complete open */ + LOCAL->index = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXINDEX)); + LOCAL->status = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXSTATUS)); + LOCAL->sortcache = cpystr (mix_file (LOCAL->buf,stream->mailbox, + MIXSORTCACHE)); + stream->sequence++; /* bump sequence number */ + /* parse mailbox */ + stream->nmsgs = stream->recent = 0; + if (silent = stream->silent) LOCAL->internal = T; + stream->silent = T; + if (mix_ping (stream)) { /* do initial ping */ + /* try burping in case we are exclusive */ + if (!stream->rdonly) mix_expunge (stream,"",NIL); + if (!(stream->nmsgs || stream->silent)) + MM_LOG ("Mailbox is empty",(long) NIL); + stream->silent = silent; /* now notify upper level */ + mail_exists (stream,stream->nmsgs); + stream->perm_seen = stream->perm_deleted = stream->perm_flagged = + stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T; + stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff; + stream->kwd_create = /* can we create new user flags? */ + (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? NIL : T; + } + else { /* got murdelyzed in ping */ + mix_abort (stream); + stream = NIL; + } + } + return stream; /* return stream to caller */ +} + +/* MIX mail close + * Accepts: MAIL stream + * close options + */ + +void mix_close (MAILSTREAM *stream,long options) +{ + if (LOCAL) { /* only if a file is open */ + int silent = stream->silent; + stream->silent = T; /* note this stream is dying */ + /* burp-only or expunge */ + mix_expunge (stream,(options & CL_EXPUNGE) ? NIL : "",NIL); + mix_abort (stream); + stream->silent = silent; /* reset silent state */ + } +} + + +/* MIX mail abort stream + * Accepts: MAIL stream + */ + +void mix_abort (MAILSTREAM *stream) +{ + if (LOCAL) { /* only if a file is open */ + /* close current message file if open */ + if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); + /* close current metadata file if open */ + if (LOCAL->mfd >= 0) close (LOCAL->mfd); + if (LOCAL->index) fs_give ((void **) &LOCAL->index); + if (LOCAL->status) fs_give ((void **) &LOCAL->status); + if (LOCAL->sortcache) fs_give ((void **) &LOCAL->sortcache); + /* free local scratch buffer */ + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + +/* MIX mail fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + +char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags) +{ + unsigned long i,j,k; + int fd; + char *s,tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + if (length) *length = 0; /* default return */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + elt = mail_elt (stream,msgno);/* get elt */ + /* is message in current message file? */ + if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) { + if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); + if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox, + elt->private.spare.data), + O_RDONLY,NIL)) < 0) return ""; + /* got file */ + LOCAL->curmsg = elt->private.spare.data; + } + lseek (LOCAL->msgfd,elt->private.special.offset,L_SET); + /* size of special data and header */ + j = elt->private.msg.header.offset + elt->private.msg.header.text.size; + if (j > LOCAL->buflen) { /* is buffer big enough? */ + /* no, make one that is */ + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1); + } + /* Maybe someday validate internaldate too */ + /* slurp special data + header, validate */ + if ((read (LOCAL->msgfd,LOCAL->buf,j) == j) && + !strncmp (LOCAL->buf,MSGTOK,MSGTSZ) && + (elt->private.uid == strtoul ((char *) LOCAL->buf + MSGTSZ,&s,16)) && + (*s++ == ':') && (s = strchr (s,':')) && + (k = strtoul (s+1,&s,16)) && (*s++ == ':') && + (s < (char *) (LOCAL->buf + elt->private.msg.header.offset))) { + /* won, set offset and size of message */ + i = elt->private.msg.header.offset; + *length = elt->private.msg.header.text.size; + if (k != elt->rfc822_size) { + sprintf (tmp,"Inconsistency in mix message size, uid=%lx (%lu != %lu)", + elt->private.uid,elt->rfc822_size,k); + MM_LOG (tmp,WARN); + } + } + else { /* document the problem */ + LOCAL->buf[100] = '\0'; /* tie off buffer at no more than 100 octets */ + /* or at newline, whichever is first */ + if (s = strpbrk (LOCAL->buf,"\015\012")) *s = '\0'; + sprintf (tmp,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s", + elt->private.uid,elt->rfc822_size,LOCAL->buf); + MM_LOG (tmp,ERROR); + *length = i = j = 0; /* default to empty */ + } + LOCAL->buf[j] = '\0'; /* tie off buffer at the end */ + return (char *) LOCAL->buf + i; +} + +/* MIX mail fetch message text (body only) + * Accepts: MAIL stream + * message # to fetch + * pointer to returned stringstruct + * option flags + * Returns: T on success, NIL on failure + */ + +long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + unsigned long i; + FDDATA d; + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + elt = mail_elt (stream,msgno); + /* is message in current message file? */ + if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) { + if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); + if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox, + elt->private.spare.data), + O_RDONLY,NIL)) < 0) return NIL; + /* got file */ + LOCAL->curmsg = elt->private.spare.data; + } + /* doing non-peek fetch? */ + if (!(flags & FT_PEEK) && !elt->seen) { + FILE *idxf; /* yes, process metadata/index/status */ + FILE *statf = mix_parse (stream,&idxf,NIL,LONGT); + elt->seen = T; /* mark as seen */ + MM_FLAGS (stream,elt->msgno); + /* update status file if possible */ + if (statf && !stream->rdonly) { + elt->private.mod = LOCAL->statusseq = mix_modseq (LOCAL->statusseq); + mix_status_update (stream,statf,NIL); + } + if (idxf) fclose (idxf); /* release index and status file */ + if (statf) fclose (statf); + } + d.fd = LOCAL->msgfd; /* set up file descriptor */ + /* offset of message text */ + d.pos = elt->private.special.offset + elt->private.msg.header.offset + + elt->private.msg.header.text.size; + d.chunk = LOCAL->buf; /* initial buffer chunk */ + d.chunksize = CHUNKSIZE; /* chunk size */ + INIT (bs,fd_string,&d,elt->rfc822_size - elt->private.msg.header.text.size); + return T; +} + +/* MIX mail modify flags + * Accepts: MAIL stream + * sequence + * flag(s) + * option flags + */ + +void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) +{ + MESSAGECACHE *elt; + unsigned long i,uf,ffkey; + long f; + short nf; + FILE *idxf; + FILE *statf = mix_parse (stream,&idxf,NIL,LONGT); + unsigned long seq = mix_modseq (LOCAL->statusseq); + /* find first free key */ + for (ffkey = 0; (ffkey < NUSERFLAGS) && stream->user_flags[ffkey]; ++ffkey); + /* parse sequence and flags */ + if (((flags & ST_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) && + ((f = mail_parse_flags (stream,flag,&uf)) || uf)) { + /* alter flags */ + for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) { + struct { /* old flags */ + unsigned int seen : 1; + unsigned int deleted : 1; + unsigned int flagged : 1; + unsigned int answered : 1; + unsigned int draft : 1; + unsigned long user_flags; + } old; + old.seen = elt->seen; old.deleted = elt->deleted; + old.flagged = elt->flagged; old.answered = elt->answered; + old.draft = elt->draft; old.user_flags = elt->user_flags; + if (f&fSEEN) elt->seen = nf; + if (f&fDELETED) elt->deleted = nf; + if (f&fFLAGGED) elt->flagged = nf; + if (f&fANSWERED) elt->answered = nf; + if (f&fDRAFT) elt->draft = nf; + /* user flags */ + if (flags & ST_SET) elt->user_flags |= uf; + else elt->user_flags &= ~uf; + if ((old.seen != elt->seen) || (old.deleted != elt->deleted) || + (old.flagged != elt->flagged) || + (old.answered != elt->answered) || (old.draft != elt->draft) || + (old.user_flags != elt->user_flags)) { + if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq; + MM_FLAGS (stream,elt->msgno); + } + } + /* update status file after change */ + if (statf && (seq == LOCAL->statusseq)) + mix_status_update (stream,statf,NIL); + /* update metadata if created a keyword */ + if ((ffkey < NUSERFLAGS) && stream->user_flags[ffkey] && + !mix_meta_update (stream)) + MM_LOG ("Error updating mix metadata after keyword creation",ERROR); + } + if (statf) fclose (statf); /* release status file if still open */ + if (idxf) fclose (idxf); /* release index file */ +} + +/* MIX mail sort messages + * Accepts: mail stream + * character set + * search program + * sort program + * option flags + * Returns: vector of sorted message sequences or NIL if error + */ + +unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, + SORTPGM *pgm,long flags) +{ + unsigned long *ret; + FILE *sortcache = mix_sortcache_open (stream); + ret = mail_sort_msgs (stream,charset,spg,pgm,flags); + mix_sortcache_update (stream,&sortcache); + return ret; +} + + +/* MIX mail thread messages + * Accepts: mail stream + * thread type + * character set + * search program + * option flags + * Returns: thread node tree or NIL if error + */ + +THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset, + SEARCHPGM *spg,long flags) +{ + THREADNODE *ret; + FILE *sortcache = mix_sortcache_open (stream); + ret = mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs); + mix_sortcache_update (stream,&sortcache); + return ret; +} + +/* MIX mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +static int snarfing = 0; /* lock against recursive snarfing */ + +long mix_ping (MAILSTREAM *stream) +{ + FILE *idxf,*statf; + struct stat sbuf; + STRING msg; + MESSAGECACHE *elt; + int mfd,ifd,sfd; + unsigned long i,msglen; + char *message,date[MAILTMPLEN],flags[MAILTMPLEN]; + MAILSTREAM *sysibx = NIL; + long ret = NIL; + long snarfok = LONGT; + /* time to snarf? */ + if (stream->inbox && !stream->rdonly && !snarfing && + (time (0) >= (LOCAL->lastsnarf + + (time_t) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) { + appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); + copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL); + MM_CRITICAL (stream); /* go critical */ + snarfing = T; /* don't recursively snarf */ + /* disable APPENDUID/COPYUID callbacks */ + mail_parameters (NIL,SET_APPENDUID,NIL); + mail_parameters (NIL,SET_COPYUID,NIL); + /* sizes match and anything in sysinbox? */ + if (!stat (sysinbox (),&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG) && + sbuf.st_size && (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) && + !sysibx->rdonly && sysibx->nmsgs) { + /* for each message in sysibx mailbox */ + for (i = 1; snarfok && (i <= sysibx->nmsgs); ++i) + if (!(elt = mail_elt (sysibx,i))->deleted && + (message = mail_fetch_message (sysibx,i,&msglen,FT_PEEK)) && + msglen) { + mail_date (date,elt); /* make internal date string */ + /* make flag string */ + flags[0] = flags[1] = '\0'; + if (elt->seen) strcat (flags," \\Seen"); + if (elt->flagged) strcat (flags," \\Flagged"); + if (elt->answered) strcat (flags," \\Answered"); + if (elt->draft) strcat (flags," \\Draft"); + flags[0] = '('; + strcat (flags,")"); + INIT (&msg,mail_string,message,msglen); + if (snarfok = mail_append_full (stream,"INBOX",flags,date,&msg)) { + char sequence[15]; + sprintf (sequence,"%lu",i); + mail_flag (sysibx,sequence,"\\Deleted",ST_SET); + } + } + + /* now expunge all those messages */ + if (snarfok) mail_expunge (sysibx); + else { + sprintf (LOCAL->buf,"Can't copy new mail at message: %lu",i - 1); + MM_LOG (LOCAL->buf,WARN); + } + } + if (sysibx) mail_close (sysibx); + /* reenable APPENDUID/COPYUID */ + mail_parameters (NIL,SET_APPENDUID,(void *) au); + mail_parameters (NIL,SET_COPYUID,(void *) cu); + snarfing = NIL; /* no longer snarfing */ + MM_NOCRITICAL (stream); /* release critical */ + LOCAL->lastsnarf = time (0);/* note time of last snarf */ + } + /* expunging OK if global flag set */ + if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T; + /* process metadata/index/status */ + if (statf = mix_parse (stream,&idxf,LONGT, + (LOCAL->internal ? NIL : LONGT))) { + fclose (statf); /* just close the status file */ + ret = LONGT; /* declare success */ + } + if (idxf) fclose (idxf); /* release index file */ + LOCAL->expok = NIL; /* expunge no longer OK */ + if (!ret) mix_abort (stream); /* murdelyze stream if ping fails */ + return ret; +} + + +/* MIX mail checkpoint mailbox (burp only) + * Accepts: MAIL stream + */ + +void mix_check (MAILSTREAM *stream) +{ + if (stream->rdonly) /* won't do on readonly files! */ + MM_LOG ("Checkpoint ignored on readonly mailbox",NIL); + /* do burp-only expunge action */ + if (mix_expunge (stream,"",NIL)) MM_LOG ("Check completed",(long) NIL); +} + +/* MIX mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL, empty string for burp only + * expunge options + * Returns: T on success, NIL if failure + */ + +long mix_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + FILE *idxf = NIL; + FILE *statf = NIL; + MESSAGECACHE *elt; + int ifd,sfd; + long ret; + unsigned long i; + unsigned long nexp = 0; + unsigned long reclaimed = 0; + int burponly = (sequence && !*sequence); + LOCAL->expok = T; /* expunge during ping is OK */ + if (!(ret = burponly || !sequence || + ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) || stream->rdonly); + /* read index and open status exclusive */ + else if (statf = mix_parse (stream,&idxf,LONGT, + LOCAL->internal ? NIL : LONGT)) { + /* expunge unless just burping */ + if (!burponly) for (i = 1; i <= stream->nmsgs;) { + elt = mail_elt (stream,i);/* need to expunge this message? */ + if (sequence ? elt->sequence : elt->deleted) { + ++nexp; /* yes, make it so */ + mail_expunged (stream,i); + } + else ++i; /* otherwise advance to next message */ + } + + /* burp if can get exclusive access */ + if (!flock (LOCAL->mfd,LOCK_EX|LOCK_NB)) { + void *a; + struct direct **names = NIL; + long nfiles = scandir (stream->mailbox,&names,mix_select,mix_msgfsort); + if (nfiles > 0) { /* if have message files */ + MIXBURP *burp,*cur; + /* initialize burp list */ + for (i = 0, burp = cur = NIL; i < nfiles; ++i) { + MIXBURP *nxt = (MIXBURP *) memset (fs_get (sizeof (MIXBURP)),0, + sizeof (MIXBURP)); + /* another file found */ + if (cur) cur = cur->next = nxt; + else cur = burp = nxt; + cur->name = names[i]->d_name; + cur->fileno = strtoul (cur->name + sizeof (MIXNAME) - 1,NIL,16); + cur->tail = &cur->set; + fs_give ((void **) &names[i]); + } + /* now load ranges */ + for (i = 1, cur = burp; ret && (i <= stream->nmsgs); i++) { + /* is this message in current set? */ + elt = mail_elt (stream,i); + if (cur && (elt->private.spare.data != cur->fileno)) { + /* restart if necessary */ + if (elt->private.spare.data < cur->fileno) cur = burp; + /* hunt for appropriate mailbox */ + while (cur && (elt->private.spare.data > cur->fileno)) + cur = cur->next; + /* ought to have found it now... */ + if (cur && (elt->private.spare.data != cur->fileno)) cur = NIL; + } + /* if found, add to set */ + if (cur) ret = mix_addset (&cur->tail,elt->private.special.offset, + elt->private.msg.header.offset + + elt->rfc822_size); + else { /* uh-oh */ + sprintf (LOCAL->buf,"Can't locate mix message file %.08lx", + elt->private.spare.data); + MM_LOG (LOCAL->buf,ERROR); + ret = NIL; + } + } + if (ret) /* if no errors, burp all files */ + for (cur = burp; ret && cur; cur = cur->next) { + /* if non-empty, burp it */ + if (cur->set.last) ret = mix_burp (stream,cur,&reclaimed); + /* empty, delete it unless new msg file */ + else if (mix_file_data (LOCAL->buf,stream->mailbox,cur->fileno) && + ((cur->fileno == LOCAL->newmsg) ? + truncate (LOCAL->buf,0) : unlink (LOCAL->buf))) { + sprintf (LOCAL->buf, + "Can't delete empty message file %.80s: %.80s", + cur->name,strerror (errno)); + MM_LOG (LOCAL->buf,WARN); + } + } + } + else MM_LOG ("No mix message files found during expunge",WARN); + /* free directory list */ + if (a = (void *) names) fs_give ((void **) &a); + } + + /* either way, re-acquire shared lock */ + if (flock (LOCAL->mfd,LOCK_SH|LOCK_NB)) + fatal ("Unable to re-acquire metadata shared lock!"); + /* Do this step even if ret is NIL (meaning some burp problem)! */ + if (nexp || reclaimed) { /* rewrite index and status if changed */ + LOCAL->indexseq = mix_modseq (LOCAL->indexseq); + if (mix_index_update (stream,idxf,NIL)) { + LOCAL->statusseq = mix_modseq (LOCAL->statusseq); + /* set failure if update fails */ + ret = mix_status_update (stream,statf,NIL); + } + } + } + if (statf) fclose (statf); /* close status if still open */ + if (idxf) fclose (idxf); /* close index if still open */ + LOCAL->expok = NIL; /* cancel expok */ + if (ret) { /* only if success */ + char *s = NIL; + if (nexp) sprintf (s = LOCAL->buf,"Expunged %lu messages",nexp); + else if (reclaimed) + sprintf (s=LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed); + else if (!burponly) + s = stream->rdonly ? "Expunge ignored on readonly mailbox" : + "No messages deleted, so no update needed"; + if (s) MM_LOG (s,(long) NIL); + } + return ret; +} + +/* MIX test for message file name + * Accepts: candidate directory name + * Returns: T if message file name, NIL otherwise + * + * ".mix" with no suffix was used by experimental versions + */ + +int mix_select (struct direct *name) +{ + char c,*s; + /* make sure name has prefix */ + if (mix_dirfmttest (name->d_name)) { + for (c = *(s = name->d_name + sizeof (MIXNAME) - 1); c && isxdigit (c); + c = *s++); + if (!c) return T; /* all-hex or no suffix */ + } + return NIL; /* not suffix or non-hex */ +} + + +/* MIX msg file name comparision + * Accepts: first candidate directory entry + * second candidate directory entry + * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2 + */ + +int mix_msgfsort (const void *d1,const void *d2) +{ + char *n1 = (*(struct direct **) d1)->d_name + sizeof (MIXNAME) - 1; + char *n2 = (*(struct direct **) d2)->d_name + sizeof (MIXNAME) - 1; + return compare_ulong (*n1 ? strtoul (n1,NIL,16) : 0, + *n2 ? strtoul (n2,NIL,16) : 0); +} + + +/* MIX add a range to a set + * Accepts: pointer to set to add + * start of set + * size of set + * Returns: T if success, set updated, NIL otherwise + */ + +long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size) +{ + SEARCHSET *s = *set; + if (start < s->last) { /* sanity check */ + char tmp[MAILTMPLEN]; + sprintf (tmp,"Backwards-running mix index %lu < %lu",start,s->last); + MM_LOG (tmp,ERROR); + return NIL; + } + /* range initially empty? */ + if (!s->last) s->first = start; + else if (start > s->last) /* no, start new range if can't append */ + (*set = s = s->next = mail_newsearchset ())->first = start; + s->last = start + size; /* end of current range */ + return LONGT; +} + +/* MIX burp message file + * Accepts: MAIL stream + * current burp block for this message + * Returns: T if successful, NIL if failed + */ + +static char *staterr = "Error in stat of mix message file %.80s: %.80s"; +static char *truncerr = "Error truncating mix message file %.80s: %.80s"; + +long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed) +{ + MESSAGECACHE *elt; + SEARCHSET *set; + struct stat sbuf; + off_t rpos,wpos; + size_t size,wsize,wpending,written; + int fd; + FILE *f; + void *s; + unsigned long i; + long ret = NIL; + /* build file name */ + mix_file_data (LOCAL->buf,stream->mailbox,burp->fileno); + /* need to burp at start or multiple ranges? */ + if (!burp->set.first && !burp->set.next) { + /* easy case, single range at start of file */ + if (stat (LOCAL->buf,&sbuf)) { + sprintf (LOCAL->buf,staterr,burp->name,strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + } + /* is this range sane? */ + else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) { + /* if matches range then no burp needed! */ + if (burp->set.last == sbuf.st_size) ret = LONGT; + /* just need to remove cruft at end */ + else if (ret = !truncate (LOCAL->buf,burp->set.last)) + *reclaimed += sbuf.st_size - burp->set.last; + else { + sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + } + } + } + /* have to do more work, get the file */ + else if (((fd = open (LOCAL->buf,O_RDWR,NIL)) < 0) || + !(f = fdopen (fd,"r+b"))) { + sprintf (LOCAL->buf,"Error opening mix message file %.80s: %.80s", + burp->name,strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + if (fd >= 0) close (fd); /* in case fdopen() failure */ + } + else if (fstat (fd,&sbuf)) { /* get file size */ + sprintf (LOCAL->buf,staterr,burp->name,strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + fclose (f); + } + + /* only if sane */ + else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) { + /* make sure each range starts with token */ + for (set = &burp->set; set; set = set->next) + if (fseek (f,set->first,SEEK_SET) || + (fread (LOCAL->buf,1,MSGTSZ,f) != MSGTSZ) || + strncmp (LOCAL->buf,MSGTOK,MSGTSZ)) { + sprintf (LOCAL->buf,"Bad message token in mix message file at %lu", + set->first); + MM_LOG (LOCAL->buf,ERROR); + fclose (f); + return NIL; /* burp fails for this file */ + } + /* burp out each old message */ + for (set = &burp->set, wpos = 0; set; set = set->next) { + /* move down this range */ + for (rpos = set->first, size = set->last - set->first; + size; size -= wsize) { + if (rpos != wpos) { /* data to skip at start? */ + /* no, slide this buffer down */ + wsize = min (size,LOCAL->buflen); + /* failure is not an option here */ + while (fseek (f,rpos,SEEK_SET) || + (fread (LOCAL->buf,1,wsize,f) != wsize)) { + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } + /* nor here */ + while (fseek (f,wpos,SEEK_SET)) { + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } + /* and especially not here */ + for (s = LOCAL->buf, wpending = wsize; wpending; wpending -= written) + if (!(written = fwrite (LOCAL->buf,1,wpending,f))) { + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } + } + else wsize = size; /* nothing to skip, say we wrote it all */ + rpos += wsize; wpos += wsize; + } + } + + while (fflush (f)) { /* failure also not an option here... */ + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } + if (ftruncate (fd,wpos)) { /* flush cruft at end of file */ + sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno)); + MM_LOG (LOCAL->buf,WARN); + } + else *reclaimed += rpos - wpos; + ret = !fclose (f); /* close file */ + /* slide down message positions in index */ + for (i = 1,rpos = 0; i <= stream->nmsgs; ++i) + if ((elt = mail_elt (stream,i))->private.spare.data == burp->fileno) { + elt->private.special.offset = rpos; + rpos += elt->private.msg.header.offset + elt->rfc822_size; + } + /* debugging */ + if (rpos != wpos) fatal ("burp size consistency check!"); + } + return ret; +} + + +/* MIX burp sanity check to make sure not burping off end of file + * Accepts: burp set + * file size + * file name + * Returns: T if sane, NIL if insane + */ + +long mix_burp_check (SEARCHSET *set,size_t size,char *file) +{ + do if (set->last > size) { /* sanity check */ + char tmp[MAILTMPLEN]; + sprintf (tmp,"Unexpected short mix message file %.80s %lu < %lu", + file,size,set->last); + MM_LOG (tmp,ERROR); + return NIL; /* don't burp this file at all */ + } while (set = set->next); + return LONGT; +} + +/* MIX mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if copy successful, else NIL + */ + +long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + FDDATA d; + STRING st; + char tmp[2*MAILTMPLEN]; + long ret = mix_isvalid (mailbox,LOCAL->buf); + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + MAILSTREAM *astream = NIL; + FILE *idxf = NIL; + FILE *msgf = NIL; + FILE *statf = NIL; + if (!ret) switch (errno) { /* make sure valid mailbox */ + case NIL: /* no error in stat() */ + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + break; + default: /* some stat() error */ + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); + break; + } + /* get sequence to copy */ + else if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)))); + /* acquire stream to append */ + else if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) && + !astream->rdonly && + (((MIXLOCAL *) astream->local)->expok = T) && + (statf = mix_parse (astream,&idxf,LONGT,NIL))) ? + LONGT : NIL) { + int fd; + unsigned long i; + MESSAGECACHE *elt; + unsigned long newsize,hdrsize,size; + MIXLOCAL *local = (MIXLOCAL *) astream->local; + unsigned long seq = mix_modseq (local->metaseq); + /* make sure new modseq fits */ + if (local->indexseq > seq) seq = local->indexseq + 1; + if (local->statusseq > seq) seq = local->statusseq + 1; + /* calculate size of per-message header */ + sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0); + hdrsize = strlen (local->buf); + + MM_CRITICAL (stream); /* go critical */ + astream->silent = T; /* no events here */ + /* calculate size that will be added */ + for (i = 1, newsize = 0; i <= stream->nmsgs; ++i) + if ((elt = mail_elt (stream,i))->sequence) + newsize += hdrsize + elt->rfc822_size; + /* open data file */ + if (msgf = mix_data_open (astream,&fd,&size,newsize)) { + char *t; + unsigned long j,uid,uidv; + copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL); + SEARCHSET *source = cu ? mail_newsearchset () : NIL; + SEARCHSET *dest = cu ? mail_newsearchset () : NIL; + for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); ++i) + if (((elt = mail_elt (stream,i))->sequence) && elt->rfc822_size) { + /* is message in current message file? */ + if ((LOCAL->msgfd < 0) || + (elt->private.spare.data != LOCAL->curmsg)) { + if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); + if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf, + stream->mailbox, + elt->private.spare.data), + O_RDONLY,NIL)) >= 0) + LOCAL->curmsg = elt->private.spare.data; + } + if (LOCAL->msgfd < 0) ret = NIL; + else { /* got file */ + d.fd = LOCAL->msgfd;/* set up file descriptor */ + /* start of message */ + d.pos = elt->private.special.offset + + elt->private.msg.header.offset; + d.chunk = LOCAL->buf; + d.chunksize = CHUNKSIZE; + INIT (&st,fd_string,&d,elt->rfc822_size); + /* init flag string */ + tmp[0] = tmp[1] = '\0'; + if (j = elt->user_flags) do + if ((t = stream->user_flags[find_rightmost_bit (&j)]) && *t) + strcat (strcat (tmp," "),t); + while (j); + if (elt->seen) strcat (tmp," \\Seen"); + if (elt->deleted) strcat (tmp," \\Deleted"); + if (elt->flagged) strcat (tmp," \\Flagged"); + if (elt->answered) strcat (tmp," \\Answered"); + if (elt->draft) strcat (tmp," \\Draft"); + tmp[0] = '('; /* wrap list */ + strcat (tmp,")"); + /* if append OK, add to source set */ + if ((ret = mix_append_msg (astream,msgf,tmp,elt,&st,dest, + seq)) && source) + mail_append_set (source,mail_uid (stream,i)); + } + } + + /* finish write if success */ + if (ret && (ret = !fflush (msgf))) { + fclose (msgf); /* all good, close the msg file now */ + /* write new metadata, index, and status */ + local->metaseq = local->indexseq = local->statusseq = seq; + if (ret = (mix_meta_update (astream) && + mix_index_update (astream,idxf,LONGT))) { + /* success, delete if doing a move */ + if (options & CP_MOVE) + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) { + elt->deleted = T; + if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq; + MM_FLAGS (stream,elt->msgno); + } + /* done with status file now */ + mix_status_update (astream,statf,LONGT); + /* return sets if doing COPYUID */ + if (cu) (*cu) (stream,mailbox,astream->uid_validity,source,dest); + source = dest = NIL; /* don't free these sets now */ + } + } + else { /* error */ + if (errno) { /* output error message if system call error */ + sprintf (tmp,"Message copy failed: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + } + ftruncate (fd,size); /* revert file */ + close (fd); /* make sure that fclose doesn't corrupt us */ + fclose (msgf); /* free the stdio resources */ + } + /* flush any sets remaining */ + mail_free_searchset (&source); + mail_free_searchset (&dest); + } + else { /* message file open failed */ + sprintf (tmp,"Error opening copy message file: %.80s", + strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; + } + MM_NOCRITICAL (stream); + } + else MM_LOG ("Can't open copy mailbox",ERROR); + if (statf) fclose (statf); /* close status if still open */ + if (idxf) fclose (idxf); /* close index if still open */ + /* finished with append stream */ + if (astream) mail_close (astream); + return ret; /* return state */ +} + +/* MIX mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + STRING *message; + char *flags,*date,tmp[MAILTMPLEN]; + /* N.B.: can't use LOCAL->buf for tmp */ + long ret = mix_isvalid (mailbox,tmp); + /* default stream to prototype */ + if (!stream) stream = user_flags (&mixproto); + if (!ret) switch (errno) { /* if not valid mailbox */ + case ENOENT: /* no such file? */ + if (ret = compare_cstring (mailbox,"INBOX") ? + NIL : mix_create (NIL,"INBOX")) + break; + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); + break; + default: + sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + break; + } + + /* get first message */ + if (ret && MM_APPEND (af) (stream,data,&flags,&date,&message)) { + MAILSTREAM *astream; + FILE *idxf = NIL; + FILE *msgf = NIL; + FILE *statf = NIL; + if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) && + !astream->rdonly && + (((MIXLOCAL *) astream->local)->expok = T) && + (statf = mix_parse (astream,&idxf,LONGT,NIL))) ? + LONGT : NIL) { + int fd; + unsigned long size,hdrsize; + MESSAGECACHE elt; + MIXLOCAL *local = (MIXLOCAL *) astream->local; + unsigned long seq = mix_modseq (local->metaseq); + /* make sure new modseq fits */ + if (local->indexseq > seq) seq = local->indexseq + 1; + if (local->statusseq > seq) seq = local->statusseq + 1; + /* calculate size of per-message header */ + sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0); + hdrsize = strlen (local->buf); + MM_CRITICAL (astream); /* go critical */ + astream->silent = T; /* no events here */ + /* open data file */ + if (msgf = mix_data_open (astream,&fd,&size,hdrsize + SIZE (message))) { + appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); + SEARCHSET *dst = au ? mail_newsearchset () : NIL; + while (ret && message) {/* while good to go and have messages */ + errno = NIL; /* in case one of these causes failure */ + /* guard against zero-length */ + if (!(ret = SIZE (message))) + MM_LOG ("Append of zero-length message",ERROR); + else if (date && !(ret = mail_parse_date (&elt,date))) { + sprintf (tmp,"Bad date in append: %.80s",date); + MM_LOG (tmp,ERROR); + } + else { + if (!date) { /* if date not specified, use now */ + internal_date (tmp); + mail_parse_date (&elt,tmp); + } + ret = mix_append_msg (astream,msgf,flags,&elt,message,dst,seq) && + MM_APPEND (af) (stream,data,&flags,&date,&message); + } + } + + /* finish write if success */ + if (ret && (ret = !fflush (msgf))) { + fclose (msgf); /* all good, close the msg file now */ + /* write new metadata, index, and status */ + local->metaseq = local->indexseq = local->statusseq = seq; + if ((ret = (mix_meta_update (astream) && + mix_index_update (astream,idxf,LONGT) && + mix_status_update (astream,statf,LONGT))) && au) { + (*au) (mailbox,astream->uid_validity,dst); + dst = NIL; /* don't free this set now */ + } + } + else { /* failure */ + if (errno) { /* output error message if system call error */ + sprintf (tmp,"Message append failed: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + } + ftruncate (fd,size); /* revert all writes to file*/ + close (fd); /* make sure that fclose doesn't corrupt us */ + fclose (msgf); /* free the stdio resources */ + } + /* flush any set remaining */ + mail_free_searchset (&dst); + } + else { /* message file open failed */ + sprintf (tmp,"Error opening append message file: %.80s", + strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; + } + MM_NOCRITICAL (astream); /* release critical */ + } + else MM_LOG ("Can't open append mailbox",ERROR); + if (statf) fclose (statf); /* close status if still open */ + if (idxf) fclose (idxf); /* close index if still open */ + if (astream) mail_close (astream); + } + return ret; +} + +/* MIX mail append single message + * Accepts: MAIL stream + * flags for new message if non-NIL + * elt with source date if non-NIL + * stringstruct of message text + * searchset to place UID + * modseq of message + * Returns: T if success, NIL if failure + */ + +long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt, + STRING *msg,SEARCHSET *set,unsigned long seq) +{ + MESSAGECACHE *elt; + int c,cs; + unsigned long i,j,k,uf,hoff; + long sf; + stream->kwd_create = NIL; /* don't copy unknown keywords */ + sf = mail_parse_flags (stream,flags,&uf); + /* swell the cache */ + mail_exists (stream,++stream->nmsgs); + /* assign new UID from metadata */ + (elt = mail_elt (stream,stream->nmsgs))->private.uid = ++stream->uid_last; + elt->private.mod = seq; /* set requested modseq in status */ + elt->rfc822_size = SIZE (msg);/* copy message size and date to index */ + elt->year = delt->year; elt->month = delt->month; elt->day = delt->day; + elt->hours = delt->hours; elt->minutes = delt->minutes; + elt->seconds = delt->seconds; elt->zoccident = delt->zoccident; + elt->zhours = delt->zhours; elt->zminutes = delt->zminutes; + /* + * Do NOT set elt->valid here! mix_status_update() uses it to determine + * whether a message should be marked as old. + */ + if (sf&fSEEN) elt->seen = T; /* copy flags to status */ + if (sf&fDELETED) elt->deleted = T; + if (sf&fFLAGGED) elt->flagged = T; + if (sf&fANSWERED) elt->answered = T; + if (sf&fDRAFT) elt->draft = T; + elt->user_flags |= uf; + /* message is in new message file */ + elt->private.spare.data = LOCAL->newmsg; + + /* offset to message internal header */ + elt->private.special.offset = ftell (f); + /* build header for message */ + fprintf (f,MSRFMT,MSGTOK,elt->private.uid, + elt->year + BASEYEAR,elt->month,elt->day, + elt->hours,elt->minutes,elt->seconds, + elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes, + elt->rfc822_size); + /* offset to header from internal header */ + elt->private.msg.header.offset = ftell (f) - elt->private.special.offset; + for (cs = 0; SIZE (msg); ) { /* copy message */ + if (elt->private.msg.header.text.size) { + if (msg->cursize) /* blat entire chunk if have it */ + for (j = msg->cursize; j; j -= k) + if (!(k = fwrite (msg->curpos,1,j,f))) return NIL; + SETPOS (msg,GETPOS (msg) + msg->cursize); + } + else { /* still searching for delimiter */ + c = 0xff & SNX (msg); /* get source character */ + if (putc (c,f) == EOF) return NIL; + switch (cs) { /* decide what to do based on state */ + case 0: /* previous char ordinary */ + if (c == '\015') cs = 1;/* advance if CR */ + break; + case 1: /* previous CR, advance if LF */ + cs = (c == '\012') ? 2 : 0; + break; + case 2: /* previous CRLF, advance if CR */ + cs = (c == '\015') ? 3 : 0; + break; + case 3: /* previous CRLFCR, done if LF */ + if (c == '\012') elt->private.msg.header.text.size = + elt->rfc822_size - SIZE (msg); + cs = 0; /* reset mechanism */ + break; + } + } + } + /* if no delimiter, header is entire msg */ + if (!elt->private.msg.header.text.size) + elt->private.msg.header.text.size = elt->rfc822_size; + /* add this message to set */ + mail_append_set (set,elt->private.uid); + return LONGT; /* success */ +} + +/* MIX mail read metadata, index, and status + * Accepts: MAIL stream + * returned index file + * index file flags (non-NIL if want to add/remove messages) + * status file flags (non-NIL if want to update elt->valid and old) + * Returns: open status file, or NIL if failure + * + * Note that this routine can return an open index file even if it fails! + */ + +static char *shortmsg = + "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)"; + +FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags) +{ + int fd; + unsigned long i; + char *s,*t; + struct stat sbuf; + FILE *statf = NIL; + short metarepairneeded = 0; + short indexrepairneeded = 0; + short silent = stream->silent; + *idxf = NIL; /* in case error */ + /* readonly means no updates */ + if (stream->rdonly) iflags = sflags = NIL; + /* open index file */ + if ((fd = open (LOCAL->index,iflags ? O_RDWR : O_RDONLY,NIL)) < 0) + MM_LOG ("Error opening mix index file",ERROR); + /* acquire exclusive access and FILE */ + else if (!flock (fd,iflags ? LOCK_EX : LOCK_SH) && + !(*idxf = fdopen (fd,iflags ? "r+b" : "rb"))) { + MM_LOG ("Error obtaining stream on mix index file",ERROR); + flock (fd,LOCK_UN); /* relinquish lock */ + close (fd); + } + + /* slurp metadata */ + else if (s = mix_meta_slurp (stream,&i)) { + unsigned long j = 0; /* non-zero if UIDVALIDITY/UIDLAST changed */ + if (i != LOCAL->metaseq) { /* metadata changed? */ + char *t,*k; + LOCAL->metaseq = i; /* note new metadata sequence */ + while (s && *s) { /* parse entire metadata file */ + /* locate end of line */ + if (s = strstr (t = s,"\015\012")) { + *s = '\0'; /* tie off line */ + s += 2; /* skip past CRLF */ + switch (*t++) { /* parse line */ + case 'V': /* UIDVALIDITY */ + if (!isxdigit (*t) || !(i = strtoul (t,&t,16))) { + MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR); + return NIL; /* give up */ + } + if (i != stream->uid_validity) j = stream->uid_validity = i; + break; + case 'L': /* new UIDLAST */ + if (!isxdigit (*t)) { + MM_LOG ("Error in mix metadata file UIDLAST record",ERROR); + return NIL; /* give up */ + } + if ((i = strtoul (t,&t,16)) != stream->uid_last) + j = stream->uid_last = i; + break; + case 'N': /* new message file */ + if (!isxdigit (*t)) { + MM_LOG ("Error in mix metadata file new msg record",ERROR); + return NIL; /* give up */ + } + if ((i = strtoul (t,&t,16)) != stream->uid_last) + LOCAL->newmsg = i; + break; + case 'K': /* new keyword list */ + for (i = 0; t && *t && (i < NUSERFLAGS); ++i) { + if (t = strchr (k = t,' ')) *t++ = '\0'; + /* make sure keyword non-empty */ + if (*k && (strlen (k) <= MAXUSERFLAG)) { + /* in case value changes (shouldn't happen) */ + if (stream->user_flags[i] && strcmp (stream->user_flags[i],k)){ + char tmp[MAILTMPLEN]; + sprintf (tmp,"flag rename old=%.80s new=%.80s", + stream->user_flags[i],k); + MM_LOG (tmp,WARN); + fs_give ((void **) &stream->user_flags[i]); + } + if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (k); + } + else break; /* empty keyword */ + } + if ((i < NUSERFLAGS) && stream->user_flags[i]) { + MM_LOG ("Error in mix metadata file keyword record",ERROR); + return NIL; /* give up */ + } + else if (i == NUSERFLAGS) stream->kwd_create = NIL; + break; + } + } + if (t && *t) { /* junk in line */ + MM_LOG ("Error in mix metadata record",ERROR); + return NIL; /* give up */ + } + } + } + + /* get sequence */ + if (!(i = mix_read_sequence (*idxf)) || (i < LOCAL->indexseq)) { + MM_LOG ("Error in mix index file sequence record",ERROR); + return NIL; /* give up */ + } + /* sequence changed from last time? */ + else if (j || (i > LOCAL->indexseq)) { + unsigned long uid,nmsgs,curfile,curfilesize,curpos; + char *t,*msg,tmp[MAILTMPLEN]; + /* start with no messages */ + curfile = curfilesize = curpos = nmsgs = 0; + /* update sequence iff expunging OK */ + if (LOCAL->expok) LOCAL->indexseq = i; + /* get first elt */ + while ((s = mix_read_record (*idxf,LOCAL->buf,LOCAL->buflen,"index")) && + *s) + switch (*s) { + case ':': /* message record */ + if (!(isxdigit (*++s) && (uid = strtoul (s,&t,16)))) msg = "UID"; + else if (!((*t++ == ':') && isdigit (*t) && isdigit (t[1]) && + isdigit (t[2]) && isdigit (t[3]) && isdigit (t[4]) && + isdigit (t[5]) && isdigit (t[6]) && isdigit (t[7]) && + isdigit (t[8]) && isdigit (t[9]) && isdigit (t[10]) && + isdigit (t[11]) && isdigit (t[12]) && isdigit (t[13]) && + ((t[14] == '+') || (t[14] == '-')) && + isdigit (t[15]) && isdigit (t[16]) && isdigit (t[17]) && + isdigit (t[18]))) msg = "internaldate"; + else if ((*(s = t+19) != ':') || !isxdigit (*++s)) msg = "size"; + else { + unsigned int y = (((*t - '0') * 1000) + ((t[1] - '0') * 100) + + ((t[2] - '0') * 10) + t[3] - '0') - BASEYEAR; + unsigned int m = ((t[4] - '0') * 10) + t[5] - '0'; + unsigned int d = ((t[6] - '0') * 10) + t[7] - '0'; + unsigned int hh = ((t[8] - '0') * 10) + t[9] - '0'; + unsigned int mm = ((t[10] - '0') * 10) + t[11] - '0'; + unsigned int ss = ((t[12] - '0') * 10) + t[13] - '0'; + unsigned int z = (t[14] == '-') ? 1 : 0; + unsigned int zh = ((t[15] - '0') * 10) + t[16] - '0'; + unsigned int zm = ((t[17] - '0') * 10) + t[18] - '0'; + unsigned long size = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + unsigned long file = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + unsigned long pos = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + unsigned long hpos = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + unsigned long hsiz = strtoul (s,&s,16); + if (uid > stream->uid_last) { + sprintf (tmp,"mix index invalid UID (%08lx < %08lx)", + uid,stream->uid_last); + if (stream->rdonly) { + MM_LOG (tmp,ERROR); + return NIL; + } + strcat (tmp,", repaired"); + MM_LOG (tmp,WARN); + stream->uid_last = uid; + metarepairneeded = T; + } + + /* ignore expansion values */ + if (*s++ == ':') { + MESSAGECACHE *elt; + ++nmsgs; /* this is another mesage */ + /* within current known range of messages? */ + while (nmsgs <= stream->nmsgs) { + /* yes, get corresponding elt */ + elt = mail_elt (stream,nmsgs); + /* existing message with matching data? */ + if (uid == elt->private.uid) { + /* beware of Dracula's resurrection */ + if (elt->private.ghost) { + sprintf (tmp,"mix index data unexpunged UID: %lx", + uid); + MM_LOG (tmp,ERROR); + return NIL; + } + /* also of static data changing */ + if ((size != elt->rfc822_size) || + (file != elt->private.spare.data) || + (pos != elt->private.special.offset) || + (hpos != elt->private.msg.header.offset) || + (hsiz != elt->private.msg.header.text.size) || + (y != elt->year) || (m != elt->month) || + (d != elt->day) || (hh != elt->hours) || + (mm != elt->minutes) || (ss != elt->seconds) || + (z != elt->zoccident) || (zh != elt->zhours) || + (zm != elt->zminutes)) { + sprintf (tmp,"mix index data mismatch: %lx",uid); + MM_LOG (tmp,ERROR); + return NIL; + } + break; + } + /* existing msg with lower UID is expunged */ + else if (uid > elt->private.uid) { + if (LOCAL->expok) mail_expunged (stream,nmsgs); + else {/* message expunged, but not yet for us */ + ++nmsgs; + elt->private.ghost = T; + } + } + else { /* unexpected message record */ + sprintf (tmp,"mix index UID mismatch (%lx < %lx)", + uid,elt->private.uid); + MM_LOG (tmp,ERROR); + return NIL; + } + } + + /* time to create a new message? */ + if (nmsgs > stream->nmsgs) { + /* defer announcing until later */ + stream->silent = T; + mail_exists (stream,nmsgs); + stream->silent = silent; + (elt = mail_elt (stream,nmsgs))->recent = T; + elt->private.uid = uid; elt->rfc822_size = size; + elt->private.spare.data = file; + elt->private.special.offset = pos; + elt->private.msg.header.offset = hpos; + elt->private.msg.header.text.size = hsiz; + elt->year = y; elt->month = m; elt->day = d; + elt->hours = hh; elt->minutes = mm; + elt->seconds = ss; elt->zoccident = z; + elt->zhours = zh; elt->zminutes = zm; + /* message in same file? */ + if (curfile == file) { + if (pos < curpos) { + MESSAGECACHE *plt = mail_elt (stream,elt->msgno-1); + /* uh-oh, calculate delta? */ + i = curpos - pos; + sprintf (tmp,shortmsg,plt->msgno,plt->private.uid, + i,pos,curpos); + /* possible to fix? */ + if (!stream->rdonly && LOCAL->expok && + (i < plt->rfc822_size)) { + plt->rfc822_size -= i; + if (plt->rfc822_size < + plt->private.msg.header.text.size) + plt->private.msg.header.text.size = + plt->rfc822_size; + strcat (tmp,", repaired"); + indexrepairneeded = T; + } + MM_LOG (tmp,WARN); + } + } + else { /* new file, restart */ + if (stat (mix_file_data (LOCAL->buf,stream->mailbox, + curfile = file),&sbuf)) { + sprintf (tmp,"Missing mix data file: %.500s", + LOCAL->buf); + MM_LOG (tmp,ERROR); + return NIL; + } + curfile = file; + curfilesize = sbuf.st_size; + } + + /* position of message in file */ + curpos = pos + elt->private.msg.header.offset + + elt->rfc822_size; + /* short file? */ + if (curfilesize < curpos) { + /* uh-oh, calculate delta? */ + i = curpos - curfilesize; + sprintf (tmp,shortmsg,elt->msgno,elt->private.uid, + i,curfilesize,curpos); + /* possible to fix? */ + if (!stream->rdonly && LOCAL->expok && + (i < elt->rfc822_size)) { + elt->rfc822_size -= i; + if (elt->rfc822_size < + elt->private.msg.header.text.size) + elt->private.msg.header.text.size = + elt->rfc822_size; + strcat (tmp,", repaired"); + indexrepairneeded = T; + } + MM_LOG (tmp,WARN); + } + } + break; + } + else msg = "expansion"; + } + else msg = "header size"; + } + else msg = "header position"; + } + else msg = "message position"; + } + else msg = "file#"; + } + sprintf (tmp,"Error in %s in mix index file: %.500s",msg,s); + MM_LOG (tmp,ERROR); + return NIL; + default: + sprintf (tmp,"Unknown record in mix index file: %.500s",s); + MM_LOG (tmp,ERROR); + return NIL; + } + if (!s) return NIL; /* barfage from mix_read_record() */ + /* expunge trailing messages not in index */ + if (LOCAL->expok) while (nmsgs < stream->nmsgs) + mail_expunged (stream,stream->nmsgs); + } + + /* repair metadata and index if needed */ + if ((metarepairneeded ? mix_meta_update (stream) : T) && + (indexrepairneeded ? mix_index_update (stream,*idxf,NIL) : T)) { + MESSAGECACHE *elt; + int fd; + unsigned long uid,uf,sf,mod; + char *s; + int updatep = NIL; + /* open status file */ + if ((fd = open (LOCAL->status, + stream->rdonly ? O_RDONLY : O_RDWR,NIL)) < 0) + MM_LOG ("Error opening mix status file",ERROR); + /* acquire exclusive access and FILE */ + else if (!flock (fd,stream->rdonly ? LOCK_SH : LOCK_EX) && + !(statf = fdopen (fd,stream->rdonly ? "rb" : "r+b"))) { + MM_LOG ("Error obtaining stream on mix status file",ERROR); + flock (fd,LOCK_UN); /* relinquish lock */ + close (fd); + } + /* get sequence */ + else if (!(i = mix_read_sequence (statf)) || + ((i < LOCAL->statusseq) && stream->nmsgs && (i != 1))) { + sprintf (LOCAL->buf, + "Error in mix status sequence record, i=%lx, seq=%lx", + i,LOCAL->statusseq); + MM_LOG (LOCAL->buf,ERROR); + } + /* sequence changed from last time? */ + else if (i != LOCAL->statusseq) { + /* update sequence, get first elt */ + if (i > LOCAL->statusseq) LOCAL->statusseq = i; + if (stream->nmsgs) { + elt = mail_elt (stream,i = 1); + + /* read message records */ + while ((t = s = mix_read_record (statf,LOCAL->buf,LOCAL->buflen, + "status")) && *s && (*s++ == ':') && + isxdigit (*s)) { + uid = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + uf = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + sf = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + mod = strtoul (s,&s,16); + /* ignore expansion values */ + if (*s++ == ':') { + /* need to move ahead to next elt? */ + while ((uid > elt->private.uid) && (i < stream->nmsgs)) + elt = mail_elt (stream,++i); + /* update elt if altered */ + if ((uid == elt->private.uid) && + (!elt->valid || (mod != elt->private.mod))) { + elt->user_flags = uf; + elt->private.mod = mod; + elt->seen = (sf & fSEEN) ? T : NIL; + elt->deleted = (sf & fDELETED) ? T : NIL; + elt->flagged = (sf & fFLAGGED) ? T : NIL; + elt->answered = (sf & fANSWERED) ? T : NIL; + elt->draft = (sf & fDRAFT) ? T : NIL; + /* announce if altered existing message */ + if (elt->valid) MM_FLAGS (stream,elt->msgno); + /* first time, is old message? */ + else if (sf & fOLD) { + /* yes, clear recent and set valid */ + elt->recent = NIL; + elt->valid = T; + } + /* recent, allowed to update its status? */ + else if (sflags) { + /* yes, set valid and check in status */ + elt->valid = T; + elt->private.mod = mix_modseq (elt->private.mod); + updatep = T; + } + /* leave valid unset and recent if sflags not set */ + } + continue; /* everything looks good */ + } + } + } + } + break; /* error somewhere */ + } + + if (t && *t) { /* non-null means bogus record */ + char msg[MAILTMPLEN]; + sprintf (msg,"Error in mix status file message record%s: %.80s", + stream->rdonly ? "" : ", fixing",t); + MM_LOG (msg,WARN); + /* update it if not readonly */ + if (!stream->rdonly) updatep = T; + } + if (updatep) { /* need to update? */ + LOCAL->statusseq = mix_modseq (LOCAL->statusseq); + mix_status_update (stream,statf,LONGT); + } + } + } + } + } + if (statf) { /* still happy? */ + unsigned long j; + stream->silent = silent; /* now notify upper level */ + mail_exists (stream,stream->nmsgs); + for (i = 1, j = 0; i <= stream->nmsgs; ++i) + if (mail_elt (stream,i)->recent) ++j; + mail_recent (stream,j); + } + return statf; +} + +/* MIX metadata file routines */ + +/* MIX read metadata + * Accepts: MAIL stream + * return pointer for modseq + * Returns: pointer to metadata after modseq or NIL if failure + */ + +char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq) +{ + struct stat sbuf; + char *s; + char *ret = NIL; + if (fstat (LOCAL->mfd,&sbuf)) + MM_LOG ("Error obtaining size of mix metatdata file",ERROR); + if (sbuf.st_size > LOCAL->buflen) { + /* should be just a few dozen bytes */ + if (sbuf.st_size > METAMAX) fatal ("absurd mix metadata file size"); + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1); + } + /* read current metadata file */ + LOCAL->buf[sbuf.st_size] = '\0'; + if (lseek (LOCAL->mfd,0,L_SET) || + (read (LOCAL->mfd,s = LOCAL->buf,sbuf.st_size) != sbuf.st_size)) + MM_LOG ("Error reading mix metadata file",ERROR); + else if ((*s != 'S') || !isxdigit (s[1]) || + ((*seq = strtoul (s+1,&s,16)) < LOCAL->metaseq) || + (*s++ != '\015') || (*s++ != '\012')) + MM_LOG ("Error in mix metadata file sequence record",ERROR); + else ret = s; + return ret; +} + +/* MIX update metadata + * Accepts: MAIL stream + * Returns: T on success, NIL if error + * + * Index MUST be locked!! + */ + +long mix_meta_update (MAILSTREAM *stream) +{ + long ret; + /* do nothing if stream readonly */ + if (stream->rdonly) ret = LONGT; + else { + unsigned char c,*s,*ss,*t; + unsigned long i; + /* The worst-case metadata is limited to: + * 4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1)) + * which comes out to 1994 octets. This is much smaller than the normal + * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of + * LOCAL->buf. + * + * If more stuff gets added to the metadata, or if you change the value + * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the + * above assertation. + */ + sprintf (LOCAL->buf,SEQFMT,LOCAL->metaseq = mix_modseq (LOCAL->metaseq)); + sprintf (LOCAL->buf + strlen (LOCAL->buf),MTAFMT, + stream->uid_validity,stream->uid_last,LOCAL->newmsg); + for (i = 0, c = 'K', s = ss = LOCAL->buf + strlen (LOCAL->buf); + (i < NUSERFLAGS) && (t = stream->user_flags[i]); ++i) { + if (!*t) fatal ("impossible empty keyword"); + *s++ = c; /* write delimiter */ + while (*t) *s++ = *t++; /* write keyword */ + c = ' '; /* delimiter is now space */ + } + if (s != ss) { /* tie off keywords line */ + *s++ = '\015'; *s++ = '\012'; + } + /* calculate length of metadata */ + if ((i = s - LOCAL->buf) > LOCAL->buflen) + fatal ("impossible buffer overflow"); + lseek (LOCAL->mfd,0,L_SET); /* rewind file */ + /* write new metadata */ + ret = (write (LOCAL->mfd,LOCAL->buf,i) == i) ? LONGT : NIL; + ftruncate (LOCAL->mfd,i); /* and tie off at that point */ + } + return ret; +} + +/* MIX index file routines */ + + +/* MIX update index + * Accepts: MAIL stream + * open FILE + * expansion check flag + * Returns: T on success, NIL if error + */ + +long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag) +{ + unsigned long i; + long ret = LONGT; + if (!stream->rdonly) { /* do nothing if stream readonly */ + if (flag) { /* need to do expansion check? */ + char tmp[MAILTMPLEN]; + size_t size; + struct stat sbuf; + /* calculate file size we need */ + for (i = 1, size = 0; i <= stream->nmsgs; ++i) + if (!mail_elt (stream,i)->private.ghost) ++size; + if (size) { /* Winston Smith's first dairy entry */ + sprintf (tmp,IXRFMT,0,14,4,4,13,0,0,'+',0,0,0,0,0,0,0); + size *= strlen (tmp); + } + /* calculate file size we need */ + sprintf (tmp,SEQFMT,LOCAL->indexseq); + size += strlen (tmp); + /* get current file size */ + if (fstat (fileno (idxf),&sbuf)) { + MM_LOG ("Error getting size of mix index file",ERROR); + ret = NIL; + } + /* need to write additional space? */ + else if (sbuf.st_size < size) { + void *buf = fs_get (size -= sbuf.st_size); + memset (buf,0,size); + if (fseek (idxf,0,SEEK_END) || (fwrite (buf,1,size,idxf) != size) || + fflush (idxf)) { + fseek (idxf,sbuf.st_size,SEEK_SET); + ftruncate (fileno (idxf),sbuf.st_size); + MM_LOG ("Error extending mix index file",ERROR); + ret = NIL; + } + fs_give ((void **) &buf); + } + } + + if (ret) { /* if still good to go */ + rewind (idxf); /* let's start at the very beginning */ + /* write modseq first */ + fprintf (idxf,SEQFMT,LOCAL->indexseq); + /* then write all messages */ + for (i = 1; ret && (i <= stream->nmsgs); i++) { + MESSAGECACHE *elt = mail_elt (stream,i); + if (!elt->private.ghost)/* only write living messages */ + fprintf (idxf,IXRFMT,elt->private.uid, + elt->year + BASEYEAR,elt->month,elt->day, + elt->hours,elt->minutes,elt->seconds, + elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes, + elt->rfc822_size,elt->private.spare.data, + elt->private.special.offset, + elt->private.msg.header.offset, + elt->private.msg.header.text.size); + if (ferror (idxf)) { + MM_LOG ("Error updating mix index file",ERROR); + ret = NIL; + } + } + if (fflush (idxf)) { + MM_LOG ("Error flushing mix index file",ERROR); + ret = NIL; + } + if (ret) ftruncate (fileno (idxf),ftell (idxf)); + } + } + return ret; +} + +/* MIX status file routines */ + + +/* MIX update status + * Accepts: MAIL stream + * pointer to open FILE + * expansion check flag + * Returns: T on success, NIL if error + */ + +long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag) +{ + unsigned long i; + char tmp[MAILTMPLEN]; + long ret = LONGT; + if (!stream->rdonly) { /* do nothing if stream readonly */ + if (flag) { /* need to do expansion check? */ + char tmp[MAILTMPLEN]; + size_t size; + struct stat sbuf; + /* calculate file size we need */ + for (i = 1, size = 0; i <= stream->nmsgs; ++i) + if (!mail_elt (stream,i)->private.ghost) ++size; + if (size) { /* number of living messages */ + sprintf (tmp,STRFMT,0,0,0,0); + size *= strlen (tmp); + } + sprintf (tmp,SEQFMT,LOCAL->statusseq); + size += strlen (tmp); + /* get current file size */ + if (fstat (fileno (statf),&sbuf)) { + MM_LOG ("Error getting size of mix status file",ERROR); + ret = NIL; + } + /* need to write additional space? */ + else if (sbuf.st_size < size) { + void *buf = fs_get (size -= sbuf.st_size); + memset (buf,0,size); + if (fseek (statf,0,SEEK_END) || (fwrite (buf,1,size,statf) != size) || + fflush (statf)) { + fseek (statf,sbuf.st_size,SEEK_SET); + ftruncate (fileno (statf),sbuf.st_size); + MM_LOG ("Error extending mix status file",ERROR); + ret = NIL; + } + fs_give ((void **) &buf); + } + } + + if (ret) { /* if still good to go */ + rewind (statf); /* let's start at the very beginning */ + /* write sequence */ + fprintf (statf,SEQFMT,LOCAL->statusseq); + /* write message status records */ + for (i = 1; ret && (i <= stream->nmsgs); ++i) { + MESSAGECACHE *elt = mail_elt (stream,i); + /* make sure all messages have a modseq */ + if (!elt->private.mod) elt->private.mod = LOCAL->statusseq; + if (!elt->private.ghost)/* only write living messages */ + fprintf (statf,STRFMT,elt->private.uid,elt->user_flags, + (fSEEN * elt->seen) + (fDELETED * elt->deleted) + + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + + (fDRAFT * elt->draft) + (elt->valid ? fOLD : NIL), + elt->private.mod); + if (ferror (statf)) { + sprintf (tmp,"Error updating mix status file: %.80s", + strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; + } + } + if (ret && fflush (statf)) { + MM_LOG ("Error flushing mix status file",ERROR); + ret = NIL; + } + if (ret) ftruncate (fileno (statf),ftell (statf)); + } + } + return ret; +} + +/* MIX data file routines */ + + +/* MIX open data file + * Accepts: MAIL stream + * pointer to returned fd if success + * pointer to returned size if success + * size of new data to be added + * Returns: open FILE, or NIL if failure + * + * The curend test assumes that the last message of the mailbox is the furthest + * point that the current data file extends, and thus that is all that needs to + * be tested for short file prevention. + */ + +FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size, + unsigned long newsize) +{ + FILE *msgf = NIL; + struct stat sbuf; + MESSAGECACHE *elt = stream->nmsgs ? mail_elt (stream,stream->nmsgs) : NIL; + unsigned long curend = (elt && (elt->private.spare.data == LOCAL->newmsg)) ? + elt->private.special.offset + elt->private.msg.header.offset + + elt->rfc822_size : 0; + /* allow create if curend 0 */ + if ((*fd = open (mix_file_data (LOCAL->buf,stream->mailbox,LOCAL->newmsg), + O_RDWR | (curend ? NIL : O_CREAT),NIL)) >= 0) { + fstat (*fd,&sbuf); /* get current file size */ + /* can we use this file? */ + if ((curend <= sbuf.st_size) && + (!sbuf.st_size || ((sbuf.st_size + newsize) <= MIXDATAROLL))) + *size = sbuf.st_size; /* yes, return current size */ + else { /* short file or becoming too long */ + if (curend > sbuf.st_size) { + char tmp[MAILTMPLEN]; + sprintf (tmp,"short mix message file %.08lx (%ld > %ld), rolling", + LOCAL->newmsg,curend,sbuf.st_size); + MM_LOG (tmp,WARN); /* shouldn't happen */ + } + close (*fd); /* roll to a new file */ + while ((*fd = open (mix_file_data + (LOCAL->buf,stream->mailbox, + LOCAL->newmsg = mix_modseq (LOCAL->newmsg)), + O_RDWR | O_CREAT | O_EXCL,sbuf.st_mode)) < 0); + *size = 0; /* brand new file */ + fchmod (*fd,sbuf.st_mode);/* with same mode as previous file */ + } + } + if (*fd >= 0) { /* have a data file? */ + /* yes, get stdio and set position */ + if (msgf = fdopen (*fd,"r+b")) fseek (msgf,*size,SEEK_SET); + else close (*fd); /* fdopen() failed? */ + } + return msgf; /* return results */ +} + +/* MIX open sortcache + * Accepts: MAIL stream + * Returns: open FILE, or NIL if failure or could only get readonly sortcache + */ + +FILE *mix_sortcache_open (MAILSTREAM *stream) +{ + int fd,refwd; + unsigned long i,uid,sentdate,fromlen,tolen,cclen,subjlen,msgidlen,reflen; + char *s,*t,*msg,tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + SORTCACHE *sc; + STRINGLIST *sl; + struct stat sbuf; + int rdonly = NIL; + FILE *srtcf = NIL; + mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); + fstat (LOCAL->mfd,&sbuf); + if (!stream->nmsgs); /* do nothing if mailbox empty */ + /* open sortcache file */ + else if (((fd = open (LOCAL->sortcache,O_RDWR|O_CREAT,sbuf.st_mode)) < 0) && + !(rdonly = ((fd = open (LOCAL->sortcache,O_RDONLY,NIL)) >= 0))) + MM_LOG ("Error opening mix sortcache file",WARN); + /* acquire lock and FILE */ + else if (!flock (fd,rdonly ? LOCK_SH : LOCK_EX) && + !(srtcf = fdopen (fd,rdonly ? "rb" : "r+b"))) { + MM_LOG ("Error obtaining stream on mix sortcache file",WARN); + flock (fd,LOCK_UN); /* relinquish lock */ + close (fd); + } + else if (!(i = mix_read_sequence (srtcf)) || (i < LOCAL->sortcacheseq)) + MM_LOG ("Error in mix sortcache file sequence record",WARN); + /* sequence changed from last time? */ + else if (i > LOCAL->sortcacheseq) { + LOCAL->sortcacheseq = i; /* update sequence */ + while ((s = t = mix_read_record (srtcf,LOCAL->buf,LOCAL->buflen, + "sortcache")) && *s && + (msg = "uid") && (*s++ == ':') && isxdigit (*s)) { + uid = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + sentdate = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + fromlen = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + tolen = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + cclen = strtoul (s,&s,16); + if ((*s++ == ':') && ((*s == 'R') || (*s == ' ')) && + isxdigit (s[1])) { + refwd = (*s++ == 'R') ? T : NIL; + subjlen = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + msgidlen = strtoul (s,&s,16); + if ((*s++ == ':') && isxdigit (*s)) { + reflen = strtoul (s,&s,16); + /* ignore expansion values */ + if (*s++ == ':') { + + if (i = mail_msgno (stream,uid)) { + sc = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE); + sc->size = (elt = mail_elt (stream,i))->rfc822_size; + sc->date = sentdate; + sc->arrival = elt->day ? mail_longdate (elt) : 1; + if (refwd) sc->refwd = T; + if (fromlen) { + if (sc->from) fseek (srtcf,fromlen + 2,SEEK_CUR); + else if ((getc (srtcf) != 'F') || + (fread (sc->from = (char *) fs_get(fromlen), + 1,fromlen-1,srtcf) != (fromlen-1))|| + (sc->from[fromlen-1] = '\0') || + (getc (srtcf) != '\015') || + (getc (srtcf) != '\012')) { + msg = "from data"; + break; + } + } + if (tolen) { + if (sc->to) fseek (srtcf,tolen + 2,SEEK_CUR); + else if ((getc (srtcf) != 'T') || + (fread (sc->to = (char *) fs_get (tolen), + 1,tolen-1,srtcf) != (tolen - 1)) || + (sc->to[tolen-1] = '\0') || + (getc (srtcf) != '\015') || + (getc (srtcf) != '\012')) { + msg = "to data"; + break; + } + } + if (cclen) { + if (sc->cc) fseek (srtcf,cclen + 2,SEEK_CUR); + else if ((getc (srtcf) != 'C') || + (fread (sc->cc = (char *) fs_get (cclen), + 1,cclen-1,srtcf) != (cclen - 1)) || + (sc->cc[cclen-1] = '\0') || + (getc (srtcf) != '\015') || + (getc (srtcf) != '\012')) { + msg = "cc data"; + break; + } + } + if (subjlen) { + if (sc->subject) fseek (srtcf,subjlen + 2,SEEK_CUR); + else if ((getc (srtcf) != 'S') || + (fread (sc->subject = + (char *) fs_get (subjlen),1, + subjlen-1,srtcf) != (subjlen-1))|| + (sc->subject[subjlen-1] = '\0') || + (getc (srtcf) != '\015') || + (getc (srtcf) != '\012')) { + msg = "subject data"; + break; + } + } + + if (msgidlen) { + if (sc->message_id) + fseek (srtcf,msgidlen + 2,SEEK_CUR); + else if ((getc (srtcf) != 'M') || + (fread (sc->message_id = + (char *) fs_get (msgidlen),1, + msgidlen-1,srtcf) != (msgidlen-1))|| + (sc->message_id[msgidlen-1] = '\0') || + (getc (srtcf) != '\015') || + (getc (srtcf) != '\012')) { + msg = "message-id data"; + break; + } + } + if (reflen) { + if (sc->references) fseek(srtcf,reflen + 2,SEEK_CUR); + /* make sure it fits */ + else { + if (reflen >= LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) + fs_get ((LOCAL->buflen = reflen) + 1); + } + if ((getc (srtcf) != 'R') || + (fread (LOCAL->buf,1,reflen-1,srtcf) != + (reflen - 1)) || + (LOCAL->buf[reflen-1] = '\0') || + (getc (srtcf) != '\015') || + (getc (srtcf) != '\012')) { + msg = "references data"; + break; + } + for (s = LOCAL->buf,sl = NIL, + sc->references = mail_newstringlist (); + s && *s; s += i + 1) { + if ((i = strtoul (s,&s,16)) && (*s++ == ':') && + (s[i] == ':')) { + if (sl) sl = sl->next = mail_newstringlist(); + else sl = sc->references; + s[i] = '\0'; + sl->text.data = cpystr (s); + sl->text.size = i; + } + else s = NIL; + } + if (!s || *s || + (s != ((char *) LOCAL->buf + reflen - 1))) { + msg = "references length consistency check"; + break; + } + } + } + } + + /* UID not found, ignore this message */ + else fseek (srtcf,((fromlen ? fromlen + 2 : 0) + + (tolen ? tolen + 2 : 0) + + (cclen ? cclen + 2 : 0) + + (subjlen ? subjlen + 2 : 0) + + (msgidlen ? msgidlen + 2 : 0) + + (reflen ? reflen + 2 : 0)), + SEEK_CUR); + continue; + } + else msg = "expansion"; + } + else msg = "references"; + } + else msg = "message-id"; + } + else msg = "subject"; + } + else msg = "cc"; + } + else msg = "to"; + } + else msg = "from"; + } + else msg = "sentdate"; + break; /* error somewhere */ + } + if (!t || *t) { /* error detected? */ + if (t) { /* non-null means bogus record */ + sprintf (tmp,"Error in %s in mix sortcache record: %.500s",msg,t); + MM_LOG (tmp,WARN); + } + fclose (srtcf); /* either way, must punt */ + srtcf = NIL; + } + } + if (rdonly && srtcf) { /* can't update if readonly */ + unlink (LOCAL->sortcache); /* try deleting it */ + fclose (srtcf); /* so close it and return as if error */ + srtcf = NIL; + } + else fchmod (fd,sbuf.st_mode); + return srtcf; +} + +/* MIX update and close sortcache + * Accepts: MAIL stream + * pointer to open FILE (if FILE is NIL, do nothing) + * Returns: T on success, NIL on error + */ + +long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache) +{ + FILE *f = *sortcache; + long ret = LONGT; + if (f) { /* ignore if no file */ + unsigned long i,j; + mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); + for (i = 1; (i <= stream->nmsgs) && + !((SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE))->dirty; ++i); + if (i <= stream->nmsgs) { /* only update if some entry is dirty */ + rewind (f); /* let's start at the very beginning */ + /* write sequence */ + fprintf (f,SEQFMT,LOCAL->sortcacheseq = mix_modseq(LOCAL->sortcacheseq)); + for (i = 1; ret && (i <= stream->nmsgs); ++i) { + MESSAGECACHE *elt = mail_elt (stream,i); + SORTCACHE *s = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE); + STRINGLIST *sl; + s->dirty = NIL; /* no longer dirty */ + if (sl = s->references) /* count length of references */ + for (j = 1; sl && sl->text.data; sl = sl->next) + j += 10 + sl->text.size; + else j = 0; /* no references yet */ + fprintf (f,SCRFMT,elt->private.uid,s->date, + s->from ? strlen (s->from) + 1 : 0, + s->to ? strlen (s->to) + 1 : 0,s->cc ? strlen (s->cc) + 1 : 0, + s->refwd ? 'R' : ' ',s->subject ? strlen (s->subject) + 1: 0, + s->message_id ? strlen (s->message_id) + 1 : 0,j); + if (s->from) fprintf (f,"F%s\015\012",s->from); + if (s->to) fprintf (f,"T%s\015\012",s->to); + if (s->cc) fprintf (f,"C%s\015\012",s->cc); + if (s->subject) fprintf (f,"S%s\015\012",s->subject); + if (s->message_id) fprintf (f,"M%s\015\012",s->message_id); + if (j) { /* any references to write? */ + fputc ('R',f); /* yes, do so */ + for (sl = s->references; sl && sl->text.data; sl = sl->next) + fprintf (f,"%08lx:%s:",sl->text.size,sl->text.data); + fputs ("\015\012",f); + } + if (ferror (f)) { + MM_LOG ("Error updating mix sortcache file",WARN); + ret = NIL; + } + } + if (ret && fflush (f)) { + MM_LOG ("Error flushing mix sortcache file",WARN); + ret = NIL; + } + if (ret) ftruncate (fileno (f),ftell (f)); + } + if (fclose (f)) { + MM_LOG ("Error closing mix sortcache file",WARN); + ret = NIL; + } + } + return ret; +} + +/* MIX generic file routines */ + +/* MIX read record + * Accepts: open FILE + * buffer + * buffer length + * record type + * Returns: buffer if success, else NIL (zero-length buffer means EOF) + */ + +char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type) +{ + char *s,tmp[MAILTMPLEN]; + /* ensure string tied off */ + buf[buflen-2] = buf[buflen-1] = '\0'; + while (fgets (buf,buflen-1,f)) { + if (s = strchr (buf,'\012')) { + if ((s != buf) && (s[-1] == '\015')) --s; + *s = '\0'; /* tie off buffer */ + if (s != buf) return buf; /* return if non-empty buffer */ + sprintf (tmp,"Empty mix %s record",type); + MM_LOG (tmp,WARN); + } + else if (buf[buflen-2]) { /* overlong record is bad news */ + sprintf (tmp,"Oversize mix %s record: %.512s",type,buf); + MM_LOG (tmp,ERROR); + return NIL; + } + else { + sprintf (tmp,"Truncated mix %s record: %.512s",type,buf); + MM_LOG (tmp,WARN); + return buf; /* pass to caller anyway */ + } + } + buf[0] = '\0'; /* return empty buffer on EOF */ + return buf; +} + +/* MIX read sequence record + * Accepts: open FILE + * Returns: sequence value, or NIL if failure + */ + +unsigned long mix_read_sequence (FILE *f) +{ + unsigned long ret; + char *s,tmp[MAILTMPLEN]; + if (!mix_read_record (f,tmp,MAILTMPLEN-1,"sequence")) return NIL; + switch (tmp[0]) { /* examine record */ + case '\0': /* end of file */ + ret = 1; /* start a new sequence regime */ + break; + case 'S': /* sequence record */ + if (isxdigit (tmp[1])) { /* must be followed by hex value */ + ret = strtoul (tmp+1,&s,16); + if (!*s) break; /* and nothing more */ + } + /* drop into default case */ + default: /* anything else is an error */ + return NIL; /* return error */ + } + return ret; +} + +/* MIX internal routines */ + + +/* MIX mail build directory name + * Accepts: destination string + * source + * Returns: destination or empty string if error + */ + +char *mix_dir (char *dst,char *name) +{ + char *s; + /* empty string if mailboxfile fails */ + if (!mailboxfile (dst,name)) *dst = '\0'; + /* driver-selected INBOX */ + else if (!*dst) mailboxfile (dst,"~/INBOX"); + /* tie off unnecessary trailing / */ + else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0'; + return dst; +} + + +/* MIX mail build file name + * Accepts: destination string + * directory name + * file name + * Returns: destination + */ + +char *mix_file (char *dst,char *dir,char *name) +{ + sprintf (dst,"%.500s/%.80s%.80s",dir,MIXNAME,name); + return dst; +} + + +/* MIX mail build file name from data file number + * Accepts: destination string + * directory name + * data file number + * Returns: destination + */ + +char *mix_file_data (char *dst,char *dir,unsigned long data) +{ + char tmp[MAILTMPLEN]; + if (data) sprintf (tmp,"%08lx",data); + else tmp[0] = '\0'; /* compatibility with experimental version */ + return mix_file (dst,dir,tmp); +} + +/* MIX mail get new modseq + * Accepts: old modseq + * Returns: new modseq value + */ + +unsigned long mix_modseq (unsigned long oldseq) +{ + /* normally time now */ + unsigned long ret = (unsigned long) time (NIL); + /* ensure that modseq doesn't go backwards */ + if (ret <= oldseq) ret = oldseq + 1; + return ret; +} diff --git a/imap/src/osdep/amiga/mkauths b/imap/src/osdep/amiga/mkauths new file mode 100755 index 00000000..8e9cc0cf --- /dev/null +++ b/imap/src/osdep/amiga/mkauths @@ -0,0 +1,40 @@ +#!/bin/sh +# ======================================================================== +# Copyright 1988-2006 University of Washington +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# +# ======================================================================== + +# Program: Authenticator Linkage Generator +# +# Author: Mark Crispin +# Networks and Distributed Computing +# Computing & Communications +# University of Washington +# Administration Building, AG-44 +# Seattle, WA 98195 +# Internet: MRC@CAC.Washington.EDU +# +# Date: 5 December 1995 +# Last Edited: 30 August 2006 + +# Erase old authenticators list +rm -f auths.c +touch auths.c + +# Now define the new list +for authenticator + do + if [ -f Makefile."$authenticator" ]; then + make -f Makefile."$authenticator" `cat SPECIALS` + fi + echo "extern AUTHENTICATOR auth_"$authenticator";" >> linkage.h + echo " auth_link (&auth_"$authenticator"); /* link in the $authenticator authenticator */" | cat >> linkage.c + echo "#include \"auth_"$authenticator".c\"" >> auths.c +done diff --git a/imap/src/osdep/amiga/mmdf.c b/imap/src/osdep/amiga/mmdf.c new file mode 100644 index 00000000..e962434e --- /dev/null +++ b/imap/src/osdep/amiga/mmdf.c @@ -0,0 +1,2549 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: MMDF mail routines + * + * Author: Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@Washington.EDU + * + * Date: 20 December 1989 + * Last Edited: 27 March 2008 + */ + + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include <signal.h> +#include "mail.h" +#include "osdep.h" +#include <time.h> +#include <sys/stat.h> +#include "pseudo.h" +#include "fdstring.h" +#include "misc.h" +#include "dummy.h" + +/* Supposedly, this page has everything the MMDF driver needs to know about + * the MMDF delimiter. By changing these macros, the MMDF driver should + * change with it. Note that if you change the length of MMDFHDRTXT you + * also need to change the ISMMDF and RETIFMMDFWRD macros to reflect the new + * size. + */ + + +/* Useful MMDF constants */ + +#define MMDFCHR '\01' /* MMDF character */ +#define MMDFCHRS 0x01010101 /* MMDF header character spread in a word */ + /* MMDF header text */ +#define MMDFHDRTXT "\01\01\01\01\n" + /* length of MMDF header text */ +#define MMDFHDRLEN (sizeof (MMDFHDRTXT) - 1) + + +/* Validate MMDF header + * Accepts: pointer to candidate string to validate as an MMDF header + * Returns: T if valid; else NIL + */ + +#define ISMMDF(s) \ + ((*(s) == MMDFCHR) && ((s)[1] == MMDFCHR) && ((s)[2] == MMDFCHR) && \ + ((s)[3] == MMDFCHR) && ((s)[4] == '\n')) + + +/* Return if a 32-bit word has the start of an MMDF header + * Accepts: pointer to word of four bytes to validate as an MMDF header + * Returns: pointer to MMDF header, else proceeds + */ + +#define RETIFMMDFWRD(s) { \ + if (s[3] == MMDFCHR) { \ + if ((s[4] == MMDFCHR) && (s[5] == MMDFCHR) && (s[6] == MMDFCHR) && \ + (s[7] == '\n')) return s + 3; \ + else if (s[2] == MMDFCHR) { \ + if ((s[4] == MMDFCHR) && (s[5] == MMDFCHR) && (s[6] == '\n')) \ + return s + 2; \ + else if (s[1] == MMDFCHR) { \ + if ((s[4] == MMDFCHR) && (s[5] == '\n')) return s + 1; \ + else if ((*s == MMDFCHR) && (s[4] == '\n')) return s; \ + } \ + } \ + } \ +} + +/* Validate line + * Accepts: pointer to candidate string to validate as a From header + * return pointer to end of date/time field + * return pointer to offset from t of time (hours of ``mmm dd hh:mm'') + * return pointer to offset from t of time zone (if non-zero) + * Returns: t,ti,zn set if valid From string, else ti is NIL + */ + +#define VALID(s,x,ti,zn) { \ + ti = 0; \ + if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \ + (s[4] == ' ')) { \ + for (x = s + 5; *x && *x != '\n'; x++); \ + if (*x) { \ + if (x - s >= 41) { \ + for (zn = -1; x[zn] != ' '; zn--); \ + if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \ + (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \ + (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \ + (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\ + x += zn - 12; \ + } \ + if (x - s >= 27) { \ + if (x[-5] == ' ') { \ + if (x[-8] == ':') zn = 0,ti = -5; \ + else if (x[-9] == ' ') ti = zn = -9; \ + else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \ + ti = zn = -11; \ + } \ + else if (x[-4] == ' ') { \ + if (x[-9] == ' ') zn = -4,ti = -9; \ + } \ + else if (x[-6] == ' ') { \ + if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \ + zn = -6,ti = -11; \ + } \ + if (ti && !((x[ti - 3] == ':') && \ + (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \ + (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \ + (x[ti - 11] == ' '))) ti = 0; \ + } \ + } \ + } \ +} + +/* You are not expected to understand this macro, but read the next page if + * you are not faint of heart. + * + * Known formats to the VALID macro are: + * From user Wed Dec 2 05:53 1992 + * BSD From user Wed Dec 2 05:53:22 1992 + * SysV From user Wed Dec 2 05:53 PST 1992 + * rn From user Wed Dec 2 05:53:22 PST 1992 + * From user Wed Dec 2 05:53 -0700 1992 + * emacs From user Wed Dec 2 05:53:22 -0700 1992 + * From user Wed Dec 2 05:53 1992 PST + * From user Wed Dec 2 05:53:22 1992 PST + * From user Wed Dec 2 05:53 1992 -0700 + * Solaris From user Wed Dec 2 05:53:22 1992 -0700 + * + * Plus all of the above with `` remote from xxx'' after it. Thank you very + * much, smail and Solaris, for making my life considerably more complicated. + */ + +/* + * What? You want to understand the VALID macro anyway? Alright, since you + * insist. Actually, it isn't really all that difficult, provided that you + * take it step by step. + * + * Line 1 Initializes the return ti value to failure (0); + * Lines 2-3 Validates that the 1st-5th characters are ``From ''. + * Lines 4-5 Validates that there is an end of line and points x at it. + * Lines 6-13 First checks to see if the line is at least 41 characters long. + * If so, it scans backwards to find the rightmost space. From + * that point, it scans backwards to see if the string matches + * `` remote from''. If so, it sets x to point to the space at + * the start of the string. + * Line 14 Makes sure that there are at least 27 characters in the line. + * Lines 15-20 Checks if the date/time ends with the year (there is a space + * five characters back). If there is a colon three characters + * further back, there is no timezone field, so zn is set to 0 + * and ti is set in front of the year. Otherwise, there must + * either to be a space four characters back for a three-letter + * timezone, or a space six characters back followed by a + or - + * for a numeric timezone; in either case, zn and ti become the + * offset of the space immediately before it. + * Lines 21-23 Are the failure case for line 14. If there is a space four + * characters back, it is a three-letter timezone; there must be a + * space for the year nine characters back. zn is the zone + * offset; ti is the offset of the space. + * Lines 24-27 Are the failure case for line 20. If there is a space six + * characters back, it is a numeric timezone; there must be a + * space eleven characters back and a + or - five characters back. + * zn is the zone offset; ti is the offset of the space. + * Line 28-31 If ti is valid, make sure that the string before ti is of the + * form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise + * invalidate ti. There must be a colon three characters back + * and a space six or nine characters back (depending upon + * whether or not the character six characters back is a colon). + * There must be a space three characters further back (in front + * of the day), one seven characters back (in front of the month), + * and one eleven characters back (in front of the day of week). + * ti is set to be the offset of the space before the time. + * + * Why a macro? It gets invoked a *lot* in a tight loop. On some of the + * newer pipelined machines it is faster being open-coded than it would be if + * subroutines are called. + * + * Why does it scan backwards from the end of the line, instead of doing the + * much easier forward scan? There is no deterministic way to parse the + * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to + * see if unquoted spaces were possible. They are, and I've encountered enough + * evil mail to be totally unwilling to trust that ``it will never happen''. + */ + +/* Build parameters */ + +#define KODRETRY 15 /* kiss-of-death retry in seconds */ +#define LOCKTIMEOUT 5 /* lock timeout in minutes */ + + +/* MMDF I/O stream local data */ + +typedef struct mmdf_local { + unsigned int dirty : 1; /* disk copy needs updating */ + unsigned int ddirty : 1; /* double-dirty, ping becomes checkpoint */ + unsigned int pseudo : 1; /* uses a pseudo message */ + unsigned int appending : 1; /* don't mark new messages as old */ + int fd; /* mailbox file descriptor */ + int ld; /* lock file descriptor */ + char *lname; /* lock file name */ + off_t filesize; /* file size parsed */ + time_t filetime; /* last file time */ + unsigned char *buf; /* temporary buffer */ + unsigned long buflen; /* current size of temporary buffer */ + unsigned long uid; /* current text uid */ + SIZEDTEXT text; /* current text */ + unsigned long textlen; /* current text length */ + char *line; /* returned line */ + char *linebuf; /* line readin buffer */ + unsigned long linebuflen; /* current line readin buffer length */ +} MMDFLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((MMDFLOCAL *) stream->local) + + +/* MMDF protected file structure */ + +typedef struct mmdf_file { + MAILSTREAM *stream; /* current stream */ + off_t curpos; /* current file position */ + off_t protect; /* protected position */ + off_t filepos; /* current last written file position */ + char *buf; /* overflow buffer */ + size_t buflen; /* current overflow buffer length */ + char *bufpos; /* current buffer position */ +} MMDFFILE; + +/* Function prototypes */ + +DRIVER *mmdf_valid (char *name); +long mmdf_isvalid (char *name,char *tmp); +long mmdf_isvalid_fd (int fd,char *tmp); +void *mmdf_parameters (long function,void *value); +void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void mmdf_list (MAILSTREAM *stream,char *ref,char *pat); +void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat); +long mmdf_create (MAILSTREAM *stream,char *mailbox); +long mmdf_delete (MAILSTREAM *stream,char *mailbox); +long mmdf_rename (MAILSTREAM *stream,char *old,char *newname); +MAILSTREAM *mmdf_open (MAILSTREAM *stream); +void mmdf_close (MAILSTREAM *stream,long options); +char *mmdf_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags); +long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, + unsigned long *length,long flags); +void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); +long mmdf_ping (MAILSTREAM *stream); +void mmdf_check (MAILSTREAM *stream); +long mmdf_expunge (MAILSTREAM *stream,char *sequence,long options); +long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long mmdf_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); +int mmdf_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date, + STRING *msg); +int mmdf_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set); + +void mmdf_abort (MAILSTREAM *stream); +char *mmdf_file (char *dst,char *name); +int mmdf_lock (char *file,int flags,int mode,DOTLOCK *lock,int op); +void mmdf_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock); +int mmdf_parse (MAILSTREAM *stream,DOTLOCK *lock,int op); +char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size); +unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr); +unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt, + unsigned long uid,long flag); +long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock, + long flags); +long mmdf_extend (MAILSTREAM *stream,unsigned long size); +void mmdf_write (MMDFFILE *f,char *s,unsigned long i); +void mmdf_phys_write (MMDFFILE *f,char *buf,size_t size); + +/* MMDF mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER mmdfdriver = { + "mmdf", /* driver name */ + /* driver flags */ + DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY|DR_XPOINT, + (DRIVER *) NIL, /* next driver */ + mmdf_valid, /* mailbox is valid for us */ + mmdf_parameters, /* manipulate parameters */ + mmdf_scan, /* scan mailboxes */ + mmdf_list, /* list mailboxes */ + mmdf_lsub, /* list subscribed mailboxes */ + NIL, /* subscribe to mailbox */ + NIL, /* unsubscribe from mailbox */ + mmdf_create, /* create mailbox */ + mmdf_delete, /* delete mailbox */ + mmdf_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + mmdf_open, /* open mailbox */ + mmdf_close, /* close mailbox */ + NIL, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + mmdf_header, /* fetch message header */ + mmdf_text, /* fetch message text */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + NIL, /* modify flags */ + mmdf_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + mmdf_ping, /* ping mailbox to see if still alive */ + mmdf_check, /* check for new messages */ + mmdf_expunge, /* expunge deleted messages */ + mmdf_copy, /* copy messages to another mailbox */ + mmdf_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM mmdfproto = {&mmdfdriver}; + +char *mmdfhdr = MMDFHDRTXT; /* MMDF header */ + +/* MMDF mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *mmdf_valid (char *name) +{ + char tmp[MAILTMPLEN]; + return mmdf_isvalid (name,tmp) ? &mmdfdriver : NIL; +} + + +/* MMDF mail test for valid mailbox name + * Accepts: mailbox name + * scratch buffer + * Returns: T if valid, NIL otherwise + */ + +long mmdf_isvalid (char *name,char *tmp) +{ + int fd; + int ret = NIL; + char *t,file[MAILTMPLEN]; + struct stat sbuf; + time_t tp[2]; + errno = EINVAL; /* assume invalid argument */ + /* must be non-empty file */ + if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) { + if (!sbuf.st_size)errno = 0;/* empty file */ + else if ((fd = open (file,O_RDONLY,NIL)) >= 0) { + /* error -1 for invalid format */ + if (!(ret = mmdf_isvalid_fd (fd,tmp))) errno = -1; + close (fd); /* close the file */ + /* \Marked status? */ + if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) { + tp[0] = sbuf.st_atime; /* preserve atime and mtime */ + tp[1] = sbuf.st_mtime; + utime (file,tp); /* set the times */ + } + } + } + return ret; /* return what we should */ +} + +/* MMDF mail test for valid mailbox + * Accepts: file descriptor + * scratch buffer + * Returns: T if valid, NIL otherwise + */ + +long mmdf_isvalid_fd (int fd,char *tmp) +{ + int ret = NIL; + memset (tmp,'\0',MAILTMPLEN); + if (read (fd,tmp,MAILTMPLEN-1) >= 0) ret = ISMMDF (tmp) ? T : NIL; + return ret; /* return what we should */ +} + + +/* MMDF manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *mmdf_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = dummy_file ((char *) value,"INBOX"); + break; + } + return ret; +} + +/* MMDF mail scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + if (stream) dummy_scan (NIL,ref,pat,contents); +} + + +/* MMDF mail list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mmdf_list (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_list (NIL,ref,pat); +} + + +/* MMDF mail list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_lsub (NIL,ref,pat); +} + +/* MMDF mail create mailbox + * Accepts: MAIL stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long mmdf_create (MAILSTREAM *stream,char *mailbox) +{ + char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN]; + long ret = NIL; + int i,fd; + time_t ti = time (0); + if (!(s = dummy_file (mbx,mailbox))) { + sprintf (tmp,"Can't create %.80s: invalid name",mailbox); + MM_LOG (tmp,ERROR); + } + /* create underlying file */ + else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) { + /* done if dir-only or whiner */ + if (((s = strrchr (s,'/')) && !s[1]) || + mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T; + else if ((fd = open (mbx,O_WRONLY, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) { + sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno)); + MM_LOG (tmp,ERROR); + unlink (mbx); /* delete the file */ + } + else { /* initialize header */ + memset (tmp,'\0',MAILTMPLEN); + sprintf (tmp,"%sFrom %s %sDate: ",mmdfhdr,pseudo_from,ctime (&ti)); + rfc822_date (s = tmp + strlen (tmp)); + sprintf (s += strlen (s), /* write the pseudo-header */ + "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000", + pseudo_name,pseudo_from,mylocalhost (),pseudo_subject, + (unsigned long) ti); + for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i)) + sprintf (s += strlen (s)," %s",default_user_flag (i)); + sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr); + if (write (fd,tmp,strlen (tmp)) > 0) ret = T; + else { + sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx, + strerror (errno)); + MM_LOG (tmp,ERROR); + unlink (mbx); /* delete the file */ + } + close (fd); /* close file */ + } + } + /* set proper protections */ + return ret ? set_mbx_protections (mailbox,mbx) : NIL; +} + +/* MMDF mail delete mailbox + * Accepts: MAIL stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long mmdf_delete (MAILSTREAM *stream,char *mailbox) +{ + return mmdf_rename (stream,mailbox,NIL); +} + + +/* MMDF mail rename mailbox + * Accepts: MAIL stream + * old mailbox name + * new mailbox name (or NIL for delete) + * Returns: T on success, NIL on failure + */ + +long mmdf_rename (MAILSTREAM *stream,char *old,char *newname) +{ + long ret = NIL; + char c,*s = NIL; + char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN]; + DOTLOCK lockx; + int fd,ld; + long i; + struct stat sbuf; + MM_CRITICAL (stream); /* get the c-client lock */ + if (!dummy_file (file,old) || + (newname && (!((s = mailboxfile (tmp,newname)) && *s) || + ((s = strrchr (tmp,'/')) && !s[1])))) + sprintf (tmp,newname ? + "Can't rename mailbox %.80s to %.80s: invalid name" : + "Can't delete mailbox %.80s: invalid name", + old,newname); + /* lock out other c-clients */ + else if ((ld = lockname (lock,file,LOCK_EX|LOCK_NB,&i)) < 0) + sprintf (tmp,"Mailbox %.80s is in use by another process",old); + + else { + if ((fd = mmdf_lock (file,O_RDWR, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL), + &lockx,LOCK_EX)) < 0) + sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno)); + else { + if (newname) { /* want rename? */ + /* found superior to destination name? */ + if (s = strrchr (s,'/')) { + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,tmp,get_dir_protection (newname))) { + mmdf_unlock (fd,NIL,&lockx); + mmdf_unlock (ld,NIL,NIL); + unlink (lock); + MM_NOCRITICAL (stream); + return ret; /* return success or failure */ + } + *s = c; /* restore full name */ + } + if (rename (file,tmp)) + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname, + strerror (errno)); + else ret = T; /* set success */ + } + else if (unlink (file)) + sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno)); + else ret = T; /* set success */ + mmdf_unlock (fd,NIL,&lockx); + } + mmdf_unlock (ld,NIL,NIL); /* flush the lock */ + unlink (lock); + } + MM_NOCRITICAL (stream); /* no longer critical */ + if (!ret) MM_LOG (tmp,ERROR); /* log error */ + return ret; /* return success or failure */ +} + +/* MMDF mail open + * Accepts: Stream to open + * Returns: Stream on success, NIL on failure + */ + +MAILSTREAM *mmdf_open (MAILSTREAM *stream) +{ + long i; + int fd; + char tmp[MAILTMPLEN]; + DOTLOCK lock; + long retry; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return user_flags (&mmdfproto); + retry = stream->silent ? 1 : KODRETRY; + if (stream->local) fatal ("mmdf recycle stream"); + stream->local = memset (fs_get (sizeof (MMDFLOCAL)),0,sizeof (MMDFLOCAL)); + /* note if an INBOX or not */ + stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); + /* canonicalize the stream mailbox name */ + if (!dummy_file (tmp,stream->mailbox)) { + sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + /* flush old name */ + fs_give ((void **) &stream->mailbox); + /* save canonical name */ + stream->mailbox = cpystr (tmp); + LOCAL->fd = LOCAL->ld = -1; /* no file or state locking yet */ + LOCAL->buf = (char *) fs_get (CHUNKSIZE); + LOCAL->buflen = CHUNKSIZE - 1; + LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE); + LOCAL->text.size = CHUNKSIZE - 1; + LOCAL->linebuf = (char *) fs_get (CHUNKSIZE); + LOCAL->linebuflen = CHUNKSIZE - 1; + stream->sequence++; /* bump sequence number */ + + /* make lock for read/write access */ + if (!stream->rdonly) while (retry) { + /* try to lock file */ + if ((fd = lockname (tmp,stream->mailbox,LOCK_EX|LOCK_NB,&i)) < 0) { + /* suppressing kiss-of-death? */ + if (stream->nokod) retry = 0; + /* no, first time through? */ + else if (retry-- == KODRETRY) { + /* learned other guy's PID and can signal? */ + if (i && !kill ((int) i,SIGUSR2)) { + sprintf (tmp,"Trying to get mailbox lock from process %ld",i); + MM_LOG (tmp,WARN); + } + else retry = 0; /* give up */ + } + if (!stream->silent) { /* nothing if silent stream */ + if (retry) sleep (1); /* wait a second before trying again */ + else MM_LOG ("Mailbox is open by another process, access is readonly", + WARN); + } + } + else { /* got the lock, nobody else can alter state */ + LOCAL->ld = fd; /* note lock's fd and name */ + LOCAL->lname = cpystr (tmp); + /* make sure mode OK (don't use fchmod()) */ + chmod (LOCAL->lname,(long) mail_parameters (NIL,GET_LOCKPROTECTION,NIL)); + if (stream->silent) i = 0;/* silent streams won't accept KOD */ + else { /* note our PID in the lock */ + sprintf (tmp,"%d",getpid ()); + write (fd,tmp,(i = strlen (tmp))+1); + } + ftruncate (fd,i); /* make sure tied off */ + fsync (fd); /* make sure it's available */ + retry = 0; /* no more need to try */ + } + } + + /* parse mailbox */ + stream->nmsgs = stream->recent = 0; + /* will we be able to get write access? */ + if ((LOCAL->ld >= 0) && access (stream->mailbox,W_OK) && (errno == EACCES)) { + MM_LOG ("Can't get write access to mailbox, access is readonly",WARN); + flock (LOCAL->ld,LOCK_UN); /* release the lock */ + close (LOCAL->ld); /* close the lock file */ + LOCAL->ld = -1; /* no more lock fd */ + unlink (LOCAL->lname); /* delete it */ + } + /* reset UID validity */ + stream->uid_validity = stream->uid_last = 0; + if (stream->silent && !stream->rdonly && (LOCAL->ld < 0)) + mmdf_abort (stream); /* abort if can't get RW silent stream */ + /* parse mailbox */ + else if (mmdf_parse (stream,&lock,LOCK_SH)) { + mmdf_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); + MM_NOCRITICAL (stream); /* done with critical */ + } + if (!LOCAL) return NIL; /* failure if stream died */ + /* make sure upper level knows readonly */ + stream->rdonly = (LOCAL->ld < 0); + /* notify about empty mailbox */ + if (!(stream->nmsgs || stream->silent)) MM_LOG ("Mailbox is empty",NIL); + if (!stream->rdonly) { /* flags stick if readwrite */ + stream->perm_seen = stream->perm_deleted = + stream->perm_flagged = stream->perm_answered = stream->perm_draft = T; + if (!stream->uid_nosticky) {/* users with lives get permanent keywords */ + stream->perm_user_flags = 0xffffffff; + /* and maybe can create them too! */ + stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T; + } + } + return stream; /* return stream alive to caller */ +} + + +/* MMDF mail close + * Accepts: MAIL stream + * close options + */ + +void mmdf_close (MAILSTREAM *stream,long options) +{ + int silent = stream->silent; + stream->silent = T; /* go silent */ + /* expunge if requested */ + if (options & CL_EXPUNGE) mmdf_expunge (stream,NIL,NIL); + /* else dump final checkpoint */ + else if (LOCAL->dirty) mmdf_check (stream); + stream->silent = silent; /* restore old silence state */ + mmdf_abort (stream); /* now punt the file and local data */ +} + +/* MMDF mail fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + + /* lines to filter from header */ +static STRINGLIST *mmdf_hlines = NIL; + +char *mmdf_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags) +{ + MESSAGECACHE *elt; + unsigned char *s,*t,*tl; + *length = 0; /* default to empty */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + elt = mail_elt (stream,msgno);/* get cache */ + if (!mmdf_hlines) { /* once only code */ + STRINGLIST *lines = mmdf_hlines = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "Status")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-Status")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-Keywords")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-UID")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-IMAP")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-IMAPbase")); + } + /* go to header position */ + lseek (LOCAL->fd,elt->private.special.offset + + elt->private.msg.header.offset,L_SET); + + if (flags & FT_INTERNAL) { /* initial data OK? */ + if (elt->private.msg.header.text.size > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = + elt->private.msg.header.text.size) + 1); + } + /* read message */ + read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size); + /* got text, tie off string */ + LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0'; + /* squeeze out CRs (in case from PC) */ + for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++) + if (*t != '\r') *s++ = *t; + *s = '\0'; + *length = s - LOCAL->buf; /* adjust length */ + } + else { /* need to make a CRLF version */ + read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1), + elt->private.msg.header.text.size); + /* tie off string, and convert to CRLF */ + s[elt->private.msg.header.text.size] = '\0'; + *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s, + elt->private.msg.header.text.size); + fs_give ((void **) &s); /* free readin buffer */ + /* squeeze out spurious CRs */ + for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++) + if ((*t != '\r') || (t[1] == '\n')) *s++ = *t; + *s = '\0'; + *length = s - LOCAL->buf; /* adjust length */ + } + *length = mail_filter (LOCAL->buf,*length,mmdf_hlines,FT_NOT); + return (char *) LOCAL->buf; /* return processed copy */ +} + +/* MMDF mail fetch message text + * Accepts: MAIL stream + * message # to fetch + * pointer to returned stringstruct + * option flags + * Returns: T on success, NIL if failure + */ + +long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + char *s; + unsigned long i; + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + elt = mail_elt (stream,msgno);/* get cache element */ + /* if message not seen */ + if (!(flags & FT_PEEK) && !elt->seen) { + /* mark message seen and dirty */ + elt->seen = elt->private.dirty = LOCAL->dirty = T; + MM_FLAGS (stream,msgno); + } + s = mmdf_text_work (stream,elt,&i,flags); + INIT (bs,mail_string,s,i); /* set up stringstruct */ + return T; /* success */ +} + +/* MMDF mail fetch message text worker routine + * Accepts: MAIL stream + * message cache element + * pointer to returned header text length + * option flags + */ + +char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, + unsigned long *length,long flags) +{ + FDDATA d; + STRING bs; + unsigned char c,*s,*t,*tl,tmp[CHUNKSIZE]; + /* go to text position */ + lseek (LOCAL->fd,elt->private.special.offset + + elt->private.msg.text.offset,L_SET); + if (flags & FT_INTERNAL) { /* initial data OK? */ + if (elt->private.msg.text.text.size > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = + elt->private.msg.text.text.size) + 1); + } + /* read message */ + read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size); + /* got text, tie off string */ + LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0'; + /* squeeze out CRs (in case from PC) */ + for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++) + if (*t != '\r') *s++ = *t; + *s = '\0'; + *length = s - LOCAL->buf; /* adjust length */ + return (char *) LOCAL->buf; + } + + /* have it cached already? */ + if (elt->private.uid != LOCAL->uid) { + /* not cached, cache it now */ + LOCAL->uid = elt->private.uid; + /* is buffer big enough? */ + if (elt->rfc822_size > LOCAL->text.size) { + /* excessively conservative, but the right thing is too hard to do */ + fs_give ((void **) &LOCAL->text.data); + LOCAL->text.data = (unsigned char *) + fs_get ((LOCAL->text.size = elt->rfc822_size) + 1); + } + d.fd = LOCAL->fd; /* yes, set up file descriptor */ + d.pos = elt->private.special.offset + elt->private.msg.text.offset; + d.chunk = tmp; /* initial buffer chunk */ + d.chunksize = CHUNKSIZE; /* file chunk size */ + INIT (&bs,fd_string,&d,elt->private.msg.text.text.size); + for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) { + case '\r': /* carriage return seen */ + break; + case '\n': + *s++ = '\r'; /* insert a CR */ + default: + *s++ = c; /* copy characters */ + } + *s = '\0'; /* tie off buffer */ + /* calculate length of cached data */ + LOCAL->textlen = s - LOCAL->text.data; + } + *length = LOCAL->textlen; /* return from cache */ + return (char *) LOCAL->text.data; +} + +/* MMDF per-message modify flag + * Accepts: MAIL stream + * message cache element + */ + +void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + /* only after finishing */ + if (elt->valid) elt->private.dirty = LOCAL->dirty = T; +} + + +/* MMDF mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +long mmdf_ping (MAILSTREAM *stream) +{ + DOTLOCK lock; + struct stat sbuf; + long reparse; + /* big no-op if not readwrite */ + if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) { + if (stream->rdonly) { /* does he want to give up readwrite? */ + /* checkpoint if we changed something */ + if (LOCAL->dirty) mmdf_check (stream); + flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */ + close (LOCAL->ld); /* close the readwrite lock file */ + LOCAL->ld = -1; /* no more readwrite lock fd */ + unlink (LOCAL->lname); /* delete the readwrite lock file */ + } + else { /* see if need to reparse */ + if (!(reparse = (long) mail_parameters (NIL,GET_NETFSSTATBUG,NIL))) { + /* get current mailbox size */ + if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf); + else if (stat (stream->mailbox,&sbuf)) { + sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s", + strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + mmdf_abort (stream); + return NIL; + } + reparse = (sbuf.st_size != LOCAL->filesize); + } + /* parse if mailbox changed */ + if ((LOCAL->ddirty || reparse) && mmdf_parse (stream,&lock,LOCK_EX)) { + /* force checkpoint if double-dirty */ + if (LOCAL->ddirty) mmdf_rewrite (stream,NIL,&lock,NIL); + /* unlock mailbox */ + else mmdf_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); /* and stream */ + /* done with critical */ + MM_NOCRITICAL (stream); + } + } + } + return LOCAL ? LONGT : NIL; /* return if still alive */ +} + +/* MMDF mail check mailbox + * Accepts: MAIL stream + */ + +void mmdf_check (MAILSTREAM *stream) +{ + DOTLOCK lock; + /* parse and lock mailbox */ + if (LOCAL && (LOCAL->ld >= 0) && !stream->lock && + mmdf_parse (stream,&lock,LOCK_EX)) { + /* any unsaved changes? */ + if (LOCAL->dirty && mmdf_rewrite (stream,NIL,&lock,NIL)) { + if (!stream->silent) MM_LOG ("Checkpoint completed",NIL); + } + /* no checkpoint needed, just unlock */ + else mmdf_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); /* unlock the stream */ + MM_NOCRITICAL (stream); /* done with critical */ + } +} + + +/* MMDF mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T, always + */ + +long mmdf_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret; + unsigned long i; + DOTLOCK lock; + char *msg = NIL; + if (ret = (sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : LONGT) && + LOCAL && (LOCAL->ld >= 0) && !stream->lock && + mmdf_parse (stream,&lock,LOCK_EX)) { + /* check expunged messages if not dirty */ + for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) { + MESSAGECACHE *elt = mail_elt (stream,i); + if (mail_elt (stream,i)->deleted) LOCAL->dirty = T; + } + if (!LOCAL->dirty) { /* not dirty and no expunged messages */ + mmdf_unlock (LOCAL->fd,stream,&lock); + msg = "No messages deleted, so no update needed"; + } + else if (mmdf_rewrite (stream,&i,&lock,sequence ? LONGT : NIL)) { + if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i); + else msg = "Mailbox checkpointed, but no messages expunged"; + } + /* rewrite failed */ + else mmdf_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); /* unlock the stream */ + MM_NOCRITICAL (stream); /* done with critical */ + if (msg && !stream->silent) MM_LOG (msg,NIL); + } + else if (!stream->silent) + MM_LOG ("Expunge ignored on readonly mailbox",WARN); + return ret; +} + +/* MMDF mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if copy successful, else NIL + */ + +long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + struct stat sbuf; + int fd; + char *s,file[MAILTMPLEN]; + DOTLOCK lock; + time_t tp[2]; + unsigned long i,j; + MESSAGECACHE *elt; + long ret = T; + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? + NIL : mail_parameters (NIL,GET_COPYUID,NIL)); + SEARCHSET *source = cu ? mail_newsearchset () : NIL; + SEARCHSET *dest = cu ? mail_newsearchset () : NIL; + MAILSTREAM *tstream = NIL; + if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) return NIL; + /* make sure destination is valid */ + if (!(mmdf_valid (mailbox) || !errno)) + switch (errno) { + case ENOENT: /* no such file? */ + if (compare_cstring (mailbox,"INBOX")) { + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); + return NIL; + } + if (pc) return (*pc) (stream,sequence,mailbox,options); + mmdf_create (NIL,"INBOX");/* create empty INBOX */ + case EACCES: /* file protected */ + sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + case EINVAL: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Invalid MMDF-format mailbox name: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + default: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Not a MMDF-format mailbox: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + + /* try to open rewrite for UIDPLUS */ + if ((tstream = mail_open_work (&mmdfdriver,NIL,mailbox, + OP_SILENT|OP_NOKOD)) && tstream->rdonly) + tstream = mail_close (tstream); + if (cu && !tstream) { /* wanted a COPYUID? */ + sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s", + mailbox); + MM_LOG (LOCAL->buf,WARN); + cu = NIL; /* don't try to do COPYUID */ + } + LOCAL->buf[0] = '\0'; + MM_CRITICAL (stream); /* go critical */ + if ((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL), + &lock,LOCK_EX)) < 0) { + MM_NOCRITICAL (stream); /* done with critical */ + sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); /* log the error */ + return NIL; /* failed */ + } + fstat (fd,&sbuf); /* get current file size */ + /* write all requested messages to mailbox */ + for (i = 1; ret && (i <= stream->nmsgs); i++) + if ((elt = mail_elt (stream,i))->sequence) { + lseek (LOCAL->fd,elt->private.special.offset,L_SET); + read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size); + if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL; + else { /* internal header succeeded */ + s = mmdf_header (stream,i,&j,FT_INTERNAL); + /* header size, sans trailing newline */ + if (j && (s[j - 2] == '\n')) j--; + if (write (fd,s,j) < 0) ret = NIL; + else { /* message header succeeded */ + j = tstream ? /* write UIDPLUS data if have readwrite */ + mmdf_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) : + mmdf_xstatus (stream,LOCAL->buf,elt,NIL,NIL); + if (write (fd,LOCAL->buf,j) < 0) ret = NIL; + else { /* message status succeeded */ + s = mmdf_text_work (stream,elt,&j,FT_INTERNAL); + if ((write (fd,s,j) < 0) || (write (fd,mmdfhdr,MMDFHDRLEN) < 0)) + ret = NIL; + else if (cu) { /* need to pass back new UID? */ + mail_append_set (source,mail_uid (stream,i)); + mail_append_set (dest,tstream->uid_last); + } + } + } + } + } + + if (!ret || fsync (fd)) { /* force out the update */ + sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno)); + ftruncate (fd,sbuf.st_size); + ret = NIL; + } + /* force UIDVALIDITY assignment now */ + if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0); + /* return sets if doing COPYUID */ + if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest); + else { /* flush any sets we may have built */ + mail_free_searchset (&source); + mail_free_searchset (&dest); + } + tp[1] = time (0); /* set mtime to now */ + if (ret) tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */ + else tp[0] = /* else preserve \Marked status */ + ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ? + sbuf.st_atime : tp[1]; + utime (file,tp); /* set the times */ + mmdf_unlock (fd,NIL,&lock); /* unlock and close mailbox */ + if (tstream) { /* update last UID if we can */ + MMDFLOCAL *local = (MMDFLOCAL *) tstream->local; + local->dirty = T; /* do a rewrite */ + local->appending = T; /* but not at the cost of marking as old */ + tstream = mail_close (tstream); + } + /* log the error */ + if (!ret) MM_LOG (LOCAL->buf,ERROR); + /* delete if requested message */ + else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) + elt->deleted = elt->private.dirty = LOCAL->dirty = T; + MM_NOCRITICAL (stream); /* release critical */ + return ret; +} + +/* MMDF mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +#define BUFLEN 8*MAILTMPLEN + +long mmdf_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + struct stat sbuf; + int fd; + unsigned long i; + char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN]; + time_t tp[2]; + FILE *sf,*df; + MESSAGECACHE elt; + DOTLOCK lock; + STRING *message; + unsigned long uidlocation = 0; + appenduid_t au = (appenduid_t) + (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL : + mail_parameters (NIL,GET_APPENDUID,NIL)); + SEARCHSET *dst = au ? mail_newsearchset () : NIL; + long ret = LONGT; + MAILSTREAM *tstream = NIL; + /* default stream to prototype */ + if (!stream) { /* stream specified? */ + stream = &mmdfproto; /* no, default stream to prototype */ + for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i) + fs_give ((void **) &stream->user_flags[i]); + } + if (!mmdf_valid (mailbox)) switch (errno) { + case ENOENT: /* no such file? */ + if (compare_cstring (mailbox,"INBOX")) { + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); + return NIL; + } + mmdf_create (NIL,"INBOX"); /* create empty INBOX */ + case 0: /* merely empty file? */ + tstream = stream; + break; + case EACCES: /* file protected */ + sprintf (tmp,"Can't access destination: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + case EINVAL: + sprintf (tmp,"Invalid MMDF-format mailbox name: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + default: + sprintf (tmp,"Not a MMDF-format mailbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get sniffing stream for keywords */ + else if (!(tstream = mail_open (NIL,mailbox, + OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) { + sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + + /* get first message */ + if (!MM_APPEND (af) (tstream,data,&flags,&date,&message)) return NIL; + if (!(sf = tmpfile ())) { /* must have scratch file */ + sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ()); + if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) { + sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + unlink (tmp); + } + do { /* parse date */ + if (!date) rfc822_date (date = tmp); + if (!mail_parse_date (&elt,date)) { + sprintf (tmp,"Bad date in append: %.80s",date); + MM_LOG (tmp,ERROR); + } + else { /* user wants to suppress time zones? */ + if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) { + time_t when = mail_longdate (&elt); + date = ctime (&when); /* use traditional date */ + } + /* use POSIX-style date */ + else date = mail_cdate (tmp,&elt); + if (!SIZE (message)) MM_LOG ("Append of zero-length message",ERROR); + else if (!mmdf_collect_msg (tstream,sf,flags,date,message)) { + sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + } + /* get next message */ + else if (MM_APPEND (af) (tstream,data,&flags,&date,&message)) continue; + } + fclose (sf); /* punt scratch file */ + return NIL; /* give up */ + } while (message); /* until no more messages */ + if (fflush (sf)) { + sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + fclose (sf); /* punt scratch file */ + return NIL; /* give up */ + } + i = ftell (sf); /* size of scratch file */ + if (tstream != stream) tstream = mail_close (tstream); + + MM_CRITICAL (stream); /* go critical */ + /* try to open readwrite for UIDPLUS */ + if ((tstream = mail_open_work (&mmdfdriver,NIL,mailbox, + OP_SILENT|OP_NOKOD)) && tstream->rdonly) + tstream = mail_close (tstream); + if (au && !tstream) { /* wanted an APPENDUID? */ + sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox); + MM_LOG (tmp,WARN); + au = NIL; + } + if (((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL), + &lock,LOCK_EX)) < 0) || + !(df = fdopen (fd,"ab"))) { + MM_NOCRITICAL (stream); /* done with critical */ + sprintf (tmp,"Can't open append mailbox: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + fstat (fd,&sbuf); /* get current file size */ + rewind (sf); + tp[1] = time (0); /* set mtime to now */ + /* write all messages */ + if (!mmdf_append_msgs (tstream,sf,df,au ? dst : NIL) || + (fflush (df) == EOF) || fsync (fd)) { + sprintf (buf,"Message append failed: %s",strerror (errno)); + MM_LOG (buf,ERROR); + ftruncate (fd,sbuf.st_size); + tp[0] = /* preserve \Marked status */ + ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ? + sbuf.st_atime : tp[1]; + ret = NIL; /* return error */ + } + else tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */ + utime (file,tp); /* set the times */ + fclose (sf); /* done with scratch file */ + /* force UIDVALIDITY assignment now */ + if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0); + /* return sets if doing APPENDUID */ + if (au && ret) (*au) (mailbox,tstream->uid_validity,dst); + else mail_free_searchset (&dst); + mmdf_unlock (fd,NIL,&lock); /* unlock and close mailbox */ + fclose (df); + if (tstream) { /* update last UID if we can */ + MMDFLOCAL *local = (MMDFLOCAL *) tstream->local; + local->dirty = T; /* do a rewrite */ + local->appending = T; /* but not at the cost of marking as old */ + tstream = mail_close (tstream); + } + MM_NOCRITICAL (stream); /* release critical */ + return ret; +} + +/* Collect and write single message to append scratch file + * Accepts: MAIL stream + * scratch file + * flags + * date + * message stringstruct + * Returns: NIL if write error, else T + */ + +int mmdf_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date, + STRING *msg) +{ + unsigned char *s,*t; + unsigned long uf; + long f = mail_parse_flags (stream,flags,&uf); + /* write metadata, note date ends with NL */ + if (fprintf (sf,"%ld %lu %s",f,SIZE (msg) + 1,date) < 0) return NIL; + while (uf) /* write user flags */ + if ((s = stream->user_flags[find_rightmost_bit (&uf)]) && + (fprintf (sf," %s",s) < 0)) return NIL; + if (putc ('\n',sf) == EOF) return NIL; + while (SIZE (msg)) { /* copy text to scratch file */ + for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s) + if (!*s) *s = 0x80; /* disallow NUL */ + /* write buffered text */ + if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize) + SETPOS (msg,GETPOS (msg) + msg->cursize); + else return NIL; /* failed */ + } + /* write trailing newline and return */ + return (putc ('\n',sf) == EOF) ? NIL : T; +} + +/* Append messages from scratch file to mailbox + * Accepts: MAIL stream + * source file + * destination file + * uidset to update if non-NIL + * Returns: T if success, NIL if failure + */ + +int mmdf_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set) +{ + int c; + long f; + unsigned long i,j; + char *x,tmp[MAILTMPLEN]; + int hdrp = T; + /* get message metadata line */ + while (fgets (tmp,MAILTMPLEN,sf)) { + if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL; + f = strtol (tmp,&x,10); /* get flags */ + if (!((*x++ == ' ') && isdigit (*x))) return NIL; + i = strtoul (x,&x,10); /* get message size */ + if ((*x++ != ' ') || /* build initial header */ + (fprintf (df,"%sFrom %s@%s %sStatus: ",mmdfhdr,myusername(), + mylocalhost(),x) < 0) || + (f&fSEEN && (putc ('R',df) == EOF)) || + (fputs ("\nX-Status: ",df) == EOF) || + (f&fDELETED && (putc ('D',df) == EOF)) || + (f&fFLAGGED && (putc ('F',df) == EOF)) || + (f&fANSWERED && (putc ('A',df) == EOF)) || + (f&fDRAFT && (putc ('T',df) == EOF)) || + (fputs ("\nX-Keywords:",df) == EOF)) return NIL; + /* copy keywords */ + while ((c = getc (sf)) != '\n') switch (c) { + case EOF: + return NIL; + default: + if (putc (c,df) == EOF) return NIL; + } + if ((putc ('\n',df) == EOF) || + (set && (fprintf (df,"X-UID: %lu\n",++(stream->uid_last)) < 0))) + return NIL; + + for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) { + /* get read line length */ + if (i < (j = strlen (tmp))) fatal ("mmdf_append_msgs overrun"); + i -= j; /* number of bytes left */ + /* squish out ^A and CRs (note copies NUL) */ + for (x = tmp; x = strpbrk (x,"\01\r"); --j) memmove (x,x+1,j-(x-tmp)); + if (!j) continue; /* do nothing if line emptied */ + /* start of line? */ + if ((c == '\n')) switch (tmp[0]) { + case 'S': case 's': /* possible "Status:" */ + if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) && + ((tmp[2] == 'a') || (tmp[2] == 'A')) && + ((tmp[3] == 't') || (tmp[3] == 'T')) && + ((tmp[4] == 'u') || (tmp[4] == 'U')) && + ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') && + (fputs ("X-Original-",df) == EOF)) return NIL; + break; + case 'X': case 'x': /* possible X-??? header */ + if (hdrp && (tmp[1] == '-') && + /* possible X-UID: */ + (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) && + ((tmp[3] == 'I') || (tmp[3] == 'i')) && + ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) || + /* possible X-IMAP: */ + ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) && + ((tmp[3] == 'M') || (tmp[3] == 'm')) && + ((tmp[4] == 'A') || (tmp[4] == 'a')) && + ((tmp[5] == 'P') || (tmp[5] == 'p')) && + ((tmp[6] == ':') || + /* or X-IMAPbase: */ + ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) && + ((tmp[7] == 'a') || (tmp[7] == 'A')) && + ((tmp[8] == 's') || (tmp[8] == 'S')) && + ((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) || + /* possible X-Status: */ + ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) && + ((tmp[3] == 't') || (tmp[3] == 'T')) && + ((tmp[4] == 'a') || (tmp[4] == 'A')) && + ((tmp[5] == 't') || (tmp[5] == 'T')) && + ((tmp[6] == 'u') || (tmp[6] == 'U')) && + ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) || + /* possible X-Keywords: */ + ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) && + ((tmp[3] == 'e') || (tmp[3] == 'E')) && + ((tmp[4] == 'y') || (tmp[4] == 'Y')) && + ((tmp[5] == 'w') || (tmp[5] == 'W')) && + ((tmp[6] == 'o') || (tmp[6] == 'O')) && + ((tmp[7] == 'r') || (tmp[7] == 'R')) && + ((tmp[8] == 'd') || (tmp[8] == 'D')) && + ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) && + (fputs ("X-Original-",df) == EOF)) return NIL; + break; + case '\n': /* blank line */ + hdrp = NIL; + break; + default: /* nothing to do */ + break; + } + /* just write the line */ + if (fwrite (tmp,1,j,df) != j) return NIL; + } + /* make sure read entire msg & wrote trailer */ + if (i || (fputs (mmdfhdr,df) == EOF)) return NIL; + /* update set */ + if (stream) mail_append_set (set,stream->uid_last); + } + return T; +} + +/* Internal routines */ + + +/* MMDF mail abort stream + * Accepts: MAIL stream + */ + +void mmdf_abort (MAILSTREAM *stream) +{ + if (LOCAL) { /* only if a file is open */ + if (LOCAL->fd >= 0) close (LOCAL->fd); + if (LOCAL->ld >= 0) { /* have a mailbox lock? */ + flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */ + close (LOCAL->ld); /* close the lock file */ + unlink (LOCAL->lname); /* and delete it */ + } + if (LOCAL->lname) fs_give ((void **) &LOCAL->lname); + /* free local text buffers */ + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); + if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data); + if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf); + if (LOCAL->line) fs_give ((void **) &LOCAL->line); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + +/* MMDF open and lock mailbox + * Accepts: file name to open/lock + * file open mode + * destination buffer for lock file name + * type of locking operation (LOCK_SH or LOCK_EX) + */ + +int mmdf_lock (char *file,int flags,int mode,DOTLOCK *lock,int op) +{ + int fd; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + (*bn) (BLOCK_FILELOCK,NIL); + /* try locking the easy way */ + if (dotlock_lock (file,lock,-1)) { + /* got dotlock file, easy open */ + if ((fd = open (file,flags,mode)) >= 0) flock (fd,op); + else dotlock_unlock (lock); /* open failed, free the dotlock */ + } + /* no dot lock file, open file now */ + else if ((fd = open (file,flags,mode)) >= 0) { + /* try paranoid way to make a dot lock file */ + if (dotlock_lock (file,lock,fd)) { + close (fd); /* get fresh fd in case of timing race */ + if ((fd = open (file,flags,mode)) >= 0) flock (fd,op); + /* open failed, free the dotlock */ + else dotlock_unlock (lock); + } + else flock (fd,op); /* paranoid way failed, just flock() it */ + } + (*bn) (BLOCK_NONE,NIL); + return fd; +} + +/* MMDF unlock and close mailbox + * Accepts: file descriptor + * (optional) mailbox stream to check atime/mtime + * (optional) lock file name + */ + +void mmdf_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock) +{ + if (stream) { /* need to muck with times? */ + struct stat sbuf; + time_t tp[2]; + time_t now = time (0); + fstat (fd,&sbuf); /* get file times */ + if (LOCAL->ld >= 0) { /* yes, readwrite session? */ + tp[0] = now; /* set atime to now */ + /* set mtime to (now - 1) if necessary */ + tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1; + } + else if (stream->recent) { /* readonly with recent messages */ + if ((sbuf.st_atime >= sbuf.st_mtime) || + (sbuf.st_atime >= sbuf.st_ctime)) + /* keep past mtime, whack back atime */ + tp[0] = (tp[1] = (sbuf.st_mtime < now) ? sbuf.st_mtime : now) - 1; + else now = 0; /* no time change needed */ + } + /* readonly with no recent messages */ + else if ((sbuf.st_atime < sbuf.st_mtime) || + (sbuf.st_atime < sbuf.st_ctime)) { + tp[0] = now; /* set atime to now */ + /* set mtime to (now - 1) if necessary */ + tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1; + } + else now = 0; /* no time change needed */ + /* set the times, note change */ + if (now && !utime (stream->mailbox,tp)) LOCAL->filetime = tp[1]; + } + flock (fd,LOCK_UN); /* release flock'ers */ + if (!stream) close (fd); /* close the file if no stream */ + dotlock_unlock (lock); /* flush the lock file if any */ +} + +/* MMDF mail parse and lock mailbox + * Accepts: MAIL stream + * space to write lock file name + * type of locking operation + * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure + */ + +int mmdf_parse (MAILSTREAM *stream,DOTLOCK *lock,int op) +{ + int ti,zn,m; + unsigned long i,j,k; + unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30]; + int retain = T; + unsigned long nmsgs = stream->nmsgs; + unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0; + unsigned long recent = stream->recent; + unsigned long oldnmsgs = stream->nmsgs; + short silent = stream->silent; + short pseudoseen = NIL; + struct stat sbuf; + STRING bs; + FDDATA d; + MESSAGECACHE *elt; + mail_lock (stream); /* guard against recursion or pingers */ + /* toss out previous descriptor */ + if (LOCAL->fd >= 0) close (LOCAL->fd); + MM_CRITICAL (stream); /* open and lock mailbox (shared OK) */ + if ((LOCAL->fd = mmdf_lock (stream->mailbox,(LOCAL->ld >= 0) ? + O_RDWR : O_RDONLY, + (long)mail_parameters(NIL,GET_MBXPROTECTION,NIL), + lock,op)) < 0) { + sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + mmdf_abort (stream); + mail_unlock (stream); + MM_NOCRITICAL (stream); /* done with critical */ + return NIL; + } + fstat (LOCAL->fd,&sbuf); /* get status */ + /* validate change in size */ + if (sbuf.st_size < LOCAL->filesize) { + sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted", + (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size); + MM_LOG (tmp,ERROR); /* this is pretty bad */ + mmdf_unlock (LOCAL->fd,stream,lock); + mmdf_abort (stream); + mail_unlock (stream); + MM_NOCRITICAL (stream); /* done with critical */ + return NIL; + } + + /* new data? */ + else if (i = sbuf.st_size - LOCAL->filesize) { + d.fd = LOCAL->fd; /* yes, set up file descriptor */ + d.pos = LOCAL->filesize; /* get to that position in the file */ + d.chunk = LOCAL->buf; /* initial buffer chunk */ + d.chunksize = CHUNKSIZE; /* file chunk size */ + INIT (&bs,fd_string,&d,i); /* initialize stringstruct */ + /* skip leading whitespace for broken MTAs */ + while (((c = CHR (&bs)) == '\n') || (c == '\r') || + (c == ' ') || (c == '\t')) SNX (&bs); + if (SIZE (&bs)) { /* read new data */ + /* remember internal header position */ + j = LOCAL->filesize + GETPOS (&bs); + s = mmdf_mbxline (stream,&bs,&i); + stream->silent = T; /* quell main program new message events */ + do { /* read MMDF header */ + if (!(i && ISMMDF (s))){/* see if valid MMDF header */ + sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s", + (char *) s); + /* see if we can back up to a line */ + if (i && (j > MMDFHDRLEN)) { + SETPOS (&bs,j -= MMDFHDRLEN); + /* read previous line */ + s = mmdf_mbxline (stream,&bs,&i); + /* kill the error if it looks good */ + if (i && ISMMDF (s)) tmp[0] = '\0'; + } + if (tmp[0]) { + MM_LOG (tmp,ERROR); + mmdf_unlock (LOCAL->fd,stream,lock); + mmdf_abort (stream); + mail_unlock (stream); + MM_NOCRITICAL (stream); + return NIL; + } + } + /* instantiate first new message */ + mail_exists (stream,++nmsgs); + (elt = mail_elt (stream,nmsgs))->valid = T; + recent++; /* assume recent by default */ + elt->recent = T; + /* note position/size of internal header */ + elt->private.special.offset = j; + elt->private.special.text.size = i; + + s = mmdf_mbxline (stream,&bs,&i); + ti = 0; /* assume not a valid date */ + zn = 0,t = NIL; + if (i) VALID (s,t,ti,zn); + if (ti) { /* generate plausible IMAPish date string */ + /* this is also part of header */ + elt->private.special.text.size += i; + date[2] = date[6] = date[20] = '-'; date[11] = ' '; + date[14] = date[17] = ':'; + /* dd */ + date[0] = t[ti - 2]; date[1] = t[ti - 1]; + /* mmm */ + date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4]; + /* hh */ + date[12] = t[ti + 1]; date[13] = t[ti + 2]; + /* mm */ + date[15] = t[ti + 4]; date[16] = t[ti + 5]; + if (t[ti += 6]==':'){ /* ss */ + date[18] = t[++ti]; date[19] = t[++ti]; + ti++; /* move to space */ + } + else date[18] = date[19] = '0'; + /* yy -- advance over timezone if necessary */ + if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4); + date[7] = t[ti + 1]; date[8] = t[ti + 2]; + date[9] = t[ti + 3]; date[10] = t[ti + 4]; + /* zzz */ + t = zn ? (t + zn + 1) : (unsigned char *) "LCL"; + date[21] = *t++; date[22] = *t++; date[23] = *t++; + if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0'; + else { /* numeric time zone */ + date[24] = *t++; date[25] = *t++; + date[26] = '\0'; date[20] = ' '; + } + /* set internal date */ + if (!mail_parse_date (elt,date)) { + sprintf (tmp,"Unable to parse internal date: %s",(char *) date); + MM_LOG (tmp,WARN); + } + } + else { /* make date from file date */ + struct tm *tm = gmtime (&sbuf.st_mtime); + elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1; + elt->year = tm->tm_year + 1900 - BASEYEAR; + elt->hours = tm->tm_hour; elt->minutes = tm->tm_min; + elt->seconds = tm->tm_sec; + elt->zhours = 0; elt->zminutes = 0; + t = NIL; /* suppress line read */ + } + /* header starts here */ + elt->private.msg.header.offset = elt->private.special.text.size; + + do { /* look for message body */ + j = GETPOS (&bs); /* note position before line */ + if (t) s = t = mmdf_mbxline (stream,&bs,&i); + else t = s; /* this line read was suppressed */ + if (ISMMDF (s)) { /* found terminator in header? */ + SETPOS (&bs,j); /* oops, back up before line */ + /* must insert a newline */ + elt->private.spare.data++; + break; /* punt */ + } + /* this line is part of header */ + elt->private.msg.header.text.size += i; + if (i) switch (*s) { /* check header lines */ + case 'X': /* possible X-???: line */ + if (s[1] == '-') { /* must be immediately followed by hyphen */ + /* X-Status: becomes Status: in S case */ + if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' && + s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2; + /* possible X-Keywords */ + else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' && + s[5] == 'w' && s[6] == 'o' && s[7] == 'r' && + s[8] == 'd' && s[9] == 's' && s[10] == ':') { + SIZEDTEXT uf; + retain = NIL; /* don't retain continuation */ + s += 11; /* flush leading whitespace */ + while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){ + while (*s == ' ') s++; + /* find end of keyword */ + if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s); + /* got a keyword? */ + if ((k = (u - s)) && (k <= MAXUSERFLAG)) { + uf.data = (unsigned char *) s; + uf.size = k; + for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j) + if (!compare_csizedtext (stream->user_flags[j],&uf)) { + elt->user_flags |= ((long) 1) << j; + break; + } + } + s = u; /* advance to next keyword */ + } + break; + } + + /* possible X-IMAP */ + else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') && + (s[5] == 'P') && ((m = (s[6] == ':')) || + ((s[6] == 'b') && (s[7] == 'a') && + (s[8] == 's') && (s[9] == 'e') && + (s[10] == ':')))) { + retain = NIL; /* don't retain continuation */ + if ((nmsgs == 1) && !stream->uid_validity) { + /* advance to data */ + s += m ? 7 : 11; + /* flush whitespace */ + while (*s == ' ') s++; + j = 0; /* slurp UID validity */ + /* found a digit? */ + while (isdigit (*s)) { + j *= 10; /* yes, add it in */ + j += *s++ - '0'; + } + /* flush whitespace */ + while (*s == ' ') s++; + /* must have valid UID validity and UID last */ + if (j && isdigit (*s)) { + /* pseudo-header seen if X-IMAP */ + if (m) pseudoseen = LOCAL->pseudo = T; + /* save UID validity */ + stream->uid_validity = j; + j = 0; /* slurp UID last */ + while (isdigit (*s)) { + j *= 10; /* yes, add it in */ + j += *s++ - '0'; + } + /* save UID last */ + stream->uid_last = j; + /* process keywords */ + for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n')); + s = u,j++) { + /* flush leading whitespace */ + while (*s == ' ') s++; + u = strpbrk (s," \n\r"); + /* got a keyword? */ + if ((j < NUSERFLAGS) && (k = (u - s)) && + (k <= MAXUSERFLAG)) { + if (stream->user_flags[j]) + fs_give ((void **) &stream->user_flags[j]); + stream->user_flags[j] = (char *) fs_get (k + 1); + strncpy (stream->user_flags[j],s,k); + stream->user_flags[j][k] = '\0'; + } + } + } + } + break; + } + + /* possible X-UID */ + else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' && + s[5] == ':') { + retain = NIL; /* don't retain continuation */ + /* only believe if have a UID validity */ + if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) { + s += 6; /* advance to UID value */ + /* flush whitespace */ + while (*s == ' ') s++; + j = 0; + /* found a digit? */ + while (isdigit (*s)) { + j *= 10; /* yes, add it in */ + j += *s++ - '0'; + } + /* flush remainder of line */ + while (*s != '\n') s++; + /* make sure not duplicated */ + if (elt->private.uid) + sprintf (tmp,"Message %lu UID %lu already has UID %lu", + pseudoseen ? elt->msgno - 1 : elt->msgno, + j,elt->private.uid); + /* make sure UID doesn't go backwards */ + else if (j <= prevuid) + sprintf (tmp,"Message %lu UID %lu less than %lu", + pseudoseen ? elt->msgno - 1 : elt->msgno, + j,prevuid + 1); +#if 0 /* this is currently broken by UIDPLUS */ + /* or skip by mailbox's recorded last */ + else if (j > stream->uid_last) + sprintf (tmp,"Message %lu UID %lu greater than last %lu", + pseudoseen ? elt->msgno - 1 : elt->msgno, + j,stream->uid_last); +#endif + else { /* normal UID case */ + prevuid = elt->private.uid = j; +#if 1 /* temporary kludge for UIDPLUS */ + if (prevuid > stream->uid_last) { + stream->uid_last = prevuid; + LOCAL->ddirty = LOCAL->dirty = T; + } +#endif + break; /* exit this cruft */ + } + MM_LOG (tmp,WARN); + /* invalidate UID validity */ + stream->uid_validity = 0; + elt->private.uid = 0; + } + break; + } + } + /* otherwise fall into S case */ + + case 'S': /* possible Status: line */ + if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' && + s[4] == 'u' && s[5] == 's' && s[6] == ':') { + retain = NIL; /* don't retain continuation */ + s += 6; /* advance to status flags */ + do switch (*s++) {/* parse flags */ + case 'R': /* message read */ + elt->seen = T; + break; + case 'O': /* message old */ + if (elt->recent) { + elt->recent = NIL; + recent--; /* it really wasn't recent */ + } + break; + case 'D': /* message deleted */ + elt->deleted = T; + break; + case 'F': /* message flagged */ + elt->flagged = T; + break; + case 'A': /* message answered */ + elt->answered = T; + break; + case 'T': /* message is a draft */ + elt->draft = T; + break; + default: /* some other crap */ + break; + } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))); + break; /* all done */ + } + /* otherwise fall into default case */ + + default: /* ordinary header line */ + if ((*s == 'S') || (*s == 's') || + (((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) { + unsigned char *e,*v; + /* must match what mail_filter() does */ + for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1); + (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') && + ((c > ' ') || ((c != ' ') && (c != '\t') && + (c != '\r') && (c != '\n'))); + *v++ = *u++); + *v = '\0'; /* tie off */ + /* matches internal header? */ + if (!compare_cstring (tmp,"STATUS") || + !compare_cstring (tmp,"X-STATUS") || + !compare_cstring (tmp,"X-KEYWORDS") || + !compare_cstring (tmp,"X-UID") || + !compare_cstring (tmp,"X-IMAP") || + !compare_cstring (tmp,"X-IMAPBASE")) { + char err[MAILTMPLEN]; + sprintf (err,"Discarding bogus %s header in message %lu", + (char *) tmp,elt->msgno); + MM_LOG (err,WARN); + retain = NIL; /* don't retain continuation */ + break; /* different case or something */ + } + } + /* retain or non-continuation? */ + if (retain || ((*s != ' ') && (*s != '\t'))) { + retain = T; /* retaining continuation now */ + /* line length in LF format newline */ + for (j = k = 0; j < i; ++j) if (s[j] != '\r') ++k; + /* "internal" header size */ + elt->private.spare.data += k; + /* message size */ + elt->rfc822_size += k + 1; + } + else { + char err[MAILTMPLEN]; + sprintf (err,"Discarding bogus continuation in msg %lu: %.80s", + elt->msgno,(char *) s); + if (u = strpbrk (err,"\r\n")) *u = '\0'; + MM_LOG (err,WARN); + break; /* different case or something */ + } + break; + } + } while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n'))); + /* "internal" header sans trailing newline */ + if (i) elt->private.spare.data--; + /* assign a UID if none found */ + if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) { + prevuid = elt->private.uid = ++stream->uid_last; + elt->private.dirty = T; + } + else elt->private.dirty = elt->recent; + + /* note size of header, location of text */ + elt->private.msg.header.text.size = + (elt->private.msg.text.offset = + (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) - + elt->private.special.text.size; + /* note current position */ + j = LOCAL->filesize + GETPOS (&bs); + if (i) do { /* look for next message */ + s = mmdf_mbxline (stream,&bs,&i); + if (i) { /* got new data? */ + if (ISMMDF (s)) break; + else { /* not a header line, add it to message */ + elt->rfc822_size += i; + for (j = 0; j < i; ++j) switch (s[j]) { + case '\r': /* squeeze out CRs */ + elt->rfc822_size -= 1; + break; + case '\n': /* LF becomes CRLF */ + elt->rfc822_size += 1; + break; + default: + break; + } + /* update current position */ + j = LOCAL->filesize + GETPOS (&bs); + } + } + } while (i); /* until found a header */ + elt->private.msg.text.text.size = j - + (elt->private.special.offset + elt->private.msg.text.offset); + if (i) { /* get next header line */ + /* remember first internal header position */ + j = LOCAL->filesize + GETPOS (&bs); + s = mmdf_mbxline (stream,&bs,&i); + } + /* until end of buffer */ + } while (!stream->sniff && i); + if (pseudoseen) { /* flush pseudo-message if present */ + /* decrement recent count */ + if (mail_elt (stream,1)->recent) recent--; + /* and the exists count */ + mail_exists (stream,nmsgs--); + mail_expunged(stream,1);/* fake an expunge of that message */ + } + /* need to start a new UID validity? */ + if (!stream->uid_validity) { + stream->uid_validity = time (0); + /* in case a whiner with no life */ + if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) + stream->uid_nosticky = T; + else if (nmsgs) { /* don't bother if empty file */ + /* make dirty to restart UID epoch */ + LOCAL->ddirty = LOCAL->dirty = T; + /* need to rewrite msg 1 if not pseudo */ + if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T; + MM_LOG ("Assigning new unique identifiers to all messages",NIL); + } + } + stream->nmsgs = oldnmsgs; /* whack it back down */ + stream->silent = silent; /* restore old silent setting */ + /* notify upper level of new mailbox sizes */ + mail_exists (stream,nmsgs); + mail_recent (stream,recent); + /* mark dirty so O flags are set */ + if (recent) LOCAL->dirty = T; + } + } + /* no change, don't babble if never got time */ + else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime) + MM_LOG ("New mailbox modification time but apparently no changes",WARN); + /* update parsed file size and time */ + LOCAL->filesize = sbuf.st_size; + LOCAL->filetime = sbuf.st_mtime; + return T; /* return the winnage */ +} + +/* MMDF read line from mailbox + * Accepts: mail stream + * stringstruct + * pointer to line size + * Returns: pointer to input line + */ + +char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size) +{ + unsigned long i,j,k,m; + char *s,*t,*te; + char *ret = ""; + /* flush old buffer */ + if (LOCAL->line) fs_give ((void **) &LOCAL->line); + /* if buffer needs refreshing */ + if (!bs->cursize) SETPOS (bs,GETPOS (bs)); + if (SIZE (bs)) { /* find newline */ + /* end of fast scan */ + te = (t = (s = bs->curpos) + bs->cursize) - 12; + while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) { + --s; /* back up */ + break; /* exit loop */ + } + /* final character-at-a-time scan */ + while ((s < t) && (*s != '\n')) ++s; + /* difficult case if line spans buffer */ + if ((i = s - bs->curpos) == bs->cursize) { + /* have space in line buffer? */ + if (i > LOCAL->linebuflen) { + fs_give ((void **) &LOCAL->linebuf); + LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i); + } + /* remember what we have so far */ + memcpy (LOCAL->linebuf,bs->curpos,i); + /* load next buffer */ + SETPOS (bs,k = GETPOS (bs) + i); + /* end of fast scan */ + te = (t = (s = bs->curpos) + bs->cursize) - 12; + /* fast scan in overlap buffer */ + while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) { + --s; /* back up */ + break; /* exit loop */ + } + + /* final character-at-a-time scan */ + while ((s < t) && (*s != '\n')) ++s; + /* huge line? */ + if ((j = s - bs->curpos) == bs->cursize) { + SETPOS (bs,GETPOS (bs) + j); + /* look for end of line (s-l-o-w!!) */ + for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j); + SETPOS (bs,k); /* go back to where it started */ + } + /* got size of data, make buffer for return */ + ret = LOCAL->line = (char *) fs_get (i + j + 2); + /* copy first chunk */ + memcpy (ret,LOCAL->linebuf,i); + while (j) { /* copy remainder */ + if (!bs->cursize) SETPOS (bs,GETPOS (bs)); + memcpy (ret + i,bs->curpos,k = min (j,bs->cursize)); + i += k; /* account for this much read in */ + j -= k; + bs->curpos += k; /* increment new position */ + bs->cursize -= k; /* eat that many bytes */ + } + /* read newline at end */ + if (SIZE (bs)) ret[i++] = SNX (bs); + ret[i] = '\0'; /* makes debugging easier */ + } + else { /* this is easy */ + ret = bs->curpos; /* string it at this position */ + bs->curpos += ++i; /* increment new position */ + bs->cursize -= i; /* eat that many bytes */ + } + *size = i; /* return that to user */ + } + else *size = 0; /* end of data, return empty */ + /* embedded MMDF header at end of line? */ + if ((*size > sizeof (MMDFHDRTXT)) && + (s = ret + *size - (i = sizeof (MMDFHDRTXT) - 1)) && ISMMDF (s)) { + SETPOS (bs,GETPOS (bs) - i);/* back up to start of MMDF header */ + *size -= i; /* reduce length of line */ + ret[*size - 1] = '\n'; /* force newline at end */ + } + return ret; +} + +/* MMDF make pseudo-header + * Accepts: MAIL stream + * buffer to write pseudo-header + * Returns: length of pseudo-header + */ + +unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr) +{ + int i; + char *s,tmp[MAILTMPLEN]; + time_t now = time (0); + rfc822_fixed_date (tmp); + sprintf (hdr,"%sFrom %s %.24s\nDate: %s\nFrom: %s <%s@%.80s>\nSubject: %s\nMessage-ID: <%lu@%.80s>\nX-IMAP: %010lu %010lu", + mmdfhdr,pseudo_from,ctime (&now), + tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject, + (unsigned long) now,mylocalhost (),stream->uid_validity, + stream->uid_last); + for (s = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i) + if (stream->user_flags[i]) + sprintf (s += strlen (s)," %s",stream->user_flags[i]); + sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr); + return strlen (hdr); +} + +/* MMDF make status string + * Accepts: MAIL stream + * destination string to write + * message cache entry + * UID to write if non-zero (else use elt->private.uid) + * non-zero flag to write UID (.LT. 0 to write UID base info too) + * Returns: length of string + */ + +unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt, + unsigned long uid,long flag) +{ + char *t,stack[64]; + char *s = status; + unsigned long n; + int pad = 50; + int sticky = uid ? T : !stream->uid_nosticky; + /* This used to use sprintf(), but thanks to certain cretinous C libraries + with horribly slow implementations of sprintf() I had to change it to this + mess. At least it should be fast. */ + if ((flag < 0) && sticky) { /* need to write X-IMAPbase: header? */ + *s++ = 'X'; *s++ = '-'; *s++ = 'I'; *s++ = 'M'; *s++ = 'A'; *s++ = 'P'; + *s++ = 'b'; *s++ = 'a'; *s++ = 's'; *s++ = 'e'; *s++ = ':'; *s++ = ' '; + t = stack; + n = stream->uid_validity; /* push UID validity digits on the stack */ + do *t++ = (char) (n % 10) + '0'; + while (n /= 10); + /* pop UID validity digits from stack */ + while (t > stack) *s++ = *--t; + *s++ = ' '; + n = stream->uid_last; /* push UID last digits on the stack */ + do *t++ = (char) (n % 10) + '0'; + while (n /= 10); + /* pop UID last digits from stack */ + while (t > stack) *s++ = *--t; + for (n = 0; n < NUSERFLAGS; ++n) if (t = stream->user_flags[n]) + for (*s++ = ' '; *t; *s++ = *t++); + *s++ = '\n'; + pad += 30; /* increased padding if have IMAPbase */ + } + *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's'; + *s++ = ':'; *s++ = ' '; + if (elt->seen) *s++ = 'R'; + /* only write O if have a UID */ + if (flag && (!elt->recent || !LOCAL->appending)) *s++ = 'O'; + *s++ = '\n'; + *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; + *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' '; + if (elt->deleted) *s++ = 'D'; + if (elt->flagged) *s++ = 'F'; + if (elt->answered) *s++ = 'A'; + if (elt->draft) *s++ = 'T'; + *s++ = '\n'; + + if (sticky) { /* only do this if UIDs sticky */ + *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w'; + *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':'; + if (n = elt->user_flags) do { + *s++ = ' '; + for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++); + } while (n); + n = s - status; /* get size of stuff so far */ + /* pad X-Keywords to make size constant */ + if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' '; + *s++ = '\n'; + if (flag) { /* want to include UID? */ + t = stack; + /* push UID digits on the stack */ + n = uid ? uid : elt->private.uid; + do *t++ = (char) (n % 10) + '0'; + while (n /= 10); + *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':'; + *s++ = ' '; + /* pop UID from stack */ + while (t > stack) *s++ = *--t; + *s++ = '\n'; + } + } + *s++ = '\n'; *s = '\0'; /* end of extended message status */ + return s - status; /* return size of resulting string */ +} + +/* Rewrite mailbox file + * Accepts: MAIL stream, must be critical and locked + * return pointer to number of expunged messages if want expunge + * lock file name + * expunge sequence, not deleted flag + * Returns: T if success and mailbox unlocked, NIL if failure + */ + +#define OVERFLOWBUFLEN 8192 /* initial overflow buffer length */ + +long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock, + long flags) +{ + MESSAGECACHE *elt; + MMDFFILE f; + char *s; + time_t tp[2]; + long ret,flag; + unsigned long i,j; + unsigned long recent = stream->recent; + unsigned long size = LOCAL->pseudo ? mmdf_pseudo (stream,LOCAL->buf) : 0; + if (nexp) *nexp = 0; /* initially nothing expunged */ + /* calculate size of mailbox after rewrite */ + for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) { + elt = mail_elt (stream,i); /* get cache */ + if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) { + /* add RFC822 size of this message */ + size += elt->private.special.text.size + elt->private.spare.data + + mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag) + + elt->private.msg.text.text.size + MMDFHDRLEN; + flag = 1; /* only count X-IMAPbase once */ + } + } + /* no messages, has a life, and no pseudo */ + if (!size && !mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) { + LOCAL->pseudo = T; /* so make a pseudo-message now */ + size = mmdf_pseudo (stream,LOCAL->buf); + } + /* extend the file as necessary */ + if (ret = mmdf_extend (stream,size)) { + /* Set up buffered I/O file structure + * curpos current position being written through buffering + * filepos current position being written physically to the disk + * bufpos current position being written in the buffer + * protect current maximum position that can be written to the disk + * before buffering is forced + * The code tries to buffer so that that disk is written in multiples of + * OVERBLOWBUFLEN bytes. + */ + f.stream = stream; /* note mail stream */ + f.curpos = f.filepos = 0; /* start of file */ + f.protect = stream->nmsgs ? /* initial protection pointer */ + mail_elt (stream,1)->private.special.offset : 8192; + f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN); + + if (LOCAL->pseudo) /* update pseudo-header */ + mmdf_write (&f,LOCAL->buf,mmdf_pseudo (stream,LOCAL->buf)); + /* loop through all messages */ + for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) { + elt = mail_elt (stream,i);/* get cache */ + /* expunge this message? */ + if (nexp && elt->deleted && (flags ? elt->sequence : T)) { + /* one less recent message */ + if (elt->recent) --recent; + mail_expunged(stream,i);/* notify upper levels */ + ++*nexp; /* count up one more expunged message */ + } + else { /* preserve this message */ + i++; /* advance to next message */ + if ((flag < 0) || /* need to rewrite message? */ + elt->private.dirty || (f.curpos != elt->private.special.offset) || + (elt->private.msg.header.text.size != + (elt->private.spare.data + + mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) { + unsigned long newoffset = f.curpos; + /* yes, seek to internal header */ + lseek (LOCAL->fd,elt->private.special.offset,L_SET); + read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size); + /* see if need to squeeze out a CR */ + if (LOCAL->buf[elt->private.special.text.size - 2] == '\r') { + LOCAL->buf[--elt->private.special.text.size - 1] = '\n'; + --size; /* squeezed out a CR from PC */ + } + /* protection pointer moves to RFC822 header */ + f.protect = elt->private.special.offset + + elt->private.msg.header.offset; + /* write internal header */ + mmdf_write (&f,LOCAL->buf,elt->private.special.text.size); + /* get RFC822 header */ + s = mmdf_header (stream,elt->msgno,&j,FT_INTERNAL); + /* in case this got decremented */ + elt->private.msg.header.offset = elt->private.special.text.size; + /* header size, sans trailing newline */ + if ((j < 2) || (s[j - 2] == '\n')) j--; + /* this can happen if CRs were squeezed */ + if (j < elt->private.spare.data) { + /* so fix up counts */ + size -= elt->private.spare.data - j; + elt->private.spare.data = j; + } + else if (j != elt->private.spare.data) + fatal ("header size inconsistent"); + /* protection pointer moves to RFC822 text */ + f.protect = elt->private.special.offset + + elt->private.msg.text.offset; + mmdf_write (&f,s,j); /* write RFC822 header */ + /* write status and UID */ + mmdf_write (&f,LOCAL->buf, + j = mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag)); + flag = 1; /* only write X-IMAPbase once */ + /* new file header size */ + elt->private.msg.header.text.size = elt->private.spare.data + j; + + /* did text move? */ + if (f.curpos != f.protect) { + /* get message text */ + s = mmdf_text_work (stream,elt,&j,FT_INTERNAL); + /* this can happen if CRs were squeezed */ + if (j < elt->private.msg.text.text.size) { + /* so fix up counts */ + size -= elt->private.msg.text.text.size - j; + elt->private.msg.text.text.size = j; + } + /* can't happen it says here */ + else if (j > elt->private.msg.text.text.size) + fatal ("text size inconsistent"); + /* new text offset, status/UID may change it */ + elt->private.msg.text.offset = f.curpos - newoffset; + /* protection pointer moves to next message */ + f.protect = (i <= stream->nmsgs) ? + mail_elt (stream,i)->private.special.offset : + (f.curpos + j + MMDFHDRLEN); + mmdf_write (&f,s,j);/* write text */ + /* write trailing newline */ + mmdf_write (&f,mmdfhdr,MMDFHDRLEN); + } + else { /* tie off header and status */ + mmdf_write (&f,NIL,NIL); + f.curpos = f.protect =/* restart everything at end of message */ + f.filepos += elt->private.msg.text.text.size + MMDFHDRLEN; + } + /* new internal header offset */ + elt->private.special.offset = newoffset; + elt->private.dirty =NIL;/* message is now clean */ + } + else { /* no need to rewrite this message */ + /* tie off previous message if needed */ + mmdf_write (&f,NIL,NIL); + f.curpos = f.protect =/* restart everything at end of message */ + f.filepos += elt->private.special.text.size + + elt->private.msg.header.text.size + + elt->private.msg.text.text.size + MMDFHDRLEN; + } + } + } + + mmdf_write (&f,NIL,NIL); /* tie off final message */ + if (size != f.filepos) fatal ("file size inconsistent"); + fs_give ((void **) &f.buf); /* free buffer */ + /* make sure tied off */ + ftruncate (LOCAL->fd,LOCAL->filesize = size); + fsync (LOCAL->fd); /* make sure the updates take */ + if (size && (flag < 0)) fatal ("lost UID base information"); + /* no longer dirty */ + LOCAL->ddirty = LOCAL->dirty = NIL; + /* notify upper level of new mailbox sizes */ + mail_exists (stream,stream->nmsgs); + mail_recent (stream,recent); + /* set atime to now, mtime a second earlier */ + tp[1] = (tp[0] = time (0)) - 1; + /* set the times, note change */ + if (!utime (stream->mailbox,tp)) LOCAL->filetime = tp[1]; + close (LOCAL->fd); /* close and reopen file */ + if ((LOCAL->fd = open (stream->mailbox,O_RDWR, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) + < 0) { + sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + mmdf_abort (stream); + } + dotlock_unlock (lock); /* flush the lock file */ + } + return ret; /* return state from algorithm */ +} + +/* Extend MMDF mailbox file + * Accepts: MAIL stream + * new desired size + * Return: T if success, else NIL + */ + +long mmdf_extend (MAILSTREAM *stream,unsigned long size) +{ + unsigned long i = (size > LOCAL->filesize) ? size - LOCAL->filesize : 0; + if (i) { /* does the mailbox need to grow? */ + if (i > LOCAL->buflen) { /* make sure have enough space */ + /* this user won the lottery all right */ + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1); + } + memset (LOCAL->buf,'\0',i); /* get a block of nulls */ + while (T) { /* until write successful or punt */ + lseek (LOCAL->fd,LOCAL->filesize,L_SET); + if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break; + else { + long e = errno; /* note error before doing ftruncate */ + ftruncate (LOCAL->fd,LOCAL->filesize); + if (MM_DISKERROR (stream,e,NIL)) { + fsync (LOCAL->fd); /* user chose to punt */ + sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e)); + if (!stream->silent) MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + } + } + } + return LONGT; +} + +/* Write data to buffered file + * Accepts: buffered file pointer + * file data or NIL to indicate "flush buffer" + * date size (ignored for "flush buffer") + * Does not return until success + */ + +void mmdf_write (MMDFFILE *f,char *buf,unsigned long size) +{ + unsigned long i,j,k; + if (buf) { /* doing buffered write? */ + i = f->bufpos - f->buf; /* yes, get size of current buffer data */ + /* yes, have space in current buffer chunk? */ + if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) { + /* yes, fill up buffer as much as we can */ + memcpy (f->bufpos,buf,k = min (j,size)); + f->bufpos += k; /* new buffer position */ + f->curpos += k; /* new current position */ + if (j -= k) return; /* all done if still have buffer free space */ + buf += k; /* full, get new unwritten data pointer */ + size -= k; /* new data size */ + i += k; /* new buffer data size */ + } + /* This chunk of the buffer is full. See if can make some space by + * writing to the disk, if there's enough unprotected space to do so. + * Try to fill out any unaligned chunk, along with any subsequent full + * chunks that will fit in unprotected space. + */ + /* any unprotected space we can write to? */ + if (j = min (i,f->protect - f->filepos)) { + /* yes, filepos not at chunk boundary? */ + if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j)) + j -= k; /* yes, and can write out partial chunk */ + else k = 0; /* no partial chunk to write */ + /* if at least a chunk free, write that too */ + if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN); + if (k) { /* write data if there is anything we can */ + mmdf_phys_write (f,f->buf,k); + /* slide buffer */ + if (i -= k) memmove (f->buf,f->buf + k,i); + f->bufpos = f->buf + i; /* new end of buffer */ + } + } + + /* Have flushed the buffer as best as possible. All done if no more + * data to write. Otherwise, if the buffer is empty AND if the unwritten + * data is larger than a chunk AND the unprotected space is also larger + * than a chunk, then write as many chunks as we can directly from the + * data. Buffer the rest, expanding the buffer as needed. + */ + if (size) { /* have more data that we need to buffer? */ + /* can write any of it to disk instead? */ + if ((f->bufpos == f->buf) && + ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) { + /* write as much as we can right now */ + mmdf_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN)); + buf += j; /* new data pointer */ + size -= j; /* new data size */ + f->curpos += j; /* advance current pointer */ + } + if (size) { /* still have data that we need to buffer? */ + /* yes, need to expand the buffer? */ + if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) { + /* note current position in buffer */ + j = f->bufpos - f->buf; + i += OVERFLOWBUFLEN; /* yes, grow another chunk */ + fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN)); + /* in case buffer relocated */ + f->bufpos = f->buf + j; + } + /* buffer remaining data */ + memcpy (f->bufpos,buf,size); + f->bufpos += size; /* new end of buffer */ + f->curpos += size; /* advance current pointer */ + } + } + } + else { /* flush buffer to disk */ + mmdf_phys_write (f,f->buf,i = f->bufpos - f->buf); + f->bufpos = f->buf; /* reset buffer */ + /* update positions */ + f->curpos = f->protect = f->filepos; + } +} + +/* Physical disk write + * Accepts: buffered file pointer + * buffer address + * buffer size + * Does not return until success + */ + +void mmdf_phys_write (MMDFFILE *f,char *buf,size_t size) +{ + MAILSTREAM *stream = f->stream; + /* write data at desired position */ + while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) || + (write (LOCAL->fd,buf,size) < 0))) { + int e; + char tmp[MAILTMPLEN]; + sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno)); + MM_LOG (tmp,ERROR); + MM_DISKERROR (NIL,e,T); /* serious problem, must retry */ + } + f->filepos += size; /* update file position */ +} diff --git a/imap/src/osdep/amiga/mtx.c b/imap/src/osdep/amiga/mtx.c new file mode 100644 index 00000000..8e6f76e8 --- /dev/null +++ b/imap/src/osdep/amiga/mtx.c @@ -0,0 +1,1371 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: MTX mail routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 22 May 1990 + * Last Edited: 11 October 2007 + */ + + +/* FILE TIME SEMANTICS + * + * The atime is the last read time of the file. + * The mtime is the last flags update time of the file. + * The ctime is the last write time of the file. + */ + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include "osdep.h" +#include <pwd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include "misc.h" +#include "dummy.h" +#include "fdstring.h" + +/* MTX I/O stream local data */ + +typedef struct mtx_local { + unsigned int shouldcheck: 1; /* if ping should do a check instead */ + unsigned int mustcheck: 1; /* if ping must do a check instead */ + int fd; /* file descriptor for I/O */ + off_t filesize; /* file size parsed */ + time_t filetime; /* last file time */ + time_t lastsnarf; /* last snarf time */ + unsigned char *buf; /* temporary buffer */ + unsigned long buflen; /* current size of temporary buffer */ +} MTXLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((MTXLOCAL *) stream->local) + + +/* Function prototypes */ + +DRIVER *mtx_valid (char *name); +int mtx_isvalid (char *name,char *tmp); +void *mtx_parameters (long function,void *value); +void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void mtx_list (MAILSTREAM *stream,char *ref,char *pat); +void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat); +long mtx_create (MAILSTREAM *stream,char *mailbox); +long mtx_delete (MAILSTREAM *stream,char *mailbox); +long mtx_rename (MAILSTREAM *stream,char *old,char *newname); +long mtx_status (MAILSTREAM *stream,char *mbx,long flags); +MAILSTREAM *mtx_open (MAILSTREAM *stream); +void mtx_close (MAILSTREAM *stream,long options); +void mtx_flags (MAILSTREAM *stream,char *sequence,long flags); +char *mtx_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags); +long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); +void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); +long mtx_ping (MAILSTREAM *stream); +void mtx_check (MAILSTREAM *stream); +void mtx_snarf (MAILSTREAM *stream); +long mtx_expunge (MAILSTREAM *stream,char *sequence,long options); +long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +char *mtx_file (char *dst,char *name); +long mtx_parse (MAILSTREAM *stream); +MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno); +void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt); +void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag); +unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno, + unsigned long *size); + +/* MTX mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER mtxdriver = { + "mtx", /* driver name */ + /* driver flags */ + DR_LOCAL|DR_MAIL|DR_CRLF|DR_NOSTICKY|DR_LOCKING, + (DRIVER *) NIL, /* next driver */ + mtx_valid, /* mailbox is valid for us */ + mtx_parameters, /* manipulate parameters */ + mtx_scan, /* scan mailboxes */ + mtx_list, /* list mailboxes */ + mtx_lsub, /* list subscribed mailboxes */ + NIL, /* subscribe to mailbox */ + NIL, /* unsubscribe from mailbox */ + dummy_create, /* create mailbox */ + mtx_delete, /* delete mailbox */ + mtx_rename, /* rename mailbox */ + mtx_status, /* status of mailbox */ + mtx_open, /* open mailbox */ + mtx_close, /* close mailbox */ + mtx_flags, /* fetch message "fast" attributes */ + mtx_flags, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + mtx_header, /* fetch message header */ + mtx_text, /* fetch message body */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + mtx_flag, /* modify flags */ + mtx_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + mtx_ping, /* ping mailbox to see if still alive */ + mtx_check, /* check for new messages */ + mtx_expunge, /* expunge deleted messages */ + mtx_copy, /* copy messages to another mailbox */ + mtx_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM mtxproto = {&mtxdriver}; + +/* MTX mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *mtx_valid (char *name) +{ + char tmp[MAILTMPLEN]; + return mtx_isvalid (name,tmp) ? &mtxdriver : NIL; +} + + +/* MTX mail test for valid mailbox + * Accepts: mailbox name + * Returns: T if valid, NIL otherwise + */ + +int mtx_isvalid (char *name,char *tmp) +{ + int fd; + int ret = NIL; + char *s,file[MAILTMPLEN]; + struct stat sbuf; + time_t tp[2]; + errno = EINVAL; /* assume invalid argument */ + /* if file, get its status */ + if ((s = mtx_file (file,name)) && !stat (s,&sbuf)) { + if (!sbuf.st_size) { /* allow empty file if INBOX */ + if ((s = mailboxfile (tmp,name)) && !*s) ret = T; + else errno = 0; /* empty file */ + } + else if ((fd = open (file,O_RDONLY,NIL)) >= 0) { + memset (tmp,'\0',MAILTMPLEN); + if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\015')) && + (s[1] == '\012')) { /* valid format? */ + *s = '\0'; /* tie off header */ + /* must begin with dd-mmm-yy" */ + ret = (((tmp[2] == '-' && tmp[6] == '-') || + (tmp[1] == '-' && tmp[5] == '-')) && + (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL; + } + else errno = -1; /* bogus format */ + close (fd); /* close the file */ + /* \Marked status? */ + if (sbuf.st_ctime > sbuf.st_atime) { + tp[0] = sbuf.st_atime; /* preserve atime and mtime */ + tp[1] = sbuf.st_mtime; + utime (file,tp); /* set the times */ + } + } + } + /* in case INBOX but not mtx format */ + else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1; + return ret; /* return what we should */ +} + +/* MTX manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *mtx_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = mtx_file ((char *) value,"INBOX"); + break; + } + return ret; +} + + +/* MTX mail scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + if (stream) dummy_scan (NIL,ref,pat,contents); +} + + +/* MTX mail list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mtx_list (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_list (NIL,ref,pat); +} + + +/* MTX mail list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_lsub (NIL,ref,pat); +} + +/* MTX mail delete mailbox + * Accepts: MAIL stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long mtx_delete (MAILSTREAM *stream,char *mailbox) +{ + return mtx_rename (stream,mailbox,NIL); +} + + +/* MTX mail rename mailbox + * Accepts: MAIL stream + * old mailbox name + * new mailbox name (or NIL for delete) + * Returns: T on success, NIL on failure + */ + +long mtx_rename (MAILSTREAM *stream,char *old,char *newname) +{ + long ret = T; + char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN]; + int fd,ld; + struct stat sbuf; + if (!mtx_file (file,old) || + (newname && (!((s = mailboxfile (tmp,newname)) && *s) || + ((s = strrchr (tmp,'/')) && !s[1])))) { + sprintf (tmp,newname ? + "Can't rename mailbox %.80s to %.80s: invalid name" : + "Can't delete mailbox %.80s: invalid name", + old,newname); + MM_LOG (tmp,ERROR); + return NIL; + } + else if ((fd = open (file,O_RDWR,NIL)) < 0) { + sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get exclusive parse/append permission */ + if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) { + MM_LOG ("Unable to lock rename mailbox",ERROR); + return NIL; + } + /* lock out other users */ + if (flock (fd,LOCK_EX|LOCK_NB)) { + close (fd); /* couldn't lock, give up on it then */ + sprintf (tmp,"Mailbox %.80s is in use by another process",old); + MM_LOG (tmp,ERROR); + unlockfd (ld,lock); /* release exclusive parse/append permission */ + return NIL; + } + + if (newname) { /* want rename? */ + if (s = strrchr (tmp,'/')) {/* found superior to destination name? */ + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,tmp,get_dir_protection (newname))) + ret = NIL; + else *s = c; /* restore full name */ + } + /* rename the file */ + if (ret && rename (file,tmp)) { + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname, + strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; /* set failure */ + } + } + else if (unlink (file)) { + sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; /* set failure */ + } + flock (fd,LOCK_UN); /* release lock on the file */ + close (fd); /* close the file */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + /* recreate file if renamed INBOX */ + if (ret && !compare_cstring (old,"INBOX")) dummy_create (NIL,"INBOX.MTX"); + return ret; /* return success */ +} + +/* Mtx Mail status + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long mtx_status (MAILSTREAM *stream,char *mbx,long flags) +{ + MAILSTATUS status; + unsigned long i; + MAILSTREAM *tstream = NIL; + MAILSTREAM *systream = NIL; + /* make temporary stream (unless this mbx) */ + if (!stream && !(stream = tstream = + mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL; + status.flags = flags; /* return status values */ + status.messages = stream->nmsgs; + status.recent = stream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++) + if (!mail_elt (stream,i)->seen) status.unseen++; + status.uidnext = stream->uid_last + 1; + status.uidvalidity = stream->uid_validity; + /* calculate post-snarf results */ + if (!status.recent && stream->inbox && + (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) { + status.messages += systream->nmsgs; + status.recent += systream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1; i <= systream->nmsgs; i++) + if (!mail_elt (systream,i)->seen) status.unseen++; + /* kludge but probably good enough */ + status.uidnext += systream->nmsgs; + } + MM_STATUS(stream,mbx,&status);/* pass status to main program */ + if (tstream) mail_close (tstream); + if (systream) mail_close (systream); + return T; /* success */ +} + +/* MTX mail open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *mtx_open (MAILSTREAM *stream) +{ + int fd,ld; + char tmp[MAILTMPLEN]; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return user_flags (&mtxproto); + if (stream->local) fatal ("mtx recycle stream"); + user_flags (stream); /* set up user flags */ + /* canonicalize the mailbox name */ + if (!mtx_file (tmp,stream->mailbox)) { + sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox); + MM_LOG (tmp,ERROR); + } + if (stream->rdonly || + (fd = open (tmp,O_RDWR,NIL)) < 0) { + if ((fd = open (tmp,O_RDONLY,NIL)) < 0) { + sprintf (tmp,"Can't open mailbox: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + else if (!stream->rdonly) { /* got it, but readonly */ + MM_LOG ("Can't get write access to mailbox, access is readonly",WARN); + stream->rdonly = T; + } + } + stream->local = fs_get (sizeof (MTXLOCAL)); + LOCAL->fd = fd; /* bind the file */ + LOCAL->buf = (char *) fs_get (CHUNKSIZE); + LOCAL->buflen = CHUNKSIZE - 1; + /* note if an INBOX or not */ + stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr (tmp); + /* get shared parse permission */ + if ((ld = lockfd (fd,tmp,LOCK_SH)) < 0) { + MM_LOG ("Unable to lock open mailbox",ERROR); + return NIL; + } + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH); /* lock the file */ + (*bn) (BLOCK_NONE,NIL); + unlockfd (ld,tmp); /* release shared parse permission */ + LOCAL->filesize = 0; /* initialize parsed file size */ + /* time not set up yet */ + LOCAL->lastsnarf = LOCAL->filetime = 0; + LOCAL->mustcheck = LOCAL->shouldcheck = NIL; + stream->sequence++; /* bump sequence number */ + /* parse mailbox */ + stream->nmsgs = stream->recent = 0; + if (mtx_ping (stream) && !stream->nmsgs) + MM_LOG ("Mailbox is empty",(long) NIL); + if (!LOCAL) return NIL; /* failure if stream died */ + stream->perm_seen = stream->perm_deleted = + stream->perm_flagged = stream->perm_answered = stream->perm_draft = + stream->rdonly ? NIL : T; + stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff; + return stream; /* return stream to caller */ +} + +/* MTX mail close + * Accepts: MAIL stream + * close options + */ + +void mtx_close (MAILSTREAM *stream,long options) +{ + if (stream && LOCAL) { /* only if a file is open */ + int silent = stream->silent; + stream->silent = T; /* note this stream is dying */ + if (options & CL_EXPUNGE) mtx_expunge (stream,NIL,NIL); + stream->silent = silent; /* restore previous status */ + flock (LOCAL->fd,LOCK_UN); /* unlock local file */ + close (LOCAL->fd); /* close the local file */ + /* free local text buffer */ + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + + +/* MTX mail fetch flags + * Accepts: MAIL stream + * sequence + * option flags + * Sniffs at file to see if some other process changed the flags + */ + +void mtx_flags (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i; + if (mtx_ping (stream) && /* ping mailbox, get new status for messages */ + ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) + if (mail_elt (stream,i)->sequence) mtx_elt (stream,i); +} + +/* MTX mail fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + +char *mtx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags) +{ + *length = 0; /* default to empty */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + /* get to header position */ + lseek (LOCAL->fd,mtx_hdrpos (stream,msgno,length),L_SET); + /* is buffer big enough? */ + if (*length > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1); + } + LOCAL->buf[*length] = '\0'; /* tie off string */ + /* slurp the data */ + read (LOCAL->fd,LOCAL->buf,*length); + return (char *) LOCAL->buf; +} + +/* MTX mail fetch message text (body only) + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: T, always + */ + +long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + FDDATA d; + unsigned long i,j; + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + elt = mtx_elt (stream,msgno); /* get message status */ + /* if message not seen */ + if (!(flags & FT_PEEK) && !elt->seen) { + elt->seen = T; /* mark message as seen */ + /* recalculate status */ + mtx_update_status (stream,msgno,NIL); + MM_FLAGS (stream,msgno); + } + /* find header position */ + i = mtx_hdrpos (stream,msgno,&j); + d.fd = LOCAL->fd; /* set up file descriptor */ + d.pos = i + j; + d.chunk = LOCAL->buf; /* initial buffer chunk */ + d.chunksize = CHUNKSIZE; + INIT (bs,fd_string,&d,elt->rfc822_size - j); + return T; /* success */ +} + +/* MTX mail modify flags + * Accepts: MAIL stream + * sequence + * flag(s) + * option flags + */ + +void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) +{ + time_t tp[2]; + struct stat sbuf; + if (!stream->rdonly) { /* make sure the update takes */ + fsync (LOCAL->fd); + fstat (LOCAL->fd,&sbuf); /* get current write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* make sure read comes after all that */ + utime (stream->mailbox,tp); + } +} + + +/* MTX mail per-message modify flags + * Accepts: MAIL stream + * message cache element + */ + +void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + struct stat sbuf; + /* maybe need to do a checkpoint? */ + if (LOCAL->filetime && !LOCAL->shouldcheck) { + fstat (LOCAL->fd,&sbuf); /* get current write time */ + if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T; + LOCAL->filetime = 0; /* don't do this test for any other messages */ + } + /* recalculate status */ + mtx_update_status (stream,elt->msgno,NIL); +} + +/* MTX mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream still alive, NIL if not + */ + +long mtx_ping (MAILSTREAM *stream) +{ + unsigned long i = 1; + long r = T; + int ld; + char lock[MAILTMPLEN]; + struct stat sbuf; + if (stream && LOCAL) { /* only if stream already open */ + fstat (LOCAL->fd,&sbuf); /* get current file poop */ + if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) && + (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T; + /* check for changed message status */ + if (LOCAL->mustcheck || LOCAL->shouldcheck) { + LOCAL->filetime = sbuf.st_mtime; + if (LOCAL->shouldcheck) /* babble when we do this unilaterally */ + MM_NOTIFY (stream,"[CHECK] Checking for flag updates",NIL); + while (i <= stream->nmsgs) mtx_elt (stream,i++); + LOCAL->mustcheck = LOCAL->shouldcheck = NIL; + } + /* get shared parse/append permission */ + if ((sbuf.st_size != LOCAL->filesize) && + ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) { + /* parse resulting mailbox */ + r = (mtx_parse (stream)) ? T : NIL; + unlockfd (ld,lock); /* release shared parse/append permission */ + } + if (LOCAL) { /* stream must still be alive */ + /* snarf if this is a read-write inbox */ + if (stream->inbox && !stream->rdonly) { + mtx_snarf (stream); + fstat (LOCAL->fd,&sbuf);/* see if file changed now */ + if ((sbuf.st_size != LOCAL->filesize) && + ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) { + /* parse resulting mailbox */ + r = (mtx_parse (stream)) ? T : NIL; + unlockfd (ld,lock); /* release shared parse/append permission */ + } + } + } + } + return r; /* return result of the parse */ +} + + +/* MTX mail check mailbox (reparses status too) + * Accepts: MAIL stream + */ + +void mtx_check (MAILSTREAM *stream) +{ + /* mark that a check is desired */ + if (LOCAL) LOCAL->mustcheck = T; + if (mtx_ping (stream)) MM_LOG ("Check completed",(long) NIL); +} + +/* MTX mail snarf messages from system inbox + * Accepts: MAIL stream + */ + +void mtx_snarf (MAILSTREAM *stream) +{ + unsigned long i = 0; + unsigned long j,r,hdrlen,txtlen; + struct stat sbuf; + char *hdr,*txt,lock[MAILTMPLEN],tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + MAILSTREAM *sysibx = NIL; + int ld; + /* give up if can't get exclusive permission */ + if ((time (0) >= (LOCAL->lastsnarf + + (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) && + strcmp (sysinbox (),stream->mailbox) && + ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) { + MM_CRITICAL (stream); /* go critical */ + /* sizes match and anything in sysinbox? */ + if (!stat (sysinbox (),&sbuf) && sbuf.st_size && + !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) && + (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) && + (!sysibx->rdonly) && (r = sysibx->nmsgs)) { + /* yes, go to end of file in our mailbox */ + lseek (LOCAL->fd,sbuf.st_size,L_SET); + /* for each message in sysibx mailbox */ + while (r && (++i <= sysibx->nmsgs)) { + /* snarf message from system INBOX */ + hdr = cpystr (mail_fetchheader_full (sysibx,i,NIL,&hdrlen,NIL)); + txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_PEEK); + /* if have a message */ + if (j = hdrlen + txtlen) { + /* calculate header line */ + mail_date (LOCAL->buf,elt = mail_elt (sysibx,i)); + sprintf (LOCAL->buf + strlen (LOCAL->buf), + ",%lu;0000000000%02o\015\012",j,(unsigned) + ((fSEEN * elt->seen) + (fDELETED * elt->deleted) + + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + + (fDRAFT * elt->draft))); + /* copy message */ + if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) || + (write (LOCAL->fd,hdr,hdrlen) < 0) || + (write (LOCAL->fd,txt,txtlen) < 0)) r = 0; + } + fs_give ((void **) &hdr); + } + + /* make sure all the updates take */ + if (fsync (LOCAL->fd)) r = 0; + if (r) { /* delete all the messages we copied */ + if (r == 1) strcpy (tmp,"1"); + else sprintf (tmp,"1:%lu",r); + mail_flag (sysibx,tmp,"\\Deleted",ST_SET); + mail_expunge (sysibx); /* now expunge all those messages */ + } + else { + sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno)); + MM_LOG (LOCAL->buf,WARN); + ftruncate (LOCAL->fd,sbuf.st_size); + } + fstat (LOCAL->fd,&sbuf); /* yes, get current file size */ + LOCAL->filetime = sbuf.st_mtime; + } + if (sysibx) mail_close (sysibx); + MM_NOCRITICAL (stream); /* release critical */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + LOCAL->lastsnarf = time (0);/* note time of last snarf */ + } +} + +/* MTX mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T, always + */ + +long mtx_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret; + time_t tp[2]; + struct stat sbuf; + off_t pos = 0; + int ld; + unsigned long i = 1; + unsigned long j,k,m,recent; + unsigned long n = 0; + unsigned long delta = 0; + char lock[MAILTMPLEN]; + MESSAGECACHE *elt; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + if (!(ret = (sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : LONGT) && + mtx_ping (stream))); /* parse sequence if given, ping stream */ + else if (stream->rdonly) MM_LOG ("Expunge ignored on readonly mailbox",WARN); + else { + if (LOCAL->filetime && !LOCAL->shouldcheck) { + fstat (LOCAL->fd,&sbuf); /* get current write time */ + if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T; + } + /* The cretins who designed flock() created a window of vulnerability in + * upgrading locks from shared to exclusive or downgrading from exclusive + * to shared. Rather than maintain the lock at shared status at a minimum, + * flock() actually *releases* the former lock. Obviously they never talked + * to any database guys. Fortunately, we have the parse/append permission + * lock. If we require this lock before going exclusive on the mailbox, + * another process can not sneak in and steal the exclusive mailbox lock on + * us, because it will block on trying to get parse/append permission first. + */ + /* get exclusive parse/append permission */ + if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0) + MM_LOG ("Unable to lock expunge mailbox",ERROR); + /* make sure see any newly-arrived messages */ + else if (!mtx_parse (stream)); + /* get exclusive access */ + else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) { + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH);/* recover previous lock */ + (*bn) (BLOCK_NONE,NIL); + MM_LOG ("Can't expunge because mailbox is in use by another process", + ERROR); + unlockfd (ld,lock); /* release exclusive parse/append permission */ + } + + else { + MM_CRITICAL (stream); /* go critical */ + recent = stream->recent; /* get recent now that pinged and locked */ + /* for each message */ + while (i <= stream->nmsgs) { + /* get cache element */ + elt = mtx_elt (stream,i); + /* number of bytes to smash or preserve */ + k = elt->private.special.text.size + elt->rfc822_size; + /* if need to expunge this message */ + if (elt->deleted && (sequence ? elt->sequence : T)) { + /* if recent, note one less recent message */ + if (elt->recent) --recent; + delta += k; /* number of bytes to delete */ + /* notify upper levels */ + mail_expunged (stream,i); + n++; /* count up one more expunged message */ + } + else if (i++ && delta) {/* preserved message */ + /* first byte to preserve */ + j = elt->private.special.offset; + do { /* read from source position */ + m = min (k,LOCAL->buflen); + lseek (LOCAL->fd,j,L_SET); + read (LOCAL->fd,LOCAL->buf,m); + pos = j - delta; /* write to destination position */ + lseek (LOCAL->fd,pos,L_SET); + while (T) { + lseek (LOCAL->fd,pos,L_SET); + if (write (LOCAL->fd,LOCAL->buf,m) > 0) break; + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } + pos += m; /* new position */ + j += m; /* next chunk, perhaps */ + } while (k -= m); /* until done */ + /* note the new address of this text */ + elt->private.special.offset -= delta; + } + /* preserved but no deleted messages */ + else pos = elt->private.special.offset + k; + } + if (n) { /* truncate file after last message */ + if (pos != (LOCAL->filesize -= delta)) { + sprintf (LOCAL->buf, + "Calculated size mismatch %lu != %lu, delta = %lu", + (unsigned long) pos,(unsigned long) LOCAL->filesize,delta); + MM_LOG (LOCAL->buf,WARN); + LOCAL->filesize = pos;/* fix it then */ + } + ftruncate (LOCAL->fd,LOCAL->filesize); + sprintf (LOCAL->buf,"Expunged %lu messages",n); + /* output the news */ + MM_LOG (LOCAL->buf,(long) NIL); + } + else MM_LOG ("No messages deleted, so no update needed",(long) NIL); + fsync (LOCAL->fd); /* force disk update */ + fstat (LOCAL->fd,&sbuf); /* get new write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* reset atime to now */ + utime (stream->mailbox,tp); + MM_NOCRITICAL (stream); /* release critical */ + /* notify upper level of new mailbox size */ + mail_exists (stream,stream->nmsgs); + mail_recent (stream,recent); + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH);/* allow sharers again */ + (*bn) (BLOCK_NONE,NIL); + unlockfd (ld,lock); /* release exclusive parse/append permission */ + } + } + return ret; +} + +/* MTX mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if success, NIL if failed + */ + +long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + struct stat sbuf; + time_t tp[2]; + MESSAGECACHE *elt; + unsigned long i,j,k; + long ret = LONGT; + int fd,ld; + char file[MAILTMPLEN],lock[MAILTMPLEN]; + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + /* make sure valid mailbox */ + if (!mtx_isvalid (mailbox,LOCAL->buf)) switch (errno) { + case ENOENT: /* no such file? */ + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); + return NIL; + case 0: /* merely empty file? */ + break; + case EACCES: /* file protected */ + sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + case EINVAL: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Invalid MTX-format mailbox name: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + default: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Not a MTX-format mailbox: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) return NIL; + /* got file? */ + if ((fd = open (mtx_file (file,mailbox),O_RDWR,NIL)) < 0) { + sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + MM_CRITICAL (stream); /* go critical */ + /* get exclusive parse/append permission */ + if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) { + MM_LOG ("Unable to lock copy mailbox",ERROR); + MM_NOCRITICAL (stream); + return NIL; + } + fstat (fd,&sbuf); /* get current file size */ + lseek (fd,sbuf.st_size,L_SET);/* move to end of file */ + + /* for each requested message */ + for (i = 1; ret && (i <= stream->nmsgs); i++) + if ((elt = mail_elt (stream,i))->sequence) { + lseek (LOCAL->fd,elt->private.special.offset,L_SET); + /* number of bytes to copy */ + k = elt->private.special.text.size + elt->rfc822_size; + do { /* read from source position */ + j = min (k,LOCAL->buflen); + read (LOCAL->fd,LOCAL->buf,j); + if (write (fd,LOCAL->buf,j) < 0) ret = NIL; + } while (ret && (k -= j));/* until done */ + } + /* make sure all the updates take */ + if (!(ret && (ret = !fsync (fd)))) { + sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + ftruncate (fd,sbuf.st_size); + } + if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */ + /* else preserve \Marked status */ + else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0); + tp[1] = sbuf.st_mtime; /* preserve mtime */ + utime (file,tp); /* set the times */ + close (fd); /* close the file */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + MM_NOCRITICAL (stream); /* release critical */ + /* delete all requested messages */ + if (ret && (options & CP_MOVE)) { + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mtx_elt (stream,i))->sequence) { + elt->deleted = T; /* mark message deleted */ + /* recalculate status */ + mtx_update_status (stream,i,NIL); + } + if (!stream->rdonly) { /* make sure the update takes */ + fsync (LOCAL->fd); + fstat (LOCAL->fd,&sbuf); /* get current write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* make sure atime remains greater */ + utime (stream->mailbox,tp); + } + } + if (ret && mail_parameters (NIL,GET_COPYUID,NIL)) + MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN); + return ret; +} + +/* MTX mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + struct stat sbuf; + int fd,ld,c; + char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN]; + time_t tp[2]; + FILE *df; + MESSAGECACHE elt; + long f; + unsigned long i,uf; + STRING *message; + long ret = LONGT; + /* default stream to prototype */ + if (!stream) stream = user_flags (&mtxproto); + /* make sure valid mailbox */ + if (!mtx_isvalid (mailbox,tmp)) switch (errno) { + case ENOENT: /* no such file? */ + if (!compare_cstring (mailbox,"INBOX")) dummy_create (NIL,"INBOX.MTX"); + else { + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); + return NIL; + } + /* falls through */ + case 0: /* merely empty file? */ + break; + case EACCES: /* file protected */ + sprintf (tmp,"Can't access destination: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + case EINVAL: + sprintf (tmp,"Invalid MTX-format mailbox name: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + default: + sprintf (tmp,"Not a MTX-format mailbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get first message */ + if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL; + + /* open destination mailbox */ + if (((fd = open (mtx_file (file,mailbox),O_WRONLY|O_APPEND,NIL)) < 0) || + !(df = fdopen (fd,"ab"))) { + sprintf (tmp,"Can't open append mailbox: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get parse/append permission */ + if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) { + MM_LOG ("Unable to lock append mailbox",ERROR); + close (fd); + return NIL; + } + MM_CRITICAL (stream); /* go critical */ + fstat (fd,&sbuf); /* get current file size */ + errno = 0; + do { /* parse flags */ + if (!SIZE (message)) { /* guard against zero-length */ + MM_LOG ("Append of zero-length message",ERROR); + ret = NIL; + break; + } + f = mail_parse_flags (stream,flags,&i); + /* reverse bits (dontcha wish we had CIRC?) */ + for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i))); + if (date) { /* parse date if given */ + if (!mail_parse_date (&elt,date)) { + sprintf (tmp,"Bad date in append: %.80s",date); + MM_LOG (tmp,ERROR); + ret = NIL; /* mark failure */ + break; + } + mail_date (tmp,&elt); /* write preseved date */ + } + else internal_date (tmp); /* get current date in IMAP format */ + /* write header */ + if (fprintf (df,"%s,%lu;%010lo%02lo\015\012",tmp,i = SIZE (message),uf, + (unsigned long) f) < 0) ret = NIL; + else { /* write message */ + if (i) do c = 0xff & SNX (message); + while ((putc (c,df) != EOF) && --i); + /* get next message */ + if (i || !MM_APPEND (af) (stream,data,&flags,&date,&message)) ret = NIL; + } + } while (ret && message); + /* if error... */ + if (!ret || (fflush (df) == EOF)) { + ftruncate (fd,sbuf.st_size);/* revert file */ + close (fd); /* make sure fclose() doesn't corrupt us */ + if (errno) { + sprintf (tmp,"Message append failed: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + } + ret = NIL; + } + if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */ + /* else preserve \Marked status */ + else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0); + tp[1] = sbuf.st_mtime; /* preserve mtime */ + utime (file,tp); /* set the times */ + fclose (df); /* close the file */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + MM_NOCRITICAL (stream); /* release critical */ + if (ret && mail_parameters (NIL,GET_APPENDUID,NIL)) + MM_LOG ("Can not return meaningful APPENDUID with this mailbox format", + WARN); + return ret; +} + +/* Internal routines */ + + +/* MTX mail generate file string + * Accepts: temporary buffer to write into + * mailbox name string + * Returns: local file string or NIL if failure + */ + +char *mtx_file (char *dst,char *name) +{ + char tmp[MAILTMPLEN]; + char *s = mailboxfile (dst,name); + /* return our standard inbox */ + return (s && !*s) ? mailboxfile (dst,mtx_isvalid ("~/INBOX",tmp) ? + "~/INBOX" : "INBOX.MTX") : s; +} + +/* MTX mail parse mailbox + * Accepts: MAIL stream + * Returns: T if parse OK + * NIL if failure, stream aborted + */ + +long mtx_parse (MAILSTREAM *stream) +{ + struct stat sbuf; + MESSAGECACHE *elt = NIL; + unsigned char c,*s,*t,*x; + char tmp[MAILTMPLEN]; + unsigned long i,j; + long curpos = LOCAL->filesize; + long nmsgs = stream->nmsgs; + long recent = stream->recent; + short added = NIL; + short silent = stream->silent; + fstat (LOCAL->fd,&sbuf); /* get status */ + if (sbuf.st_size < curpos) { /* sanity check */ + sprintf (tmp,"Mailbox shrank from %lu to %lu!", + (unsigned long) curpos,(unsigned long) sbuf.st_size); + MM_LOG (tmp,ERROR); + mtx_close (stream,NIL); + return NIL; + } + stream->silent = T; /* don't pass up exists events yet */ + while (sbuf.st_size - curpos){/* while there is stuff to parse */ + /* get to that position in the file */ + lseek (LOCAL->fd,curpos,L_SET); + if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) { + sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s", + (unsigned long) curpos,(unsigned long) sbuf.st_size, + i ? strerror (errno) : "no data read"); + MM_LOG (tmp,ERROR); + mtx_close (stream,NIL); + return NIL; + } + LOCAL->buf[i] = '\0'; /* tie off buffer just in case */ + if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) { + sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %s", + (unsigned long) curpos,i,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + mtx_close (stream,NIL); + return NIL; + } + *s = '\0'; /* tie off header line */ + i = (s + 2) - LOCAL->buf; /* note start of text offset */ + if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) { + sprintf (tmp,"Unable to parse internal header at %lu: %s", + (unsigned long) curpos,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + mtx_close (stream,NIL); + return NIL; + } + *s++ = '\0'; *t++ = '\0'; /* tie off fields */ + + added = T; /* note that a new message was added */ + /* swell the cache */ + mail_exists (stream,++nmsgs); + /* instantiate an elt for this message */ + (elt = mail_elt (stream,nmsgs))->valid = T; + elt->private.uid = ++stream->uid_last; + /* note file offset of header */ + elt->private.special.offset = curpos; + /* in case error */ + elt->private.special.text.size = 0; + /* header size not known yet */ + elt->private.msg.header.text.size = 0; + x = s; /* parse the header components */ + if (mail_parse_date (elt,LOCAL->buf) && + (elt->rfc822_size = strtoul (s,(char **) &s,10)) && (!(s && *s)) && + isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) && + isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) && + isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) && + isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12]) + elt->private.special.text.size = i; + else { /* oops */ + sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s", + curpos,(char *) LOCAL->buf,(char *) x,(char *) t); + MM_LOG (tmp,ERROR); + mtx_close (stream,NIL); + return NIL; + } + /* make sure didn't run off end of file */ + if ((curpos += (elt->rfc822_size + i)) > sbuf.st_size) { + sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)", + elt->private.special.offset,(unsigned long) curpos, + (unsigned long) sbuf.st_size); + MM_LOG (tmp,ERROR); + mtx_close (stream,NIL); + return NIL; + } + c = t[10]; /* remember first system flags byte */ + t[10] = '\0'; /* tie off flags */ + j = strtoul (t,NIL,8); /* get user flags value */ + t[10] = c; /* restore first system flags byte */ + /* set up all valid user flags (reversed!) */ + while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) && + stream->user_flags[i]) elt->user_flags |= 1 << i; + /* calculate system flags */ + if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T; + if (j & fDELETED) elt->deleted = T; + if (j & fFLAGGED) elt->flagged = T; + if (j & fANSWERED) elt->answered = T; + if (j & fDRAFT) elt->draft = T; + if (!(j & fOLD)) { /* newly arrived message? */ + elt->recent = T; + recent++; /* count up a new recent message */ + /* mark it as old */ + mtx_update_status (stream,nmsgs,NIL); + } + } + fsync (LOCAL->fd); /* make sure all the fOLD flags take */ + /* update parsed file size and time */ + LOCAL->filesize = sbuf.st_size; + fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */ + LOCAL->filetime = sbuf.st_mtime; + if (added && !stream->rdonly){/* make sure atime updated */ + time_t tp[2]; + tp[0] = time (0); + tp[1] = LOCAL->filetime; + utime (stream->mailbox,tp); + } + stream->silent = silent; /* can pass up events now */ + mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */ + mail_recent (stream,recent); /* and of change in recent messages */ + return LONGT; /* return the winnage */ +} + +/* MTX get cache element with status updating from file + * Accepts: MAIL stream + * message number + * Returns: cache element + */ + +MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno) +{ + MESSAGECACHE *elt = mail_elt (stream,msgno); + struct { /* old flags */ + unsigned int seen : 1; + unsigned int deleted : 1; + unsigned int flagged : 1; + unsigned int answered : 1; + unsigned int draft : 1; + unsigned long user_flags; + } old; + old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged; + old.answered = elt->answered; old.draft = elt->draft; + old.user_flags = elt->user_flags; + mtx_read_flags (stream,elt); + if ((old.seen != elt->seen) || (old.deleted != elt->deleted) || + (old.flagged != elt->flagged) || (old.answered != elt->answered) || + (old.draft != elt->draft) || (old.user_flags != elt->user_flags)) + MM_FLAGS (stream,msgno); /* let top level know */ + return elt; +} + +/* MTX read flags from file + * Accepts: MAIL stream + * Returns: cache element + */ + +void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + unsigned long i,j; + /* noop if readonly and have valid flags */ + if (stream->rdonly && elt->valid) return; + /* set the seek pointer */ + lseek (LOCAL->fd,(off_t) elt->private.special.offset + + elt->private.special.text.size - 14,L_SET); + /* read the new flags */ + if (read (LOCAL->fd,LOCAL->buf,12) < 0) { + sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno)); + fatal (LOCAL->buf); + } + /* calculate system flags */ + i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0'); + elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL; + elt->flagged = i & fFLAGGED ? T : NIL; + elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL; + LOCAL->buf[10] = '\0'; /* tie off flags */ + j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */ + /* set up all valid user flags (reversed!) */ + while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) && + stream->user_flags[i]) elt->user_flags |= 1 << i; + elt->valid = T; /* have valid flags now */ +} + +/* MTX update status string + * Accepts: MAIL stream + * message number + * flag saying whether or not to sync + */ + +void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag) +{ + time_t tp[2]; + struct stat sbuf; + MESSAGECACHE *elt = mail_elt (stream,msgno); + unsigned long j,k = 0; + /* readonly */ + if (stream->rdonly || !elt->valid) mtx_read_flags (stream,elt); + else { /* readwrite */ + j = elt->user_flags; /* get user flags */ + /* reverse bits (dontcha wish we had CIRC?) */ + while (j) k |= 1 << (29 - find_rightmost_bit (&j)); + /* print new flag string */ + sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned) + (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) + + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + + (fDRAFT * elt->draft))); + /* get to that place in the file */ + lseek (LOCAL->fd,(off_t) elt->private.special.offset + + elt->private.special.text.size - 14,L_SET); + /* write new flags */ + write (LOCAL->fd,LOCAL->buf,12); + if (syncflag) { /* sync if requested */ + fsync (LOCAL->fd); + fstat (LOCAL->fd,&sbuf); /* get new write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* make sure read is later */ + utime (stream->mailbox,tp); + } + } +} + +/* MTX locate header for a message + * Accepts: MAIL stream + * message number + * pointer to returned header size + * Returns: position of header in file + */ + +unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno, + unsigned long *size) +{ + unsigned long siz; + long i = 0; + int q = 0; + char *s,tmp[MAILTMPLEN]; + MESSAGECACHE *elt = mtx_elt (stream,msgno); + unsigned long ret = elt->private.special.offset + + elt->private.special.text.size; + /* is header size known? */ + if (!(*size = elt->private.msg.header.text.size)) { + lseek (LOCAL->fd,ret,L_SET);/* get to header position */ + /* search message for CRLF CRLF */ + for (siz = 1,s = tmp; siz <= elt->rfc822_size; siz++) { + /* read another buffer as necessary */ + if ((--i <= 0) && /* buffer empty? */ + (read (LOCAL->fd,s = tmp, + i = min (elt->rfc822_size - siz,(long) MAILTMPLEN)) < 0)) + return ret; /* I/O error? */ + switch (q) { /* sniff at buffer */ + case 0: /* first character */ + q = (*s++ == '\015') ? 1 : 0; + break; + case 1: /* second character */ + q = (*s++ == '\012') ? 2 : 0; + break; + case 2: /* third character */ + q = (*s++ == '\015') ? 3 : 0; + break; + case 3: /* fourth character */ + if (*s++ == '\012') { /* have the sequence? */ + /* yes, note for later */ + elt->private.msg.header.text.size = *size = siz; + return ret; + } + q = 0; /* lost... */ + break; + } + } + /* header consumes entire message */ + elt->private.msg.header.text.size = *size = elt->rfc822_size; + } + return ret; +} diff --git a/imap/src/osdep/amiga/mx.c b/imap/src/osdep/amiga/mx.c new file mode 100644 index 00000000..45495279 --- /dev/null +++ b/imap/src/osdep/amiga/mx.c @@ -0,0 +1,1287 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: MX mail routines + * + * Author(s): Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 3 May 1996 + * Last Edited: 6 January 2008 + */ + + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include "osdep.h" +#include <pwd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include "misc.h" +#include "dummy.h" +#include "fdstring.h" + +/* Index file */ + +#define MXINDEXNAME "/.mxindex" +#define MXINDEX(d,s) strcat (mx_file (d,s),MXINDEXNAME) + + +/* MX I/O stream local data */ + +typedef struct mx_local { + int fd; /* file descriptor of open index */ + unsigned char *buf; /* temporary buffer */ + unsigned long buflen; /* current size of temporary buffer */ + unsigned long cachedtexts; /* total size of all cached texts */ + time_t scantime; /* last time directory scanned */ +} MXLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((MXLOCAL *) stream->local) + + +/* Function prototypes */ + +DRIVER *mx_valid (char *name); +int mx_isvalid (char *name,char *tmp); +int mx_namevalid (char *name); +void *mx_parameters (long function,void *value); +long mx_dirfmttest (char *name); +void mx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +long mx_scan_contents (char *name,char *contents,unsigned long csiz, + unsigned long fsiz); +void mx_list (MAILSTREAM *stream,char *ref,char *pat); +void mx_lsub (MAILSTREAM *stream,char *ref,char *pat); +long mx_subscribe (MAILSTREAM *stream,char *mailbox); +long mx_unsubscribe (MAILSTREAM *stream,char *mailbox); +long mx_create (MAILSTREAM *stream,char *mailbox); +long mx_delete (MAILSTREAM *stream,char *mailbox); +long mx_rename (MAILSTREAM *stream,char *old,char *newname); +int mx_rename_work (char *src,size_t srcl,char *dst,size_t dstl,char *name); +MAILSTREAM *mx_open (MAILSTREAM *stream); +void mx_close (MAILSTREAM *stream,long options); +void mx_fast (MAILSTREAM *stream,char *sequence,long flags); +char *mx_fast_work (MAILSTREAM *stream,MESSAGECACHE *elt); +char *mx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags); +long mx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +void mx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); +void mx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); +long mx_ping (MAILSTREAM *stream); +void mx_check (MAILSTREAM *stream); +long mx_expunge (MAILSTREAM *stream,char *sequence,long options); +long mx_copy (MAILSTREAM *stream,char *sequence,char *mailbox, + long options); +long mx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); +long mx_append_msg (MAILSTREAM *stream,char *flags,MESSAGECACHE *elt, + STRING *st,SEARCHSET *set); + +int mx_select (struct direct *name); +int mx_numsort (const void *d1,const void *d2); +char *mx_file (char *dst,char *name); +long mx_lockindex (MAILSTREAM *stream); +void mx_unlockindex (MAILSTREAM *stream); +void mx_setdate (char *file,MESSAGECACHE *elt); + + +/* MX mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER mxdriver = { + "mx", /* driver name */ + /* driver flags */ + DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT, + (DRIVER *) NIL, /* next driver */ + mx_valid, /* mailbox is valid for us */ + mx_parameters, /* manipulate parameters */ + mx_scan, /* scan mailboxes */ + mx_list, /* find mailboxes */ + mx_lsub, /* find subscribed mailboxes */ + mx_subscribe, /* subscribe to mailbox */ + mx_unsubscribe, /* unsubscribe from mailbox */ + mx_create, /* create mailbox */ + mx_delete, /* delete mailbox */ + mx_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + mx_open, /* open mailbox */ + mx_close, /* close mailbox */ + mx_fast, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + mx_header, /* fetch message header only */ + mx_text, /* fetch message body only */ + NIL, /* fetch partial message test */ + NIL, /* unique identifier */ + NIL, /* message number */ + mx_flag, /* modify flags */ + mx_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + mx_ping, /* ping mailbox to see if still alive */ + mx_check, /* check for new messages */ + mx_expunge, /* expunge deleted messages */ + mx_copy, /* copy messages to another mailbox */ + mx_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM mxproto = {&mxdriver}; + +/* MX mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *mx_valid (char *name) +{ + char tmp[MAILTMPLEN]; + return mx_isvalid (name,tmp) ? &mxdriver : NIL; +} + + +/* MX mail test for valid mailbox + * Accepts: mailbox name + * temporary buffer to use + * Returns: T if valid, NIL otherwise with errno holding dir stat error + */ + +int mx_isvalid (char *name,char *tmp) +{ + struct stat sbuf; + errno = NIL; /* zap error */ + if ((strlen (name) <= NETMAXMBX) && *mx_file (tmp,name) && + !stat (tmp,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) { + /* name is directory; is it mx? */ + if (!stat (MXINDEX (tmp,name),&sbuf) && + ((sbuf.st_mode & S_IFMT) == S_IFREG)) return T; + errno = NIL; /* directory but not mx */ + } + else if (!compare_cstring (name,"INBOX")) errno = NIL; + return NIL; +} + + +/* MX mail test for valid mailbox + * Accepts: mailbox name + * Returns: T if valid, NIL otherwise + */ + +int mx_namevalid (char *name) +{ + char *s = (*name == '/') ? name + 1 : name; + while (s && *s) { /* make sure valid name */ + if (isdigit (*s)) s++; /* digit, check this node further... */ + else if (*s == '/') break; /* all digit node, barf */ + /* non-digit, skip to next node or return */ + else if (!((s = strchr (s+1,'/')) && *++s)) return T; + } + return NIL; /* all numeric or empty node */ +} + +/* MX manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *mx_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = mailboxfile ((char *) value,"~/INBOX"); + break; + case GET_DIRFMTTEST: + ret = (void *) mx_dirfmttest; + break; + case GET_SCANCONTENTS: + ret = (void *) mx_scan_contents; + break; + } + return ret; +} + + +/* MX test for directory format internal node + * Accepts: candidate node name + * Returns: T if internal name, NIL otherwise + */ + +long mx_dirfmttest (char *name) +{ + int c; + /* success if index name or all-numberic */ + if (strcmp (name,MXINDEXNAME+1)) + while (c = *name++) if (!isdigit (c)) return NIL; + return LONGT; +} + +/* MX scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void mx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + if (stream) dummy_scan (NIL,ref,pat,contents); +} + + +/* MX scan mailbox for contents + * Accepts: mailbox name + * desired contents + * contents size + * file size (ignored) + * Returns: NIL if contents not found, T if found + */ + +long mx_scan_contents (char *name,char *contents,unsigned long csiz, + unsigned long fsiz) +{ + long i,nfiles; + void *a; + char *s; + long ret = NIL; + size_t namelen = strlen (name); + struct stat sbuf; + struct direct **names = NIL; + if ((nfiles = scandir (name,&names,mx_select,mx_numsort)) > 0) + for (i = 0; i < nfiles; ++i) { + if (!ret) { + sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2), + "%s/%s",name,names[i]->d_name); + if (!stat (s,&sbuf) && (csiz <= sbuf.st_size)) + ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size); + fs_give ((void **) &s); + } + fs_give ((void **) &names[i]); + } + /* free directory list */ + if (a = (void *) names) fs_give ((void **) &a); + return ret; +} + +/* MX list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mx_list (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_list (NIL,ref,pat); +} + + +/* MX list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void mx_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_lsub (NIL,ref,pat); +} + +/* MX mail subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long mx_subscribe (MAILSTREAM *stream,char *mailbox) +{ + return sm_subscribe (mailbox); +} + + +/* MX mail unsubscribe to mailbox + * Accepts: mail stream + * mailbox to delete from subscription list + * Returns: T on success, NIL on failure + */ + +long mx_unsubscribe (MAILSTREAM *stream,char *mailbox) +{ + return sm_unsubscribe (mailbox); +} + +/* MX mail create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long mx_create (MAILSTREAM *stream,char *mailbox) +{ + DRIVER *test; + int fd; + char *s,tmp[MAILTMPLEN]; + int mask = umask (0); + long ret = NIL; + if (!mx_namevalid (mailbox)) /* validate name */ + sprintf (tmp,"Can't create mailbox %.80s: invalid MX-format name",mailbox); + /* must not already exist */ + else if ((test = mail_valid (NIL,mailbox,NIL)) && + strcmp (test->name,"dummy")) + sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox); + /* create directory */ + else if (!dummy_create_path (stream,MXINDEX (tmp,mailbox), + get_dir_protection (mailbox))) + sprintf (tmp,"Can't create mailbox %.80s: %s",mailbox,strerror (errno)); + else { /* success */ + /* set index protection */ + set_mbx_protections (mailbox,tmp); + /* tie off directory name */ + *(s = strrchr (tmp,'/') + 1) = '\0'; + /* set directory protection */ + set_mbx_protections (mailbox,tmp); + ret = LONGT; + } + umask (mask); /* restore mask */ + if (!ret) MM_LOG (tmp,ERROR); /* some error */ + return ret; +} + +/* MX mail delete mailbox + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long mx_delete (MAILSTREAM *stream,char *mailbox) +{ + DIR *dirp; + struct direct *d; + char *s; + char tmp[MAILTMPLEN]; + if (!mx_isvalid (mailbox,tmp)) + sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox); + /* delete index */ + else if (unlink (MXINDEX (tmp,mailbox))) + sprintf (tmp,"Can't delete mailbox %.80s index: %s", + mailbox,strerror (errno)); + else { /* get directory name */ + *(s = strrchr (tmp,'/')) = '\0'; + if (dirp = opendir (tmp)) { /* open directory */ + *s++ = '/'; /* restore delimiter */ + /* massacre messages */ + while (d = readdir (dirp)) if (mx_select (d)) { + strcpy (s,d->d_name); /* make path */ + unlink (tmp); /* sayonara */ + } + closedir (dirp); /* flush directory */ + *(s = strrchr (tmp,'/')) = '\0'; + if (rmdir (tmp)) { /* try to remove the directory */ + sprintf (tmp,"Can't delete name %.80s: %s",mailbox,strerror (errno)); + MM_LOG (tmp,WARN); + } + } + return T; /* always success */ + } + MM_LOG (tmp,ERROR); /* something failed */ + return NIL; +} + +/* MX mail rename mailbox + * Accepts: MX mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long mx_rename (MAILSTREAM *stream,char *old,char *newname) +{ + char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN]; + struct stat sbuf; + if (!mx_isvalid (old,tmp)) + sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old); + else if (!mx_namevalid (newname)) + sprintf (tmp,"Can't rename to mailbox %.80s: invalid MX-format name", + newname); + /* new mailbox name must not be valid */ + else if (mx_isvalid (newname,tmp)) + sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists", + newname); + else { + mx_file (tmp,old); /* build old directory name */ + mx_file (tmp1,newname); /* and new directory name */ + /* easy if not INBOX */ + if (compare_cstring (old,"INBOX")) { + /* found superior to destination name? */ + if (s = strrchr (mx_file (tmp1,newname),'/')) { + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,tmp1,get_dir_protection (newname))) + return NIL; + *s = c; /* restore full name */ + } + if (!rename (tmp,tmp1)) return LONGT; + } + + /* RFC 3501 requires this */ + else if (dummy_create_path (stream,strcat (tmp1,"/"), + get_dir_protection (newname))) { + void *a; + int i,n,lasterror; + struct direct **names = NIL; + size_t srcl = strlen (tmp); + size_t dstl = strlen (tmp1); + /* rename each mx file to new directory */ + for (i = lasterror = 0,n = scandir (tmp,&names,mx_select,mx_numsort); + i < n; ++i) { + if (mx_rename_work (tmp,srcl,tmp1,dstl,names[i]->d_name)) + lasterror = errno; + fs_give ((void **) &names[i]); + } + /* free directory list */ + if (a = (void *) names) fs_give ((void **) &a); + if (lasterror || mx_rename_work (tmp,srcl,tmp1,dstl,MXINDEXNAME+1)) + errno = lasterror; + else return mx_create (NIL,"INBOX"); + } + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s", + old,newname,strerror (errno)); + } + MM_LOG (tmp,ERROR); /* something failed */ + return NIL; +} + + +/* MX rename worker routine + * Accepts: source directory name + * source directory name length + * destination directory name + * destination directory name length + * name of node to move + * Returns: zero if success, non-zero if error + */ + +int mx_rename_work (char *src,size_t srcl,char *dst,size_t dstl,char *name) +{ + int ret; + size_t len = strlen (name); + char *s = (char *) fs_get (srcl + len + 2); + char *d = (char *) fs_get (dstl + len + 1); + sprintf (s,"%s/%s",src,name); + sprintf (d,"%s%s",dst,name); + ret = rename (s,d); + fs_give ((void **) &s); + fs_give ((void **) &d); + return ret; +} + +/* MX mail open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *mx_open (MAILSTREAM *stream) +{ + char tmp[MAILTMPLEN]; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return user_flags (&mxproto); + if (stream->local) fatal ("mx recycle stream"); + stream->local = fs_get (sizeof (MXLOCAL)); + /* note if an INBOX or not */ + stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); + mx_file (tmp,stream->mailbox);/* get directory name */ + /* canonicalize mailbox name */ + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr (tmp); + /* make temporary buffer */ + LOCAL->buf = (char *) fs_get (CHUNKSIZE); + LOCAL->buflen = CHUNKSIZE - 1; + LOCAL->scantime = 0; /* not scanned yet */ + LOCAL->fd = -1; /* no index yet */ + LOCAL->cachedtexts = 0; /* no cached texts */ + stream->sequence++; /* bump sequence number */ + /* parse mailbox */ + stream->nmsgs = stream->recent = 0; + if (mx_ping (stream) && !(stream->nmsgs || stream->silent)) + MM_LOG ("Mailbox is empty",(long) NIL); + stream->perm_seen = stream->perm_deleted = stream->perm_flagged = + stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T; + stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff; + stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? + NIL : T; /* can we create new user flags? */ + return stream; /* return stream to caller */ +} + +/* MX mail close + * Accepts: MAIL stream + * close options + */ + +void mx_close (MAILSTREAM *stream,long options) +{ + if (LOCAL) { /* only if a file is open */ + int silent = stream->silent; + stream->silent = T; /* note this stream is dying */ + if (options & CL_EXPUNGE) mx_expunge (stream,NIL,NIL); + /* free local scratch buffer */ + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + stream->silent = silent; /* reset silent state */ + } +} + +/* MX mail fetch fast information + * Accepts: MAIL stream + * sequence + * option flags + */ + +void mx_fast (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i; + MESSAGECACHE *elt; + if (stream && LOCAL && + ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) mx_fast_work (stream,elt); +} + + +/* MX mail fetch fast information + * Accepts: MAIL stream + * message cache element + * Returns: name of message file + */ + +char *mx_fast_work (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + struct stat sbuf; + struct tm *tm; + /* build message file name */ + sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,elt->private.uid); + /* have size yet? */ + if (!elt->rfc822_size && !stat (LOCAL->buf,&sbuf)) { + /* make plausible IMAPish date string */ + tm = gmtime (&sbuf.st_mtime); + elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1; + elt->year = tm->tm_year + 1900 - BASEYEAR; + elt->hours = tm->tm_hour; elt->minutes = tm->tm_min; + elt->seconds = tm->tm_sec; + elt->zhours = 0; elt->zminutes = 0; elt->zoccident = 0; + elt->rfc822_size = sbuf.st_size; + } + return (char *) LOCAL->buf; /* return file name */ +} + +/* MX mail fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + +char *mx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, + long flags) +{ + unsigned long i; + int fd; + MESSAGECACHE *elt; + *length = 0; /* default to empty */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + elt = mail_elt (stream,msgno);/* get elt */ + if (!elt->private.msg.header.text.data) { + /* purge cache if too big */ + if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) { + mail_gc (stream,GC_TEXTS);/* just can't keep that much */ + LOCAL->cachedtexts = 0; + } + if ((fd = open (mx_fast_work (stream,elt),O_RDONLY,NIL)) < 0) return ""; + /* is buffer big enough? */ + if (elt->rfc822_size > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = elt->rfc822_size) + 1); + } + /* slurp message */ + read (fd,LOCAL->buf,elt->rfc822_size); + /* tie off file */ + LOCAL->buf[elt->rfc822_size] = '\0'; + close (fd); /* flush message file */ + /* find end of header */ + if (elt->rfc822_size < 4) i = 0; + else for (i = 4; (i < elt->rfc822_size) && + !((LOCAL->buf[i - 4] == '\015') && + (LOCAL->buf[i - 3] == '\012') && + (LOCAL->buf[i - 2] == '\015') && + (LOCAL->buf[i - 1] == '\012')); i++); + /* copy header */ + cpytxt (&elt->private.msg.header.text,LOCAL->buf,i); + cpytxt (&elt->private.msg.text.text,LOCAL->buf+i,elt->rfc822_size - i); + /* add to cached size */ + LOCAL->cachedtexts += elt->rfc822_size; + } + *length = elt->private.msg.header.text.size; + return (char *) elt->private.msg.header.text.data; +} + +/* MX mail fetch message text (body only) + * Accepts: MAIL stream + * message # to fetch + * pointer to returned stringstruct + * option flags + * Returns: T on success, NIL on failure + */ + +long mx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + unsigned long i; + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + elt = mail_elt (stream,msgno); + /* snarf message if don't have it yet */ + if (!elt->private.msg.text.text.data) { + mx_header (stream,msgno,&i,flags); + if (!elt->private.msg.text.text.data) return NIL; + } + /* mark as seen */ + if (!(flags & FT_PEEK) && mx_lockindex (stream)) { + elt->seen = T; + mx_unlockindex (stream); + MM_FLAGS (stream,msgno); + } + INIT (bs,mail_string,elt->private.msg.text.text.data, + elt->private.msg.text.text.size); + return T; +} + +/* MX mail modify flags + * Accepts: MAIL stream + * sequence + * flag(s) + * option flags + */ + +void mx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) +{ + mx_unlockindex (stream); /* finished with index */ +} + + +/* MX per-message modify flags + * Accepts: MAIL stream + * message cache element + */ + +void mx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + mx_lockindex (stream); /* lock index if not already locked */ +} + +/* MX mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +long mx_ping (MAILSTREAM *stream) +{ + MAILSTREAM *sysibx = NIL; + MESSAGECACHE *elt,*selt; + struct stat sbuf; + char *s,tmp[MAILTMPLEN]; + int fd; + unsigned long i,j,r,old; + long nmsgs = stream->nmsgs; + long recent = stream->recent; + int silent = stream->silent; + if (stat (stream->mailbox,&sbuf)) return NIL; + stream->silent = T; /* don't pass up exists events yet */ + if (sbuf.st_ctime != LOCAL->scantime) { + struct direct **names = NIL; + long nfiles = scandir (stream->mailbox,&names,mx_select,mx_numsort); + if (nfiles < 0) nfiles = 0; /* in case error */ + old = stream->uid_last; + /* note scanned now */ + LOCAL->scantime = sbuf.st_ctime; + /* scan directory */ + for (i = 0; i < nfiles; ++i) { + /* if newly seen, add to list */ + if ((j = atoi (names[i]->d_name)) > old) { + /* swell the cache */ + mail_exists (stream,++nmsgs); + stream->uid_last = (elt = mail_elt (stream,nmsgs))->private.uid = j; + elt->valid = T; /* note valid flags */ + if (old) { /* other than the first pass? */ + elt->recent = T; /* yup, mark as recent */ + recent++; /* bump recent count */ + } + } + fs_give ((void **) &names[i]); + } + /* free directory */ + if (s = (void *) names) fs_give ((void **) &s); + } + stream->nmsgs = nmsgs; /* don't upset mail_uid() */ + + /* if INBOX, snarf from system INBOX */ + if (mx_lockindex (stream) && stream->inbox && + !strcmp (sysinbox (),stream->mailbox)) { + old = stream->uid_last; + MM_CRITICAL (stream); /* go critical */ + /* see if anything in system inbox */ + if (!stat (sysinbox (),&sbuf) && sbuf.st_size && + (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) && + !sysibx->rdonly && (r = sysibx->nmsgs)) { + for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */ + /* build file name we will use */ + sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,++old); + /* snarf message from Berkeley mailbox */ + selt = mail_elt (sysibx,i); + if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) + >= 0) && + (s = mail_fetchheader_full (sysibx,i,NIL,&j,FT_PEEK)) && + (write (fd,s,j) == j) && + (s = mail_fetchtext_full (sysibx,i,&j,FT_PEEK)) && + (write (fd,s,j) == j) && !fsync (fd) && !close (fd)) { + /* swell the cache */ + mail_exists (stream,++nmsgs); + stream->uid_last = /* create new elt, note its file number */ + (elt = mail_elt (stream,nmsgs))->private.uid = old; + recent++; /* bump recent count */ + /* set up initial flags and date */ + elt->valid = elt->recent = T; + elt->seen = selt->seen; + elt->deleted = selt->deleted; + elt->flagged = selt->flagged; + elt->answered = selt->answered; + elt->draft = selt->draft; + elt->day = selt->day;elt->month = selt->month;elt->year = selt->year; + elt->hours = selt->hours;elt->minutes = selt->minutes; + elt->seconds = selt->seconds; + elt->zhours = selt->zhours; elt->zminutes = selt->zminutes; + elt->zoccident = selt->zoccident; + mx_setdate (LOCAL->buf,elt); + sprintf (tmp,"%lu",i);/* delete it from the sysinbox */ + mail_flag (sysibx,tmp,"\\Deleted",ST_SET); + } + else { /* failed to snarf */ + if (fd) { /* did it ever get opened? */ + close (fd); /* close descriptor */ + unlink (LOCAL->buf);/* flush this file */ + } + sprintf (tmp,"Message copy to MX mailbox failed: %.80s", + s,strerror (errno)); + MM_LOG (tmp,ERROR); + r = 0; /* stop the snarf in its tracks */ + } + } + /* update scan time */ + if (!stat (stream->mailbox,&sbuf)) LOCAL->scantime = sbuf.st_ctime; + mail_expunge (sysibx); /* now expunge all those messages */ + } + if (sysibx) mail_close (sysibx); + MM_NOCRITICAL (stream); /* release critical */ + } + mx_unlockindex (stream); /* done with index */ + stream->silent = silent; /* can pass up events now */ + mail_exists (stream,nmsgs); /* notify upper level of mailbox size */ + mail_recent (stream,recent); + return T; /* return that we are alive */ +} + +/* MX mail check mailbox + * Accepts: MAIL stream + */ + +void mx_check (MAILSTREAM *stream) +{ + if (mx_ping (stream)) MM_LOG ("Check completed",(long) NIL); +} + + +/* MX mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T, always + */ + +long mx_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret; + MESSAGECACHE *elt; + unsigned long i = 1; + unsigned long n = 0; + unsigned long recent = stream->recent; + if (ret = (sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : LONGT) && + mx_lockindex (stream)) { /* lock the index */ + MM_CRITICAL (stream); /* go critical */ + while (i <= stream->nmsgs) {/* for each message */ + elt = mail_elt (stream,i);/* if deleted, need to trash it */ + if (elt->deleted && (sequence ? elt->sequence : T)) { + sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,elt->private.uid); + if (unlink (LOCAL->buf)) {/* try to delete the message */ + sprintf (LOCAL->buf,"Expunge of message %lu failed, aborted: %s",i, + strerror (errno)); + MM_LOG (LOCAL->buf,(long) NIL); + break; + } + /* note uncached */ + LOCAL->cachedtexts -= ((elt->private.msg.header.text.data ? + elt->private.msg.header.text.size : 0) + + (elt->private.msg.text.text.data ? + elt->private.msg.text.text.size : 0)); + mail_gc_msg (&elt->private.msg,GC_ENV | GC_TEXTS); + if(elt->recent)--recent;/* if recent, note one less recent message */ + mail_expunged(stream,i);/* notify upper levels */ + n++; /* count up one more expunged message */ + } + else i++; /* otherwise try next message */ + } + if (n) { /* output the news if any expunged */ + sprintf (LOCAL->buf,"Expunged %lu messages",n); + MM_LOG (LOCAL->buf,(long) NIL); + } + else MM_LOG ("No messages deleted, so no update needed",(long) NIL); + MM_NOCRITICAL (stream); /* release critical */ + mx_unlockindex (stream); /* finished with index */ + /* notify upper level of new mailbox size */ + mail_exists (stream,stream->nmsgs); + mail_recent (stream,recent); + } + return ret; +} + +/* MX mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if copy successful, else NIL + */ + +long mx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + FDDATA d; + STRING st; + MESSAGECACHE *elt; + MAILSTREAM *astream; + struct stat sbuf; + int fd; + unsigned long i,j,uid,uidv; + char *t,tmp[MAILTMPLEN]; + long ret; + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + /* make sure valid mailbox */ + if (!mx_valid (mailbox)) switch (errno) { + case NIL: /* no error in stat() */ + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Not a MX-format mailbox: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + default: /* some stat() error */ + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); + return NIL; + } + /* copy the messages */ + if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)))); + /* acquire stream to append to */ + else if (!(astream = mail_open (NIL,mailbox,OP_SILENT))) { + MM_LOG ("Can't open copy mailbox",ERROR); + ret = NIL; + } + else { + MM_CRITICAL (stream); /* go critical */ + if (!(ret = mx_lockindex (astream))) + MM_LOG ("Message copy failed: unable to lock index",ERROR); + else { + + copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL); + SEARCHSET *source = cu ? mail_newsearchset () : NIL; + SEARCHSET *dest = cu ? mail_newsearchset () : NIL; + for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); i++) + if ((elt = mail_elt (stream,i))->sequence) { + if (ret = ((fd = open (mx_fast_work (stream,elt),O_RDONLY,NIL)) + >= 0)) { + fstat (fd,&sbuf); /* get size of message */ + d.fd = fd; /* set up file descriptor */ + d.pos = 0; /* start of file */ + d.chunk = LOCAL->buf; + d.chunksize = CHUNKSIZE; + INIT (&st,fd_string,&d,sbuf.st_size); + /* init flag string */ + tmp[0] = tmp[1] = '\0'; + if (j = elt->user_flags) do + if (t = stream->user_flags[find_rightmost_bit (&j)]) + strcat (strcat (tmp," "),t); + while (j); + if (elt->seen) strcat (tmp," \\Seen"); + if (elt->deleted) strcat (tmp," \\Deleted"); + if (elt->flagged) strcat (tmp," \\Flagged"); + if (elt->answered) strcat (tmp," \\Answered"); + if (elt->draft) strcat (tmp," \\Draft"); + tmp[0] = '('; /* open list */ + strcat (tmp,")"); /* close list */ + if (ret = mx_append_msg (astream,tmp,elt,&st,dest)) { + /* add to source set if needed */ + if (source) mail_append_set (source,mail_uid (stream,i)); + /* delete if doing a move */ + if (options & CP_MOVE) elt->deleted = T; + } + } + } + /* return sets if doing COPYUID */ + if (cu && ret) (*cu) (stream,mailbox,astream->uid_validity,source,dest); + else { /* flush any sets we may have built */ + mail_free_searchset (&source); + mail_free_searchset (&dest); + } + mx_unlockindex (astream); /* unlock index */ + } + MM_NOCRITICAL (stream); + mail_close (astream); /* finished with append stream */ + } + return ret; /* return success */ +} + +/* MX mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long mx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + MESSAGECACHE elt; + MAILSTREAM *astream; + char *flags,*date,tmp[MAILTMPLEN]; + STRING *message; + long ret = LONGT; + /* default stream to prototype */ + if (!stream) stream = user_flags (&mxproto); + /* N.B.: can't use LOCAL->buf for tmp */ + /* make sure valid mailbox */ + if (!mx_isvalid (mailbox,tmp)) switch (errno) { + case ENOENT: /* no such file? */ + if (!compare_cstring (mailbox,"INBOX")) mx_create (NIL,"INBOX"); + else { + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); + return NIL; + } + /* falls through */ + case 0: /* merely empty file? */ + break; + case EINVAL: + sprintf (tmp,"Invalid MX-format mailbox name: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + default: + sprintf (tmp,"Not a MX-format mailbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + + /* get first message */ + if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL; + if (!(astream = mail_open (NIL,mailbox,OP_SILENT))) { + MM_LOG ("Can't open append mailbox",ERROR); + return NIL; + } + MM_CRITICAL (astream); /* go critical */ + /* lock the index */ + if (!(ret = mx_lockindex (astream))) + MM_LOG ("Message append failed: unable to lock index",ERROR); + else { + appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); + SEARCHSET *dst = au ? mail_newsearchset () : NIL; + do { + /* guard against zero-length */ + if (!(ret = SIZE (message))) + MM_LOG ("Append of zero-length message",ERROR); + else if (date && !(ret = mail_parse_date (&elt,date))) { + sprintf (tmp,"Bad date in append: %.80s",date); + MM_LOG (tmp,ERROR); + } + else ret = mx_append_msg (astream,flags,date ? &elt : NIL,message,dst)&& + MM_APPEND (af) (stream,data,&flags,&date,&message); + } while (ret && message); + /* return sets if doing APPENDUID */ + if (au && ret) (*au) (mailbox,astream->uid_validity,dst); + else mail_free_searchset (&dst); + mx_unlockindex (astream); /* unlock index */ + } + MM_NOCRITICAL (astream); /* release critical */ + mail_close (astream); + return ret; +} + +/* MX mail append single message + * Accepts: MAIL stream + * flags for new message if non-NIL + * elt with source date if non-NIL + * stringstruct of message text + * searchset to place UID + * Returns: T if success, NIL if failure + */ + +long mx_append_msg (MAILSTREAM *stream,char *flags,MESSAGECACHE *elt, + STRING *st,SEARCHSET *set) +{ + char tmp[MAILTMPLEN]; + int fd; + unsigned long uf; + long f = mail_parse_flags (stream,flags,&uf); + /* make message file name */ + sprintf (tmp,"%s/%lu",stream->mailbox,++stream->uid_last); + if ((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) { + sprintf (tmp,"Can't create append message: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + while (SIZE (st)) { /* copy the file */ + if (st->cursize && (write (fd,st->curpos,st->cursize) < 0)) { + unlink (tmp); /* delete file */ + close (fd); /* close the file */ + sprintf (tmp,"Message append failed: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + SETPOS (st,GETPOS (st) + st->cursize); + } + close (fd); /* close the file */ + if (elt) mx_setdate (tmp,elt);/* set file date */ + /* swell the cache */ + mail_exists (stream,++stream->nmsgs); + /* copy flags */ + mail_append_set (set,(elt = mail_elt (stream,stream->nmsgs))->private.uid = + stream->uid_last); + if (f&fSEEN) elt->seen = T; + if (f&fDELETED) elt->deleted = T; + if (f&fFLAGGED) elt->flagged = T; + if (f&fANSWERED) elt->answered = T; + if (f&fDRAFT) elt->draft = T; + elt->user_flags |= uf; + return LONGT; +} + +/* Internal routines */ + + +/* MX file name selection test + * Accepts: candidate directory entry + * Returns: T to use file name, NIL to skip it + */ + +int mx_select (struct direct *name) +{ + char c; + char *s = name->d_name; + while (c = *s++) if (!isdigit (c)) return NIL; + return T; +} + + +/* MX file name comparision + * Accepts: first candidate directory entry + * second candidate directory entry + * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2 + */ + +int mx_numsort (const void *d1,const void *d2) +{ + return atoi ((*(struct direct **) d1)->d_name) - + atoi ((*(struct direct **) d2)->d_name); +} + + +/* MX mail build file name + * Accepts: destination string + * source + * Returns: destination + */ + +char *mx_file (char *dst,char *name) +{ + char *s; + /* empty string if mailboxfile fails */ + if (!mailboxfile (dst,name)) *dst = '\0'; + /* driver-selected INBOX */ + else if (!*dst) mailboxfile (dst,"~/INBOX"); + /* tie off unnecessary trailing / */ + else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0'; + return dst; +} + +/* MX read and lock index + * Accepts: MAIL stream + * Returns: T if success, NIL if failure + */ + +long mx_lockindex (MAILSTREAM *stream) +{ + unsigned long uf,sf,uid; + int k = 0; + unsigned long msgno = 1; + struct stat sbuf; + char *s,*t,*idx,tmp[2*MAILTMPLEN]; + MESSAGECACHE *elt; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + if ((LOCAL->fd < 0) && /* get index file, no-op if already have it */ + (LOCAL->fd = open (strcat (strcpy (tmp,stream->mailbox),MXINDEXNAME), + O_RDWR|O_CREAT, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) + >= 0) { + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_EX); /* get exclusive lock */ + (*bn) (BLOCK_NONE,NIL); + fstat (LOCAL->fd,&sbuf); /* get size of index */ + /* slurp index */ + read (LOCAL->fd,s = idx = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size); + idx[sbuf.st_size] = '\0'; /* tie off index */ + /* parse index */ + if (sbuf.st_size) while (s && *s) switch (*s) { + case 'V': /* UID validity record */ + stream->uid_validity = strtoul (s+1,&s,16); + break; + case 'L': /* UID last record */ + stream->uid_last = strtoul (s+1,&s,16); + break; + case 'K': /* keyword */ + /* find end of keyword */ + if (s = strchr (t = ++s,'\n')) { + *s++ = '\0'; /* tie off keyword */ + /* copy keyword */ + if ((k < NUSERFLAGS) && !stream->user_flags[k] && + (strlen (t) <= MAXUSERFLAG)) stream->user_flags[k] = cpystr (t); + k++; /* one more keyword */ + } + break; + + case 'M': /* message status record */ + uid = strtoul (s+1,&s,16);/* get UID for this message */ + if (*s == ';') { /* get user flags */ + uf = strtoul (s+1,&s,16); + if (*s == '.') { /* get system flags */ + sf = strtoul (s+1,&s,16); + while ((msgno <= stream->nmsgs) && (mail_uid (stream,msgno) < uid)) + msgno++; /* find message number for this UID */ + if ((msgno <= stream->nmsgs) && (mail_uid (stream,msgno) == uid)) { + (elt = mail_elt (stream,msgno))->valid = T; + elt->user_flags=uf; /* set user and system flags in elt */ + if (sf & fSEEN) elt->seen = T; + if (sf & fDELETED) elt->deleted = T; + if (sf & fFLAGGED) elt->flagged = T; + if (sf & fANSWERED) elt->answered = T; + if (sf & fDRAFT) elt->draft = T; + } + break; + } + } + default: /* bad news */ + sprintf (tmp,"Error in index: %.80s",s); + MM_LOG (tmp,ERROR); + *s = NIL; /* ignore remainder of index */ + } + else { /* new index */ + stream->uid_validity = time (0); + user_flags (stream); /* init stream with default user flags */ + } + fs_give ((void **) &idx); /* flush index */ + } + return (LOCAL->fd >= 0) ? T : NIL; +} + +/* MX write and unlock index + * Accepts: MAIL stream + */ + +#define MXIXBUFLEN 2048 + +void mx_unlockindex (MAILSTREAM *stream) +{ + unsigned long i,j; + off_t size = 0; + char *s,tmp[MXIXBUFLEN + 64]; + MESSAGECACHE *elt; + if (LOCAL->fd >= 0) { + lseek (LOCAL->fd,0,L_SET); /* rewind file */ + /* write header */ + sprintf (s = tmp,"V%08lxL%08lx",stream->uid_validity,stream->uid_last); + for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i) + sprintf (s += strlen (s),"K%s\n",stream->user_flags[i]); + /* write messages */ + for (i = 1; i <= stream->nmsgs; i++) { + /* filled buffer? */ + if (((s += strlen (s)) - tmp) > MXIXBUFLEN) { + write (LOCAL->fd,tmp,j = s - tmp); + size += j; + *(s = tmp) = '\0'; /* dump out and restart buffer */ + } + elt = mail_elt (stream,i); + sprintf(s,"M%08lx;%08lx.%04x",elt->private.uid,elt->user_flags,(unsigned) + ((fSEEN * elt->seen) + (fDELETED * elt->deleted) + + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + + (fDRAFT * elt->draft))); + } + /* write tail end of buffer */ + if ((s += strlen (s)) != tmp) { + write (LOCAL->fd,tmp,j = s - tmp); + size += j; + } + ftruncate (LOCAL->fd,size); + flock (LOCAL->fd,LOCK_UN); /* unlock the index */ + close (LOCAL->fd); /* finished with file */ + LOCAL->fd = -1; /* no index now */ + } +} + +/* Set date for message + * Accepts: file name + * elt containing date + */ + +void mx_setdate (char *file,MESSAGECACHE *elt) +{ + time_t tp[2]; + tp[0] = time (0); /* atime is now */ + tp[1] = mail_longdate (elt); /* modification time */ + utime (file,tp); /* set the times */ +} diff --git a/imap/src/osdep/amiga/news.c b/imap/src/osdep/amiga/news.c new file mode 100644 index 00000000..4cf5bb70 --- /dev/null +++ b/imap/src/osdep/amiga/news.c @@ -0,0 +1,738 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: News routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 4 September 1991 + * Last Edited: 30 January 2007 + */ + + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include "osdep.h" +#include <sys/stat.h> +#include <sys/time.h> +#include "misc.h" +#include "newsrc.h" +#include "fdstring.h" + + +/* news_load_message() flags */ + +#define NLM_HEADER 0x1 /* load message text */ +#define NLM_TEXT 0x2 /* load message text */ + +/* NEWS I/O stream local data */ + +typedef struct news_local { + unsigned int dirty : 1; /* disk copy of .newsrc needs updating */ + char *dir; /* spool directory name */ + char *name; /* local mailbox name */ + unsigned char buf[CHUNKSIZE]; /* scratch buffer */ + unsigned long cachedtexts; /* total size of all cached texts */ +} NEWSLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((NEWSLOCAL *) stream->local) + + +/* Function prototypes */ + +DRIVER *news_valid (char *name); +DRIVER *news_isvalid (char *name,char *mbx); +void *news_parameters (long function,void *value); +void news_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void news_list (MAILSTREAM *stream,char *ref,char *pat); +void news_lsub (MAILSTREAM *stream,char *ref,char *pat); +long news_canonicalize (char *ref,char *pat,char *pattern); +long news_subscribe (MAILSTREAM *stream,char *mailbox); +long news_unsubscribe (MAILSTREAM *stream,char *mailbox); +long news_create (MAILSTREAM *stream,char *mailbox); +long news_delete (MAILSTREAM *stream,char *mailbox); +long news_rename (MAILSTREAM *stream,char *old,char *newname); +MAILSTREAM *news_open (MAILSTREAM *stream); +int news_select (struct direct *name); +int news_numsort (const void *d1,const void *d2); +void news_close (MAILSTREAM *stream,long options); +void news_fast (MAILSTREAM *stream,char *sequence,long flags); +void news_flags (MAILSTREAM *stream,char *sequence,long flags); +void news_load_message (MAILSTREAM *stream,unsigned long msgno,long flags); +char *news_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags); +long news_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +void news_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); +long news_ping (MAILSTREAM *stream); +void news_check (MAILSTREAM *stream); +long news_expunge (MAILSTREAM *stream,char *sequence,long options); +long news_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long news_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +/* News routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER newsdriver = { + "news", /* driver name */ + /* driver flags */ + DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE|DR_NONEWMAIL|DR_DIRFMT, + (DRIVER *) NIL, /* next driver */ + news_valid, /* mailbox is valid for us */ + news_parameters, /* manipulate parameters */ + news_scan, /* scan mailboxes */ + news_list, /* find mailboxes */ + news_lsub, /* find subscribed mailboxes */ + news_subscribe, /* subscribe to mailbox */ + news_unsubscribe, /* unsubscribe from mailbox */ + news_create, /* create mailbox */ + news_delete, /* delete mailbox */ + news_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + news_open, /* open mailbox */ + news_close, /* close mailbox */ + news_fast, /* fetch message "fast" attributes */ + news_flags, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + news_header, /* fetch message header */ + news_text, /* fetch message body */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + NIL, /* modify flags */ + news_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + news_ping, /* ping mailbox to see if still alive */ + news_check, /* check for new messages */ + news_expunge, /* expunge deleted messages */ + news_copy, /* copy messages to another mailbox */ + news_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM newsproto = {&newsdriver}; + +/* News validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *news_valid (char *name) +{ + int fd; + char *s,*t,*u; + struct stat sbuf; + if ((name[0] == '#') && (name[1] == 'n') && (name[2] == 'e') && + (name[3] == 'w') && (name[4] == 's') && (name[5] == '.') && + !strchr (name,'/') && + !stat ((char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),&sbuf) && + ((fd = open ((char *) mail_parameters (NIL,GET_NEWSACTIVE,NIL),O_RDONLY, + NIL)) >= 0)) { + fstat (fd,&sbuf); /* get size of active file */ + /* slurp in active file */ + read (fd,t = s = (char *) fs_get (sbuf.st_size+1),sbuf.st_size); + s[sbuf.st_size] = '\0'; /* tie off file */ + close (fd); /* flush file */ + while (*t && (u = strchr (t,' '))) { + *u++ = '\0'; /* tie off at end of name */ + if (!strcmp (name+6,t)) { + fs_give ((void **) &s); /* flush data */ + return &newsdriver; + } + t = 1 + strchr (u,'\n'); /* next line */ + } + fs_give ((void **) &s); /* flush data */ + } + return NIL; /* return status */ +} + +/* News manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *news_parameters (long function,void *value) +{ + return (function == GET_NEWSRC) ? env_parameters (function,value) : NIL; +} + + +/* News scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void news_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + char tmp[MAILTMPLEN]; + if (news_canonicalize (ref,pat,tmp)) + mm_log ("Scan not valid for news mailboxes",ERROR); +} + +/* News find list of newsgroups + * Accepts: mail stream + * reference + * pattern to search + */ + +void news_list (MAILSTREAM *stream,char *ref,char *pat) +{ + int fd; + int i; + char *s,*t,*u,*r,pattern[MAILTMPLEN],name[MAILTMPLEN]; + struct stat sbuf; + if (!pat || !*pat) { /* empty pattern? */ + if (news_canonicalize (ref,"*",pattern)) { + /* tie off name at root */ + if (s = strchr (pattern,'.')) *++s = '\0'; + else pattern[0] = '\0'; + mm_list (stream,'.',pattern,LATT_NOSELECT); + } + } + else if (news_canonicalize (ref,pat,pattern) && + !stat ((char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),&sbuf) && + ((fd = open ((char *) mail_parameters (NIL,GET_NEWSACTIVE,NIL), + O_RDONLY,NIL)) >= 0)) { + fstat (fd,&sbuf); /* get file size and read data */ + read (fd,s = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size); + close (fd); /* close file */ + s[sbuf.st_size] = '\0'; /* tie off string */ + strcpy (name,"#news."); /* write initial prefix */ + i = strlen (pattern); /* length of pattern */ + if (pattern[--i] != '%') i = 0; + if (t = strtok_r (s,"\n",&r)) do if (u = strchr (t,' ')) { + *u = '\0'; /* tie off at end of name */ + strcpy (name + 6,t); /* make full form of name */ + if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL); + else if (i && (u = strchr (name + i,'.'))) { + *u = '\0'; /* tie off at delimiter, see if matches */ + if (pmatch_full (name,pattern,'.')) + mm_list (stream,'.',name,LATT_NOSELECT); + } + } while (t = strtok_r (NIL,"\n",&r)); + fs_give ((void **) &s); + } +} + +/* News find list of subscribed newsgroups + * Accepts: mail stream + * reference + * pattern to search + */ + +void news_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + char pattern[MAILTMPLEN]; + /* return data from newsrc */ + if (news_canonicalize (ref,pat,pattern)) newsrc_lsub (stream,pattern); +} + + +/* News canonicalize newsgroup name + * Accepts: reference + * pattern + * returned single pattern + * Returns: T on success, NIL on failure + */ + +long news_canonicalize (char *ref,char *pat,char *pattern) +{ + unsigned long i; + char *s; + if (ref && *ref) { /* have a reference */ + strcpy (pattern,ref); /* copy reference to pattern */ + /* # overrides mailbox field in reference */ + if (*pat == '#') strcpy (pattern,pat); + /* pattern starts, reference ends, with . */ + else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.')) + strcat (pattern,pat + 1); /* append, omitting one of the period */ + else strcat (pattern,pat); /* anything else is just appended */ + } + else strcpy (pattern,pat); /* just have basic name */ + if ((pattern[0] == '#') && (pattern[1] == 'n') && (pattern[2] == 'e') && + (pattern[3] == 'w') && (pattern[4] == 's') && (pattern[5] == '.') && + !strchr (pattern,'/')) { /* count wildcards */ + for (i = 0, s = pattern; *s; *s++) if ((*s == '*') || (*s == '%')) ++i; + /* success if not too many */ + if (i <= MAXWILDCARDS) return LONGT; + MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR); + } + return NIL; +} + +/* News subscribe to mailbox + * Accepts: mail stream + * mailbox to add to subscription list + * Returns: T on success, NIL on failure + */ + +long news_subscribe (MAILSTREAM *stream,char *mailbox) +{ + return news_valid (mailbox) ? newsrc_update (stream,mailbox+6,':') : NIL; +} + + +/* NEWS unsubscribe to mailbox + * Accepts: mail stream + * mailbox to delete from subscription list + * Returns: T on success, NIL on failure + */ + +long news_unsubscribe (MAILSTREAM *stream,char *mailbox) +{ + return news_valid (mailbox) ? newsrc_update (stream,mailbox+6,'!') : NIL; +} + +/* News create mailbox + * Accepts: mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long news_create (MAILSTREAM *stream,char *mailbox) +{ + return NIL; /* never valid for News */ +} + + +/* News delete mailbox + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long news_delete (MAILSTREAM *stream,char *mailbox) +{ + return NIL; /* never valid for News */ +} + + +/* News rename mailbox + * Accepts: mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long news_rename (MAILSTREAM *stream,char *old,char *newname) +{ + return NIL; /* never valid for News */ +} + +/* News open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *news_open (MAILSTREAM *stream) +{ + long i,nmsgs; + char *s,tmp[MAILTMPLEN]; + struct direct **names = NIL; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return &newsproto; + if (stream->local) fatal ("news recycle stream"); + /* build directory name */ + sprintf (s = tmp,"%s/%s",(char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL), + stream->mailbox + 6); + while (s = strchr (s,'.')) *s = '/'; + /* scan directory */ + if ((nmsgs = scandir (tmp,&names,news_select,news_numsort)) >= 0) { + mail_exists (stream,nmsgs); /* notify upper level that messages exist */ + stream->local = fs_get (sizeof (NEWSLOCAL)); + LOCAL->dirty = NIL; /* no update to .newsrc needed yet */ + LOCAL->dir = cpystr (tmp); /* copy directory name for later */ + LOCAL->name = cpystr (stream->mailbox + 6); + for (i = 0; i < nmsgs; ++i) { + stream->uid_last = mail_elt (stream,i+1)->private.uid = + atoi (names[i]->d_name); + fs_give ((void **) &names[i]); + } + s = (void *) names; /* stupid language */ + fs_give ((void **) &s); /* free directory */ + LOCAL->cachedtexts = 0; /* no cached texts */ + stream->sequence++; /* bump sequence number */ + stream->rdonly = stream->perm_deleted = T; + /* UIDs are always valid */ + stream->uid_validity = 0xbeefface; + /* read .newsrc entries */ + mail_recent (stream,newsrc_read (LOCAL->name,stream)); + /* notify if empty newsgroup */ + if (!(stream->nmsgs || stream->silent)) { + sprintf (tmp,"Newsgroup %s is empty",LOCAL->name); + mm_log (tmp,WARN); + } + } + else mm_log ("Unable to scan newsgroup spool directory",ERROR); + return LOCAL ? stream : NIL; /* if stream is alive, return to caller */ +} + +/* News file name selection test + * Accepts: candidate directory entry + * Returns: T to use file name, NIL to skip it + */ + +int news_select (struct direct *name) +{ + char c; + char *s = name->d_name; + while (c = *s++) if (!isdigit (c)) return NIL; + return T; +} + + +/* News file name comparision + * Accepts: first candidate directory entry + * second candidate directory entry + * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2 + */ + +int news_numsort (const void *d1,const void *d2) +{ + return atoi ((*(struct direct **) d1)->d_name) - + atoi ((*(struct direct **) d2)->d_name); +} + + +/* News close + * Accepts: MAIL stream + * option flags + */ + +void news_close (MAILSTREAM *stream,long options) +{ + if (LOCAL) { /* only if a file is open */ + news_check (stream); /* dump final checkpoint */ + if (LOCAL->dir) fs_give ((void **) &LOCAL->dir); + if (LOCAL->name) fs_give ((void **) &LOCAL->name); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + +/* News fetch fast information + * Accepts: MAIL stream + * sequence + * option flags + */ + +void news_fast (MAILSTREAM *stream,char *sequence,long flags) +{ + MESSAGECACHE *elt; + unsigned long i; + /* set up metadata for all messages */ + if (stream && LOCAL && ((flags & FT_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence && + !(elt->day && elt->rfc822_size)) news_load_message (stream,i,NIL); +} + + +/* News fetch flags + * Accepts: MAIL stream + * sequence + * option flags + */ + +void news_flags (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i; + if ((flags & FT_UID) ? /* validate all elts */ + mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence)) + for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->valid = T; +} + +/* News load message into cache + * Accepts: MAIL stream + * message # + * option flags + */ + +void news_load_message (MAILSTREAM *stream,unsigned long msgno,long flags) +{ + unsigned long i,j,nlseen; + int fd; + unsigned char c,*t; + struct stat sbuf; + MESSAGECACHE *elt; + FDDATA d; + STRING bs; + elt = mail_elt (stream,msgno);/* get elt */ + /* build message file name */ + sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid); + /* anything we need not currently cached? */ + if ((!elt->day || !elt->rfc822_size || + ((flags & NLM_HEADER) && !elt->private.msg.header.text.data) || + ((flags & NLM_TEXT) && !elt->private.msg.text.text.data)) && + ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) { + fstat (fd,&sbuf); /* get file metadata */ + d.fd = fd; /* set up file descriptor */ + d.pos = 0; /* start of file */ + d.chunk = LOCAL->buf; + d.chunksize = CHUNKSIZE; + INIT (&bs,fd_string,&d,sbuf.st_size); + if (!elt->day) { /* set internaldate to file date */ + struct tm *tm = gmtime (&sbuf.st_mtime); + elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1; + elt->year = tm->tm_year + 1900 - BASEYEAR; + elt->hours = tm->tm_hour; elt->minutes = tm->tm_min; + elt->seconds = tm->tm_sec; + elt->zhours = 0; elt->zminutes = 0; + } + + if (!elt->rfc822_size) { /* know message size yet? */ + for (i = 0, j = SIZE (&bs), nlseen = 0; j--; ) switch (SNX (&bs)) { + case '\015': /* unlikely carriage return */ + if (!j || (CHR (&bs) != '\012')) { + i++; /* ugh, raw CR */ + nlseen = NIL; + break; + } + SNX (&bs); /* eat the line feed, drop in */ + case '\012': /* line feed? */ + i += 2; /* count a CRLF */ + /* header size known yet? */ + if (!elt->private.msg.header.text.size && nlseen) { + /* note position in file */ + elt->private.special.text.size = GETPOS (&bs); + /* and CRLF-adjusted size */ + elt->private.msg.header.text.size = i; + } + nlseen = T; /* note newline seen */ + break; + default: /* ordinary chararacter */ + i++; + nlseen = NIL; + break; + } + SETPOS (&bs,0); /* restore old position */ + elt->rfc822_size = i; /* note that we have size now */ + /* header is entire message if no delimiter */ + if (!elt->private.msg.header.text.size) + elt->private.msg.header.text.size = elt->rfc822_size; + /* text is remainder of message */ + elt->private.msg.text.text.size = + elt->rfc822_size - elt->private.msg.header.text.size; + } + + /* need to load cache with message data? */ + if (((flags & NLM_HEADER) && !elt->private.msg.header.text.data) || + ((flags & NLM_TEXT) && !elt->private.msg.text.text.data)) { + /* purge cache if too big */ + if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) { + /* just can't keep that much */ + mail_gc (stream,GC_TEXTS); + LOCAL->cachedtexts = 0; + } + if ((flags & NLM_HEADER) && !elt->private.msg.header.text.data) { + t = elt->private.msg.header.text.data = + (unsigned char *) fs_get (elt->private.msg.header.text.size + 1); + LOCAL->cachedtexts += elt->private.msg.header.text.size; + /* read in message header */ + for (i = 0; i <= elt->private.msg.header.text.size; i++) + switch (c = SNX (&bs)) { + case '\015': /* unlikely carriage return */ + *t++ = c; + if ((CHR (&bs) == '\012')) *t++ = SNX (&bs); + break; + case '\012': /* line feed? */ + *t++ = '\015'; + default: + *t++ = c; + break; + } + *t = '\0'; /* tie off string */ + } + if ((flags & NLM_TEXT) && !elt->private.msg.text.text.data) { + t = elt->private.msg.text.text.data = + (unsigned char *) fs_get (elt->private.msg.text.text.size + 1); + SETPOS (&bs,elt->private.msg.header.text.size); + LOCAL->cachedtexts += elt->private.msg.text.text.size; + /* read in message text */ + for (i = 0; i <= elt->private.msg.text.text.size; i++) + switch (c = SNX (&bs)) { + case '\015': /* unlikely carriage return */ + *t++ = c; + if ((CHR (&bs) == '\012')) *t++ = SNX (&bs); + break; + case '\012': /* line feed? */ + *t++ = '\015'; + default: + *t++ = c; + break; + } + *t = '\0'; /* tie off string */ + } + } + close (fd); /* flush message file */ + } +} + +/* News fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + +char *news_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags) +{ + MESSAGECACHE *elt; + *length = 0; /* default to empty */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + elt = mail_elt (stream,msgno);/* get elt */ + if (!elt->private.msg.header.text.data) + news_load_message (stream,msgno,NLM_HEADER); + *length = elt->private.msg.header.text.size; + return (char *) elt->private.msg.header.text.data; +} + + +/* News fetch message text (body only) + * Accepts: MAIL stream + * message # to fetch + * pointer to returned stringstruct + * option flags + * Returns: T on success, NIL on failure + */ + +long news_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + elt = mail_elt (stream,msgno);/* get elt */ + /* snarf message if don't have it yet */ + if (!elt->private.msg.text.text.data) { + news_load_message (stream,msgno,NLM_TEXT); + if (!elt->private.msg.text.text.data) return NIL; + } + if (!(flags & FT_PEEK)) { /* mark as seen */ + mail_elt (stream,msgno)->seen = T; + mm_flags (stream,msgno); + } + INIT (bs,mail_string,elt->private.msg.text.text.data, + elt->private.msg.text.text.size); + return T; +} + +/* News per-message modify flag + * Accepts: MAIL stream + * message cache element + */ + +void news_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + if (!LOCAL->dirty) { /* only bother checking if not dirty yet */ + if (elt->valid) { /* if done, see if deleted changed */ + if (elt->sequence != elt->deleted) LOCAL->dirty = T; + elt->sequence = T; /* leave the sequence set */ + } + /* note current setting of deleted flag */ + else elt->sequence = elt->deleted; + } +} + + +/* News ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +long news_ping (MAILSTREAM *stream) +{ + return T; /* always alive */ +} + + +/* News check mailbox + * Accepts: MAIL stream + */ + +void news_check (MAILSTREAM *stream) +{ + /* never do if no updates */ + if (LOCAL->dirty) newsrc_write (LOCAL->name,stream); + LOCAL->dirty = NIL; +} + + +/* News expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T if success, NIL if failure + */ + +long news_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL); + return LONGT; +} + +/* News copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * option flags + * Returns: T if copy successful, else NIL + */ + +long news_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + if (pc) return (*pc) (stream,sequence,mailbox,options); + mm_log ("Copy not valid for News",ERROR); + return NIL; +} + + +/* News append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback function + * data for callback + * Returns: T if append successful, else NIL + */ + +long news_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + mm_log ("Append not valid for news",ERROR); + return NIL; +} diff --git a/imap/src/osdep/amiga/nl_ami.c b/imap/src/osdep/amiga/nl_ami.c new file mode 100644 index 00000000..b2d5616a --- /dev/null +++ b/imap/src/osdep/amiga/nl_ami.c @@ -0,0 +1,92 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UNIX/VMS newline routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + +/* Copy string with CRLF newlines + * Accepts: destination string + * pointer to size of destination string buffer + * source string + * length of source string + * Returns: length of copied string + */ + +unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl, + unsigned char *src,unsigned long srcl) +{ + long i = srcl * 2,j; + unsigned char c,*d = src; + if (*dst) { /* candidate destination provided? */ + /* count NLs if doesn't fit worst-case */ + if (i > *dstl) for (i = j = srcl; j; --j) if (*d++ == '\012') i++; + /* still too small, must reset destination */ + if (i > *dstl) fs_give ((void **) dst); + } + /* make a new buffer if needed */ + if (!*dst) *dst = (char *) fs_get ((*dstl = i) + 1); + d = *dst; /* destination string */ + if (srcl) do { /* main copy loop */ + if ((c = *src++) < '\016') { + /* prepend CR to LF */ + if (c == '\012') *d++ = '\015'; + /* unlikely CR */ + else if ((c == '\015') && (srcl > 1) && (*src == '\012')) { + *d++ = c; /* copy the CR */ + c = *src++; /* grab the LF */ + --srcl; /* adjust the count */ + } + } + *d++ = c; /* copy character */ + } while (--srcl); + *d = '\0'; /* tie off destination */ + return d - *dst; /* return length */ +} + +/* Length of string after strcrlfcpy applied + * Accepts: source string + * Returns: length of string + */ + +unsigned long strcrlflen (STRING *s) +{ + unsigned long pos = GETPOS (s); + unsigned long i = SIZE (s); + unsigned long j = i; + while (j--) switch (SNX (s)) {/* search for newlines */ + case '\015': /* unlikely carriage return */ + if (j && (CHR (s) == '\012')) { + SNX (s); /* eat the line feed */ + j--; + } + break; + case '\012': /* line feed? */ + i++; + default: /* ordinary chararacter */ + break; + } + SETPOS (s,pos); /* restore old position */ + return i; +} diff --git a/imap/src/osdep/amiga/os_ami.c b/imap/src/osdep/amiga/os_ami.c new file mode 100644 index 00000000..1ba3ca5f --- /dev/null +++ b/imap/src/osdep/amiga/os_ami.c @@ -0,0 +1,80 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Operating-system dependent routines -- Amiga version + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 11 May 1989 + * Last Edited: 30 August 2006 + */ + +#define PINE /* to get full DIR description in <dirent.h> */ +#include "tcp_ami.h" /* must be before osdep includes tcp.h */ +#include "mail.h" +#include "osdep.h" +#include <stdio.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include <pwd.h> +#include "misc.h" + +extern int sys_nerr; +extern char *sys_errlist[]; + +#define DIR_SIZE(d) d->d_reclen /* for scandir.c */ + +#include "fs_ami.c" +#include "ftl_ami.c" +#include "nl_ami.c" +#include "env_ami.c" +#include "tcp_ami.c" +#include "log_std.c" +#include "gr_waitp.c" +#include "tz_bsd.c" +#include "scandir.c" +#include "gethstid.c" + +#undef utime + +/* Amiga has its own wierd utime() with an incompatible struct utimbuf that + * does not match with the traditional time_t [2]. + */ + +/* Portable utime() that takes it args like real Unix systems + * Accepts: file path + * traditional utime() argument + * Returns: utime() results + */ + +int portable_utime (char *file,time_t timep[2]) +{ + struct utimbuf times; + times.actime = timep[0]; /* copy the portable values */ + times.modtime = timep[1]; + return utime (file,×); /* now call Amiga's routine */ +} diff --git a/imap/src/osdep/amiga/os_ami.h b/imap/src/osdep/amiga/os_ami.h new file mode 100644 index 00000000..293005ca --- /dev/null +++ b/imap/src/osdep/amiga/os_ami.h @@ -0,0 +1,54 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Operating-system dependent routines -- Amiga version + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 11 May 1989 + * Last Edited: 15 September 2006 + */ + +#include <proto/socket.h> +#include <sys/types.h> +#include <sys/dir.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <sys/file.h> + +/* Different names, equivalent things in BSD and SysV */ + +#define direct dirent +#define utime portable_utime +int portable_utime (char *file,time_t timep[2]); + +#include "env_ami.h" +#include "fs.h" +#include "ftl.h" +#include "nl.h" +#include "tcp.h" + +long gethostid (void); +typedef int (*select_t) (struct direct *name); +typedef int (*compar_t) (void *d1,void *d2); +int scandir (char *dirname,struct direct ***namelist,select_t select, + compar_t compar); +int alphasort (void *d1,void *d2); diff --git a/imap/src/osdep/amiga/phile.c b/imap/src/osdep/amiga/phile.c new file mode 100644 index 00000000..ce72d0a9 --- /dev/null +++ b/imap/src/osdep/amiga/phile.c @@ -0,0 +1,553 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: File routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 25 August 1993 + * Last Edited: 9 May 2006 + */ + + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include <signal.h> +#include "mail.h" +#include "osdep.h" +#include <pwd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include "rfc822.h" +#include "misc.h" +#include "dummy.h" + +/* Types returned from phile_type() */ + +#define PTYPEBINARY 0 /* binary data */ +#define PTYPETEXT 1 /* textual data */ +#define PTYPECRTEXT 2 /* textual data with CR */ +#define PTYPE8 4 /* textual 8bit data */ +#define PTYPEISO2022JP 8 /* textual Japanese */ +#define PTYPEISO2022KR 16 /* textual Korean */ +#define PTYPEISO2022CN 32 /* textual Chinese */ + + +/* PHILE I/O stream local data */ + +typedef struct phile_local { + ENVELOPE *env; /* file envelope */ + BODY *body; /* file body */ + char tmp[MAILTMPLEN]; /* temporary buffer */ +} PHILELOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((PHILELOCAL *) stream->local) + + +/* Function prototypes */ + +DRIVER *phile_valid (char *name); +int phile_isvalid (char *name,char *tmp); +void *phile_parameters (long function,void *value); +void phile_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void phile_list (MAILSTREAM *stream,char *ref,char *pat); +void phile_lsub (MAILSTREAM *stream,char *ref,char *pat); +long phile_create (MAILSTREAM *stream,char *mailbox); +long phile_delete (MAILSTREAM *stream,char *mailbox); +long phile_rename (MAILSTREAM *stream,char *old,char *newname); +long phile_status (MAILSTREAM *stream,char *mbx,long flags); +MAILSTREAM *phile_open (MAILSTREAM *stream); +int phile_type (unsigned char *s,unsigned long i,unsigned long *j); +void phile_close (MAILSTREAM *stream,long options); +ENVELOPE *phile_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body, + long flags); +char *phile_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags); +long phile_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +long phile_ping (MAILSTREAM *stream); +void phile_check (MAILSTREAM *stream); +long phile_expunge (MAILSTREAM *stream,char *sequence,long options); +long phile_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long phile_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +/* File routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER philedriver = { + "phile", /* driver name */ + /* driver flags */ + DR_LOCAL|DR_READONLY|DR_NOSTICKY, + (DRIVER *) NIL, /* next driver */ + phile_valid, /* mailbox is valid for us */ + phile_parameters, /* manipulate parameters */ + phile_scan, /* scan mailboxes */ + phile_list, /* list mailboxes */ + phile_lsub, /* list subscribed mailboxes */ + NIL, /* subscribe to mailbox */ + NIL, /* unsubscribe from mailbox */ + dummy_create, /* create mailbox */ + dummy_delete, /* delete mailbox */ + dummy_rename, /* rename mailbox */ + phile_status, /* status of mailbox */ + phile_open, /* open mailbox */ + phile_close, /* close mailbox */ + NIL, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + phile_structure, /* fetch message envelopes */ + phile_header, /* fetch message header only */ + phile_text, /* fetch message body only */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + NIL, /* modify flags */ + NIL, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + phile_ping, /* ping mailbox to see if still alive */ + phile_check, /* check for new messages */ + phile_expunge, /* expunge deleted messages */ + phile_copy, /* copy messages to another mailbox */ + phile_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM phileproto = {&philedriver}; + +/* File validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *phile_valid (char *name) +{ + char tmp[MAILTMPLEN]; + return phile_isvalid (name,tmp) ? &philedriver : NIL; +} + + +/* File test for valid mailbox + * Accepts: mailbox name + * Returns: T if valid, NIL otherwise + */ + +int phile_isvalid (char *name,char *tmp) +{ + struct stat sbuf; + char *s; + /* INBOX never accepted, any other name is */ + return ((s = mailboxfile (tmp,name)) && *s && !stat (s,&sbuf) && + !(sbuf.st_mode & S_IFDIR) && + /* only allow empty files if no empty proto + or if #ftp */ + (sbuf.st_size || !default_proto (T) || + ((*name == '#') && ((name[1] == 'f') || (name[1] == 'F')) && + ((name[2] == 't') || (name[2] == 'T')) && + ((name[3] == 'p') || (name[3] == 'P')) && (name[4] == '/')))); +} + +/* File manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *phile_parameters (long function,void *value) +{ + return NIL; +} + +/* File mail scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void phile_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + if (stream) dummy_scan (NIL,ref,pat,contents); +} + + +/* File list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void phile_list (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_list (NIL,ref,pat); +} + + +/* File list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void phile_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_lsub (NIL,ref,pat); +} + + +/* File status + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long phile_status (MAILSTREAM *stream,char *mbx,long flags) +{ + char *s,tmp[MAILTMPLEN]; + MAILSTATUS status; + struct stat sbuf; + long ret = NIL; + if ((s = mailboxfile (tmp,mbx)) && *s && !stat (s,&sbuf)) { + status.flags = flags; /* return status values */ + status.unseen = (stream && mail_elt (stream,1)->seen) ? 0 : 1; + status.messages = status.recent = status.uidnext = 1; + status.uidvalidity = sbuf.st_mtime; + /* pass status to main program */ + mm_status (stream,mbx,&status); + ret = LONGT; /* success */ + } + return ret; +} + +/* File open + * Accepts: Stream to open + * Returns: Stream on success, NIL on failure + */ + +MAILSTREAM *phile_open (MAILSTREAM *stream) +{ + int i,k,fd; + unsigned long j,m; + char *s,tmp[MAILTMPLEN]; + struct passwd *pw; + struct stat sbuf; + struct tm *t; + MESSAGECACHE *elt; + SIZEDTEXT *buf; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return &phileproto; + if (stream->local) fatal ("phile recycle stream"); + /* open associated file */ + if (!mailboxfile (tmp,stream->mailbox) || !tmp[0] || stat (tmp,&sbuf) || + (fd = open (tmp,O_RDONLY,NIL)) < 0) { + sprintf (tmp,"Unable to open file %s",stream->mailbox); + mm_log (tmp,ERROR); + return NIL; + } + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr (tmp); + stream->local = fs_get (sizeof (PHILELOCAL)); + mail_exists (stream,1); /* make sure upper level knows */ + mail_recent (stream,1); + elt = mail_elt (stream,1); /* instantiate cache element */ + elt->valid = elt->recent = T; /* mark valid flags */ + stream->sequence++; /* bump sequence number */ + stream->rdonly = T; /* make sure upper level knows readonly */ + /* instantiate a new envelope and body */ + LOCAL->env = mail_newenvelope (); + LOCAL->body = mail_newbody (); + + t = gmtime (&sbuf.st_mtime); /* get UTC time and Julian day */ + i = t->tm_hour * 60 + t->tm_min; + k = t->tm_yday; + t = localtime(&sbuf.st_mtime);/* get local time */ + /* calculate time delta */ + i = t->tm_hour * 60 + t->tm_min - i; + if (k = t->tm_yday - k) i += ((k < 0) == (abs (k) == 1)) ? -24*60 : 24*60; + k = abs (i); /* time from UTC either way */ + elt->hours = t->tm_hour; elt->minutes = t->tm_min; elt->seconds = t->tm_sec; + elt->day = t->tm_mday; elt->month = t->tm_mon + 1; + elt->year = t->tm_year - (BASEYEAR - 1900); + elt->zoccident = (k == i) ? 0 : 1; + elt->zhours = k/60; + elt->zminutes = k % 60; + sprintf (tmp,"%s, %d %s %d %02d:%02d:%02d %c%02d%02d", + days[t->tm_wday],t->tm_mday,months[t->tm_mon],t->tm_year+1900, + t->tm_hour,t->tm_min,t->tm_sec,elt->zoccident ? '-' : '+', + elt->zhours,elt->zminutes); + /* set up Date field */ + LOCAL->env->date = cpystr (tmp); + + /* fill in From field from file owner */ + LOCAL->env->from = mail_newaddr (); + if (pw = getpwuid (sbuf.st_uid)) strcpy (tmp,pw->pw_name); + else sprintf (tmp,"User-Number-%ld",(long) sbuf.st_uid); + LOCAL->env->from->mailbox = cpystr (tmp); + LOCAL->env->from->host = cpystr (mylocalhost ()); + /* set subject to be mailbox name */ + LOCAL->env->subject = cpystr (stream->mailbox); + /* slurp the data */ + (buf = &elt->private.special.text)->size = sbuf.st_size; + read (fd,buf->data = (unsigned char *) fs_get (buf->size + 1),buf->size); + buf->data[buf->size] = '\0'; + close (fd); /* close the file */ + /* analyze data type */ + if (i = phile_type (buf->data,buf->size,&j)) { + LOCAL->body->type = TYPETEXT; + LOCAL->body->subtype = cpystr ("PLAIN"); + if (!(i & PTYPECRTEXT)) { /* change Internet newline format as needed */ + s = (char *) buf->data; /* make copy of UNIX-format string */ + buf->data = NIL; /* zap the buffer */ + buf->size = strcrlfcpy (&buf->data,&m,s,buf->size); + fs_give ((void **) &s); /* flush original UNIX-format string */ + } + LOCAL->body->parameter = mail_newbody_parameter (); + LOCAL->body->parameter->attribute = cpystr ("charset"); + LOCAL->body->parameter->value = + cpystr ((i & PTYPEISO2022JP) ? "ISO-2022-JP" : + (i & PTYPEISO2022KR) ? "ISO-2022-KR" : + (i & PTYPEISO2022CN) ? "ISO-2022-CN" : + (i & PTYPE8) ? "X-UNKNOWN" : "US-ASCII"); + LOCAL->body->encoding = (i & PTYPE8) ? ENC8BIT : ENC7BIT; + LOCAL->body->size.lines = j; + } + else { /* binary data */ + LOCAL->body->type = TYPEAPPLICATION; + LOCAL->body->subtype = cpystr ("OCTET-STREAM"); + LOCAL->body->parameter = mail_newbody_parameter (); + LOCAL->body->parameter->attribute = cpystr ("name"); + LOCAL->body->parameter->value = + cpystr ((s = (strrchr (stream->mailbox,'/'))) ? s+1 : stream->mailbox); + LOCAL->body->encoding = ENCBASE64; + buf->data = rfc822_binary (s = (char *) buf->data,buf->size,&buf->size); + fs_give ((void **) &s); /* flush originary binary contents */ + } + phile_header (stream,1,&j,NIL); + LOCAL->body->size.bytes = LOCAL->body->contents.text.size = buf->size; + elt->rfc822_size = j + buf->size; + /* only one message ever... */ + stream->uid_validity = sbuf.st_mtime; + stream->uid_last = elt->private.uid = 1; + return stream; /* return stream alive to caller */ +} + +/* File determine data type + * Accepts: data to examine + * size of data + * pointer to line count return + * Returns: PTYPE mask of data type + */ + +int phile_type (unsigned char *s,unsigned long i,unsigned long *j) +{ + int ret = PTYPETEXT; + char *charvec = "bbbbbbbaaalaacaabbbbbbbbbbbebbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + *j = 0; /* no lines */ + /* check type of every character */ + while (i--) switch (charvec[*s++]) { + case 'A': + ret |= PTYPE8; /* 8bit character */ + break; + case 'a': + break; /* ASCII character */ + case 'b': + return PTYPEBINARY; /* binary byte seen, stop immediately */ + case 'c': + ret |= PTYPECRTEXT; /* CR indicates Internet text */ + break; + case 'e': /* ESC */ + if (*s == '$') { /* ISO-2022 sequence? */ + switch (s[1]) { + case 'B': case '@': ret |= PTYPEISO2022JP; break; + case ')': + switch (s[2]) { + case 'A': case 'E': case 'G': ret |= PTYPEISO2022CN; break; + case 'C': ret |= PTYPEISO2022KR; break; + } + case '*': + switch (s[2]) { + case 'H': ret |= PTYPEISO2022CN; break; + } + case '+': + switch (s[2]) { + case 'I': case 'J': case 'K': case 'L': case 'M': + ret |= PTYPEISO2022CN; break; + } + } + } + break; + case 'l': /* newline */ + (*j)++; + break; + } + return ret; /* return type of data */ +} + +/* File close + * Accepts: MAIL stream + * close options + */ + +void phile_close (MAILSTREAM *stream,long options) +{ + if (LOCAL) { /* only if a file is open */ + fs_give ((void **) &mail_elt (stream,1)->private.special.text.data); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + +/* File fetch structure + * Accepts: MAIL stream + * message # to fetch + * pointer to return body + * option flags + * Returns: envelope of this message, body returned in body value + * + * Fetches the "fast" information as well + */ + +ENVELOPE *phile_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body, + long flags) +{ + if (body) *body = LOCAL->body; + return LOCAL->env; /* return the envelope */ +} + + +/* File fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + +char *phile_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags) +{ + rfc822_header (LOCAL->tmp,LOCAL->env,LOCAL->body); + *length = strlen (LOCAL->tmp); + return LOCAL->tmp; +} + + +/* File fetch message text (body only) + * Accepts: MAIL stream + * message # to fetch + * pointer to returned stringstruct + * option flags + * Returns: T, always + */ + +long phile_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + SIZEDTEXT *buf = &mail_elt (stream,msgno)->private.special.text; + if (!(flags &FT_PEEK)) { /* mark message as seen */ + mail_elt (stream,msgno)->seen = T; + mm_flags (stream,msgno); + } + INIT (bs,mail_string,buf->data,buf->size); + return T; +} + +/* File ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + * No-op for readonly files, since read/writer can expunge it from under us! + */ + +long phile_ping (MAILSTREAM *stream) +{ + return T; +} + +/* File check mailbox + * Accepts: MAIL stream + * No-op for readonly files, since read/writer can expunge it from under us! + */ + +void phile_check (MAILSTREAM *stream) +{ + mm_log ("Check completed",NIL); +} + +/* File expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T if success, NIL if failure + */ + +long phile_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL); + return LONGT; +} + +/* File copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if copy successful, else NIL + */ + +long phile_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + char tmp[MAILTMPLEN]; + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (tmp,"Can't copy - file \"%s\" is not in valid mailbox format", + stream->mailbox); + mm_log (tmp,ERROR); + return NIL; +} + + +/* File append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback function + * data for callback + * Returns: T if append successful, else NIL + */ + +long phile_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + char tmp[MAILTMPLEN],file[MAILTMPLEN]; + char *s = mailboxfile (file,mailbox); + if (s && *s) + sprintf (tmp,"Can't append - not in valid mailbox format: %.80s",s); + else sprintf (tmp,"Can't append - invalid name: %.80s",mailbox); + mm_log (tmp,ERROR); + return NIL; +} diff --git a/imap/src/osdep/amiga/pmatch.c b/imap/src/osdep/amiga/pmatch.c new file mode 100644 index 00000000..a7539e50 --- /dev/null +++ b/imap/src/osdep/amiga/pmatch.c @@ -0,0 +1,89 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: IMAP Wildcard Matching Routines (case-dependent) + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 15 June 2000 + * Last Edited: 30 August 2006 + */ + +/* Wildcard pattern match + * Accepts: base string + * pattern string + * delimiter character + * Returns: T if pattern matches base, else NIL + */ + +long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim) +{ + switch (*pat) { + case '%': /* non-recursive */ + /* % at end, OK if no inferiors */ + if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T; + /* scan remainder of string until delimiter */ + do if (pmatch_full (s,pat+1,delim)) return T; + while ((*s != delim) && *s++); + break; + case '*': /* match 0 or more characters */ + if (!pat[1]) return T; /* * at end, unconditional match */ + /* scan remainder of string */ + do if (pmatch_full (s,pat+1,delim)) return T; + while (*s++); + break; + case '\0': /* end of pattern */ + return *s ? NIL : T; /* success if also end of base */ + default: /* match this character */ + return (*pat == *s) ? pmatch_full (s+1,pat+1,delim) : NIL; + } + return NIL; +} + +/* Directory pattern match + * Accepts: base string + * pattern string + * delimiter character + * Returns: T if base is a matching directory of pattern, else NIL + */ + +long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim) +{ + switch (*pat) { + case '%': /* non-recursive */ + if (!*s) return T; /* end of base means have a subset match */ + if (!*++pat) return NIL; /* % at end, no inferiors permitted */ + /* scan remainder of string until delimiter */ + do if (dmatch (s,pat,delim)) return T; + while ((*s != delim) && *s++); + if (*s && !s[1]) return T; /* ends with delimiter, must be subset */ + return dmatch (s,pat,delim);/* do new scan */ + case '*': /* match 0 or more characters */ + return T; /* unconditional match */ + case '\0': /* end of pattern */ + break; + default: /* match this character */ + if (*s) return (*pat == *s) ? dmatch (s+1,pat+1,delim) : NIL; + /* end of base, return if at delimiter */ + else if (*pat == delim) return T; + break; + } + return NIL; +} diff --git a/imap/src/osdep/amiga/pseudo.c b/imap/src/osdep/amiga/pseudo.c new file mode 100644 index 00000000..1aae8a53 --- /dev/null +++ b/imap/src/osdep/amiga/pseudo.c @@ -0,0 +1,36 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Pseudo Header Strings + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 26 September 1996 + * Last Edited: 30 August 2006 + */ + +/* Local sites may wish to alter this text */ + +char *pseudo_from = "MAILER-DAEMON"; +char *pseudo_name = "Mail System Internal Data"; +char *pseudo_subject = "DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA"; +char *pseudo_msg = + "This text is part of the internal format of your mail folder, and is not\na real message. It is created automatically by the mail system software.\nIf deleted, important folder data will be lost, and it will be re-created\nwith the data reset to initial values." + ; diff --git a/imap/src/osdep/amiga/pseudo.h b/imap/src/osdep/amiga/pseudo.h new file mode 100644 index 00000000..c9c07628 --- /dev/null +++ b/imap/src/osdep/amiga/pseudo.h @@ -0,0 +1,30 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Pseudo Header Strings + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 26 September 1996 + * Last Edited: 30 August 2006 + */ + + +extern char *pseudo_from,*pseudo_name,*pseudo_subject,*pseudo_msg; diff --git a/imap/src/osdep/amiga/scandir.c b/imap/src/osdep/amiga/scandir.c new file mode 100644 index 00000000..878343e8 --- /dev/null +++ b/imap/src/osdep/amiga/scandir.c @@ -0,0 +1,81 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Scan directories + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 15 September 2006 + */ + +/* Emulator for BSD scandir() call + * Accepts: directory name + * destination pointer of names array + * selection function + * comparison function + * Returns: number of elements in the array or -1 if error + */ + +int scandir (char *dirname,struct direct ***namelist,select_t select, + compar_t compar) +{ + struct direct *p,*d,**names; + int nitems; + struct stat stb; + long nlmax; + DIR *dirp = opendir (dirname);/* open directory and get status poop */ + if ((!dirp) || (fstat (dirp->dd_fd,&stb) < 0)) return -1; + nlmax = stb.st_size / 24; /* guesstimate at number of files */ + names = (struct direct **) fs_get (nlmax * sizeof (struct direct *)); + nitems = 0; /* initially none found */ + while (d = readdir (dirp)) { /* read directory item */ + /* matches select criterion? */ + if (select && !(*select) (d)) continue; + /* get size of direct record for this file */ + p = (struct direct *) fs_get (DIR_SIZE (d)); + p->d_ino = d->d_ino; /* copy the poop */ + strcpy (p->d_name,d->d_name); + if (++nitems >= nlmax) { /* if out of space, try bigger guesstimate */ + void *s = (void *) names; /* stupid language */ + nlmax *= 2; /* double it */ + fs_resize ((void **) &s,nlmax * sizeof (struct direct *)); + names = (struct direct **) s; + } + names[nitems - 1] = p; /* store this file there */ + } + closedir (dirp); /* done with directory */ + /* sort if necessary */ + if (nitems && compar) qsort (names,nitems,sizeof (struct direct *),compar); + *namelist = names; /* return directory */ + return nitems; /* and size */ +} + +/* Alphabetic file name comparision + * Accepts: first candidate directory entry + * second candidate directory entry + * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2 + */ + +int alphasort (void *d1,void *d2) +{ + return strcmp ((*(struct direct **) d1)->d_name, + (*(struct direct **) d2)->d_name); +} diff --git a/imap/src/osdep/amiga/ssl_none.c b/imap/src/osdep/amiga/ssl_none.c new file mode 100644 index 00000000..e4dedda7 --- /dev/null +++ b/imap/src/osdep/amiga/ssl_none.c @@ -0,0 +1,141 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Dummy (no SSL) authentication/encryption module + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 7 February 2001 + * Last Edited: 30 August 2006 + */ + +/* Init server for SSL + * Accepts: server name + */ + +void ssl_server_init (char *server) +{ + syslog (LOG_ERR,"This server does not support SSL"); + exit (1); /* punt this program too */ +} + + +/* Start TLS + * Accepts: /etc/services service name + * Returns: cpystr'd error string if TLS failed, else NIL for success + */ + +char *ssl_start_tls (char *server) +{ + return cpystr ("This server does not support TLS"); +} + +/* Get character + * Returns: character or EOF + */ + +int PBIN (void) +{ + return getchar (); +} + + +/* Get string + * Accepts: destination string pointer + * number of bytes available + * Returns: destination string pointer or NIL if EOF + */ + +char *PSIN (char *s,int n) +{ + return fgets (s,n,stdin); +} + + +/* Get record + * Accepts: destination string pointer + * number of bytes to read + * Returns: T if success, NIL otherwise + */ + +long PSINR (char *s,unsigned long n) +{ + unsigned long i; + while (n && ((i = fread (s,1,n,stdin)) || (errno == EINTR))) s += i,n -= i; + return n ? NIL : LONGT; +} + + +/* Wait for input + * Accepts: timeout in seconds + * Returns: T if have input on stdin, else NIL + */ + +long INWAIT (long seconds) +{ + return server_input_wait (seconds); +} + +/* Put character + * Accepts: character + * Returns: character written or EOF + */ + +int PBOUT (int c) +{ + return putchar (c); +} + + +/* Put string + * Accepts: source string pointer + * Returns: 0 or EOF if error + */ + +int PSOUT (char *s) +{ + return fputs (s,stdout); +} + + +/* Put record + * Accepts: source sized text + * Returns: 0 or EOF if error + */ + +int PSOUTR (SIZEDTEXT *s) +{ + unsigned char *t; + unsigned long i,j; + for (t = s->data,i = s->size; + (i && ((j = fwrite (t,1,i,stdout)) || (errno == EINTR))); + t += j,i -= j); + return i ? EOF : NIL; +} + + +/* Flush output + * Returns: 0 or EOF if error + */ + +int PFLUSH (void) +{ + return fflush (stdout); +} diff --git a/imap/src/osdep/amiga/tcp_ami.c b/imap/src/osdep/amiga/tcp_ami.c new file mode 100644 index 00000000..974717c0 --- /dev/null +++ b/imap/src/osdep/amiga/tcp_ami.c @@ -0,0 +1,797 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Amiga TCP/IP routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 13 January 2008 + */ + +#undef write /* don't use redefined write() */ + +static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */ +static long ttmo_open = 0; /* TCP timeouts, in seconds */ +static long ttmo_read = 0; +static long ttmo_write = 0; +static long allowreversedns = T;/* allow reverse DNS lookup */ +static long tcpdebug = NIL; /* extra TCP debugging telemetry */ + +extern long maxposint; /* get this from write.c */ + +/* Local function prototypes */ + +int tcp_socket_open (struct sockaddr_in *sin,char *tmp,int *ctr,char *hst, + unsigned long port); +static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size, + long *contd); +long tcp_abort (TCPSTREAM *stream); +char *tcp_name (struct sockaddr_in *sin,long flag); +char *tcp_name_valid (char *s); + +/* TCP/IP manipulate parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *tcp_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case SET_TIMEOUT: + tmoh = (tcptimeout_t) value; + case GET_TIMEOUT: + ret = (void *) tmoh; + break; + case SET_OPENTIMEOUT: + ttmo_open = (long) value; + case GET_OPENTIMEOUT: + ret = (void *) ttmo_open; + break; + case SET_READTIMEOUT: + ttmo_read = (long) value; + case GET_READTIMEOUT: + ret = (void *) ttmo_read; + break; + case SET_WRITETIMEOUT: + ttmo_write = (long) value; + case GET_WRITETIMEOUT: + ret = (void *) ttmo_write; + break; + case SET_ALLOWREVERSEDNS: + allowreversedns = (long) value; + case GET_ALLOWREVERSEDNS: + ret = (void *) allowreversedns; + break; + case SET_TCPDEBUG: + tcpdebug = (long) value; + case GET_TCPDEBUG: + ret = (void *) tcpdebug; + break; + } + return ret; +} + +/* TCP/IP open + * Accepts: host name + * contact service name + * contact port number and optional silent flag + * Returns: TCP/IP stream if success else NIL + */ + +TCPSTREAM *tcp_open (char *host,char *service,unsigned long port) +{ + TCPSTREAM *stream = NIL; + int i; + int sock = -1; + int ctr = 0; + int silent = (port & NET_SILENT) ? T : NIL; + int *ctrp = (port & NET_NOOPENTIMEOUT) ? NIL : &ctr; + char *s; + struct sockaddr_in sin; + struct hostent *he; + char hostname[MAILTMPLEN]; + char tmp[MAILTMPLEN]; + struct servent *sv = NIL; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + void *data; + port &= 0xffff; /* erase flags */ + /* lookup service */ + if (service && (sv = getservbyname (service,"tcp"))) + port = ntohs (sin.sin_port = sv->s_port); + /* copy port number in network format */ + else sin.sin_port = htons (port); + /* The domain literal form is used (rather than simply the dotted decimal + as with other Amiga programs) because it has to be a valid "host name" + in mailsystem terminology. */ + /* look like domain literal? */ + if (host[0] == '[' && host[(strlen (host))-1] == ']') { + strcpy (hostname,host+1); /* yes, copy number part */ + hostname[(strlen (hostname))-1] = '\0'; + if ((sin.sin_addr.s_addr = inet_addr (hostname)) == -1) + sprintf (tmp,"Bad format domain-literal: %.80s",host); + else { + sin.sin_family = AF_INET; /* family is always Internet */ + strcpy (hostname,host); /* hostname is user's argument */ + (*bn) (BLOCK_TCPOPEN,NIL); + /* get an open socket for this system */ + sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port); + (*bn) (BLOCK_NONE,NIL); + } + } + + else { /* lookup host name */ + if (tcpdebug) { + sprintf (tmp,"DNS resolution %.80s",host); + mm_log (tmp,TCPDEBUG); + } + (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */ + data = (*bn) (BLOCK_SENSITIVE,NIL); + if (!(he = gethostbyname (lcase (strcpy (hostname,host))))) + sprintf (tmp,"No such host as %.80s",host); + (*bn) (BLOCK_NONSENSITIVE,data); + (*bn) (BLOCK_NONE,NIL); + if (he) { /* DNS resolution won? */ + if (tcpdebug) mm_log ("DNS resolution done",TCPDEBUG); + /* copy address type */ + sin.sin_family = he->h_addrtype; + /* copy host name */ + strcpy (hostname,he->h_name); +#ifdef HOST_NOT_FOUND /* muliple addresses only on DNS systems */ + for (sock = -1,i = 0; (sock < 0) && (s = he->h_addr_list[i]); i++) { + if (i && !silent) mm_log (tmp,WARN); + memcpy (&sin.sin_addr,s,he->h_length); + (*bn) (BLOCK_TCPOPEN,NIL); + sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port); + (*bn) (BLOCK_NONE,NIL); + } +#else /* the one true address then */ + memcpy (&sin.sin_addr,he->h_addr,he->h_length); + (*bn) (BLOCK_TCPOPEN,NIL); + sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port); + (*bn) (BLOCK_NONE,NIL); +#endif + } + } + if (sock >= 0) { /* won */ + stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0, + sizeof (TCPSTREAM)); + stream->port = port; /* port number */ + /* init sockets */ + stream->tcpsi = stream->tcpso = sock; + /* stash in the snuck-in byte */ + if (stream->ictr = ctr) *(stream->iptr = stream->ibuf) = tmp[0]; + /* copy official host name */ + stream->host = cpystr (hostname); + if (tcpdebug) mm_log ("Stream open and ready for read",TCPDEBUG); + } + else if (!silent) mm_log (tmp,ERROR); + return stream; /* return success */ +} + +/* Open a TCP socket + * Accepts: Internet socket address block + * scratch buffer + * pointer to "first byte read in" storage or NIL + * host name for error message + * port number for error message + * Returns: socket if success, else -1 with error string in scratch buffer + */ + +int tcp_socket_open (struct sockaddr_in *sin,char *tmp,int *ctr,char *hst, + unsigned long port) +{ + int i,ti,sock,flgs; + time_t now; + struct protoent *pt = getprotobyname ("tcp"); + fd_set fds,efds; + struct timeval tmo; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + void *data = (*bn) (BLOCK_SENSITIVE,NIL); + sprintf (tmp,"Trying IP address [%s]",inet_ntoa (sin->sin_addr)); + mm_log (tmp,NIL); + /* make a socket */ + if ((sock = socket (sin->sin_family,SOCK_STREAM,pt ? pt->p_proto : 0)) < 0) { + sprintf (tmp,"Unable to create TCP socket: %s",strerror (errno)); + (*bn) (BLOCK_NONSENSITIVE,data); + return -1; + } + else if (sock >= FD_SETSIZE) {/* unselectable sockets are useless */ + sprintf (tmp,"Unable to create selectable TCP socket (%d >= %d)", + sock,FD_SETSIZE); + (*bn) (BLOCK_NONSENSITIVE,data); + close (sock); + errno = EMFILE; + return -1; + } + flgs = fcntl (sock,F_GETFL,0);/* get current socket flags */ + /* set non-blocking if want open timeout */ + if (ctr) fcntl (sock,F_SETFL,flgs | FNDELAY); + /* open connection */ + while ((i = connect (sock,(struct sockaddr *) sin, + sizeof (struct sockaddr_in))) < 0 && (errno == EINTR)); + (*bn) (BLOCK_NONSENSITIVE,data); + if (i < 0) switch (errno) { /* failed? */ + case EAGAIN: /* DG brain damage */ + case EINPROGRESS: /* what we expect to happen */ + case EALREADY: /* or another form of it */ + case EISCONN: /* restart after interrupt? */ + case EADDRINUSE: /* restart after interrupt? */ + break; /* well, not really, it was interrupted */ + default: + sprintf (tmp,"Can't connect to %.80s,%lu: %s",hst,port,strerror (errno)); + close (sock); /* flush socket */ + return -1; + } + + if (ctr) { /* want open timeout */ + now = time (0); /* open timeout */ + ti = ttmo_open ? now + ttmo_open : 0; + tmo.tv_usec = 0; + FD_ZERO (&fds); /* initialize selection vector */ + FD_ZERO (&efds); /* handle errors too */ + FD_SET (sock,&fds); /* block for error or readable */ + FD_SET (sock,&efds); + do { /* block under timeout */ + tmo.tv_sec = ti ? ti - now : 0; + i = select (sock+1,&fds,0,&efds,ti ? &tmo : 0); + now = time (0); /* fake timeout if interrupt & time expired */ + if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0; + } while ((i < 0) && (errno == EINTR)); + if (i > 0) { /* success, make sure really connected */ + fcntl (sock,F_SETFL,flgs);/* restore blocking status */ + /* This used to be a zero-byte read(), but that crashes Solaris */ + /* get socket status */ + while (((i = *ctr = read (sock,tmp,1)) < 0) && (errno == EINTR)); + } + if (i <= 0) { /* timeout or error? */ + i = i ? errno : ETIMEDOUT;/* determine error code */ + close (sock); /* flush socket */ + errno = i; /* return error code */ + sprintf (tmp,"Connection failed to %.80s,%lu: %s",hst,port, + strerror (errno)); + return -1; + } + } + return sock; /* return the socket */ +} + +/* TCP/IP authenticated open + * Accepts: host name + * service name + * returned user name buffer + * Returns: TCP/IP stream if success else NIL + */ + +TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf) +{ + return NIL; /* disabled */ +} + +/* TCP receive line + * Accepts: TCP stream + * Returns: text line string or NIL if failure + */ + +char *tcp_getline (TCPSTREAM *stream) +{ + unsigned long n,contd; + char *ret = tcp_getline_work (stream,&n,&contd); + if (ret && contd) { /* got a line needing continuation? */ + STRINGLIST *stl = mail_newstringlist (); + STRINGLIST *stc = stl; + do { /* collect additional lines */ + stc->text.data = (unsigned char *) ret; + stc->text.size = n; + stc = stc->next = mail_newstringlist (); + ret = tcp_getline_work (stream,&n,&contd); + } while (ret && contd); + if (ret) { /* stash final part of line on list */ + stc->text.data = (unsigned char *) ret; + stc->text.size = n; + /* determine how large a buffer we need */ + for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size; + ret = fs_get (n + 1); /* copy parts into buffer */ + for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next) + memcpy (ret + n,stc->text.data,stc->text.size); + ret[n] = '\0'; + } + mail_free_stringlist (&stl);/* either way, done with list */ + } + return ret; +} + +/* TCP receive line or partial line + * Accepts: TCP stream + * pointer to return size + * pointer to return continuation flag + * Returns: text line string, size and continuation flag, or NIL if failure + */ + +static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size, + long *contd) +{ + unsigned long n; + char *s,*ret,c,d; + *contd = NIL; /* assume no continuation */ + /* make sure have data */ + if (!tcp_getdata (stream)) return NIL; + for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) { + d = *stream->iptr++; /* slurp another character */ + if ((c == '\015') && (d == '\012')) { + ret = (char *) fs_get (n--); + memcpy (ret,s,*size = n); /* copy into a free storage string */ + ret[n] = '\0'; /* tie off string with null */ + return ret; + } + } + /* copy partial string from buffer */ + memcpy ((ret = (char *) fs_get (n)),s,*size = n); + /* get more data from the net */ + if (!tcp_getdata (stream)) fs_give ((void **) &ret); + /* special case of newline broken by buffer */ + else if ((c == '\015') && (*stream->iptr == '\012')) { + stream->iptr++; /* eat the line feed */ + stream->ictr--; + ret[*size = --n] = '\0'; /* tie off string with null */ + } + else *contd = LONGT; /* continuation needed */ + return ret; +} + +/* TCP/IP receive buffer + * Accepts: TCP/IP stream + * size in bytes + * buffer to read into + * Returns: T if success, NIL otherwise + */ + +long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *s) +{ + unsigned long n; + /* make sure socket still alive */ + if (stream->tcpsi < 0) return NIL; + /* can transfer bytes from buffer? */ + if (n = min (size,stream->ictr)) { + memcpy (s,stream->iptr,n); /* yes, slurp as much as we can from it */ + s += n; /* update pointer */ + stream->iptr +=n; + size -= n; /* update # of bytes to do */ + stream->ictr -=n; + } + if (size) { + int i; + fd_set fds,efds; + struct timeval tmo; + time_t t = time (0); + blocknotify_t bn=(blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + (*bn) (BLOCK_TCPREAD,NIL); + while (size > 0) { /* until request satisfied */ + time_t tl = time (0); + time_t now = tl; + time_t ti = ttmo_read ? now + ttmo_read : 0; + if (tcpdebug) mm_log ("Reading TCP buffer",TCPDEBUG); + tmo.tv_usec = 0; + FD_ZERO (&fds); /* initialize selection vector */ + FD_ZERO (&efds); /* handle errors too */ + FD_SET (stream->tcpsi,&fds); + FD_SET (stream->tcpsi,&efds); + errno = NIL; /* block and read */ + do { /* block under timeout */ + tmo.tv_sec = ti ? ti - now : 0; + i = select (stream->tcpsi+1,&fds,0,&efds,ti ? &tmo : 0); + now = time (0); /* fake timeout if interrupt & time expired */ + if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0; + } while ((i < 0) && (errno == EINTR)); + if (i > 0) { /* select says there's data to read? */ + while (((i = read (stream->tcpsi,s,(int) min (maxposint,size))) < 0) && + (errno == EINTR)); + if (i < 1) return tcp_abort (stream); + s += i; /* point at new place to write */ + size -= i; /* reduce byte count */ + if (tcpdebug) mm_log ("Successfully read TCP buffer",TCPDEBUG); + } + else if (i || !tmoh || !(*tmoh) (now - t,now - tl)) + return tcp_abort (stream); + } + (*bn) (BLOCK_NONE,NIL); + } + *s = '\0'; /* tie off string */ + return T; +} + +/* TCP/IP receive data + * Accepts: TCP/IP stream + * Returns: T if success, NIL otherwise + */ + +long tcp_getdata (TCPSTREAM *stream) +{ + int i; + fd_set fds,efds; + struct timeval tmo; + time_t t = time (0); + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + if (stream->tcpsi < 0) return NIL; + (*bn) (BLOCK_TCPREAD,NIL); + while (stream->ictr < 1) { /* if nothing in the buffer */ + time_t tl = time (0); /* start of request */ + time_t now = tl; + time_t ti = ttmo_read ? now + ttmo_read : 0; + if (tcpdebug) mm_log ("Reading TCP data",TCPDEBUG); + tmo.tv_usec = 0; + FD_ZERO (&fds); /* initialize selection vector */ + FD_ZERO (&efds); /* handle errors too */ + FD_SET (stream->tcpsi,&fds);/* set bit in selection vector */ + FD_SET(stream->tcpsi,&efds);/* set bit in error selection vector */ + errno = NIL; /* block and read */ + do { /* block under timeout */ + tmo.tv_sec = ti ? ti - now : 0; + i = select (stream->tcpsi+1,&fds,0,&efds,ti ? &tmo : 0); + now = time (0); /* fake timeout if interrupt & time expired */ + if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0; + } while ((i < 0) && (errno == EINTR)); + if (i > 0) { /* got data? */ + while (((i = read (stream->tcpsi,stream->ibuf,BUFLEN)) < 0) && + (errno == EINTR)); + if (i < 1) return tcp_abort (stream); + stream->iptr = stream->ibuf;/* point at TCP buffer */ + stream->ictr = i; /* set new byte count */ + if (tcpdebug) mm_log ("Successfully read TCP data",TCPDEBUG); + } + else if (i || !tmoh || !(*tmoh) (now - t,now - tl)) + return tcp_abort (stream);/* error or timeout no-continue */ + } + (*bn) (BLOCK_NONE,NIL); + return T; +} + +/* TCP/IP send string as record + * Accepts: TCP/IP stream + * string pointer + * Returns: T if success else NIL + */ + +long tcp_soutr (TCPSTREAM *stream,char *string) +{ + return tcp_sout (stream,string,(unsigned long) strlen (string)); +} + + +/* TCP/IP send string + * Accepts: TCP/IP stream + * string pointer + * byte count + * Returns: T if success else NIL + */ + +long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size) +{ + int i; + fd_set fds,efds; + struct timeval tmo; + time_t t = time (0); + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + if (stream->tcpso < 0) return NIL; + (*bn) (BLOCK_TCPWRITE,NIL); + while (size > 0) { /* until request satisfied */ + time_t tl = time (0); /* start of request */ + time_t now = tl; + time_t ti = ttmo_write ? now + ttmo_write : 0; + if (tcpdebug) mm_log ("Writing to TCP",TCPDEBUG); + tmo.tv_usec = 0; + FD_ZERO (&fds); /* initialize selection vector */ + FD_ZERO (&efds); /* handle errors too */ + FD_SET (stream->tcpso,&fds);/* set bit in selection vector */ + FD_SET(stream->tcpso,&efds);/* set bit in error selection vector */ + errno = NIL; /* block and write */ + do { /* block under timeout */ + tmo.tv_sec = ti ? ti - now : 0; + i = select (stream->tcpso+1,0,&fds,&efds,ti ? &tmo : 0); + now = time (0); /* fake timeout if interrupt & time expired */ + if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0; + } while ((i < 0) && (errno == EINTR)); + if (i > 0) { /* OK to send data? */ + while (((i = write (stream->tcpso,string,size)) < 0) &&(errno == EINTR)); + if (i < 0) return tcp_abort (stream); + size -= i; /* how much we sent */ + string += i; + if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG); + } + else if (i || !tmoh || !(*tmoh) (now - t,now - tl)) + return tcp_abort (stream);/* error or timeout no-continue */ + } + (*bn) (BLOCK_NONE,NIL); + return T; /* all done */ +} + +/* TCP/IP close + * Accepts: TCP/IP stream + */ + +void tcp_close (TCPSTREAM *stream) +{ + tcp_abort (stream); /* nuke the stream */ + /* flush host names */ + if (stream->host) fs_give ((void **) &stream->host); + if (stream->remotehost) fs_give ((void **) &stream->remotehost); + if (stream->localhost) fs_give ((void **) &stream->localhost); + fs_give ((void **) &stream); /* flush the stream */ +} + + +/* TCP/IP abort stream + * Accepts: TCP/IP stream + * Returns: NIL always + */ + +long tcp_abort (TCPSTREAM *stream) +{ + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + if (stream->tcpsi >= 0) { /* no-op if no socket */ + (*bn) (BLOCK_TCPCLOSE,NIL); + close (stream->tcpsi); /* nuke the socket */ + if (stream->tcpsi != stream->tcpso) close (stream->tcpso); + stream->tcpsi = stream->tcpso = -1; + } + (*bn) (BLOCK_NONE,NIL); + return NIL; +} + +/* TCP/IP get host name + * Accepts: TCP/IP stream + * Returns: host name for this stream + */ + +char *tcp_host (TCPSTREAM *stream) +{ + return stream->host; /* use tcp_remotehost() if want guarantees */ +} + + +/* TCP/IP get remote host name + * Accepts: TCP/IP stream + * Returns: host name for this stream + */ + +char *tcp_remotehost (TCPSTREAM *stream) +{ + if (!stream->remotehost) { + struct sockaddr_in sin; + int sinlen = sizeof (struct sockaddr_in); + stream->remotehost = /* get socket's peer name */ + (getpeername (stream->tcpsi,(struct sockaddr *) &sin,(void *) &sinlen) || + (sin.sin_family != AF_INET)) ? + cpystr (stream->host) : tcp_name (&sin,NIL); + } + return stream->remotehost; +} + + +/* TCP/IP return port for this stream + * Accepts: TCP/IP stream + * Returns: port number for this stream + */ + +unsigned long tcp_port (TCPSTREAM *stream) +{ + return stream->port; /* return port number */ +} + + +/* TCP/IP get local host name + * Accepts: TCP/IP stream + * Returns: local host name + */ + +char *tcp_localhost (TCPSTREAM *stream) +{ + if (!stream->localhost) { + struct sockaddr_in sin; + int sinlen = sizeof (struct sockaddr_in); + stream->localhost = /* get socket's name */ + ((stream->port & 0xffff000) || + getsockname (stream->tcpsi,(struct sockaddr *) &sin,(void *) &sinlen) || + (sin.sin_family != AF_INET)) ? + cpystr (mylocalhost ()) : tcp_name (&sin,NIL); + } + return stream->localhost; /* return local host name */ +} + +/* TCP/IP get client host address (server calls only) + * Returns: client host address + */ + +static char *myClientAddr = NIL; + +char *tcp_clientaddr () +{ + if (!myClientAddr) { + struct sockaddr_in sin; + int sinlen = sizeof (struct sockaddr_in); + myClientAddr = /* get stdin's peer name */ + cpystr (getpeername (0,(struct sockaddr *) &sin,(void *) &sinlen) ? + "UNKNOWN" : ((sin.sin_family == AF_INET) ? + inet_ntoa (sin.sin_addr) : "NON-IPv4")); + } + return myClientAddr; +} + + +/* TCP/IP get client host name (server calls only) + * Returns: client host name + */ + +static char *myClientHost = NIL; + +char *tcp_clienthost () +{ + if (!myClientHost) { + struct sockaddr_in sin; + int sinlen = sizeof (struct sockaddr_in); + myClientHost = /* get stdin's peer name */ + getpeername (0,(struct sockaddr *) &sin,(void *) &sinlen) ? + cpystr ("UNKNOWN") : ((sin.sin_family == AF_INET) ? + tcp_name (&sin,T) : cpystr ("NON-IPv4")); + } + return myClientHost; +} + +/* TCP/IP get server host address (server calls only) + * Returns: server host address + */ + +static char *myServerAddr = NIL; + +char *tcp_serveraddr () +{ + if (!myServerAddr) { + struct sockaddr_in sin; + int sinlen = sizeof (struct sockaddr_in); + myServerAddr = /* get stdin's peer name */ + cpystr (getsockname (0,(struct sockaddr *) &sin,(void *) &sinlen) ? + "UNKNOWN" : ((sin.sin_family == AF_INET) ? + inet_ntoa (sin.sin_addr) : "NON-IPv4")); + } + return myServerAddr; +} + + +/* TCP/IP get server host name (server calls only) + * Returns: server host name + */ + +static char *myServerHost = NIL; +static long myServerPort = -1; + +char *tcp_serverhost () +{ + if (!myServerHost) { + struct sockaddr_in sin; + int sinlen = sizeof (struct sockaddr_in); + /* get stdin's name */ + if (getsockname (0,(struct sockaddr *) &sin,(void *) &sinlen) || + (sin.sin_family != AF_INET)) myServerHost = cpystr (mylocalhost ()); + else { + myServerHost = tcp_name (&sin,NIL); + myServerPort = ntohs (sin.sin_port); + } + } + return myServerHost; +} + + +/* TCP/IP get server port number (server calls only) + * Returns: server port number + */ + +long tcp_serverport () +{ + if (!myServerHost) tcp_serverhost (); + return myServerPort; +} + +/* TCP/IP return canonical form of host name + * Accepts: host name + * Returns: canonical form of host name + */ + +char *tcp_canonical (char *name) +{ + char *ret,host[MAILTMPLEN]; + struct hostent *he; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + void *data; + /* look like domain literal? */ + if (name[0] == '[' && name[strlen (name) - 1] == ']') return name; + (*bn) (BLOCK_DNSLOOKUP,NIL); /* quell alarms */ + data = (*bn) (BLOCK_SENSITIVE,NIL); + if (tcpdebug) { + sprintf (host,"DNS canonicalization %.80s",name); + mm_log (host,TCPDEBUG); + } + /* note that Amiga requires lowercase! */ + ret = (he = gethostbyname (lcase (strcpy (host,name)))) ? + (char *) he->h_name : name; + (*bn) (BLOCK_NONSENSITIVE,data); + (*bn) (BLOCK_NONE,NIL); /* alarms OK now */ + if (tcpdebug) mm_log ("DNS canonicalization done",TCPDEBUG); + return ret; +} + + +/* TCP/IP return name from socket + * Accepts: socket + * verbose flag + * Returns: cpystr name + */ + +char *tcp_name (struct sockaddr_in *sin,long flag) +{ + char *ret,*t,adr[MAILTMPLEN],tmp[MAILTMPLEN]; + sprintf (ret = adr,"[%.80s]",inet_ntoa (sin->sin_addr)); + if (allowreversedns) { + struct hostent *he; + blocknotify_t bn = (blocknotify_t)mail_parameters(NIL,GET_BLOCKNOTIFY,NIL); + void *data; + if (tcpdebug) { + sprintf (tmp,"Reverse DNS resolution %s",adr); + mm_log (tmp,TCPDEBUG); + } + (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */ + data = (*bn) (BLOCK_SENSITIVE,NIL); + /* translate address to name */ + if (t = tcp_name_valid ((he = gethostbyaddr ((char *) &sin->sin_addr, + sizeof (struct in_addr), + sin->sin_family)) ? + (char *) he->h_name : NIL)) { + /* produce verbose form if needed */ + if (flag) sprintf (ret = tmp,"%s %s",t,adr); + else ret = t; + } + (*bn) (BLOCK_NONSENSITIVE,data); + (*bn) (BLOCK_NONE,NIL); /* alarms OK now */ + if (tcpdebug) mm_log ("Reverse DNS resolution done",TCPDEBUG); + } + return cpystr (ret); +} + + +/* Validate name + * Accepts: domain name + * Returns: T if valid, NIL otherwise + */ + +char *tcp_name_valid (char *s) +{ + int c; + char *ret,*tail; + /* must be non-empty and not too long */ + if ((ret = (s && *s) ? s : NIL) && (tail = ret + NETMAXHOST)) { + /* must be alnum, dot, or hyphen */ + while ((c = *s++) && (s <= tail) && + (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || + ((c >= '0') && (c <= '9')) || (c == '-') || (c == '.'))); + if (c) ret = NIL; + } + return ret; +} diff --git a/imap/src/osdep/amiga/tcp_ami.h b/imap/src/osdep/amiga/tcp_ami.h new file mode 100644 index 00000000..d4b29797 --- /dev/null +++ b/imap/src/osdep/amiga/tcp_ami.h @@ -0,0 +1,48 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UNIX TCP/IP routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 1 August 1988 + * Last Edited: 30 August 2006 + */ + + +/* TCP input buffer */ + +#define BUFLEN 8192 + + +/* TCP I/O stream */ + +#define TCPSTREAM struct tcp_stream +TCPSTREAM { + char *host; /* host name */ + unsigned long port; /* port number */ + char *localhost; /* local host name */ + char *remotehost; /* remote host name */ + int tcpsi; /* input socket */ + int tcpso; /* output socket */ + int ictr; /* input counter */ + char *iptr; /* input pointer */ + char ibuf[BUFLEN]; /* input buffer */ +}; diff --git a/imap/src/osdep/amiga/tenex.c b/imap/src/osdep/amiga/tenex.c new file mode 100644 index 00000000..eee61fba --- /dev/null +++ b/imap/src/osdep/amiga/tenex.c @@ -0,0 +1,1470 @@ +/* ======================================================================== + * Copyright 1988-2007 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Tenex mail routines + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 22 May 1990 + * Last Edited: 11 October 2007 + */ + + +/* FILE TIME SEMANTICS + * + * The atime is the last read time of the file. + * The mtime is the last flags update time of the file. + * The ctime is the last write time of the file. + * + * TEXT SIZE SEMANTICS + * + * Most of the text sizes are in internal (LF-only) form, except for the + * msg.text size. Beware. + */ + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include "osdep.h" +#include <sys/stat.h> +#include "misc.h" +#include "dummy.h" + +/* TENEX I/O stream local data */ + +typedef struct tenex_local { + unsigned int shouldcheck: 1; /* if ping should do a check instead */ + unsigned int mustcheck: 1; /* if ping must do a check instead */ + int fd; /* file descriptor for I/O */ + off_t filesize; /* file size parsed */ + time_t filetime; /* last file time */ + time_t lastsnarf; /* local snarf time */ + unsigned char *buf; /* temporary buffer */ + unsigned long buflen; /* current size of temporary buffer */ + unsigned long uid; /* current text uid */ + SIZEDTEXT text; /* current text */ +} TENEXLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((TENEXLOCAL *) stream->local) + + +/* Function prototypes */ + +DRIVER *tenex_valid (char *name); +int tenex_isvalid (char *name,char *tmp); +void *tenex_parameters (long function,void *value); +void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void tenex_list (MAILSTREAM *stream,char *ref,char *pat); +void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat); +long tenex_create (MAILSTREAM *stream,char *mailbox); +long tenex_delete (MAILSTREAM *stream,char *mailbox); +long tenex_rename (MAILSTREAM *stream,char *old,char *newname); +long tenex_status (MAILSTREAM *stream,char *mbx,long flags); +MAILSTREAM *tenex_open (MAILSTREAM *stream); +void tenex_close (MAILSTREAM *stream,long options); +void tenex_fast (MAILSTREAM *stream,char *sequence,long flags); +void tenex_flags (MAILSTREAM *stream,char *sequence,long flags); +char *tenex_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags); +long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); +void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); +long tenex_ping (MAILSTREAM *stream); +void tenex_check (MAILSTREAM *stream); +void tenex_snarf (MAILSTREAM *stream); +long tenex_expunge (MAILSTREAM *stream,char *sequence,long options); +long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + +unsigned long tenex_size (MAILSTREAM *stream,unsigned long m); +char *tenex_file (char *dst,char *name); +long tenex_parse (MAILSTREAM *stream); +MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno); +void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt); +void tenex_update_status (MAILSTREAM *stream,unsigned long msgno, + long syncflag); +unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno, + unsigned long *size); + +/* Tenex mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER tenexdriver = { + "tenex", /* driver name */ + DR_LOCAL|DR_MAIL|DR_NOSTICKY|DR_LOCKING, + /* driver flags */ + (DRIVER *) NIL, /* next driver */ + tenex_valid, /* mailbox is valid for us */ + tenex_parameters, /* manipulate parameters */ + tenex_scan, /* scan mailboxes */ + tenex_list, /* list mailboxes */ + tenex_lsub, /* list subscribed mailboxes */ + NIL, /* subscribe to mailbox */ + NIL, /* unsubscribe from mailbox */ + dummy_create, /* create mailbox */ + tenex_delete, /* delete mailbox */ + tenex_rename, /* rename mailbox */ + tenex_status, /* status of mailbox */ + tenex_open, /* open mailbox */ + tenex_close, /* close mailbox */ + tenex_fast, /* fetch message "fast" attributes */ + tenex_flags, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + tenex_header, /* fetch message header */ + tenex_text, /* fetch message body */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + tenex_flag, /* modify flags */ + tenex_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + tenex_ping, /* ping mailbox to see if still alive */ + tenex_check, /* check for new messages */ + tenex_expunge, /* expunge deleted messages */ + tenex_copy, /* copy messages to another mailbox */ + tenex_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM tenexproto = {&tenexdriver}; + +/* Tenex mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *tenex_valid (char *name) +{ + char tmp[MAILTMPLEN]; + return tenex_isvalid (name,tmp) ? &tenexdriver : NIL; +} + + +/* Tenex mail test for valid mailbox + * Accepts: mailbox name + * Returns: T if valid, NIL otherwise + */ + +int tenex_isvalid (char *name,char *tmp) +{ + int fd; + int ret = NIL; + char *s,file[MAILTMPLEN]; + struct stat sbuf; + time_t tp[2]; + errno = EINVAL; /* assume invalid argument */ + /* if file, get its status */ + if ((s = tenex_file (file,name)) && !stat (s,&sbuf)) { + if (!sbuf.st_size) { /* allow empty file if INBOX */ + if ((s = mailboxfile (tmp,name)) && !*s) ret = T; + else errno = 0; /* empty file */ + } + else if ((fd = open (file,O_RDONLY,NIL)) >= 0) { + memset (tmp,'\0',MAILTMPLEN); + if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\012')) && + (s[-1] != '\015')) { /* valid format? */ + *s = '\0'; /* tie off header */ + /* must begin with dd-mmm-yy" */ + ret = (((tmp[2] == '-' && tmp[6] == '-') || + (tmp[1] == '-' && tmp[5] == '-')) && + (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL; + } + else errno = -1; /* bogus format */ + close (fd); /* close the file */ + /* \Marked status? */ + if (sbuf.st_ctime > sbuf.st_atime) { + tp[0] = sbuf.st_atime; /* preserve atime and mtime */ + tp[1] = sbuf.st_mtime; + utime (file,tp); /* set the times */ + } + } + } + /* in case INBOX but not tenex format */ + else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1; + return ret; /* return what we should */ +} + +/* Tenex manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *tenex_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = tenex_file ((char *) value,"INBOX"); + break; + } + return ret; +} + + +/* Tenex mail scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + if (stream) dummy_scan (NIL,ref,pat,contents); +} + + +/* Tenex mail list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void tenex_list (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_list (NIL,ref,pat); +} + + +/* Tenex mail list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_lsub (NIL,ref,pat); +} + +/* Tenex mail delete mailbox + * Accepts: MAIL stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long tenex_delete (MAILSTREAM *stream,char *mailbox) +{ + return tenex_rename (stream,mailbox,NIL); +} + + +/* Tenex mail rename mailbox + * Accepts: MAIL stream + * old mailbox name + * new mailbox name (or NIL for delete) + * Returns: T on success, NIL on failure + */ + +long tenex_rename (MAILSTREAM *stream,char *old,char *newname) +{ + long ret = T; + char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN]; + int fd,ld; + struct stat sbuf; + if (!tenex_file (file,old) || + (newname && (!((s = mailboxfile (tmp,newname)) && *s) || + ((s = strrchr (tmp,'/')) && !s[1])))) { + sprintf (tmp,newname ? + "Can't rename mailbox %.80s to %.80s: invalid name" : + "Can't delete mailbox %.80s: invalid name", + old,newname); + MM_LOG (tmp,ERROR); + return NIL; + } + else if ((fd = open (file,O_RDWR,NIL)) < 0) { + sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get exclusive parse/append permission */ + if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) { + MM_LOG ("Unable to lock rename mailbox",ERROR); + return NIL; + } + /* lock out other users */ + if (flock (fd,LOCK_EX|LOCK_NB)) { + close (fd); /* couldn't lock, give up on it then */ + sprintf (tmp,"Mailbox %.80s is in use by another process",old); + MM_LOG (tmp,ERROR); + unlockfd (ld,lock); /* release exclusive parse/append permission */ + return NIL; + } + + if (newname) { /* want rename? */ + if (s = strrchr (tmp,'/')) {/* found superior to destination name? */ + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,tmp,get_dir_protection (newname))) + ret = NIL; + else *s = c; /* restore full name */ + } + /* rename the file */ + if (ret && rename (file,tmp)) { + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname, + strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; /* set failure */ + } + } + else if (unlink (file)) { + sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno)); + MM_LOG (tmp,ERROR); + ret = NIL; /* set failure */ + } + flock (fd,LOCK_UN); /* release lock on the file */ + close (fd); /* close the file */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + /* recreate file if renamed INBOX */ + if (ret && !compare_cstring (old,"INBOX")) dummy_create (NIL,"mail.txt"); + return ret; /* return success */ +} + +/* Tenex Mail status + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long tenex_status (MAILSTREAM *stream,char *mbx,long flags) +{ + MAILSTATUS status; + unsigned long i; + MAILSTREAM *tstream = NIL; + MAILSTREAM *systream = NIL; + /* make temporary stream (unless this mbx) */ + if (!stream && !(stream = tstream = + mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL; + status.flags = flags; /* return status values */ + status.messages = stream->nmsgs; + status.recent = stream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++) + if (!mail_elt (stream,i)->seen) status.unseen++; + status.uidnext = stream->uid_last + 1; + status.uidvalidity = stream->uid_validity; + /* calculate post-snarf results */ + if (!status.recent && stream->inbox && + (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) { + status.messages += systream->nmsgs; + status.recent += systream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1; i <= systream->nmsgs; i++) + if (!mail_elt (systream,i)->seen) status.unseen++; + /* kludge but probably good enough */ + status.uidnext += systream->nmsgs; + } + MM_STATUS(stream,mbx,&status);/* pass status to main program */ + if (tstream) mail_close (tstream); + if (systream) mail_close (systream); + return T; /* success */ +} + +/* Tenex mail open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *tenex_open (MAILSTREAM *stream) +{ + int fd,ld; + char tmp[MAILTMPLEN]; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return user_flags (&tenexproto); + if (stream->local) fatal ("tenex recycle stream"); + user_flags (stream); /* set up user flags */ + /* canonicalize the mailbox name */ + if (!tenex_file (tmp,stream->mailbox)) { + sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox); + MM_LOG (tmp,ERROR); + } + if (stream->rdonly || + (fd = open (tmp,O_RDWR,NIL)) < 0) { + if ((fd = open (tmp,O_RDONLY,NIL)) < 0) { + sprintf (tmp,"Can't open mailbox: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + else if (!stream->rdonly) { /* got it, but readonly */ + MM_LOG ("Can't get write access to mailbox, access is readonly",WARN); + stream->rdonly = T; + } + } + stream->local = fs_get (sizeof (TENEXLOCAL)); + LOCAL->buf = (char *) fs_get (CHUNKSIZE); + LOCAL->buflen = CHUNKSIZE - 1; + LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE); + LOCAL->text.size = CHUNKSIZE - 1; + + /* note if an INBOX or not */ + stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); + LOCAL->fd = fd; /* bind the file */ + /* flush old name */ + fs_give ((void **) &stream->mailbox); + /* save canonical name */ + stream->mailbox = cpystr (tmp); + /* get shared parse permission */ + if ((ld = lockfd (fd,tmp,LOCK_SH)) < 0) { + MM_LOG ("Unable to lock open mailbox",ERROR); + return NIL; + } + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH); /* lock the file */ + (*bn) (BLOCK_NONE,NIL); + unlockfd (ld,tmp); /* release shared parse permission */ + LOCAL->filesize = 0; /* initialize parsed file size */ + /* time not set up yet */ + LOCAL->lastsnarf = LOCAL->filetime = 0; + LOCAL->mustcheck = LOCAL->shouldcheck = NIL; + stream->sequence++; /* bump sequence number */ + /* parse mailbox */ + stream->nmsgs = stream->recent = 0; + if (tenex_ping (stream) && !stream->nmsgs) + MM_LOG ("Mailbox is empty",(long) NIL); + if (!LOCAL) return NIL; /* failure if stream died */ + stream->perm_seen = stream->perm_deleted = + stream->perm_flagged = stream->perm_answered = stream->perm_draft = + stream->rdonly ? NIL : T; + stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff; + return stream; /* return stream to caller */ +} + +/* Tenex mail close + * Accepts: MAIL stream + * close options + */ + +void tenex_close (MAILSTREAM *stream,long options) +{ + if (stream && LOCAL) { /* only if a file is open */ + int silent = stream->silent; + stream->silent = T; /* note this stream is dying */ + if (options & CL_EXPUNGE) tenex_expunge (stream,NIL,NIL); + stream->silent = silent; /* restore previous status */ + flock (LOCAL->fd,LOCK_UN); /* unlock local file */ + close (LOCAL->fd); /* close the local file */ + /* free local text buffer */ + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); + if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + +/* Tenex mail fetch fast data + * Accepts: MAIL stream + * sequence + * option flags + */ + +void tenex_fast (MAILSTREAM *stream,char *sequence,long flags) +{ + STRING bs; + MESSAGECACHE *elt; + unsigned long i; + if (stream && LOCAL && + ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) { + if (!elt->rfc822_size) { /* have header size yet? */ + lseek (LOCAL->fd,elt->private.special.offset + + elt->private.special.text.size,L_SET); + /* resize bigbuf if necessary */ + if (LOCAL->buflen < elt->private.msg.full.text.size) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buflen = elt->private.msg.full.text.size; + LOCAL->buf = (char *) fs_get (LOCAL->buflen + 1); + } + /* tie off string */ + LOCAL->buf[elt->private.msg.full.text.size] = '\0'; + /* read in the message */ + read (LOCAL->fd,LOCAL->buf,elt->private.msg.full.text.size); + INIT (&bs,mail_string,(void *) LOCAL->buf, + elt->private.msg.full.text.size); + /* calculate its CRLF size */ + elt->rfc822_size = strcrlflen (&bs); + } + tenex_elt (stream,i); /* get current flags from file */ + } +} + + +/* Tenex mail fetch flags + * Accepts: MAIL stream + * sequence + * option flags + * Sniffs at file to get flags + */ + +void tenex_flags (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i; + if (stream && LOCAL && + ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1; i <= stream->nmsgs; i++) + if (mail_elt (stream,i)->sequence) tenex_elt (stream,i); +} + +/* TENEX mail fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + +char *tenex_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags) +{ + char *s; + unsigned long i; + *length = 0; /* default to empty */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + /* get to header position */ + lseek (LOCAL->fd,tenex_hdrpos (stream,msgno,&i),L_SET); + if (flags & FT_INTERNAL) { + if (i > LOCAL->buflen) { /* resize if not enough space */ + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1); + } + /* slurp the data */ + read (LOCAL->fd,LOCAL->buf,*length = i); + } + else { + s = (char *) fs_get (i + 1);/* get readin buffer */ + s[i] = '\0'; /* tie off string */ + read (LOCAL->fd,s,i); /* slurp the data */ + /* make CRLF copy of string */ + *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,i); + fs_give ((void **) &s); /* free readin buffer */ + } + return (char *) LOCAL->buf; +} + +/* TENEX mail fetch message text (body only) + * Accepts: MAIL stream + * message # to fetch + * pointer to returned stringstruct + * option flags + * Returns: T, always + */ + +long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + char *s; + unsigned long i,j; + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + /* get message status */ + elt = tenex_elt (stream,msgno); + /* if message not seen */ + if (!(flags & FT_PEEK) && !elt->seen) { + elt->seen = T; /* mark message as seen */ + /* recalculate status */ + tenex_update_status (stream,msgno,T); + MM_FLAGS (stream,msgno); + } + if (flags & FT_INTERNAL) { /* if internal representation wanted */ + /* find header position */ + i = tenex_hdrpos (stream,msgno,&j); + if (i > LOCAL->buflen) { /* resize if not enough space */ + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1); + } + /* go to text position */ + lseek (LOCAL->fd,i + j,L_SET); + /* slurp the data */ + read (LOCAL->fd,LOCAL->buf,i); + /* set up stringstruct for internal */ + INIT (bs,mail_string,LOCAL->buf,i); + } + else { /* normal form, previous text cached? */ + if (elt->private.uid == LOCAL->uid) + i = elt->private.msg.text.text.size; + else { /* not cached, cache it now */ + LOCAL->uid = elt->private.uid; + /* find header position */ + i = tenex_hdrpos (stream,msgno,&j); + /* go to text position */ + lseek (LOCAL->fd,i + j,L_SET); + s = (char *) fs_get ((i = tenex_size (stream,msgno) - j) + 1); + s[i] = '\0'; /* tie off string */ + read (LOCAL->fd,s,i); /* slurp the data */ + /* make CRLF copy of string */ + i = elt->private.msg.text.text.size = + strcrlfcpy (&LOCAL->text.data,&LOCAL->text.size,s,i); + fs_give ((void **) &s); /* free readin buffer */ + } + /* set up stringstruct */ + INIT (bs,mail_string,LOCAL->text.data,i); + } + return T; /* success */ +} + +/* Tenex mail modify flags + * Accepts: MAIL stream + * sequence + * flag(s) + * option flags + */ + +void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) +{ + time_t tp[2]; + struct stat sbuf; + if (!stream->rdonly) { /* make sure the update takes */ + fsync (LOCAL->fd); + fstat (LOCAL->fd,&sbuf); /* get current write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* make sure read comes after all that */ + utime (stream->mailbox,tp); + } +} + + +/* Tenex mail per-message modify flags + * Accepts: MAIL stream + * message cache element + */ + +void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + struct stat sbuf; + /* maybe need to do a checkpoint? */ + if (LOCAL->filetime && !LOCAL->shouldcheck) { + fstat (LOCAL->fd,&sbuf); /* get current write time */ + if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T; + LOCAL->filetime = 0; /* don't do this test for any other messages */ + } + /* recalculate status */ + tenex_update_status (stream,elt->msgno,NIL); +} + +/* Tenex mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream still alive, NIL if not + */ + +long tenex_ping (MAILSTREAM *stream) +{ + unsigned long i = 1; + long r = T; + int ld; + char lock[MAILTMPLEN]; + struct stat sbuf; + if (stream && LOCAL) { /* only if stream already open */ + fstat (LOCAL->fd,&sbuf); /* get current file poop */ + if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) && + (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T; + /* check for changed message status */ + if (LOCAL->mustcheck || LOCAL->shouldcheck) { + LOCAL->filetime = sbuf.st_mtime; + if (LOCAL->shouldcheck) /* babble when we do this unilaterally */ + MM_NOTIFY (stream,"[CHECK] Checking for flag updates",NIL); + while (i <= stream->nmsgs) tenex_elt (stream,i++); + LOCAL->mustcheck = LOCAL->shouldcheck = NIL; + } + /* get shared parse/append permission */ + if ((sbuf.st_size != LOCAL->filesize) && + ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) { + /* parse resulting mailbox */ + r = (tenex_parse (stream)) ? T : NIL; + unlockfd (ld,lock); /* release shared parse/append permission */ + } + if (LOCAL) { /* stream must still be alive */ + /* snarf if this is a read-write inbox */ + if (stream->inbox && !stream->rdonly) { + tenex_snarf (stream); + fstat (LOCAL->fd,&sbuf);/* see if file changed now */ + if ((sbuf.st_size != LOCAL->filesize) && + ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) { + /* parse resulting mailbox */ + r = (tenex_parse (stream)) ? T : NIL; + unlockfd (ld,lock); /* release shared parse/append permission */ + } + } + } + } + return r; /* return result of the parse */ +} + + +/* Tenex mail check mailbox (reparses status too) + * Accepts: MAIL stream + */ + +void tenex_check (MAILSTREAM *stream) +{ + /* mark that a check is desired */ + if (LOCAL) LOCAL->mustcheck = T; + if (tenex_ping (stream)) MM_LOG ("Check completed",(long) NIL); +} + +/* Tenex mail snarf messages from system inbox + * Accepts: MAIL stream + */ + +void tenex_snarf (MAILSTREAM *stream) +{ + unsigned long i = 0; + unsigned long j,r,hdrlen,txtlen; + struct stat sbuf; + char *hdr,*txt,lock[MAILTMPLEN],tmp[MAILTMPLEN]; + MESSAGECACHE *elt; + MAILSTREAM *sysibx = NIL; + int ld; + /* give up if can't get exclusive permission */ + if ((time (0) >= (LOCAL->lastsnarf + + (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) && + strcmp (sysinbox (),stream->mailbox) && + ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) { + MM_CRITICAL (stream); /* go critical */ + /* sizes match and anything in sysinbox? */ + if (!stat (sysinbox (),&sbuf) && sbuf.st_size && + !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) && + (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) && + (!sysibx->rdonly) && (r = sysibx->nmsgs)) { + /* yes, go to end of file in our mailbox */ + lseek (LOCAL->fd,sbuf.st_size,L_SET); + /* for each message in sysibx mailbox */ + while (r && (++i <= sysibx->nmsgs)) { + /* snarf message from system INBOX */ + hdr = cpystr (mail_fetchheader_full(sysibx,i,NIL,&hdrlen,FT_INTERNAL)); + txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_INTERNAL|FT_PEEK); + /* if have a message */ + if (j = hdrlen + txtlen) { + /* calculate header line */ + mail_date (LOCAL->buf,elt = mail_elt (sysibx,i)); + sprintf (LOCAL->buf + strlen (LOCAL->buf), + ",%lu;0000000000%02o\n",j,(unsigned) + ((fSEEN * elt->seen) + (fDELETED * elt->deleted) + + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + + (fDRAFT * elt->draft))); + /* copy message */ + if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) || + (write (LOCAL->fd,hdr,hdrlen) < 0) || + (write (LOCAL->fd,txt,txtlen) < 0)) r = 0; + } + fs_give ((void **) &hdr); + } + + /* make sure all the updates take */ + if (fsync (LOCAL->fd)) r = 0; + if (r) { /* delete all the messages we copied */ + if (r == 1) strcpy (tmp,"1"); + else sprintf (tmp,"1:%lu",r); + mail_flag (sysibx,tmp,"\\Deleted",ST_SET); + mail_expunge (sysibx); /* now expunge all those messages */ + } + else { + sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno)); + MM_LOG (LOCAL->buf,WARN); + ftruncate (LOCAL->fd,sbuf.st_size); + } + fstat (LOCAL->fd,&sbuf); /* yes, get current file size */ + LOCAL->filetime = sbuf.st_mtime; + } + if (sysibx) mail_close (sysibx); + MM_NOCRITICAL (stream); /* release critical */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + LOCAL->lastsnarf = time (0);/* note time of last snarf */ + } +} + +/* Tenex mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T, always + */ + +long tenex_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret; + time_t tp[2]; + struct stat sbuf; + off_t pos = 0; + int ld; + unsigned long i = 1; + unsigned long j,k,m,recent; + unsigned long n = 0; + unsigned long delta = 0; + char lock[MAILTMPLEN]; + MESSAGECACHE *elt; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + if (!(ret = (sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : LONGT) && + tenex_ping (stream))); /* parse sequence if given, ping stream */ + else if (stream->rdonly) MM_LOG ("Expunge ignored on readonly mailbox",WARN); + else { + if (LOCAL->filetime && !LOCAL->shouldcheck) { + fstat (LOCAL->fd,&sbuf); /* get current write time */ + if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T; + } + /* The cretins who designed flock() created a window of vulnerability in + * upgrading locks from shared to exclusive or downgrading from exclusive + * to shared. Rather than maintain the lock at shared status at a minimum, + * flock() actually *releases* the former lock. Obviously they never talked + * to any database guys. Fortunately, we have the parse/append permission + * lock. If we require this lock before going exclusive on the mailbox, + * another process can not sneak in and steal the exclusive mailbox lock on + * us, because it will block on trying to get parse/append permission first. + */ + /* get exclusive parse/append permission */ + if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0) + MM_LOG ("Unable to lock expunge mailbox",ERROR); + /* make sure see any newly-arrived messages */ + else if (!tenex_parse (stream)); + /* get exclusive access */ + else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) { + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH);/* recover previous lock */ + (*bn) (BLOCK_NONE,NIL); + MM_LOG ("Can't expunge because mailbox is in use by another process", + ERROR); + unlockfd (ld,lock); /* release exclusive parse/append permission */ + } + + else { + MM_CRITICAL (stream); /* go critical */ + recent = stream->recent; /* get recent now that pinged and locked */ + /* for each message */ + while (i <= stream->nmsgs) { + /* get cache element */ + elt = tenex_elt (stream,i); + /* number of bytes to smash or preserve */ + k = elt->private.special.text.size + tenex_size (stream,i); + /* if need to expunge this message */ + if (elt->deleted && (sequence ? elt->sequence : T)) { + /* if recent, note one less recent message */ + if (elt->recent) --recent; + delta += k; /* number of bytes to delete */ + /* notify upper levels */ + mail_expunged (stream,i); + n++; /* count up one more expunged message */ + } + else if (i++ && delta) {/* preserved message */ + /* first byte to preserve */ + j = elt->private.special.offset; + do { /* read from source position */ + m = min (k,LOCAL->buflen); + lseek (LOCAL->fd,j,L_SET); + read (LOCAL->fd,LOCAL->buf,m); + pos = j - delta; /* write to destination position */ + lseek (LOCAL->fd,pos,L_SET); + while (T) { + lseek (LOCAL->fd,pos,L_SET); + if (write (LOCAL->fd,LOCAL->buf,m) > 0) break; + MM_NOTIFY (stream,strerror (errno),WARN); + MM_DISKERROR (stream,errno,T); + } + pos += m; /* new position */ + j += m; /* next chunk, perhaps */ + } while (k -= m); /* until done */ + /* note the new address of this text */ + elt->private.special.offset -= delta; + } + /* preserved but no deleted messages */ + else pos = elt->private.special.offset + k; + } + + if (n) { /* truncate file after last message */ + if (pos != (LOCAL->filesize -= delta)) { + sprintf (LOCAL->buf, + "Calculated size mismatch %lu != %lu, delta = %lu", + (unsigned long) pos,(unsigned long) LOCAL->filesize,delta); + MM_LOG (LOCAL->buf,WARN); + LOCAL->filesize = pos;/* fix it then */ + } + ftruncate (LOCAL->fd,LOCAL->filesize); + sprintf (LOCAL->buf,"Expunged %lu messages",n); + /* output the news */ + MM_LOG (LOCAL->buf,(long) NIL); + } + else MM_LOG ("No messages deleted, so no update needed",(long) NIL); + fsync (LOCAL->fd); /* force disk update */ + fstat (LOCAL->fd,&sbuf); /* get new write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* reset atime to now */ + utime (stream->mailbox,tp); + MM_NOCRITICAL (stream); /* release critical */ + /* notify upper level of new mailbox size */ + mail_exists (stream,stream->nmsgs); + mail_recent (stream,recent); + (*bn) (BLOCK_FILELOCK,NIL); + flock (LOCAL->fd,LOCK_SH);/* allow sharers again */ + (*bn) (BLOCK_NONE,NIL); + unlockfd (ld,lock); /* release exclusive parse/append permission */ + } + } + return LONGT; +} + +/* Tenex mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if success, NIL if failed + */ + +long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + struct stat sbuf; + time_t tp[2]; + MESSAGECACHE *elt; + unsigned long i,j,k; + long ret = LONGT; + int fd,ld; + char file[MAILTMPLEN],lock[MAILTMPLEN]; + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + /* make sure valid mailbox */ + if (!tenex_isvalid (mailbox,LOCAL->buf)) switch (errno) { + case ENOENT: /* no such file? */ + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); + return NIL; + case 0: /* merely empty file? */ + break; + case EACCES: /* file protected */ + sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + case EINVAL: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Invalid Tenex-format mailbox name: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + default: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Not a Tenex-format mailbox: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) return NIL; + /* got file? */ + if ((fd = open (tenex_file(file,mailbox),O_RDWR,NIL)) < 0) { + sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + MM_CRITICAL (stream); /* go critical */ + /* get exclusive parse/append permission */ + if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) { + MM_LOG ("Unable to lock copy mailbox",ERROR); + MM_NOCRITICAL (stream); + return NIL; + } + fstat (fd,&sbuf); /* get current file size */ + lseek (fd,sbuf.st_size,L_SET);/* move to end of file */ + + /* for each requested message */ + for (i = 1; ret && (i <= stream->nmsgs); i++) + if ((elt = mail_elt (stream,i))->sequence) { + lseek (LOCAL->fd,elt->private.special.offset,L_SET); + /* number of bytes to copy */ + k = elt->private.special.text.size + tenex_size (stream,i); + do { /* read from source position */ + j = min (k,LOCAL->buflen); + read (LOCAL->fd,LOCAL->buf,j); + if (write (fd,LOCAL->buf,j) < 0) ret = NIL; + } while (ret && (k -= j));/* until done */ + } + /* make sure all the updates take */ + if (!(ret && (ret = !fsync (fd)))) { + sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + ftruncate (fd,sbuf.st_size); + } + if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */ + /* else preserve \Marked status */ + else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0); + tp[1] = sbuf.st_mtime; /* preserve mtime */ + utime (file,tp); /* set the times */ + close (fd); /* close the file */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + MM_NOCRITICAL (stream); /* release critical */ + /* delete all requested messages */ + if (ret && (options & CP_MOVE)) { + for (i = 1; i <= stream->nmsgs; i++) + if ((elt = tenex_elt (stream,i))->sequence) { + elt->deleted = T; /* mark message deleted */ + /* recalculate status */ + tenex_update_status (stream,i,NIL); + } + if (!stream->rdonly) { /* make sure the update takes */ + fsync (LOCAL->fd); + fstat (LOCAL->fd,&sbuf); /* get current write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* make sure atime remains greater */ + utime (stream->mailbox,tp); + } + } + if (ret && mail_parameters (NIL,GET_COPYUID,NIL)) + MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN); + return ret; +} + +/* Tenex mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + struct stat sbuf; + int fd,ld,c; + char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN]; + time_t tp[2]; + FILE *df; + MESSAGECACHE elt; + long f; + unsigned long i,j,uf,size; + STRING *message; + long ret = LONGT; + /* default stream to prototype */ + if (!stream) stream = user_flags (&tenexproto); + /* make sure valid mailbox */ + if (!tenex_isvalid (mailbox,tmp)) switch (errno) { + case ENOENT: /* no such file? */ + if (!compare_cstring (mailbox,"INBOX")) dummy_create (NIL,"mail.txt"); + else { + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); + return NIL; + } + /* falls through */ + case 0: /* merely empty file? */ + break; + case EACCES: /* file protected */ + sprintf (tmp,"Can't access destination: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + case EINVAL: + sprintf (tmp,"Invalid TENEX-format mailbox name: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + default: + sprintf (tmp,"Not a TENEX-format mailbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get first message */ + if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL; + + /* open destination mailbox */ + if (((fd = open (tenex_file (file,mailbox),O_WRONLY|O_APPEND,NIL)) < 0) || + !(df = fdopen (fd,"ab"))) { + sprintf (tmp,"Can't open append mailbox: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get parse/append permission */ + if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) { + MM_LOG ("Unable to lock append mailbox",ERROR); + close (fd); + return NIL; + } + MM_CRITICAL (stream); /* go critical */ + fstat (fd,&sbuf); /* get current file size */ + errno = 0; + do { /* parse flags */ + if (!SIZE (message)) { /* guard against zero-length */ + MM_LOG ("Append of zero-length message",ERROR); + ret = NIL; + break; + } + f = mail_parse_flags (stream,flags,&i); + /* reverse bits (dontcha wish we had CIRC?) */ + for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i))); + if (date) { /* parse date if given */ + if (!mail_parse_date (&elt,date)) { + sprintf (tmp,"Bad date in append: %.80s",date); + MM_LOG (tmp,ERROR); + ret = NIL; /* mark failure */ + break; + } + mail_date (tmp,&elt); /* write preseved date */ + } + else internal_date (tmp); /* get current date in IMAP format */ + i = GETPOS (message); /* remember current position */ + for (j = SIZE (message), size = 0; j; --j) + if (SNX (message) != '\015') ++size; + SETPOS (message,i); /* restore position */ + /* write header */ + if (fprintf (df,"%s,%lu;%010lo%02lo\n",tmp,size,uf,(unsigned long) f) < 0) + ret = NIL; + else { /* write message */ + while (size) if ((c = 0xff & SNX (message)) != '\015') { + if (putc (c,df) != EOF) --size; + else break; + } + /* get next message */ + if (size || !MM_APPEND (af) (stream,data,&flags,&date,&message)) + ret = NIL; + } + } while (ret && message); + /* if error... */ + if (!ret || (fflush (df) == EOF)) { + ftruncate (fd,sbuf.st_size);/* revert file */ + close (fd); /* make sure fclose() doesn't corrupt us */ + if (errno) { + sprintf (tmp,"Message append failed: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + } + ret = NIL; + } + if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */ + /* else preserve \Marked status */ + else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0); + tp[1] = sbuf.st_mtime; /* preserve mtime */ + utime (file,tp); /* set the times */ + fclose (df); /* close the file */ + unlockfd (ld,lock); /* release exclusive parse/append permission */ + MM_NOCRITICAL (stream); /* release critical */ + if (ret && mail_parameters (NIL,GET_APPENDUID,NIL)) + MM_LOG ("Can not return meaningful APPENDUID with this mailbox format", + WARN); + return ret; +} + +/* Internal routines */ + + +/* Tenex mail return internal message size in bytes + * Accepts: MAIL stream + * message # + * Returns: internal size of message + */ + +unsigned long tenex_size (MAILSTREAM *stream,unsigned long m) +{ + MESSAGECACHE *elt = mail_elt (stream,m); + return ((m < stream->nmsgs) ? mail_elt (stream,m+1)->private.special.offset : + LOCAL->filesize) - + (elt->private.special.offset + elt->private.special.text.size); +} + + +/* Tenex mail generate file string + * Accepts: temporary buffer to write into + * mailbox name string + * Returns: local file string or NIL if failure + */ + +char *tenex_file (char *dst,char *name) +{ + char tmp[MAILTMPLEN]; + char *s = mailboxfile (dst,name); + /* return our standard inbox */ + return (s && !*s) ? mailboxfile (dst,tenex_isvalid ("~/INBOX",tmp) ? + "~/INBOX" : "mail.txt") : s; +} + +/* Tenex mail parse mailbox + * Accepts: MAIL stream + * Returns: T if parse OK + * NIL if failure, stream aborted + */ + +long tenex_parse (MAILSTREAM *stream) +{ + struct stat sbuf; + MESSAGECACHE *elt = NIL; + unsigned char c,*s,*t,*x; + char tmp[MAILTMPLEN]; + unsigned long i,j; + long curpos = LOCAL->filesize; + long nmsgs = stream->nmsgs; + long recent = stream->recent; + short added = NIL; + short silent = stream->silent; + fstat (LOCAL->fd,&sbuf); /* get status */ + if (sbuf.st_size < curpos) { /* sanity check */ + sprintf (tmp,"Mailbox shrank from %lu to %lu!", + (unsigned long) curpos,(unsigned long) sbuf.st_size); + MM_LOG (tmp,ERROR); + tenex_close (stream,NIL); + return NIL; + } + stream->silent = T; /* don't pass up exists events yet */ + while (sbuf.st_size - curpos){/* while there is stuff to parse */ + /* get to that position in the file */ + lseek (LOCAL->fd,curpos,L_SET); + if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) { + sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s", + (unsigned long) curpos,(unsigned long) sbuf.st_size, + i ? strerror (errno) : "no data read"); + MM_LOG (tmp,ERROR); + tenex_close (stream,NIL); + return NIL; + } + LOCAL->buf[i] = '\0'; /* tie off buffer just in case */ + if (!(s = strchr (LOCAL->buf,'\012'))) { + sprintf (tmp,"Unable to find newline at %lu in %lu bytes, text: %s", + (unsigned long) curpos,i,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + tenex_close (stream,NIL); + return NIL; + } + *s = '\0'; /* tie off header line */ + i = (s + 1) - LOCAL->buf; /* note start of text offset */ + if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) { + sprintf (tmp,"Unable to parse internal header at %lu: %s", + (unsigned long) curpos,(char *) LOCAL->buf); + MM_LOG (tmp,ERROR); + tenex_close (stream,NIL); + return NIL; + } + *s++ = '\0'; *t++ = '\0'; /* tie off fields */ + + added = T; /* note that a new message was added */ + /* swell the cache */ + mail_exists (stream,++nmsgs); + /* instantiate an elt for this message */ + (elt = mail_elt (stream,nmsgs))->valid = T; + elt->private.uid = ++stream->uid_last; + /* note file offset of header */ + elt->private.special.offset = curpos; + /* in case error */ + elt->private.special.text.size = 0; + /* header size not known yet */ + elt->private.msg.header.text.size = 0; + x = s; /* parse the header components */ + if (mail_parse_date (elt,LOCAL->buf) && + (elt->private.msg.full.text.size = strtoul (s,(char **) &s,10)) && + (!(s && *s)) && isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) && + isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) && + isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) && + isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12]) + elt->private.special.text.size = i; + else { /* oops */ + sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s", + curpos,(char *) LOCAL->buf,(char *) x,(char *) t); + MM_LOG (tmp,ERROR); + tenex_close (stream,NIL); + return NIL; + } + /* make sure didn't run off end of file */ + if ((curpos += (elt->private.msg.full.text.size + i)) > sbuf.st_size) { + sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)", + elt->private.special.offset,(unsigned long) curpos, + (unsigned long) sbuf.st_size); + MM_LOG (tmp,ERROR); + tenex_close (stream,NIL); + return NIL; + } + c = t[10]; /* remember first system flags byte */ + t[10] = '\0'; /* tie off flags */ + j = strtoul (t,NIL,8); /* get user flags value */ + t[10] = c; /* restore first system flags byte */ + /* set up all valid user flags (reversed!) */ + while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) && + stream->user_flags[i]) elt->user_flags |= 1 << i; + /* calculate system flags */ + if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T; + if (j & fDELETED) elt->deleted = T; + if (j & fFLAGGED) elt->flagged = T; + if (j & fANSWERED) elt->answered = T; + if (j & fDRAFT) elt->draft = T; + if (!(j & fOLD)) { /* newly arrived message? */ + elt->recent = T; + recent++; /* count up a new recent message */ + /* mark it as old */ + tenex_update_status (stream,nmsgs,NIL); + } + } + fsync (LOCAL->fd); /* make sure all the fOLD flags take */ + /* update parsed file size and time */ + LOCAL->filesize = sbuf.st_size; + fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */ + LOCAL->filetime = sbuf.st_mtime; + if (added && !stream->rdonly){/* make sure atime updated */ + time_t tp[2]; + tp[0] = time (0); + tp[1] = LOCAL->filetime; + utime (stream->mailbox,tp); + } + stream->silent = silent; /* can pass up events now */ + mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */ + mail_recent (stream,recent); /* and of change in recent messages */ + return LONGT; /* return the winnage */ +} + +/* Tenex get cache element with status updating from file + * Accepts: MAIL stream + * message number + * Returns: cache element + */ + +MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno) +{ + MESSAGECACHE *elt = mail_elt (stream,msgno); + struct { /* old flags */ + unsigned int seen : 1; + unsigned int deleted : 1; + unsigned int flagged : 1; + unsigned int answered : 1; + unsigned int draft : 1; + unsigned long user_flags; + } old; + old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged; + old.answered = elt->answered; old.draft = elt->draft; + old.user_flags = elt->user_flags; + tenex_read_flags (stream,elt); + if ((old.seen != elt->seen) || (old.deleted != elt->deleted) || + (old.flagged != elt->flagged) || (old.answered != elt->answered) || + (old.draft != elt->draft) || (old.user_flags != elt->user_flags)) + MM_FLAGS (stream,msgno); /* let top level know */ + return elt; +} + +/* Tenex read flags from file + * Accepts: MAIL stream + * Returns: cache element + */ + +void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + unsigned long i,j; + /* noop if readonly and have valid flags */ + if (stream->rdonly && elt->valid) return; + /* set the seek pointer */ + lseek (LOCAL->fd,(off_t) elt->private.special.offset + + elt->private.special.text.size - 13,L_SET); + /* read the new flags */ + if (read (LOCAL->fd,LOCAL->buf,12) < 0) { + sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno)); + fatal (LOCAL->buf); + } + /* calculate system flags */ + i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0'); + elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL; + elt->flagged = i & fFLAGGED ? T : NIL; + elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL; + LOCAL->buf[10] = '\0'; /* tie off flags */ + j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */ + /* set up all valid user flags (reversed!) */ + while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) && + stream->user_flags[i]) elt->user_flags |= 1 << i; + elt->valid = T; /* have valid flags now */ +} + +/* Tenex update status string + * Accepts: MAIL stream + * message number + * flag saying whether or not to sync + */ + +void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag) +{ + time_t tp[2]; + struct stat sbuf; + MESSAGECACHE *elt = mail_elt (stream,msgno); + unsigned long j,k = 0; + /* readonly */ + if (stream->rdonly || !elt->valid) tenex_read_flags (stream,elt); + else { /* readwrite */ + j = elt->user_flags; /* get user flags */ + /* reverse bits (dontcha wish we had CIRC?) */ + while (j) k |= 1 << (29 - find_rightmost_bit (&j)); + /* print new flag string */ + sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned) + (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) + + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + + (fDRAFT * elt->draft))); + /* get to that place in the file */ + lseek (LOCAL->fd,(off_t) elt->private.special.offset + + elt->private.special.text.size - 13,L_SET); + /* write new flags */ + write (LOCAL->fd,LOCAL->buf,12); + if (syncflag) { /* sync if requested */ + fsync (LOCAL->fd); + fstat (LOCAL->fd,&sbuf); /* get new write time */ + tp[1] = LOCAL->filetime = sbuf.st_mtime; + tp[0] = time (0); /* make sure read is later */ + utime (stream->mailbox,tp); + } + } +} + +/* Tenex locate header for a message + * Accepts: MAIL stream + * message number + * pointer to returned header size + * Returns: position of header in file + */ + +unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno, + unsigned long *size) +{ + unsigned long siz; + long i = 0; + char c = '\0'; + char *s = NIL; + MESSAGECACHE *elt = tenex_elt (stream,msgno); + unsigned long ret = elt->private.special.offset + + elt->private.special.text.size; + unsigned long msiz = tenex_size (stream,msgno); + /* is header size known? */ + if (!(*size = elt->private.msg.header.text.size)) { + lseek (LOCAL->fd,ret,L_SET);/* get to header position */ + /* search message for LF LF */ + for (siz = 0; siz < msiz; siz++) { + if (--i <= 0) /* read another buffer as necessary */ + read (LOCAL->fd,s = LOCAL->buf,i = min (msiz-siz,(long) MAILTMPLEN)); + /* two newline sequence? */ + if ((c == '\012') && (*s == '\012')) { + /* yes, note for later */ + elt->private.msg.header.text.size = (*size = siz + 1); + + return ret; /* return to caller */ + } + else c = *s++; /* next character */ + } + /* header consumes entire message */ + elt->private.msg.header.text.size = *size = msiz; + } + return ret; +} diff --git a/imap/src/osdep/amiga/tz_bsd.c b/imap/src/osdep/amiga/tz_bsd.c new file mode 100644 index 00000000..75ee624c --- /dev/null +++ b/imap/src/osdep/amiga/tz_bsd.c @@ -0,0 +1,38 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: BSD-style Time Zone String + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 30 August 1994 + * Last Edited: 30 August 2006 + */ + + +/* Append local timezone name + * Accepts: destination string + */ + +void rfc822_timezone (char *s,void *t) +{ + /* append timezone from tm struct */ + sprintf (s + strlen (s)," (%.50s)",((struct tm *) t)->tm_zone); +} diff --git a/imap/src/osdep/amiga/unix.c b/imap/src/osdep/amiga/unix.c new file mode 100644 index 00000000..be3c437b --- /dev/null +++ b/imap/src/osdep/amiga/unix.c @@ -0,0 +1,2708 @@ +/* ======================================================================== + * Copyright 1988-2008 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UNIX mail routines + * + * Author: Mark Crispin + * UW Technology + * University of Washington + * Seattle, WA 98195 + * Internet: MRC@Washington.EDU + * + * Date: 20 December 1989 + * Last Edited: 27 March 2008 + */ + + +/* DEDICATION + * + * This file is dedicated to my dog, Unix, also known as Yun-chan and + * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix + * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after + * a two-month bout with cirrhosis of the liver. + * + * He was a dear friend, and I miss him terribly. + * + * Lift a leg, Yunie. Luv ya forever!!!! + */ + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include <signal.h> +#include "mail.h" +#include "osdep.h" +#include <time.h> +#include <sys/stat.h> +#include "unix.h" +#include "pseudo.h" +#include "fdstring.h" +#include "misc.h" +#include "dummy.h" + +/* UNIX I/O stream local data */ + +typedef struct unix_local { + unsigned int dirty : 1; /* disk copy needs updating */ + unsigned int ddirty : 1; /* double-dirty, ping becomes checkpoint */ + unsigned int pseudo : 1; /* uses a pseudo message */ + unsigned int appending : 1; /* don't mark new messages as old */ + int fd; /* mailbox file descriptor */ + int ld; /* lock file descriptor */ + char *lname; /* lock file name */ + off_t filesize; /* file size parsed */ + time_t filetime; /* last file time */ + time_t lastsnarf; /* last snarf time (for mbox driver) */ + unsigned char *buf; /* temporary buffer */ + unsigned long buflen; /* current size of temporary buffer */ + unsigned long uid; /* current text uid */ + SIZEDTEXT text; /* current text */ + unsigned long textlen; /* current text length */ + char *line; /* returned line */ + char *linebuf; /* line readin buffer */ + unsigned long linebuflen; /* current line readin buffer length */ +} UNIXLOCAL; + + +/* Convenient access to local data */ + +#define LOCAL ((UNIXLOCAL *) stream->local) + + +/* UNIX protected file structure */ + +typedef struct unix_file { + MAILSTREAM *stream; /* current stream */ + off_t curpos; /* current file position */ + off_t protect; /* protected position */ + off_t filepos; /* current last written file position */ + char *buf; /* overflow buffer */ + size_t buflen; /* current overflow buffer length */ + char *bufpos; /* current buffer position */ +} UNIXFILE; + +/* Function prototypes */ + +DRIVER *unix_valid (char *name); +long unix_isvalid_fd (int fd); +void *unix_parameters (long function,void *value); +void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); +void unix_list (MAILSTREAM *stream,char *ref,char *pat); +void unix_lsub (MAILSTREAM *stream,char *ref,char *pat); +long unix_create (MAILSTREAM *stream,char *mailbox); +long unix_delete (MAILSTREAM *stream,char *mailbox); +long unix_rename (MAILSTREAM *stream,char *old,char *newname); +MAILSTREAM *unix_open (MAILSTREAM *stream); +void unix_close (MAILSTREAM *stream,long options); +char *unix_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags); +long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, + unsigned long *length,long flags); +void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); +long unix_ping (MAILSTREAM *stream); +void unix_check (MAILSTREAM *stream); +long unix_expunge (MAILSTREAM *stream,char *sequence,long options); +long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); +int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date, + STRING *msg); +int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set); + +void unix_abort (MAILSTREAM *stream); +char *unix_file (char *dst,char *name); +int unix_lock (char *file,int flags,int mode,DOTLOCK *lock,int op); +void unix_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock); +int unix_parse (MAILSTREAM *stream,DOTLOCK *lock,int op); +char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size); +unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr); +unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt, + unsigned long uid,long flag); +long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock, + long flags); +long unix_extend (MAILSTREAM *stream,unsigned long size); +void unix_write (UNIXFILE *f,char *s,unsigned long i); +void unix_phys_write (UNIXFILE *f,char *buf,size_t size); + +/* mbox mail routines */ + +/* Function prototypes */ + +DRIVER *mbox_valid (char *name); +long mbox_create (MAILSTREAM *stream,char *mailbox); +long mbox_delete (MAILSTREAM *stream,char *mailbox); +long mbox_rename (MAILSTREAM *stream,char *old,char *newname); +long mbox_status (MAILSTREAM *stream,char *mbx,long flags); +MAILSTREAM *mbox_open (MAILSTREAM *stream); +long mbox_ping (MAILSTREAM *stream); +void mbox_check (MAILSTREAM *stream); +long mbox_expunge (MAILSTREAM *stream,char *sequence,long options); +long mbox_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); + + +/* UNIX mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER unixdriver = { + "unix", /* driver name */ + /* driver flags */ + DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY|DR_XPOINT, + (DRIVER *) NIL, /* next driver */ + unix_valid, /* mailbox is valid for us */ + unix_parameters, /* manipulate parameters */ + unix_scan, /* scan mailboxes */ + unix_list, /* list mailboxes */ + unix_lsub, /* list subscribed mailboxes */ + NIL, /* subscribe to mailbox */ + NIL, /* unsubscribe from mailbox */ + unix_create, /* create mailbox */ + unix_delete, /* delete mailbox */ + unix_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + unix_open, /* open mailbox */ + unix_close, /* close mailbox */ + NIL, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message envelopes */ + unix_header, /* fetch message header */ + unix_text, /* fetch message text */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + NIL, /* modify flags */ + unix_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + unix_ping, /* ping mailbox to see if still alive */ + unix_check, /* check for new messages */ + unix_expunge, /* expunge deleted messages */ + unix_copy, /* copy messages to another mailbox */ + unix_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM unixproto = {&unixdriver}; + + /* driver parameters */ +static long unix_fromwidget = T; + +/* UNIX mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *unix_valid (char *name) +{ + int fd; + DRIVER *ret = NIL; + char *t,file[MAILTMPLEN]; + struct stat sbuf; + time_t tp[2]; + errno = EINVAL; /* assume invalid argument */ + /* must be non-empty file */ + if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) { + if (!sbuf.st_size)errno = 0;/* empty file */ + else if ((fd = open (file,O_RDONLY,NIL)) >= 0) { + /* OK if mailbox format good */ + if (unix_isvalid_fd (fd)) ret = &unixdriver; + else errno = -1; /* invalid format */ + close (fd); /* close the file */ + /* \Marked status? */ + if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) { + tp[0] = sbuf.st_atime; /* yes, preserve atime and mtime */ + tp[1] = sbuf.st_mtime; + utime (file,tp); /* set the times */ + } + } + } + return ret; /* return what we should */ +} + +/* UNIX mail test for valid mailbox + * Accepts: file descriptor + * scratch buffer + * Returns: T if valid, NIL otherwise + */ + +long unix_isvalid_fd (int fd) +{ + int zn; + int ret = NIL; + char tmp[MAILTMPLEN],*s,*t,c = '\n'; + memset (tmp,'\0',MAILTMPLEN); + if (read (fd,tmp,MAILTMPLEN-1) >= 0) { + for (s = tmp; (*s == '\r') || (*s == '\n') || (*s == ' ') || (*s == '\t');) + c = *s++; + if (c == '\n') VALID (s,t,ret,zn); + } + return ret; /* return what we should */ +} + + +/* UNIX manipulate driver parameters + * Accepts: function code + * function-dependent value + * Returns: function-dependent return value + */ + +void *unix_parameters (long function,void *value) +{ + void *ret = NIL; + switch ((int) function) { + case GET_INBOXPATH: + if (value) ret = dummy_file ((char *) value,"INBOX"); + break; + case SET_FROMWIDGET: + unix_fromwidget = (long) value; + case GET_FROMWIDGET: + ret = (void *) unix_fromwidget; + break; + } + return ret; +} + +/* UNIX mail scan mailboxes + * Accepts: mail stream + * reference + * pattern to search + * string to scan + */ + +void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) +{ + if (stream) dummy_scan (NIL,ref,pat,contents); +} + + +/* UNIX mail list mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void unix_list (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_list (NIL,ref,pat); +} + + +/* UNIX mail list subscribed mailboxes + * Accepts: mail stream + * reference + * pattern to search + */ + +void unix_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + if (stream) dummy_lsub (NIL,ref,pat); +} + +/* UNIX mail create mailbox + * Accepts: MAIL stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long unix_create (MAILSTREAM *stream,char *mailbox) +{ + char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN]; + long ret = NIL; + int i,fd; + time_t ti = time (0); + if (!(s = dummy_file (mbx,mailbox))) { + sprintf (tmp,"Can't create %.80s: invalid name",mailbox); + MM_LOG (tmp,ERROR); + } + /* create underlying file */ + else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) { + /* done if dir-only or whiner */ + if (((s = strrchr (s,'/')) && !s[1]) || + mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T; + else if ((fd = open (mbx,O_WRONLY, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) { + sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno)); + MM_LOG (tmp,ERROR); + unlink (mbx); /* delete the file */ + } + else { /* initialize header */ + memset (tmp,'\0',MAILTMPLEN); + sprintf (tmp,"From %s %sDate: ",pseudo_from,ctime (&ti)); + rfc822_fixed_date (s = tmp + strlen (tmp)); + /* write the pseudo-header */ + sprintf (s += strlen (s), + "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000", + pseudo_name,pseudo_from,mylocalhost (),pseudo_subject, + (unsigned long) ti); + for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i)) + sprintf (s += strlen (s)," %s",default_user_flag (i)); + sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n\n",pseudo_msg); + if (write (fd,tmp,strlen (tmp)) > 0) ret = T; + else { + sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx, + strerror (errno)); + MM_LOG (tmp,ERROR); + unlink (mbx); /* delete the file */ + } + close (fd); /* close file */ + } + } + /* set proper protections */ + return ret ? set_mbx_protections (mailbox,mbx) : NIL; +} + +/* UNIX mail delete mailbox + * Accepts: MAIL stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long unix_delete (MAILSTREAM *stream,char *mailbox) +{ + return unix_rename (stream,mailbox,NIL); +} + + +/* UNIX mail rename mailbox + * Accepts: MAIL stream + * old mailbox name + * new mailbox name (or NIL for delete) + * Returns: T on success, NIL on failure + */ + +long unix_rename (MAILSTREAM *stream,char *old,char *newname) +{ + long ret = NIL; + char c,*s = NIL; + char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN]; + DOTLOCK lockx; + int fd,ld; + long i; + struct stat sbuf; + MM_CRITICAL (stream); /* get the c-client lock */ + if (!dummy_file (file,old) || + (newname && (!((s = mailboxfile (tmp,newname)) && *s) || + ((s = strrchr (tmp,'/')) && !s[1])))) + sprintf (tmp,newname ? + "Can't rename mailbox %.80s to %.80s: invalid name" : + "Can't delete mailbox %.80s: invalid name", + old,newname); + /* lock out other c-clients */ + else if ((ld = lockname (lock,file,LOCK_EX|LOCK_NB,&i)) < 0) + sprintf (tmp,"Mailbox %.80s is in use by another process",old); + + else { + if ((fd = unix_lock (file,O_RDWR, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL), + &lockx,LOCK_EX)) < 0) + sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno)); + else { + if (newname) { /* want rename? */ + /* found superior to destination name? */ + if (s = strrchr (s,'/')) { + c = *++s; /* remember first character of inferior */ + *s = '\0'; /* tie off to get just superior */ + /* name doesn't exist, create it */ + if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !dummy_create_path (stream,tmp,get_dir_protection (newname))) { + unix_unlock (fd,NIL,&lockx); + unix_unlock (ld,NIL,NIL); + unlink (lock); + MM_NOCRITICAL (stream); + return ret; /* return success or failure */ + } + *s = c; /* restore full name */ + } + if (rename (file,tmp)) + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname, + strerror (errno)); + else ret = T; /* set success */ + } + else if (unlink (file)) + sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno)); + else ret = T; /* set success */ + unix_unlock (fd,NIL,&lockx); + } + unix_unlock (ld,NIL,NIL); /* flush the lock */ + unlink (lock); + } + MM_NOCRITICAL (stream); /* no longer critical */ + if (!ret) MM_LOG (tmp,ERROR); /* log error */ + return ret; /* return success or failure */ +} + +/* UNIX mail open + * Accepts: Stream to open + * Returns: Stream on success, NIL on failure + */ + +MAILSTREAM *unix_open (MAILSTREAM *stream) +{ + long i; + int fd; + char tmp[MAILTMPLEN]; + DOTLOCK lock; + long retry; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return user_flags (&unixproto); + retry = stream->silent ? 1 : KODRETRY; + if (stream->local) fatal ("unix recycle stream"); + stream->local = memset (fs_get (sizeof (UNIXLOCAL)),0,sizeof (UNIXLOCAL)); + /* note if an INBOX or not */ + stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); + /* canonicalize the stream mailbox name */ + if (!dummy_file (tmp,stream->mailbox)) { + sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + /* flush old name */ + fs_give ((void **) &stream->mailbox); + /* save canonical name */ + stream->mailbox = cpystr (tmp); + LOCAL->fd = LOCAL->ld = -1; /* no file or state locking yet */ + LOCAL->buf = (char *) fs_get (CHUNKSIZE); + LOCAL->buflen = CHUNKSIZE - 1; + LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE); + LOCAL->text.size = CHUNKSIZE - 1; + LOCAL->linebuf = (char *) fs_get (CHUNKSIZE); + LOCAL->linebuflen = CHUNKSIZE - 1; + stream->sequence++; /* bump sequence number */ + + /* make lock for read/write access */ + if (!stream->rdonly) while (retry) { + /* try to lock file */ + if ((fd = lockname (tmp,stream->mailbox,LOCK_EX|LOCK_NB,&i)) < 0) { + /* suppressing kiss-of-death? */ + if (stream->nokod) retry = 0; + /* no, first time through? */ + else if (retry-- == KODRETRY) { + /* learned other guy's PID and can signal? */ + if (i && !kill ((int) i,SIGUSR2)) { + sprintf (tmp,"Trying to get mailbox lock from process %ld",i); + MM_LOG (tmp,WARN); + } + else retry = 0; /* give up */ + } + if (!stream->silent) { /* nothing if silent stream */ + if (retry) sleep (1); /* wait a second before trying again */ + else MM_LOG ("Mailbox is open by another process, access is readonly", + WARN); + } + } + else { /* got the lock, nobody else can alter state */ + LOCAL->ld = fd; /* note lock's fd and name */ + LOCAL->lname = cpystr (tmp); + /* make sure mode OK (don't use fchmod()) */ + chmod (LOCAL->lname,(long) mail_parameters (NIL,GET_LOCKPROTECTION,NIL)); + if (stream->silent) i = 0;/* silent streams won't accept KOD */ + else { /* note our PID in the lock */ + sprintf (tmp,"%d",getpid ()); + write (fd,tmp,(i = strlen (tmp))+1); + } + ftruncate (fd,i); /* make sure tied off */ + fsync (fd); /* make sure it's available */ + retry = 0; /* no more need to try */ + } + } + + /* parse mailbox */ + stream->nmsgs = stream->recent = 0; + /* will we be able to get write access? */ + if ((LOCAL->ld >= 0) && access (stream->mailbox,W_OK) && (errno == EACCES)) { + MM_LOG ("Can't get write access to mailbox, access is readonly",WARN); + flock (LOCAL->ld,LOCK_UN); /* release the lock */ + close (LOCAL->ld); /* close the lock file */ + LOCAL->ld = -1; /* no more lock fd */ + unlink (LOCAL->lname); /* delete it */ + } + /* reset UID validity */ + stream->uid_validity = stream->uid_last = 0; + if (stream->silent && !stream->rdonly && (LOCAL->ld < 0)) + unix_abort (stream); /* abort if can't get RW silent stream */ + /* parse mailbox */ + else if (unix_parse (stream,&lock,LOCK_SH)) { + unix_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); + MM_NOCRITICAL (stream); /* done with critical */ + } + if (!LOCAL) return NIL; /* failure if stream died */ + /* make sure upper level knows readonly */ + stream->rdonly = (LOCAL->ld < 0); + /* notify about empty mailbox */ + if (!(stream->nmsgs || stream->silent)) MM_LOG ("Mailbox is empty",NIL); + if (!stream->rdonly) { /* flags stick if readwrite */ + stream->perm_seen = stream->perm_deleted = + stream->perm_flagged = stream->perm_answered = stream->perm_draft = T; + if (!stream->uid_nosticky) {/* users with lives get permanent keywords */ + stream->perm_user_flags = 0xffffffff; + /* and maybe can create them too! */ + stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T; + } + } + return stream; /* return stream alive to caller */ +} + + +/* UNIX mail close + * Accepts: MAIL stream + * close options + */ + +void unix_close (MAILSTREAM *stream,long options) +{ + int silent = stream->silent; + stream->silent = T; /* go silent */ + /* expunge if requested */ + if (options & CL_EXPUNGE) unix_expunge (stream,NIL,NIL); + /* else dump final checkpoint */ + else if (LOCAL->dirty) unix_check (stream); + stream->silent = silent; /* restore old silence state */ + unix_abort (stream); /* now punt the file and local data */ +} + +/* UNIX mail fetch message header + * Accepts: MAIL stream + * message # to fetch + * pointer to returned header text length + * option flags + * Returns: message header in RFC822 format + */ + + /* lines to filter from header */ +static STRINGLIST *unix_hlines = NIL; + +char *unix_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length,long flags) +{ + MESSAGECACHE *elt; + unsigned char *s,*t,*tl; + *length = 0; /* default to empty */ + if (flags & FT_UID) return "";/* UID call "impossible" */ + elt = mail_elt (stream,msgno);/* get cache */ + if (!unix_hlines) { /* once only code */ + STRINGLIST *lines = unix_hlines = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "Status")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-Status")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-Keywords")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-UID")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-IMAP")); + lines = lines->next = mail_newstringlist (); + lines->text.size = strlen ((char *) (lines->text.data = + (unsigned char *) "X-IMAPbase")); + } + /* go to header position */ + lseek (LOCAL->fd,elt->private.special.offset + + elt->private.msg.header.offset,L_SET); + + if (flags & FT_INTERNAL) { /* initial data OK? */ + if (elt->private.msg.header.text.size > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = + elt->private.msg.header.text.size) + 1); + } + /* read message */ + read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size); + /* got text, tie off string */ + LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0'; + /* squeeze out CRs (in case from PC) */ + for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++) + if (*t != '\r') *s++ = *t; + *s = '\0'; + *length = s - LOCAL->buf; /* adjust length */ + } + else { /* need to make a CRLF version */ + read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1), + elt->private.msg.header.text.size); + /* tie off string, and convert to CRLF */ + s[elt->private.msg.header.text.size] = '\0'; + *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s, + elt->private.msg.header.text.size); + fs_give ((void **) &s); /* free readin buffer */ + /* squeeze out spurious CRs */ + for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++) + if ((*t != '\r') || (t[1] == '\n')) *s++ = *t; + *s = '\0'; + *length = s - LOCAL->buf; /* adjust length */ + } + *length = mail_filter (LOCAL->buf,*length,unix_hlines,FT_NOT); + return (char *) LOCAL->buf; /* return processed copy */ +} + +/* UNIX mail fetch message text + * Accepts: MAIL stream + * message # to fetch + * pointer to returned stringstruct + * option flags + * Returns: T on success, NIL if failure + */ + +long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) +{ + char *s; + unsigned long i; + MESSAGECACHE *elt; + /* UID call "impossible" */ + if (flags & FT_UID) return NIL; + elt = mail_elt (stream,msgno);/* get cache element */ + /* if message not seen */ + if (!(flags & FT_PEEK) && !elt->seen) { + /* mark message seen and dirty */ + elt->seen = elt->private.dirty = LOCAL->dirty = T; + MM_FLAGS (stream,msgno); + } + s = unix_text_work (stream,elt,&i,flags); + INIT (bs,mail_string,s,i); /* set up stringstruct */ + return T; /* success */ +} + +/* UNIX mail fetch message text worker routine + * Accepts: MAIL stream + * message cache element + * pointer to returned header text length + * option flags + */ + +char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, + unsigned long *length,long flags) +{ + FDDATA d; + STRING bs; + unsigned char c,*s,*t,*tl,tmp[CHUNKSIZE]; + /* go to text position */ + lseek (LOCAL->fd,elt->private.special.offset + + elt->private.msg.text.offset,L_SET); + if (flags & FT_INTERNAL) { /* initial data OK? */ + if (elt->private.msg.text.text.size > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = + elt->private.msg.text.text.size) + 1); + } + /* read message */ + read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size); + /* got text, tie off string */ + LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0'; + /* squeeze out CRs (in case from PC) */ + for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++) + if (*t != '\r') *s++ = *t; + *s = '\0'; + *length = s - LOCAL->buf; /* adjust length */ + return (char *) LOCAL->buf; + } + + /* have it cached already? */ + if (elt->private.uid != LOCAL->uid) { + /* not cached, cache it now */ + LOCAL->uid = elt->private.uid; + /* is buffer big enough? */ + if (elt->rfc822_size > LOCAL->text.size) { + /* excessively conservative, but the right thing is too hard to do */ + fs_give ((void **) &LOCAL->text.data); + LOCAL->text.data = (unsigned char *) + fs_get ((LOCAL->text.size = elt->rfc822_size) + 1); + } + d.fd = LOCAL->fd; /* yes, set up file descriptor */ + d.pos = elt->private.special.offset + elt->private.msg.text.offset; + d.chunk = tmp; /* initial buffer chunk */ + d.chunksize = CHUNKSIZE; /* file chunk size */ + INIT (&bs,fd_string,&d,elt->private.msg.text.text.size); + for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) { + case '\r': /* carriage return seen */ + break; + case '\n': + *s++ = '\r'; /* insert a CR */ + default: + *s++ = c; /* copy characters */ + } + *s = '\0'; /* tie off buffer */ + /* calculate length of cached data */ + LOCAL->textlen = s - LOCAL->text.data; + } + *length = LOCAL->textlen; /* return from cache */ + return (char *) LOCAL->text.data; +} + +/* UNIX per-message modify flag + * Accepts: MAIL stream + * message cache element + */ + +void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + /* only after finishing */ + if (elt->valid) elt->private.dirty = LOCAL->dirty = T; +} + + +/* UNIX mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + */ + +long unix_ping (MAILSTREAM *stream) +{ + DOTLOCK lock; + struct stat sbuf; + long reparse; + /* big no-op if not readwrite */ + if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) { + if (stream->rdonly) { /* does he want to give up readwrite? */ + /* checkpoint if we changed something */ + if (LOCAL->dirty) unix_check (stream); + flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */ + close (LOCAL->ld); /* close the readwrite lock file */ + LOCAL->ld = -1; /* no more readwrite lock fd */ + unlink (LOCAL->lname); /* delete the readwrite lock file */ + } + else { /* see if need to reparse */ + if (!(reparse = (long) mail_parameters (NIL,GET_NETFSSTATBUG,NIL))) { + /* get current mailbox size */ + if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf); + else if (stat (stream->mailbox,&sbuf)) { + sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s", + strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + unix_abort (stream); + return NIL; + } + reparse = (sbuf.st_size != LOCAL->filesize); + } + /* parse if mailbox changed */ + if ((LOCAL->ddirty || reparse) && unix_parse (stream,&lock,LOCK_EX)) { + /* force checkpoint if double-dirty */ + if (LOCAL->ddirty) unix_rewrite (stream,NIL,&lock,NIL); + /* unlock mailbox */ + else unix_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); /* and stream */ + MM_NOCRITICAL (stream); /* done with critical */ + } + } + } + return LOCAL ? LONGT : NIL; /* return if still alive */ +} + +/* UNIX mail check mailbox + * Accepts: MAIL stream + */ + +void unix_check (MAILSTREAM *stream) +{ + DOTLOCK lock; + /* parse and lock mailbox */ + if (LOCAL && (LOCAL->ld >= 0) && !stream->lock && + unix_parse (stream,&lock,LOCK_EX)) { + /* any unsaved changes? */ + if (LOCAL->dirty && unix_rewrite (stream,NIL,&lock,NIL)) { + if (!stream->silent) MM_LOG ("Checkpoint completed",NIL); + } + /* no checkpoint needed, just unlock */ + else unix_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); /* unlock the stream */ + MM_NOCRITICAL (stream); /* done with critical */ + } +} + + +/* UNIX mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T, always + */ + +long unix_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret; + unsigned long i; + DOTLOCK lock; + char *msg = NIL; + /* parse and lock mailbox */ + if (ret = (sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : LONGT) && + LOCAL && (LOCAL->ld >= 0) && !stream->lock && + unix_parse (stream,&lock,LOCK_EX)) { + /* check expunged messages if not dirty */ + for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) { + MESSAGECACHE *elt = mail_elt (stream,i); + if (mail_elt (stream,i)->deleted) LOCAL->dirty = T; + } + if (!LOCAL->dirty) { /* not dirty and no expunged messages */ + unix_unlock (LOCAL->fd,stream,&lock); + msg = "No messages deleted, so no update needed"; + } + else if (unix_rewrite (stream,&i,&lock,sequence ? LONGT : NIL)) { + if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i); + else msg = "Mailbox checkpointed, but no messages expunged"; + } + /* rewrite failed */ + else unix_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); /* unlock the stream */ + MM_NOCRITICAL (stream); /* done with critical */ + if (msg && !stream->silent) MM_LOG (msg,NIL); + } + else if (!stream->silent) MM_LOG("Expunge ignored on readonly mailbox",WARN); + return ret; +} + +/* UNIX mail copy message(s) + * Accepts: MAIL stream + * sequence + * destination mailbox + * copy options + * Returns: T if copy successful, else NIL + */ + +long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + struct stat sbuf; + int fd; + char *s,file[MAILTMPLEN]; + DOTLOCK lock; + time_t tp[2]; + unsigned long i,j; + MESSAGECACHE *elt; + long ret = T; + mailproxycopy_t pc = + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); + copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? + NIL : mail_parameters (NIL,GET_COPYUID,NIL)); + SEARCHSET *source = cu ? mail_newsearchset () : NIL; + SEARCHSET *dest = cu ? mail_newsearchset () : NIL; + MAILSTREAM *tstream = NIL; + DRIVER *d; + for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL); + (d && strcmp (d->name,"mbox") && !(d->flags & DR_DISABLE)); + d = d->next); /* see if mbox driver active */ + if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) return NIL; + /* make sure destination is valid */ + if (!((d && mbox_valid (mailbox) && (mailbox = "mbox")) || + unix_valid (mailbox) || !errno)) + switch (errno) { + case ENOENT: /* no such file? */ + if (compare_cstring (mailbox,"INBOX")) { + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); + return NIL; + } + if (pc) return (*pc) (stream,sequence,mailbox,options); + unix_create (NIL,"INBOX");/* create empty INBOX */ + case EACCES: /* file protected */ + sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + case EINVAL: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Invalid UNIX-format mailbox name: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + default: + if (pc) return (*pc) (stream,sequence,mailbox,options); + sprintf (LOCAL->buf,"Not a UNIX-format mailbox: %.80s",mailbox); + MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + + /* try to open rewrite for UIDPLUS */ + if ((tstream = mail_open_work (&unixdriver,NIL,mailbox, + OP_SILENT|OP_NOKOD)) && tstream->rdonly) + tstream = mail_close (tstream); + if (cu && !tstream) { /* wanted a COPYUID? */ + sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s", + mailbox); + MM_LOG (LOCAL->buf,WARN); + cu = NIL; /* don't try to do COPYUID */ + } + LOCAL->buf[0] = '\0'; + MM_CRITICAL (stream); /* go critical */ + if ((fd = unix_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL), + &lock,LOCK_EX)) < 0) { + MM_NOCRITICAL (stream); /* done with critical */ + sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR);/* log the error */ + return NIL; /* failed */ + } + fstat (fd,&sbuf); /* get current file size */ + /* write all requested messages to mailbox */ + for (i = 1; ret && (i <= stream->nmsgs); i++) + if ((elt = mail_elt (stream,i))->sequence) { + lseek (LOCAL->fd,elt->private.special.offset,L_SET); + read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size); + if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL; + else { /* internal header succeeded */ + s = unix_header (stream,i,&j,FT_INTERNAL); + /* header size, sans trailing newline */ + if (j && (s[j - 2] == '\n')) j--; + if (write (fd,s,j) < 0) ret = NIL; + else { /* message header succeeded */ + j = tstream ? /* write UIDPLUS data if have readwrite */ + unix_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) : + unix_xstatus (stream,LOCAL->buf,elt,NIL,NIL); + if (write (fd,LOCAL->buf,j) < 0) ret = NIL; + else { /* message status succeeded */ + s = unix_text_work (stream,elt,&j,FT_INTERNAL); + if ((write (fd,s,j) < 0) || (write (fd,"\n",1) < 0)) ret = NIL; + else if (cu) { /* need to pass back new UID? */ + mail_append_set (source,mail_uid (stream,i)); + mail_append_set (dest,tstream->uid_last); + } + } + } + } + } + + if (!ret || fsync (fd)) { /* force out the update */ + sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno)); + ftruncate (fd,sbuf.st_size); + ret = NIL; + } + /* force UIDVALIDITY assignment now */ + if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0); + /* return sets if doing COPYUID */ + if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest); + else { /* flush any sets we may have built */ + mail_free_searchset (&source); + mail_free_searchset (&dest); + } + tp[1] = time (0); /* set mtime to now */ + if (ret) tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */ + else tp[0] = /* else preserve \Marked status */ + ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ? + sbuf.st_atime : tp[1]; + utime (file,tp); /* set the times */ + unix_unlock (fd,NIL,&lock); /* unlock and close mailbox */ + if (tstream) { /* update last UID if we can */ + UNIXLOCAL *local = (UNIXLOCAL *) tstream->local; + local->dirty = T; /* do a rewrite */ + local->appending = T; /* but not at the cost of marking as old */ + tstream = mail_close (tstream); + } + /* log the error */ + if (!ret) MM_LOG (LOCAL->buf,ERROR); + /* delete if requested message */ + else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence) + elt->deleted = elt->private.dirty = LOCAL->dirty = T; + MM_NOCRITICAL (stream); /* release critical */ + return ret; +} + +/* UNIX mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +#define BUFLEN 8*MAILTMPLEN + +long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + struct stat sbuf; + int fd; + unsigned long i; + char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN]; + time_t tp[2]; + FILE *sf,*df; + MESSAGECACHE elt; + DOTLOCK lock; + STRING *message; + unsigned long uidlocation = 0; + appenduid_t au = (appenduid_t) + (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL : + mail_parameters (NIL,GET_APPENDUID,NIL)); + SEARCHSET *dst = au ? mail_newsearchset () : NIL; + long ret = LONGT; + MAILSTREAM *tstream = NIL; + if (!stream) { /* stream specified? */ + stream = &unixproto; /* no, default stream to prototype */ + for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i) + fs_give ((void **) &stream->user_flags[i]); + } + if (!unix_valid (mailbox)) switch (errno) { + case ENOENT: /* no such file? */ + if (compare_cstring (mailbox,"INBOX")) { + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); + return NIL; + } + unix_create (NIL,"INBOX"); /* create empty INBOX */ + case 0: /* merely empty file? */ + tstream = stream; + break; + case EACCES: /* file protected */ + sprintf (tmp,"Can't access destination: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + case EINVAL: + sprintf (tmp,"Invalid UNIX-format mailbox name: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + default: + sprintf (tmp,"Not a UNIX-format mailbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + /* get sniffing stream for keywords */ + else if (!(tstream = mail_open (NIL,mailbox, + OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) { + sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; + } + + /* get first message */ + if (!MM_APPEND (af) (tstream,data,&flags,&date,&message)) return NIL; + if (!(sf = tmpfile ())) { /* must have scratch file */ + sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ()); + if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) { + sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + unlink (tmp); + } + do { /* parse date */ + if (!date) rfc822_date (date = tmp); + if (!mail_parse_date (&elt,date)) { + sprintf (tmp,"Bad date in append: %.80s",date); + MM_LOG (tmp,ERROR); + } + else { /* user wants to suppress time zones? */ + if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) { + time_t when = mail_longdate (&elt); + date = ctime (&when); /* use traditional date */ + } + /* use POSIX-style date */ + else date = mail_cdate (tmp,&elt); + if (!SIZE (message)) MM_LOG ("Append of zero-length message",ERROR); + else if (!unix_collect_msg (tstream,sf,flags,date,message)) { + sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + } + /* get next message */ + else if (MM_APPEND (af) (tstream,data,&flags,&date,&message)) continue; + } + fclose (sf); /* punt scratch file */ + return NIL; /* give up */ + } while (message); /* until no more messages */ + if (fflush (sf)) { + sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno)); + MM_LOG (tmp,ERROR); + fclose (sf); /* punt scratch file */ + return NIL; /* give up */ + } + i = ftell (sf); /* size of scratch file */ + /* close sniffing stream */ + if (tstream != stream) tstream = mail_close (tstream); + + MM_CRITICAL (stream); /* go critical */ + /* try to open readwrite for UIDPLUS */ + if ((tstream = mail_open_work (&unixdriver,NIL,mailbox, + OP_SILENT|OP_NOKOD)) && tstream->rdonly) + tstream = mail_close (tstream); + if (au && !tstream) { /* wanted an APPENDUID? */ + sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox); + MM_LOG (tmp,WARN); + au = NIL; + } + if (((fd = unix_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL), + &lock,LOCK_EX)) < 0) || + !(df = fdopen (fd,"ab"))) { + MM_NOCRITICAL (stream); /* done with critical */ + sprintf (tmp,"Can't open append mailbox: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + return NIL; + } + fstat (fd,&sbuf); /* get current file size */ + rewind (sf); + tp[1] = time (0); /* set mtime to now */ + /* write all messages */ + if (!unix_append_msgs (tstream,sf,df,au ? dst : NIL) || + (fflush (df) == EOF) || fsync (fd)) { + sprintf (buf,"Message append failed: %s",strerror (errno)); + MM_LOG (buf,ERROR); + ftruncate (fd,sbuf.st_size); + tp[0] = /* preserve \Marked status */ + ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ? + sbuf.st_atime : tp[1]; + ret = NIL; /* return error */ + } + else tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */ + utime (file,tp); /* set the times */ + fclose (sf); /* done with scratch file */ + /* force UIDVALIDITY assignment now */ + if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0); + /* return sets if doing APPENDUID */ + if (au && ret) (*au) (mailbox,tstream->uid_validity,dst); + else mail_free_searchset (&dst); + unix_unlock (fd,NIL,&lock); /* unlock and close mailbox */ + fclose (df); /* note that unix_unlock() released the fd */ + if (tstream) { /* update last UID if we can */ + UNIXLOCAL *local = (UNIXLOCAL *) tstream->local; + local->dirty = T; /* do a rewrite */ + local->appending = T; /* but not at the cost of marking as old */ + tstream = mail_close (tstream); + } + MM_NOCRITICAL (stream); /* release critical */ + return ret; +} + +/* Collect and write single message to append scratch file + * Accepts: MAIL stream + * scratch file + * flags + * date + * message stringstruct + * Returns: NIL if write error, else T + */ + +int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date, + STRING *msg) +{ + unsigned char *s,*t; + unsigned long uf; + long f = mail_parse_flags (stream,flags,&uf); + /* write metadata, note date ends with NL */ + if (fprintf (sf,"%ld %lu %s",f,SIZE (msg) + 1,date) < 0) return NIL; + while (uf) /* write user flags */ + if ((s = stream->user_flags[find_rightmost_bit (&uf)]) && + (fprintf (sf," %s",s) < 0)) return NIL; + if (putc ('\n',sf) == EOF) return NIL; + while (SIZE (msg)) { /* copy text to scratch file */ + for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s) + if (!*s) *s = 0x80; /* disallow NUL */ + /* write buffered text */ + if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize) + SETPOS (msg,GETPOS (msg) + msg->cursize); + else return NIL; /* failed */ + } + /* write trailing newline and return */ + return (putc ('\n',sf) == EOF) ? NIL : T; +} + +/* Append messages from scratch file to mailbox + * Accepts: MAIL stream + * source file + * destination file + * uidset to update if non-NIL + * Returns: T if success, NIL if failure + */ + +int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set) +{ + int ti,zn,c; + long f; + unsigned long i,j; + char *x,tmp[MAILTMPLEN]; + int hdrp = T; + /* get message metadata line */ + while (fgets (tmp,MAILTMPLEN,sf)) { + if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL; + f = strtol (tmp,&x,10); /* get flags */ + if (!((*x++ == ' ') && isdigit (*x))) return NIL; + i = strtoul (x,&x,10); /* get message size */ + if ((*x++ != ' ') || /* build initial header */ + (fprintf (df,"From %s@%s %sStatus: ",myusername(),mylocalhost(),x)<0)|| + (f&fSEEN && (putc ('R',df) == EOF)) || + (fputs ("\nX-Status: ",df) == EOF) || + (f&fDELETED && (putc ('D',df) == EOF)) || + (f&fFLAGGED && (putc ('F',df) == EOF)) || + (f&fANSWERED && (putc ('A',df) == EOF)) || + (f&fDRAFT && (putc ('T',df) == EOF)) || + (fputs ("\nX-Keywords:",df) == EOF)) return NIL; + /* copy keywords */ + while ((c = getc (sf)) != '\n') switch (c) { + case EOF: + return NIL; + default: + if (putc (c,df) == EOF) return NIL; + } + if ((putc ('\n',df) == EOF) || + (set && (fprintf (df,"X-UID: %lu\n",++(stream->uid_last)) < 0))) + return NIL; + + for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) { + /* get read line length */ + if (i < (j = strlen (tmp))) fatal ("unix_append_msgs overrun"); + i -= j; /* number of bytes left */ + /* squish out CRs (note also copies NUL) */ + for (x = tmp; x = strchr (x,'\r'); --j) memmove (x,x+1,j-(x-tmp)); + if (!j) continue; /* do nothing if line emptied */ + /* start of line? */ + if ((c == '\n')) switch (tmp[0]) { + case 'F': /* possible "From " (case counts here) */ + if ((j > 4) && (tmp[0] == 'F') && (tmp[1] == 'r') && (tmp[2] == 'o') && + (tmp[3] == 'm') && (tmp[4] == ' ')) { + if (!unix_fromwidget) { + VALID (tmp,x,ti,zn);/* conditional, only write widget if */ + if (!ti) break; /* it looks like a valid header */ + } /* write the widget */ + if (putc ('>',df) == EOF) return NIL; + } + break; + case 'S': case 's': /* possible "Status:" */ + if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) && + ((tmp[2] == 'a') || (tmp[2] == 'A')) && + ((tmp[3] == 't') || (tmp[3] == 'T')) && + ((tmp[4] == 'u') || (tmp[4] == 'U')) && + ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') && + (fputs ("X-Original-",df) == EOF)) return NIL; + break; + case 'X': case 'x': /* possible X-??? header */ + if (hdrp && (tmp[1] == '-') && + /* possible X-UID: */ + (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) && + ((tmp[3] == 'I') || (tmp[3] == 'i')) && + ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) || + /* possible X-IMAP: */ + ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) && + ((tmp[3] == 'M') || (tmp[3] == 'm')) && + ((tmp[4] == 'A') || (tmp[4] == 'a')) && + ((tmp[5] == 'P') || (tmp[5] == 'p')) && + ((tmp[6] == ':') || + /* or X-IMAPbase: */ + ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) && + ((tmp[7] == 'a') || (tmp[7] == 'A')) && + ((tmp[8] == 's') || (tmp[8] == 'S')) && + ((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) || + /* possible X-Status: */ + ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) && + ((tmp[3] == 't') || (tmp[3] == 'T')) && + ((tmp[4] == 'a') || (tmp[4] == 'A')) && + ((tmp[5] == 't') || (tmp[5] == 'T')) && + ((tmp[6] == 'u') || (tmp[6] == 'U')) && + ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) || + /* possible X-Keywords: */ + ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) && + ((tmp[3] == 'e') || (tmp[3] == 'E')) && + ((tmp[4] == 'y') || (tmp[4] == 'Y')) && + ((tmp[5] == 'w') || (tmp[5] == 'W')) && + ((tmp[6] == 'o') || (tmp[6] == 'O')) && + ((tmp[7] == 'r') || (tmp[7] == 'R')) && + ((tmp[8] == 'd') || (tmp[8] == 'D')) && + ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) && + (fputs ("X-Original-",df) == EOF)) return NIL; + case '\n': /* blank line */ + hdrp = NIL; + break; + default: /* nothing to do */ + break; + } + /* just write the line */ + if (fwrite (tmp,1,j,df) != j) return NIL; + } + if (i) return NIL; /* didn't read entire message */ + /* update set */ + if (stream) mail_append_set (set,stream->uid_last); + } + return T; +} + +/* Internal routines */ + + +/* UNIX mail abort stream + * Accepts: MAIL stream + */ + +void unix_abort (MAILSTREAM *stream) +{ + if (LOCAL) { /* only if a file is open */ + if (LOCAL->fd >= 0) close (LOCAL->fd); + if (LOCAL->ld >= 0) { /* have a mailbox lock? */ + flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */ + close (LOCAL->ld); /* close the lock file */ + unlink (LOCAL->lname); /* and delete it */ + } + if (LOCAL->lname) fs_give ((void **) &LOCAL->lname); + /* free local text buffers */ + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); + if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data); + if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf); + if (LOCAL->line) fs_give ((void **) &LOCAL->line); + /* nuke the local data */ + fs_give ((void **) &stream->local); + stream->dtb = NIL; /* log out the DTB */ + } +} + +/* UNIX open and lock mailbox + * Accepts: file name to open/lock + * file open mode + * destination buffer for lock file name + * type of locking operation (LOCK_SH or LOCK_EX) + */ + +int unix_lock (char *file,int flags,int mode,DOTLOCK *lock,int op) +{ + int fd; + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); + (*bn) (BLOCK_FILELOCK,NIL); + /* try locking the easy way */ + if (dotlock_lock (file,lock,-1)) { + /* got dotlock file, easy open */ + if ((fd = open (file,flags,mode)) >= 0) flock (fd,op); + else dotlock_unlock (lock); /* open failed, free the dotlock */ + } + /* no dot lock file, open file now */ + else if ((fd = open (file,flags,mode)) >= 0) { + /* try paranoid way to make a dot lock file */ + if (dotlock_lock (file,lock,fd)) { + close (fd); /* get fresh fd in case of timing race */ + if ((fd = open (file,flags,mode)) >= 0) flock (fd,op); + /* open failed, free the dotlock */ + else dotlock_unlock (lock); + } + else flock (fd,op); /* paranoid way failed, just flock() it */ + } + (*bn) (BLOCK_NONE,NIL); + return fd; +} + +/* UNIX unlock and close mailbox + * Accepts: file descriptor + * (optional) mailbox stream to check atime/mtime + * (optional) lock file name + */ + +void unix_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock) +{ + if (stream) { /* need to muck with times? */ + struct stat sbuf; + time_t tp[2]; + time_t now = time (0); + fstat (fd,&sbuf); /* get file times */ + if (LOCAL->ld >= 0) { /* yes, readwrite session? */ + tp[0] = now; /* set atime to now */ + /* set mtime to (now - 1) if necessary */ + tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1; + } + else if (stream->recent) { /* readonly with recent messages */ + if ((sbuf.st_atime >= sbuf.st_mtime) || + (sbuf.st_atime >= sbuf.st_ctime)) + /* keep past mtime, whack back atime */ + tp[0] = (tp[1] = (sbuf.st_mtime < now) ? sbuf.st_mtime : now) - 1; + else now = 0; /* no time change needed */ + } + /* readonly with no recent messages */ + else if ((sbuf.st_atime < sbuf.st_mtime) || + (sbuf.st_atime < sbuf.st_ctime)) { + tp[0] = now; /* set atime to now */ + /* set mtime to (now - 1) if necessary */ + tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1; + } + else now = 0; /* no time change needed */ + /* set the times, note change */ + if (now && !utime (stream->mailbox,tp)) LOCAL->filetime = tp[1]; + } + flock (fd,LOCK_UN); /* release flock'ers */ + if (!stream) close (fd); /* close the file if no stream */ + dotlock_unlock (lock); /* flush the lock file if any */ +} + +/* UNIX mail parse and lock mailbox + * Accepts: MAIL stream + * space to write lock file name + * type of locking operation + * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure + */ + +int unix_parse (MAILSTREAM *stream,DOTLOCK *lock,int op) +{ + int zn; + unsigned long i,j,k,m; + unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30]; + int ti = 0,retain = T; + unsigned long nmsgs = stream->nmsgs; + unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0; + unsigned long recent = stream->recent; + unsigned long oldnmsgs = stream->nmsgs; + short silent = stream->silent; + short pseudoseen = NIL; + struct stat sbuf; + STRING bs; + FDDATA d; + MESSAGECACHE *elt; + mail_lock (stream); /* guard against recursion or pingers */ + /* toss out previous descriptor */ + if (LOCAL->fd >= 0) close (LOCAL->fd); + MM_CRITICAL (stream); /* open and lock mailbox (shared OK) */ + if ((LOCAL->fd = unix_lock (stream->mailbox,(LOCAL->ld >= 0) ? + O_RDWR : O_RDONLY, + (long)mail_parameters(NIL,GET_MBXPROTECTION,NIL), + lock,op)) < 0) { + sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno)); + MM_LOG (tmp,ERROR); + unix_abort (stream); + mail_unlock (stream); + MM_NOCRITICAL (stream); /* done with critical */ + return NIL; + } + fstat (LOCAL->fd,&sbuf); /* get status */ + /* validate change in size */ + if (sbuf.st_size < LOCAL->filesize) { + sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted", + (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size); + MM_LOG (tmp,ERROR); /* this is pretty bad */ + unix_unlock (LOCAL->fd,stream,lock); + unix_abort (stream); + mail_unlock (stream); + MM_NOCRITICAL (stream); /* done with critical */ + return NIL; + } + + /* new data? */ + else if (i = sbuf.st_size - LOCAL->filesize) { + d.fd = LOCAL->fd; /* yes, set up file descriptor */ + d.pos = LOCAL->filesize; /* get to that position in the file */ + d.chunk = LOCAL->buf; /* initial buffer chunk */ + d.chunksize = CHUNKSIZE; /* file chunk size */ + INIT (&bs,fd_string,&d,i); /* initialize stringstruct */ + /* skip leading whitespace for broken MTAs */ + while (((c = CHR (&bs)) == '\n') || (c == '\r') || + (c == ' ') || (c == '\t')) SNX (&bs); + if (SIZE (&bs)) { /* read new data */ + /* remember internal header position */ + j = LOCAL->filesize + GETPOS (&bs); + s = unix_mbxline (stream,&bs,&i); + t = NIL,zn = 0; + if (i) VALID (s,t,ti,zn); /* see if valid From line */ + if (!ti) { /* someone pulled the rug from under us */ + sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s", + (char *) s); + MM_LOG (tmp,ERROR); + unix_unlock (LOCAL->fd,stream,lock); + unix_abort (stream); + mail_unlock (stream); + /* done with critical */ + MM_NOCRITICAL (stream); + return NIL; + } + stream->silent = T; /* quell main program new message events */ + do { /* found a message */ + /* instantiate first new message */ + mail_exists (stream,++nmsgs); + (elt = mail_elt (stream,nmsgs))->valid = T; + recent++; /* assume recent by default */ + elt->recent = T; + /* note position/size of internal header */ + elt->private.special.offset = j; + elt->private.msg.header.offset = elt->private.special.text.size = i; + + /* generate plausible IMAPish date string */ + date[2] = date[6] = date[20] = '-'; date[11] = ' '; + date[14] = date[17] = ':'; + /* dd */ + date[0] = t[ti - 2]; date[1] = t[ti - 1]; + /* mmm */ + date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4]; + /* hh */ + date[12] = t[ti + 1]; date[13] = t[ti + 2]; + /* mm */ + date[15] = t[ti + 4]; date[16] = t[ti + 5]; + if (t[ti += 6] == ':') {/* ss */ + date[18] = t[++ti]; date[19] = t[++ti]; + ti++; /* move to space */ + } + else date[18] = date[19] = '0'; + /* yy -- advance over timezone if necessary */ + if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4); + date[7] = t[ti + 1]; date[8] = t[ti + 2]; + date[9] = t[ti + 3]; date[10] = t[ti + 4]; + /* zzz */ + t = zn ? (t + zn + 1) : (unsigned char *) "LCL"; + date[21] = *t++; date[22] = *t++; date[23] = *t++; + if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0'; + else { /* numeric time zone */ + date[24] = *t++; date[25] = *t++; + date[26] = '\0'; date[20] = ' '; + } + /* set internal date */ + if (!mail_parse_date (elt,date)) { + sprintf (tmp,"Unable to parse internal date: %s",(char *) date); + MM_LOG (tmp,WARN); + } + + do { /* look for message body */ + s = t = unix_mbxline (stream,&bs,&i); + if (i) switch (*s) { /* check header lines */ + case 'X': /* possible X-???: line */ + if (s[1] == '-') { /* must be immediately followed by hyphen */ + /* X-Status: becomes Status: in S case */ + if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' && + s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2; + /* possible X-Keywords */ + else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' && + s[5] == 'w' && s[6] == 'o' && s[7] == 'r' && + s[8] == 'd' && s[9] == 's' && s[10] == ':') { + SIZEDTEXT uf; + retain = NIL; /* don't retain continuation */ + s += 11; /* flush leading whitespace */ + while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){ + while (*s == ' ') s++; + /* find end of keyword */ + if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s); + /* got a keyword? */ + if ((k = (u - s)) && (k <= MAXUSERFLAG)) { + uf.data = (unsigned char *) s; + uf.size = k; + for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j) + if (!compare_csizedtext (stream->user_flags[j],&uf)) { + elt->user_flags |= ((long) 1) << j; + break; + } + } + s = u; /* advance to next keyword */ + } + break; + } + + /* possible X-IMAP */ + else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') && + (s[5] == 'P') && ((m = (s[6] == ':')) || + ((s[6] == 'b') && (s[7] == 'a') && + (s[8] == 's') && (s[9] == 'e') && + (s[10] == ':')))) { + retain = NIL; /* don't retain continuation */ + if ((nmsgs == 1) && !stream->uid_validity) { + /* advance to data */ + s += m ? 7 : 11; + /* flush whitespace */ + while (*s == ' ') s++; + j = 0; /* slurp UID validity */ + /* found a digit? */ + while (isdigit (*s)) { + j *= 10; /* yes, add it in */ + j += *s++ - '0'; + } + /* flush whitespace */ + while (*s == ' ') s++; + /* must have valid UID validity and UID last */ + if (j && isdigit (*s)) { + /* pseudo-header seen if X-IMAP */ + if (m) pseudoseen = LOCAL->pseudo = T; + /* save UID validity */ + stream->uid_validity = j; + j = 0; /* slurp UID last */ + while (isdigit (*s)) { + j *= 10; /* yes, add it in */ + j += *s++ - '0'; + } + /* save UID last */ + stream->uid_last = j; + /* process keywords */ + for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n')); + s = u,j++) { + /* flush leading whitespace */ + while (*s == ' ') s++; + u = strpbrk (s," \n\r"); + /* got a keyword? */ + if ((j < NUSERFLAGS) && (k = (u - s)) && + (k <= MAXUSERFLAG)) { + if (stream->user_flags[j]) + fs_give ((void **) &stream->user_flags[j]); + stream->user_flags[j] = (char *) fs_get (k + 1); + strncpy (stream->user_flags[j],s,k); + stream->user_flags[j][k] = '\0'; + } + } + } + } + break; + } + + /* possible X-UID */ + else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' && + s[5] == ':') { + retain = NIL; /* don't retain continuation */ + /* only believe if have a UID validity */ + if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) { + s += 6; /* advance to UID value */ + /* flush whitespace */ + while (*s == ' ') s++; + j = 0; + /* found a digit? */ + while (isdigit (*s)) { + j *= 10; /* yes, add it in */ + j += *s++ - '0'; + } + /* flush remainder of line */ + while (*s != '\n') s++; + /* make sure not duplicated */ + if (elt->private.uid) + sprintf (tmp,"Message %lu UID %lu already has UID %lu", + pseudoseen ? elt->msgno - 1 : elt->msgno, + j,elt->private.uid); + /* make sure UID doesn't go backwards */ + else if (j <= prevuid) + sprintf (tmp,"Message %lu UID %lu less than %lu", + pseudoseen ? elt->msgno - 1 : elt->msgno, + j,prevuid + 1); +#if 0 /* this is currently broken by UIDPLUS */ + /* or skip by mailbox's recorded last */ + else if (j > stream->uid_last) + sprintf (tmp,"Message %lu UID %lu greater than last %lu", + pseudoseen ? elt->msgno - 1 : elt->msgno, + j,stream->uid_last); +#endif + else { /* normal UID case */ + prevuid = elt->private.uid = j; +#if 1 /* temporary kludge for UIDPLUS */ + if (prevuid > stream->uid_last) { + stream->uid_last = prevuid; + LOCAL->ddirty = LOCAL->dirty = T; + } +#endif + break; /* exit this cruft */ + } + MM_LOG (tmp,WARN); + /* invalidate UID validity */ + stream->uid_validity = 0; + elt->private.uid = 0; + } + break; + } + } + /* otherwise fall into S case */ + + case 'S': /* possible Status: line */ + if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' && + s[4] == 'u' && s[5] == 's' && s[6] == ':') { + retain = NIL; /* don't retain continuation */ + s += 6; /* advance to status flags */ + do switch (*s++) {/* parse flags */ + case 'R': /* message read */ + elt->seen = T; + break; + case 'O': /* message old */ + if (elt->recent) { + elt->recent = NIL; + recent--; /* it really wasn't recent */ + } + break; + case 'D': /* message deleted */ + elt->deleted = T; + break; + case 'F': /* message flagged */ + elt->flagged = T; + break; + case 'A': /* message answered */ + elt->answered = T; + break; + case 'T': /* message is a draft */ + elt->draft = T; + break; + default: /* some other crap */ + break; + } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))); + break; /* all done */ + } + /* otherwise fall into default case */ + + default: /* ordinary header line */ + if ((*s == 'S') || (*s == 's') || + (((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) { + unsigned char *e,*v; + /* must match what mail_filter() does */ + for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1); + (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') && + ((c > ' ') || ((c != ' ') && (c != '\t') && + (c != '\r') && (c != '\n'))); + *v++ = *u++); + *v = '\0'; /* tie off */ + /* matches internal header? */ + if (!compare_cstring (tmp,"STATUS") || + !compare_cstring (tmp,"X-STATUS") || + !compare_cstring (tmp,"X-KEYWORDS") || + !compare_cstring (tmp,"X-UID") || + !compare_cstring (tmp,"X-IMAP") || + !compare_cstring (tmp,"X-IMAPBASE")) { + char err[MAILTMPLEN]; + sprintf (err,"Discarding bogus %s header in message %lu", + (char *) tmp,elt->msgno); + MM_LOG (err,WARN); + retain = NIL; /* don't retain continuation */ + break; /* different case or something */ + } + } + /* retain or non-continuation? */ + if (retain || ((*s != ' ') && (*s != '\t'))) { + retain = T; /* retaining continuation now */ + /* line length in LF format newline */ + for (j = k = 0; j < i; ++j) if (s[j] != '\r') ++k; + /* "internal" header size */ + elt->private.spare.data += k; + /* message size */ + elt->rfc822_size += k + 1; + } + else { + char err[MAILTMPLEN]; + sprintf (err,"Discarding bogus continuation in msg %lu: %.80s", + elt->msgno,(char *) s); + if (u = strpbrk (err,"\r\n")) *u = '\0'; + MM_LOG (err,WARN); + break; /* different case or something */ + } + break; + } + } while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n'))); + /* "internal" header sans trailing newline */ + if (i) elt->private.spare.data--; + /* assign a UID if none found */ + if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) { + prevuid = elt->private.uid = ++stream->uid_last; + elt->private.dirty = T; + LOCAL->ddirty = T; /* force update */ + } + else elt->private.dirty = elt->recent; + + /* note size of header, location of text */ + elt->private.msg.header.text.size = + (elt->private.msg.text.offset = + (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) - + elt->private.special.text.size; + k = m = 0; /* no previous line size yet */ + /* note current position */ + j = LOCAL->filesize + GETPOS (&bs); + if (i) do { /* look for next message */ + s = unix_mbxline (stream,&bs,&i); + if (i) { /* got new data? */ + VALID (s,t,ti,zn); /* yes, parse line */ + if (!ti) { /* not a header line, add it to message */ + elt->rfc822_size += i; + for (j = 0; j < i; ++j) switch (s[j]) { + case '\r': /* squeeze out CRs */ + elt->rfc822_size -= 1; + break; + case '\n': /* LF becomes CRLF */ + elt->rfc822_size += 1; + break; + default: + break; + } + if ((i == 1) && (*s == '\n')) { + k = 2; + m = 1; + } + else if ((i == 2) && (*s == '\r') && (s[1] == '\n')) + k = m = 2; + else k = m = 0; /* file does not end with newline! */ + /* update current position */ + j = LOCAL->filesize + GETPOS (&bs); + } + } + } while (i && !ti); /* until found a header */ + elt->private.msg.text.text.size = j - + (elt->private.special.offset + elt->private.msg.text.offset); + /* flush ending blank line */ + elt->private.msg.text.text.size -= m; + elt->rfc822_size -= k; + /* until end of buffer */ + } while (!stream->sniff && i); + if (pseudoseen) { /* flush pseudo-message if present */ + /* decrement recent count */ + if (mail_elt (stream,1)->recent) recent--; + /* and the exists count */ + mail_exists (stream,nmsgs--); + mail_expunged(stream,1);/* fake an expunge of that message */ + } + /* need to start a new UID validity? */ + if (!stream->uid_validity) { + stream->uid_validity = time (0); + /* in case a whiner with no life */ + if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) + stream->uid_nosticky = T; + else if (nmsgs) { /* don't bother if empty file */ + /* make dirty to restart UID epoch */ + LOCAL->ddirty = LOCAL->dirty = T; + /* need to rewrite msg 1 if not pseudo */ + if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T; + MM_LOG ("Assigning new unique identifiers to all messages",NIL); + } + } + stream->nmsgs = oldnmsgs; /* whack it back down */ + stream->silent = silent; /* restore old silent setting */ + /* notify upper level of new mailbox sizes */ + mail_exists (stream,nmsgs); + mail_recent (stream,recent); + /* mark dirty so O flags are set */ + if (recent) LOCAL->dirty = T; + } + } + /* no change, don't babble if never got time */ + else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime) + MM_LOG ("New mailbox modification time but apparently no changes",WARN); + /* update parsed file size and time */ + LOCAL->filesize = sbuf.st_size; + LOCAL->filetime = sbuf.st_mtime; + return T; /* return the winnage */ +} + +/* UNIX read line from mailbox + * Accepts: mail stream + * stringstruct + * pointer to line size + * Returns: pointer to input line + */ + +char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size) +{ + unsigned long i,j,k,m; + char *s,*t,*te; + char *ret = ""; + /* flush old buffer */ + if (LOCAL->line) fs_give ((void **) &LOCAL->line); + /* if buffer needs refreshing */ + if (!bs->cursize) SETPOS (bs,GETPOS (bs)); + if (SIZE (bs)) { /* find newline */ + /* end of fast scan */ + te = (t = (s = bs->curpos) + bs->cursize) - 12; + while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) { + --s; /* back up */ + break; /* exit loop */ + } + /* final character-at-a-time scan */ + while ((s < t) && (*s != '\n')) ++s; + /* difficult case if line spans buffer */ + if ((i = s - bs->curpos) == bs->cursize) { + /* have space in line buffer? */ + if (i > LOCAL->linebuflen) { + fs_give ((void **) &LOCAL->linebuf); + LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i); + } + /* remember what we have so far */ + memcpy (LOCAL->linebuf,bs->curpos,i); + /* load next buffer */ + SETPOS (bs,k = GETPOS (bs) + i); + /* end of fast scan */ + te = (t = (s = bs->curpos) + bs->cursize) - 12; + /* fast scan in overlap buffer */ + while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') || + (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) { + --s; /* back up */ + break; /* exit loop */ + } + + /* final character-at-a-time scan */ + while ((s < t) && (*s != '\n')) ++s; + /* huge line? */ + if ((j = s - bs->curpos) == bs->cursize) { + SETPOS (bs,GETPOS (bs) + j); + /* look for end of line (s-l-o-w!!) */ + for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j); + SETPOS (bs,k); /* go back to where it started */ + } + /* got size of data, make buffer for return */ + ret = LOCAL->line = (char *) fs_get (i + j + 2); + /* copy first chunk */ + memcpy (ret,LOCAL->linebuf,i); + while (j) { /* copy remainder */ + if (!bs->cursize) SETPOS (bs,GETPOS (bs)); + memcpy (ret + i,bs->curpos,k = min (j,bs->cursize)); + i += k; /* account for this much read in */ + j -= k; + bs->curpos += k; /* increment new position */ + bs->cursize -= k; /* eat that many bytes */ + } + if (!bs->cursize) SETPOS (bs,GETPOS (bs)); + /* read newline at end */ + if (SIZE (bs)) ret[i++] = SNX (bs); + ret[i] = '\0'; /* makes debugging easier */ + } + else { /* this is easy */ + ret = bs->curpos; /* string it at this position */ + bs->curpos += ++i; /* increment new position */ + bs->cursize -= i; /* eat that many bytes */ + } + *size = i; /* return that to user */ + } + else *size = 0; /* end of data, return empty */ + return ret; +} + +/* UNIX make pseudo-header + * Accepts: MAIL stream + * buffer to write pseudo-header + * Returns: length of pseudo-header + */ + +unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr) +{ + int i; + char *s,tmp[MAILTMPLEN]; + time_t now = time (0); + rfc822_fixed_date (tmp); + sprintf (hdr,"From %s %.24s\nDate: %s\nFrom: %s <%s@%.80s>\nSubject: %s\nMessage-ID: <%lu@%.80s>\nX-IMAP: %010lu %010lu", + pseudo_from,ctime (&now), + tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject, + (unsigned long) now,mylocalhost (),stream->uid_validity, + stream->uid_last); + for (s = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i) + if (stream->user_flags[i]) + sprintf (s += strlen (s)," %s",stream->user_flags[i]); + sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n\n",pseudo_msg); + return strlen (hdr); /* return header length */ +} + +/* UNIX make status string + * Accepts: MAIL stream + * destination string to write + * message cache entry + * UID to write if non-zero (else use elt->private.uid) + * non-zero flag to write UID (.LT. 0 to write UID base info too) + * Returns: length of string + */ + +unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt, + unsigned long uid,long flag) +{ + char *t,stack[64]; + char *s = status; + unsigned long n; + int pad = 50; + int sticky = uid ? T : !stream->uid_nosticky; + /* This used to use sprintf(), but thanks to certain cretinous C libraries + with horribly slow implementations of sprintf() I had to change it to this + mess. At least it should be fast. */ + if ((flag < 0) && sticky) { /* need to write X-IMAPbase: header? */ + *s++ = 'X'; *s++ = '-'; *s++ = 'I'; *s++ = 'M'; *s++ = 'A'; *s++ = 'P'; + *s++ = 'b'; *s++ = 'a'; *s++ = 's'; *s++ = 'e'; *s++ = ':'; *s++ = ' '; + t = stack; + n = stream->uid_validity; /* push UID validity digits on the stack */ + do *t++ = (char) (n % 10) + '0'; + while (n /= 10); + /* pop UID validity digits from stack */ + while (t > stack) *s++ = *--t; + *s++ = ' '; + n = stream->uid_last; /* push UID last digits on the stack */ + do *t++ = (char) (n % 10) + '0'; + while (n /= 10); + /* pop UID last digits from stack */ + while (t > stack) *s++ = *--t; + for (n = 0; n < NUSERFLAGS; ++n) if (t = stream->user_flags[n]) + for (*s++ = ' '; *t; *s++ = *t++); + *s++ = '\n'; + pad += 30; /* increased padding if have IMAPbase */ + } + *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's'; + *s++ = ':'; *s++ = ' '; + if (elt->seen) *s++ = 'R'; + /* only write O if have a UID */ + if (flag && (!elt->recent || !LOCAL->appending)) *s++ = 'O'; + *s++ = '\n'; + *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; + *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' '; + if (elt->deleted) *s++ = 'D'; + if (elt->flagged) *s++ = 'F'; + if (elt->answered) *s++ = 'A'; + if (elt->draft) *s++ = 'T'; + *s++ = '\n'; + + if (sticky) { /* only do this if UIDs sticky */ + *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w'; + *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':'; + if (n = elt->user_flags) do { + *s++ = ' '; + for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++); + } while (n); + n = s - status; /* get size of stuff so far */ + /* pad X-Keywords to make size constant */ + if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' '; + *s++ = '\n'; + if (flag) { /* want to include UID? */ + t = stack; + /* push UID digits on the stack */ + n = uid ? uid : elt->private.uid; + do *t++ = (char) (n % 10) + '0'; + while (n /= 10); + *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':'; + *s++ = ' '; + /* pop UID from stack */ + while (t > stack) *s++ = *--t; + *s++ = '\n'; + } + } + *s++ = '\n'; *s = '\0'; /* end of extended message status */ + return s - status; /* return size of resulting string */ +} + +/* Rewrite mailbox file + * Accepts: MAIL stream, must be critical and locked + * return pointer to number of expunged messages if want expunge + * lock file name + * expunge sequence, not deleted flag + * Returns: T if success and mailbox unlocked, NIL if failure + */ + +#define OVERFLOWBUFLEN 8192 /* initial overflow buffer length */ + +long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock, + long flags) +{ + MESSAGECACHE *elt; + UNIXFILE f; + char *s; + time_t tp[2]; + long ret,flag; + unsigned long i,j; + unsigned long recent = stream->recent; + unsigned long size = LOCAL->pseudo ? unix_pseudo (stream,LOCAL->buf) : 0; + if (nexp) *nexp = 0; /* initially nothing expunged */ + /* calculate size of mailbox after rewrite */ + for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) { + elt = mail_elt (stream,i); /* get cache */ + if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) { + /* add RFC822 size of this message */ + size += elt->private.special.text.size + elt->private.spare.data + + unix_xstatus (stream,LOCAL->buf,elt,NIL,flag) + + elt->private.msg.text.text.size + 1; + flag = 1; /* only count X-IMAPbase once */ + } + } + /* no messages, has a life, and no pseudo */ + if (!size && !mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) { + LOCAL->pseudo = T; /* so make a pseudo-message now */ + size = unix_pseudo (stream,LOCAL->buf); + } + /* extend the file as necessary */ + if (ret = unix_extend (stream,size)) { + /* Set up buffered I/O file structure + * curpos current position being written through buffering + * filepos current position being written physically to the disk + * bufpos current position being written in the buffer + * protect current maximum position that can be written to the disk + * before buffering is forced + * The code tries to buffer so that that disk is written in multiples of + * OVERBLOWBUFLEN bytes. + */ + f.stream = stream; /* note mail stream */ + f.curpos = f.filepos = 0; /* start of file */ + f.protect = stream->nmsgs ? /* initial protection pointer */ + mail_elt (stream,1)->private.special.offset : 8192; + f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN); + + if (LOCAL->pseudo) /* update pseudo-header */ + unix_write (&f,LOCAL->buf,unix_pseudo (stream,LOCAL->buf)); + /* loop through all messages */ + for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) { + elt = mail_elt (stream,i);/* get cache */ + /* expunge this message? */ + if (nexp && elt->deleted && (flags ? elt->sequence : T)) { + /* one less recent message */ + if (elt->recent) --recent; + mail_expunged(stream,i);/* notify upper levels */ + ++*nexp; /* count up one more expunged message */ + } + else { /* preserve this message */ + i++; /* advance to next message */ + if ((flag < 0) || /* need to rewrite message? */ + elt->private.dirty || (f.curpos != elt->private.special.offset) || + (elt->private.msg.header.text.size != + (elt->private.spare.data + + unix_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) { + unsigned long newoffset = f.curpos; + /* yes, seek to internal header */ + lseek (LOCAL->fd,elt->private.special.offset,L_SET); + read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size); + /* see if need to squeeze out a CR */ + if (LOCAL->buf[elt->private.special.text.size - 2] == '\r') { + LOCAL->buf[--elt->private.special.text.size - 1] = '\n'; + --size; /* squeezed out a CR from PC */ + } + /* protection pointer moves to RFC822 header */ + f.protect = elt->private.special.offset + + elt->private.msg.header.offset; + /* write internal header */ + unix_write (&f,LOCAL->buf,elt->private.special.text.size); + /* get RFC822 header */ + s = unix_header (stream,elt->msgno,&j,FT_INTERNAL); + /* in case this got decremented */ + elt->private.msg.header.offset = elt->private.special.text.size; + /* header size, sans trailing newline */ + if ((j < 2) || (s[j - 2] == '\n')) j--; + /* this can happen if CRs were squeezed */ + if (j < elt->private.spare.data) { + /* so fix up counts */ + size -= elt->private.spare.data - j; + elt->private.spare.data = j; + } + else if (j != elt->private.spare.data) + fatal ("header size inconsistent"); + /* protection pointer moves to RFC822 text */ + f.protect = elt->private.special.offset + + elt->private.msg.text.offset; + unix_write (&f,s,j); /* write RFC822 header */ + /* write status and UID */ + unix_write (&f,LOCAL->buf, + j = unix_xstatus (stream,LOCAL->buf,elt,NIL,flag)); + flag = 1; /* only write X-IMAPbase once */ + /* new file header size */ + elt->private.msg.header.text.size = elt->private.spare.data + j; + + /* did text move? */ + if (f.curpos != f.protect) { + /* get message text */ + s = unix_text_work (stream,elt,&j,FT_INTERNAL); + /* this can happen if CRs were squeezed */ + if (j < elt->private.msg.text.text.size) { + /* so fix up counts */ + size -= elt->private.msg.text.text.size - j; + elt->private.msg.text.text.size = j; + } + /* can't happen it says here */ + else if (j > elt->private.msg.text.text.size) + fatal ("text size inconsistent"); + /* new text offset, status/UID may change it */ + elt->private.msg.text.offset = f.curpos - newoffset; + /* protection pointer moves to next message */ + f.protect = (i <= stream->nmsgs) ? + mail_elt (stream,i)->private.special.offset : (f.curpos + j + 1); + unix_write (&f,s,j);/* write text */ + /* write trailing newline */ + unix_write (&f,"\n",1); + } + else { /* tie off header and status */ + unix_write (&f,NIL,NIL); + /* protection pointer moves to next message */ + f.protect = (i <= stream->nmsgs) ? + mail_elt (stream,i)->private.special.offset : size; + /* locate end of message text */ + j = f.filepos + elt->private.msg.text.text.size; + /* trailing newline already there? */ + if (f.protect == (j + 1)) f.curpos = f.filepos = f.protect; + else { /* trailing newline missing, write it */ + f.curpos = f.filepos = j; + unix_write (&f,"\n",1); + } + } + /* new internal header offset */ + elt->private.special.offset = newoffset; + elt->private.dirty =NIL;/* message is now clean */ + } + else { /* no need to rewrite this message */ + /* tie off previous message if needed */ + unix_write (&f,NIL,NIL); + /* protection pointer moves to next message */ + f.protect = (i <= stream->nmsgs) ? + mail_elt (stream,i)->private.special.offset : size; + /* locate end of message text */ + j = f.filepos + elt->private.special.text.size + + elt->private.msg.header.text.size + + elt->private.msg.text.text.size; + /* trailing newline already there? */ + if (f.protect == (j + 1)) f.curpos = f.filepos = f.protect; + else { /* trailing newline missing, write it */ + f.curpos = f.filepos = j; + unix_write (&f,"\n",1); + } + } + } + } + + unix_write (&f,NIL,NIL); /* tie off final message */ + if (size != f.filepos) fatal ("file size inconsistent"); + fs_give ((void **) &f.buf); /* free buffer */ + /* make sure tied off */ + ftruncate (LOCAL->fd,LOCAL->filesize = size); + fsync (LOCAL->fd); /* make sure the updates take */ + if (size && (flag < 0)) fatal ("lost UID base information"); + /* no longer dirty */ + LOCAL->ddirty = LOCAL->dirty = NIL; + /* notify upper level of new mailbox sizes */ + mail_exists (stream,stream->nmsgs); + mail_recent (stream,recent); + /* set atime to now, mtime a second earlier */ + tp[1] = (tp[0] = time (0)) - 1; + /* set the times, note change */ + if (!utime (stream->mailbox,tp)) LOCAL->filetime = tp[1]; + close (LOCAL->fd); /* close and reopen file */ + if ((LOCAL->fd = open (stream->mailbox,O_RDWR, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) + < 0) { + sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno)); + MM_LOG (LOCAL->buf,ERROR); + unix_abort (stream); + } + dotlock_unlock (lock); /* flush the lock file */ + } + return ret; /* return state from algorithm */ +} + +/* Extend UNIX mailbox file + * Accepts: MAIL stream + * new desired size + * Return: T if success, else NIL + */ + +long unix_extend (MAILSTREAM *stream,unsigned long size) +{ + unsigned long i = (size > LOCAL->filesize) ? size - LOCAL->filesize : 0; + if (i) { /* does the mailbox need to grow? */ + if (i > LOCAL->buflen) { /* make sure have enough space */ + /* this user won the lottery all right */ + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1); + } + memset (LOCAL->buf,'\0',i); /* get a block of nulls */ + while (T) { /* until write successful or punt */ + lseek (LOCAL->fd,LOCAL->filesize,L_SET); + if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break; + else { + long e = errno; /* note error before doing ftruncate */ + ftruncate (LOCAL->fd,LOCAL->filesize); + if (MM_DISKERROR (stream,e,NIL)) { + fsync (LOCAL->fd); /* user chose to punt */ + sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e)); + if (!stream->silent) MM_LOG (LOCAL->buf,ERROR); + return NIL; + } + } + } + } + return LONGT; +} + +/* Write data to buffered file + * Accepts: buffered file pointer + * file data or NIL to indicate "flush buffer" + * date size (ignored for "flush buffer") + * Does not return until success + */ + +void unix_write (UNIXFILE *f,char *buf,unsigned long size) +{ + unsigned long i,j,k; + if (buf) { /* doing buffered write? */ + i = f->bufpos - f->buf; /* yes, get size of current buffer data */ + /* yes, have space in current buffer chunk? */ + if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) { + /* yes, fill up buffer as much as we can */ + memcpy (f->bufpos,buf,k = min (j,size)); + f->bufpos += k; /* new buffer position */ + f->curpos += k; /* new current position */ + if (j -= k) return; /* all done if still have buffer free space */ + buf += k; /* full, get new unwritten data pointer */ + size -= k; /* new data size */ + i += k; /* new buffer data size */ + } + /* This chunk of the buffer is full. See if can make some space by + * writing to the disk, if there's enough unprotected space to do so. + * Try to fill out any unaligned chunk, along with any subsequent full + * chunks that will fit in unprotected space. + */ + /* any unprotected space we can write to? */ + if (j = min (i,f->protect - f->filepos)) { + /* yes, filepos not at chunk boundary? */ + if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j)) + j -= k; /* yes, and can write out partial chunk */ + else k = 0; /* no partial chunk to write */ + /* if at least a chunk free, write that too */ + if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN); + if (k) { /* write data if there is anything we can */ + unix_phys_write (f,f->buf,k); + /* slide buffer */ + if (i -= k) memmove (f->buf,f->buf + k,i); + f->bufpos = f->buf + i; /* new end of buffer */ + } + } + + /* Have flushed the buffer as best as possible. All done if no more + * data to write. Otherwise, if the buffer is empty AND if the unwritten + * data is larger than a chunk AND the unprotected space is also larger + * than a chunk, then write as many chunks as we can directly from the + * data. Buffer the rest, expanding the buffer as needed. + */ + if (size) { /* have more data that we need to buffer? */ + /* can write any of it to disk instead? */ + if ((f->bufpos == f->buf) && + ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) { + /* write as much as we can right now */ + unix_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN)); + buf += j; /* new data pointer */ + size -= j; /* new data size */ + f->curpos += j; /* advance current pointer */ + } + if (size) { /* still have data that we need to buffer? */ + /* yes, need to expand the buffer? */ + if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) { + /* note current position in buffer */ + j = f->bufpos - f->buf; + i += OVERFLOWBUFLEN; /* yes, grow another chunk */ + fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN)); + /* in case buffer relocated */ + f->bufpos = f->buf + j; + } + /* buffer remaining data */ + memcpy (f->bufpos,buf,size); + f->bufpos += size; /* new end of buffer */ + f->curpos += size; /* advance current pointer */ + } + } + } + else { /* flush buffer to disk */ + unix_phys_write (f,f->buf,i = f->bufpos - f->buf); + f->bufpos = f->buf; /* reset buffer */ + /* update positions */ + f->curpos = f->protect = f->filepos; + } +} + +/* Physical disk write + * Accepts: buffered file pointer + * buffer address + * buffer size + * Does not return until success + */ + +void unix_phys_write (UNIXFILE *f,char *buf,size_t size) +{ + MAILSTREAM *stream = f->stream; + /* write data at desired position */ + while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) || + (write (LOCAL->fd,buf,size) < 0))) { + int e; + char tmp[MAILTMPLEN]; + sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno)); + MM_LOG (tmp,ERROR); + MM_DISKERROR (NIL,e,T); /* serious problem, must retry */ + } + f->filepos += size; /* update file position */ +} + +/* MBOX mail routines */ + + +/* Driver dispatch used by MAIL */ + +DRIVER mboxdriver = { + "mbox", /* driver name */ + /* driver flags */ + DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY, + (DRIVER *) NIL, /* next driver */ + mbox_valid, /* mailbox is valid for us */ + unix_parameters, /* manipulate parameters */ + unix_scan, /* scan mailboxes */ + unix_list, /* find mailboxes */ + unix_lsub, /* find subscribed mailboxes */ + NIL, /* subscribe to mailbox */ + NIL, /* unsubscribe from mailbox */ + mbox_create, /* create mailbox */ + mbox_delete, /* delete mailbox */ + mbox_rename, /* rename mailbox */ + mbox_status, /* status of mailbox */ + mbox_open, /* open mailbox */ + unix_close, /* close mailbox */ + NIL, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message structure */ + unix_header, /* fetch message header */ + unix_text, /* fetch message body */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + NIL, /* modify flags */ + unix_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + mbox_ping, /* ping mailbox to see if still alive */ + mbox_check, /* check for new messages */ + mbox_expunge, /* expunge deleted messages */ + unix_copy, /* copy messages to another mailbox */ + mbox_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + /* prototype stream */ +MAILSTREAM mboxproto = {&mboxdriver}; + +/* MBOX mail validate mailbox + * Accepts: mailbox name + * Returns: our driver if name is valid, NIL otherwise + */ + +DRIVER *mbox_valid (char *name) +{ + /* only INBOX, mbox must exist */ + if (!compare_cstring (name,"INBOX") && (unix_valid ("mbox") || !errno) && + (unix_valid (sysinbox()) || !errno || (errno == ENOENT))) + return &mboxdriver; + return NIL; /* can't win (yet, anyway) */ +} + +/* MBOX mail create mailbox + * Accepts: MAIL stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long mbox_create (MAILSTREAM *stream,char *mailbox) +{ + char tmp[MAILTMPLEN]; + if (!compare_cstring (mailbox,"INBOX")) return unix_create (NIL,"mbox"); + sprintf (tmp,"Can't create non-INBOX name as mbox: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; +} + + +/* MBOX mail delete mailbox + * Accepts: MAIL stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long mbox_delete (MAILSTREAM *stream,char *mailbox) +{ + return mbox_rename (stream,mailbox,NIL); +} + + +/* MBOX mail rename mailbox + * Accepts: MAIL stream + * old mailbox name + * new mailbox name (or NIL for delete) + * Returns: T on success, NIL on failure + */ + +long mbox_rename (MAILSTREAM *stream,char *old,char *newname) +{ + char tmp[MAILTMPLEN]; + long ret = unix_rename (stream,"~/mbox",newname); + /* recreate file if renamed INBOX */ + if (ret) unix_create (NIL,"mbox"); + else MM_LOG (tmp,ERROR); /* log error */ + return ret; /* return success */ +} + +/* MBOX Mail status + * Accepts: mail stream + * mailbox name + * status flags + * Returns: T on success, NIL on failure + */ + +long mbox_status (MAILSTREAM *stream,char *mbx,long flags) +{ + MAILSTATUS status; + unsigned long i; + MAILSTREAM *tstream = NIL; + MAILSTREAM *systream = NIL; + /* make temporary stream (unless this mbx) */ + if (!stream && !(stream = tstream = + mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL; + status.flags = flags; /* return status values */ + status.messages = stream->nmsgs; + status.recent = stream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++) + if (!mail_elt (stream,i)->seen) status.unseen++; + status.uidnext = stream->uid_last + 1; + status.uidvalidity = stream->uid_validity; + if (!status.recent && /* calculate post-snarf results */ + (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) { + status.messages += systream->nmsgs; + status.recent += systream->recent; + if (flags & SA_UNSEEN) /* must search to get unseen messages */ + for (i = 1; i <= systream->nmsgs; i++) + if (!mail_elt (systream,i)->seen) status.unseen++; + /* kludge but probably good enough */ + status.uidnext += systream->nmsgs; + } + MM_STATUS(stream,mbx,&status);/* pass status to main program */ + if (tstream) mail_close (tstream); + if (systream) mail_close (systream); + return T; /* success */ +} + +/* MBOX mail open + * Accepts: stream to open + * Returns: stream on success, NIL on failure + */ + +MAILSTREAM *mbox_open (MAILSTREAM *stream) +{ + unsigned long i = 1; + unsigned long recent = 0; + /* return prototype for OP_PROTOTYPE call */ + if (!stream) return &mboxproto; + /* change mailbox file name */ + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr ("mbox"); + /* open mailbox, snarf new mail */ + if (!(unix_open (stream) && mbox_ping (stream))) return NIL; + stream->inbox = T; /* mark that this is an INBOX */ + /* notify upper level of mailbox sizes */ + mail_exists (stream,stream->nmsgs); + while (i <= stream->nmsgs) if (mail_elt (stream,i++)->recent) ++recent; + mail_recent (stream,recent); /* including recent messages */ + return stream; +} + +/* MBOX mail ping mailbox + * Accepts: MAIL stream + * Returns: T if stream alive, else NIL + * No-op for readonly files, since read/writer can expunge it from under us! + */ + +static int snarfed = 0; /* number of snarfs */ + +long mbox_ping (MAILSTREAM *stream) +{ + int sfd; + unsigned long size; + struct stat sbuf; + char *s; + DOTLOCK lock,lockx; + /* time to try snarf and sysinbox non-empty? */ + if (LOCAL && !stream->rdonly && !stream->lock && + (time (0) >= (LOCAL->lastsnarf + + (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) && + !stat (sysinbox (),&sbuf) && sbuf.st_size) { + MM_CRITICAL (stream); /* yes, go critical */ + /* open and lock sysinbox */ + if ((sfd = unix_lock (sysinbox (),O_RDWR, + (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL), + &lockx,LOCK_EX)) >= 0) { + /* locked sysinbox in good format? */ + if (fstat (sfd,&sbuf) || !(size = sbuf.st_size) || + !unix_isvalid_fd (sfd)) { + sprintf (LOCAL->buf,"Mail drop %s is not in standard Unix format", + sysinbox ()); + MM_LOG (LOCAL->buf,ERROR); + } + /* sysinbox good, parse and excl-lock mbox */ + else if (unix_parse (stream,&lock,LOCK_EX)) { + lseek (sfd,0,L_SET); /* read entire sysinbox into memory */ + read (sfd,s = (char *) fs_get (size + 1),size); + s[size] = '\0'; /* tie it off */ + /* append to end of mbox */ + lseek (LOCAL->fd,LOCAL->filesize,L_SET); + + /* copy to mbox */ + if ((write (LOCAL->fd,s,size) < 0) || fsync (LOCAL->fd)) { + sprintf (LOCAL->buf,"New mail move failed: %s",strerror (errno)); + MM_LOG (LOCAL->buf,WARN); + /* revert mbox to previous size */ + ftruncate (LOCAL->fd,LOCAL->filesize); + } + /* sysinbox better not have changed */ + else if (fstat (sfd,&sbuf) || (size != sbuf.st_size)) { + sprintf (LOCAL->buf,"Mail drop %s lock failure, old=%lu now=%lu", + sysinbox (),size,(unsigned long) sbuf.st_size); + MM_LOG (LOCAL->buf,ERROR); + /* revert mbox to previous size */ + ftruncate (LOCAL->fd,LOCAL->filesize); + /* Believe it or not, a Singaporean government system actually had + * symlinks from /var/mail/user to ~user/mbox. To compound this + * error, they used an SVR4 system; BSD and OSF locks would have + * prevented it but not SVR4 locks. + */ + if (!fstat (sfd,&sbuf) && (size == sbuf.st_size)) + syslog (LOG_ALERT,"File %s and %s are the same file!", + sysinbox (),stream->mailbox); + } + else { /* data copied OK */ + ftruncate (sfd,0); /* truncate sysinbox to zero bytes */ + if (!snarfed++) { /* have we snarfed before? */ + /* syslog if server, else user log */ + sprintf (LOCAL->buf,"Moved %lu bytes of new mail to %s from %s", + size,stream->mailbox,sysinbox ()); + if (strcmp ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL), + "unknown")) + syslog (LOG_INFO,"%s host= %s",LOCAL->buf,tcp_clienthost ()); + else MM_LOG (LOCAL->buf,WARN); + } + } + /* done with sysinbox text */ + fs_give ((void **) &s); + /* all done with mbox */ + unix_unlock (LOCAL->fd,stream,&lock); + mail_unlock (stream); /* unlock the stream */ + MM_NOCRITICAL (stream); /* done with critical */ + } + /* all done with sysinbox */ + unix_unlock (sfd,NIL,&lockx); + } + MM_NOCRITICAL (stream); /* done with critical */ + LOCAL->lastsnarf = time (0);/* note time of last snarf */ + } + return unix_ping (stream); /* do the unix routine now */ +} + +/* MBOX mail check mailbox + * Accepts: MAIL stream + */ + +void mbox_check (MAILSTREAM *stream) +{ + /* do local ping, then do unix routine */ + if (mbox_ping (stream)) unix_check (stream); +} + + +/* MBOX mail expunge mailbox + * Accepts: MAIL stream + * sequence to expunge if non-NIL + * expunge options + * Returns: T, always + */ + +long mbox_expunge (MAILSTREAM *stream,char *sequence,long options) +{ + long ret = unix_expunge (stream,sequence,options); + mbox_ping (stream); /* do local ping */ + return ret; +} + + +/* MBOX mail append message from stringstruct + * Accepts: MAIL stream + * destination mailbox + * append callback + * data for callback + * Returns: T if append successful, else NIL + */ + +long mbox_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + char tmp[MAILTMPLEN]; + if (mbox_valid (mailbox)) return unix_append (stream,"mbox",af,data); + sprintf (tmp,"Can't append to that name: %.80s",mailbox); + MM_LOG (tmp,ERROR); + return NIL; +} diff --git a/imap/src/osdep/amiga/unix.h b/imap/src/osdep/amiga/unix.h new file mode 100644 index 00000000..76cde8b5 --- /dev/null +++ b/imap/src/osdep/amiga/unix.h @@ -0,0 +1,219 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: UNIX mail routines, Amiga version + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 20 December 1989 + * Last Edited: 30 August 2006 + */ + + +/* DEDICATION + * + * This file is dedicated to my dog, Unix, also known as Yun-chan and + * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix + * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after + * a two-month bout with cirrhosis of the liver. + * + * He was a dear friend, and I miss him terribly. + * + * Lift a leg, Yunie. Luv ya forever!!!! + */ + +/* Validate line + * Accepts: pointer to candidate string to validate as a From header + * return pointer to end of date/time field + * return pointer to offset from t of time (hours of ``mmm dd hh:mm'') + * return pointer to offset from t of time zone (if non-zero) + * Returns: t,ti,zn set if valid From string, else ti is NIL + */ + +#define VALID(s,x,ti,zn) { \ + int remote = 0; \ + ti = 0; \ + if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \ + (s[4] == ' ')) { \ + for (x = s + 5; *x && *x != '\012'; x++); \ + if (*x) { \ + if (x[-1] == '\015') --x; \ + if (x - s >= 41) { \ + for (zn = -1; x[zn] != ' '; zn--); \ + if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \ + (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \ + (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \ + (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\ + { \ + while (x[zn-13] == ' ') zn--; \ + x += zn - 12; \ + remote = 1; \ + } \ + } \ + if (x - s >= 27) { \ + if (x[-5] == ' ') { \ + if (x[-8] == ':') zn = 0,ti = -5; \ + else if (x[-9] == ' ') ti = zn = -9; \ + else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \ + ti = zn = -11; \ + } \ + else if (x[-4] == ' ') { \ + if (x[-9] == ' ') zn = -4,ti = -9; \ + else if ( (x[-13] == ' ') && (x[-16] == ' ') \ + && (x[-20] ==' ') && \ + ( ((x[-22] == ' ') && (x[-23] == ',')) || \ + ((x[-23] == ' ') && (x[-24] == ',')) ) ) { \ + char weekday[4]={0,}, month[4]={0,}, time[11]={0,}; \ + char tzone[4]={0,}; \ + char realtime[80]; \ + int day,year,start=-26; \ + if (x[-23] == ' ') x--; \ + sscanf(&x[start],"%3c, %d %s %d %s %s", \ + weekday,&day,month,&year,time,tzone); \ + sprintf(realtime,"%s %s %2d %s %d %s", \ + weekday,month,day,time, \ + ( (year < 100) ? year+1900 : year),tzone); \ + if (remote) \ + strcat(realtime," remote from "); \ + else \ + strcat(realtime,"\n"); \ + strncpy(&x[start],realtime,strlen(realtime)); \ + zn = -2; \ + ti = -7; \ + } \ + } \ + else if (x[-6] == ' ') { \ + if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \ + zn = -6,ti = -11; \ + } \ + else if (x[-9] == ' ') { \ + if ( ( (x[-12] == ' ') && (x[-16] == ' ') && \ + ( ((x[-18] == ' ') && (x[-19] == ',') ) || \ + ((x[-19] == ' ') && (x[-20] == ',')) ) \ + || \ + ((x[-14] == ' ') && (x[-18] == ' ') && \ + ( ((x[-20] == ' ') && (x[-21] == ',') ) || \ + ((x[-21] == ' ') && (x[-22] == ',')) ) ) ) ) { \ + char weekday[4]={0,}, month[4]={0,},time[11]={0,}; \ + int day,year,start=-24; \ + char realtime[80]; \ + if (x[-12] == ' ') x++; \ + if (x[-19] == ' ') x++; \ + sscanf(&x[start],"%3c, %d %3c %d %s",weekday, \ + &day,month,&year,time); \ + sprintf(realtime,"%s %s %2d %s %d",weekday,month,day,time,\ + ( (year < 100) ? year+1900 : year)); \ + if (remote) \ + strcat(realtime," remote from "); \ + else \ + strcat(realtime,"\n"); \ + strncpy(&x[start],realtime,strlen(realtime)); \ + ti=-5; \ + zn=0; \ + } \ + } \ + if (ti && !((x[ti - 3] == ':') && \ + (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \ + (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \ + (x[ti - 11] == ' '))) ti = 0; \ + } \ + } \ + } \ +} + +/* You are not expected to understand this macro, but read the next page if + * you are not faint of heart. + * + * Known formats to the VALID macro are: + * From user Wed Dec 2 05:53 1992 + * BSD From user Wed Dec 2 05:53:22 1992 + * SysV From user Wed Dec 2 05:53 PST 1992 + * rn From user Wed Dec 2 05:53:22 PST 1992 + * From user Wed Dec 2 05:53 -0700 1992 + * emacs From user Wed Dec 2 05:53:22 -0700 1992 + * From user Wed Dec 2 05:53 1992 PST + * From user Wed Dec 2 05:53:22 1992 PST + * From user Wed Dec 2 05:53 1992 -0700 + * Solaris From user Wed Dec 2 05:53:22 1992 -0700 + * + * Amiga From user Wed, 6 Dec 92 05:53:22 who did this !!! + * CHANGED in place to + * From user Wed Dec 2 05:53:22 1992 + * + * Plus all of the above with `` remote from xxx'' after it. Thank you very + * much, smail and Solaris, for making my life considerably more complicated. + */ + +/* + * What? You want to understand the VALID macro anyway? Alright, since you + * insist. Actually, it isn't really all that difficult, provided that you + * take it step by step. + * + * Line 1 Initializes the return ti value to failure (0); + * Lines 2-3 Validates that the 1st-5th characters are ``From ''. + * Lines 4-6 Validates that there is an end of line and points x at it. + * Lines 7-14 First checks to see if the line is at least 41 characters long. + * If so, it scans backwards to find the rightmost space. From + * that point, it scans backwards to see if the string matches + * `` remote from''. If so, it sets x to point to the space at + * the start of the string. + * Line 15 Makes sure that there are at least 27 characters in the line. + * Lines 16-21 Checks if the date/time ends with the year (there is a space + * five characters back). If there is a colon three characters + * further back, there is no timezone field, so zn is set to 0 + * and ti is set in front of the year. Otherwise, there must + * either to be a space four characters back for a three-letter + * timezone, or a space six characters back followed by a + or - + * for a numeric timezone; in either case, zn and ti become the + * offset of the space immediately before it. + * Lines 22-24 Are the failure case for line 14. If there is a space four + * characters back, it is a three-letter timezone; there must be a + * space for the year nine characters back. zn is the zone + * offset; ti is the offset of the space. + * Lines 25-28 Are the failure case for line 20. If there is a space six + * characters back, it is a numeric timezone; there must be a + * space eleven characters back and a + or - five characters back. + * zn is the zone offset; ti is the offset of the space. + * Line 29-32 If ti is valid, make sure that the string before ti is of the + * form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise + * invalidate ti. There must be a colon three characters back + * and a space six or nine characters back (depending upon + * whether or not the character six characters back is a colon). + * There must be a space three characters further back (in front + * of the day), one seven characters back (in front of the month), + * and one eleven characters back (in front of the day of week). + * ti is set to be the offset of the space before the time. + * + * Why a macro? It gets invoked a *lot* in a tight loop. On some of the + * newer pipelined machines it is faster being open-coded than it would be if + * subroutines are called. + * + * Why does it scan backwards from the end of the line, instead of doing the + * much easier forward scan? There is no deterministic way to parse the + * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to + * see if unquoted spaces were possible. They are, and I've encountered enough + * evil mail to be totally unwilling to trust that ``it will never happen''. + */ + +/* Build parameters */ + +#define KODRETRY 15 /* kiss-of-death retry in seconds */ +#define LOCKTIMEOUT 5 /* lock timeout in minutes */ +#define CHUNK 16384 /* read-in chunk size */ diff --git a/imap/src/osdep/amiga/write.c b/imap/src/osdep/amiga/write.c new file mode 100644 index 00000000..c7854815 --- /dev/null +++ b/imap/src/osdep/amiga/write.c @@ -0,0 +1,59 @@ +/* ======================================================================== + * Copyright 1988-2006 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * ======================================================================== + */ + +/* + * Program: Write data, treating partial writes as an error + * + * Author: Mark Crispin + * Networks and Distributed Computing + * Computing & Communications + * University of Washington + * Administration Building, AG-44 + * Seattle, WA 98195 + * Internet: MRC@CAC.Washington.EDU + * + * Date: 26 May 1995 + * Last Edited: 30 August 2006 + */ + +/* The whole purpose of this unfortunate routine is to deal with DOS and + * certain cretinous versions of UNIX which decided that the "bytes actually + * written" return value from write() gave them license to use that for things + * that are really errors, such as disk quota exceeded, maximum file size + * exceeded, disk full, etc. + * + * BSD won't screw us this way on the local filesystem, but who knows what + * some NFS-mounted filesystem will do. + */ + +#undef write + +/* Write data to file + * Accepts: file descriptor + * I/O vector structure + * number of vectors in structure + * Returns: number of bytes written if successful, -1 if failure + */ + +long maxposint = (long)((((unsigned long) 1) << ((sizeof(int) * 8) - 1)) - 1); + +long safe_write (int fd,char *buf,long nbytes) +{ + long i,j; + if (nbytes > 0) for (i = nbytes; i; i -= j,buf += j) { + while (((j = write (fd,buf,(int) min (maxposint,i))) < 0) && + (errno == EINTR)); + if (j < 0) return j; + } + return nbytes; +} |