1/*++ 2/* NAME 3/* maildir 3 4/* SUMMARY 5/* delivery to maildir 6/* SYNOPSIS 7/* #include "virtual.h" 8/* 9/* int deliver_maildir(state, usr_attr) 10/* LOCAL_STATE state; 11/* USER_ATTR usr_attr; 12/* DESCRIPTION 13/* deliver_maildir() delivers a message to a qmail-style maildir. 14/* 15/* Arguments: 16/* .IP state 17/* The attributes that specify the message, recipient and more. 18/* .IP usr_attr 19/* Attributes describing user rights and environment information. 20/* DIAGNOSTICS 21/* deliver_maildir() always succeeds or it bounces the message. 22/* SEE ALSO 23/* bounce(3) 24/* LICENSE 25/* .ad 26/* .fi 27/* The Secure Mailer license must be distributed with this software. 28/* AUTHOR(S) 29/* Wietse Venema 30/* IBM T.J. Watson Research 31/* P.O. Box 704 32/* Yorktown Heights, NY 10598, USA 33/*--*/ 34 35/* System library. */ 36 37#include "sys_defs.h" 38#include <sys/stat.h> 39#include <sys/time.h> 40#include <unistd.h> 41#include <time.h> 42#include <errno.h> 43 44/* Utility library. */ 45 46#include <msg.h> 47#include <mymalloc.h> 48#include <stringops.h> 49#include <vstream.h> 50#include <vstring.h> 51#include <make_dirs.h> 52#include <set_eugid.h> 53#include <get_hostname.h> 54#include <sane_fsops.h> 55#include <warn_stat.h> 56 57/* Global library. */ 58 59#include <mail_copy.h> 60#include <bounce.h> 61#include <defer.h> 62#include <sent.h> 63#include <mail_params.h> 64#include <mbox_open.h> 65#include <dsn_util.h> 66 67/* Application-specific. */ 68 69#include "virtual.h" 70 71/* deliver_maildir - delivery to maildir-style mailbox */ 72 73int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr) 74{ 75 const char *myname = "deliver_maildir"; 76 char *newdir; 77 char *tmpdir; 78 char *curdir; 79 char *tmpfile; 80 char *newfile; 81 DSN_BUF *why = state.msg_attr.why; 82 VSTRING *buf; 83 VSTREAM *dst; 84 int mail_copy_status; 85 int deliver_status; 86 int copy_flags; 87 struct stat st; 88 struct timeval starttime; 89 90 GETTIMEOFDAY(&starttime); 91 92 /* 93 * Make verbose logging easier to understand. 94 */ 95 state.level++; 96 if (msg_verbose) 97 MSG_LOG_STATE(myname, state); 98 99 /* 100 * Don't deliver trace-only requests. 101 */ 102 if (DEL_REQ_TRACE_ONLY(state.request->flags)) { 103 dsb_simple(why, "2.0.0", "delivers to maildir"); 104 return (sent(BOUNCE_FLAGS(state.request), 105 SENT_ATTR(state.msg_attr))); 106 } 107 108 /* 109 * Initialize. Assume the operation will fail. Set the delivered 110 * attribute to reflect the final recipient. 111 */ 112 if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) 113 msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); 114 state.msg_attr.delivered = state.msg_attr.rcpt.address; 115 mail_copy_status = MAIL_COPY_STAT_WRITE; 116 buf = vstring_alloc(100); 117 118 copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH 119 | MAIL_COPY_DELIVERED | MAIL_COPY_ORIG_RCPT; 120 121 newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0); 122 tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0); 123 curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0); 124 125 /* 126 * Create and write the file as the recipient, so that file quota work. 127 * Create any missing directories on the fly. The file name is chosen 128 * according to ftp://koobera.math.uic.edu/www/proto/maildir.html: 129 * 130 * "A unique name has three pieces, separated by dots. On the left is the 131 * result of time(). On the right is the result of gethostname(). In the 132 * middle is something that doesn't repeat within one second on a single 133 * host. I fork a new process for each delivery, so I just use the 134 * process ID. If you're delivering several messages from one process, 135 * use starttime.pid_count.host, where starttime is the time that your 136 * process started, and count is the number of messages you've 137 * delivered." 138 * 139 * Well, that stopped working on fast machines, and on operating systems 140 * that randomize process ID values. When creating a file in tmp/ we use 141 * the process ID because it still is an exclusive resource. When moving 142 * the file to new/ we use the device number and inode number. I do not 143 * care if this breaks on a remote AFS file system, because people should 144 * know better. 145 * 146 * On January 26, 2003, http://cr.yp.to/proto/maildir.html said: 147 * 148 * A unique name has three pieces, separated by dots. On the left is the 149 * result of time() or the second counter from gettimeofday(). On the 150 * right is the result of gethostname(). (To deal with invalid host 151 * names, replace / with \057 and : with \072.) In the middle is a 152 * delivery identifier, discussed below. 153 * 154 * [...] 155 * 156 * Modern delivery identifiers are created by concatenating enough of the 157 * following strings to guarantee uniqueness: 158 * 159 * [...] 160 * 161 * In, where n is (in hexadecimal) the UNIX inode number of this file. 162 * Unfortunately, inode numbers aren't always available through NFS. 163 * 164 * Vn, where n is (in hexadecimal) the UNIX device number of this file. 165 * Unfortunately, device numbers aren't always available through NFS. 166 * (Device numbers are also not helpful with the standard UNIX 167 * filesystem: a maildir has to be within a single UNIX device for link() 168 * and rename() to work.) 169 * 170 * Mn, where n is (in decimal) the microsecond counter from the same 171 * gettimeofday() used for the left part of the unique name. 172 * 173 * Pn, where n is (in decimal) the process ID. 174 * 175 * [...] 176 */ 177 set_eugid(usr_attr.uid, usr_attr.gid); 178 vstring_sprintf(buf, "%lu.P%d.%s", 179 (unsigned long) starttime.tv_sec, var_pid, get_hostname()); 180 tmpfile = concatenate(tmpdir, STR(buf), (char *) 0); 181 newfile = 0; 182 if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0 183 && (errno != ENOENT 184 || make_dirs(tmpdir, 0700) < 0 185 || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { 186 dsb_simple(why, mbox_dsn(errno, "4.2.0"), 187 "create maildir file %s: %m", tmpfile); 188 } else if (fstat(vstream_fileno(dst), &st) < 0) { 189 190 /* 191 * Coverity 200604: file descriptor leak in code that never executes. 192 * Code replaced by msg_fatal(), as it is not worthwhile to continue 193 * after an impossible error condition. 194 */ 195 msg_fatal("fstat %s: %m", tmpfile); 196 } else { 197 vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s", 198 (unsigned long) starttime.tv_sec, 199 (unsigned long) st.st_dev, 200 (unsigned long) st.st_ino, 201 (unsigned long) starttime.tv_usec, 202 get_hostname()); 203 newfile = concatenate(newdir, STR(buf), (char *) 0); 204 if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), 205 dst, copy_flags, "\n", 206 why)) == 0) { 207 if (sane_link(tmpfile, newfile) < 0 208 && (errno != ENOENT 209 || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 210 || sane_link(tmpfile, newfile) < 0)) { 211 dsb_simple(why, mbox_dsn(errno, "4.2.0"), 212 "create maildir file %s: %m", newfile); 213 mail_copy_status = MAIL_COPY_STAT_WRITE; 214 } 215 } 216 if (unlink(tmpfile) < 0) 217 msg_warn("remove %s: %m", tmpfile); 218 } 219 set_eugid(var_owner_uid, var_owner_gid); 220 221 /* 222 * The maildir location is controlled by the mail administrator. If 223 * delivery fails, try again later. We would just bounce when the maildir 224 * location possibly under user control. 225 */ 226 if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { 227 deliver_status = DEL_STAT_DEFER; 228 } else if (mail_copy_status != 0) { 229 if (errno == EACCES) { 230 msg_warn("maildir access problem for UID/GID=%lu/%lu: %s", 231 (long) usr_attr.uid, (long) usr_attr.gid, 232 STR(why->reason)); 233 msg_warn("perhaps you need to create the maildirs in advance"); 234 } 235 vstring_sprintf_prepend(why->reason, "maildir delivery failed: "); 236 deliver_status = 237 (STR(why->status)[0] == '4' ? 238 defer_append : bounce_append) 239 (BOUNCE_FLAGS(state.request), 240 BOUNCE_ATTR(state.msg_attr)); 241 } else { 242 dsb_simple(why, "2.0.0", "delivered to maildir"); 243 deliver_status = sent(BOUNCE_FLAGS(state.request), 244 SENT_ATTR(state.msg_attr)); 245 } 246 vstring_free(buf); 247 myfree(newdir); 248 myfree(tmpdir); 249 myfree(curdir); 250 myfree(tmpfile); 251 if (newfile) 252 myfree(newfile); 253 return (deliver_status); 254} 255