summaryrefslogtreecommitdiffstats
path: root/imap/imapd.c
diff options
context:
space:
mode:
authorSam Varshavchik2013-08-19 16:39:41 -0400
committerSam Varshavchik2013-08-25 14:43:51 -0400
commit9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch)
tree7a81a04cb51efb078ee350859a64be2ebc6b8813 /imap/imapd.c
parenta9520698b770168d1f33d6301463bb70a19655ec (diff)
downloadcourier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2
Initial checkin
Imported from subversion report, converted to git. Updated all paths in scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'imap/imapd.c')
-rw-r--r--imap/imapd.c6665
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(&quotainfo, ".") == 0)
+ maildir_closequotafile(&quotainfo);
+ 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, &quotainfo, 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(&quotainfo, 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(&current_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(&current_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(&current_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= &current_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(&current_maildir_info);
+ imapscan_init(&current_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(&current_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(&current_maildir_info);
+ imapscan_init(&current_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(&quotainfo,
+ ".") == 0)
+ {
+ if (quotainfo.fd >= 0)
+ has_quota=1;
+ maildir_closequotafile(&quotainfo);
+ }
+
+ 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(".", &quotainfo,
+ 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(&quotainfo,
+ 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= &copy_info.uidplus_list;
+ copy_info.acls=access_rights;
+
+ if (has_quota < 0 ||
+ do_msgset(msgset, &do_copy_message, &copy_info, uid) ||
+ uidplus_fill(copy_info.mailbox, copy_info.uidplus_list,
+ &copy_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(&current_maildir_info);
+ mainloop();
+ fetch_free_cached();
+ bye();
+ return (0);
+}