diff options
Diffstat (limited to 'imap/imapd.c')
| -rw-r--r-- | imap/imapd.c | 6665 |
1 files changed, 6665 insertions, 0 deletions
diff --git a/imap/imapd.c b/imap/imapd.c new file mode 100644 index 0000000..7a61e2b --- /dev/null +++ b/imap/imapd.c @@ -0,0 +1,6665 @@ +/* +** Copyright 1998 - 2011 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <fcntl.h> +#include <pwd.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif +#if HAVE_UTIME_H +#include <utime.h> +#endif +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif +#if HAVE_LOCALE_H +#include <locale.h> +#endif +#if HAVE_SYS_UTSNAME_H +#include <sys/utsname.h> +#endif + +#include <courierauth.h> +#include "maildir/maildiraclt.h" +#include "maildir/maildirnewshared.h" + +#include <sys/types.h> +#include <sys/stat.h> + +#include "imaptoken.h" +#include "imapwrite.h" +#include "imapscanclient.h" + +#include "mysignal.h" +#include "imapd.h" +#include "fetchinfo.h" +#include "searchinfo.h" +#include "storeinfo.h" +#include "mailboxlist.h" +#include "thread.h" +#include "outbox.h" + +#include "maildir/config.h" +#include "maildir/maildiraclt.h" +#include "maildir/maildircreate.h" +#include "maildir/maildirrequota.h" +#include "maildir/maildirgetquota.h" +#include "maildir/maildirquota.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildirwatch.h" +#include "maildir/maildirkeywords.h" +#include "maildir/maildirinfo.h" +#include "maildir/loginexec.h" + +#include "unicode/unicode.h" +#include "maildir/maildirkeywords.h" +#include "courierauth.h" + +#define KEYWORD_IMAPVERBOTTEN " (){%*\"\\]" +#define KEYWORD_SMAPVERBOTTEN "," + +extern time_t rfc822_parsedt(const char *); +extern void fetchflags(unsigned long); +extern unsigned long header_count, body_count; +extern time_t start_time; +extern void smap(); +extern void smap_fetchflags(unsigned long); + +extern int do_fetch(unsigned long, int, void *); +extern void fetch_free_cached(); +extern int keywords(); +extern int fastkeywords(); +extern void imapscanfail(const char *); +extern void bye_msg(const char *); + +extern void mainloop(); +extern void initcapability(); +extern void imapcapability(); +extern int magictrash(); + +#if SMAP +int smapflag=0; + +extern void snapshot_save(); +extern void snapshot_needed(); + +#endif + +static const char *protocol; + +char *dot_trash = "." TRASH; +char *trash = TRASH; + +char *current_mailbox=0; /* .folder */ +FILE *debugfile=0; +#if 0 +char *imapscanpath; +#endif + +int current_temp_fd=-1; +const char *current_temp_fn=NULL; + +struct imapscaninfo current_maildir_info; +int current_mailbox_ro; +char *current_mailbox_acl; + +dev_t homedir_dev; +ino_t homedir_ino; + +void rfc2045_error(const char *p) +{ + if (write(2, p, strlen(p)) < 0) + _exit(1); + _exit(0); +} + + +extern int maildirsize_read(const char *,int *,off_t *,unsigned *,unsigned *,struct stat *); + +int maildir_info_suppress(const char *maildir) +{ + struct stat stat_buf; + + if (stat(maildir, &stat_buf) < 0 || + /* maildir inaccessible, perhaps another server? */ + + (stat_buf.st_dev == homedir_dev && + stat_buf.st_ino == homedir_ino) + /* Exclude ourselves from the shared list */ + + ) + { + return 1; + } + + return 0; +} + + +void quotainfo_out(const char* qroot) +{ + char quotabuf[QUOTABUFSIZE]; + char qresult[200]=""; + char qbuf[200]; + + if ((maildir_getquota(".", quotabuf) == 0) && (strcmp(qroot,"ROOT") == 0)) + { + struct maildirsize quotainfo; + + if (maildir_openquotafile("ainfo, ".") == 0) + maildir_closequotafile("ainfo); + else + quotainfo.quota.nbytes=quotainfo.size.nbytes= + quotainfo.quota.nmessages= + quotainfo.size.nmessages=0; + + if (quotainfo.quota.nbytes > 0) + { + sprintf(qbuf,"STORAGE %ld %ld", + (long)((quotainfo.size.nbytes+1023)/1024), + (long)((quotainfo.quota.nbytes+1023)/1024)); + strcat(qresult,qbuf); + } + if (quotainfo.quota.nmessages > 0) + { + sprintf(qbuf,"MESSAGE %d %d", + quotainfo.size.nmessages, + quotainfo.quota.nmessages); + if (strcmp(qresult,"")!=0) strcat(qresult," "); + strcat(qresult,qbuf); + } + } + + writes("* "); + writes("QUOTA \""); + writes(qroot); + writes("\""); + if (strcmp(qresult,"")!=0) + { + writes(" ("); + writes(qresult); + writes(")"); + }; + writes("\r\n"); +} + +int is_trash(const char *m) +{ + if (strcmp(m, dot_trash)) + { + /* + * not trying to delete .Trash but folder inside of .Trash + */ + return (0); + } + else + { + /* + * trying to delete .Trash - stop them + */ + return (1); + } +} + +void emptytrash() +{ + char *dir, *all_settings, *next_folder, *folder, *p; + unsigned l; + + all_settings=getenv("IMAP_EMPTYTRASH"); + + if (!all_settings) + return; + + all_settings=strdup(all_settings); + if (!all_settings) + return; + + if (strchr(all_settings, ':') == 0 && + strchr(all_settings, ',') == 0) + { + l=atoi(all_settings); + + if (l <= 0) + l=1; + + maildir_getnew(".", trash, NULL, NULL); + if ((dir=maildir_folderdir(".", trash))) + { + maildir_purge(dir, l * 24 * 60 * 60); + free(dir); + } + free(all_settings); + return; + } + + for (folder=all_settings; folder && *folder; ) + { + if (*folder == ',') + { + ++folder; + continue; + } + next_folder=strchr(folder, ','); + if (next_folder) + *next_folder++=0; + + p=strchr(folder, ':'); + if (!p) + { + folder=next_folder; + continue; + } + + *p++=0; + + l=atoi(p); + if (l <= 0) l=1; + + maildir_getnew(".", folder, NULL, NULL); + if ((dir=maildir_folderdir(".", folder))) + { + maildir_purge(dir, l * 24 * 60 * 60); + free(dir); + } + folder=next_folder; + } + free(all_settings); +} + +#if 0 +int is_draft(const char *m) +{ +#if 1 + /* Fix some PINE bugs first */ + + if (strcmp(m, "." DRAFTS)) return (0); + return (1); +#else + return (0); +#endif +} +#endif + +int is_reserved(const char *m) +{ + if (strncmp(m, "./", 2) == 0) m += 2; + + if (is_trash(m)) return (1); + return (0); +} + +int is_reserved_name(const char *name) +{ + if (strncmp(name, INBOX, strlen(INBOX)) == 0) + return is_trash(name+strlen(INBOX)); + return 0; +} + +char *decode_valid_mailbox(const char *p, int autosubscribe) +{ + struct maildir_info mi; + char *q, *r; + + if (maildir_info_imap_find(&mi, p, getenv("AUTHENTICATED")) < 0) + { + return NULL; + } + + if (mi.homedir && mi.maildir) + { + q=maildir_name2dir(mi.homedir, mi.maildir); + + if (q) + { + r=malloc(strlen(q)+sizeof("/.")); + if (!r) write_error_exit(0); + strcat(strcpy(r, q), "/."); + if (access(r, 0) == 0) + { + free(r); + maildir_info_destroy(&mi); + return q; + } + free(r); + free(q); + } + maildir_info_destroy(&mi); + return NULL; + } + + if (mi.mailbox_type == MAILBOXTYPE_OLDSHARED) + { + const char *q; + char *r; + + if ((q=strchr(p, '.')) == NULL) + { + maildir_info_destroy(&mi); + errno=EINVAL; + return NULL; + } + + r=maildir_shareddir(".", q+1); + if (!r) + { + maildir_info_destroy(&mi); + errno=EINVAL; + return NULL; + } + + if (access(r, 0) == 0) + { + maildir_info_destroy(&mi); + return r; + } + + maildir_shared_subscribe(".", q+1); + if (access(r, 0) == 0) + { + maildir_info_destroy(&mi); + return r; + } + + free(r); + maildir_info_destroy(&mi); + return NULL; + } + maildir_info_destroy(&mi); + return (NULL); +} + +static time_t decode_date_time(char *p) +{ +unsigned i; + + /* Convert to format rfc822_parsedt likes */ + + for (i=1; p[i] != ' '; i++) + { + if (!p[i]) return (0); + if (p[i] == '-') p[i]=' '; + } + return (rfc822_parsedt(p)); +} + +int get_flagname(const char *p, struct imapflags *flags) +{ + if (strcasecmp(p, "\\SEEN") == 0) + flags->seen=1; + else if (strcasecmp(p, "\\ANSWERED") == 0) + flags->answered=1; + else if (strcasecmp(p, "\\DRAFT") == 0) + flags->drafts=1; + else if (strcasecmp(p, "\\DELETED") == 0) + flags->deleted=1; + else if (strcasecmp(p, "\\FLAGGED") == 0) + flags->flagged=1; + else return (-1); + return (0); +} + +int valid_keyword(const char *kw) +{ + const char *p; + + if (!keywords()) + return 0; + + /* Check for valid keyword names */ + + for (p=kw; *p; p++) + { + if ((unsigned char)*p <= ' ' + || strchr(KEYWORD_IMAPVERBOTTEN, *p)) + return 0; + } + return 1; +} + +int get_keyword(struct libmail_kwMessage **kwPtr, const char *kw) +{ + if (libmail_kwmSetName(current_maildir_info.keywordList, *kwPtr, kw) < 0) + write_error_exit(0); + + return 0; +} + + +int get_flagsAndKeywords(struct imapflags *flags, + struct libmail_kwMessage **kwPtr) +{ +struct imaptoken *t; + + while ((t=nexttoken_nouc())->tokentype == IT_ATOM) + { + if (get_flagname(t->tokenbuf, flags)) + { + if (!valid_keyword(t->tokenbuf)) + return -1; + + if (get_keyword(kwPtr, t->tokenbuf)) + return -1; + } + } + return (t->tokentype == IT_RPAREN ? 0:-1); +} + +void get_message_flags( + struct imapscanmessageinfo *mi, + char *buf, struct imapflags *flags) +{ + const char *filename=mi->filename; + + const char *DRAFT="\\Draft"; + const char *FLAGGED="\\Flagged"; + const char *REPLIED="\\Answered"; + const char *SEEN="\\Seen"; + const char *DELETED="\\Deleted"; + const char *RECENT="\\Recent"; + + const char *SPC=" "; + + if (buf) + *buf=0; + + if (flags) + flags->seen=flags->answered=flags->deleted=flags->flagged + =flags->recent=flags->drafts=0; + + if ((filename=strrchr(filename, MDIRSEP[0])) == 0 + || strncmp(filename, MDIRSEP "2,", 3)) return; + +#if SMAP + if (smapflag) + { + SPC=","; + DRAFT="DRAFT"; + FLAGGED="MARKED"; + REPLIED="REPLIED"; + SEEN="SEEN"; + DELETED="DELETED"; + RECENT="RECENT"; + } +#endif + + if (strchr(filename, 'D')) + { + if (buf) strcat(buf, DRAFT); + if (flags) flags->drafts=1; + } + + if (strchr(filename, 'F')) + { + if (buf) strcat(strcat(buf, *buf ? SPC:""), FLAGGED); + if (flags) flags->flagged=1; + } + if (strchr(filename, 'R')) + { + if (buf) strcat(strcat(buf, *buf ? SPC:""), REPLIED); + if (flags) flags->answered=1; + } + + if (strchr(filename, 'S') != NULL) + { + if (buf) strcat(strcat(buf, *buf ? SPC:""), SEEN); + if (flags) flags->seen=1; + } + + if (strchr(filename, 'T')) + { + if (buf) strcat(strcat(buf, *buf ? SPC:""), DELETED); + if (flags) flags->deleted=1; + } + + if (mi->recentflag) + { + if (flags) flags->recent=1; + if (buf) strcat(strcat(buf, *buf ? SPC:""), RECENT); + } +} + +static char *parse_mailbox_error(const char *tag, + struct imaptoken *curtoken, + int ok_hierarchy, /* RFC 2060 errata - DELETE can take + ** a trailing hierarchy separator if the + ** IMAP server supports subfolders of + ** a real folder (such as this one). + */ + + int autosubscribe) /* Really DUMP clients that do a LIST, + ** and don't bother to check if a folder is + ** subscribed to, or not (Pine) + */ +{ +char *mailbox; + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + mailbox=0; + } + else + { + if (ok_hierarchy && (mailbox=strrchr(curtoken->tokenbuf, + HIERCH)) && mailbox[1] == 0) + *mailbox=0; + + mailbox=decode_valid_mailbox(curtoken->tokenbuf, + autosubscribe); + } + + if ( mailbox == 0) + { + writes(tag); + writes(" NO Mailbox does not exist, or must be subscribed to.\r\n"); + return (0); + } + return (mailbox); +} + +/* + STORE NEW MESSAGE INTO A MAILBOX +*/ + +void append_flags(char *buf, struct imapflags *flags) +{ + if (flags->drafts) strcat(buf, "D"); + if (flags->flagged) strcat(buf, "F"); + if (flags->answered) strcat(buf, "R"); + if (flags->seen) strcat(buf, "S"); + if (flags->deleted) strcat(buf, "T"); +} + + /* First, figure out the filenames used in tmp and new */ + +FILE *maildir_mkfilename(const char *mailbox, struct imapflags *flags, + unsigned long s, char **tmpname, char **newname) +{ + char *p; + char uniqbuf[80]; + static unsigned uniqcnt=0; + FILE *fp; + struct maildir_tmpcreate_info createInfo; + + sprintf(uniqbuf, "%u", uniqcnt++); + + maildir_tmpcreate_init(&createInfo); + createInfo.openmode=0666; + createInfo.maildir=mailbox; + createInfo.uniq=uniqbuf; + createInfo.msgsize=s; + createInfo.hostname=getenv("HOSTNAME"); + createInfo.doordie=1; + + if ((fp=maildir_tmpcreate_fp(&createInfo)) == NULL) + return NULL; + + *tmpname=createInfo.tmpname; + *newname=createInfo.newname; + + createInfo.tmpname=NULL; + createInfo.newname=NULL; + maildir_tmpcreate_free(&createInfo); + + strcpy(uniqbuf, MDIRSEP "2,"); + append_flags(uniqbuf, flags); + + /* Ok, this message will really go to cur, not new */ + + p=malloc(strlen(*newname)+strlen(uniqbuf)+1); + if (!p) write_error_exit(0); + strcpy(p, *newname); + memcpy(strrchr(p, '/')-3, "cur", 3); /* HACK OF THE MILLENIA */ + strcat(p, uniqbuf); + free(*newname); + *newname=p; + return fp; +} + +void set_time(const char *tmpname, time_t timestamp) +{ +#if HAVE_UTIME + if (timestamp) + { + struct utimbuf ub; + + ub.actime=ub.modtime=timestamp; + utime(tmpname, &ub); + } +#else +#if HAVE_UTIMES + if (timestamp) + { + struct timeval tv; + + tv.tv_sec=timestamp; + tv.tv_usec=0; + utimes(tmpname, &tv); + } +#endif +#endif +} + +static int store_mailbox(const char *tag, const char *mailbox, + struct imapflags *flags, + struct libmail_kwMessage *keywords, + time_t timestamp, + unsigned long nbytes, + unsigned long *new_uidv, + unsigned long *new_uid) +{ +char *tmpname; +char *newname; +char *p; +char *e; +FILE *fp; +unsigned long n; +static const char nowrite[]=" NO [ALERT] Cannot create message - no write permission or out of disk space.\r\n"; +int lastnl; +int rb; + + fp=maildir_mkfilename(mailbox, flags, 0, &tmpname, &newname); + + if (!fp) + { + writes(tag); + writes(nowrite); + return -1; + } + + writes("+ OK\r\n"); + writeflush(); + lastnl=0; + + current_temp_fd=fileno(fp); + current_temp_fn=tmpname; + + while (nbytes) + { + read_string(&p, &n, nbytes); + nbytes -= n; + if (p[n-1] == '\n') lastnl = 1; + else lastnl = 0; + + while (n) + { + e = memchr(p, '\r', n); + if (e && p == e) + { + rb=1; + } + else if (e) + { + rb = fwrite(p, 1, e-p, fp); + } + else + { + rb = fwrite(p, 1, n, fp); + } + n -= rb; + p += rb; + } + } + if (!lastnl) putc('\n', fp); + + current_temp_fd=-1; + current_temp_fn=NULL; + + if (fflush(fp) || ferror(fp)) + { + fprintf(stderr, + "ERR: error storing a message, user=%s, errno=%d\n", + getenv("AUTHENTICATED"), errno); + + fclose(fp); + unlink(tmpname); + writes(tag); + writes(nowrite); + free(tmpname); + free(newname); + return (-1); + } + + nbytes=ftell(fp); + if (nbytes == (unsigned long)-1 || + (p=maildir_requota(newname, nbytes)) == 0) + + { + fclose(fp); + unlink(tmpname); + writes(tag); + writes(nowrite); + free(tmpname); + free(newname); + return (-1); + } + + free(newname); + + fclose(fp); + + if (maildirquota_countfolder(mailbox) && + maildirquota_countfile(p)) + { + struct maildirsize quotainfo; + + if (maildir_quota_add_start(mailbox, "ainfo, nbytes, 1, + getenv("MAILDIRQUOTA"))) + { + unlink(tmpname); + free(tmpname); + free(p); + writes(tag); + writes(" NO [ALERT] You exceeded your mail quota.\r\n"); + return (-1); + } + maildir_quota_add_end("ainfo, nbytes, 1); + } + + if (check_outbox(tmpname, mailbox)) + { + unlink(tmpname); + writes(tag); + writes(" NO [ALERT] Unable to send E-mail message.\r\n"); + free(tmpname); + free(p); + return (-1); + } + + { + struct imapscaninfo new_maildir_info; + struct uidplus_info new_uidplus_info; + int rc; + + imapscan_init(&new_maildir_info); + + memset(&new_uidplus_info, 0, sizeof(new_uidplus_info)); + + new_uidplus_info.tmpfilename=tmpname; + new_uidplus_info.curfilename=p; + new_uidplus_info.mtime=timestamp; + + if (keywords && keywords->firstEntry) + { + if (maildir_kwSave(mailbox, + strrchr(p, '/')+1, + keywords, + &new_uidplus_info.tmpkeywords, + &new_uidplus_info.newkeywords, + 0)) + { + unlink(tmpname); + writes(tag); + writes(" NO [ALERT] "); + writes(strerror(errno)); + free(tmpname); + free(p); + return (-1); + } + } + + rc=imapscan_maildir(&new_maildir_info, mailbox, 0, 0, + &new_uidplus_info); + + if (new_uidplus_info.tmpkeywords) + free(new_uidplus_info.tmpkeywords); + + if (new_uidplus_info.newkeywords) + free(new_uidplus_info.newkeywords); + + if (rc) + { + free(tmpname); + free(p); + writes(tag); + writes(nowrite); + return -1; + } + + *new_uidv=new_maildir_info.uidv; + *new_uid=new_uidplus_info.uid; + imapscan_free(&new_maildir_info); + } + + free(tmpname); + free(p); + return (0); +} + + +/************** Create and delete folders **************/ + +#if 0 +static int checksubdir(const char *s) +{ +DIR *dirp; +struct dirent *de; + + dirp=opendir(s); + while (dirp && (de=readdir(dirp)) != 0) + if (de->d_name[0] != '.') + { + closedir(dirp); + return (1); + } + if (dirp) closedir(dirp); + return (0); +} +#endif + +int mddelete(const char *s) +{ + int rc; + struct stat stat_buf; + + /* If the top level maildir is a sym link, don't delete it */ + + if (stat(s, &stat_buf) < 0 && + S_ISLNK(stat_buf.st_mode)) + return -1; + + trap_signals(); + rc=maildir_del(s); + if (release_signals()) _exit(0); + return (rc); +} + +int mdcreate(const char *mailbox) +{ + trap_signals(); + if (maildir_make(mailbox, 0700, 0700, 1) < 0) + { + if (release_signals()) _exit(0); + return (-1); + } + + if (release_signals()) _exit(0); + return (0); +} + +/****************************************************************************/ + +/* do_msgset parses a message set, and calls a processing func for each msg */ + +/****************************************************************************/ + +static int do_msgset(const char *msgset, + int (*msgfunc)(unsigned long, int, void *), + void *msgfunc_arg, int isuid) +{ +unsigned long i, j; +int rc; +unsigned long last=0; + + if (current_maildir_info.nmessages > 0) + { + last=current_maildir_info.nmessages; + if (isuid) + { + last=current_maildir_info.msgs[last-1].uid; + } + } + + while (isdigit((int)(unsigned char)*msgset) || *msgset == '*') + { + i=0; + if (*msgset == '*') + { + i=last; + ++msgset; + } + else while (isdigit((int)(unsigned char)*msgset)) + { + i=i*10 + (*msgset++-'0'); + } + if (*msgset != ':') + j=i; + else + { + j=0; + ++msgset; + if (*msgset == '*') + { + j=last; + ++msgset; + } + else while (isdigit((int)(unsigned char)*msgset)) + { + j=j*10 + (*msgset++-'0'); + } + } + if (j < i) + { +#if 0 + /* BUGS issue */ + writes("* NO Invalid message set: "); + writen(i); + writes(":"); + writen(j); + writes("\r\n"); +#endif + } + else if (isuid) + { + unsigned long k; + + for (k=0; k<current_maildir_info.nmessages; k++) + if (current_maildir_info.msgs[k].uid >= i) + break; + if (k >= current_maildir_info.nmessages || + current_maildir_info.msgs[k].uid > j) + { +#if 0 + /* BUGS issue */ + writes("* NO Invalid message: UID "); + writen(i); + if (j > i) + { + writes(":"); + writen(j); + } + writes("\r\n"); +#endif + } + else while (k < current_maildir_info.nmessages && + current_maildir_info.msgs[k].uid <= j) + { + if ((rc=(*msgfunc)(k+1, 1, msgfunc_arg)) != 0) + return (rc); + ++k; + } + } + else + { + do + { + if (i == 0 || + i > current_maildir_info.nmessages) + { + writes("* NO Invalid message sequence number: "); + writen(i); + writes("\r\n"); + break; + } + + if ((rc=(*msgfunc)(i, 0, msgfunc_arg)) != 0) + return (rc); + } while (i++ < j); + } + + if (*msgset++ != ',') break; + } + return (0); +} + +/** Show currently defined flags and keywords **/ + +static int write_keyword_name(struct libmail_keywordEntry *, void *); + +static void mailboxflags(int ro) +{ +#if SMAP + if (smapflag) + return; +#endif + + writes("* FLAGS ("); + + if (current_maildir_info.keywordList) + { + void (*writefunc)(const char *)=writes; + + libmail_kwEnumerate(current_maildir_info.keywordList, + &write_keyword_name, &writefunc); + } + + writes("\\Draft \\Answered \\Flagged" + " \\Deleted \\Seen \\Recent)\r\n"); + writes("* OK [PERMANENTFLAGS ("); + + + if (ro) + { + writes(")] No permanent flags permitted\r\n"); + } + else + { + if (current_maildir_info.keywordList) + { + void (*writefunc)(const char *)=writes; + + libmail_kwEnumerate(current_maildir_info + .keywordList, + &write_keyword_name, + &writefunc); + } + + if (keywords()) + writes("\\* "); + + writes("\\Draft \\Answered \\Flagged \\Deleted \\Seen)] Limited\r\n"); + } +} + +static int write_keyword_name(struct libmail_keywordEntry *kw, void *dummy) +{ + void (**writefunc)(const char *)=(void (**)(const char *))dummy; + + (**writefunc)(keywordName(kw)); + (**writefunc)(" "); + return 0; +} + +/****************************************************************************/ + +/* Show how many messages are in the mailbox */ + +/****************************************************************************/ + +static void mailboxmetrics() +{ +unsigned long i,j; + +#if SMAP + if (smapflag) + { + writes("* EXISTS "); + writen(current_maildir_info.nmessages); + writes("\n"); + return; + } +#endif + + + writes("* "); + writen(current_maildir_info.nmessages); + writes(" EXISTS\r\n"); + + writes("* "); + i=0; + + for (j=0; j<current_maildir_info.nmessages; j++) + if (current_maildir_info.msgs[j].recentflag) + ++i; + writen(i); + writes(" RECENT\r\n"); +} + +/****************************************************************************/ + +/* Do the NOOP stuff */ + +/****************************************************************************/ + +struct noopKeywordUpdateInfo { + struct libmail_kwHashtable *keywordList; + struct libmail_kwMessage *messagePtr; +}; + +static int noopAddKeyword(struct libmail_keywordEntry *ke, void *vp) +{ + struct noopKeywordUpdateInfo *kui= + (struct noopKeywordUpdateInfo *)vp; + + libmail_kwmSetName(kui->keywordList, kui->messagePtr, keywordName(ke)); + /* + ** ke originates from a different keyword namespace, so must use + ** its name. + */ + return 0; +} + +void doNoop(int real_noop) +{ + struct imapscaninfo new_maildir_info; + unsigned long i, j; + int needstats=0; + unsigned long expunged; +#if SMAP + unsigned long smap_expunge_count=0; + unsigned long smap_expunge_bias=0; + + unsigned long smap_last=0; + unsigned long smap_range=0; + + int takeSnapshot=1; +#endif + struct noopKeywordUpdateInfo kui; + + imapscan_init(&new_maildir_info); + + if (imapscan_maildir(&new_maildir_info, current_mailbox, 0, + current_mailbox_ro, NULL)) + return; + + j=0; + expunged=0; + for (i=0; i<current_maildir_info.nmessages; i++) + { + struct libmail_kwMessage *a, *b; + + while (j < new_maildir_info.nmessages && + new_maildir_info.msgs[j].uid < + current_maildir_info.msgs[i].uid) + { + /* How did this happen??? */ + + new_maildir_info.msgs[j].changedflags=1; + ++j; + needstats=1; +#if SMAP + takeSnapshot=0; +#endif + } + + if (j >= new_maildir_info.nmessages || + new_maildir_info.msgs[j].uid > + current_maildir_info.msgs[i].uid) + { +#if SMAP + if (smapflag) + { + takeSnapshot=0; + + if (smap_expunge_count > 100) + { + if (smap_range > 0) + { + writes("-"); + writen(smap_last + smap_range + - smap_expunge_bias + + 1); + } + writes("\n"); + smap_expunge_count=0; + } + + if (smap_expunge_count == 0) + { + smap_expunge_bias=expunged; + + writes("* EXPUNGE "); + writen(i+1-smap_expunge_bias); + smap_last=i; + smap_range=0; + } + else if (smap_last + smap_range + 1 == i) + { + ++smap_range; + } + else + { + if (smap_range > 0) + { + writes("-"); + writen(smap_last + smap_range + - smap_expunge_bias + + 1); + } + writes(" "); + writen(i+1-smap_expunge_bias); + smap_last=i; + smap_range=0; + } + ++smap_expunge_count; + } + else +#endif + { + writes("* "); + writen(i+1-expunged); + writes(" EXPUNGE\r\n"); + needstats=1; + } + + ++expunged; + continue; + } + + /* Must be the same */ + + a=current_maildir_info.msgs[i].keywordMsg; + b=new_maildir_info.msgs[j].keywordMsg; + + if (strcmp(current_maildir_info.msgs[i].filename, + new_maildir_info.msgs[j].filename) || + (a && !b) || (!a && b) || + (a && b && libmail_kwmCmp(a, b))) + { + new_maildir_info.msgs[j].changedflags=1; +#if SMAP + takeSnapshot=0; +#endif + } + if (current_maildir_info.msgs[i].recentflag) + new_maildir_info.msgs[j].recentflag=1; +#if SMAP + if (smapflag) + new_maildir_info.msgs[j].recentflag=0; +#endif + new_maildir_info.msgs[j].copiedflag= + current_maildir_info.msgs[i].copiedflag; + ++j; + } + +#if SMAP + if (smapflag && smap_expunge_count) + { + if (smap_range > 0) + { + writes("-"); + writen(smap_last + smap_range - smap_expunge_bias + 1); + } + writes("\n"); + } + +#endif + + while (j < new_maildir_info.nmessages) + { +#if SMAP + if (smapflag) + takeSnapshot=0; +#endif + ++j; + needstats=1; + } + + new_maildir_info.keywordList->keywordAddedRemoved=0; + + + /********************************************************** + ** + ** current_maildir_info: existing keywords + ** new_maildir_info: new keywords + ** + ** Process new/deleted keywords as follows: + */ + + /* + ** 1. Make sure that the old keyword list includes any new keywords. + */ + + kui.keywordList=current_maildir_info.keywordList; + kui.messagePtr=libmail_kwmCreate(); + + if (!kui.messagePtr) + write_error_exit(0); + + current_maildir_info.keywordList->keywordAddedRemoved=0; + libmail_kwEnumerate(new_maildir_info.keywordList, + noopAddKeyword, &kui); + + if (current_maildir_info.keywordList->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); + libmail_kwmDestroy(kui.messagePtr); + + + /* + ** 2. Temporarily add all existing keywords to the new keyword list. + */ + + kui.keywordList=new_maildir_info.keywordList; + kui.messagePtr=libmail_kwmCreate(); + + if (!kui.messagePtr) + write_error_exit(0); + libmail_kwEnumerate(current_maildir_info.keywordList, + noopAddKeyword, &kui); + + imapscan_copy(¤t_maildir_info, &new_maildir_info); + imapscan_free(&new_maildir_info); + +#if SMAP + if (takeSnapshot) + { + if (real_noop && smapflag) + snapshot_save(); + } + else + snapshot_needed(); +#endif + + if (needstats) + mailboxmetrics(); + + for (j=0; j < current_maildir_info.nmessages; j++) + if (current_maildir_info.msgs[j].changedflags) + { +#if SMAP + if (smapflag) + smap_fetchflags(j); + else +#endif + fetchflags(j); + } + + /* + ** After sending changed flags to the client, see if any keywords + ** have gone away. + */ + + current_maildir_info.keywordList->keywordAddedRemoved=0; + libmail_kwmDestroy(kui.messagePtr); + if (current_maildir_info.keywordList->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); +} + +static char *alloc_filename(const char *mbox, const char *name) +{ +char *p=malloc(strlen(mbox)+strlen(name)+sizeof("/cur/")); + + if (!p) write_error_exit(0); + + strcat(strcat(strcpy(p, mbox), "/cur/"), name); + return (p); +} + +/****************************************************************************/ + +/* Do the ID stuff */ + +/****************************************************************************/ +static int doId() +{ + const char *ev = getenv("IMAP_ID_FIELDS"); + unsigned int flags=0; + struct imaptoken *curtoken; + + if (!ev) + return -1; + + flags = atoi(ev); + + /* The data sent by the client isn't used for anything, but make sure + * it is syntactically correct. */ + curtoken = nexttoken(); + switch (curtoken->tokentype) { + case IT_NIL: + break; + case IT_LPAREN: + { + unsigned int k = 0; + + curtoken = nexttoken(); + + fprintf(stderr, "INFO: ID, user=%s, ip=[%s]", + getenv("AUTHENTICATED"), + getenv("TCPREMOTEIP")); + + while ((k < 30) && (curtoken->tokentype != IT_RPAREN)) { + k++; + if (curtoken->tokentype != IT_QUOTED_STRING) + { + fprintf(stderr, "\n"); + fflush(stderr); + return -1; + } + fprintf(stderr, ", %s=", curtoken->tokenbuf); + + curtoken = nexttoken(); + if ((curtoken->tokentype != IT_QUOTED_STRING) && + (curtoken->tokentype != IT_NIL)) + { + fprintf(stderr, "\n"); + fflush(stderr); + return -1; + } + fprintf(stderr, "%s", + curtoken->tokentype == IT_QUOTED_STRING + ? curtoken->tokenbuf:"(nil)"); + curtoken = nexttoken(); + } + fprintf(stderr, "\n"); + fflush(stderr); + + /* no strings sent */ + if (k == 0) + return -1; + + /* at most 30 pairs allowed */ + if ((k >= 30) && (curtoken->tokentype != IT_RPAREN)) + return -1; + + break; + } + default: + return -1; + } + + writes("* ID (\"name\" \"Courier-IMAP"); + + if (flags & 1) { + const char *sp = strchr(PROGRAMVERSION, ' ') + 1; + writes("\" \"version\" \""); + writemem(sp, strchr(sp, '/') - sp); + } + +#if HAVE_SYS_UTSNAME_H + if (flags & 6) { + struct utsname uts; + if (uname(&uts) == 0) + { + if (flags & 2) { + writes("\" \"os\" \""); + writeqs(uts.sysname); + } + if (flags & 4) { + writes("\" \"os-version\" \""); + writeqs(uts.release); + } + + } + } +#endif + + writes("\" \"vendor\" \"Double Precision, Inc.\")\r\n"); + + return 0; +} + +/****************************************************************************/ + +/* Do the IDLE stuff */ + +/****************************************************************************/ + +extern int doidle(time_t, int); + +int imapenhancedidle(void) +{ + struct maildirwatch *w; + struct maildirwatch_contents c; + int rc; + int started=0; + + if (!current_mailbox) + return (-1); + + if ((w=maildirwatch_alloc(current_mailbox)) == NULL) + { + perror(current_mailbox); + fprintf(stderr, "This may be a problem with FAM or Gamin\n"); + return (-1); + } + + rc=maildirwatch_start(w, &c); + + if (rc < 0) + { + perror("maildirwatch_start"); + maildirwatch_free(w); + return (-1); + } + + if (rc > 0) + { + maildirwatch_free(w); + return (-1); /* Fallback */ + } + +#if SMAP + if (smapflag) + { + writes("+OK Entering ENHANCED idle mode\n"); + } + else +#endif + writes("+ entering ENHANCED idle mode\r\n"); + writeflush(); + + for (;;) + { + if (!started) + { + int fd; + int rc; + + rc=maildirwatch_started(&c, &fd); + + if (rc > 0) + { + started=1; + doNoop(0); + writeflush(); + } + else + { + if (rc < 0) + perror("maildirwatch_started"); + if (doidle(60, fd)) + break; + } + continue; + } + + if (started < 0) /* Error fallback mode*/ + { + if (doidle(60, -1)) + break; + } + else + { + int changed; + int fd; + int timeout; + + if (maildirwatch_check(&c, &changed, &fd, &timeout) + == 0) + { + if (!changed) + { + if (doidle(timeout, fd)) + break; + continue; + } + + maildirwatch_end(&c); + doNoop(0); + + rc=maildirwatch_start(w, &c); + + if (rc < 0) + { + perror("maildirwatch_start"); + started= -1; + } + else + started=0; + } + else + { + started= -1; + } + } + + doNoop(0); + writeflush(); + } + + maildirwatch_end(&c); + maildirwatch_free(w); + return (0); +} + +void imapidle(void) +{ + const char * envp = getenv("IMAP_IDLE_TIMEOUT"); + const int idleTimeout = envp ? atoi(envp) : 60; + +#if SMAP + if (smapflag) + { + writes("+OK Entering idle mode...\n"); + } + else +#endif + writes("+ entering idle mode\r\n"); + if (current_mailbox) + doNoop(0); + writeflush(); + while (!doidle(idleTimeout, -1)) + { + if (current_mailbox) + doNoop(0); + writeflush(); + } +} + +/****************************************************************************/ + +/* Do the EXPUNGE stuff */ + +/****************************************************************************/ + +void do_expunge(unsigned long from, unsigned long to, int force); + +static int uid_expunge(unsigned long msgnum, int uidflag, void *void_arg) +{ + do_expunge(msgnum-1, msgnum, 0); + return 0; +} + +void expunge() +{ + do_expunge(0, current_maildir_info.nmessages, 0); +} + +void do_expunge(unsigned long expunge_start, + unsigned long expunge_end, + int force) +{ +unsigned long j; +struct imapflags flags; +char *old_name; +int move_to_trash=0; +struct stat stat_buf; +const char *cp=getenv("IMAP_LOG_DELETIONS"); +int log_deletions= cp && *cp != '0'; + + fetch_free_cache(); + + if (magictrash() && + !is_trash(strncmp(current_mailbox, "./", 2) == 0? + current_mailbox+2:current_mailbox)) + move_to_trash=1; + + for (j=expunge_start; j < expunge_end; j++) + { + int file_counted=0; + + get_message_flags(current_maildir_info.msgs+j, 0, &flags); + + if (!flags.deleted && !force) + continue; + + old_name=alloc_filename(current_mailbox, + current_maildir_info.msgs[j].filename); + + if (stat(old_name, &stat_buf) < 0) + { + free(old_name); + continue; + } + + if (maildirquota_countfolder(current_mailbox) && + maildirquota_countfile(old_name)) + file_counted=1; + + if (is_sharedsubdir(current_mailbox)) + { + maildir_unlinksharedmsg(old_name); + } + else if (move_to_trash && current_maildir_info + .msgs[j].copiedflag == 0) + { + char *new_name; + char *p; + int will_count=0; + + new_name=alloc_filename(dot_trash, + current_maildir_info.msgs[j].filename); + + if (maildirquota_countfolder(dot_trash)) + will_count=1; + + if (file_counted != will_count) + { + unsigned long filesize=0; + + if (maildir_parsequota(old_name, &filesize)) + { + if (stat(old_name, &stat_buf)) + stat_buf.st_size=0; + filesize=(unsigned long) + stat_buf.st_size; + } + + maildir_quota_deleted(".", + (long)filesize * + (will_count + -file_counted), + will_count + -file_counted); + } + + if ((p=strrchr(new_name, '/')) && + (p=strrchr(p, MDIRSEP[0])) && + strncmp(p, MDIRSEP "2,", 3) == 0) + { + char *q; + + /* Don't mark it as deleted in the Trash */ + + if ((q=strchr(p, 'T')) != NULL) + while ((*q=q[1]) != 0) + ++q; + + /* Don't mark it as a draft msg in the Trash */ + + if ((q=strchr(p, 'D')) != NULL) + while ((*q=q[1]) != 0) + ++q; + } + + if (log_deletions) + fprintf(stderr, "INFO: EXPUNGED, user=%s, ip=[%s], old_name=%s, new_name=%s: only new_name will be left\n", + getenv("AUTHENTICATED"), + getenv("TCPREMOTEIP"), + old_name, new_name); + + if (rename(old_name, new_name)) + { + mdcreate(dot_trash); + rename(old_name, new_name); + } + + unlink(old_name); + /* triggers linux kernel bug if also moved to Trash by + sqwebmail */ + + free(new_name); + } + else + { + unlink(old_name); + + if (file_counted) + { + unsigned long filesize=0; + + if (maildir_parsequota(old_name, &filesize)) + { + if (stat(old_name, &stat_buf)) + stat_buf.st_size=0; + filesize=(unsigned long) + stat_buf.st_size; + } + + maildir_quota_deleted(".",-(long)filesize, -1); + } + } + + if (log_deletions) + fprintf(stderr, "INFO: EXPUNGED, user=%s, ip=[%s], old_name=%s\n", + getenv("AUTHENTICATED"), + getenv("TCPREMOTEIP"), + old_name); + free(old_name); + } +} + + +static FILE *newsubscribefile(char **tmpname) +{ + char uniqbuf[80]; + static unsigned tmpuniqcnt=0; + FILE *fp; + struct maildir_tmpcreate_info createInfo; + + maildir_tmpcreate_init(&createInfo); + + sprintf(uniqbuf, "imapsubscribe%u", tmpuniqcnt++); + + createInfo.uniq=uniqbuf; + createInfo.hostname=getenv("HOSTNAME"); + createInfo.doordie=1; + + if ((fp=maildir_tmpcreate_fp(&createInfo)) == NULL) + write_error_exit(0); + + *tmpname=createInfo.tmpname; + createInfo.tmpname=NULL; + maildir_tmpcreate_free(&createInfo); + + return (fp); +} + +static int sub_strcmp(const char *a, const char *b) +{ + if (strncasecmp(a, "inbox", 5) == 0 && + strncasecmp(b, "inbox", 5) == 0) + { + a += 5; + b += 5; + } + return (strcmp(a, b)); +} + +static void subscribe(const char *f) +{ + char *newf; + FILE *newfp=newsubscribefile(&newf); + FILE *oldfp; + + if ((oldfp=fopen(SUBSCRIBEFILE, "r")) != 0) + { + char buf[BUFSIZ]; + + while (fgets(buf, sizeof(buf), oldfp) != 0) + { + char *p=strchr(buf, '\n'); + + if (p) *p=0; + if (sub_strcmp(buf, f) == 0) + { + fclose(oldfp); + fclose(newfp); + unlink(newf); + free(newf); + return; /* Already subscribed */ + } + fprintf(newfp, "%s\n", buf); + } + fclose(oldfp); + } + fprintf(newfp, "%s\n", f); + if (fflush(newfp) || ferror(newfp)) + write_error_exit(0); + fclose(newfp); + rename(newf, SUBSCRIBEFILE); + free(newf); +} + +static void unsubscribe(const char *f) +{ + char *newf; + FILE *newfp=newsubscribefile(&newf); + FILE *oldfp; + + if ((oldfp=fopen(SUBSCRIBEFILE, "r")) != 0) + { + char buf[BUFSIZ]; + + while (fgets(buf, sizeof(buf), oldfp) != 0) + { + char *p=strchr(buf, '\n'); + + if (p) *p=0; + if (sub_strcmp(buf, f) == 0) + continue; + fprintf(newfp, "%s\n", buf); + } + fclose(oldfp); + } + if (fflush(newfp) || ferror(newfp)) + write_error_exit(0); + fclose(newfp); + rename(newf, SUBSCRIBEFILE); + free(newf); +} + +/* +** Count selected messages (if there's >1 copy to OUTBOX should fail). +*/ + +static int do_count(unsigned long n, int byuid, void *voidptr) +{ + const char *p=getenv("OUTBOX_MULTIPLE_SEND"); + + ++ *(int *)voidptr; + + if (p && atoi(p)) + *(int *)voidptr=1; /* Suppress the error, below */ + + return 0; +} + +static void dirsync(const char *folder) +{ +#if EXPLICITDIRSYNC + + char *p=malloc(strlen(folder)+sizeof("/new")); + int fd; + + if (!p) + write_error_exit(0); + + p=strcat(strcpy(p, folder), "/new"); + + fd=open(p, O_RDONLY); + + if (fd >= 0) + { + fsync(fd); + close(fd); + } + + p=strcat(strcpy(p, folder), "/cur"); + + fd=open(p, O_RDONLY); + + if (fd >= 0) + { + fsync(fd); + close(fd); + } + + free(p); +#endif +} + +/* +** Keyword updates for +FLAGS and -FLAGS +*/ + +static int addRemoveKeywords1(void *); + +static int addRemoveKeywords2(int (*callback_func)(void *, void *), + void *callback_func_arg, + struct storeinfo *storeinfo_s, + int *tryagain); + +struct addremove_info { + int (*callback_func)(void *, void *); + void *callback_func_arg; + struct storeinfo *storeinfo_s; + int *tryagain; +}; + + +int addRemoveKeywords(int (*callback_func)(void *, void *), + void *callback_func_arg, + struct storeinfo *storeinfo_s) +{ + int tryagain; + struct addremove_info ai; + + if (!keywords()) + return 0; + + if (current_mailbox_acl && + strchr(current_mailbox_acl, ACL_WRITE[0]) == NULL) + return 0; /* No permission */ + do + { + ai.callback_func=callback_func; + ai.callback_func_arg=callback_func_arg; + ai.storeinfo_s=storeinfo_s; + ai.tryagain= &tryagain; + + if (imapmaildirlock(¤t_maildir_info, + current_mailbox, + addRemoveKeywords1, + &ai)) + return -1; + } while (tryagain); + + return 0; +} + +static int addRemoveKeywords1(void *void_arg) +{ + struct addremove_info *ai=(struct addremove_info *)void_arg; + + return addRemoveKeywords2(ai->callback_func, + ai->callback_func_arg, + ai->storeinfo_s, + ai->tryagain); +} + +int doAddRemoveKeywords(unsigned long, int, void *); + +struct addRemoveKeywordInfo { + struct libmail_kwGeneric kwg; + struct storeinfo *storeinfo; +}; + +static int addRemoveKeywords2(int (*callback_func)(void *, void *), + void *callback_func_arg, + struct storeinfo *storeinfo_s, + int *tryagain) +{ + struct addRemoveKeywordInfo arki; + int rc; + + *tryagain=0; + + libmail_kwgInit(&arki.kwg); + + arki.storeinfo=storeinfo_s; + + rc=libmail_kwgReadMaildir(&arki.kwg, current_mailbox); + + if (rc == 0) + rc= (*callback_func)(callback_func_arg, &arki); + + if (rc < 0) + { + libmail_kwgDestroy(&arki.kwg); + return -1; + } + + if (rc > 0) /* Race */ + { + libmail_kwgDestroy(&arki.kwg); + *tryagain=1; + return 0; + } + + libmail_kwgDestroy(&arki.kwg); + return 0; +} + +int doAddRemoveKeywords(unsigned long n, int uid, void *vp) +{ + struct addRemoveKeywordInfo *arki= + (struct addRemoveKeywordInfo *)vp; + struct libmail_kwGenericEntry *ge= + libmail_kwgFindByName(&arki->kwg, + current_maildir_info.msgs[--n].filename); + char *tmpname=NULL, *newname=NULL; + struct stat stat_buf; + int rc; + + if (!ge || ge->keywords == NULL) + { + if (arki->storeinfo->plusminus == '+') + { + rc=maildir_kwSave(current_mailbox, + current_maildir_info.msgs[n]. + filename, + arki->storeinfo->keywords, + &tmpname, &newname, 1); + + if (rc < 0) + return -1; + } + } + else if (arki->storeinfo->plusminus == '+') + { + int flag=0; + struct libmail_kwMessageEntry *kme; + + for (kme=arki->storeinfo->keywords->firstEntry; + kme; kme=kme->next) + { + if ((rc=libmail_kwmSet(ge->keywords, + kme->libmail_keywordEntryPtr)) + < 0) + { + write_error_exit(0); + return 0; + } + + if (rc == 0) + flag=1; + } + + if (flag) + { + rc=maildir_kwSave(current_mailbox, + current_maildir_info.msgs[n]. + filename, + ge->keywords, + &tmpname, &newname, 1); + + if (rc < 0) + return -1; + } + } + else + { + int flag=0; + struct libmail_kwMessageEntry *kme; + + for (kme=arki->storeinfo->keywords->firstEntry; + kme; kme=kme->next) + { + struct libmail_keywordEntry *kwe; + + if ((kwe=libmail_kweFind(&arki->kwg.kwHashTable, + keywordName(kme-> + libmail_keywordEntryPtr), + 0)) != NULL && + + libmail_kwmClear(ge->keywords, kwe) == 0) + flag=1; + } + + if (flag) + { + rc=maildir_kwSave(current_mailbox, + current_maildir_info.msgs[n]. + filename, + ge->keywords, + &tmpname, &newname, 1); + + if (rc < 0) + return -1; + } + + + } + + if (tmpname) + { + current_maildir_info.msgs[n].changedflags=1; + + if (link(tmpname, newname) < 0) + { + unlink(tmpname); + free(tmpname); + free(newname); + + return (errno == EEXIST ? 1:-1); + } + + if (stat(tmpname, &stat_buf) == 0 && + stat_buf.st_nlink == 2) + { + unlink(tmpname); + free(tmpname); + free(newname); + return 0; + } + unlink(tmpname); + free(tmpname); + free(newname); + return 1; + } + + return 0; +} + +/* IMAP interface to add/remove keyword */ + +struct imap_addRemoveKeywordInfo { + char *msgset; + int uid; +}; + +static int markmessages(unsigned long n, int i, void *dummy) +{ + --n; + current_maildir_info.msgs[n].storeflag=1; + return 0; +} + +static int imap_addRemoveKeywords(void *myVoidArg, void *addRemoveVoidArg) +{ + struct imap_addRemoveKeywordInfo *i= + (struct imap_addRemoveKeywordInfo *)myVoidArg; + unsigned long j; + + for (j=0; j<current_maildir_info.nmessages; j++) + current_maildir_info.msgs[j].storeflag=0; + + do_msgset(i->msgset, markmessages, NULL, i->uid); + + for (j=0; j<current_maildir_info.nmessages; j++) + { + int rc; + + if (!current_maildir_info.msgs[j].storeflag) + continue; + + rc=doAddRemoveKeywords(j+1, i->uid, addRemoveVoidArg); + if (rc) + return rc; + } + return 0; +} + +/* +** After adding messages to a maildir, compute their new UIDs. +*/ + +static int uidplus_fill(const char *mailbox, + struct uidplus_info *uidplus_list, + unsigned long *uidv) +{ + struct imapscaninfo scan_info; + + imapscan_init(&scan_info); + + if (imapscan_maildir(&scan_info, mailbox, 0, 0, uidplus_list)) + return -1; + + *uidv=scan_info.uidv; + + imapscan_free(&scan_info); + return 0; +} + +static void uidplus_writemsgset(struct uidplus_info *uidplus_list, + int new_uids) +{ +#define UIDN(u) ( new_uids ? (u)->uid:(u)->old_uid ) + + unsigned long uid_start, uid_end; + const char *comma=""; + + writes(" "); + while (uidplus_list) + { + uid_start=UIDN(uidplus_list); + uid_end=uid_start; + + while (uidplus_list->next && + UIDN(uidplus_list->next) == uid_end + 1) + { + uidplus_list=uidplus_list->next; + ++uid_end; + } + + writes(comma); + writen(uid_start); + if (uid_end != uid_start) + { + writes(":"); + writen(uid_end); + } + comma=","; + uidplus_list=uidplus_list->next; + } + +#undef UIDN +} + +static void uidplus_free(struct uidplus_info *uidplus_list) +{ + struct uidplus_info *u; + + while ((u=uidplus_list) != NULL) + { + uidplus_list=u->next; + free(u->tmpfilename); + free(u->curfilename); + + if (u->tmpkeywords) + free(u->tmpkeywords); + if (u->newkeywords) + free(u->newkeywords); + free(u); + } +} + +/* Abort a partially-filled uidplus */ + +static void uidplus_abort(struct uidplus_info *uidplus_list) +{ + struct uidplus_info *u; + + while ((u=uidplus_list) != NULL) + { + uidplus_list=u->next; + unlink(u->tmpfilename); + unlink(u->curfilename); + + if (u->tmpkeywords) + { + unlink(u->tmpkeywords); + free(u->tmpkeywords); + } + + if (u->newkeywords) + { + unlink(u->newkeywords); + free(u->newkeywords); + } + + free(u->tmpfilename); + free(u->curfilename); + free(u); + } +} + +static void rename_callback(const char *old_path, const char *new_path) +{ +struct imapscaninfo minfo; + + char *p=malloc(strlen(new_path)+sizeof("/" IMAPDB)); + + if (!p) + write_error_exit(0); + + strcat(strcpy(p, new_path), "/" IMAPDB); + unlink(p); + free(p); + imapscan_init(&minfo); + imapscan_maildir(&minfo, new_path, 0,0, NULL); + imapscan_free(&minfo); +} + +static int broken_uidvs() +{ + const char *p=getenv("IMAP_BROKENUIDV"); + + return (p && atoi(p) != 0); +} + +static void logoutmsg() +{ + bye_msg("INFO: LOGOUT"); +} + +void bye() +{ + if (current_temp_fd >= 0) + close(current_temp_fd); + if (current_temp_fn) + unlink(current_temp_fn); + + if (current_mailbox) + free(current_mailbox); + imapscan_free(¤t_maildir_info); + maildirwatch_cleanup(); + fetch_free_cached(); + exit(0); +} + +char *get_myrightson(const char *mailbox); + +static void list_acl(const char *mailbox, + maildir_aclt_list *); +static int get_acllist(maildir_aclt_list *l, + const char *mailbox, + char **computed_mailbox_owner); + +static void list_myrights(const char *mailbox, + const char *myrights); +static void list_postaddress(const char *mailbox); + +static void accessdenied(const char *cmd, const char *folder, + const char *acl_required); + +static int list_callback(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg) +{ + const char *sep=""; + const char *cmd=(const char *)void_arg; + maildir_aclt_list l; + + char *myrights=NULL; + + /* + ** If we're going to list ACLs, grab the ACLs now, because + ** get_acllist2() may end up calling myrights(), which generates + ** its own output. + */ + + if (flags & (LIST_MYRIGHTS | LIST_ACL)) + { + myrights=get_myrightson(mailbox); + + if (flags & LIST_MYRIGHTS) + { + if (!maildir_acl_canlistrights(myrights)) + { + flags &= ~LIST_MYRIGHTS; +#if 0 + /* F.Y.I. only, should not be enabled + 'cause make check may fail on systems + which return directory entries in a + different order */ + + writes("* ACLFAILED \""); + writeqs(mailbox); + writes("\""); + accessdenied("LIST(MYRIGHTS)", + mailbox, + ACL_LOOKUP + ACL_READ + ACL_INSERT + ACL_CREATE + ACL_DELETEFOLDER + ACL_EXPUNGE + ACL_ADMINISTER); +#endif + } + } + + if (flags & LIST_ACL) + { + if (!strchr(myrights, ACL_ADMINISTER[0])) + { + flags &= ~LIST_ACL; +#if 0 + /* F.Y.I. only, should not be enabled + 'cause make check may fail on systems + which return directory entries in a + different order */ + + writes("* ACLFAILED \""); + writeqs(mailbox); + writes("\""); + accessdenied("LIST(ACL)", + mailbox, + ACL_ADMINISTER); +#endif + } + } + } + + if (flags & LIST_ACL) + { + if (get_acllist(&l, mailbox, NULL) < 0) + { + fprintf(stderr, + "ERR: Error reading ACLs for %s: %s\n", + mailbox, strerror(errno)); + flags &= ~LIST_ACL; + } + } + + writes("* "); + writes(cmd); + writes(" ("); + +#define DO_FLAG(flag, flagname) \ + if (flags & (flag)) { writes(sep); writes(flagname); \ + sep=" "; } + + DO_FLAG(MAILBOX_NOSELECT, "\\Noselect"); + DO_FLAG(MAILBOX_UNMARKED, "\\Unmarked"); + DO_FLAG(MAILBOX_MARKED, "\\Marked"); + DO_FLAG(MAILBOX_NOCHILDREN, "\\HasNoChildren"); + DO_FLAG(MAILBOX_NOINFERIORS, "\\Noinferiors"); + DO_FLAG(MAILBOX_CHILDREN, "\\HasChildren"); +#undef DO_FLAG + + writes(") "); + writes(hiersep); + writes(" \""); + writeqs(mailbox); + writes("\""); + + if (flags & (LIST_ACL|LIST_MYRIGHTS|LIST_POSTADDRESS)) + { + writes(" ("); + if (flags & LIST_ACL) + list_acl(mailbox, &l); + if (flags & LIST_MYRIGHTS) + list_myrights(mailbox, myrights); + if (flags & LIST_POSTADDRESS) + list_postaddress(mailbox); + writes(")"); + } + + writes("\r\n"); + if (myrights) + free(myrights); + if (flags & LIST_ACL) + maildir_aclt_list_destroy(&l); + + return 0; +} + +static void list_postaddress(const char *mailbox) +{ + writes("(POSTADDRESS NIL)"); +} + +/* +** Wrapper for maildir_aclt_read and maildir_aclt_write +*/ + +static int acl_read_folder(maildir_aclt_list *aclt_list, + const char *maildir, + const char *path) +{ + char *p, *q; + int rc; + + /* Handle legacy shared. namespace */ + + if (strcmp(path, SHARED) == 0) + { + maildir_aclt_list_init(aclt_list); + if (maildir_aclt_list_add(aclt_list, "anyone", + ACL_LOOKUP, NULL) < 0) + { + maildir_aclt_list_destroy(aclt_list); + return -1; + } + return 0; + } + + if (strncmp(path, SHARED ".", sizeof(SHARED)) == 0) + { + maildir_aclt_list_init(aclt_list); + + if (strchr(path+sizeof(SHARED), '.') == 0) + { + if (maildir_aclt_list_add(aclt_list, + "anyone", + ACL_LOOKUP, NULL) < 0) + { + maildir_aclt_list_destroy(aclt_list); + return -1; + } + } + + p=maildir_shareddir(maildir, path+sizeof(SHARED)); + if (!p) + { + if (maildir_aclt_list_add(aclt_list, + "anyone", + ACL_LOOKUP, NULL) < 0) + { + maildir_aclt_list_destroy(aclt_list); + return -1; + } + return 0; + } + q=malloc(strlen(p)+sizeof("/new")); + if (!q) + { + free(p); + maildir_aclt_list_destroy(aclt_list); + return -1; + } + strcat(strcpy(q, p), "/new"); + free(p); + + if (maildir_aclt_list_add(aclt_list, + "anyone", + access(q, W_OK) == 0 ? + ACL_LOOKUP ACL_READ + ACL_SEEN ACL_WRITE ACL_INSERT + + ACL_DELETEFOLDER /* Wrong, but needed for ACL1 compatibility */ + + ACL_DELETEMSGS ACL_EXPUNGE: + ACL_LOOKUP ACL_READ ACL_SEEN + ACL_WRITE, NULL) < 0) + { + free(q); + maildir_aclt_list_destroy(aclt_list); + return -1; + } + free(q); + return 0; + } + + p=maildir_name2dir(".", path); + + if (!p) + return -1; + + rc=maildir_acl_read(aclt_list, maildir, p[0] == '.' && p[1] == '/' + ? p+2:p); + free(p); + return rc; +} + +static int acl_write_folder(maildir_aclt_list *aclt_list, + const char *maildir, + const char *path, + + const char *owner, + const char **err_failedrights) +{ + char *p; + int rc; + + if (strcmp(path, SHARED) == 0 || + strncmp(path, SHARED ".", sizeof(SHARED ".")-1) == 0) + { + /* Legacy */ + + return 1; + } + + p=maildir_name2dir(".", path); + + if (!p) + return -1; + + rc=maildir_acl_write(aclt_list, maildir, p[0] == '.' && p[1] == '/' + ? p+2:p, + owner, err_failedrights); + free(p); + return rc; +} + +static void writeacl(const char *); + +static void myrights() +{ + writes("* OK [MYRIGHTS \""); + writeacl(current_mailbox_acl); + writes("\"] ACL\r\n"); +} + +static int list_acl_cb(const char *ident, + const maildir_aclt *acl, + void *cb_arg); + +char *compute_myrights(maildir_aclt_list *l, + const char *l_owner); + +static int get_acllist(maildir_aclt_list *l, + const char *p, + char **computed_mailbox_owner) +{ + int rc; + char *v; + + struct maildir_info mi; + char *dummy_mailbox_owner=NULL; + + if (!computed_mailbox_owner) + computed_mailbox_owner= &dummy_mailbox_owner; + + if (maildir_info_imap_find(&mi, p, getenv("AUTHENTICATED")) < 0) + return -1; + + v=NULL; + + if (mi.homedir && mi.maildir) + { + rc=acl_read_folder(l, mi.homedir, mi.maildir); + v=maildir_name2dir(mi.homedir, mi.maildir); + } + else if (mi.mailbox_type == MAILBOXTYPE_OLDSHARED) + { + rc=acl_read_folder(l, ".", p); /* It handles it */ + } + else if (mi.mailbox_type == MAILBOXTYPE_NEWSHARED) + { + /* Intermediate #shared node. Punt */ + + maildir_aclt_list_init(l); + rc=0; + + if (maildir_aclt_list_add(l, "anyone", + ACL_LOOKUP, NULL) < 0) + { + maildir_aclt_list_destroy(l); + rc=-1; + } + } + else + { + /* Unknown mailbox type, no ACLs */ + + maildir_aclt_list_init(l); + rc=0; + } + + if (rc) + { + if (v) + free(v); + maildir_info_destroy(&mi); + return -1; + } + + *computed_mailbox_owner=my_strdup(mi.owner); + + maildir_info_destroy(&mi); + + /* Detect if ACLs on the currently-open folder have changed */ + + if (rc == 0 && current_mailbox && +#if SMAP + !smapflag && +#endif + + v && strcmp(v, current_mailbox) == 0) + { + char *new_acl=compute_myrights(l, *computed_mailbox_owner); + + if (new_acl && strcmp(new_acl, current_mailbox_acl)) + { + free(current_mailbox_acl); + current_mailbox_acl=new_acl; + new_acl=NULL; + myrights(); + } + + if (new_acl) + free(new_acl); + } + if (v) + free(v); + if (dummy_mailbox_owner) + free(dummy_mailbox_owner); + return rc; +} + +static void list_acl(const char *mailbox, + maildir_aclt_list *l) +{ + writes("(\"ACL\" ("); + maildir_aclt_list_enum(l, list_acl_cb, NULL); + writes("))"); +} + +static void writeacl(const char *aclstr) +{ + char *p, *q, *r; + const char *cp; + int n=0; + + for (cp=aclstr; *cp; cp++) + if (*cp == ACL_EXPUNGE[0]) + n |= 1; + else if (*cp == ACL_DELETEMSGS[0]) + n |= 2; + else if (*cp == ACL_DELETEFOLDER[0]) + n |= 4; + + if (n != 7) + { + writeqs(aclstr); + return; + } + + p=my_strdup(aclstr); + + *strchr(p, ACL_EXPUNGE[0])= ACL_DELETE_SPECIAL[0]; + + q=r=p; + while (*q) + { + if (*q != ACL_EXPUNGE[0] && *q != ACL_DELETEMSGS[0] && + *q != ACL_DELETEFOLDER[0]) + *r++= *q; + ++q; + } + *r=0; + writeqs(p); + free(p); +} + +static int list_acl_cb(const char *ident, + const maildir_aclt *acl, + void *cb_arg) +{ + writes("(\""); + writeqs(ident); + writes("\" \""); + writeacl(maildir_aclt_ascstr(acl)); + writes("\")"); + return 0; +} + +static void writeacl1(char *p) +{ + char *q, *r; + + if (strchr(p, ACL_EXPUNGE[0]) && + strchr(p, ACL_DELETEMSGS[0]) && + strchr(p, ACL_DELETEFOLDER[0])) + *strchr(p, ACL_EXPUNGE[0])=ACL_DELETE_SPECIAL[0]; + /* See no evil */ + + + for (q=r=p; *q; q++) + { + if (*q == ACL_EXPUNGE[0] || + *q == ACL_DELETEMSGS[0] || + *q == ACL_DELETEFOLDER[0]) + { + continue; + } + + *r++= *q; + } + *r=0; + writeqs(p); +} + +static int getacl_cb(const char *ident, + const maildir_aclt *acl, + void *cb_arg) +{ + char *p; + int isneg=0; + + if (*ident == '-') + { + isneg=1; + ++ident; + } + + if (strchr(ident, '=')) + { + if (strncmp(ident, "user=", 5)) + return 0; /* Hear no evil */ + ident += 5; + } + + writes(" \""); + if (isneg) + writes("-"); + writeqs(ident); + writes("\" \""); + + + p=my_strdup(maildir_aclt_ascstr(acl)); + + writeacl1(p); + + free(p); + writes("\""); + return 0; +} + +char *get_myrightson(const char *mailbox) +{ + maildir_aclt_list l; + char *mailbox_owner; + char *rights; + + if (get_acllist(&l, mailbox, &mailbox_owner) < 0) + return NULL; + + rights=compute_myrights(&l, mailbox_owner); + free(mailbox_owner); + maildir_aclt_list_destroy(&l); + return rights; +} + + +char *compute_myrights(maildir_aclt_list *l, const char *l_owner) +{ + maildir_aclt aa; + char *a; + + if (maildir_acl_computerights(&aa, l, getenv("AUTHENTICATED"), + l_owner) < 0) + return NULL; + + a=my_strdup(maildir_aclt_ascstr(&aa)); + maildir_aclt_destroy(&aa); + return a; +} + +void check_rights(const char *mailbox, char *rights_buf) +{ + char *r=get_myrightson(mailbox); + char *p, *q; + + if (!r) + { + fprintf(stderr, "ERR: Error reading ACLs for %s: %s\n", + mailbox, strerror(errno)); + *rights_buf=0; + return; + } + + for (p=q=rights_buf; *p; p++) + { + if (strchr(r, *p) == NULL) + continue; + + *q++ = *p; + } + *q=0; + free(r); +} + +static void list_myrights(const char *mailbox, + const char *r) +{ + + writes("(\"MYRIGHTS\" "); + + if (r == NULL) + { + fprintf(stderr, "ERR: Error reading ACLs for %s: %s\n", + mailbox, strerror(errno)); + writes(" NIL"); + } + else + { + writes("\""); + writeacl(r); + writes("\""); + } + writes(")"); +} + + +struct temp_acl_mailbox_list { + struct temp_acl_mailbox_list *next; + char *mailbox; + char *hier; + int flags; +}; + +/* +** Callback function from aclmailbox_scan that saves the listed mailboxes into +** a temporary link list. +*/ + +static int aclmailbox_scan(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg) +{ + struct temp_acl_mailbox_list **p + =(struct temp_acl_mailbox_list **)void_arg, + *q=malloc(sizeof(struct temp_acl_mailbox_list)); + + if (!q || !(q->mailbox=malloc(strlen(mailbox)+1))) + { + if (q) free(q); + return -1; + } + if (!(q->hier=strdup(hiersep))) + { + free(q->mailbox); + free(q); + return -1; + } + strcpy(q->mailbox, mailbox); + q->next= *p; + q->flags=flags; + *p=q; + return 0; +} + +static void free_temp_acl_mailbox(struct temp_acl_mailbox_list *p) +{ + free(p->mailbox); + free(p->hier); +} + +static int cmp_mb(const void *a, const void *b) +{ + return (strcmp ( ((struct temp_acl_mailbox_list *)a)->mailbox, + ((struct temp_acl_mailbox_list *)b)->mailbox)); +} + +/* +** Combine mailbox patterns; remove duplicate mailboxes. +** +** mailbox_merge takes the temp_acl_mailbox_list generated from a pattern, +** combines it with the current mailbox list (kept as an array), sorts the +** end result, then removes dupes. +*/ + +static int aclmailbox_merge(struct temp_acl_mailbox_list *l, + struct temp_acl_mailbox_list **mailbox_list) +{ + size_t n, o; + struct temp_acl_mailbox_list *p; + struct temp_acl_mailbox_list *newa; + + /* Count # of mailboxes so far */ + + for (n=0; *mailbox_list && (*mailbox_list)[n].mailbox; n++) + ; + + /* Count # of new mailboxes */ + + for (p=l, o=0; p; p=p->next) + ++o; + + if (n + o == 0) + return 0; /* The list is empty */ + + /* Expand the array */ + + if ((newa= *mailbox_list == NULL ? malloc( (n+o+1)*sizeof(*l)): + realloc(*mailbox_list, (n+o+1)*sizeof(*l))) == NULL) + return -1; + + *mailbox_list=newa; + while (l) + { + newa[n]= *l; + + if ((newa[n].mailbox=strdup(l->mailbox)) == NULL) + return -1; + + if ((newa[n].hier=strdup(l->hier)) == NULL) + { + free(newa[n].mailbox); + newa[n].mailbox=NULL; + return -1; + } + + ++n; + memset(&newa[n], 0, sizeof(newa[n])); + + l=l->next; + } + qsort(newa, n, sizeof(*l), cmp_mb); + + /* Remove dupes */ + + for (n=o=0; newa[n].mailbox; n++) + { + if (newa[n+1].mailbox && + strcmp(newa[n].mailbox, newa[n+1].mailbox) == 0) + { + free_temp_acl_mailbox(&newa[n]); + continue; + } + newa[o]=newa[n]; + ++o; + } + memset(&newa[o], 0, sizeof(newa[o])); + + return 0; +} + +static int aclstore(const char *, struct temp_acl_mailbox_list *); +static int aclset(const char *, struct temp_acl_mailbox_list *); +static int acldelete(const char *, struct temp_acl_mailbox_list *); + +static void free_mailboxlist(struct temp_acl_mailbox_list *mailboxlist) +{ + size_t i; + for (i=0; mailboxlist && mailboxlist[i].mailbox; i++) + { + free(mailboxlist[i].hier); + free(mailboxlist[i].mailbox); + } + if (mailboxlist) + free(mailboxlist); +} + +static void free_tempmailboxlist(struct temp_acl_mailbox_list *mailboxlist) +{ + struct temp_acl_mailbox_list *p; + + while ((p=mailboxlist) != NULL) + { + mailboxlist=p->next; + free_temp_acl_mailbox(p); + free(p); + } +} + +static int aclcmd(const char *tag) +{ + char aclcmd[11]; + struct temp_acl_mailbox_list *mailboxlist; + struct temp_acl_mailbox_list *mblist; + struct imaptoken *curtoken; + size_t i; + int rc; + int (*aclfunc)(const char *, struct temp_acl_mailbox_list *); + + /* Expect ACL followed only by: STORE/DELETE/SET */ + + if ((curtoken=nexttoken())->tokentype != IT_ATOM || + strlen(curtoken->tokenbuf) > sizeof(aclcmd)-1) + { + errno=EINVAL; + return -1; + } + + strcpy(aclcmd, curtoken->tokenbuf); + + mailboxlist=NULL; + + switch ((curtoken=nexttoken_nouc())->tokentype) { + case IT_LPAREN: + while ((curtoken=nexttoken_nouc())->tokentype != IT_RPAREN) + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + { + errno=EINVAL; + return -1; + } + + mblist=NULL; + + if (mailbox_scan("", curtoken->tokenbuf, 0, + aclmailbox_scan, &mblist) || + aclmailbox_merge(mblist, &mailboxlist)) + { + free_tempmailboxlist(mblist); + free_mailboxlist(mailboxlist); + return -1; + + } + + free_tempmailboxlist(mblist); + } + break; + case IT_QUOTED_STRING: + case IT_ATOM: + case IT_NUMBER: + + mblist=NULL; + if (mailbox_scan("", curtoken->tokenbuf, LIST_CHECK1FOLDER, + aclmailbox_scan, &mblist) || + aclmailbox_merge(mblist, &mailboxlist)) + + { + free_tempmailboxlist(mblist); + free_mailboxlist(mailboxlist); + return -1; + } + free_tempmailboxlist(mblist); + break; + } + + rc=0; + + aclfunc=strcmp(aclcmd, "STORE") == 0 ? aclstore: + strcmp(aclcmd, "DELETE") == 0 ? acldelete: + strcmp(aclcmd, "SET") == 0 ? aclset:NULL; + + rc= aclfunc ? (*aclfunc)(tag, mailboxlist): -1; + + if (rc == 0) + { + for (i=0; mailboxlist && mailboxlist[i].mailbox; i++) + { + if (mailboxlist[i].mailbox[0]) + list_callback(mailboxlist[i].hier, + mailboxlist[i].mailbox, + LIST_ACL | mailboxlist[i].flags, + "LIST"); + } + } + free_mailboxlist(mailboxlist); + + if (rc == 0) + { + writes(tag); + writes(" OK ACL "); + writes(aclcmd); + writes(" completed.\r\n"); + } + else + { + errno=EINVAL; + } + return rc; +} + +static int fix_acl_delete(int (*func)(maildir_aclt *, const char *, + const maildir_aclt *), + maildir_aclt *newacl, + const char *aclstr) +{ + char *p, *q; + int rc; + + if (strchr(aclstr, ACL_DELETE_SPECIAL[0]) == NULL) + return (*func)(newacl, aclstr, NULL); + + + p=malloc(strlen(aclstr)+sizeof(ACL_DELETEFOLDER + ACL_DELETEMSGS + ACL_EXPUNGE)); + if (!p) + return -1; + + q=p; + while (*aclstr) + { + if (*aclstr != ACL_DELETE_SPECIAL[0]) + *q++ = *aclstr; + ++aclstr; + } + + strcpy(q, ACL_DELETEFOLDER ACL_DELETEMSGS ACL_EXPUNGE); + + rc=(*func)(newacl, p, NULL); + free(p); + return rc; +} + +static int fix_acl_delete2(maildir_aclt_list *aclt_list, + const char *identifier, + const char *aclstr) +{ + char *p, *q; + int rc; + + if (strchr(aclstr, ACL_DELETE_SPECIAL[0]) == NULL) + return maildir_aclt_list_add(aclt_list, identifier, aclstr, + NULL); + + + p=malloc(strlen(aclstr)+sizeof(ACL_DELETEFOLDER + ACL_DELETEMSGS + ACL_EXPUNGE)); + if (!p) + return -1; + + q=p; + while (*aclstr) + { + if (*aclstr != ACL_DELETE_SPECIAL[0]) + *q++ = *aclstr; + ++aclstr; + } + + strcpy(q, ACL_DELETEFOLDER ACL_DELETEMSGS ACL_EXPUNGE); + + rc=maildir_aclt_list_add(aclt_list, identifier, p, NULL); + free(p); + return rc; +} + +void aclminimum(const char *identifier) +{ + if (strcmp(identifier, "administrators") == 0 || + strcmp(identifier, "group=administrators") == 0) + { + writes(ACL_ALL); + return; + } + + if (strcmp(identifier, "-administrators") == 0 || + strcmp(identifier, "-group=administrators") == 0) + { + writes("\"\""); + return; + } + + writes(*identifier == '-' ? "\"\"":ACL_ADMINISTER ACL_LOOKUP); + writes(" " ACL_CREATE + " " ACL_EXPUNGE + " " ACL_INSERT + " " ACL_POST + " " ACL_READ + " " ACL_SEEN + " " ACL_DELETEMSGS + " " ACL_WRITE + " " ACL_DELETEFOLDER); +} + +static void aclfailed(const char *mailbox, const char *identifier) +{ + if (!identifier) + { + writes("* ACLFAILED \""); + writeqs(mailbox); + writes("\" "); + writes(strerror(errno)); + writes("\r\n"); + return; + } + + writes("* RIGHTS-INFO \""); + writeqs(mailbox); + writes("\" \""); + writeqs(identifier); + writes("\" "); + + aclminimum(identifier); + writes("\r\n"); +} + +static int acl_settable_folder(char *mailbox, + struct maildir_info *mi) +{ + if (maildir_info_imap_find(mi, mailbox, getenv("AUTHENTICATED")) < 0) + { + aclfailed(mailbox, NULL); + *mailbox=0; + return (-1); + } + + if (mi->homedir == NULL || mi->maildir == NULL) + { + writes("* ACLFAILED \""); + writeqs(mailbox); + writes("\" ACLs may not be modified for special mailbox\r\n"); + maildir_info_destroy(mi); + *mailbox=0; + return -1; + } + return 0; +} + +int acl_lock(const char *homedir, + int (*func)(void *), + void *void_arg) +{ + struct imapscaninfo ii; + int rc; + + imapscan_init(&ii); + rc=imapmaildirlock(&ii, homedir, func, void_arg); + imapscan_free(&ii); + return rc; +} + +static int do_acl_mod_0(void *); + +struct do_acl_info { + maildir_aclt_list *aclt_list; + struct maildir_info *mi; + const char *identifier; + const char *newrights; + const char **acl_error; +}; + + +static int do_acl_mod(maildir_aclt_list *aclt_list, + struct maildir_info *mi, + const char *identifier, + const char *newrights, + const char **acl_error) +{ + struct do_acl_info dai; + + *acl_error=NULL; + + dai.aclt_list=aclt_list; + dai.mi=mi; + dai.identifier=identifier; + dai.newrights=newrights; + dai.acl_error=acl_error; + return acl_lock(mi->homedir, do_acl_mod_0, &dai); +} + +static int do_acl_mod_0(void *void_arg) +{ + struct do_acl_info *dai= + (struct do_acl_info *)void_arg; + maildir_aclt_list *aclt_list=dai->aclt_list; + struct maildir_info *mi=dai->mi; + const char *identifier=dai->identifier; + const char *newrights=dai->newrights; + const char **acl_error=dai->acl_error; + + if (newrights[0] == '+') + { + maildir_aclt newacl; + const maildir_aclt *oldacl; + + if (fix_acl_delete(&maildir_aclt_init, + &newacl, newrights+1) < 0 + || ((oldacl=maildir_aclt_list_find(aclt_list, identifier)) + != NULL && maildir_aclt_add(&newacl, NULL, oldacl) < 0) + || maildir_aclt_list_add(aclt_list, identifier, NULL, + &newacl) < 0 || + acl_write_folder(aclt_list, mi->homedir, + mi->maildir, + mi->owner, acl_error) < 0) + + { + maildir_aclt_destroy(&newacl); + return -1; + } + maildir_aclt_destroy(&newacl); + } + else if (newrights[0] == '-') + { + maildir_aclt newacl; + const maildir_aclt *oldacl; + + oldacl=maildir_aclt_list_find(aclt_list, identifier); + + if (maildir_aclt_init(&newacl, oldacl == NULL ? "":NULL, + oldacl) < 0 + || fix_acl_delete(&maildir_aclt_del, + &newacl, newrights+1) < 0 + || (strlen(maildir_aclt_ascstr(&newacl)) == 0 ? + maildir_aclt_list_del(aclt_list, identifier): + maildir_aclt_list_add(aclt_list, identifier, + NULL, &newacl)) < 0 || + acl_write_folder(aclt_list, mi->homedir, + mi->maildir, + mi->owner, + acl_error) < 0) + { + maildir_aclt_destroy(&newacl); + return -1; + } + maildir_aclt_destroy(&newacl); + } + else + { + acl_error=NULL; + + if ((newrights[0] == 0 ? + maildir_aclt_list_del(aclt_list, identifier): + fix_acl_delete2(aclt_list, identifier, newrights)) < 0 + || acl_write_folder(aclt_list, mi->homedir, + mi->maildir, mi->owner, + acl_error) < 0) + { + return -1; + } + } + + return 0; +} + +static int aclstore(const char *tag, struct temp_acl_mailbox_list *mailboxes) +{ + struct imaptoken *curtoken; + char *identifier; + size_t i; + + if ((curtoken=nexttoken_nouc())->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return -1; + + if ((identifier=strdup(curtoken->tokenbuf)) == NULL) + write_error_exit(0); + + if ((curtoken=nexttoken_nouc())->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + { + free(identifier); + return -1; + } + + for (i=0; mailboxes && mailboxes[i].mailbox; i++) + { + maildir_aclt_list aclt_list; + const char *acl_error; + struct maildir_info mi; + + if (acl_settable_folder(mailboxes[i].mailbox, &mi)) + continue; + + { + CHECK_RIGHTSM(mailboxes[i].mailbox, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + writes("* ACLFAILED \""); + writeqs(mailboxes[i].mailbox); + writes("\""); + accessdenied("ACL STORE", + mailboxes[i].mailbox, + ACL_ADMINISTER); + maildir_info_destroy(&mi); + mailboxes[i].mailbox[0]=0; + continue; + } + } + + if (acl_read_folder(&aclt_list, mi.homedir, mi.maildir)) + { + aclfailed(mailboxes[i].mailbox, NULL); + maildir_info_destroy(&mi); + continue; + } + + if (do_acl_mod(&aclt_list, &mi, identifier, curtoken->tokenbuf, + &acl_error) < 0) + { + aclfailed(mailboxes[i].mailbox, acl_error); + + maildir_aclt_list_destroy(&aclt_list); + maildir_info_destroy(&mi); + continue; + } + maildir_aclt_list_destroy(&aclt_list); + maildir_info_destroy(&mi); + } + + free(identifier); + return 0; +} + +struct aclset_info { + struct maildir_info *mi; + maildir_aclt_list *newlist; + const char **acl_error; +}; + +static int do_aclset(void *); + +static int aclset(const char *tag, struct temp_acl_mailbox_list *mailboxes) +{ + struct imaptoken *curtoken; + char *identifier; + maildir_aclt_list newlist; + size_t i; + + maildir_aclt_list_init(&newlist); + + while ((curtoken=nexttoken_nouc())->tokentype != IT_EOL) + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return -1; + if ((identifier=strdup(curtoken->tokenbuf)) == NULL) + write_error_exit(0); + + if ((curtoken=nexttoken_nouc())->tokentype + != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + { + free(identifier); + maildir_aclt_list_destroy(&newlist); + return -1; + } + + if (fix_acl_delete2(&newlist, identifier, + curtoken->tokenbuf) < 0) + { + maildir_aclt_list_destroy(&newlist); + writes(tag); + writes(" NO ACL SET <"); + writes(identifier); + writes(", "); + writes(curtoken->tokenbuf); + writes("> failed.\r\n"); + free(identifier); + return 0; + } + free(identifier); + } + + for (i=0; mailboxes && mailboxes[i].mailbox; i++) + { + const char *acl_error; + struct maildir_info mi; + struct aclset_info ai; + + if (acl_settable_folder(mailboxes[i].mailbox, &mi)) + continue; + + { + CHECK_RIGHTSM(mailboxes[i].mailbox, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + maildir_info_destroy(&mi); + writes("* ACLFAILED \""); + writeqs(mailboxes[i].mailbox); + writes("\""); + accessdenied("ACL SET", mailboxes[i].mailbox, + ACL_ADMINISTER); + mailboxes[i].mailbox[0]=0; + continue; + } + } + + acl_error=NULL; + ai.mi=&mi; + ai.acl_error= &acl_error; + ai.newlist= &newlist; + + if (acl_lock(mi.homedir, do_aclset, &ai)) + { + aclfailed(mailboxes[i].mailbox, acl_error); + maildir_info_destroy(&mi); + mailboxes[i].mailbox[0]=0; + continue; + } + maildir_info_destroy(&mi); + } + maildir_aclt_list_destroy(&newlist); + return 0; +} + +static int do_aclset(void *void_arg) +{ + struct aclset_info *ai=(struct aclset_info *)void_arg; + + return acl_write_folder(ai->newlist, ai->mi->homedir, + ai->mi->maildir, + ai->mi->owner, ai->acl_error); +} + +struct acldelete_info { + const char *mailbox; + const char *identifier; + struct maildir_info *mi; +}; + +static int do_acldelete(void *); + +static int acldelete(const char *tag, struct temp_acl_mailbox_list *mailboxes) +{ + struct imaptoken *curtoken; + const char *identifier; + size_t i; + + if ((curtoken=nexttoken_nouc())->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return -1; + + identifier=curtoken->tokenbuf; + + for (i=0; mailboxes && mailboxes[i].mailbox; i++) + { + struct maildir_info mi; + struct acldelete_info ai; + + if (acl_settable_folder(mailboxes[i].mailbox, &mi)) + continue; + + { + CHECK_RIGHTSM(mailboxes[i].mailbox, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + writes("* ACLFAILED \""); + writeqs(mailboxes[i].mailbox); + writes("\""); + accessdenied("ACL DELETE", + mailboxes[i].mailbox, + ACL_ADMINISTER); + maildir_info_destroy(&mi); + mailboxes[i].mailbox[0]=0; + continue; + } + } + + ai.mailbox=mailboxes[i].mailbox; + ai.mi= &mi; + ai.identifier=identifier; + if (acl_lock(mi.homedir, do_acldelete, &ai)) + mailboxes[i].mailbox[0]=0; + maildir_info_destroy(&mi); + } + return 0; +} + +static int do_acldelete(void *void_arg) +{ + struct acldelete_info *ai= + (struct acldelete_info *)void_arg; + const char *mailbox=ai->mailbox; + struct maildir_info *mi=ai->mi; + + maildir_aclt_list aclt_list; + const char *acl_error; + + if (acl_read_folder(&aclt_list, mi->homedir, mi->maildir) < 0) + { + writes("* NO Error reading ACLs for "); + writes(mailbox); + writes(": "); + writes(strerror(errno)); + writes("\r\n"); + return -1; + } + + acl_error=NULL; + + if (maildir_aclt_list_del(&aclt_list, ai->identifier) < 0 || + acl_write_folder(&aclt_list, mi->homedir, mi->maildir, + mi->owner, &acl_error) < 0) + { + aclfailed(mailbox, acl_error); + maildir_aclt_list_destroy(&aclt_list); + return -1; + } + maildir_aclt_list_destroy(&aclt_list); + return 0; +} + + +static void accessdenied(const char *cmd, const char *folder, + const char *acl_required) +{ + writes(" NO Access denied for "); + writes(cmd); + writes(" on "); + writes(folder); + writes(" (ACL \""); + writes(acl_required); + writes("\" required)\r\n"); +} + +/* Even if the folder does not exist, if there are subfolders it exists +** virtually. +*/ + +static int folder_exists_cb(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg) +{ + *(int *)void_arg=1; + return 0; +} + +static int folder_exists(const char *folder) +{ + int flag=0; + + if (mailbox_scan("", folder, LIST_CHECK1FOLDER, + folder_exists_cb, &flag)) + return 0; + return flag; +} + +int do_folder_delete(char *mailbox_name) +{ + maildir_aclt_list l; + const char *acl_error; + struct maildir_info mi; + + if (maildir_info_imap_find(&mi, mailbox_name, getenv("AUTHENTICATED")) + < 0) + return -1; + + if (mi.homedir == NULL || mi.maildir == NULL) + { + maildir_info_destroy(&mi); + return -1; + } + + if (acl_read_folder(&l, mi.homedir, mi.maildir) < 0) + return -1; + + if (strcmp(mi.maildir, INBOX)) + { + char *p=maildir_name2dir(mi.homedir, mi.maildir); + + if (p && is_reserved(p) == 0 && mddelete(p) == 0) + { + if (folder_exists(mailbox_name)) + { + acl_write_folder(&l, mi.homedir, + mi.maildir, NULL, + &acl_error); + } + maildir_aclt_list_destroy(&l); + maildir_quota_recalculate(mi.homedir); + free(p); + maildir_info_destroy(&mi); + return 0; + } + + if (p) + free(p); + } + maildir_aclt_list_destroy(&l); + return -1; +} + +int acl_flags_adjust(const char *access_rights, + struct imapflags *flags) +{ + if (strchr(access_rights, ACL_DELETEMSGS[0]) == NULL) + flags->deleted=0; + if (strchr(access_rights, ACL_SEEN[0]) == NULL) + flags->seen=0; + if (strchr(access_rights, ACL_WRITE[0]) == NULL) + { + flags->answered=flags->flagged=flags->drafts=0; + return 1; + } + return 0; +} + +static int append(const char *tag, const char *mailbox, const char *path) +{ + + struct imapflags flags; + struct libmail_kwMessage *keywords; + time_t timestamp=0; + unsigned long new_uidv, new_uid; + char access_rights[8]; + struct imaptoken *curtoken; + + if (access(path, 0)) + { + writes(tag); + writes(" NO [TRYCREATE] Must create mailbox before append\r\n"); + return (0); + } + + { + CHECK_RIGHTSM(mailbox, + append_rights, + ACL_INSERT ACL_DELETEMSGS + ACL_SEEN ACL_WRITE); + + if (strchr(append_rights, ACL_INSERT[0]) == NULL) + { + writes(tag); + accessdenied("APPEND", + mailbox, + ACL_INSERT); + return 0; + } + + strcpy(access_rights, append_rights); + } + + if (current_mailbox && + strcmp(path, current_mailbox) == 0 && current_mailbox_ro) + { + writes(tag); + writes(" NO Current box is selected READ-ONLY.\r\n"); + return (0); + } + + curtoken=nexttoken_noparseliteral(); + memset(&flags, 0, sizeof(flags)); + if ((keywords=libmail_kwmCreate()) == NULL) + write_error_exit(0); + + if (curtoken->tokentype == IT_LPAREN) + { + if (get_flagsAndKeywords(&flags, &keywords)) + { + libmail_kwmDestroy(keywords); + return (-1); + } + curtoken=nexttoken_noparseliteral(); + } + else if (curtoken->tokentype == IT_ATOM) + { + if (get_flagname(curtoken->tokenbuf, &flags)) + { + if (!valid_keyword(curtoken->tokenbuf)) + { + libmail_kwmDestroy(keywords); + return -1; + } + + libmail_kwmSetName(current_maildir_info.keywordList, + keywords, + curtoken->tokenbuf); + } + curtoken=nexttoken_noparseliteral(); + } + else if (curtoken->tokentype == IT_NIL) + curtoken=nexttoken_noparseliteral(); + + if (curtoken->tokentype == IT_QUOTED_STRING) + { + timestamp=decode_date_time(curtoken->tokenbuf); + if (timestamp == 0) + { + libmail_kwmDestroy(keywords); + return (-1); + } + curtoken=nexttoken_noparseliteral(); + } + else if (curtoken->tokentype == IT_NIL) + curtoken=nexttoken_noparseliteral(); + + if (curtoken->tokentype != IT_LITERAL_STRING_START) + { + libmail_kwmDestroy(keywords); + return (-1); + } + + acl_flags_adjust(access_rights, &flags); + + if (store_mailbox(tag, path, &flags, + acl_flags_adjust(access_rights, &flags) + ? NULL:keywords, + timestamp, + curtoken->tokennum, &new_uidv, &new_uid)) + { + libmail_kwmDestroy(keywords); + unread('\n'); + return (0); + } + libmail_kwmDestroy(keywords); + + if (nexttoken()->tokentype != IT_EOL) + { + return (-1); + } + + dirsync(path); + writes(tag); + writes(" OK [APPENDUID "); + writen(new_uidv); + writes(" "); + writen(new_uid); + writes("] APPEND Ok.\r\n"); + return (0); +} + + +/* Check for 'c' rights on the parent directory. */ + +static int check_parent_create(const char *tag, + const char *cmd, char *folder) +{ + char *parentPtr; + + parentPtr=strrchr(folder, HIERCH); + + if (parentPtr) + { + *parentPtr=0; + + { + CHECK_RIGHTSM(folder, + create_rights, + ACL_CREATE); + + if (create_rights[0]) + { + if (parentPtr) + *parentPtr=HIERCH; + return 0; + } + } + } + + writes(tag); + accessdenied(cmd, folder, ACL_CREATE); + if (parentPtr) + *parentPtr=HIERCH; + return -1; +} + +/* Convert ACL1 identifiers to ACL2 */ + +static char *acl2_identifier(const char *tag, + const char *identifier) +{ + const char *ident_orig=identifier; + + char *p; + int isneg=0; + + if (*identifier == '-') + { + isneg=1; + ++identifier; + } + + if (strcmp(identifier, "anyone") == 0 || + strcmp(identifier, "anonymous") == 0 || + strcmp(identifier, "authuser") == 0 || + strcmp(identifier, "owner") == 0 || + strcmp(identifier, "administrators") == 0) + return my_strdup(ident_orig); + + if (strchr(identifier, '=')) + { + writes(tag); + writes(" NO Invalid ACL identifier.\r\n"); + return NULL; + } + + p=malloc(sizeof("-user=")+strlen(identifier)); + + if (!p) + write_error_exit(0); + return strcat(strcat(strcpy(p, isneg ? "-":""), "user="), identifier); +} + + +int folder_rename(struct maildir_info *mi1, + struct maildir_info *mi2, + const char **errmsg) +{ + char *old_mailbox, *new_mailbox; + + if (mi1->homedir == NULL || mi1->maildir == NULL) + { + *errmsg="Invalid mailbox name"; + return -1; + } + + if (mi2->homedir == NULL || mi2->maildir == NULL) + { + *errmsg="Invalid new mailbox name"; + return -1; + } + + if (current_mailbox) + { + char *mailbox=maildir_name2dir(mi1->homedir, + mi1->maildir); + size_t l; + + if (!mailbox) + { + *errmsg="Invalid mailbox name"; + return -1; + } + + l=strlen(mailbox); + + if (strncmp(mailbox, current_mailbox, l) == 0 && + (current_mailbox[l] == 0 || + current_mailbox[l] == HIERCH)) + { + free(mailbox); + *errmsg="Can't RENAME the currently-open folder"; + return -1; + } + free(mailbox); + } + + if (strcmp(mi1->homedir, mi2->homedir)) + { + *errmsg="Cannot move a folder to a different account."; + return -1; + } + + if (strcmp(mi1->maildir, INBOX) == 0 || + strcmp(mi2->maildir, INBOX) == 0) + { + *errmsg="INBOX rename not implemented."; + return -1; + } + + if (is_reserved_name(mi1->maildir) || + is_reserved_name(mi2->maildir)) + { + *errmsg="Reserved folder name - cannot rename."; + return -1; + } + + /* Depend on maildir_name2dir returning ./.folder, see + ** maildir_rename() call. */ + + if ((old_mailbox=maildir_name2dir(".", mi1->maildir)) == NULL || + strncmp(old_mailbox, "./", 2)) + { + if (old_mailbox) + free(old_mailbox); + *errmsg="Internal error in RENAME: maildir_name2dir failed" + " for the old folder rename."; + return -1; + } + + if ((new_mailbox=maildir_name2dir(".", mi2->maildir)) == NULL || + strncmp(new_mailbox, "./", 2)) + { + free(old_mailbox); + if (new_mailbox) + free(new_mailbox); + *errmsg="Internal error in RENAME: maildir_name2dir failed" + " for the new folder rename."; + return -1; + } + + fetch_free_cache(); + + if (maildir_rename(mi1->homedir, + old_mailbox+2, new_mailbox+2, + MAILDIR_RENAME_FOLDER | + MAILDIR_RENAME_SUBFOLDERS, + &rename_callback)) + { + free(old_mailbox); + free(new_mailbox); + + *errmsg="@RENAME failed: "; + return -1; + } + + maildir_quota_recalculate(mi1->homedir); + free(old_mailbox); + free(new_mailbox); + return 0; +} + +static int validate_charset(const char *tag, char **charset) +{ + libmail_u_convert_handle_t conv; + unicode_char *ucptr; + size_t ucsize; + + if (*charset == NULL) + *charset=my_strdup("ISO-8859-1"); + + conv=libmail_u_convert_tou_init(*charset, &ucptr, &ucsize, 1); + + if (!conv) + { + writes(tag); + writes(" NO [BADCHARSET] The requested character set is not supported.\r\n"); + return (-1); + } + if (libmail_u_convert_deinit(conv, NULL) == 0) + free(ucptr); + return (0); +} + +int do_imap_command(const char *tag) +{ +struct imaptoken *curtoken=nexttoken(); +int uid=0; + + if (curtoken->tokentype != IT_ATOM) return (-1); + + /* Commands that work in authenticated state */ + + if (strcmp(curtoken->tokenbuf, "CAPABILITY") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + writes("* CAPABILITY "); + imapcapability(); + writes("\r\n"); + writes(tag); + writes(" OK CAPABILITY completed\r\n"); + return (0); + } + if (strcmp(curtoken->tokenbuf, "NOOP") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + if (current_mailbox) + doNoop(1); + writes(tag); + writes(" OK NOOP completed\r\n"); + return (0); + } + if (strcmp(curtoken->tokenbuf, "ID") == 0) + { + if (doId() < 0) + return (-1); + writes(tag); + writes(" OK ID completed\r\n"); + return (0); + } + if (strcmp(curtoken->tokenbuf, "IDLE") == 0) + { + const char *p; + + if (nexttoken()->tokentype != IT_EOL) return (-1); + + read_eol(); + + if ((p=getenv("IMAP_ENHANCEDIDLE")) == NULL + || !atoi(p) + || imapenhancedidle()) + imapidle(); + curtoken=nexttoken(); + if (strcmp(curtoken->tokenbuf, "DONE") == 0) + { + if (current_mailbox) + doNoop(0); + writes(tag); + writes(" OK IDLE completed\r\n"); + return (0); + } + return (-1); + } + if (strcmp(curtoken->tokenbuf, "LOGOUT") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + fetch_free_cache(); + writes("* BYE Courier-IMAP server shutting down\r\n"); + writes(tag); + writes(" OK LOGOUT completed\r\n"); + writeflush(); + emptytrash(); + logoutmsg(); + bye(); + } + + if (strcmp(curtoken->tokenbuf, "LIST") == 0 + || strcmp(curtoken->tokenbuf, "LSUB") == 0) + { + char *reference, *name; + int rc; + char cmdbuf[5]; + int list_flags=0; + + strcpy(cmdbuf, curtoken->tokenbuf); + + curtoken=nexttoken_nouc(); + if (curtoken->tokentype == IT_LPAREN) + { + while ((curtoken=nexttoken())->tokentype != IT_RPAREN) + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return (-1); + + if (strcmp(curtoken->tokenbuf, "ACL") == 0) + list_flags |= LIST_ACL; + if (strcmp(curtoken->tokenbuf, "MYRIGHTS")==0) + list_flags |= LIST_MYRIGHTS; + if (strcmp(curtoken->tokenbuf, + "POSTADDRESS")==0) + list_flags |= LIST_POSTADDRESS; + } + + curtoken=nexttoken_nouc(); + } + + + if (curtoken->tokentype == IT_NIL) + reference=my_strdup(""); + else + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return (-1); + reference=my_strdup(curtoken->tokenbuf); + } + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype == IT_NIL) + name=my_strdup(""); + else + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return (-1); + name=my_strdup(curtoken->tokenbuf); + } + if (nexttoken()->tokentype != IT_EOL) return (-1); + + if (strcmp(cmdbuf, "LIST")) + list_flags |= LIST_SUBSCRIBED; + + rc=mailbox_scan(reference, name, + list_flags, + list_callback, cmdbuf); + + free(reference); + free(name); + if (rc == 0) + { + writes(tag); + writes(" OK "); + writes(cmdbuf); + writes(" completed\r\n"); + } + else + { + writes(tag); + writes(" NO "); + writes(strerror(errno)); + writes("\r\n"); + rc=0; + } + writeflush(); + return (rc); + } + + if (strcmp(curtoken->tokenbuf, "APPEND") == 0) + { + struct imaptoken *tok=nexttoken_nouc(); + struct maildir_info mi; + + if (tok->tokentype != IT_NUMBER && + tok->tokentype != IT_ATOM && + tok->tokentype != IT_QUOTED_STRING) + return (-1); + + if (maildir_info_imap_find(&mi, tok->tokenbuf, + getenv("AUTHENTICATED")) < 0) + { + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (mi.homedir && mi.maildir) + { + char *p=maildir_name2dir(mi.homedir, mi.maildir); + int rc; + + if (!p) + { + maildir_info_destroy(&mi); + writes(tag); + accessdenied("APPEND", + tok->tokenbuf, + ACL_INSERT); + return 0; + } + + rc=append(tag, tok->tokenbuf, p); + free(p); + maildir_info_destroy(&mi); + return (rc); + } + else if (mi.mailbox_type == MAILBOXTYPE_OLDSHARED) + { + char *p=strchr(tok->tokenbuf, '.'); + + if (p && (p=maildir_shareddir(".", p+1)) != NULL) + { + int rc; + char *q=malloc(strlen(p)+sizeof("/shared")); + + if (!q) write_error_exit(0); + + strcat(strcpy(q, p), "/shared"); + free(p); + rc=append(tag, tok->tokenbuf, q); + free(q); + maildir_info_destroy(&mi); + return rc; + } + } + + writes(tag); + accessdenied("APPEND", "folder", ACL_INSERT); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "GETQUOTAROOT") == 0) + { + char qroot[20]; + struct maildir_info minfo; + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + if (maildir_info_imap_find(&minfo, curtoken->tokenbuf, + getenv("AUTHENTICATED"))) + { + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + switch (minfo.mailbox_type) { + case MAILBOXTYPE_INBOX: + strcpy(qroot, "ROOT"); + break; + case MAILBOXTYPE_OLDSHARED: + strcpy(qroot, "SHARED"); + break; + case MAILBOXTYPE_NEWSHARED: + strcpy(qroot, "PUBLIC"); + break; + } + maildir_info_destroy(&minfo); + + writes("*"); + writes(" QUOTAROOT \""); + writeqs(curtoken->tokenbuf); + writes("\" \""); + writes(qroot); + writes("\"\r\n"); + quotainfo_out(qroot); + writes(tag); + writes(" OK GETQUOTAROOT Ok.\r\n"); + return(0); + } + + + if (strcmp(curtoken->tokenbuf, "SETQUOTA") == 0) + { + writes(tag); + writes(" NO SETQUOTA No permission.\r\n"); + return(0); + } + + if (strcmp(curtoken->tokenbuf, "GETQUOTA") == 0) + { + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + quotainfo_out(curtoken->tokenbuf); + writes(tag); + writes(" OK GETQUOTA Ok.\r\n"); + return(0); + } + + if (strcmp(curtoken->tokenbuf, "STATUS") == 0) + { + char *mailbox; + int get_messages=0, + get_recent=0, + get_uidnext=0, + get_uidvalidity=0, + get_unseen=0; + + struct imapscaninfo other_info, *loaded_infoptr, + *infoptr; + const char *p; + char *orig_mailbox; + int oneonly; + + curtoken=nexttoken_nouc(); + mailbox=parse_mailbox_error(tag, curtoken, 0, 0); + if ( mailbox == 0) + return (0); + + orig_mailbox=my_strdup(curtoken->tokenbuf); + curtoken=nexttoken(); + + oneonly=0; + if (curtoken->tokentype != IT_LPAREN) + { + if (curtoken->tokentype != IT_ATOM) + { + free(mailbox); + free(orig_mailbox); + return (-1); + } + oneonly=1; + } + else nexttoken(); + + while ((curtoken=currenttoken())->tokentype == IT_ATOM) + { + if (strcmp(curtoken->tokenbuf, "MESSAGES") == 0) + get_messages=1; + if (strcmp(curtoken->tokenbuf, "RECENT") == 0) + get_recent=1; + if (strcmp(curtoken->tokenbuf, "UIDNEXT") == 0) + get_uidnext=1; + if (strcmp(curtoken->tokenbuf, "UIDVALIDITY") == 0) + get_uidvalidity=1; + if (strcmp(curtoken->tokenbuf, "UNSEEN") == 0) + get_unseen=1; + nexttoken(); + if (oneonly) break; + } + + if ((!oneonly && curtoken->tokentype != IT_RPAREN) || + nexttoken()->tokentype != IT_EOL) + { + free(mailbox); + free(orig_mailbox); + return (-1); + } + + { + CHECK_RIGHTSM(orig_mailbox, status_rights, ACL_READ); + + if (!status_rights[0]) + { + writes(tag); + accessdenied("STATUS", orig_mailbox, + ACL_READ); + free(mailbox); + free(orig_mailbox); + return 0; + } + } + + + if (current_mailbox && strcmp(current_mailbox, mailbox) == 0) + { + loaded_infoptr=0; + infoptr= ¤t_maildir_info; + } + else + { + loaded_infoptr= &other_info; + infoptr=loaded_infoptr; + + imapscan_init(loaded_infoptr); + + if (imapscan_maildir(infoptr, mailbox, 1, 1, NULL)) + { + writes(tag); + writes(" NO [ALERT] STATUS failed\r\n"); + free(mailbox); + free(orig_mailbox); + return (0); + } + } + + writes("*"); + writes(" STATUS \""); + writeqs(orig_mailbox); + writes("\" ("); + p=""; + if (get_messages) + { + writes("MESSAGES "); + writen(infoptr->nmessages+infoptr->left_unseen); + p=" "; + } + if (get_recent) + { + unsigned long n=infoptr->left_unseen; + unsigned long i; + + for (i=0; i<infoptr->nmessages; i++) + if (infoptr->msgs[i].recentflag) + ++n; + writes(p); + writes("RECENT "); + writen(n); + p=" "; + } + + if (get_uidnext) + { + writes(p); + writes("UIDNEXT "); + writen(infoptr->nextuid); + p=" "; + } + + if (get_uidvalidity) + { + writes(p); + writes("UIDVALIDITY "); + writen(infoptr->uidv); + p=" "; + } + + if (get_unseen) + { + unsigned long n=infoptr->left_unseen, i; + + for (i=0; i<infoptr->nmessages; i++) + { + const char *p=infoptr->msgs[i].filename; + + p=strrchr(p, MDIRSEP[0]); + if (p && strncmp(p, MDIRSEP "2,", 3) == 0 && + strchr(p, 'S')) continue; + ++n; + } + writes(p); + writes("UNSEEN "); + writen(n); + } + writes(")\r\n"); + if (loaded_infoptr) + imapscan_free(loaded_infoptr); + free(mailbox); + free(orig_mailbox); + writes(tag); + writes(" OK STATUS Completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "CREATE") == 0) + { + char *mailbox, *orig_mailbox, *p; + int isdummy; + struct maildir_info mi; + struct imapscaninfo minfo; + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + isdummy=0; + + p=strrchr(curtoken->tokenbuf, HIERCH); + if (p && p[1] == '\0') + { + *p=0; + isdummy=1; /* Ignore hierarchy creation */ + } + + if (maildir_info_imap_find(&mi, curtoken->tokenbuf, + getenv("AUTHENTICATED"))) + { + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (!mi.homedir || !mi.maildir) + { + maildir_info_destroy(&mi); + writes(tag); + accessdenied("CREATE", + curtoken->tokenbuf, + ACL_CREATE); + maildir_info_destroy(&mi); + return (0); + } + + mailbox=maildir_name2dir(mi.homedir, mi.maildir); + if (!mailbox) + { + writes(tag); + writes(" NO Invalid mailbox name\r\n"); + maildir_info_destroy(&mi); + return (0); + } + + if (strcmp(mailbox, ".") == 0) + { + writes(tag); + writes(" NO INBOX already exists!\r\n"); + free(mailbox); + maildir_info_destroy(&mi); + return (0); + } + + if (check_parent_create(tag, "CREATE", curtoken->tokenbuf)) + { + free(mailbox); + maildir_info_destroy(&mi); + return (0); + } + + if (isdummy) *p=HIERCH; + orig_mailbox=my_strdup(curtoken->tokenbuf); + + if (nexttoken()->tokentype != IT_EOL) + { + free(mailbox); + free(orig_mailbox); + maildir_info_destroy(&mi); + return (-1); + } + + if (!isdummy) + { + int did_exist; + maildir_aclt_list l; + + if ((did_exist=folder_exists(orig_mailbox)) != 0) + { + if (acl_read_folder(&l, + mi.homedir, + mi.maildir) < 0) + { + free(mailbox); + free(orig_mailbox); + writes(tag); + writes(" NO Cannot create this folder" + ".\r\n"); + maildir_info_destroy(&mi); + return (0); + } + maildir_acl_delete(mi.homedir, mi.maildir); + /* Clear out fluff */ + } + + if (mdcreate(mailbox)) + { + if (did_exist) + maildir_aclt_list_destroy(&l); + free(mailbox); + free(orig_mailbox); + writes(tag); + writes(" NO Cannot create this folder.\r\n"); + maildir_info_destroy(&mi); + return (0); + } + if (did_exist) + { + const char *acl_error; + + acl_write_folder(&l, mi.homedir, + mi.maildir, NULL, + &acl_error); + maildir_aclt_list_destroy(&l); + } + } + writes(tag); + writes(" OK \""); + writeqs(orig_mailbox); + writes("\" created.\r\n"); + + /* + ** This is a dummy call to acl_read_folder that initialized + ** the default ACLs for this folder to its parent. + */ + + { + CHECK_RIGHTSM(curtoken->tokenbuf, create_rights, + ACL_CREATE); + } + + imapscan_init(&minfo); + imapscan_maildir(&minfo, mailbox, 0,0, NULL); + imapscan_free(&minfo); + + free(mailbox); + free(orig_mailbox); + maildir_info_destroy(&mi); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "DELETE") == 0) + { + char *mailbox; + char *p; + char *mailbox_name; + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + p=strrchr(curtoken->tokenbuf, HIERCH); + if (p && p[1] == '\0') /* Ignore hierarchy DELETE */ + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + writes(tag); + writes(" OK Folder directory delete punted.\r\n"); + return (0); + } + + mailbox_name=my_strdup(curtoken->tokenbuf); + mailbox=parse_mailbox_error(tag, curtoken, 1, 0); + if ( mailbox == 0) + { + free(mailbox_name); + return (0); + } + + if (nexttoken()->tokentype != IT_EOL) + { + free(mailbox_name); + free(mailbox); + return (-1); + } + + if (current_mailbox && strcmp(mailbox, current_mailbox) == 0) + { + free(mailbox_name); + free(mailbox); + writes(tag); + writes(" NO Cannot delete currently-open folder.\r\n"); + return (0); + } + + if (strncmp(curtoken->tokenbuf, SHARED HIERCHS, + sizeof(SHARED HIERCHS)-1) == 0) + { + maildir_shared_unsubscribe(0, curtoken->tokenbuf+ + sizeof(SHARED HIERCHS)-1); + free(mailbox_name); + free(mailbox); + writes(tag); + writes(" OK UNSUBSCRIBEd a shared folder.\r\n"); + return (0); + } + + { + CHECK_RIGHTSM(curtoken->tokenbuf, + delete_rights, + ACL_DELETEFOLDER); + if (delete_rights[0] == 0) + { + free(mailbox_name); + free(mailbox); + writes(tag); + accessdenied("DELETE", + curtoken->tokenbuf, + ACL_DELETEFOLDER); + return 0; + } + } + + if (!broken_uidvs()) + sleep(2); /* Make sure we never recycle them*/ + + fetch_free_cache(); + + + if (do_folder_delete(mailbox_name)) + { + writes(tag); + writes(" NO Cannot delete this folder.\r\n"); + } + else + { + writes(tag); + writes(" OK Folder deleted.\r\n"); + } + + free(mailbox_name); + free(mailbox); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "RENAME") == 0) + { + char *p; + struct maildir_info mi1, mi2; + const char *errmsg; + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + writes(tag); + writes(" NO Invalid mailbox\r\n"); + return (0); + } + + if ((p=strrchr(curtoken->tokenbuf, HIERCH)) && p[1] == 0) + *p=0; + + if (maildir_info_imap_find(&mi1, curtoken->tokenbuf, + getenv("AUTHENTICATED")) < 0) + { + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (mi1.homedir == NULL || mi1.maildir == NULL) + { + maildir_info_destroy(&mi1); + writes(tag); + writes(" NO Invalid mailbox\r\n"); + return (0); + } + + { + CHECK_RIGHTSM(curtoken->tokenbuf, + rename_rights, ACL_DELETEFOLDER); + + if (rename_rights[0] == 0) + { + maildir_info_destroy(&mi1); + writes(tag); + accessdenied("RENAME", curtoken->tokenbuf, + ACL_DELETEFOLDER); + return (0); + } + } + + + curtoken=nexttoken_nouc(); + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + maildir_info_destroy(&mi1); + return (-1); + } + + if ((p=strrchr(curtoken->tokenbuf, HIERCH)) && p[1] == 0) + { + *p=0; + } + + + if (maildir_info_imap_find(&mi2, curtoken->tokenbuf, + getenv("AUTHENTICATED")) < 0) + { + maildir_info_destroy(&mi1); + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (check_parent_create(tag, "RENAME", curtoken->tokenbuf)) + { + maildir_info_destroy(&mi1); + maildir_info_destroy(&mi2); + return 0; + } + + if (nexttoken()->tokentype != IT_EOL) + { + maildir_info_destroy(&mi1); + maildir_info_destroy(&mi2); + return (-1); + } + + if (!broken_uidvs()) + sleep(2); + /* Make sure IMAP uidvs are not recycled */ + + if (folder_rename(&mi1, &mi2, &errmsg)) + { + writes(tag); + writes(" NO "); + writes(*errmsg == '@' ? errmsg+1:errmsg); + if (*errmsg == '@') + writes(strerror(errno)); + writes("\r\n"); + } + else + { + writes(tag); + writes(" OK Folder renamed.\r\n"); + } + + maildir_info_destroy(&mi1); + maildir_info_destroy(&mi2); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "SELECT") == 0 || + strcmp(curtoken->tokenbuf, "EXAMINE") == 0) + { + char *mailbox; + int ro=curtoken->tokenbuf[0] == 'E'; + const char *p; + + + curtoken=nexttoken_nouc(); + + if (current_mailbox) + { + free(current_mailbox); + imapscan_free(¤t_maildir_info); + imapscan_init(¤t_maildir_info); + current_mailbox=0; + } + + if (current_mailbox_acl) + free(current_mailbox_acl); + current_mailbox_acl=0; + + mailbox=parse_mailbox_error(tag, curtoken, 0, 1); + if ( mailbox == 0) + return (0); + + current_mailbox_acl=get_myrightson(curtoken->tokenbuf); + if (current_mailbox_acl == NULL) + { + free(mailbox); + writes(tag); + writes(" NO Unable to read ACLs for "); + writes(curtoken->tokenbuf); + writes(": "); + writes(strerror(errno)); + writes("\r\n"); + return 0; + } + + if (strchr(current_mailbox_acl, ACL_READ[0]) == NULL) + { + free(mailbox); + free(current_mailbox_acl); + current_mailbox_acl=NULL; + writes(tag); + accessdenied("SELECT/EXAMINE", curtoken->tokenbuf, + ACL_READ); + return 0; + } + + if (nexttoken()->tokentype != IT_EOL) + { + free(mailbox); + return (-1); + } + + if (imapscan_maildir(¤t_maildir_info, mailbox, 0, ro, + NULL)) + { + free(mailbox); + writes(tag); + writes(" NO Unable to open this mailbox.\r\n"); + return (0); + } + current_mailbox=mailbox; + + /* check if this is a shared read-only folder */ + + if (is_sharedsubdir(mailbox) && + maildir_sharedisro(mailbox)) + ro=1; + + current_mailbox_ro=ro; + + mailboxflags(ro); + mailboxmetrics(); + writes("* OK [UIDVALIDITY "); + writen(current_maildir_info.uidv); + writes("] Ok\r\n"); + myrights(); + writes(tag); + + for (p=current_mailbox_acl; *p; p++) + if (strchr(ACL_INSERT ACL_EXPUNGE + ACL_SEEN ACL_WRITE ACL_DELETEMSGS, + *p)) + break; + + if (*p == 0) + ro=1; + + writes(ro ? " OK [READ-ONLY] Ok\r\n":" OK [READ-WRITE] Ok\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "SUBSCRIBE") == 0) + { + char *mailbox; + char *p; + struct maildir_info mi; + + curtoken=nexttoken_nouc(); + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + p=strrchr(curtoken->tokenbuf, HIERCH); + if (p && p[1] == '\0') /* Ignore hierarchy DELETE */ + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + writes(tag); + writes(" OK Folder directory subscribe punted.\r\n"); + return (0); + } + + mailbox=my_strdup(curtoken->tokenbuf); + if (nexttoken()->tokentype != IT_EOL) + return (-1); + + if (maildir_info_imap_find(&mi, mailbox, + getenv("AUTHENTICATED")) < 0) + { + free(mailbox); + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (mi.mailbox_type != MAILBOXTYPE_OLDSHARED) + { + maildir_info_destroy(&mi); + subscribe(mailbox); + free(mailbox); + writes(tag); + writes(" OK Folder subscribed.\r\n"); + return (0); + } + maildir_info_destroy(&mi); + + p=strchr(mailbox, '.'); + + p=p ? maildir_shareddir(".", p+1):NULL; + + if (p == NULL || access(p, 0) == 0) + { + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" OK Already subscribed.\r\n"); + return (0); + } + + if (!p || maildir_shared_subscribe(0, strchr(mailbox, '.')+1)) + { + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" NO Cannot subscribe to this folder.\r\n"); + return (0); + } + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" OK SUBSCRIBE completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "UNSUBSCRIBE") == 0) + { + char *mailbox; + char *p; + struct maildir_info mi; + + curtoken=nexttoken_nouc(); + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + p=strrchr(curtoken->tokenbuf, HIERCH); + if (p && p[1] == '\0') /* Ignore hierarchy DELETE */ + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + writes(tag); + writes(" OK Folder directory unsubscribe punted.\r\n"); + return (0); + } + + mailbox=my_strdup(curtoken->tokenbuf); + if (nexttoken()->tokentype != IT_EOL) + return (-1); + + if (maildir_info_imap_find(&mi, mailbox, + getenv("AUTHENTICATED")) < 0) + { + free(mailbox); + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (mi.mailbox_type != MAILBOXTYPE_OLDSHARED) + { + maildir_info_destroy(&mi); + unsubscribe(mailbox); + free(mailbox); + writes(tag); + writes(" OK Folder unsubscribed.\r\n"); + return (0); + } + maildir_info_destroy(&mi); + + p=strchr(mailbox, '.'); + + p=p ? maildir_shareddir(".", p+1):NULL; + + + if (p == NULL || access(p, 0)) + { + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" OK Already unsubscribed.\r\n"); + return (0); + } + + fetch_free_cache(); + + if (!p || maildir_shared_unsubscribe(0, + strchr(mailbox, '.')+1)) + { + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" NO Cannot subscribe to this folder.\r\n"); + return (0); + } + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" OK UNSUBSCRIBE completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "NAMESPACE") == 0) + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + writes("* NAMESPACE ((\"INBOX.\" \".\")) NIL " + "((\"#shared.\" \".\")(\"" + SHARED ".\" \".\"))\r\n"); + writes(tag); + writes(" OK NAMESPACE completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "ACL") == 0) + { + if (aclcmd(tag)) + { + writes(tag); + writes(" ACL FAILED: "); + writes(strerror(errno)); + writes("\r\n"); + } + return 0; + } + + /* RFC 2086 */ + + if (strcmp(curtoken->tokenbuf, "SETACL") == 0 || + strcmp(curtoken->tokenbuf, "DELETEACL") == 0) + { + char *mailbox; + char *identifier; + struct maildir_info mi; + maildir_aclt_list aclt_list; + const char *acl_error; + int doset=curtoken->tokenbuf[0] == 'S'; + const char *origcmd=doset ? "SETACL":"DELETEACL"; + + curtoken=nexttoken_nouc(); + + mailbox=parse_mailbox_error(tag, curtoken, 0, 0); + if (!mailbox) + return 0; + free(mailbox); + + mailbox=my_strdup(curtoken->tokenbuf); + + if (maildir_info_imap_find(&mi, mailbox, + getenv("AUTHENTICATED")) < 0) + { + writes(tag); + writes(" NO Invalid mailbox.\r\n"); + free(mailbox); + return 0; + } + + if (mi.homedir == NULL || mi.maildir == NULL) + { + maildir_info_destroy(&mi); + writes(tag); + writes(" NO Cannot set ACLs for this mailbox\r\n"); + free(mailbox); + return 0; + } + + switch ((curtoken=nexttoken_nouc())->tokentype) { + case IT_QUOTED_STRING: + case IT_ATOM: + case IT_NUMBER: + break; + default: + maildir_info_destroy(&mi); + free(mailbox); + return -1; + } + + identifier=acl2_identifier(tag, curtoken->tokenbuf); + + if (identifier == NULL) + { + maildir_info_destroy(&mi); + free(mailbox); + return 0; + } + + if (doset) + { + switch ((curtoken=nexttoken_nouc())->tokentype) { + case IT_QUOTED_STRING: + case IT_ATOM: + case IT_NUMBER: + break; + default: + free(identifier); + maildir_info_destroy(&mi); + free(mailbox); + return -1; + } + } + + { + CHECK_RIGHTSM(mailbox, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + writes(tag); + accessdenied(origcmd, mailbox, + ACL_ADMINISTER); + free(identifier); + maildir_info_destroy(&mi); + free(mailbox); + return 0; + } + } + + if (acl_read_folder(&aclt_list, mi.homedir, mi.maildir)) + { + writes(tag); + writes(" NO Cannot read existing ACLs.\r\n"); + free(identifier); + maildir_info_destroy(&mi); + free(mailbox); + return 0; + } + + if (do_acl_mod(&aclt_list, &mi, identifier, + doset ? curtoken->tokenbuf:"", + &acl_error) < 0) + { + writes(tag); + writes(acl_error ? + " NO Cannot modify ACLs as requested.\r\n" : + " NO Cannot modify ACLs on this mailbox.\r\n"); + } + else + { + char *p=get_myrightson(mailbox); + + if (p) + free(p); + /* Side effect - change current folder's ACL */ + + writes(tag); + writes(" OK ACLs updated.\r\n"); + } + + maildir_aclt_list_destroy(&aclt_list); + maildir_info_destroy(&mi); + free(identifier); + free(mailbox); + return 0; + } + + if (strcmp(curtoken->tokenbuf, "GETACL") == 0) + { + maildir_aclt_list l; + char *mailbox_owner; + char *mb; + + curtoken=nexttoken_nouc(); + + mb=parse_mailbox_error(tag, curtoken, 0, 0); + if (!mb) + return 0; + free(mb); + + { + CHECK_RIGHTSM(curtoken->tokenbuf, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + writes(tag); + accessdenied("GETACL", curtoken->tokenbuf, + ACL_ADMINISTER); + return 0; + } + } + + if (get_acllist(&l, curtoken->tokenbuf, + &mailbox_owner) < 0) + { + writes(tag); + writes(" NO Cannot retrieve ACLs for mailbox.\r\n"); + return 0; + } + free(mailbox_owner); + + writes("* ACL \""); + writeqs(curtoken->tokenbuf); + writes("\""); + maildir_aclt_list_enum(&l, getacl_cb, NULL); + writes("\r\n"); + writes(tag); + writes(" OK GETACL completed.\r\n"); + maildir_aclt_list_destroy(&l); + return 0; + } + + if (strcmp(curtoken->tokenbuf, "LISTRIGHTS") == 0) + { + maildir_aclt_list l; + char *mailbox_owner; + char *mb; + + curtoken=nexttoken_nouc(); + + mb=parse_mailbox_error(tag, curtoken, 0, 0); + if (!mb) + return 0; + free(mb); + + { + char *myrights=get_myrightson(curtoken->tokenbuf); + + if (!strchr(myrights, ACL_LOOKUP[0]) && + !strchr(myrights, ACL_READ[0]) && + !strchr(myrights, ACL_INSERT[0]) && + !strchr(myrights, ACL_CREATE[0]) && + !strchr(myrights, ACL_DELETEFOLDER[0]) && + !strchr(myrights, ACL_EXPUNGE[0]) && + !strchr(myrights, ACL_ADMINISTER[0])) + { + free(myrights); + writes(tag); + accessdenied("GETACL", curtoken->tokenbuf, + ACL_ADMINISTER); + return 0; + } + free(myrights); + } + + if (get_acllist(&l, curtoken->tokenbuf, + &mailbox_owner) < 0) + { + writes(tag); + writes(" NO Cannot retrieve ACLs for mailbox.\r\n"); + return 0; + } + + mb=my_strdup(curtoken->tokenbuf); + + switch ((curtoken=nexttoken_nouc())->tokentype) { + case IT_QUOTED_STRING: + case IT_ATOM: + case IT_NUMBER: + break; + default: + free(mb); + free(mailbox_owner); + maildir_aclt_list_destroy(&l); + return -1; + } + + writes("* LISTRIGHTS \""); + writeqs(mb); + writes("\" \""); + writeqs(curtoken->tokenbuf); + writes("\""); + free(mb); + + + if (curtoken->tokenbuf[0] == '-' && + (MAILDIR_ACL_ANYONE(curtoken->tokenbuf+1) || + (strncmp(mailbox_owner, "user=", 5) == 0 && + strcmp(curtoken->tokenbuf+1, mailbox_owner+5) == 0))) + { + writes(" \"\" " + ACL_CREATE " " + ACL_DELETE_SPECIAL " " + ACL_INSERT " " + ACL_POST " " + ACL_READ " " + ACL_SEEN " " + ACL_WRITE "\r\n"); + } + else if (strncmp(mailbox_owner, "user=", 5) == 0 && + strcmp(curtoken->tokenbuf, mailbox_owner+5) == 0) + { + writes(" \"" + ACL_ADMINISTER + ACL_LOOKUP "\" " + ACL_CREATE " " + ACL_DELETE_SPECIAL " " + ACL_INSERT " " + ACL_POST " " + ACL_READ " " + ACL_SEEN " " + ACL_WRITE "\r\n"); + } + else + { + writes(" \"\" " + ACL_ADMINISTER " " + ACL_CREATE " " + ACL_DELETE_SPECIAL " " + ACL_INSERT " " + ACL_LOOKUP " " + ACL_POST " " + ACL_READ " " + ACL_SEEN " " + ACL_WRITE "\r\n"); + } + writes(tag); + writes(" OK LISTRIGHTS completed.\r\n"); + free(mailbox_owner); + maildir_aclt_list_destroy(&l); + return 0; + } + + if (strcmp(curtoken->tokenbuf, "MYRIGHTS") == 0) + { + char *mb; + + curtoken=nexttoken_nouc(); + + mb=parse_mailbox_error(tag, curtoken, 0, 0); + if (!mb) + return 0; + free(mb); + + { + char *myrights=get_myrightson(curtoken->tokenbuf); + + if (!strchr(myrights, ACL_LOOKUP[0]) && + !strchr(myrights, ACL_READ[0]) && + !strchr(myrights, ACL_INSERT[0]) && + !strchr(myrights, ACL_CREATE[0]) && + !strchr(myrights, ACL_DELETEFOLDER[0]) && + !strchr(myrights, ACL_EXPUNGE[0]) && + !strchr(myrights, ACL_ADMINISTER[0])) + { + free(myrights); + writes(tag); + accessdenied("GETACL", curtoken->tokenbuf, + ACL_ADMINISTER); + return 0; + } + free(myrights); + } + + mb=get_myrightson(curtoken->tokenbuf); + + if (!mb) + { + writes(tag); + writes(" NO Cannot retrieve ACLs for mailbox.\r\n"); + return 0; + } + + writes("* MYRIGHTS \""); + writeqs(curtoken->tokenbuf); + writes("\" \""); + + writeacl1(mb); + free(mb); + writes("\"\r\n"); + writes(tag); + writes(" OK MYRIGHTS completed.\r\n"); + return 0; + } + + /* mailbox commands */ + + if (current_mailbox == 0) return (-1); + + if (strcmp(curtoken->tokenbuf, "UID") == 0) + { + uid=1; + if ((curtoken=nexttoken())->tokentype != IT_ATOM) + return (-1); + if (strcmp(curtoken->tokenbuf, "COPY") && + strcmp(curtoken->tokenbuf, "FETCH") && + strcmp(curtoken->tokenbuf, "SEARCH") && + strcmp(curtoken->tokenbuf, "THREAD") && + strcmp(curtoken->tokenbuf, "SORT") && + strcmp(curtoken->tokenbuf, "STORE") && + strcmp(curtoken->tokenbuf, "EXPUNGE")) + return (-1); + } + + if (strcmp(curtoken->tokenbuf, "CLOSE") == 0) + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + + if (!current_mailbox_ro + && strchr(current_mailbox_acl, ACL_EXPUNGE[0])) + expunge(); + free(current_mailbox); + imapscan_free(¤t_maildir_info); + imapscan_init(¤t_maildir_info); + current_mailbox=0; + writes(tag); + writes(" OK mailbox closed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "FETCH") == 0) + { + struct fetchinfo *fi; + char *msgset; + + curtoken=nexttoken(); + if (!ismsgset(curtoken)) return (-1); + msgset=my_strdup(curtoken->tokenbuf); + + if ((curtoken=nexttoken())->tokentype != IT_LPAREN) + { + if (curtoken->tokentype != IT_ATOM) + { + free(msgset); + return (-1); + } + fi=fetchinfo_alloc(1); + } + else + { + (void)nexttoken(); + fi=fetchinfo_alloc(0); + if (fi && currenttoken()->tokentype != IT_RPAREN) + { + fetchinfo_free(fi); + fi=0; + } + nexttoken(); + } + + if (fi == 0 || currenttoken()->tokentype != IT_EOL) + { + free(msgset); + if (fi) fetchinfo_free(fi); + return (-1); + } + + do_msgset(msgset, &do_fetch, fi, uid); + fetchinfo_free(fi); + free(msgset); + writes(tag); + writes(" OK FETCH completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "STORE") == 0) + { + char *msgset; + struct storeinfo storeinfo_s; + + curtoken=nexttoken(); + if (!ismsgset(curtoken)) return (-1); + msgset=my_strdup(curtoken->tokenbuf); + + (void)nexttoken(); + current_maildir_info.keywordList->keywordAddedRemoved=0; + + if (storeinfo_init(&storeinfo_s) || + currenttoken()->tokentype != IT_EOL) + { + if (storeinfo_s.keywords) + libmail_kwmDestroy(storeinfo_s.keywords); + free(msgset); + return (-1); + } + + /* Do not change \Deleted if this is a readonly mailbox */ + + if (current_mailbox_ro && storeinfo_s.flags.deleted) + { + if (storeinfo_s.keywords) + libmail_kwmDestroy(storeinfo_s.keywords); + free(msgset); + writes(tag); + writes(" NO Current box is selected READ-ONLY.\r\n"); + return (0); + } + + fetch_free_cache(); + + if (current_maildir_info.keywordList->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); + + current_maildir_info.keywordList->keywordAddedRemoved=0; + + if (do_msgset(msgset, &do_store, &storeinfo_s, uid)) + { + if (storeinfo_s.keywords) + libmail_kwmDestroy(storeinfo_s.keywords); + free(msgset); + if (current_maildir_info.keywordList + ->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); + + writes(tag); + writes(" NO [ALERT] You exceeded your mail quota.\r\n"); + return (0); + } + if (storeinfo_s.keywords) + { + struct imap_addRemoveKeywordInfo imapInfo; + + switch (storeinfo_s.plusminus) { + case '+': + case '-': + + imapInfo.msgset=msgset; + imapInfo.uid=uid; + + if (!fastkeywords() && + addRemoveKeywords(&imap_addRemoveKeywords, + &imapInfo, &storeinfo_s)) + { + libmail_kwmDestroy(storeinfo_s.keywords); + free(msgset); + writes(tag); + writes(" NO An error occured while" + " updating keywords: "); + writes(strerror(errno)); + writes(".\r\n"); + return 0; + } + break; + } + + libmail_kwmDestroy(storeinfo_s.keywords); + } + free(msgset); + if (current_maildir_info.keywordList->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); + writes(tag); + writes(" OK STORE completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "SEARCH") == 0) + { + char *charset=0; + struct searchinfo *si, *sihead; + unsigned long i; + + curtoken=nexttoken_okbracket(); + if (curtoken->tokentype == IT_ATOM && + strcmp(curtoken->tokenbuf, "CHARSET") == 0) + { + curtoken=nexttoken(); + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + charset=my_strdup(curtoken->tokenbuf); + curtoken=nexttoken(); + } + + if (validate_charset(tag, &charset)) + { + if (charset) + free(charset); + return (0); + } + + if ((si=alloc_parsesearch(&sihead)) == 0) + { + free(charset); + return (-1); + } + if (currenttoken()->tokentype != IT_EOL) + { + free(charset); + free_search(sihead); + return (-1); + } + +#if 0 + writes("* OK "); + debug_search(si); + writes("\r\n"); +#endif + writes("* SEARCH"); + dosearch(si, sihead, charset, uid); + writes("\r\n"); + free(charset); + + for (i=0; i<current_maildir_info.nmessages; i++) + if (current_maildir_info.msgs[i].changedflags) + fetchflags(i); + writes(tag); + writes(" OK SEARCH done.\r\n"); + free_search(sihead); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "THREAD") == 0) + { + char *charset=0; + struct searchinfo *si, *sihead; + unsigned long i; + + /* The following jazz is mainly for future extensions */ + + void (*thread_func)(struct searchinfo *, struct searchinfo *, + const char *, int); + search_type thread_type; + + { + const char *p=getenv("IMAP_DISABLETHREADSORT"); + int n= p ? atoi(p):0; + + if (n > 0) + { + writes(tag); + writes(" NO This command is disabled by the system administrator.\r\n"); + return (0); + } + } + + curtoken=nexttoken(); + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + if (strcmp(curtoken->tokenbuf, "ORDEREDSUBJECT") == 0) + { + thread_func=dothreadorderedsubj; + thread_type=search_orderedsubj; + } + else if (strcmp(curtoken->tokenbuf, "REFERENCES") == 0) + { + thread_func=dothreadreferences; + thread_type=search_references1; + } + else + { + return (-1); + } + + curtoken=nexttoken(); + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + charset=my_strdup(curtoken->tokenbuf); + curtoken=nexttoken(); + + if ((si=alloc_parsesearch(&sihead)) == 0) + { + if (charset) free(charset); + return (-1); + } + + si=alloc_searchextra(si, &sihead, thread_type); + + if (currenttoken()->tokentype != IT_EOL) + { + if (charset) free(charset); + free_search(sihead); + return (-1); + } + + if (validate_charset(tag, &charset)) + { + if (charset) + free(charset); + return (0); + } + + writes("* THREAD "); + (*thread_func)(si, sihead, charset, uid); + writes("\r\n"); + free(charset); + + for (i=0; i<current_maildir_info.nmessages; i++) + if (current_maildir_info.msgs[i].changedflags) + fetchflags(i); + writes(tag); + writes(" OK THREAD done.\r\n"); + free_search(sihead); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "SORT") == 0) + { + char *charset=0; + struct searchinfo *si, *sihead; + unsigned long i; + struct temp_sort_stack *ts=0; + + { + const char *p=getenv("IMAP_DISABLETHREADSORT"); + int n= p ? atoi(p):0; + + if (n > 0) + { + writes(tag); + writes(" NO This command is disabled by the system administrator.\r\n"); + return (0); + } + } + + curtoken=nexttoken(); + if (curtoken->tokentype != IT_LPAREN) return (-1); + while ((curtoken=nexttoken())->tokentype != IT_RPAREN) + { + search_type st; + struct temp_sort_stack *newts; + + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + free_temp_sort_stack(ts); + return (-1); + } + + if (strcmp(curtoken->tokenbuf, "SUBJECT") == 0) + { + st=search_orderedsubj; + } + else if (strcmp(curtoken->tokenbuf, "ARRIVAL") == 0) + { + st=search_arrival; + } + else if (strcmp(curtoken->tokenbuf, "CC") == 0) + { + st=search_cc; + } + else if (strcmp(curtoken->tokenbuf, "DATE") == 0) + { + st=search_date; + } + else if (strcmp(curtoken->tokenbuf, "FROM") == 0) + { + st=search_from; + } + else if (strcmp(curtoken->tokenbuf, "REVERSE") == 0) + { + st=search_reverse; + } + else if (strcmp(curtoken->tokenbuf, "SIZE") == 0) + { + st=search_size; + } + else if (strcmp(curtoken->tokenbuf, "TO") == 0) + { + st=search_to; + } + else + { + free_temp_sort_stack(ts); + return (-1); + } + + newts=(struct temp_sort_stack *)malloc( + sizeof(struct temp_sort_stack)); + if (!newts) write_error_exit(0); + newts->next=ts; + newts->type=st; + ts=newts; + } + + if (ts == 0 /* No criteria */ + || ts->type == search_reverse) + /* Can't end with the REVERSE keyword */ + { + free_temp_sort_stack(ts); + return (-1); + } + + curtoken=nexttoken(); + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + free_temp_sort_stack(ts); + return (-1); + } + + charset=my_strdup(curtoken->tokenbuf); + curtoken=nexttoken(); + + if ((si=alloc_parsesearch(&sihead)) == 0) + { + if (charset) free(charset); + free_temp_sort_stack(ts); + return (-1); + } + + while (ts) + { + struct temp_sort_stack *cts=ts; + + ts=cts->next; + si=alloc_searchextra(si, &sihead, cts->type); + free(cts); + } + + if (currenttoken()->tokentype != IT_EOL) + { + if (charset) free(charset); + free_search(sihead); + return (-1); + } + + if (validate_charset(tag, &charset)) + { + if (charset) free(charset); + free_search(sihead); + return (0); + } + + writes("* SORT"); + dosortmsgs(si, sihead, charset, uid); + writes("\r\n"); + free(charset); + + for (i=0; i<current_maildir_info.nmessages; i++) + if (current_maildir_info.msgs[i].changedflags) + fetchflags(i); + writes(tag); + writes(" OK SORT done.\r\n"); + free_search(sihead); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "CHECK") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + doNoop(0); + writes(tag); + writes(" OK CHECK completed\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "EXPUNGE") == 0) + { + if (strchr(current_mailbox_acl, ACL_EXPUNGE[0]) == NULL) + { + writes(tag); + accessdenied("EXPUNGE", "current mailbox", + ACL_EXPUNGE); + return 0; + } + + if (current_mailbox_ro) + { + writes(tag); + writes(" NO Cannot expunge read-only mailbox.\r\n"); + return 0; + } + + if (uid) + { + char *msgset; + + curtoken=nexttoken(); + if (!ismsgset(curtoken)) return (-1); + msgset=my_strdup(curtoken->tokenbuf); + if (nexttoken()->tokentype != IT_EOL) return (-1); + + do_msgset(msgset, &uid_expunge, NULL, 1); + free(msgset); + } + else + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + expunge(); + } + doNoop(0); + writes(tag); + writes(" OK EXPUNGE completed\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "COPY") == 0) + { + struct maildirsize quotainfo; + char *mailbox; + char *msgset; + struct copyquotainfo cqinfo; + int has_quota; + int isshared; + struct do_copy_info copy_info; + unsigned long copy_uidv; + char access_rights[8]; + + curtoken=nexttoken(); + if (!ismsgset(curtoken)) return (-1); + msgset=my_strdup(curtoken->tokenbuf); + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + free(msgset); + return (-1); + } + + mailbox=decode_valid_mailbox(curtoken->tokenbuf, 1); + + if (!mailbox) + { + struct maildir_info mi; + + free(msgset); + + if (maildir_info_imap_find(&mi, curtoken->tokenbuf, + getenv("AUTHENTICATED")) + == 0) + { + if (nexttoken()->tokentype == IT_EOL) + { + maildir_info_destroy(&mi); + writes(tag); + writes(" NO [TRYCREATE] Mailbox does not exist.\r\n"); + return (0); + } + maildir_info_destroy(&mi); + } + return (-1); + } + + { + CHECK_RIGHTSM(curtoken->tokenbuf, + append_rights, + ACL_INSERT ACL_DELETEMSGS + ACL_SEEN ACL_WRITE); + + if (strchr(append_rights, ACL_INSERT[0]) == NULL) + { + writes(tag); + accessdenied("COPY", + curtoken->tokenbuf, + ACL_INSERT); + return 0; + } + + strcpy(access_rights, append_rights); + } + + if (nexttoken()->tokentype != IT_EOL) + { + free(msgset); + return (-1); + } + + if (access(mailbox, 0)) + { + writes(tag); + writes(" NO [TRYCREATE] Mailbox does not exist.\r\n"); + free(msgset); + free(mailbox); + return (0); + } + + fetch_free_cache(); + cqinfo.destmailbox=mailbox; + cqinfo.acls=access_rights; + + /* + ** If the destination is a shared folder, copy it into the + ** real shared folder. + */ + + isshared=0; + if (is_sharedsubdir(cqinfo.destmailbox)) + { + char *p=malloc(strlen(cqinfo.destmailbox)+sizeof("/shared")); + + if (!p) write_error_exit(0); + strcat(strcpy(p, cqinfo.destmailbox), "/shared"); + + free(mailbox); + mailbox=cqinfo.destmailbox=p; + isshared=1; + } + + cqinfo.nbytes=0; + cqinfo.nfiles=0; + + has_quota=0; + if (!isshared && maildirquota_countfolder(cqinfo.destmailbox)) + { + if (maildir_openquotafile("ainfo, + ".") == 0) + { + if (quotainfo.fd >= 0) + has_quota=1; + maildir_closequotafile("ainfo); + } + + if (has_quota > 0 && + do_msgset(msgset, &do_copy_quota_calc, &cqinfo, + uid)) + has_quota= -1; + } + + if (has_quota > 0 && cqinfo.nfiles > 0) + { + + if (maildir_quota_add_start(".", "ainfo, + cqinfo.nbytes, + cqinfo.nfiles, + getenv("MAILDIRQUOTA"))) + { + writes(tag); + writes( + " NO [ALERT] You exceeded your mail quota.\r\n"); + free(msgset); + free(mailbox); + return (0); + } + + maildir_quota_add_end("ainfo, + cqinfo.nbytes, + cqinfo.nfiles); + } + + if (is_outbox(mailbox)) + { + int counter=0; + + if (do_msgset(msgset, &do_count, &counter, uid) || + counter > 1) + { + writes(tag); + writes(" NO [ALERT] Only one message may be sent at a time.\r\n"); + free(msgset); + free(mailbox); + return (0); + } + } + + copy_info.mailbox=mailbox; + copy_info.uidplus_list=NULL; + copy_info.uidplus_tail= ©_info.uidplus_list; + copy_info.acls=access_rights; + + if (has_quota < 0 || + do_msgset(msgset, &do_copy_message, ©_info, uid) || + uidplus_fill(copy_info.mailbox, copy_info.uidplus_list, + ©_uidv)) + { + uidplus_abort(copy_info.uidplus_list); + writes(tag); + writes(" NO [ALERT] COPY failed - no write permission or out of disk space.\r\n"); + free(msgset); + free(mailbox); + return (0); + } + + dirsync(mailbox); + + writes(tag); + writes(" OK"); + + if (copy_info.uidplus_list != NULL) + { + writes(" [COPYUID "); + writen(copy_uidv); + uidplus_writemsgset(copy_info.uidplus_list, 0); + uidplus_writemsgset(copy_info.uidplus_list, 1); + writes("]"); + } + + writes(" COPY completed.\r\n"); + uidplus_free(copy_info.uidplus_list); + + free(msgset); + free(mailbox); + return (0); + } + return (-1); +} + +static void dogethostname() +{ +char buf[2048]; +char *p; + + if (gethostname(buf, sizeof(buf)) < 0) + strcpy(buf, "courier-imap"); + p=malloc(strlen(buf)+sizeof("HOSTNAME=")); + if (!p) + write_error_exit(0); + strcat(strcpy(p, "HOSTNAME="), buf); + putenv(p); +} + +#if 0 +static char *getcurdir() +{ +char *pathbuf=0; +size_t pathlen=256; + + for (;;) + { + if ((pathbuf=pathbuf ? realloc(pathbuf, pathlen): + malloc(pathlen)) == 0) + write_error_exit(0); + if (getcwd(pathbuf, pathlen-1)) + return (pathbuf); + if (errno != ERANGE) + { + free(pathbuf); + return (0); + } + pathlen += 256; + } +} + +static char *getimapscanpath(const char *argv0) +{ +char *p, *q; + + if (*argv0 != '/') + { + p=getcurdir(); + if (!p) + { + perror("getcwd"); + exit(1); + } + q=malloc(strlen(p)+strlen(argv0)+sizeof("//imapscan")); + if (!q) write_error_exit(0); + strcat(strcat(strcpy(q, p), "/"), argv0); + } + else + { + q=malloc(strlen(argv0)+sizeof("imapscan")); + if (!q) write_error_exit(0); + strcpy(q, argv0); + } + p=strrchr(q, '/'); + if (p && p[1] == 0) + { + *p=0; + p=strrchr(q, '/'); + } + + if (p) + p[1]=0; + else *q=0; + + strcat(q, "imapscan"); + return (q); +} +#endif + +static void chkdisabled(const char *ip, const char *port) +{ + const char *p; + if (auth_getoptionenvint("disableimap")) + { + writes("* BYE IMAP access disabled for this account.\r\n"); + writeflush(); + exit(0); + } + + if ( auth_getoptionenvint("disableinsecureimap") + && ((p=getenv("IMAP_TLS")) == NULL || !atoi(p))) + { + writes("* BYE IMAP access disabled via insecure connection.\r\n"); + writeflush(); + exit(0); + } + + fprintf(stderr, "INFO: LOGIN, user=%s, ip=[%s], port=[%s], protocol=%s\n", + getenv("AUTHENTICATED"), ip, port, + protocol); +} + +static int chk_clock_skew() +{ + static const char fn[]="tmp/courier-imap.clockskew.chk"; + struct stat stat_buf; + int fd; + time_t t; + + unlink(fn); + fd=open(fn, O_RDWR|O_TRUNC|O_CREAT, 0666); + time(&t); + + if (fd < 0) + return 0; /* Something else is wrong */ + + if (fstat(fd, &stat_buf) < 0) + { + close(fd); + return -1; /* Something else is wrong */ + } + close(fd); + unlink(fn); + + if (stat_buf.st_mtime < t - 30 || stat_buf.st_mtime > t+30) + return -1; + return 0; +} + +#if SMAP + +static int is_smap() +{ + const char *p; + + p=getenv("PROTOCOL"); + + if (p && strcmp(p, "SMAP1") == 0) + return 1; + return 0; +} + +#else + +#define is_smap() 0 + +#endif + + +int main(int argc, char **argv) +{ + const char *ip; + const char *p; + const char *tag; + const char *port; + mode_t oldumask; + +#ifdef HAVE_SETVBUF_IOLBF + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); +#endif + time(&start_time); + if (argc > 1 && strcmp(argv[1], "--version") == 0) + { + printf("%s\n", PROGRAMVERSION); + exit(0); + } + + if ((tag=getenv("IMAPLOGINTAG")) != 0) + { + if (getenv("AUTHENTICATED") == NULL) + { + printf("* BYE AUTHENTICATED environment variable not set.\r\n"); + fflush(stdout); + exit(0); + } + } + else + { + const char *p; + + putenv("TCPREMOTEIP=127.0.0.1"); + putenv("TCPREMOTEPORT=0"); + + p=getenv("AUTHENTICATED"); + if (!p || !*p) + { + struct passwd *pw=getpwuid(getuid()); + char *me; + + if (!pw) + { + fprintf(stderr, + "ERR: uid %lu not found in passwd file\n", + (unsigned long)getuid()); + exit(1); + } + + me=malloc(sizeof("AUTHENTICATED=")+strlen(pw->pw_name)); + if (!me) + write_error_exit(0); + + strcat(strcpy(me, "AUTHENTICATED="), pw->pw_name); + putenv(me); + } + } + +#if HAVE_SETLOCALE + setlocale(LC_CTYPE, "C"); +#endif + + ip=getenv("TCPREMOTEIP"); + if (!ip || !*ip) exit(0); + + port=getenv("TCPREMOTEPORT"); + if (!port || !*port) exit(0); + + protocol=getenv("PROTOCOL"); + + if (!protocol || !*protocol) + protocol="IMAP"; + + putenv("IMAP_STARTTLS=NO"); /* No longer grok STARTTLS */ + + /* We use select() with a timeout, so use non-blocking filedescs */ + + if (fcntl(0, F_SETFL, O_NONBLOCK) || + fcntl(1, F_SETFL, O_NONBLOCK)) + { + perror("fcntl"); + exit(1); + } + + { + struct stat buf; + + if ( stat(".", &buf) < 0 || buf.st_mode & S_ISVTX) + { + fprintf(stderr, "INFO: LOCKED, user=%s, ip=[%s], port=[%s]\n", + getenv("AUTHENTICATED"), ip, port); + + if (is_smap()) + writes("-ERR "); + else + writes("* BYE "); + + writes("Your account is temporarily unavailable (+t bit set on home directory).\r\n"); + writeflush(); + exit(0); + } + } + + if (argc > 1) + p=argv[1]; + else + p=getenv("MAILDIR"); + + if (!p) + p="./Maildir"; +#if 0 + imapscanpath=getimapscanpath(argv[0]); +#endif + if (chdir(p)) + { + fprintf(stderr, "chdir %s: %s\n", p, strerror(errno)); + write_error_exit(strerror(errno)); + } + maildir_loginexec(); + + if (auth_getoptionenvint("disableshared")) + { + maildir_acl_disabled=1; + maildir_newshared_disabled=1; + } + + /* Remember my device/inode */ + + { + struct stat buf; + + if ( stat(".", &buf) < 0) + write_error_exit("Cannot stat current directory"); + + homedir_dev=buf.st_dev; + homedir_ino=buf.st_ino; + + errno=0; + + p=getenv("IMAP_MAILBOX_SANITY_CHECK"); + + if (!p || !*p) p="1"; + + if (atoi(p)) + { + if ( buf.st_uid != geteuid() || + buf.st_gid != getegid()) + write_error_exit("Account's mailbox directory is not owned by the correct uid or gid"); + } + } + + p=getenv("HOSTNAME"); + if (!p) + dogethostname(); + + if ((p=getenv("IMAP_TRASHFOLDERNAME")) != 0 && *p) + { + trash = strdup(p); + dot_trash = malloc(strlen(trash) + 2); + dot_trash[0] = '.'; + strcpy(&dot_trash[1], trash); + } + +#if 0 + mdcreate("." DRAFTS); +#endif + + if ((p=getenv("IMAPDEBUGFILE")) != 0 && *p && + access(p, 0) == 0) + { + oldumask = umask(027); + debugfile=fopen(p, "a"); + umask(oldumask); + if (debugfile==NULL) + write_error_exit(0); + } + initcapability(); + + emptytrash(); + signal(SIGPIPE, SIG_IGN); + + libmail_kwVerbotten=KEYWORD_IMAPVERBOTTEN; + libmail_kwCaseSensitive=0; + + if (!keywords()) + libmail_kwEnabled=0; + + maildir_info_munge_complex((p=getenv("IMAP_SHAREDMUNGENAMES")) && + atoi(p)); + +#if SMAP + if (is_smap()) + { + if (chk_clock_skew()) + { + writes("-ERR Clock skew detected. Check the clock on the file server\r\n"); + writeflush(); + exit(0); + } + + writes("+OK SMAP1 LOGIN Ok.\n"); + + smapflag=1; + + libmail_kwVerbotten=KEYWORD_SMAPVERBOTTEN; + libmail_kwCaseSensitive=1; + + chkdisabled(ip, port); + smap(); + logoutmsg(); + emptytrash(); + return (0); + } +#endif + + if (chk_clock_skew()) + { + writes("* BYE Clock skew detected. Check the clock on the file server\r\n"); + writeflush(); + exit(0); + } + + { + struct maildirwatch *w; + + if ((w=maildirwatch_alloc(".")) == NULL) + { + writes("* OK [ALERT] Filesystem notification initialization error -- contact your mail administrator (check for configuration errors with the FAM/Gamin library)\r\n"); + } + else + { + maildirwatch_free(w); + } + } + + if ((tag=getenv("IMAPLOGINTAG")) != 0) + { + writes(tag); + writes(" OK LOGIN Ok.\r\n"); + } + else + writes("* PREAUTH Ready.\r\n"); + writeflush(); + chkdisabled(ip, port); + imapscan_init(¤t_maildir_info); + mainloop(); + fetch_free_cached(); + bye(); + return (0); +} |
