Esempio n. 1
0
/*
 * 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;
}
Esempio n. 2
0
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);
}
Esempio n. 3
0
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);
    }
}
Esempio n. 4
0
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);
}
Esempio n. 5
0
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);
}
Esempio n. 6
0
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 */
	}
    }
}
Esempio n. 7
0
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);
    }
}
Esempio n. 8
0
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);
}
Esempio n. 9
0
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);
}
Esempio n. 10
0
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();
}
Esempio n. 11
0
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);
}
Esempio n. 12
0
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);
}
Esempio n. 13
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() */
Esempio n. 14
0
/*
 * 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;
}
Esempio n. 15
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;
}
Esempio n. 16
0
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;
}
Esempio n. 17
0
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
}
Esempio n. 18
0
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);
    }
}
Esempio n. 19
0
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
}
Esempio n. 20
0
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();
}