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