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