Home | History | Annotate | Line # | Download | only in global
      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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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