1/* $NetBSD$ */ 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 146const 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 197const 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 221int 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 240int 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 267int 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 274int 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 289int 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 312VSTREAM *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 404VSTREAM *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