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