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