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 
mail_queue_dir(VSTRING * buf,const char * queue_name,const char * queue_id)147 const 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 
mail_queue_path(VSTRING * buf,const char * queue_name,const char * queue_id)206 const 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 
mail_queue_mkdirs(const char * path)230 int     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 
mail_queue_rename(const char * queue_id,const char * old_queue,const char * new_queue)249 int     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 
mail_queue_remove(const char * queue_name,const char * queue_id)276 int     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 
mail_queue_name_ok(const char * queue_name)283 int     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 
mail_queue_id_ok(const char * queue_id)298 int     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 
mail_queue_enter(const char * queue_name,mode_t mode,struct timeval * tp)321 VSTREAM *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 
mail_queue_open(const char * queue_name,const char * queue_id,int flags,mode_t mode)426 VSTREAM *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