summaryrefslogtreecommitdiffstats
path: root/src/virtual/maildir.c
blob: a6770610e59906ea349d7c56a2d7c1a03a665e75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*++
/* NAME
/*	maildir 3
/* SUMMARY
/*	delivery to maildir
/* SYNOPSIS
/*	#include "virtual.h"
/*
/*	int	deliver_maildir(state, usr_attr)
/*	LOCAL_STATE state;
/*	USER_ATTR usr_attr;
/* DESCRIPTION
/*	deliver_maildir() delivers a message to a qmail-style maildir.
/*
/*	Arguments:
/* .IP state
/*	The attributes that specify the message, recipient and more.
/* .IP usr_attr
/*	Attributes describing user rights and environment information.
/* DIAGNOSTICS
/*	deliver_maildir() always succeeds or it bounces the message.
/* SEE ALSO
/*	bounce(3)
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include "sys_defs.h"
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>
#include <vstream.h>
#include <vstring.h>
#include <make_dirs.h>
#include <set_eugid.h>
#include <get_hostname.h>
#include <sane_fsops.h>
#include <warn_stat.h>

/* Global library. */

#include <mail_copy.h>
#include <bounce.h>
#include <defer.h>
#include <sent.h>
#include <mail_params.h>
#include <mbox_open.h>
#include <dsn_util.h>

/* Application-specific. */

#include "virtual.h"

/* deliver_maildir - delivery to maildir-style mailbox */

