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