xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/mail_queue.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*	$NetBSD: mail_queue.c,v 1.1.1.1 2009/06/23 10:08:46 tron 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 #include "mail_queue.h"
141 
142 #define STR	vstring_str
143 
144 /* mail_queue_dir - construct mail queue directory name */
145 
146 const char *mail_queue_dir(VSTRING *buf, const char *queue_name,
147 			           const char *queue_id)
148 {
149     const char *myname = "mail_queue_dir";
150     static VSTRING *private_buf = 0;
151     static VSTRING *hash_buf = 0;
152     static ARGV *hash_queue_names = 0;
153     char  **cpp;
154 
155     /*
156      * Sanity checks.
157      */
158     if (mail_queue_name_ok(queue_name) == 0)
159 	msg_panic("%s: bad queue name: %s", myname, queue_name);
160     if (mail_queue_id_ok(queue_id) == 0)
161 	msg_panic("%s: bad queue id: %s", myname, queue_id);
162 
163     /*
164      * Initialize.
165      */
166     if (buf == 0) {
167 	if (private_buf == 0)
168 	    private_buf = vstring_alloc(100);
169 	buf = private_buf;
170     }
171     if (hash_buf == 0) {
172 	hash_buf = vstring_alloc(100);
173 	hash_queue_names = argv_split(var_hash_queue_names, " \t\r\n,");
174     }
175 
176     /*
177      * First, put the basic queue directory name into place.
178      */
179     vstring_strcpy(buf, queue_name);
180     vstring_strcat(buf, "/");
181 
182     /*
183      * Then, see if we need to append a little directory forest.
184      */
185     for (cpp = hash_queue_names->argv; *cpp; cpp++) {
186 	if (strcasecmp(*cpp, queue_name) == 0) {
187 	    vstring_strcat(buf,
188 		      dir_forest(hash_buf, queue_id, var_hash_queue_depth));
189 	    break;
190 	}
191     }
192     return (STR(buf));
193 }
194 
195 /* mail_queue_path - map mail queue id to path name */
196 
197 const char *mail_queue_path(VSTRING *buf, const char *queue_name,
198 			            const char *queue_id)
199 {
200     static VSTRING *private_buf = 0;
201 
202     /*
203      * Initialize.
204      */
205     if (buf == 0) {
206 	if (private_buf == 0)
207 	    private_buf = vstring_alloc(100);
208 	buf = private_buf;
209     }
210 
211     /*
212      * Append the queue id to the possibly hashed queue directory.
213      */
214     (void) mail_queue_dir(buf, queue_name, queue_id);
215     vstring_strcat(buf, queue_id);
216     return (STR(buf));
217 }
218 
219 /* mail_queue_mkdirs - fill in missing directories */
220 
221 int     mail_queue_mkdirs(const char *path)
222 {
223     const char *myname = "mail_queue_mkdirs";
224     char   *saved_path = mystrdup(path);
225     int     ret;
226 
227     /*
228      * Truncate a copy of the pathname (for safety sake), and create the
229      * missing directories.
230      */
231     if (split_at_right(saved_path, '/') == 0)
232 	msg_panic("%s: no slash in: %s", myname, saved_path);
233     ret = make_dirs(saved_path, 0700);
234     myfree(saved_path);
235     return (ret);
236 }
237 
238 /* mail_queue_rename - move message to another queue */
239 
240 int     mail_queue_rename(const char *queue_id, const char *old_queue,
241 			          const char *new_queue)
242 {
243     VSTRING *old_buf = vstring_alloc(100);
244     VSTRING *new_buf = vstring_alloc(100);
245     int     error;
246 
247     /*
248      * Try the operation. If it fails, see if it is because of missing
249      * intermediate directories.
250      */
251     error = sane_rename(mail_queue_path(old_buf, old_queue, queue_id),
252 			mail_queue_path(new_buf, new_queue, queue_id));
253     if (error != 0 && mail_queue_mkdirs(STR(new_buf)) == 0)
254 	error = sane_rename(STR(old_buf), STR(new_buf));
255 
256     /*
257      * Cleanup.
258      */
259     vstring_free(old_buf);
260     vstring_free(new_buf);
261 
262     return (error);
263 }
264 
265 /* mail_queue_remove - remove mail queue file */
266 
267 int     mail_queue_remove(const char *queue_name, const char *queue_id)
268 {
269     return (REMOVE(mail_queue_path((VSTRING *) 0, queue_name, queue_id)));
270 }
271 
272 /* mail_queue_name_ok - validate mail queue name */
273 
274 int     mail_queue_name_ok(const char *queue_name)
275 {
276     const char *cp;
277 
278     if (*queue_name == 0 || strlen(queue_name) > 100)
279 	return (0);
280 
281     for (cp = queue_name; *cp; cp++)
282 	if (!ISALNUM(*cp))
283 	    return (0);
284     return (1);
285 }
286 
287 /* mail_queue_id_ok - validate mail queue id */
288 
289 int     mail_queue_id_ok(const char *queue_id)
290 {
291     const char *cp;
292 
293     /*
294      * A file name is either a queue ID (short alphanumeric string in
295      * time+inum form) or a fast flush service logfile name (destination
296      * domain name with non-alphanumeric characters replaced by "_").
297      */
298     if (*queue_id == 0 || strlen(queue_id) > VALID_HOSTNAME_LEN)
299 	return (0);
300 
301     /*
302      * OK if in time+inum form or in host_domain_tld form.
303      */
304     for (cp = queue_id; *cp; cp++)
305 	if (!ISALNUM(*cp) && *cp != '_')
306 	    return (0);
307     return (1);
308 }
309 
310 /* mail_queue_enter - make mail queue entry with locally-unique name */
311 
312 VSTREAM *mail_queue_enter(const char *queue_name, mode_t mode,
313 			          struct timeval * tp)
314 {
315     const char *myname = "mail_queue_enter";
316     static VSTRING *id_buf;
317     static int pid;
318     static VSTRING *path_buf;
319     static VSTRING *temp_path;
320     struct timeval tv;
321     int     fd;
322     const char *file_id;
323     VSTREAM *stream;
324     int     count;
325 
326     /*
327      * Initialize.
328      */
329     if (id_buf == 0) {
330 	pid = getpid();
331 	id_buf = vstring_alloc(10);
332 	path_buf = vstring_alloc(10);
333 	temp_path = vstring_alloc(100);
334     }
335     if (tp == 0)
336 	tp = &tv;
337 
338     /*
339      * Create a file with a temporary name that does not collide. The process
340      * ID alone is not sufficiently unique: maildrops can be shared via the
341      * network. Not that I recommend using a network-based queue, or having
342      * multiple hosts write to the same queue, but we should try to avoid
343      * losing mail if we can.
344      *
345      * If someone is racing against us, try to win.
346      */
347     for (;;) {
348 	GETTIMEOFDAY(tp);
349 	vstring_sprintf(temp_path, "%s/%d.%d", queue_name,
350 			(int) tp->tv_usec, pid);
351 	if ((fd = open(STR(temp_path), O_RDWR | O_CREAT | O_EXCL, mode)) >= 0)
352 	    break;
353 	if (errno == EEXIST || errno == EISDIR)
354 	    continue;
355 	msg_warn("%s: create file %s: %m", myname, STR(temp_path));
356 	sleep(10);
357     }
358 
359     /*
360      * Rename the file to something that is derived from the file ID. I saw
361      * this idea first being used in Zmailer. On any reasonable file system
362      * the file ID is guaranteed to be unique. Better let the OS resolve
363      * collisions than doing a worse job in an application. Another
364      * attractive property of file IDs is that they can appear in messages
365      * without leaking a significant amount of system information (unlike
366      * process ids). Not so nice is that files need to be renamed when they
367      * are moved to another file system.
368      *
369      * If someone is racing against us, try to win.
370      */
371     file_id = get_file_id(fd);
372 
373     /*
374      * XXX Some systems seem to have clocks that correlate with process
375      * scheduling or something. Unfortunately, we cannot add random
376      * quantities to the time, because the non-inode part of a queue ID must
377      * not repeat within the same second. The queue ID is the sole thing that
378      * prevents multiple messages from getting the same Message-ID value.
379      */
380     for (count = 0;; count++) {
381 	GETTIMEOFDAY(tp);
382 	vstring_sprintf(id_buf, "%05X%s", (int) tp->tv_usec, file_id);
383 	mail_queue_path(path_buf, queue_name, STR(id_buf));
384 	if (sane_rename(STR(temp_path), STR(path_buf)) == 0)	/* success */
385 	    break;
386 	if (errno == EPERM || errno == EISDIR)	/* collision. weird. */
387 	    continue;
388 	if (errno != ENOENT || mail_queue_mkdirs(STR(path_buf)) < 0) {
389 	    msg_warn("%s: rename %s to %s: %m", myname,
390 		     STR(temp_path), STR(path_buf));
391 	}
392 	if (count > 1000)			/* XXX whatever */
393 	    msg_fatal("%s: rename %s to %s: giving up", myname,
394 		      STR(temp_path), STR(path_buf));
395     }
396 
397     stream = vstream_fdopen(fd, O_RDWR);
398     vstream_control(stream, VSTREAM_CTL_PATH, STR(path_buf), VSTREAM_CTL_END);
399     return (stream);
400 }
401 
402 /* mail_queue_open - open mail queue file */
403 
404 VSTREAM *mail_queue_open(const char *queue_name, const char *queue_id,
405 			         int flags, mode_t mode)
406 {
407     const char *path = mail_queue_path((VSTRING *) 0, queue_name, queue_id);
408     VSTREAM *fp;
409 
410     /*
411      * Try the operation. If file creation fails, see if it is because of a
412      * missing subdirectory.
413      */
414     if ((fp = vstream_fopen(path, flags, mode)) == 0)
415 	if (errno == ENOENT)
416 	    if ((flags & O_CREAT) == O_CREAT && mail_queue_mkdirs(path) == 0)
417 		fp = vstream_fopen(path, flags, mode);
418     return (fp);
419 }
420