/* * Delete records pointed to missing article file */ int clean_dirent(const char *direct) { int fdr, fdw, fdt; FILEHEADER fhr; FILE *fhw; char fn_dirty[PATHLEN]; int result = 0; if ((fdr = open(direct, O_RDWR)) < 0) return -1; if (myflock(fdr, LOCK_EX)) { close(fdr); return -1; } fhw = tmpfile(); fdw = fileno(fhw); while (myread(fdr, &fhr, FH_SIZE) == FH_SIZE) { if (fhr.filename[0]) { fhr.filename[sizeof(fhr.filename)-1] = '\0'; setdotfile(fn_dirty, direct, fhr.filename); if ((fdt = open(fn_dirty, O_RDONLY)) > 0) { close(fdt); if (mywrite(fdw, &fhr, FH_SIZE) != FH_SIZE) { result = -1; break; } ++result; } } } if (result > 0) result = myfdcp(fdw, fdr); close(fdw); flock(fdr, LOCK_UN); close(fdr); return result; }
static void event_server_execute(int unused_event, char *context) { VSTREAM *stream = (VSTREAM *) context; HTABLE *attr = (vstream_flags(stream) == event_server_saved_flags ? (HTABLE *) vstream_context(stream) : 0); if (event_server_lock != 0 && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("select unlock: %m"); /* * Do bother the application when the client disconnected. Don't drop the * already accepted client request after "postfix reload"; that would be * rude. */ if (master_notify(var_pid, event_server_generation, MASTER_STAT_TAKEN) < 0) /* void */ ; event_server_service(stream, event_server_name, event_server_argv); if (master_notify(var_pid, event_server_generation, MASTER_STAT_AVAIL) < 0) event_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); if (attr) htable_free(attr, myfree); }
static void multi_server_execute(int unused_event, char *context) { VSTREAM *stream = (VSTREAM *) context; if (multi_server_lock != 0 && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("select unlock: %m"); /* * Do not bother the application when the client disconnected. Don't drop * the already accepted client request after "postfix reload"; that would * be rude. */ if (peekfd(vstream_fileno(stream)) > 0) { if (master_notify(var_pid, multi_server_generation, MASTER_STAT_TAKEN) < 0) /* void */ ; multi_server_service(stream, multi_server_name, multi_server_argv); if (master_notify(var_pid, multi_server_generation, MASTER_STAT_AVAIL) < 0) multi_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); } else { multi_server_disconnect(stream); } }
static int dict_db_update(DICT *dict, const char *name, const char *value) { DICT_DB *dict_db = (DICT_DB *) dict; DB *db = dict_db->db; DBT db_key; DBT db_value; int status; dict->error = 0; /* * Sanity check. */ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) msg_panic("dict_db_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } memset(&db_key, 0, sizeof(db_key)); memset(&db_value, 0, sizeof(db_value)); db_key.data = (void *) name; db_value.data = (void *) value; db_key.size = strlen(name); db_value.size = strlen(value); /* * If undecided about appending a null byte to key and value, choose a * default depending on the platform. */ if ((dict->flags & DICT_FLAG_TRY1NULL) && (dict->flags & DICT_FLAG_TRY0NULL)) { #ifdef DB_NO_TRAILING_NULL dict->flags &= ~DICT_FLAG_TRY1NULL; #else dict->flags &= ~DICT_FLAG_TRY0NULL; #endif } /* * Optionally append a null byte to key and value. */ if (dict->flags & DICT_FLAG_TRY1NULL) { db_key.size++; db_value.size++; } /* * Acquire an exclusive lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); /* * Do the update. */ if ((status = DICT_DB_PUT(db, &db_key, &db_value, (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : DONT_CLOBBER)) < 0) msg_fatal("error writing %s: %m", dict_db->dict.name); if (status) { if (dict->flags & DICT_FLAG_DUP_IGNORE) /* void */ ; else if (dict->flags & DICT_FLAG_DUP_WARN) msg_warn("%s: duplicate entry: \"%s\"", dict_db->dict.name, name); else msg_fatal("%s: duplicate entry: \"%s\"", dict_db->dict.name, name); } if (dict->flags & DICT_FLAG_SYNC_UPDATE) if (DICT_DB_SYNC(db, 0) < 0) msg_fatal("%s: flush dictionary: %m", dict_db->dict.name); /* * Release the exclusive lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); return (status); }
static const char *dict_db_lookup(DICT *dict, const char *name) { DICT_DB *dict_db = (DICT_DB *) dict; DB *db = dict_db->db; DBT db_key; DBT db_value; int status; const char *result = 0; dict->error = 0; /* * Sanity check. */ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) msg_panic("dict_db_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); memset(&db_key, 0, sizeof(db_key)); memset(&db_value, 0, sizeof(db_value)); /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } /* * Acquire a shared lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); /* * See if this DB file was written with one null byte appended to key and * value. */ if (dict->flags & DICT_FLAG_TRY1NULL) { db_key.data = (void *) name; db_key.size = strlen(name) + 1; if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0) msg_fatal("error reading %s: %m", dict_db->dict.name); if (status == 0) { dict->flags &= ~DICT_FLAG_TRY0NULL; result = SCOPY(dict_db->val_buf, db_value.data, db_value.size); } } /* * See if this DB file was written with no null byte appended to key and * value. */ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { db_key.data = (void *) name; db_key.size = strlen(name); if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0) msg_fatal("error reading %s: %m", dict_db->dict.name); if (status == 0) { dict->flags &= ~DICT_FLAG_TRY1NULL; result = SCOPY(dict_db->val_buf, db_value.data, db_value.size); } } /* * Release the shared lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); return (result); }
int main(int argc, char **argv) { static VSTREAM *lock_fp; static VSTREAM *data_lock_fp; VSTRING *lock_path; VSTRING *data_lock_path; off_t inherited_limit; int debug_me = 0; int ch; int fd; int n; int test_lock = 0; VSTRING *why; WATCHDOG *watchdog; ARGV *import_env; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Initialize. */ umask(077); /* never fails! */ /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Strip and save the process name for diagnostics etc. */ var_procname = mystrdup(basename(argv[0])); /* * When running a child process, don't leak any open files that were * leaked to us by our own (privileged) parent process. Descriptors 0-2 * are taken care of after we have initialized error logging. * * Some systems such as AIX have a huge per-process open file limit. In * those cases, limit the search for potential file descriptor leaks to * just the first couple hundred. * * The Debian post-installation script passes an open file descriptor into * the master process and waits forever for someone to close it. Because * of this we have to close descriptors > 2, and pray that doing so does * not break things. */ closefrom(3); /* * Initialize logging and exit handler. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * The mail system must be run by the superuser so it can revoke * privileges for selected operations. That's right - it takes privileges * to toss privileges. */ if (getuid() != 0) msg_fatal("the master command is reserved for the superuser"); if (unsafe() != 0) msg_fatal("the master command must not run as a set-uid process"); /* * Process JCL. */ while ((ch = GETOPT(argc, argv, "c:Dde:tv")) > 0) { switch (ch) { case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'd': master_detach = 0; break; case 'e': event_request_timer(master_exit_event, (char *) 0, atoi(optarg)); break; case 'D': debug_me = 1; break; case 't': test_lock = 1; break; case 'v': msg_verbose++; break; default: usage(argv[0]); /* NOTREACHED */ } } /* * This program takes no other arguments. */ if (argc > optind) usage(argv[0]); /* * If started from a terminal, get rid of any tty association. This also * means that all errors and warnings must go to the syslog daemon. */ if (master_detach) for (fd = 0; fd < 3; fd++) { (void) close(fd); if (open("/dev/null", O_RDWR, 0) != fd) msg_fatal("open /dev/null: %m"); } /* * Run in a separate process group, so that "postfix stop" can terminate * all MTA processes cleanly. Give up if we can't separate from our * parent process. We're not supposed to blow away the parent. */ if (debug_me == 0 && master_detach != 0 && setsid() == -1 && getsid(0) != getpid()) msg_fatal("unable to set session and process group ID: %m"); /* * Make some room for plumbing with file descriptors. XXX This breaks * when a service listens on many ports. In order to do this right we * must change the master-child interface so that descriptors do not need * to have fixed numbers. * * In a child we need two descriptors for the flow control pipe, one for * child->master status updates and at least one for listening. */ for (n = 0; n < 5; n++) { if (close_on_exec(dup(0), CLOSE_ON_EXEC) < 0) msg_fatal("dup(0): %m"); } /* * Final initializations. Unfortunately, we must read the global Postfix * configuration file after doing command-line processing, so that we get * consistent results when we SIGHUP the server to reload configuration * files. */ master_vars_init(); /* * In case of multi-protocol support. This needs to be done because * master does not invoke mail_params_init() (it was written before that * code existed). */ (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols); /* * Environment import filter, to enforce consistent behavior whether * Postfix is started by hand, or at system boot time. */ import_env = argv_split(var_import_environ, ", \t\r\n"); clean_env(import_env->argv); argv_free(import_env); if ((inherited_limit = get_file_limit()) < 0) set_file_limit(OFF_T_MAX); if (chdir(var_queue_dir)) msg_fatal("chdir %s: %m", var_queue_dir); /* * Lock down the master.pid file. In test mode, no file means that it * isn't locked. */ lock_path = vstring_alloc(10); data_lock_path = vstring_alloc(10); why = vstring_alloc(10); vstring_sprintf(lock_path, "%s/%s.pid", DEF_PID_DIR, var_procname); if (test_lock && access(vstring_str(lock_path), F_OK) < 0) exit(0); lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0644, why); if (test_lock) exit(lock_fp ? 0 : 1); if (lock_fp == 0) msg_fatal("open lock file %s: %s", vstring_str(lock_path), vstring_str(why)); vstream_fprintf(lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, (unsigned long) var_pid); if (vstream_fflush(lock_fp)) msg_fatal("cannot update lock file %s: %m", vstring_str(lock_path)); close_on_exec(vstream_fileno(lock_fp), CLOSE_ON_EXEC); /* * Lock down the Postfix-writable data directory. */ vstring_sprintf(data_lock_path, "%s/%s.lock", var_data_dir, var_procname); set_eugid(var_owner_uid, var_owner_gid); data_lock_fp = open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0644, why); set_ugid(getuid(), getgid()); if (data_lock_fp == 0) msg_fatal("open lock file %s: %s", vstring_str(data_lock_path), vstring_str(why)); vstream_fprintf(data_lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, (unsigned long) var_pid); if (vstream_fflush(data_lock_fp)) msg_fatal("cannot update lock file %s: %m", vstring_str(data_lock_path)); close_on_exec(vstream_fileno(data_lock_fp), CLOSE_ON_EXEC); /* * Clean up. */ vstring_free(why); vstring_free(lock_path); vstring_free(data_lock_path); /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Finish initialization, last part. We must process configuration files * after processing command-line parameters, so that we get consistent * results when we SIGHUP the server to reload configuration files. */ master_config(); master_sigsetup(); master_flow_init(); msg_info("daemon started -- version %s, configuration %s", var_mail_version, var_config_dir); /* * Process events. The event handler will execute the read/write/timer * action routines. Whenever something has happened, see if we received * any signal in the mean time. Although the master process appears to do * multiple things at the same time, it really is all a single thread, so * that there are no concurrency conflicts within the master process. */ #define MASTER_WATCHDOG_TIME 1000 watchdog = watchdog_create(MASTER_WATCHDOG_TIME, (WATCHDOG_FN) 0, (char *) 0); for (;;) { #ifdef HAS_VOLATILE_LOCKS if (myflock(vstream_fileno(lock_fp), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("refresh exclusive lock: %m"); if (myflock(vstream_fileno(data_lock_fp), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("refresh exclusive lock: %m"); #endif watchdog_start(watchdog); /* same as trigger servers */ event_loop(MASTER_WATCHDOG_TIME / 2); if (master_gotsighup) { msg_info("reload -- version %s, configuration %s", var_mail_version, var_config_dir); master_gotsighup = 0; /* this first */ master_vars_init(); /* then this */ master_refresh(); /* then this */ } if (master_gotsigchld) { if (msg_verbose) msg_info("got sigchld"); master_gotsigchld = 0; /* this first */ master_reap_child(); /* then this */ } } }
QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id, int qflags, mode_t mode) { const char *myname = "qmgr_message_alloc"; QMGR_MESSAGE *message; if (msg_verbose) msg_info("%s: %s %s", myname, queue_name, queue_id); /* * Create an in-core message structure. */ message = qmgr_message_create(queue_name, queue_id, qflags); /* * Extract message envelope information: time of arrival, sender address, * recipient addresses. Skip files with malformed envelope information. */ #define QMGR_LOCK_MODE (MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) if (qmgr_message_open(message) < 0) { qmgr_message_free(message); return (0); } if (myflock(vstream_fileno(message->fp), INTERNAL_LOCK, QMGR_LOCK_MODE) < 0) { msg_info("%s: skipped, still being delivered", queue_id); qmgr_message_close(message); qmgr_message_free(message); return (QMGR_MESSAGE_LOCKED); } if (qmgr_message_read(message) < 0) { qmgr_message_close(message); qmgr_message_free(message); return (0); } else { /* * We have validated the queue file content, so it is safe to modify * the file properties now. */ if (mode != 0 && fchmod(vstream_fileno(message->fp), mode) < 0) msg_fatal("fchmod %s: %m", VSTREAM_PATH(message->fp)); /* * Reset the defer log. This code should not be here, but we must * reset the defer log *after* acquiring the exclusive lock on the * queue file and *before* resolving new recipients. Since all those * operations are encapsulated so nicely by this routine, the defer * log reset has to be done here as well. * * Note: it is safe to remove the defer logfile from a previous queue * run of this queue file, because the defer log contains information * about recipients that still exist in this queue file. */ if (mail_queue_remove(MAIL_QUEUE_DEFER, queue_id) && errno != ENOENT) msg_fatal("%s: %s: remove %s %s: %m", myname, queue_id, MAIL_QUEUE_DEFER, queue_id); qmgr_message_sort(message); qmgr_message_resolve(message); qmgr_message_sort(message); qmgr_message_assign(message); qmgr_message_close(message); if (message->rcpt_offset == 0) qmgr_message_move_limits(message); return (message); } }
static int flush_send_path(const char *path, int how) { const char *myname = "flush_send_path"; VSTRING *queue_id; VSTRING *queue_file; VSTREAM *log; struct utimbuf tbuf; static char qmgr_flush_trigger[] = { QMGR_REQ_FLUSH_DEAD, /* flush dead site/transport cache */ }; static char qmgr_scan_trigger[] = { QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */ }; HTABLE *dup_filter; int count; /* * Sanity check. */ if (!mail_queue_id_ok(path)) return (FLUSH_STAT_BAD); /* * Open the logfile. If the file does not exist, then there is no queued * mail for this destination. */ if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path, O_RDWR, 0600)) == 0) { if (errno != ENOENT) msg_fatal("%s: open fast flush logfile %s: %m", myname, path); return (FLUSH_STAT_OK); } /* * We must lock the logfile, so that we don't lose information when it is * truncated. Unfortunately, this means that the file can be locked for a * significant amount of time. If things really get stuck the Postfix * watchdog will take care of it. */ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("%s: lock fast flush logfile %s: %m", myname, path); /* * With the UNTHROTTLE_BEFORE strategy, we ask the queue manager to * unthrottle all transports and queues before we move a deferred queue * file to the incoming queue. This minimizes a race condition where the * queue manager seizes a queue file before it knows that we want to * flush that message. * * This reduces the race condition time window to a very small amount (the * flush server does not really know when the queue manager reads its * command fifo). But there is a worse race, where the queue manager * moves a deferred queue file to the active queue before we have a * chance to expedite its delivery. */ if (how & UNTHROTTLE_BEFORE) mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, qmgr_flush_trigger, sizeof(qmgr_flush_trigger)); /* * This is the part that dominates running time: schedule the listed * queue files for delivery by updating their file time stamps and by * moving them from the deferred queue to the incoming queue. This should * take no more than a couple seconds under normal conditions. Filter out * duplicate queue file names to avoid hammering the file system, with * some finite limit on the amount of memory that we are willing to * sacrifice for duplicate filtering. Graceful degradation. * * By moving selected queue files from the deferred queue to the incoming * queue we optimize for the case where most deferred mail is for other * sites. If that assumption does not hold, i.e. all deferred mail is for * the same site, then doing a "fast flush" will cost more disk I/O than * a "slow flush" that delivers the entire deferred queue. This penalty * is only temporary - it will go away after we unite the active queue * and the incoming queue. */ queue_id = vstring_alloc(10); queue_file = vstring_alloc(10); dup_filter = htable_create(10); tbuf.actime = tbuf.modtime = event_time(); for (count = 0; vstring_get_nonl(queue_id, log) != VSTREAM_EOF; count++) { if (!mail_queue_id_ok(STR(queue_id))) { msg_warn("bad queue id \"%.30s...\" in fast flush logfile %s", STR(queue_id), path); continue; } if (dup_filter->used >= FLUSH_DUP_FILTER_SIZE || htable_find(dup_filter, STR(queue_id)) == 0) { if (msg_verbose) msg_info("%s: logfile %s: update queue file %s time stamps", myname, path, STR(queue_id)); if (dup_filter->used <= FLUSH_DUP_FILTER_SIZE) htable_enter(dup_filter, STR(queue_id), 0); count += flush_one_file(STR(queue_id), queue_file, &tbuf, how); } else { if (msg_verbose) msg_info("%s: logfile %s: skip queue file %s as duplicate", myname, path, STR(queue_file)); } } htable_free(dup_filter, (void (*) (void *)) 0); vstring_free(queue_file); vstring_free(queue_id); /* * Truncate the fast flush log. */ if (count > 0 && ftruncate(vstream_fileno(log), (off_t) 0) < 0) msg_fatal("%s: truncate fast flush logfile %s: %m", myname, path); /* * Workaround for noatime mounts. Use futimes() if available. */ (void) utimes(VSTREAM_PATH(log), (struct timeval *) 0); /* * Request delivery and clean up. */ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path); if (vstream_fclose(log) != 0) msg_warn("%s: read fast flush logfile %s: %m", myname, path); if (count > 0) { if (msg_verbose) msg_info("%s: requesting delivery for logfile %s", myname, path); mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, qmgr_scan_trigger, sizeof(qmgr_scan_trigger)); } return (FLUSH_STAT_OK); }
static DICT *dict_cdbm_open(const char *path, int dict_flags) { DICT_CDBM *dict_cdbm; char *cdb_path; char *tmp_path; int fd; struct stat st0, st1; cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0); /* * Repeat until we have opened *and* locked *existing* file. Since the * new (tmp) file will be renamed to be .cdb file, locking here is * somewhat funny to work around possible race conditions. Note that we * can't open a file with O_TRUNC as we can't know if another process * isn't creating it at the same time. */ for (;;) { if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0) return (dict_surrogate(DICT_TYPE_CDB, path, O_RDWR, dict_flags, "open database %s: %m", tmp_path)); if (fstat(fd, &st0) < 0) msg_fatal("fstat(%s): %m", tmp_path); /* * Get an exclusive lock - we're going to change the database so we * can't have any spectators. */ if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("lock %s: %m", tmp_path); if (stat(tmp_path, &st1) < 0) msg_fatal("stat(%s): %m", tmp_path); /* * Compare file's state before and after lock: should be the same, * and nlinks should be >0, or else we opened non-existing file... */ if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink && st0.st_nlink > 0) break; /* successefully opened */ close(fd); } #ifndef NO_FTRUNCATE if (st0.st_size) ftruncate(fd, 0); #endif dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path, sizeof(*dict_cdbm)); if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0) msg_fatal("initialize database %s: %m", tmp_path); dict_cdbm->dict.close = dict_cdbm_close; dict_cdbm->dict.update = dict_cdbm_update; dict_cdbm->cdb_path = cdb_path; dict_cdbm->tmp_path = tmp_path; dict_cdbm->dict.owner.uid = st1.st_uid; dict_cdbm->dict.owner.status = (st1.st_uid != 0); close_on_exec(fd, CLOSE_ON_EXEC); /* * If undecided about appending a null byte to key and value, choose a * default to not append a null byte when creating a cdb. */ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) dict_flags |= DICT_FLAG_TRY0NULL; else if ((dict_flags & DICT_FLAG_TRY1NULL) && (dict_flags & DICT_FLAG_TRY0NULL)) dict_flags &= ~DICT_FLAG_TRY0NULL; dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; if (dict_flags & DICT_FLAG_FOLD_FIX) dict_cdbm->dict.fold_buf = vstring_alloc(10); return (&dict_cdbm->dict); }
NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) { const char *myname = "multi_server_main"; VSTREAM *stream = 0; char *root_dir = 0; char *user_name = 0; int debug_me = 0; int daemon_mode = 1; char *service_name = basename(argv[0]); int delay; int c; int fd; va_list ap; MAIL_SERVER_INIT_FN pre_init = 0; MAIL_SERVER_INIT_FN post_init = 0; MAIL_SERVER_LOOP_FN loop = 0; int key; char *transport = 0; #if 0 char *lock_path; VSTRING *why; #endif int alone = 0; int zerolimit = 0; WATCHDOG *watchdog; char *oname_val; char *oname; char *oval; const char *err; char *generation; int msg_vstream_needed = 0; int redo_syslog_init = 0; /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Don't die for frivolous reasons. */ #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif /* * May need this every now and then. */ var_procname = mystrdup(basename(argv[0])); set_mail_conf_str(VAR_PROCNAME, var_procname); /* * Initialize logging and exit handler. Do the syslog first, so that its * initialization completes before we enter the optional chroot jail. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); if (msg_verbose) msg_info("daemon started"); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Initialize from the configuration file. Allow command-line options to * override compiled-in defaults or configured parameter values. */ mail_conf_suck(); /* * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); /* * After database open error, continue execution with reduced * functionality. */ dict_allow_surrogate = 1; /* * Pick up policy settings from master process. Shut up error messages to * stderr, because no-one is going to see them. */ opterr = 0; while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { switch (c) { case 'c': root_dir = "setme"; break; case 'd': daemon_mode = 0; break; case 'D': debug_me = 1; break; case 'i': mail_conf_update(VAR_MAX_IDLE, optarg); break; case 'l': alone = 1; break; case 'm': mail_conf_update(VAR_MAX_USE, optarg); break; case 'n': service_name = optarg; break; case 'o': oname_val = mystrdup(optarg); if ((err = split_nameval(oname_val, &oname, &oval)) != 0) msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); mail_conf_update(oname, oval); if (strcmp(oname, VAR_SYSLOG_NAME) == 0) redo_syslog_init = 1; myfree(oname_val); break; case 's': if ((socket_count = atoi(optarg)) <= 0) msg_fatal("invalid socket_count: %s", optarg); break; case 'S': stream = VSTREAM_IN; break; case 'u': user_name = "setme"; break; case 't': transport = optarg; break; case 'v': msg_verbose++; break; case 'V': if (++msg_vstream_needed == 1) msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); break; case 'z': zerolimit = 1; break; default: msg_fatal("invalid option: %c", c); break; } } /* * Initialize generic parameters. */ mail_params_init(); if (redo_syslog_init) msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* * If not connected to stdin, stdin must not be a terminal. */ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { msg_vstream_init(var_procname, VSTREAM_ERR); msg_fatal("do not run this command by hand"); } /* * Application-specific initialization. */ va_start(ap, service); while ((key = va_arg(ap, int)) != 0) { switch (key) { case MAIL_SERVER_INT_TABLE: get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); break; case MAIL_SERVER_LONG_TABLE: get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); break; case MAIL_SERVER_STR_TABLE: get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); break; case MAIL_SERVER_BOOL_TABLE: get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); break; case MAIL_SERVER_TIME_TABLE: get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); break; case MAIL_SERVER_RAW_TABLE: get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); break; case MAIL_SERVER_NINT_TABLE: get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); break; case MAIL_SERVER_NBOOL_TABLE: get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); break; case MAIL_SERVER_PRE_INIT: pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_POST_INIT: post_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_LOOP: loop = va_arg(ap, MAIL_SERVER_LOOP_FN); break; case MAIL_SERVER_EXIT: multi_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); break; case MAIL_SERVER_PRE_ACCEPT: multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; case MAIL_SERVER_PRE_DISCONN: multi_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN); break; case MAIL_SERVER_IN_FLOW_DELAY: multi_server_in_flow_delay = 1; break; case MAIL_SERVER_SOLITARY: if (stream == 0 && !alone) msg_fatal("service %s requires a process limit of 1", service_name); break; case MAIL_SERVER_UNLIMITED: if (stream == 0 && !zerolimit) msg_fatal("service %s requires a process limit of 0", service_name); break; case MAIL_SERVER_PRIVILEGED: if (user_name) msg_fatal("service %s requires privileged operation", service_name); break; default: msg_panic("%s: unknown argument type: %d", myname, key); } } va_end(ap); if (root_dir) root_dir = var_queue_dir; if (user_name) user_name = var_mail_owner; /* * Can options be required? */ if (stream == 0) { if (transport == 0) msg_fatal("no transport type specified"); if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) multi_server_accept = multi_server_accept_inet; else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) multi_server_accept = multi_server_accept_local; #ifdef MASTER_XPORT_NAME_PASS else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) multi_server_accept = multi_server_accept_pass; #endif else msg_fatal("unsupported transport type: %s", transport); } /* * Retrieve process generation from environment. */ if ((generation = getenv(MASTER_GEN_NAME)) != 0) { if (!alldig(generation)) msg_fatal("bad generation: %s", generation); OCTAL_TO_UNSIGNED(multi_server_generation, generation); if (msg_verbose) msg_info("process generation: %s (%o)", generation, multi_server_generation); } /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Traditionally, BSD select() can't handle multiple processes selecting * on the same socket, and wakes up every process in select(). See TCP/IP * Illustrated volume 2 page 532. We avoid select() collisions with an * external lock file. */ /* * XXX Can't compete for exclusive access to the listen socket because we * also have to monitor existing client connections for service requests. */ #if 0 if (stream == 0 && !alone) { lock_path = concatenate(DEF_PID_DIR, "/", transport, ".", service_name, (char *) 0); why = vstring_alloc(1); if ((multi_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, (struct stat *) 0, -1, -1, why)) == 0) msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); close_on_exec(vstream_fileno(multi_server_lock), CLOSE_ON_EXEC); myfree(lock_path); vstring_free(why); } #endif /* * Set up call-back info. */ multi_server_service = service; multi_server_name = service_name; multi_server_argv = argv + optind; /* * Run pre-jail initialization. */ if (chdir(var_queue_dir) < 0) msg_fatal("chdir(\"%s\"): %m", var_queue_dir); if (pre_init) pre_init(multi_server_name, multi_server_argv); /* * Optionally, restrict the damage that this process can do. */ resolve_local_init(); tzset(); chroot_uid(root_dir, user_name); /* * Run post-jail initialization. */ if (post_init) post_init(multi_server_name, multi_server_argv); /* * Are we running as a one-shot server with the client connection on * standard input? If so, make sure the output is written to stdout so as * to satisfy common expectation. */ if (stream != 0) { vstream_control(stream, VSTREAM_CTL_DOUBLE, VSTREAM_CTL_WRITE_FD, STDOUT_FILENO, VSTREAM_CTL_END); service(stream, multi_server_name, multi_server_argv); vstream_fflush(stream); multi_server_exit(); } /* * Running as a semi-resident server. Service connection requests. * Terminate when we have serviced a sufficient number of clients, when * no-one has been talking to us for a configurable amount of time, or * when the master process terminated abnormally. */ if (var_idle_limit > 0) event_request_timer(multi_server_timeout, (char *) 0, var_idle_limit); for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { event_enable_read(fd, multi_server_accept, CAST_INT_TO_CHAR_PTR(fd)); close_on_exec(fd, CLOSE_ON_EXEC); } event_enable_read(MASTER_STATUS_FD, multi_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (char *) 0); /* * The event loop, at last. */ while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) { if (multi_server_lock != 0) { watchdog_stop(watchdog); if (myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("select lock: %m"); } watchdog_start(watchdog); delay = loop ? loop(multi_server_name, multi_server_argv) : -1; event_loop(delay); } multi_server_exit(); }
static MKMAP *mkmap_db_before_open(const char *path, DICT *(*db_open) (const char *, int, int)) { MKMAP_DB *mkmap = (MKMAP_DB *) mymalloc(sizeof(*mkmap)); struct stat st; /* * Override the default per-table cache size for map (re)builds. * * db_cache_size" is defined in util/dict_db.c and defaults to 128kB, which * works well for the lookup code. * * We use a larger per-table cache when building ".db" files. For "hash" * files performance degrades rapidly unless the memory pool is O(file * size). * * For "btree" files peformance is good with sorted input even for small * memory pools, but with random input degrades rapidly unless the memory * pool is O(file size). * * XXX This should be specified via the DICT interface so that the buffer * size becomes an object property, instead of being specified by poking * a global variable so that it becomes a class property. */ dict_db_cache_size = var_db_create_buf; /* * Fill in the generic members. */ mkmap->lock_file = concatenate(path, ".db", (char *) 0); mkmap->mkmap.open = db_open; mkmap->mkmap.after_open = mkmap_db_after_open; mkmap->mkmap.after_close = mkmap_db_after_close; /* * Unfortunately, not all systems that might support db databases do * support locking on open(), so we open the file before updating it. * * XXX Berkeley DB 4.1 refuses to open a zero-length file. This means we can * open and lock only an existing file, and that we must not truncate it. */ if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0) { if (errno != ENOENT) msg_fatal("open %s: %m", mkmap->lock_file); } /* * Get an exclusive lock - we're going to change the database so we can't * have any spectators. * * XXX Horror. Berkeley DB 4.1 refuses to open a zero-length file. This * means that we must examine the size while the file is locked, and that * we must unlink a zero-length file while it is locked. Avoid a race * condition where two processes try to open the same zero-length file * and where the second process ends up deleting the wrong file. */ else { if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("lock %s: %m", mkmap->lock_file); if (fstat(mkmap->lock_fd, &st) < 0) msg_fatal("fstat %s: %m", mkmap->lock_file); if (st.st_size == 0) { if (st.st_nlink > 0) { if (unlink(mkmap->lock_file) < 0) msg_fatal("cannot remove zero-length database file %s: %m", mkmap->lock_file); msg_warn("removing zero-length database file: %s", mkmap->lock_file); } close(mkmap->lock_fd); mkmap->lock_fd = -1; } } return (&mkmap->mkmap); }
static int deliver_request_get(VSTREAM *stream, DELIVER_REQUEST *request) { const char *myname = "deliver_request_get"; const char *path; struct stat st; static VSTRING *queue_name; static VSTRING *queue_id; static VSTRING *nexthop; static VSTRING *encoding; static VSTRING *address; static VSTRING *client_name; static VSTRING *client_addr; static VSTRING *client_port; static VSTRING *client_proto; static VSTRING *client_helo; static VSTRING *sasl_method; static VSTRING *sasl_username; static VSTRING *sasl_sender; static VSTRING *rewrite_context; static VSTRING *dsn_envid; static RCPT_BUF *rcpt_buf; int rcpt_count; int dsn_ret; /* * Initialize. For some reason I wanted to allow for multiple instances * of a deliver_request structure, thus the hoopla with string * initialization and copying. */ if (queue_name == 0) { queue_name = vstring_alloc(10); queue_id = vstring_alloc(10); nexthop = vstring_alloc(10); encoding = vstring_alloc(10); address = vstring_alloc(10); client_name = vstring_alloc(10); client_addr = vstring_alloc(10); client_port = vstring_alloc(10); client_proto = vstring_alloc(10); client_helo = vstring_alloc(10); sasl_method = vstring_alloc(10); sasl_username = vstring_alloc(10); sasl_sender = vstring_alloc(10); rewrite_context = vstring_alloc(10); dsn_envid = vstring_alloc(10); rcpt_buf = rcpb_create(); } /* * Extract the queue file name, data offset, and sender address. Abort * the conversation when they send bad information. */ if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request->flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, &request->data_offset, ATTR_TYPE_LONG, MAIL_ATTR_SIZE, &request->data_size, ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, nexthop, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, address, ATTR_TYPE_STR, MAIL_ATTR_DSN_ENVID, dsn_envid, ATTR_TYPE_INT, MAIL_ATTR_DSN_RET, &dsn_ret, ATTR_TYPE_FUNC, msg_stats_scan, (void *) &request->msg_stats, /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_NAME, client_name, ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_ADDR, client_addr, ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_PORT, client_port, ATTR_TYPE_STR, MAIL_ATTR_LOG_PROTO_NAME, client_proto, ATTR_TYPE_STR, MAIL_ATTR_LOG_HELO_NAME, client_helo, /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ ATTR_TYPE_STR, MAIL_ATTR_SASL_METHOD, sasl_method, ATTR_TYPE_STR, MAIL_ATTR_SASL_USERNAME, sasl_username, ATTR_TYPE_STR, MAIL_ATTR_SASL_SENDER, sasl_sender, /* XXX Ditto if we want to pass TLS certificate info. */ ATTR_TYPE_STR, MAIL_ATTR_RWR_CONTEXT, rewrite_context, ATTR_TYPE_INT, MAIL_ATTR_RCPT_COUNT, &rcpt_count, ATTR_TYPE_END) != 21) { msg_warn("%s: error receiving common attributes", myname); return (-1); } if (mail_open_ok(vstring_str(queue_name), vstring_str(queue_id), &st, &path) == 0) return (-1); /* Don't override hand-off time after deliver_pass() delegation. */ if (request->msg_stats.agent_handoff.tv_sec == 0) GETTIMEOFDAY(&request->msg_stats.agent_handoff); request->queue_name = mystrdup(vstring_str(queue_name)); request->queue_id = mystrdup(vstring_str(queue_id)); request->nexthop = mystrdup(vstring_str(nexthop)); request->encoding = mystrdup(vstring_str(encoding)); request->sender = mystrdup(vstring_str(address)); request->client_name = mystrdup(vstring_str(client_name)); request->client_addr = mystrdup(vstring_str(client_addr)); request->client_port = mystrdup(vstring_str(client_port)); request->client_proto = mystrdup(vstring_str(client_proto)); request->client_helo = mystrdup(vstring_str(client_helo)); request->sasl_method = mystrdup(vstring_str(sasl_method)); request->sasl_username = mystrdup(vstring_str(sasl_username)); request->sasl_sender = mystrdup(vstring_str(sasl_sender)); request->rewrite_context = mystrdup(vstring_str(rewrite_context)); request->dsn_envid = mystrdup(vstring_str(dsn_envid)); request->dsn_ret = dsn_ret; /* * Extract the recipient offset and address list. Skip over any * attributes from the sender that we do not understand. */ while (rcpt_count-- > 0) { if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_FUNC, rcpb_scan, (void *) rcpt_buf, ATTR_TYPE_END) != 1) { msg_warn("%s: error receiving recipient attributes", myname); return (-1); } recipient_list_add(&request->rcpt_list, rcpt_buf->offset, vstring_str(rcpt_buf->dsn_orcpt), rcpt_buf->dsn_notify, vstring_str(rcpt_buf->orig_addr), vstring_str(rcpt_buf->address)); } if (request->rcpt_list.len <= 0) { msg_warn("%s: no recipients in delivery request for destination %s", request->queue_id, request->nexthop); return (-1); } /* * Open the queue file and set a shared lock, in order to prevent * duplicate deliveries when the queue is flushed immediately after queue * manager restart. * * The queue manager locks the file exclusively when it enters the active * queue, and releases the lock before starting deliveries from that * file. The queue manager does not lock the file again when reading more * recipients into memory. When the queue manager is restarted, the new * process moves files from the active queue to the incoming queue to cool * off for a while. Delivery agents should therefore never try to open a * file that is locked by a queue manager process. * * Opening the queue file can fail for a variety of reasons, such as the * system running out of resources. Instead of throwing away mail, we're * raising a fatal error which forces the mail system to back off, and * retry later. */ #define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT) request->fp = mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0); if (request->fp == 0) { if (errno != ENOENT) msg_fatal("open %s %s: %m", request->queue_name, request->queue_id); msg_warn("open %s %s: %m", request->queue_name, request->queue_id); return (-1); } if (msg_verbose) msg_info("%s: file %s", myname, VSTREAM_PATH(request->fp)); if (myflock(vstream_fileno(request->fp), INTERNAL_LOCK, DELIVER_LOCK_MODE) < 0) msg_fatal("shared lock %s: %m", VSTREAM_PATH(request->fp)); close_on_exec(vstream_fileno(request->fp), CLOSE_ON_EXEC); return (0); }
/* * update the .THREADHEAD .THREADPOST files accordingly * * thrheadpos - index pos. in .THREADHEAD file (count from beginning of file) * thrpostidx - index pos. of current post (count from the pos. of its * thread head in .THREADPOST */ #ifdef USE_THREADING /* syhu */ int update_threadinfo(FILEHEADER *fhdr, char *path, int thrheadpos, int thrpostidx ) /* syhu */ { THRHEADHEADER thrhead; THRPOSTHEADER thrpost, thrpost_tmp; char dotdir[PATHLEN]; /* full-pathname for .xxxx file */ int fd_thrhead; /* file descriptor for headfile */ int fd_thrpost; /* file descriptor for postfile */ int fd_thrpost2; /* 2nd fd, used for copying */ int i; /* general counter */ int index; /* temp. index */ char *buff; /* general char pointer */ BOOL overflow = FALSE; /* if thread-unit is used up */ /* file opening & locking stuff */ sprintf( dotdir, "%s/%s", path, THREAD_HEAD_REC ); if( (fd_thrhead = open( dotdir, O_RDWR | O_CREAT, 0644 )) <= 0) return (-1); sprintf( dotdir, "%s/%s", path, THREAD_REC ); if( (fd_thrpost = open( dotdir, O_RDWR | O_CREAT, 0644 )) <= 0 ) { close( fd_thrhead ); return (-1); } if (myflock(fd_thrhead, LOCK_EX) || myflock(fd_thrpost, LOCK_EX)) { close(fd_thrhead); close(fd_thrpost); return -1; } /* load up default template 'thrhead' and 'thrpost' for later use */ /* 'thrhead' */ if( thrheadpos == (-1) ) /* if it's a original post, not followup */ { memcpy( &thrhead, fhdr, FH_SIZE ); thrhead.numfollow = 0; thrhead.thrpostidx = 0; if( (index=get_num_records_byfd( fd_thrhead, THRHEADHDR_SIZE )) == -1 ) goto end; thrhead.thrheadpos = index; if( (index=get_num_records_byfd( fd_thrpost, THRPOSTHDR_SIZE )) == -1 ) goto end; thrhead.thrpostpos = index; } else { /* load this entry from .THREADHEAD file */ if( lseek( fd_thrhead, thrheadpos*THRHEADHDR_SIZE, SEEK_SET ) == -1 || read( fd_thrhead, &thrhead, THRHEADHDR_SIZE) != THRHEADHDR_SIZE ) goto end; thrhead.numfollow++; /* update the # of follow-ups */ } /* 'thrpost' */ memcpy( &thrpost, fhdr, FH_SIZE ); thrpost.lastfollowidx = 0; thrpost.nextfollowidx = 0; thrpost.nextpostidx = 0; thrpost.thrheadpos = thrheadpos; thrpost.thrpostidx = thrhead.numfollow; /* check if the thread-space in .THREADPOST overflows, if so then move all the entries in this thread to EOF */ if( thrheadpos != (-1) ) /* if it's a follow-up */ { /* first check if this thread needs to be moved to EOF in .THREADPOST note that numfollow has been +1 for follow-up posts, NOTE: some optimization can be done here for already-last entry */ if( (thrhead.numfollow % THREADUNIT_SIZE) == 0 ) { /* update the threadhead link position to .THREADPOST's EOF */ if( (index=get_num_records_byfd(fd_thrpost,THRPOSTHDR_SIZE)) == -1 ) goto end; /* move all headers for this thread to EOF, alloc buffer for it */ if( (fd_thrpost2 = open( dotdir, O_RDONLY )) > 0 && lseek( fd_thrpost2, thrhead.thrpostpos*THRPOSTHDR_SIZE, SEEK_SET ) != -1 && lseek( fd_thrpost, 0, SEEK_END ) != -1 ) { i = thrhead.numfollow*THRPOSTHDR_SIZE; if( (buff=(char *)malloc( i )) != NULL && read( fd_thrpost2, buff, i ) == i ) { write( fd_thrpost, (void *)buff, i ); free( buff ); close( fd_thrpost2 ); thrhead.thrpostpos = index; overflow = TRUE; } } } /* end checking if space isn't enough */ } /* update linkage info in .THREADPOST */ /* first update the 'lastfollowidx' of the post being followed */ i = (thrhead.thrpostpos + thrpostidx)*THRPOSTHDR_SIZE; if( lseek( fd_thrpost, i, SEEK_SET ) == -1 || read( fd_thrpost, &thrpost_tmp, THRPOSTHDR_SIZE ) != THRPOSTHDR_SIZE ) goto end; i = thrpost_tmp.lastfollowidx; thrpost_tmp.lastfollowidx = thrpost.thrpostidx; if( thrpostidx == 0 ) /* if this is the first follow-up */ thrpost_tmp.nextfollowidx = thrpost.thrpostidx; if( lseek( fd_thrpost, -(THRPOSTHDR_SIZE), SEEK_CUR ) == -1 || write( fd_thrpost,&thrpost_tmp,THRPOSTHDR_SIZE ) != THRPOSTHDR_SIZE ) goto end; /* then update the 'nextpostidx' in the last followup hdr, IF it's not the same as the first. Since for the first there's only 'nextfollowidx' note that since this would be the last of all follow-ups, its nextpostidx should be 0 */ if( thrpostidx != 0 ) { i = (thrhead.thrpostpos + i) * THRPOSTHDR_SIZE; if( lseek( fd_thrpost, i, SEEK_SET ) == -1 || read( fd_thrpost, &thrpost_tmp, THRPOSTHDR_SIZE ) != THRPOSTHDR_SIZE ) goto end; thrpost_tmp.nextpostidx = thrpost.thrpostidx; if( lseek( fd_thrpost, -(THRPOSTHDR_SIZE), SEEK_CUR ) == -1 || write( fd_thrpost, &thrpost_tmp, THRPOSTHDR_SIZE ) != THRPOSTHDR_SIZE ) goto end; } /* move fd_thrpost & fd_thrhead to correct position for final write */ i = (thrhead.thrpostpos + thrpost.thrpostidx)*THRPOSTHDR_SIZE; if( lseek( fd_thrpost, i, SEEK_SET ) == -1 ) goto end; if( thrheadpos == -1 && lseek( fd_thrhead, 0, SEEK_END ) == -1) goto end; else if( lseek( fd_thrhead, thrheadpos*THRHEADHDR_SIZE, SEEK_SET ) == -1) goto end; /* save .THREADPOST & .THREADHEAD entry */ if( write(fd_thrpost, &thrpost, THRPOSTHDR_SIZE) != THRPOSTHDR_SIZE || write(fd_thrhead, &thrhead, THRHEADHDR_SIZE) != THRHEADHDR_SIZE ) goto end; /* write space-filling blocks in .THREADPOST, calculate how many dummy entries are needed first. this is only necessary when a new thread unit is open, such as original post or unit overflow. note that what's written is space-filling only, it's rather random */ if( thrheadpos == (-1) || overflow ) { i = THREADUNIT_SIZE - thrhead.numfollow%THREADUNIT_SIZE - 1; write( fd_thrpost, (void *)0, i*THRPOSTHDR_SIZE ); } /* adjust file header accordingly */ fhdr->thrheadpos = thrpost.thrheadpos; fhdr->thrpostidx = thrpost.thrpostidx; /* closing stuff */ end: flock( fd_thrpost, LOCK_UN ); flock( fd_thrhead, LOCK_UN ); close( fd_thrpost ); close( fd_thrhead ); return 0; } /* end of update_threadinfo() */
/* * postno is for readrc mechanism * It reads the last postno information from INFO_REC * If failed, scan all .DIR file to find the last postno. * and write it back to INFO_REC. */ int get_last_info(const char *dotdir, int fd, INFOHEADER *info, int force) { char finfo[PATHLEN]; setdotfile(finfo, dotdir, INFO_REC); if (force || (get_record(finfo, info, IH_SIZE, 1) != 0)) { int i, nr, myfd; FILEHEADER lastf, fhtmp; time_t lastmtime = 0, mtime; if (!dotdir && !fd) return -1; if (!fd) { myfd = open(dotdir, O_RDWR | O_CREAT, 0644); if (myfd == -1) return -1; if (myflock(myfd, LOCK_EX)) { close(myfd); return -1; } } else { myfd = fd; } nr = get_num_records_byfd(myfd, FH_SIZE); for (i = 1; i <= nr; ++i) { if (get_record_byfd(myfd, &fhtmp, FH_SIZE, i) == 0) { if (fhtmp.accessed & FILE_DELE) continue; if (fhtmp.mtime) mtime = fhtmp.mtime; else if (fhtmp.filename[0] == 'M') mtime = strtol(fhtmp.filename + 2, NULL, 10); else mtime = 0; if (mtime > lastmtime) { memcpy(&lastf, &fhtmp, FH_SIZE); lastmtime = mtime; } } else { break; } } if (!fd) { flock(myfd, LOCK_UN); close(myfd); } if (i <= nr) return -1; memset(info, 0, IH_SIZE); if (lastmtime) { info->last_postno = lastf.postno; info->last_mtime = lastf.mtime; strcpy(info->last_filename, lastf.filename); } else { /* There is no article yet. */ info->last_postno = 0; info->last_mtime = 0; strcpy(info->last_filename, "M.000000000.A"); } if (substitute_record(finfo, info, IH_SIZE, 1) == -1) return -1; } return 0; }
int recover_dirent(const char *direct) { struct file_list *dl; size_t dl_size; int i = 0, cmp, fdr, fdw; char dirpath[PATHLEN]; FILEHEADER fhr, nfhr; FILE *fhw; int result = 0; setdotfile(dirpath, direct, NULL); dl = get_file_list(dirpath, &dl_size, "M."); if (!dl) return -1; qsort(dl, dl_size, sizeof(struct file_list), cmpfun); if ((fdr = open(direct, O_RDWR)) < 0) return -1; if (myflock(fdr, LOCK_EX)) { close(fdr); return -1; } fhw = tmpfile(); fdw = fileno(fhw); while (myread(fdr, &fhr, FH_SIZE) == FH_SIZE) { cmp = cmpfun(fhr.filename, dl[i].fname); while (cmp > 0) { restore_fileheader(&nfhr, direct, dl[i].fname); dbg("Inserted %s\n", dl[i].fname); dbg("\tDate: %s User: %s Ident: %d\n", nfhr.date, nfhr.owner, nfhr.ident); dbg("\tTitle: %s\n", nfhr.title); if (mywrite(fdw, &nfhr, FH_SIZE) != FH_SIZE) { result = -1; break; } cmp = cmpfun(fhr.filename, dl[++i].fname); ++result; } if (cmp == 0) { ++i; } else { dbg("Missing %s\n", fhr.filename); dbg("\tDate: %s User: %s Ident: %d\n", fhr.date, fhr.owner, fhr.ident); dbg("\tTitle: %s\n", fhr.title); } if (mywrite(fdw, &fhr, FH_SIZE) != FH_SIZE) { result = -1; break; } } if (result > 0) result = myfdcp(fdw, fdr); fclose(fhw); flock(fdr, LOCK_UN); close(fdr); free(dl); return result; }
static int dict_db_delete(DICT *dict, const char *name) { DICT_DB *dict_db = (DICT_DB *) dict; DB *db = dict_db->db; DBT db_key; int status = 1; int flags = 0; dict->error = 0; /* * Sanity check. */ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) msg_panic("dict_db_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } memset(&db_key, 0, sizeof(db_key)); /* * Acquire an exclusive lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); /* * See if this DB file was written with one null byte appended to key and * value. */ if (dict->flags & DICT_FLAG_TRY1NULL) { db_key.data = (void *) name; db_key.size = strlen(name) + 1; if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0) msg_fatal("error deleting from %s: %m", dict_db->dict.name); if (status == 0) dict->flags &= ~DICT_FLAG_TRY0NULL; } /* * See if this DB file was written with no null byte appended to key and * value. */ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { db_key.data = (void *) name; db_key.size = strlen(name); if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0) msg_fatal("error deleting from %s: %m", dict_db->dict.name); if (status == 0) dict->flags &= ~DICT_FLAG_TRY1NULL; } if (dict->flags & DICT_FLAG_SYNC_UPDATE) if (DICT_DB_SYNC(db, 0) < 0) msg_fatal("%s: flush dictionary: %m", dict_db->dict.name); /* * Release the exclusive lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); return status; }
static int dict_db_sequence(DICT *dict, int function, const char **key, const char **value) { const char *myname = "dict_db_sequence"; DICT_DB *dict_db = (DICT_DB *) dict; DB *db = dict_db->db; DBT db_key; DBT db_value; int status = 0; int db_function; dict->error = 0; #if DB_VERSION_MAJOR > 1 /* * Initialize. */ memset(&db_key, 0, sizeof(db_key)); memset(&db_value, 0, sizeof(db_value)); /* * Determine the function. */ switch (function) { case DICT_SEQ_FUN_FIRST: if (dict_db->cursor == 0) DICT_DB_CURSOR(db, &(dict_db->cursor)); db_function = DB_FIRST; break; case DICT_SEQ_FUN_NEXT: if (dict_db->cursor == 0) msg_panic("%s: no cursor", myname); db_function = DB_NEXT; break; default: msg_panic("%s: invalid function %d", myname, function); } /* * Acquire a shared lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); /* * Database lookup. */ status = dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function); if (status != 0 && status != DB_NOTFOUND) msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name); /* * Release the shared lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); if (status == 0) { /* * Copy the result so it is guaranteed null terminated. */ *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); } return (status); #else /* * determine the function */ switch (function) { case DICT_SEQ_FUN_FIRST: db_function = R_FIRST; break; case DICT_SEQ_FUN_NEXT: db_function = R_NEXT; break; default: msg_panic("%s: invalid function %d", myname, function); } /* * Acquire a shared lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0) msg_fatal("error seeking %s: %m", dict_db->dict.name); /* * Release the shared lock. */ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); if (status == 0) { /* * Copy the result so that it is guaranteed null terminated. */ *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); } return status; #endif }
EDIT_FILE *edit_file_open(const char *path, int flags, mode_t mode) { struct stat before_lock; struct stat after_lock; int saved_errno; EDIT_FILE *ep; /* * Initialize. Do not bother to optimize for the error case. */ EDIT_FILE_ALLOC(ep, path, mode); /* * As long as the output file can be opened under the temporary pathname, * this code can loop or block forever. * * Applications that are concerned about deadlock should protect the * edit_file_open() call with a watchdog timer. */ for ( /* void */ ; /* void */ ; (void) vstream_fclose(ep->tmp_fp)) { /* * Try to open the output file under the temporary pathname. This * succeeds or fails immediately. To avoid creating a shared-lock DOS * opportunity after we crash, we create the output file with no * group or other permissions, and set the final permissions at the * end (this is one reason why we try to get exclusive control over * the output file instead of the original file). We postpone file * truncation until we have obtained exclusive control over the file * content and temporary pathname. If the open operation fails, we * give up immediately. The caller can retry the call if desirable. * * XXX If we replace the vstream_fopen() call by safe_open(), then we * should replace the stat() call below by lstat(). */ if ((ep->tmp_fp = vstream_fopen(ep->tmp_path, flags & ~(O_TRUNC), EDIT_FILE_MODE)) == 0) { saved_errno = errno; EDIT_FILE_FREE(ep); errno = saved_errno; return (0); } /* * At this point we may have opened an existing output file that was * already locked. Try to lock the open file exclusively. This may * take some time. */ if (myflock(vstream_fileno(ep->tmp_fp), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("lock %s: %m", ep->tmp_path); /* * At this point we have an exclusive lock, but some other process * may have renamed or removed the output file while we were waiting * for the lock. If that is the case, back out and try again. */ if (fstat(vstream_fileno(ep->tmp_fp), &before_lock) < 0) msg_fatal("open %s: %m", ep->tmp_path); if (stat(ep->tmp_path, &after_lock) < 0 || before_lock.st_dev != after_lock.st_dev || before_lock.st_ino != after_lock.st_ino #ifdef HAS_ST_GEN || before_lock.st_gen != after_lock.st_gen #endif /* No need to compare st_rdev or st_nlink here. */ ) { continue; } /* * At this point we have exclusive control over the output file * content and its temporary pathname (within the rules of the * cooperative protocol). But wait, there is more. * * There are many opportunies for trouble when opening a pre-existing * output file. Here are just a few. * * - Victor observes that a system crash in the middle of the * final-phase rename() operation may result in the output file * having both the temporary pathname and the final pathname. In that * case we must not write to the output file. * * - Wietse observes that crashes may also leave the output file in * other inconsistent states. To avoid permission-related trouble, we * simply refuse to work with an output file that has the wrong * temporary permissions. This won't stop the shared-lock DOS if we * crash after changing the file permissions, though. * * To work around these crash-related problems, remove the temporary * pathname, back out, and try again. */ if (!S_ISREG(after_lock.st_mode) #ifndef EDIT_FILE_REUSE_AFTER_CRASH || after_lock.st_size > 0 #endif || after_lock.st_nlink > 1 || (after_lock.st_mode & FILE_PERM_MASK) != EDIT_FILE_MODE) { if (unlink(ep->tmp_path) < 0 && errno != ENOENT) msg_fatal("unlink %s: %m", ep->tmp_path); continue; } /* * Settle the final details. */ #ifdef EDIT_FILE_REUSE_AFTER_CRASH if (ftruncate(vstream_fileno(ep->tmp_fp), 0) < 0) msg_fatal("truncate %s: %m", ep->tmp_path); #endif return (ep); } }
void update_pidfile(const char *pidfile) { char str[11]; /* can we assume that pid_t is limited to 32-bit? */ int rc, n; #if !defined(__CYGWIN32__) pidfd = open(pidfile, O_WRONLY | O_CREAT, 0640); if (pidfd < 0) { fprintf(stderr, "Can't open pidfile \"%s\"\n", pidfile); exit(1); } rc = myflock(pidfd, MYFLOCK_EX, MYFLOCK_NB); if (rc < 0) { fprintf(stderr, "Can't lock pidfile \"%s\", am I already running?\n", pidfile); exit(1); } #else /* On Cygwin, locking the pid file makes it impossible for vutil to open the file to the venus' pid. Use a different lock file. */ char lockname[MAXPATHLEN]; int pidfd; int namelen = strlen(pidfile); if ((namelen+4) > MAXPATHLEN) { fprintf(stderr, "pid file name too long.\n"); exit(1); } n = snprintf (lockname, MAXPATHLEN, "%s.lk", pidfile); assert(n > namelen); lockfd = open(lockname, O_WRONLY | O_CREAT, 0640); if (lockfd < 0) { fprintf(stderr, "Can't open lock file \"%s\"\n", lockname); exit(1); } rc = myflock(lockfd, MYFLOCK_EX, MYFLOCK_NB); if (rc < 0) { fprintf(stderr, "Can't lock lock file \"%s\", am I already running?\n", lockname); exit(1); } pidfd = open(pidfile, O_WRONLY | O_CREAT, 0640); if (pidfd < 0) { fprintf(stderr, "Can't open pidfile \"%s\"\n", pidfile); exit(1); } #endif n = snprintf(str, sizeof(str), "%d\n", getpid()); assert(n >= 0 && (unsigned int)n < sizeof(str)); /* write pid to lockfile */ ftruncate(pidfd, 0); rc = write(pidfd, str, n); if (rc != n) { fprintf(stderr, "Can't update pidfile \"%s\"\n", pidfile); exit(1); } #if !defined(__CYGWIN32__) /* leave pidfd open otherwise we lose the lock */ #else /* leave lockfd open, close the pidfd. */ close(pidfd); #endif }
NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,...) { char *myname = "trigger_server_main"; char *root_dir = 0; char *user_name = 0; int debug_me = 0; char *service_name = basename(argv[0]); VSTREAM *stream = 0; int delay; int c; int socket_count = 1; int fd; va_list ap; MAIL_SERVER_INIT_FN pre_init = 0; MAIL_SERVER_INIT_FN post_init = 0; MAIL_SERVER_LOOP_FN loop = 0; int key; char buf[TRIGGER_BUF_SIZE]; int len; char *transport = 0; char *lock_path; VSTRING *why; int alone = 0; int zerolimit = 0; WATCHDOG *watchdog; char *oval; /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Don't die for frivolous reasons. */ #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif /* * May need this every now and then. */ var_procname = mystrdup(basename(argv[0])); set_mail_conf_str(VAR_PROCNAME, var_procname); /* * Initialize logging and exit handler. Do the syslog first, so that its * initialization completes before we enter the optional chroot jail. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); if (msg_verbose) msg_info("daemon started"); /* * Initialize from the configuration file. Allow command-line options to * override compiled-in defaults or configured parameter values. */ mail_conf_suck(); /* * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); /* * Pick up policy settings from master process. Shut up error messages to * stderr, because no-one is going to see them. */ opterr = 0; while ((c = GETOPT(argc, argv, "cDi:lm:n:o:s:St:uvzZ")) > 0) { switch (c) { case 'c': root_dir = "setme"; break; case 'D': debug_me = 1; break; case 'i': mail_conf_update(VAR_MAX_IDLE, optarg); break; case 'l': alone = 1; break; case 'm': mail_conf_update(VAR_MAX_USE, optarg); break; case 'n': service_name = optarg; break; case 'o': if ((oval = split_at(optarg, '=')) == 0) oval = ""; mail_conf_update(optarg, oval); break; case 's': if ((socket_count = atoi(optarg)) <= 0) msg_fatal("invalid socket_count: %s", optarg); break; case 'S': stream = VSTREAM_IN; break; case 't': transport = optarg; break; case 'u': user_name = "setme"; break; case 'v': msg_verbose++; break; case 'z': zerolimit = 1; break; case 'Z': msg_debug++; break; default: msg_fatal("invalid option: %c", c); break; } } /* * Initialize generic parameters. */ mail_params_init(); /* * Application-specific initialization. */ va_start(ap, service); while ((key = va_arg(ap, int)) != 0) { switch (key) { case MAIL_SERVER_INT_TABLE: get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); break; case MAIL_SERVER_STR_TABLE: get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); break; case MAIL_SERVER_BOOL_TABLE: get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); break; case MAIL_SERVER_TIME_TABLE: get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); break; case MAIL_SERVER_RAW_TABLE: get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); break; case MAIL_SERVER_PRE_INIT: pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_POST_INIT: post_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_LOOP: loop = va_arg(ap, MAIL_SERVER_LOOP_FN); break; case MAIL_SERVER_EXIT: trigger_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); break; case MAIL_SERVER_PRE_ACCEPT: trigger_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; case MAIL_SERVER_IN_FLOW_DELAY: trigger_server_in_flow_delay = 1; break; case MAIL_SERVER_SOLITARY: if (!alone) msg_fatal("service %s requires a process limit of 1", service_name); break; case MAIL_SERVER_UNLIMITED: if (!zerolimit) msg_fatal("service %s requires a process limit of 0", service_name); break; default: msg_panic("%s: unknown argument type: %d", myname, key); } } va_end(ap); if (root_dir) root_dir = var_queue_dir; if (user_name) user_name = var_mail_owner; /* * If not connected to stdin, stdin must not be a terminal. */ if (stream == 0 && isatty(STDIN_FILENO)) { msg_vstream_init(var_procname, VSTREAM_ERR); msg_fatal("do not run this command by hand"); } /* * Can options be required? * * XXX Initially this code was implemented with UNIX-domain sockets, but * Solaris <= 2.5 UNIX-domain sockets misbehave hopelessly when the * client disconnects before the server has accepted the connection. * Symptom: the server accept() fails with EPIPE or EPROTO, but the * socket stays readable, so that the program goes into a wasteful loop. * * The initial fix was to use FIFOs, but those turn out to have their own * problems, witness the workarounds in the fifo_listen() routine. * Therefore we support both FIFOs and UNIX-domain sockets, so that the * user can choose whatever works best. * * Well, I give up. Solaris UNIX-domain sockets still don't work properly, * so it will have to limp along with a streams-specific alternative. */ if (stream == 0) { if (transport == 0) msg_fatal("no transport type specified"); if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) trigger_server_accept = trigger_server_accept_local; else if (strcasecmp(transport, MASTER_XPORT_NAME_FIFO) == 0) trigger_server_accept = trigger_server_accept_fifo; else msg_fatal("unsupported transport type: %s", transport); } /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Traditionally, BSD select() can't handle multiple processes selecting * on the same socket, and wakes up every process in select(). See TCP/IP * Illustrated volume 2 page 532. We avoid select() collisions with an * external lock file. */ if (stream == 0 && !alone) { lock_path = concatenate(DEF_PID_DIR, "/", transport, ".", service_name, (char *) 0); why = vstring_alloc(1); if ((trigger_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, (struct stat *) 0, -1, -1, why)) == 0) msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); close_on_exec(vstream_fileno(trigger_server_lock), CLOSE_ON_EXEC); myfree(lock_path); vstring_free(why); } /* * Set up call-back info. */ trigger_server_service = service; trigger_server_name = service_name; trigger_server_argv = argv + optind; /* * Run pre-jail initialization. */ if (chdir(var_queue_dir) < 0) msg_fatal("chdir(\"%s\"): %m", var_queue_dir); if (pre_init) pre_init(trigger_server_name, trigger_server_argv); /* * Optionally, restrict the damage that this process can do. */ resolve_local_init(); chroot_uid(root_dir, user_name); /* * Run post-jail initialization. */ if (post_init) post_init(trigger_server_name, trigger_server_argv); /* * Are we running as a one-shot server with the client connection on * standard input? */ if (stream != 0) { if ((len = read(vstream_fileno(stream), buf, sizeof(buf))) <= 0) msg_fatal("read: %m"); service(buf, len, trigger_server_name, trigger_server_argv); vstream_fflush(stream); trigger_server_exit(); } /* * Running as a semi-resident server. Service connection requests. * Terminate when we have serviced a sufficient number of clients, when * no-one has been talking to us for a configurable amount of time, or * when the master process terminated abnormally. */ if (var_idle_limit > 0) event_request_timer(trigger_server_timeout, (char *) 0, var_idle_limit); for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { event_enable_read(fd, trigger_server_accept, CAST_INT_TO_CHAR_PTR(fd)); close_on_exec(fd, CLOSE_ON_EXEC); } event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(1000, (WATCHDOG_FN) 0, (char *) 0); /* * The event loop, at last. */ while (var_use_limit == 0 || use_count < var_use_limit) { if (trigger_server_lock != 0) { watchdog_stop(watchdog); if (myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("select lock: %m"); } watchdog_start(watchdog); delay = loop ? loop(trigger_server_name, trigger_server_argv) : -1; event_loop(delay); } trigger_server_exit(); }