int     deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr)
{
    const char *myname = "deliver_maildir";
    char   *newdir;
    char   *tmpdir;
    char   *curdir;
    char   *tmpfile;
    char   *newfile;
    DSN_BUF *why = state.msg_attr.why;
    VSTRING *buf;
    VSTREAM *dst;
    int     mail_copy_status;
    int     deliver_status;
    int     copy_flags;
    struct stat st;
    struct timeval starttime;

    GETTIMEOFDAY(&starttime);

    /*
     * Make verbose logging easier to understand.
     */
    state.level++;
    if (msg_verbose)
	MSG_LOG_STATE(myname, state);

    /*
     * Don't deliver trace-only requests.
     */
    if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
	dsb_simple(why, "2.0.0", "delivers to maildir");
	return (sent(BOUNCE_FLAGS(state.request),
		     SENT_ATTR(state.msg_attr)));
    }

    /*
     * Initialize. Assume the operation will fail. Set the delivered
     * attribute to reflect the final recipient.
     */
    if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
	msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
    state.msg_attr.delivered = state.msg_attr.rcpt.address;
    mail_copy_status = MAIL_COPY_STAT_WRITE;
    buf = vstring_alloc(100);

    copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH
	| MAIL_COPY_DELIVERED | MAIL_COPY_ORIG_RCPT;

    newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0);
    tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0);
    curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0);

    /*
     * Create and write the file as the recipient, so that file quota work.
     * Create any missing directories on the fly. The file name is chosen
     * according to ftp://koobera.math.uic.edu/www/proto/maildir.html:
     * 
     * "A unique name has three pieces, separated by dots. On the left is the
     * result of time(). On the right is the result of gethostname(). In the
     * middle is something that doesn't repeat within one second on a single
     * host. I fork a new process for each delivery, so I just use the
     * process ID. If you're delivering several messages from one process,
     * use starttime.pid_count.host, where starttime is the time that your
     * process started, and count is the number of messages you've
     * delivered."
     * 
     * Well, that stopped working on fast machines, and on operating systems
     * that randomize process ID values. When creating a file in tmp/ we use
     * the process ID because it still is an exclusive resource. When moving
     * the file to new/ we use the device number and inode number. I do not
     * care if this breaks on a remote AFS file system, because people should
     * know better.
     * 
     * On January 26, 2003, http://cr.yp.to/proto/maildir.html said:
     * 
     * A unique name has three pieces, separated by dots. On the left is the
     * result of time() or the second counter from gettimeofday(). On the
     * right is the result of gethostname(). (To deal with invalid host
     * names, replace / with \057 and : with \072.) In the middle is a
     * delivery identifier, discussed below.
     * 
     * [...]
     * 
     * Modern delivery identifiers are created by concatenating enough of the
     * following strings to guarantee uniqueness:
     * 
     * [...]
     * 
     * In, where n is (in hexadecimal) the UNIX inode number of this file.
     * Unfortunately, inode numbers aren't always available through NFS.
     * 
     * Vn, where n is (in hexadecimal) the UNIX device number of this file.
     * Unfortunately, device numbers aren't always available through NFS.
     * (Device numbers are also not helpful with the standard UNIX
     * filesystem: a maildir has to be within a single UNIX device for link()
     * and rename() to work.)
     * 
     * Mn, where n is (in decimal) the microsecond counter from the same
     * gettimeofday() used for the left part of the unique name.
     * 
     * Pn, where n is (in decimal) the process ID.
     * 
     * [...]
     */
    set_eugid(usr_attr.uid, usr_attr.gid);
    vstring_sprintf(buf, "%lu.P%d.%s",
		 (unsigned long) starttime.tv_sec, var_pid, get_hostname());
    tmpfile = concatenate(tmpdir, STR(buf), (char *) 0);
    newfile = 0;
    if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0
	&& (errno != ENOENT
	    || make_dirs(tmpdir, 0700) < 0
	    || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) {
	dsb_simple(why, mbox_dsn(errno, "4.2.0"),
		   "create maildir file %s: %m", tmpfile);
    } else if (fstat(vstream_fileno(dst), &st) < 0) {

	/*
	 * Coverity 200604: file descriptor leak in code that never executes.
	 * Code replaced by msg_fatal(), as it is not worthwhile to continue
	 * after an impossible error condition.
	 */
	msg_fatal("fstat %s: %m", tmpfile);
    } else {
	vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s",
			(unsigned long) starttime.tv_sec,
			(unsigned long) st.st_dev,
			(unsigned long) st.st_ino,
			(unsigned long) starttime.tv_usec,
			get_hostname());
	newfile = concatenate(newdir, STR(buf), (char *) 0);
	if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr),
					  dst, copy_flags, "\n",
					  why)) == 0) {
	    if (sane_link(tmpfile, newfile) < 0
		&& (errno != ENOENT
		    || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0
		    || sane_link(tmpfile, newfile) < 0)) {
		dsb_simple(why, mbox_dsn(errno, "4.2.0"),
			   "create maildir file %s: %m", newfile);
		mail_copy_status = MAIL_COPY_STAT_WRITE;
	    }
	}
	if (unlink(tmpfile) < 0)
	    msg_warn("remove %s: %m", tmpfile);
    }
    set_eugid(var_owner_uid, var_owner_gid);

    /*
     * The maildir location is controlled by the mail administrator. If
     * delivery fails, try again later. We would just bounce when the maildir
     * location possibly under user control.
     */
    if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
	deliver_status = DEL_STAT_DEFER;
    } else if (mail_copy_status != 0) {
	if (errno == EACCES) {
	    msg_warn("maildir access problem for UID/GID=%lu/%lu: %s",
		     (long) usr_attr.uid, (long) usr_attr.gid,
		     STR(why->reason));
	    msg_warn("perhaps you need to create the maildirs in advance");
	}
	vstring_sprintf_prepend(why->reason, "maildir delivery failed: ");
	deliver_status =
	    (STR(why->status)[0] == '4' ?
	     defer_append : bounce_append)
	    (BOUNCE_FLAGS(state.request),
	     BOUNCE_ATTR(state.msg_attr));
    } else {
	dsb_simple(why, "2.0.0", "delivered to maildir");
	deliver_status = sent(BOUNCE_FLAGS(state.request),
			      SENT_ATTR(state.msg_attr));
    }
    vstring_free(buf);
    myfree(newdir);
    myfree(tmpdir);
    myfree(curdir);
    myfree(tmpfile);
    if (newfile)
	myfree(newfile);
    return (deliver_status);
}