mail_queue.c revision 1.2
1/*	$NetBSD: mail_queue.c,v 1.2 2017/02/14 01:16:45 christos Exp $	*/
2
3/*++
4/* NAME
5/*	mail_queue 3
6/* SUMMARY
7/*	mail queue file access
8/* SYNOPSIS
9/*	#include <mail_queue.h>
10/*
11/*	VSTREAM	*mail_queue_enter(queue_name, mode, tp)
12/*	const char *queue_name;
13/*	mode_t	mode;
14/*	struct timeval *tp;
15/*
16/*	VSTREAM	*mail_queue_open(queue_name, queue_id, flags, mode)
17/*	const char *queue_name;
18/*	const char *queue_id;
19/*	int	flags;
20/*	mode_t	mode;
21/*
22/*	char	*mail_queue_dir(buf, queue_name, queue_id)
23/*	VSTRING	*buf;
24/*	const char *queue_name;
25/*	const char *queue_id;
26/*
27/*	char	*mail_queue_path(buf, queue_name, queue_id)
28/*	VSTRING	*buf;
29/*	const char *queue_name;
30/*	const char *queue_id;
31/*
32/*	int	mail_queue_mkdirs(path)
33/*	const char *path;
34/*
35/*	int	mail_queue_rename(queue_id, old_queue, new_queue)
36/*	const char *queue_id;
37/*	const char *old_queue;
38/*	const char *new_queue;
39/*
40/*	int	mail_queue_remove(queue_name, queue_id)
41/*	const char *queue_name;
42/*	const char *queue_id;
43/*
44/*	int	mail_queue_name_ok(queue_name)
45/*	const char *queue_name;
46/*
47/*	int	mail_queue_id_ok(queue_id)
48/*	const char *queue_id;
49/* DESCRIPTION
50/*	This module encapsulates access to the mail queue hierarchy.
51/*	Unlike most other modules, this one does not abort the program
52/*	in case of file access problems. But it does abort when the
53/*	application attempts to use a malformed queue name or queue id.
54/*
55/*	mail_queue_enter() creates an entry in the named queue. The queue
56/*	id is the file base name, see VSTREAM_PATH().  Queue ids are
57/*	relatively short strings and are recycled in the course of time.
58/*	The only guarantee given is that on a given machine, no two queue
59/*	entries will have the same queue ID at the same time. The tp
60/*	argument, if not a null pointer, receives the time stamp that
61/*	corresponds with the queue ID.
62/*
63/*	mail_queue_open() opens the named queue file. The \fIflags\fR
64/*	and \fImode\fR arguments are as with open(2). The result is a
65/*	null pointer in case of problems.
66/*
67/*	mail_queue_dir() returns the directory name of the specified queue
68/*	file. When a null result buffer pointer is provided, the result is
69/*	written to a private buffer that may be overwritten upon the next
70/*	call.
71/*
72/*	mail_queue_path() returns the pathname of the specified queue
73/*	file. When a null result buffer pointer is provided, the result
74/*	is written to a private buffer that may be overwritten upon the
75/*	next call.
76/*
77/*	mail_queue_mkdirs() creates missing parent directories
78/*	for the file named in \fBpath\fR. A non-zero result means
79/*	that the operation failed.
80/*
81/*	mail_queue_rename() renames a queue file. A non-zero result
82/*	means the operation failed.
83/*
84/*	mail_queue_remove() removes the named queue file. A non-zero result
85/*	means the operation failed.
86/*
87/*	mail_queue_name_ok() validates a mail queue name and returns
88/*	non-zero (true) if the name contains no nasty characters.
89/*
90/*	mail_queue_id_ok() does the same thing for mail queue ID names.
91/* DIAGNOSTICS
92/*	Panic: invalid queue name or id given to mail_queue_path(),
93/*	mail_queue_rename(), or mail_queue_remove().
94/*	Fatal error: out of memory.
95/* LICENSE
96/* .ad
97/* .fi
98/*	The Secure Mailer license must be distributed with this software.
99/* AUTHOR(S)
100/*	Wietse Venema
101/*	IBM T.J. Watson Research
102/*	P.O. Box 704
103/*	Yorktown Heights, NY 10598, USA
104/*--*/
105
106/* System library. */
107
108#include <sys_defs.h>
109#include <stdio.h>			/* rename() */
110#include <stdlib.h>
111#include <ctype.h>
112#include <stdlib.h>
113#include <unistd.h>
114#include <fcntl.h>
115#include <sys/time.h>			/* gettimeofday, not in POSIX */
116#include <string.h>
117#include <errno.h>
118
119#ifdef STRCASECMP_IN_STRINGS_H
120#include <strings.h>
121#endif
122
123/* Utility library. */
124
125#include <msg.h>
126#include <vstring.h>
127#include <vstream.h>
128#include <mymalloc.h>
129#include <argv.h>
130#include <dir_forest.h>
131#include <make_dirs.h>
132#include <split_at.h>
133#include <sane_fsops.h>
134#include <valid_hostname.h>
135
136/* Global library. */
137
138#include "file_id.h"
139#include "mail_params.h"
140#define MAIL_QUEUE_INTERNAL
141#include "mail_queue.h"
142
143#define STR	vstring_str
144
145/* mail_queue_dir - construct mail queue directory name */
146
147const char *mail_queue_dir(VSTRING *buf, const char *queue_name,
148			           const char *queue_id)
149{
150    const char *myname = "mail_queue_dir";
151    static VSTRING *private_buf = 0;
152    static VSTRING *hash_buf = 0;
153    static ARGV *hash_queue_names = 0;
154    static VSTRING *usec_buf = 0;
155    const char *delim;
156    char  **cpp;
157
158    /*
159     * Sanity checks.
160     */
161    if (mail_queue_name_ok(queue_name) == 0)
162	msg_panic("%s: bad queue name: %s", myname, queue_name);
163    if (mail_queue_id_ok(queue_id) == 0)
164	msg_panic("%s: bad queue id: %s", myname, queue_id);
165
166    /*
167     * Initialize.
168     */
169    if (buf == 0) {
170	if (private_buf == 0)
171	    private_buf = vstring_alloc(100);
172	buf = private_buf;
173    }
174    if (hash_buf == 0) {
175	hash_buf = vstring_alloc(100);
176	hash_queue_names = argv_split(var_hash_queue_names, CHARS_COMMA_SP);
177    }
178
179    /*
180     * First, put the basic queue directory name into place.
181     */
182    vstring_strcpy(buf, queue_name);
183    vstring_strcat(buf, "/");
184
185    /*
186     * Then, see if we need to append a little directory forest.
187     */
188    for (cpp = hash_queue_names->argv; *cpp; cpp++) {
189	if (strcasecmp(*cpp, queue_name) == 0) {
190	    if (MQID_FIND_LG_INUM_SEPARATOR(delim, queue_id)) {
191		if (usec_buf == 0)
192		    usec_buf = vstring_alloc(20);
193		MQID_LG_GET_HEX_USEC(usec_buf, delim);
194		queue_id = STR(usec_buf);
195	    }
196	    vstring_strcat(buf,
197		      dir_forest(hash_buf, queue_id, var_hash_queue_depth));
198	    break;
199	}
200    }
201    return (STR(buf));
202}
203
204/* mail_queue_path - map mail queue id to path name */
205
206const char *mail_queue_path(VSTRING *buf, const char *queue_name,
207			            const char *queue_id)
208{
209    static VSTRING *private_buf = 0;
210
211    /*
212     * Initialize.
213     */
214    if (buf == 0) {
215	if (private_buf == 0)
216	    private_buf = vstring_alloc(100);
217	buf = private_buf;
218    }
219
220    /*
221     * Append the queue id to the possibly hashed queue directory.
222     */
223    (void) mail_queue_dir(buf, queue_name, queue_id);
224    vstring_strcat(buf, queue_id);
225    return (STR(buf));
226}
227
228/* mail_queue_mkdirs - fill in missing directories */
229
230int     mail_queue_mkdirs(const char *path)
231{
232    const char *myname = "mail_queue_mkdirs";
233    char   *saved_path = mystrdup(path);
234    int     ret;
235
236    /*
237     * Truncate a copy of the pathname (for safety sake), and create the
238     * missing directories.
239     */
240    if (split_at_right(saved_path, '/') == 0)
241	msg_panic("%s: no slash in: %s", myname, saved_path);
242    ret = make_dirs(saved_path, 0700);
243    myfree(saved_path);
244    return (ret);
245}
246
247/* mail_queue_rename - move message to another queue */
248
249int     mail_queue_rename(const char *queue_id, const char *old_queue,
250			          const char *new_queue)
251{
252    VSTRING *old_buf = vstring_alloc(100);
253    VSTRING *new_buf = vstring_alloc(100);
254    int     error;
255
256    /*
257     * Try the operation. If it fails, see if it is because of missing
258     * intermediate directories.
259     */
260    error = sane_rename(mail_queue_path(old_buf, old_queue, queue_id),
261			mail_queue_path(new_buf, new_queue, queue_id));
262    if (error != 0 && mail_queue_mkdirs(STR(new_buf)) == 0)
263	error = sane_rename(STR(old_buf), STR(new_buf));
264
265    /*
266     * Cleanup.
267     */
268    vstring_free(old_buf);
269    vstring_free(new_buf);
270
271    return (error);
272}
273
274/* mail_queue_remove - remove mail queue file */
275
276int     mail_queue_remove(const char *queue_name, const char *queue_id)
277{
278    return (REMOVE(mail_queue_path((VSTRING *) 0, queue_name, queue_id)));
279}
280
281/* mail_queue_name_ok - validate mail queue name */
282
283int     mail_queue_name_ok(const char *queue_name)
284{
285    const char *cp;
286
287    if (*queue_name == 0 || strlen(queue_name) > 100)
288	return (0);
289
290    for (cp = queue_name; *cp; cp++)
291	if (!ISALNUM(*cp))
292	    return (0);
293    return (1);
294}
295
296/* mail_queue_id_ok - validate mail queue id */
297
298int     mail_queue_id_ok(const char *queue_id)
299{
300    const char *cp;
301
302    /*
303     * A file name is either a queue ID (short alphanumeric string in
304     * time+inum form) or a fast flush service logfile name (destination
305     * domain name with non-alphanumeric characters replaced by "_").
306     */
307    if (*queue_id == 0 || strlen(queue_id) > VALID_HOSTNAME_LEN)
308	return (0);
309
310    /*
311     * OK if in time+inum form or in host_domain_tld form.
312     */
313    for (cp = queue_id; *cp; cp++)
314	if (!ISALNUM(*cp) && *cp != '_')
315	    return (0);
316    return (1);
317}
318
319/* mail_queue_enter - make mail queue entry with locally-unique name */
320
321VSTREAM *mail_queue_enter(const char *queue_name, mode_t mode,
322			          struct timeval * tp)
323{
324    const char *myname = "mail_queue_enter";
325    static VSTRING *sec_buf;
326    static VSTRING *usec_buf;
327    static VSTRING *id_buf;
328    static int pid;
329    static VSTRING *path_buf;
330    static VSTRING *temp_path;
331    struct timeval tv;
332    int     fd;
333    const char *file_id;
334    VSTREAM *stream;
335    int     count;
336
337    /*
338     * Initialize.
339     */
340    if (id_buf == 0) {
341	pid = getpid();
342	sec_buf = vstring_alloc(10);
343	usec_buf = vstring_alloc(10);
344	id_buf = vstring_alloc(10);
345	path_buf = vstring_alloc(10);
346	temp_path = vstring_alloc(100);
347    }
348    if (tp == 0)
349	tp = &tv;
350
351    /*
352     * Create a file with a temporary name that does not collide. The process
353     * ID alone is not sufficiently unique: maildrops can be shared via the
354     * network. Not that I recommend using a network-based queue, or having
355     * multiple hosts write to the same queue, but we should try to avoid
356     * losing mail if we can.
357     *
358     * If someone is racing against us, try to win.
359     */
360    for (;;) {
361	GETTIMEOFDAY(tp);
362	vstring_sprintf(temp_path, "%s/%d.%d", queue_name,
363			(int) tp->tv_usec, pid);
364	if ((fd = open(STR(temp_path), O_RDWR | O_CREAT | O_EXCL, mode)) >= 0)
365	    break;
366	if (errno == EEXIST || errno == EISDIR)
367	    continue;
368	msg_warn("%s: create file %s: %m", myname, STR(temp_path));
369	sleep(10);
370    }
371
372    /*
373     * Rename the file to something that is derived from the file ID. I saw
374     * this idea first being used in Zmailer. On any reasonable file system
375     * the file ID is guaranteed to be unique. Better let the OS resolve
376     * collisions than doing a worse job in an application. Another
377     * attractive property of file IDs is that they can appear in messages
378     * without leaking a significant amount of system information (unlike
379     * process ids). Not so nice is that files need to be renamed when they
380     * are moved to another file system.
381     *
382     * If someone is racing against us, try to win.
383     */
384    file_id = get_file_id_fd(fd, var_long_queue_ids);
385
386    /*
387     * XXX Some systems seem to have clocks that correlate with process
388     * scheduling or something. Unfortunately, we cannot add random
389     * quantities to the time, because the non-inode part of a queue ID must
390     * not repeat within the same second. The queue ID is the sole thing that
391     * prevents multiple messages from getting the same Message-ID value.
392     */
393    for (count = 0;; count++) {
394	GETTIMEOFDAY(tp);
395	if (var_long_queue_ids) {
396	    vstring_sprintf(id_buf, "%s%s%c%s",
397			    MQID_LG_ENCODE_SEC(sec_buf, tp->tv_sec),
398			    MQID_LG_ENCODE_USEC(usec_buf, tp->tv_usec),
399			    MQID_LG_INUM_SEP, file_id);
400	} else {
401	    vstring_sprintf(id_buf, "%s%s",
402			    MQID_SH_ENCODE_USEC(usec_buf, tp->tv_usec),
403			    file_id);
404	}
405	mail_queue_path(path_buf, queue_name, STR(id_buf));
406	if (sane_rename(STR(temp_path), STR(path_buf)) == 0)	/* success */
407	    break;
408	if (errno == EPERM || errno == EISDIR)	/* collision. weird. */
409	    continue;
410	if (errno != ENOENT || mail_queue_mkdirs(STR(path_buf)) < 0) {
411	    msg_warn("%s: rename %s to %s: %m", myname,
412		     STR(temp_path), STR(path_buf));
413	}
414	if (count > 1000)			/* XXX whatever */
415	    msg_fatal("%s: rename %s to %s: giving up", myname,
416		      STR(temp_path), STR(path_buf));
417    }
418
419    stream = vstream_fdopen(fd, O_RDWR);
420    vstream_control(stream, CA_VSTREAM_CTL_PATH(STR(path_buf)), CA_VSTREAM_CTL_END);
421    return (stream);
422}
423
424/* mail_queue_open - open mail queue file */
425
426VSTREAM *mail_queue_open(const char *queue_name, const char *queue_id,
427			         int flags, mode_t mode)
428{
429    const char *path = mail_queue_path((VSTRING *) 0, queue_name, queue_id);
430    VSTREAM *fp;
431
432    /*
433     * Try the operation. If file creation fails, see if it is because of a
434     * missing subdirectory.
435     */
436    if ((fp = vstream_fopen(path, flags, mode)) == 0)
437	if (errno == ENOENT)
438	    if ((flags & O_CREAT) == O_CREAT && mail_queue_mkdirs(path) == 0)
439		fp = vstream_fopen(path, flags, mode);
440    return (fp);
441}
442