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
deliver_maildir(LOCAL_STATE state,USER_ATTR usr_attr,char * path)78 int 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