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