/* * Copyright (c) 1999 Ian Freislich * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE AUTHOR 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. * * $Id: mbox_mailidx.c,v 1.22 2003/01/24 09:35:19 ianf Exp $ */ #include #include #include #include #include #include #ifdef SOLARIS #include "/usr/ucbinclude/fcntl.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "poputil.h" #include "private.h" /* Need this global variable to get configuration parameters to the compar() * routine */ static int flags; struct mbox_idx { int count; int bytes; int remove; int expire; int *idx_num; /* The number of the index of this message */ int *idx_flags; /* a copy of the flags for this message */ }; static int compar(const void *, const void *); static int mailidx_get_message(struct mail_idx *, struct mbox_idx *, int); static int mailidx_get_message_top(struct mail_idx *, struct mbox_idx *, int, int); static int mailidx_get_uidl_all(struct mail_idx *, struct mbox_idx *); static int mailidx_list_all(struct mail_idx *, struct mbox_idx *); static int mailidx_is_message(struct mbox_idx *, int); static int mailidx_open(struct mail_idx *, struct mbox_idx *, char *); static void mailidx_close(struct mail_idx *, struct mbox_idx *, char *); /* Perform open/extend/close opperations and the various locking options * at open time on the users mailbox. When the mailbox is closed, truncate * the index file to MAXINCR if the free space at the end of the index is * greater than MAXINCR. */ int mailidx_ctl(struct mail_idx *idx, char *path, int cmd, ...) { va_list ap; int flags = 0, result, finalsize; static char buffer[MAXBUFLEN + 1]; static int fd = -1; static int len = 0; struct stat stat_s; struct flock fl; fl.l_start = 0; fl.l_len = 0; fl.l_pid = getpid(); fl.l_whence = SEEK_SET; switch (cmd) { case IDX_OPEN: if (fd > -1) /*Don't reopen the file*/ return(FALSE); snprintf(buffer, MAXBUFLEN, "%s.idx", path); fd = open(buffer, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); if (fd <0) return(FALSE); va_start(ap, cmd); flags = va_arg(ap, int); va_end(ap); fl.l_type = 0; fl.l_type = (flags & IDX_O_READ) ? F_RDLCK : fl.l_type; fl.l_type = (flags & IDX_O_MODIFY) ? F_WRLCK : fl.l_type; fl.l_type = (flags & IDX_O_APPEND) ? F_RDLCK : fl.l_type; result = fcntl(fd, (flags & IDX_NONBLOCK) ? F_SETLK : F_SETLKW, &fl); if (result < 0) { close(fd); fd = -1; return(FALSE); } if (stat(buffer, &stat_s) < 0) { close(fd); fd = -1; return(FALSE); } len = stat_s.st_size; idx->hdr = mmap((void *)NULL, len, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0); if (idx->hdr == MAP_FAILED) { close(fd); fd = -1; return(FALSE); } if (len == 0) mailidx_ctl(idx, path, IDX_EXTEND); idx->msg = (struct idx_msg *)(idx->hdr + 1); idx->file_fd = open(path, O_CREAT|(flags & IDX_O_APPEND ? O_APPEND : O_RDWR), S_IRUSR|S_IWUSR); if (idx->file_fd < 0) { munmap((void *)idx->hdr, len); close(fd); fd = -1; return(FALSE); } fl.l_type = 0; fl.l_type = (flags & IDX_O_APPEND) ? F_WRLCK : fl.l_type; fl.l_type = (flags & IDX_O_MODIFY) ? F_WRLCK : fl.l_type; if (fl.l_type) { result = fcntl(idx->file_fd, (flags & IDX_NONBLOCK) ? F_SETLK : F_SETLKW, &fl); if (result < 0) { munmap((void *)idx->hdr, len); close(idx->file_fd); idx->file_fd = -1; close(fd); fd = -1; return(FALSE); } } stat(path, &stat_s); idx->st_size = stat_s.st_size; if (!(flags & IDX_O_IGNORE) && idx->st_size < idx->hdr->bytes) { munmap((void *)idx->hdr, len); close(idx->file_fd); idx->file_fd = -1; close(fd); fd = -1; return(FALSE); } break; case IDX_EXTEND: if (fd < 0) return(FALSE); if (len == 0) { munmap((void *)idx->hdr, len); len = sizeof(struct idx_hdr) + MAXINCR * sizeof(struct idx_msg); if (ftruncate(fd, len)) { close(fd); close(idx->file_fd); fd = -1; return(FALSE); } idx->hdr = mmap((void *)idx->hdr, len, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0); if (idx->hdr == MAP_FAILED) { close(fd); close(idx->file_fd); fd = -1; return(FALSE); } idx->hdr->max = MAXINCR - 1; idx->msg = (struct idx_msg *)(idx->hdr + 1); break; } munmap(idx->hdr, len); len += MAXINCR * sizeof(struct idx_msg); if (ftruncate(fd, len)) { close(fd); close(idx->file_fd); fd = -1; return(FALSE); } idx->hdr = mmap(idx->hdr, len, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0); if (idx->hdr == MAP_FAILED) { close(fd); close(idx->file_fd); fd = -1; return(FALSE); } idx->hdr->max += MAXINCR; idx->msg = (struct idx_msg *)(idx->hdr + 1); break; case IDX_CLOSE: if (fd < 0) return(FALSE); finalsize = idx->hdr->max; if (finalsize - MAXINCR > idx->hdr->count) { finalsize = idx->hdr->count + MAXINCR; idx->hdr->max = finalsize; } munmap(idx->hdr, len); ftruncate(fd, sizeof(struct idx_hdr) + finalsize * sizeof(struct idx_msg)); close(idx->file_fd); close(fd); fd = -1; len = 0; break; default: return(FALSE); } return(TRUE); } /* Open the mailbox and check the magic number. Rebuild the index if the * header is corrupted, or if the mailfile is newer than the index, add * the new messages to the index. */ int mailidx_check_reindex(char *path) { int msgs, uidlen, topoffile, lastblank, create = TRUE, addheader, headerlen; char buffer[MAXBUFLEN + 1], uidldat[MAXBUFLEN + 1], *header[2], *line, *p, *q, *r; unsigned char digest[16]; struct tm tm; time_t now; size_t bytes, len, size; off_t offset, buffleft; struct mail_idx idx; MD5_CTX context; memset(&idx, '\0', sizeof(struct mail_idx)); if (!mailidx_ctl(&idx, path, IDX_OPEN, IDX_NONBLOCK|IDX_O_MODIFY|IDX_O_IGNORE)) return(FALSE); memset((void *)buffer, NULL, MAXBUFLEN); size = bytes = idx.st_size; p = q = buffer; topoffile = TRUE; lastblank = FALSE; addheader = FALSE; headerlen = 0; msgs = -1; offset = 0; buffleft = 0; len = 0; /* Perhaps we just need to extend the index */ if (idx.hdr->magic == MAGIC_HDR && idx.st_size > idx.hdr->bytes) { msgs = idx.hdr->count; offset = idx.msg[msgs].offset + idx.msg[msgs].bytes; bytes -= offset; lseek(idx.file_fd, offset, SEEK_SET); create = FALSE; } else if (idx.hdr->magic == MAGIC_HDR && idx.st_size == idx.hdr->bytes) { mailidx_ctl(&idx, path, IDX_CLOSE); return(TRUE); } /* There really doesn't seem to be any easy or more intuitive way * to get the system timezone and daylight savings information * than this round-about method. * ctime sucks! */ now = time(NULL); memcpy(&tm, localtime(&now), sizeof(struct tm)); for (;;) { line = p; if ((p = strchr(line, '\n')) == NULL) { if (bytes == 0) break; strcpy(buffer, line); line = buffer; q = strchr(buffer, '\0'); buffleft = MAXBUFLEN - (q - buffer) - 1; if (buffleft > bytes) buffleft = bytes; len = read(idx.file_fd, q, buffleft); bytes -= len; offset += (off_t)len; q[buffleft] = '\0'; p = strchr(buffer, '\n'); } *p++ = '\0'; if (*line == '\r') { lastblank = TRUE; addheader = FALSE; continue; } if (addheader) { headerlen += strlen(line); header[msgs % 2] = xrealloc(header[msgs % 2], headerlen + 1); strcat(header[msgs % 2], line); header[msgs % 2][headerlen] = '\0'; headerlen--; } if ((topoffile || lastblank ) && !strncmp(line, "From ", 5)) { addheader = TRUE; if (msgs + 1 >= idx.hdr->max) if (!mailidx_ctl(&idx, path, IDX_EXTEND)) { mailidx_ctl(&idx, path, IDX_CLOSE); return(FALSE); } msgs++; headerlen = strlen(line); header[msgs % 2] = xmalloc(headerlen + 1); strcpy(header[msgs % 2], line); header[msgs % 2][headerlen - 1] = '\0'; headerlen -= 2; idx.msg[msgs].flags = 0; idx.msg[msgs].offset = offset - (len - (line - buffer)) - (q - buffer); if (!((r = memchr(line, ' ', line - p)) && r++ && (r = memchr(r, ' ', q - r)) && r++ )) r = p; if (!strptime(r, "%a %b %d %T %Y", &tm)) tm = *(localtime(&now)); idx.msg[msgs].d_time = mktime(&tm); if (msgs > 0) { idx.msg[msgs - 1].bytes = (size_t)( idx.msg[msgs].offset - idx.msg[msgs - 1].offset); uidlen = snprintf(uidldat, MAXBUFLEN, "%s%s%d", header[(msgs - 1) % 2], path, idx.msg[msgs - 1].bytes); free(header[(msgs - 1) % 2]); header[(msgs - 1) % 2] = NULL; MD5Init(&context); MD5Update(&context, (unsigned char *)uidldat, uidlen); MD5Final(digest, &context); sprintf(idx.msg[msgs - 1].uidl, "%s", binhex(digest, sizeof(digest))); idx.msg[msgs - 1].magic = MAGIC_MSG; } topoffile = FALSE; lastblank = FALSE; } } if (msgs > -1) { idx.msg[msgs].bytes = size - idx.msg[msgs].offset; uidlen = snprintf(uidldat, MAXBUFLEN, "%s%s%d", header[msgs % 2], path, idx.msg[msgs].bytes); free(header[msgs % 2]); header[msgs % 2] = NULL; MD5Init(&context); MD5Update(&context, (unsigned char *)uidldat, uidlen); MD5Final(digest, &context); sprintf(idx.msg[msgs].uidl, "%s", binhex(digest, sizeof(digest))); idx.msg[msgs].magic = MAGIC_MSG; } idx.hdr->count = msgs; idx.hdr->bytes = size; /* Don't blat things if we didn't create the index */ if (create) { idx.hdr->magic = MAGIC_HDR; idx.hdr->flags = IDX_F_NULL; idx.hdr->expire = 0; idx.hdr->remove = 0; } mailidx_ctl(&idx, path, IDX_CLOSE); return(TRUE); } static int mailidx_get_message(struct mail_idx *idx, struct mbox_idx *mbox, int number) { static char buffer[MAXBUFLEN]; int len, toread; sendline(SEND_FLUSH, "+OK sending message ending with a '.' on " "a line by itself"); toread = idx->msg[mbox->idx_num[number]].bytes; lseek(idx->file_fd, idx->msg[mbox->idx_num[number]].offset, SEEK_SET); while (toread > 0) { if (toread > MAXBUFLEN) len = read(idx->file_fd, buffer, MAXBUFLEN); else len = read(idx->file_fd, buffer, toread); xwrite(buffer, len); toread -= len; } mbox->idx_flags[number] |= IDX_F_READ; sendline(SEND_FLUSH, "."); return(TRUE); } /* Seek to the begining of the mail message and read the message * in MAXBUFLEN chunks. Write out n lines of the message in as large chunks * as possible. */ static int mailidx_get_message_top(struct mail_idx *idx, struct mbox_idx *mbox, int number, int lines) { char buffer[MAXBUFLEN], *p; int inbody, count, bytes, len, buffleft; char *line, *offset; p = buffer; inbody = FALSE; count = 0; bytes = idx->msg[mbox->idx_num[number]].bytes; lseek(idx->file_fd, idx->msg[mbox->idx_num[number]].offset, SEEK_SET); sendline(SEND_FLUSH, "+OK sending message ending with a '.' on " "a line by itself"); memset((void *)buffer, NULL, MAXBUFLEN); for (;;) { line = p; if ((p = strchr(line, '\n')) == NULL) { xwrite(buffer, line - buffer); if (bytes == 0 || (inbody && count > lines)) break; strcpy(buffer, line); line = buffer; offset = strchr(buffer, '\0'); buffleft = MAXBUFLEN - (offset - buffer) - 1; if (buffleft > bytes) buffleft = bytes; len = read(idx->file_fd, offset, buffleft); bytes -= len; offset[buffleft] = '\0'; p = strchr(buffer, '\n'); } if (inbody && count > lines) { xwrite(buffer, line - buffer); break; } p++; if (!inbody && *p == '\r') inbody = TRUE; if (inbody == TRUE) count++; } sendline(SEND_FLUSH, "."); mbox->idx_flags[number] |= IDX_F_TOP; return(TRUE); } /* XXXX Note to self Must flasify UIDL info for old messages. */ static int mailidx_get_uidl_all(struct mail_idx *idx, struct mbox_idx *mbox) { int i; sendline(SEND_BUF, "+OK sending list ending with a '.' on " "a line by itself"); for (i = 0; i <= mbox->count; i++) { if (mbox->idx_flags[i] & IDX_F_DELETE) continue; sendline(SEND_BUF, "%d %s", i + 1, idx->msg[mbox->idx_num[i]].uidl); } return(TRUE); } /* XXXX Note to self Must flasify UIDL info for old messages. */ static int mailidx_list_all(struct mail_idx *idx, struct mbox_idx *mbox) { int i; sendline(SEND_BUF, "+OK sending list ending with a '.' on " "a line by itself"); for (i = 0; i <= mbox->count; i++) { if (mbox->idx_flags[i] & IDX_F_DELETE) continue; sendline(SEND_BUF, "%d %d", i + 1, idx->msg[mbox->idx_num[i]].bytes); } return(TRUE); } static int mailidx_is_message(struct mbox_idx *mbox, int number) { if (number > mbox->count || number < 0) { message(NOSUCH); return(FALSE); } if (mbox->idx_flags[number] & IDX_F_DELETE) { message(ALREADYDELETED); return(FALSE); } return(TRUE); } /* Open the mailbox and cache the message flags in our internal structure * so that updates don't happen live and are only committed at the end * of the session */ static int mailidx_open(struct mail_idx *idx, struct mbox_idx *mbox, char *path) { int i, count; memset(idx, '\0', sizeof(struct mail_idx)); mailidx_check_reindex(path); if (!mailidx_ctl(idx, path, IDX_OPEN, IDX_NONBLOCK|IDX_O_READ)) return(FALSE); if (idx->hdr->magic != MAGIC_HDR) { mailidx_ctl(idx, path, IDX_CLOSE); return(FALSE); } mbox->idx_num = xcalloc(idx->hdr->count + 2, sizeof(int)); mbox->idx_flags = xcalloc(idx->hdr->count + 2, sizeof(int)); for (i = 0, count = 0; i <= idx->hdr->count; i++) { if (idx->msg[i].magic != MAGIC_MSG) continue; if (idx->msg[i].flags & IDX_F_DELETE) continue; mbox->idx_num[count] = i; mbox->idx_flags[count++] = idx->msg[i].flags; mbox->bytes += idx->msg[i].bytes; mbox->expire += (idx->msg[i].flags & IDX_F_EXPIRE) > 0; mbox->remove += (idx->msg[i].flags & IDX_F_REMOVE) > 0; } mbox->count = count - 1; if (mbox->count > 0) /* Should really use mergesort() here because it is stable * however not everyone has mergesort(). */ qsort((void *)idx->msg, mbox->count + 1, sizeof(struct idx_msg), &compar); return(TRUE); } static int compar(const void *a, const void *b) { if (flags & MAILBOX_F_SORT_TIME) /* effectively don't sort */ return(0); else if (flags & MAILBOX_F_SORT_SIZE) return(((struct idx_msg *)a)->bytes - ((struct idx_msg *)b)->bytes); else /* effectively don't sort */ return(0); } /* Commit the buffered copy of message flags to the index. * Update the global flag data as well to optomise deciding * whether compaction is required. */ static void mailidx_close(struct mail_idx *idx, struct mbox_idx *mbox, char *path) { int i; for (i = 0; i <= mbox->count; i++) { if ((idx->msg[mbox->idx_num[i]].flags & mbox->idx_flags[i]) != mbox->idx_flags[i]) idx->msg[mbox->idx_num[i]].flags |= mbox->idx_flags[i]; if ((idx->hdr->flags & mbox->idx_flags[i]) != mbox->idx_flags[i]) idx->hdr->flags |= mbox->idx_flags[i]; } free(mbox->idx_num); free(mbox->idx_flags); if (mbox->expire > idx->hdr->expire) idx->hdr->expire = mbox->expire; if (mbox->remove > idx->hdr->remove) idx->hdr->remove = mbox->remove; mailidx_ctl(idx, path, IDX_CLOSE); } /* Skip through the index and remove all mail flagged IDX_F_EXPIRE & IDX_F_READ * IDX_F_REMOVE or IDX_F_DELETE. Compact the text file and index. */ int mailidx_compact(struct connection *cxn) { struct mail_idx idx; struct idx_msg *idx_src, *idx_dst; char buffer[MAXBUFLEN]; off_t mbx_src, mbx_dst; size_t left, bytes, len, hdr_bytes; int i, ret, del, exp, rem, errors, count, facility; facility = LOG_INFO; memset(&idx, '\0', sizeof(struct mail_idx)); if (!mailidx_ctl(&idx, cxn->mailpath, IDX_OPEN, IDX_NONBLOCK|IDX_O_MODIFY)) { syslog(facility, "%s: Unable to lock mailbox for compaction", cxn->auth_string); return(FALSE); } /* Make sure there is at least 1 expired/remove/deleted message */ if (!((cxn->flags & MAILBOX_F_AUTODELETE && (idx.hdr->flags & IDX_F_EXPIRE) && (idx.hdr->flags & IDX_F_READ)) || (idx.hdr->flags & IDX_F_REMOVE) || (idx.hdr->flags & IDX_F_DELETE))) { mailidx_ctl(&idx, cxn->mailpath, IDX_CLOSE); return(TRUE); } ret = del = exp = rem = errors = 0; hdr_bytes = idx.hdr->bytes; idx_dst = NULL; idx_src = NULL; mbx_dst = 0; count = idx.hdr->count; for (i = 0; i <= count; i++) { /* Find the first deleted message and skip through * subsequent deleted messages. */ if (idx.msg[i].flags & IDX_F_DELETE) { del++; if (idx_dst == NULL) { idx_dst = &idx.msg[i]; mbx_dst = idx.msg[i].offset; } idx.hdr->bytes -= idx.msg[i].bytes; idx.hdr->count--; } else if (cxn->flags & MAILBOX_F_AUTODELETE && (idx.msg[i].flags & IDX_F_EXPIRE) && (idx.msg[i].flags & IDX_F_READ)) { exp++; if (idx_dst == NULL) { idx_dst = &idx.msg[i]; mbx_dst = idx.msg[i].offset; } idx.hdr->bytes -= idx.msg[i].bytes; idx.hdr->count--; } else if (cxn->remove && idx.msg[i].flags & IDX_F_REMOVE) { rem++; if (idx_dst == NULL) { idx_dst = &idx.msg[i]; mbx_dst = idx.msg[i].offset; } idx.hdr->bytes -= idx.msg[i].bytes; idx.hdr->count--; } else { if (idx_dst == NULL) continue; /* If we get here, idx_dst and mbx_dst point to the * first deleted message. Now we need to find the next * deleted message or EOF and move that chunk of the * mailbox to mbx_dst. */ bytes = 0; mbx_src = idx.msg[i].offset; idx_src = &idx.msg[i]; for (; i <= count; i++) { if ((cxn->flags & MAILBOX_F_AUTODELETE && (idx.msg[i].flags & IDX_F_EXPIRE) && (idx.msg[i].flags & IDX_F_READ)) || (cxn->remove && idx.msg[i].flags & IDX_F_REMOVE) || (idx.msg[i].flags & IDX_F_DELETE)) { i--; break; } bytes += idx.msg[i].bytes; } for (; idx_src <= &idx.msg[i]; idx_src++, idx_dst++) { memcpy(idx_dst, idx_src, sizeof(struct idx_msg)); idx_dst->offset -= mbx_src - mbx_dst; } left = bytes; if (left > MAXBUFLEN) left = MAXBUFLEN; lseek(idx.file_fd, mbx_src, SEEK_SET); while ((len = read(idx.file_fd, buffer, left)) > 0) { lseek(idx.file_fd, mbx_dst, SEEK_SET); write(idx.file_fd, buffer, len); mbx_src += len; mbx_dst += len; bytes -= len; left = bytes; if (left > MAXBUFLEN) left = MAXBUFLEN; if (left < 1) break; lseek(idx.file_fd, mbx_src, SEEK_SET); } } } if ((del || exp || rem) && hdr_bytes < idx.st_size) { mbx_src = hdr_bytes; left = bytes = idx.st_size - hdr_bytes; if (left > MAXBUFLEN) left = MAXBUFLEN; lseek(idx.file_fd, mbx_src, SEEK_SET); while ((len = read(idx.file_fd, buffer, left)) > 0) { lseek(idx.file_fd, mbx_dst, SEEK_SET); write(idx.file_fd, buffer, len); mbx_src += len; mbx_dst += len; bytes -= len; left = bytes; if (left > MAXBUFLEN) left = MAXBUFLEN; if (left < 1) break; lseek(idx.file_fd, mbx_src, SEEK_SET); } } ftruncate(idx.file_fd, (off_t)idx.hdr->bytes + (idx.st_size - hdr_bytes)); idx.hdr->flags = IDX_F_NULL; log_stats(cxn->auth_string, ret, idx.hdr->count + 1, idx.hdr->bytes, errors, del, idx.hdr->expire, exp, idx.hdr->remove, rem); mailidx_ctl(&idx, cxn->mailpath, IDX_CLOSE); return(TRUE); } int mailidx_mbox_op(struct connection *cxn, enum cmd cmd, ...) { static struct mail_idx idx; static struct mbox_idx mbox; va_list ap; int arg1, arg2, i, bytes, msgs; switch (cmd) { case SESSION_START: flags = cxn->flags; if (mailidx_open(&idx, &mbox, cxn->mailpath)) { bulletin_mbox_op(cxn, SESSION_START); sendline(SEND_FLUSH, "+OK %d message%s (%d octets) " "(Expire on RETR: %d Remove on QUIT: %d)", bulletin_mbox_op(cxn, BUL_MSGS) + mbox.count + 1, bulletin_mbox_op(cxn, BUL_MSGS) + mbox.count + 1 == 1 ? "" : "s", mbox.bytes + bulletin_mbox_op(cxn, BUL_SIZE), mbox.expire, mbox.remove); return(TRUE); } return(FALSE); break; case SESSION_END: mailidx_close(&idx, &mbox, cxn->mailpath); mailidx_compact(cxn); return(TRUE); case DELE: va_start(ap, cmd); arg1 = va_arg(ap, int); va_end(ap); if (arg1 - 1 > mbox.count) { bulletin_mbox_op(cxn, DELE, mbox.count + 1, arg1); break; } if (!mailidx_is_message(&mbox, arg1 - 1)) return(FALSE); mbox.idx_flags[arg1 - 1] |= IDX_F_DELETE; sendline(SEND_FLUSH, "+OK message deleted"); break; case LIST: va_start(ap, cmd); arg1 = va_arg(ap, int); va_end(ap); if (arg1 >= 0) { if (arg1 - 1 > mbox.count) { bulletin_mbox_op(cxn, LIST, mbox.count + 1, arg1); break; } if (!mailidx_is_message(&mbox, arg1 - 1)) return(FALSE); sendline(SEND_FLUSH, "+OK %d %d", arg1, idx.msg[mbox.idx_num[arg1 - 1]].bytes); } else { mailidx_list_all(&idx, &mbox); bulletin_mbox_op(cxn, LIST, mbox.count + 1, arg1); sendline(SEND_FLUSH, "."); } break; case NOOP: sendline(SEND_FLUSH, "+OK"); break; case QUIT: sendline(SEND_FLUSH, "+OK"); poputil_end(); mailidx_mbox_op(cxn, SESSION_END); bulletin_mbox_op(cxn, SESSION_END); exit(EX_OK); break; case RETR: va_start(ap, cmd); arg1 = va_arg(ap, int); va_end(ap); if (arg1 - 1 > mbox.count) { bulletin_mbox_op(cxn, RETR, mbox.count + 1, arg1); break; } if (!mailidx_is_message(&mbox, arg1 - 1)) return(FALSE); if (idx.last < arg1) idx.last = arg1; mailidx_get_message(&idx, &mbox, arg1 - 1); break; case RSET: for (i = 0; i < mbox.count; i++) mbox.idx_flags[i] &= !(IDX_F_DELETE | IDX_F_READ); idx.last = 0; bulletin_mbox_op(cxn, RSET); sendline(SEND_FLUSH, "+OK"); break; case STAT: bytes = msgs = 0; for (i = 0; i <= mbox.count; i++) { if (idx.msg[mbox.idx_num[i]].flags & IDX_F_DELETE) continue; bytes += idx.msg[mbox.idx_num[i]].bytes; msgs++; } msgs += bulletin_mbox_op(cxn, BUL_MSGS); bytes += bulletin_mbox_op(cxn, BUL_SIZE); sendline(SEND_FLUSH, "+OK %d %d", msgs, bytes); break; case TOP: va_start(ap, cmd); arg1 = va_arg(ap, int); arg2 = va_arg(ap, int); va_end(ap); if (arg1 - 1 > mbox.count) { bulletin_mbox_op(cxn, TOP, mbox.count + 1, arg1, arg2); break; } if (!mailidx_is_message(&mbox, arg1 - 1)) return(FALSE); mailidx_get_message_top(&idx, &mbox, arg1 - 1, arg2); break; case UIDL: va_start(ap, cmd); arg1 = va_arg(ap, int); va_end(ap); if (arg1 >= 0) { if (arg1 - 1 > mbox.count) { bulletin_mbox_op(cxn, UIDL, mbox.count + 1, arg1); break; } if (!mailidx_is_message(&mbox, arg1 - 1)) return(FALSE); sendline(SEND_FLUSH, "+OK %d %s", arg1, idx.msg[mbox.idx_num[arg1 - 1]].uidl); } else { mailidx_get_uidl_all(&idx, &mbox); bulletin_mbox_op(cxn, UIDL, mbox.count + 1, arg1); sendline(SEND_FLUSH, "."); } break; case LAST: /* backwards compatibility with RFC1460 */ sendline(SEND_FLUSH, "+OK %d", idx.last); break; case BUL_MSGS: /* not reached */ case BUL_SIZE: case APOP: case AUTH: case PASS: case USER: case TIMEDOUT: case INVALCMD: break; } return(FALSE); }