/* * Heirloom mailx - a mail user agent derived from Berkeley Mail. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. */ /* * Copyright (c) 2004 * Gunnar Ritter. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Gunnar Ritter * and his contributors. * 4. Neither the name of Gunnar Ritter nor the names of his contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #ifdef DOSCCS static char sccsid[] = "@(#)maildir.c 1.20 (gritter) 12/28/06"; #endif #endif /* not lint */ #include "config.h" #include "rcv.h" #include "extern.h" #include #include #include #include #include /* * Mail -- a mail program * * Maildir folder support. */ static struct mditem { struct message *md_data; unsigned md_hash; } *mdtable; static long mdprime; static sigjmp_buf maildirjmp; static int maildir_setfile1(const char *name, int newmail, int omsgCount); static int mdcmp(const void *a, const void *b); static int subdir(const char *name, const char *sub, int newmail); static void cleantmp(const char *name); static void append(const char *name, const char *sub, const char *fn); static void readin(const char *name, struct message *m); static void maildir_update(void); static void move(struct message *m); static char *mkname(time_t t, enum mflag f, const char *pref); static void maildircatch(int s); static enum okay maildir_append1(const char *name, FILE *fp, off_t off1, long size, enum mflag flag); static enum okay trycreate(const char *name); static enum okay mkmaildir(const char *name); static struct message *mdlook(const char *name, struct message *data); static void mktable(void); static enum okay subdir_remove(const char *name, const char *sub); int maildir_setfile(const char *name, int newmail, int isedit) { sighandler_type saveint; struct cw cw; int i = -1, omsgCount; (void)&saveint; (void)&i; omsgCount = msgCount; if (cwget(&cw) == STOP) { fprintf(stderr, "Fatal: Cannot open current directory\n"); return -1; } if (!newmail) quit(); saveint = safe_signal(SIGINT, SIG_IGN); if (chdir(name) < 0) { fprintf(stderr, "Cannot change directory to \"%s\".\n", name); cwrelse(&cw); return -1; } if (!newmail) { edit = isedit; if (mb.mb_itf) { fclose(mb.mb_itf); mb.mb_itf = NULL; } if (mb.mb_otf) { fclose(mb.mb_otf); mb.mb_otf = NULL; } initbox(name); mb.mb_type = MB_MAILDIR; } mdtable = NULL; if (sigsetjmp(maildirjmp, 1) == 0) { if (newmail) mktable(); if (saveint != SIG_IGN) safe_signal(SIGINT, maildircatch); i = maildir_setfile1(name, newmail, omsgCount); } if (newmail) free(mdtable); safe_signal(SIGINT, saveint); if (i < 0) { mb.mb_type = MB_VOID; *mailname = '\0'; msgCount = 0; } if (cwret(&cw) == STOP) { fputs("Fatal: Cannot change back to current directory.\n", stderr); abort(); } cwrelse(&cw); setmsize(msgCount); if (newmail && mb.mb_sorted && msgCount > omsgCount) { mb.mb_threaded = 0; sort((void *)-1); } if (!newmail) sawcom = 0; if (!newmail && !edit && msgCount == 0) { if (mb.mb_type == MB_MAILDIR && value("emptystart") == NULL) fprintf(stderr, "No mail at %s\n", name); return 1; } if (newmail && msgCount > omsgCount) newmailinfo(omsgCount); return 0; } static int maildir_setfile1(const char *name, int newmail, int omsgCount) { int i; if (!newmail) cleantmp(name); mb.mb_perm = Rflag ? 0 : MB_DELE; if ((i = subdir(name, "cur", newmail)) != 0) return i; if ((i = subdir(name, "new", newmail)) != 0) return i; append(name, NULL, NULL); for (i = newmail?omsgCount:0; i < msgCount; i++) readin(name, &message[i]); if (newmail) { if (msgCount > omsgCount) qsort(&message[omsgCount], msgCount - omsgCount, sizeof *message, mdcmp); } else { if (msgCount) qsort(message, msgCount, sizeof *message, mdcmp); } return msgCount; } /* * In combination with the names from mkname(), this comparison function * ensures that the order of messages in a maildir folder created by mailx * remains always the same. In effect, if a mbox folder is transferred to * a maildir folder by 'copy *', the order of the messages in mailx will * not change. */ static int mdcmp(const void *a, const void *b) { long i; if ((i = ((struct message *)a)->m_time - ((struct message *)b)->m_time) == 0) i = strcmp(&((struct message *)a)->m_maildir_file[4], &((struct message *)b)->m_maildir_file[4]); return i; } static int subdir(const char *name, const char *sub, int newmail) { DIR *dirfd; struct dirent *dp; if ((dirfd = opendir(sub)) == NULL) { fprintf(stderr, "Cannot open directory \"%s/%s\".\n", name, sub); return -1; } if (access(sub, W_OK) < 0) mb.mb_perm = 0; while ((dp = readdir(dirfd)) != NULL) { if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) continue; if (dp->d_name[0] == '.') continue; if (!newmail || mdlook(dp->d_name, NULL) == NULL) append(name, sub, dp->d_name); } closedir(dirfd); return 0; } static void cleantmp(const char *name) { struct stat st; DIR *dirfd; struct dirent *dp; char *fn = NULL; size_t fnsz = 0, ssz; time_t now; if ((dirfd = opendir("tmp")) == NULL) return; time(&now); while ((dp = readdir(dirfd)) != NULL) { if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) continue; if (dp->d_name[0] == '.') continue; if ((ssz = strlen(dp->d_name)) + 5 > fnsz) { free(fn); fn = smalloc(fnsz = ssz + 40); } strcpy(fn, "tmp/"); strcpy(&fn[4], dp->d_name); if (stat(fn, &st) < 0) continue; if (st.st_atime + 36*3600 < now) unlink(fn); } free(fn); closedir(dirfd); } static void append(const char *name, const char *sub, const char *fn) { struct message *m; size_t sz; time_t t = 0; enum mflag f = MUSED|MNOFROM|MNEWEST; const char *cp; char *xp; if (fn && sub) { if (strcmp(sub, "new") == 0) f |= MNEW; t = strtol(fn, &xp, 10); if ((cp = strrchr(xp, ',')) != NULL && cp > &xp[2] && cp[-1] == '2' && cp[-2] == ':') { while (*++cp) { switch (*cp) { case 'F': f |= MFLAGGED; break; case 'R': f |= MANSWERED; break; case 'S': f |= MREAD; break; case 'T': f |= MDELETED; break; case 'D': f |= MDRAFT; break; } } } } if (msgCount + 1 >= msgspace) { const int chunk = 64; message = srealloc(message, (msgspace += chunk) * sizeof *message); memset(&message[msgCount], 0, chunk * sizeof *message); } if (fn == NULL || sub == NULL) return; m = &message[msgCount++]; m->m_maildir_file = smalloc((sz = strlen(sub)) + strlen(fn) + 2); strcpy(m->m_maildir_file, sub); m->m_maildir_file[sz] = '/'; strcpy(&m->m_maildir_file[sz+1], fn); m->m_time = t; m->m_flag = f; m->m_maildir_hash = ~pjw(fn); return; } static void readin(const char *name, struct message *m) { char *buf, *bp; size_t bufsize, buflen, count; long size = 0, lines = 0; off_t offset; FILE *fp; int emptyline = 0; if ((fp = Fopen(m->m_maildir_file, "r")) == NULL) { fprintf(stderr, "Cannot read \"%s/%s\" for message %d\n", name, m->m_maildir_file, m - &message[0] + 1); m->m_flag |= MHIDDEN; return; } buf = smalloc(bufsize = LINESIZE); buflen = 0; count = fsize(fp); fseek(mb.mb_otf, 0L, SEEK_END); offset = ftell(mb.mb_otf); while (fgetline(&buf, &bufsize, &count, &buflen, fp, 1) != NULL) { bp = buf; if (buf[0] == 'F' && buf[1] == 'r' && buf[2] == 'o' && buf[3] == 'm' && buf[4] == ' ') { putc('>', mb.mb_otf); size++; } lines++; size += fwrite(bp, 1, buflen, mb.mb_otf); emptyline = *bp == '\n'; } if (!emptyline) { putc('\n', mb.mb_otf); lines++; size++; } Fclose(fp); fflush(mb.mb_otf); m->m_size = m->m_xsize = size; m->m_lines = m->m_xlines = lines; m->m_block = mailx_blockof(offset); m->m_offset = mailx_offsetof(offset); free(buf); substdate(m); } void maildir_quit(void) { sighandler_type saveint; struct cw cw; (void)&saveint; if (cwget(&cw) == STOP) { fprintf(stderr, "Fatal: Cannot open current directory\n"); return; } saveint = safe_signal(SIGINT, SIG_IGN); if (chdir(mailname) < 0) { fprintf(stderr, "Cannot change directory to \"%s\".\n", mailname); cwrelse(&cw); return; } if (sigsetjmp(maildirjmp, 1) == 0) { if (saveint != SIG_IGN) safe_signal(SIGINT, maildircatch); maildir_update(); } safe_signal(SIGINT, saveint); if (cwret(&cw) == STOP) { fputs("Fatal: Cannot change back to current directory.\n", stderr); abort(); } cwrelse(&cw); } static void maildir_update(void) { FILE *readstat = NULL; struct message *m; int dodel, c, gotcha = 0, held = 0, modflags = 0; if (mb.mb_perm == 0) goto free; if (Tflag != NULL) { if ((readstat = Zopen(Tflag, "w", NULL)) == NULL) Tflag = NULL; } if (!edit) { holdbits(); for (m = &message[0], c = 0; m < &message[msgCount]; m++) { if (m->m_flag & MBOX) c++; } if (c > 0) if (makembox() == STOP) goto bypass; } for (m = &message[0], gotcha=0, held=0; m < &message[msgCount]; m++) { if (readstat != NULL && (m->m_flag & (MREAD|MDELETED)) != 0) { char *id; if ((id = hfield("message-id", m)) != NULL || (id = hfield("article-id", m)) != NULL) fprintf(readstat, "%s\n", id); } if (edit) dodel = m->m_flag & MDELETED; else dodel = !((m->m_flag&MPRESERVE) || (m->m_flag&MTOUCH) == 0); if (dodel) { if (unlink(m->m_maildir_file) < 0) fprintf(stderr, "Cannot delete file \"%s/%s\" " "for message %d.\n", mailname, m->m_maildir_file, m - &message[0] + 1); else gotcha++; } else { if ((m->m_flag&(MREAD|MSTATUS)) == (MREAD|MSTATUS) || m->m_flag & (MNEW|MBOXED|MSAVED|MSTATUS| MFLAG|MUNFLAG| MANSWER|MUNANSWER| MDRAFT|MUNDRAFT)) { move(m); modflags++; } held++; } } bypass: if (readstat != NULL) Fclose(readstat); if ((gotcha || modflags) && edit) { printf(catgets(catd, CATSET, 168, "\"%s\" "), mailname); printf(value("bsdcompat") || value("bsdmsgs") ? catgets(catd, CATSET, 170, "complete\n") : catgets(catd, CATSET, 212, "updated.\n")); } else if (held && !edit && mb.mb_perm != 0) { if (held == 1) printf(catgets(catd, CATSET, 155, "Held 1 message in %s\n"), mailname); else if (held > 1) printf(catgets(catd, CATSET, 156, "Held %d messages in %s\n"), held, mailname); } fflush(stdout); free: for (m = &message[0]; m < &message[msgCount]; m++) free(m->m_maildir_file); } static void move(struct message *m) { char *fn, *new; fn = mkname(0, m->m_flag, &m->m_maildir_file[4]); new = savecat("cur/", fn); if (strcmp(m->m_maildir_file, new) == 0) return; if (link(m->m_maildir_file, new) < 0) { fprintf(stderr, "Cannot link \"%s/%s\" to \"%s/%s\": " "message %d not touched.\n", mailname, m->m_maildir_file, mailname, new, m - &message[0] + 1); return; } if (unlink(m->m_maildir_file) < 0) fprintf(stderr, "Cannot unlink \"%s/%s\".\n", mailname, m->m_maildir_file); } static char * mkname(time_t t, enum mflag f, const char *pref) { static unsigned long count; static pid_t mypid; char *cp; static char *node; int size, n, i; if (pref == NULL) { if (mypid == 0) mypid = getpid(); if (node == NULL) { cp = nodename(0); n = size = 0; do { if (n < size + 8) node = srealloc(node, size += 20); switch (*cp) { case '/': node[n++] = '\\', node[n++] = '0', node[n++] = '5', node[n++] = '7'; break; case ':': node[n++] = '\\', node[n++] = '0', node[n++] = '7', node[n++] = '2'; break; default: node[n++] = *cp; } } while (*cp++); } size = 60 + strlen(node); cp = salloc(size); n = snprintf(cp, size, "%lu.%06lu_%06lu.%s:2,", (unsigned long)t, (unsigned long)mypid, ++count, node); } else { size = (n = strlen(pref)) + 13; cp = salloc(size); strcpy(cp, pref); for (i = n; i > 3; i--) if (cp[i-1] == ',' && cp[i-2] == '2' && cp[i-3] == ':') { n = i; break; } if (i <= 3) { strcpy(&cp[n], ":2,"); n += 3; } } if (n < size - 7) { if (f & MDRAFTED) cp[n++] = 'D'; if (f & MFLAGGED) cp[n++] = 'F'; if (f & MANSWERED) cp[n++] = 'R'; if (f & MREAD) cp[n++] = 'S'; if (f & MDELETED) cp[n++] = 'T'; cp[n] = '\0'; } return cp; } static void maildircatch(int s) { siglongjmp(maildirjmp, s); } enum okay maildir_append(const char *name, FILE *fp) { char *buf, *bp, *lp; size_t bufsize, buflen, count; off_t off1 = -1, offs; int inhead = 1; int flag = MNEW|MNEWEST; long size = 0; enum okay ok; if (mkmaildir(name) != OKAY) return STOP; buf = smalloc(bufsize = LINESIZE); buflen = 0; count = fsize(fp); offs = ftell(fp); do { bp = fgetline(&buf, &bufsize, &count, &buflen, fp, 1); if (bp == NULL || strncmp(buf, "From ", 5) == 0) { if (off1 != (off_t)-1) { ok = maildir_append1(name, fp, off1, size, flag); if (ok == STOP) return STOP; fseek(fp, offs+buflen, SEEK_SET); } off1 = offs + buflen; size = 0; inhead = 1; flag = MNEW; } else size += buflen; offs += buflen; if (bp && buf[0] == '\n') inhead = 0; else if (bp && inhead && ascncasecmp(buf, "status", 6) == 0) { lp = &buf[6]; while (whitechar(*lp&0377)) lp++; if (*lp == ':') while (*++lp != '\0') switch (*lp) { case 'R': flag |= MREAD; break; case 'O': flag &= ~MNEW; break; } } else if (bp && inhead && ascncasecmp(buf, "x-status", 8) == 0) { lp = &buf[8]; while (whitechar(*lp&0377)) lp++; if (*lp == ':') while (*++lp != '\0') switch (*lp) { case 'F': flag |= MFLAGGED; break; case 'A': flag |= MANSWERED; break; case 'T': flag |= MDRAFTED; break; } } } while (bp != NULL); free(buf); return OKAY; } static enum okay maildir_append1(const char *name, FILE *fp, off_t off1, long size, enum mflag flag) { const int attempts = 43200; struct stat st; char buf[4096]; char *fn, *tmp, *new; FILE *op; long n, z; int i; time_t now; for (i = 0; i < attempts; i++) { time(&now); fn = mkname(now, flag, NULL); tmp = salloc(n = strlen(name) + strlen(fn) + 6); snprintf(tmp, n, "%s/tmp/%s", name, fn); if (stat(tmp, &st) < 0 && errno == ENOENT) break; sleep(2); } if (i >= attempts) { fprintf(stderr, "Cannot create unique file name in \"%s/tmp\".\n", name); return STOP; } if ((op = Fopen(tmp, "w")) == NULL) { fprintf(stderr, "Cannot write to \"%s\".\n", tmp); return STOP; } fseek(fp, off1, SEEK_SET); while (size > 0) { z = size > sizeof buf ? sizeof buf : size; if ((n = fread(buf, 1, z, fp)) != z || fwrite(buf, 1, n, op) != n) { fprintf(stderr, "Error writing to \"%s\".\n", tmp); Fclose(op); unlink(tmp); return STOP; } size -= n; } Fclose(op); new = salloc(n = strlen(name) + strlen(fn) + 6); snprintf(new, n, "%s/new/%s", name, fn); if (link(tmp, new) < 0) { fprintf(stderr, "Cannot link \"%s\" to \"%s\".\n", tmp, new); return STOP; } if (unlink(tmp) < 0) fprintf(stderr, "Cannot unlink \"%s\".\n", tmp); return OKAY; } static enum okay trycreate(const char *name) { struct stat st; if (stat(name, &st) == 0) { if (!S_ISDIR(st.st_mode)) { fprintf(stderr, "\"%s\" is not a directory.\n", name); return STOP; } } else if (makedir(name) != OKAY) { fprintf(stderr, "Cannot create directory \"%s\".\n", name); return STOP; } else imap_created_mailbox++; return OKAY; } static enum okay mkmaildir(const char *name) { char *np; size_t sz; enum okay ok = STOP; if (trycreate(name) == OKAY) { np = ac_alloc((sz = strlen(name)) + 5); strcpy(np, name); strcpy(&np[sz], "/tmp"); if (trycreate(np) == OKAY) { strcpy(&np[sz], "/new"); if (trycreate(np) == OKAY) { strcpy(&np[sz], "/cur"); if (trycreate(np) == OKAY) ok = OKAY; } } ac_free(np); } return ok; } static struct message * mdlook(const char *name, struct message *data) { struct mditem *md; unsigned c, h, n = 0; if (data && data->m_maildir_hash) h = ~data->m_maildir_hash; else h = pjw(name); h %= mdprime; md = &mdtable[c = h]; while (md->md_data != NULL) { if (strcmp(&md->md_data->m_maildir_file[4], name) == 0) break; c += n&1 ? -((n+1)/2) * ((n+1)/2) : ((n+1)/2) * ((n+1)/2); n++; while (c >= mdprime) c -= mdprime; md = &mdtable[c]; } if (data != NULL && md->md_data == NULL) md->md_data = data; return md->md_data ? md->md_data : NULL; } static void mktable(void) { int i; mdprime = nextprime(msgCount); mdtable = scalloc(mdprime, sizeof *mdtable); for (i = 0; i < msgCount; i++) mdlook(&message[i].m_maildir_file[4], &message[i]); } static enum okay subdir_remove(const char *name, const char *sub) { char *path; int pathsize, pathend, namelen, sublen, n; DIR *dirfd; struct dirent *dp; namelen = strlen(name); sublen = strlen(sub); path = smalloc(pathsize = namelen + sublen + 30); strcpy(path, name); path[namelen] = '/'; strcpy(&path[namelen+1], sub); path[namelen+sublen+1] = '/'; path[pathend = namelen + sublen + 2] = '\0'; if ((dirfd = opendir(path)) == NULL) { perror(path); free(path); return STOP; } while ((dp = readdir(dirfd)) != NULL) { if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) continue; if (dp->d_name[0] == '.') continue; n = strlen(dp->d_name); if (pathend + n + 1 > pathsize) path = srealloc(path, pathsize = pathend + n + 30); strcpy(&path[pathend], dp->d_name); if (unlink(path) < 0) { perror(path); closedir(dirfd); free(path); return STOP; } } closedir(dirfd); path[pathend] = '\0'; if (rmdir(path) < 0) { perror(path); free(path); return STOP; } free(path); return OKAY; } enum okay maildir_remove(const char *name) { if (subdir_remove(name, "tmp") == STOP || subdir_remove(name, "new") == STOP || subdir_remove(name, "cur") == STOP) return STOP; if (rmdir(name) < 0) { perror(name); return STOP; } return OKAY; }