/* * ProFTPD - FTP server daemon * Copyright (c) 1997, 1998 Public Flood Software * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu * Copyright (c) 2001, 2002, 2003, 2004 The ProFTPD Project team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu * and other respective copyright holders give permission to link this program * with OpenSSL, and distribute the resulting executable, without including * the source code for OpenSSL in the source distribution. */ /* * House initialization and main program loop * $Id: main.c,v 1.278 2006/03/15 19:41:01 castaglia Exp $ */ #include "conf.h" #include #include #ifdef HAVE_GETOPT_H # include #endif #ifdef HAVE_LIBUTIL_H # include #endif /* HAVE_LIBUTIL_H */ #if PF_ARGV_TYPE == PF_ARGV_PSTAT # ifdef HAVE_SYS_PSTAT_H # include # else # undef PF_ARGV_TYPE # define PF_ARGV_TYPE PF_ARGV_WRITEABLE # endif /* HAVE_SYS_PSTAT_H */ #endif /* PF_ARGV_PSTAT */ #if PF_ARGV_TYPE == PF_ARGV_PSSTRINGS # ifndef HAVE_SYS_EXEC_H # undef PF_ARGV_TYPE # define PF_ARGV_TYPE PF_ARGV_WRITEABLE # else # include # include # endif /* HAVE_SYS_EXEC_H */ #endif /* PF_ARGV_PSSTRINGS */ #ifdef HAVE_REGEX_H # include #endif #ifdef HAVE_REGEXP_H # include #endif /* HAVE_REGEXP_H */ #include "privs.h" int (*cmd_auth_chk)(cmd_rec *); #ifdef NEED_PERSISTENT_PASSWD unsigned char persistent_passwd = TRUE; #else unsigned char persistent_passwd = FALSE; #endif /* NEED_PERSISTENT_PASSWD */ /* From modules/module_glue.c */ extern module *static_modules[]; extern xaset_t *server_list; struct rehash { struct rehash *next; void *data; void (*rehash)(void*); }; unsigned long max_connects = 0UL; unsigned int max_connect_interval = 1; session_t session; /* Is this daemon operating in standalone mode? */ static unsigned char is_standalone = FALSE; /* Is this process the master standalone daemon process? */ unsigned char is_master = TRUE; pid_t mpid = 0; /* Master pid */ struct rehash *rehash_list = NULL; /* Pre-rehash callbacks */ uid_t daemon_uid; gid_t daemon_gid; array_header *daemon_gids; static time_t shut = 0, deny = 0, disc = 0; static char shutmsg[81] = {'\0'}; static unsigned char have_dead_child = FALSE; static char sbuf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}; static char **Argv = NULL; static char *LastArgv = NULL; static const char *PidPath = PR_PID_FILE_PATH; /* From dirtree.c */ extern array_header *server_defines; /* From mod_auth_unix.c */ extern unsigned char persistent_passwd; /* From response.c */ extern pr_response_t *resp_list, *resp_err_list; static int nodaemon = 0; static int quiet = 0; static int shutdownp = 0; static int syntax_check = 0; /* Signal handling */ static RETSIGTYPE sig_disconnect(int); static RETSIGTYPE sig_evnt(int); volatile unsigned int recvd_signal_flags = 0; /* Used to capture an "unknown" signal value that causes termination. */ static int term_signo = 0; /* Signal processing functions */ static void handle_abort(void); static void handle_chld(void); static void handle_evnt(void); static void handle_xcpu(void); static void handle_terminate(void); static void handle_terminate_other(void); static void finish_terminate(void); static const char *config_filename = PR_CONFIG_FILE_PATH; /* Add child semaphore fds into the rfd for selecting */ static int semaphore_fds(fd_set *rfd, int maxfd) { if (child_count()) { pr_child_t *ch; for (ch = child_get(NULL); ch; ch = child_get(ch)) { if (ch->ch_pipefd != -1) { FD_SET(ch->ch_pipefd, rfd); if (ch->ch_pipefd > maxfd) maxfd = ch->ch_pipefd; } } } return maxfd; } #ifdef HAVE___PROGNAME extern char *__progname, *__progname_full; #endif /* HAVE___PROGNAME */ extern char **environ; static void init_proc_title(int argc, char *argv[], char *envp[]) { register int i, envpsize; char **p; /* Move the environment so setproctitle can use the space. */ for (i = envpsize = 0; envp[i] != NULL; i++) envpsize += strlen(envp[i]) + 1; if ((p = (char **)malloc((i + 1) * sizeof(char *))) != NULL) { environ = p; for (i = 0; envp[i] != NULL; i++) if ((environ[i] = malloc(strlen(envp[i]) + 1)) != NULL) strcpy(environ[i], envp[i]); environ[i] = NULL; } Argv = argv; for (i = 0; i < argc; i++) if (!i || (LastArgv + 1 == argv[i])) LastArgv = argv[i] + strlen(argv[i]); for (i = 0; envp[i] != NULL; i++) if ((LastArgv + 1) == envp[i]) LastArgv = envp[i] + strlen(envp[i]); #ifdef HAVE___PROGNAME /* Set the __progname and __progname_full variables so glibc and company * don't go nuts. */ __progname = strdup("proftpd"); __progname_full = strdup(argv[0]); #endif /* HAVE___PROGNAME */ } #ifdef PR_DEVEL static void free_proc_title(void) { if (environ) { register unsigned int i; for (i = 0; environ[i] != NULL; i++) free(environ[i]); free(environ); environ = NULL; } # ifdef HAVE___PROGNAME free(__progname); __progname = NULL; free(__progname_full); __progname_full = NULL; # endif /* HAVE___PROGNAME */ } #endif /* PR_DEVEL */ static void set_proc_title(const char *fmt, ...) { va_list msg; static char statbuf[BUFSIZ]; #ifndef HAVE_SETPROCTITLE #if PF_ARGV_TYPE == PF_ARGV_PSTAT union pstun pst; #endif /* PF_ARGV_PSTAT */ char *p; int i,maxlen = (LastArgv - Argv[0]) - 2; #endif /* HAVE_SETPROCTITLE */ va_start(msg,fmt); memset(statbuf, 0, sizeof(statbuf)); #ifdef HAVE_SETPROCTITLE # if __FreeBSD__ >= 4 && !defined(FREEBSD4_0) && !defined(FREEBSD4_1) /* FreeBSD's setproctitle() automatically prepends the process name. */ vsnprintf(statbuf, sizeof(statbuf), fmt, msg); # else /* FREEBSD4 */ /* Manually append the process name for non-FreeBSD platforms. */ snprintf(statbuf, sizeof(statbuf), "%s", "proftpd: "); vsnprintf(statbuf + strlen(statbuf), sizeof(statbuf) - strlen(statbuf), fmt, msg); # endif /* FREEBSD4 */ setproctitle("%s", statbuf); #else /* HAVE_SETPROCTITLE */ /* Manually append the process name for non-setproctitle() platforms. */ snprintf(statbuf, sizeof(statbuf), "%s", "proftpd: "); vsnprintf(statbuf + strlen(statbuf), sizeof(statbuf) - strlen(statbuf), fmt, msg); #endif /* HAVE_SETPROCTITLE */ va_end(msg); #ifdef HAVE_SETPROCTITLE return; #else i = strlen(statbuf); #if PF_ARGV_TYPE == PF_ARGV_NEW /* We can just replace argv[] arguments. Nice and easy. */ Argv[0] = statbuf; Argv[1] = NULL; #endif /* PF_ARGV_NEW */ #if PF_ARGV_TYPE == PF_ARGV_WRITEABLE /* We can overwrite individual argv[] arguments. Semi-nice. */ snprintf(Argv[0], maxlen, "%s", statbuf); p = &Argv[0][i]; while(p < LastArgv) *p++ = '\0'; Argv[1] = NULL; #endif /* PF_ARGV_WRITEABLE */ #if PF_ARGV_TYPE == PF_ARGV_PSTAT pst.pst_command = statbuf; pstat(PSTAT_SETCMD, pst, i, 0, 0); #endif /* PF_ARGV_PSTAT */ #if PF_ARGV_TYPE == PF_ARGV_PSSTRINGS PS_STRINGS->ps_nargvstr = 1; PS_STRINGS->ps_argvstr = statbuf; #endif /* PF_ARGV_PSSTRINGS */ #endif /* HAVE_SETPROCTITLE */ } void session_set_idle(void) { pr_scoreboard_update_entry(getpid(), PR_SCORE_BEGIN_IDLE, time(NULL), PR_SCORE_CMD, "%s", "idle", NULL, NULL); pr_scoreboard_update_entry(getpid(), PR_SCORE_CMD_ARG, "%s", "", NULL, NULL); set_proc_title("%s - %s: IDLE", session.user, session.proc_prefix); } void set_auth_check(int (*chk)(cmd_rec*)) { cmd_auth_chk = chk; } static void end_login_noexit(void) { /* Clear the scoreboard entry. */ if (ServerType == SERVER_STANDALONE) { /* For standalone daemons, we only clear the scoreboard slot if we are * an exiting child process. */ if (!is_master && pr_scoreboard_del_entry(TRUE) < 0 && errno != EINVAL) pr_log_debug(DEBUG1, "error deleting scoreboard entry: %s", strerror(errno)); } else if (ServerType == SERVER_INETD) { /* For inetd-spawned daemons, we always clear the scoreboard slot. */ if (pr_scoreboard_del_entry(TRUE) < 0 && errno != EINVAL) pr_log_debug(DEBUG1, "error deleting scoreboard entry: %s", strerror(errno)); } /* If session.user is set, we have a valid login */ if (session.user) { #if (defined(BSD) && (BSD >= 199103)) snprintf(sbuf, sizeof(sbuf), "ftp%ld",(long)getpid()); #else snprintf(sbuf, sizeof(sbuf), "ftpd%d",(int)getpid()); #endif sbuf[sizeof(sbuf) - 1] = '\0'; if (session.wtmp_log) log_wtmp(sbuf, "", pr_netaddr_get_sess_remote_name(), pr_netaddr_get_sess_remote_addr()); } /* These are necessary in order that cleanups associated with these pools * (and their subpools) are properly run. */ if (session.d) { pr_inet_close(session.pool, session.d); session.d = NULL; } if (session.c) { pr_inet_close(session.pool, session.c); session.c = NULL; } /* Run all the exit handlers */ pr_event_generate("core.exit", NULL); if (!is_master || (ServerType == SERVER_INETD && !syntax_check)) { pr_log_pri(PR_LOG_INFO, "FTP session closed."); } log_closesyslog(); } /* Finish any cleaning up, mark utmp as closed and exit without flushing * buffers */ void end_login(int exitcode) { end_login_noexit(); #ifdef PR_DEVEL destroy_pool(session.pool); if (is_master) { main_server = NULL; free_pools(); free_proc_title(); } #endif /* PR_DEVEL */ _exit(exitcode); } void session_exit(int pri, void *lv, int exitval, void *dummy) { char *msg = (char *) lv; pr_log_pri(pri, "%s", msg); if (is_standalone && is_master) { pr_log_pri(PR_LOG_NOTICE, "ProFTPD " PROFTPD_VERSION_TEXT " standalone mode SHUTDOWN"); PRIVS_ROOT pr_delete_scoreboard(); if (!nodaemon) unlink(PidPath); PRIVS_RELINQUISH } end_login(exitval); } static void shutdown_exit(void *d1, void *d2, void *d3, void *d4) { if (check_shutmsg(&shut, &deny, &disc, shutmsg, sizeof(shutmsg)) == 1) { char *user; time_t now; char *msg; const char *serveraddress; config_rec *c = NULL; unsigned char *authenticated = get_param_ptr(main_server->conf, "authenticated", FALSE); serveraddress = (session.c && session.c->local_addr) ? pr_netaddr_get_ipstr(session.c->local_addr) : main_server->ServerAddress; if ((c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE)) != NULL) { pr_netaddr_t *masq_addr = (pr_netaddr_t *) c->argv[0]; serveraddress = pr_netaddr_get_ipstr(masq_addr); } time(&now); if (authenticated && *authenticated == TRUE) user = get_param_ptr(main_server->conf, C_USER, FALSE); else user = "NONE"; msg = sreplace(permanent_pool, shutmsg, "%s", pstrdup(permanent_pool, pr_strtime(shut)), "%r", pstrdup(permanent_pool, pr_strtime(deny)), "%d", pstrdup(permanent_pool, pr_strtime(disc)), "%C", (session.cwd[0] ? session.cwd : "(none)"), "%L", serveraddress, "%R", (session.c && session.c->remote_name ? session.c->remote_name : "(unknown)"), "%T", pstrdup(permanent_pool, pr_strtime(now)), "%U", user, "%V", main_server->ServerName, NULL ); pr_response_send_async(R_421, "FTP server shutting down - %s", msg); session_exit(PR_LOG_NOTICE, msg, 0, NULL); } signal(SIGUSR1, sig_disconnect); } static int get_command_class(const char *name) { cmdtable *c = pr_stash_get_symbol(PR_SYM_CMD, name, NULL, NULL); while (c && c->cmd_type != CMD) c = pr_stash_get_symbol(PR_SYM_CMD, name, c, NULL); /* By default, every command has a class of CL_ALL. This insures that * any configured ExtendedLogs that default to "all" will log the command. */ return (c ? c->class : CL_ALL); } static int _dispatch(cmd_rec *cmd, int cmd_type, int validate, char *match) { char *cmdargstr = NULL; cmdtable *c; modret_t *mr; int success = 0; int send_error = 0; static int match_index_cache = -1; static char *last_match = NULL; int *index_cache; send_error = (cmd_type == PRE_CMD || cmd_type == CMD || cmd_type == POST_CMD_ERR); if (!match) { match = cmd->argv[0]; index_cache = &cmd->stash_index; } else { if (last_match != match) { match_index_cache = -1; last_match = match; } index_cache = &match_index_cache; } c = pr_stash_get_symbol(PR_SYM_CMD, match, NULL, index_cache); while (c && !success) { session.curr_cmd = cmd->argv[0]; session.curr_phase = cmd_type; if (c->cmd_type == cmd_type) { if (c->group) cmd->group = pstrdup(cmd->pool, c->group); if (c->requires_auth && cmd_auth_chk && !cmd_auth_chk(cmd)) return -1; cmd->tmp_pool = make_sub_pool(cmd->pool); cmdargstr = make_arg_str(cmd->tmp_pool, cmd->argc, cmd->argv); if (cmd_type == CMD) { /* The client has successfully authenticated... */ if (session.user) { char *args = strchr(cmdargstr, ' '); pr_scoreboard_update_entry(getpid(), PR_SCORE_CMD, "%s", cmd->argv[0], NULL, NULL); pr_scoreboard_update_entry(getpid(), PR_SCORE_CMD_ARG, "%s", args ? (args+1) : "", NULL, NULL); set_proc_title("%s - %s: %s", session.user, session.proc_prefix, cmdargstr); /* ...else the client has not yet authenticated */ } else set_proc_title("%s:%d: %s", session.c->remote_addr ? pr_netaddr_get_ipstr(session.c->remote_addr) : "?", session.c->remote_port ? session.c->remote_port : 0, cmdargstr); } pr_log_debug(DEBUG4, "dispatching %s command '%s' to mod_%s", (cmd_type == PRE_CMD ? "PRE_CMD" : cmd_type == CMD ? "CMD" : cmd_type == POST_CMD ? "POST_CMD" : cmd_type == POST_CMD_ERR ? "POST_CMD_ERR" : cmd_type == LOG_CMD ? "LOG_CMD" : cmd_type == LOG_CMD_ERR ? "LOG_CMD_ERR" : "(unknown)"), cmdargstr, c->m->name); cmd->class |= c->class; /* KLUDGE: disable umask() for not G_WRITE operations. Config/ * Directory walking code will be completely redesigned in 1.3, * this is only necessary for perfomance reasons in 1.1/1.2 */ if (!c->group || strcmp(c->group, G_WRITE) != 0) kludge_disable_umask(); mr = call_module(c->m, c->handler, cmd); kludge_enable_umask(); if (MODRET_ISHANDLED(mr)) success = 1; else if (MODRET_ISERROR(mr)) { if (cmd_type == POST_CMD || cmd_type == LOG_CMD || cmd_type == LOG_CMD_ERR) { if (MODRET_ERRMSG(mr)) pr_log_pri(PR_LOG_NOTICE, "%s", MODRET_ERRMSG(mr)); } else if (send_error) { if (MODRET_ERRNUM(mr) && MODRET_ERRMSG(mr)) pr_response_add_err(MODRET_ERRNUM(mr), "%s", MODRET_ERRMSG(mr)); else if (MODRET_ERRMSG(mr)) pr_response_send_raw("%s", MODRET_ERRMSG(mr)); } success = -1; } if (session.user && !(session.sf_flags & SF_XFER) && cmd_type == CMD) session_set_idle(); destroy_pool(cmd->tmp_pool); } if (!success) c = pr_stash_get_symbol(PR_SYM_CMD, match, c, index_cache); } if (!c && !success && validate) { pr_response_add_err(R_500, "%s not understood", cmd->argv[0]); success = -1; } return success; } void pr_cmd_dispatch(cmd_rec *cmd) { char *cp = NULL; int success = 0; cmd->server = main_server; resp_list = resp_err_list = NULL; /* Set the pool used for the response lists for this command. */ pr_response_set_pool(cmd->pool); for (cp = cmd->argv[0]; *cp; cp++) *cp = toupper(*cp); if (!cmd->class) cmd->class = get_command_class(cmd->argv[0]); /* First, dispatch to wildcard PRE_CMD handlers. */ success = _dispatch(cmd, PRE_CMD, FALSE, C_ANY); if (!success) /* run other pre_cmd */ success = _dispatch(cmd, PRE_CMD, FALSE, NULL); if (success < 0) { /* Dispatch to POST_CMD_ERR handlers as well. */ _dispatch(cmd, POST_CMD_ERR, FALSE, C_ANY); _dispatch(cmd, POST_CMD_ERR, FALSE, NULL); _dispatch(cmd, LOG_CMD_ERR, FALSE, C_ANY); _dispatch(cmd, LOG_CMD_ERR, FALSE, NULL); pr_response_flush(&resp_err_list); return; } success = _dispatch(cmd, CMD, FALSE, C_ANY); if (!success) success = _dispatch(cmd, CMD, TRUE, NULL); if (success == 1) { success = _dispatch(cmd, POST_CMD, FALSE, C_ANY); if (!success) success = _dispatch(cmd, POST_CMD, FALSE, NULL); _dispatch(cmd, LOG_CMD, FALSE, C_ANY); _dispatch(cmd, LOG_CMD, FALSE, NULL); pr_response_flush(&resp_list); } else if (success < 0) { /* Allow for non-logging command handlers to be run if CMD fails. */ success = _dispatch(cmd, POST_CMD_ERR, FALSE, C_ANY); if (!success) success = _dispatch(cmd, POST_CMD_ERR, FALSE, NULL); _dispatch(cmd, LOG_CMD_ERR, FALSE, C_ANY); _dispatch(cmd, LOG_CMD_ERR, FALSE, NULL); pr_response_flush(&resp_err_list); } } static cmd_rec *make_ftp_cmd(pool *p, char *buf, int flags) { char *cp = buf, *wrd; cmd_rec *cmd; pool *subpool; array_header *tarr; int str_flags = PR_STR_FL_PRESERVE_COMMENTS|flags; /* Be pedantic (and RFC-compliant) by not allowing leading whitespace * in an issued FTP command. Will this cause troubles with many clients? */ if (isspace((int) buf[0])) return NULL; /* Nothing there...bail out. */ wrd = pr_str_get_word(&cp, str_flags); if (wrd == NULL) return NULL; subpool = make_sub_pool(p); cmd = (cmd_rec *) pcalloc(subpool, sizeof(cmd_rec)); cmd->pool = subpool; cmd->tmp_pool = NULL; cmd->stash_index = -1; tarr = make_array(cmd->pool, 2, sizeof(char *)); *((char **) push_array(tarr)) = pstrdup(cmd->pool, wrd); cmd->argc++; cmd->arg = pstrdup(cmd->pool, cp); while ((wrd = pr_str_get_word(&cp, str_flags)) != NULL) { *((char **) push_array(tarr)) = pstrdup(cmd->pool, wrd); cmd->argc++; } *((char **) push_array(tarr)) = NULL; cmd->argv = (char **) tarr->elts; /* This table will not contain that many entries, so a low number * of chains should suffice. */ cmd->notes = pr_table_nalloc(cmd->pool, 0, 8); return cmd; } static int idle_timeout_cb(CALLBACK_FRAME) { /* We don't want to quit in the middle of a transfer */ if (session.sf_flags & SF_XFER) { /* Restart the timer. */ return 1; } pr_event_generate("core.timeout-idle", NULL); pr_response_send_async(R_421, "Idle Timeout (%d seconds): closing control " "connection.", TimeoutIdle); session_exit(PR_LOG_INFO, "FTP session idle timeout, disconnected.", 0, NULL); pr_timer_remove(TIMER_LOGIN, ANY_MODULE); pr_timer_remove(TIMER_NOXFER, ANY_MODULE); return 0; } static void cmd_loop(server_rec *server, conn_t *c) { static long cmd_buf_size = -1; config_rec *id = NULL; char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}; char *cp; char *display = NULL; const char *serveraddress = NULL; config_rec *masq_c = NULL; int i; serveraddress = pr_netaddr_get_ipstr(c->local_addr); set_proc_title("connected: %s (%s:%d)", c->remote_name ? c->remote_name : "?", c->remote_addr ? pr_netaddr_get_ipstr(c->remote_addr) : "?", c->remote_port ? c->remote_port : 0); /* Make sure we can receive OOB data */ pr_inet_set_async(session.pool, session.c); /* Setup the main idle timer */ if (TimeoutIdle) pr_timer_add(TimeoutIdle, TIMER_IDLE, NULL, idle_timeout_cb); if ((masq_c = find_config(server->conf, CONF_PARAM, "MasqueradeAddress", FALSE)) != NULL) { pr_netaddr_t *masq_addr = (pr_netaddr_t *) masq_c->argv[0]; serveraddress = pr_netaddr_get_ipstr(masq_addr); } display = get_param_ptr(server->conf, "DisplayConnect", FALSE); if (display != NULL) { if (pr_display_file(display, NULL, R_220) < 0) pr_log_debug(DEBUG6, "unable to display DisplayConnect file '%s': %s", display, strerror(errno)); } if ((id = find_config(server->conf, CONF_PARAM, "ServerIdent", FALSE)) == NULL || *((unsigned char *) id->argv[0]) == FALSE) { unsigned char *defer_welcome = get_param_ptr(main_server->conf, "DeferWelcome", FALSE); if (id && id->argc > 1) pr_response_send(R_220, "%s", (char *) id->argv[1]); else if (defer_welcome && *defer_welcome == TRUE) pr_response_send(R_220, "ProFTPD " PROFTPD_VERSION_TEXT " Server ready."); else pr_response_send(R_220, "ProFTPD " PROFTPD_VERSION_TEXT " Server (%s) [%s]", server->ServerName, serveraddress); } else pr_response_send(R_220, "%s FTP server ready", serveraddress); pr_log_pri(PR_LOG_INFO, "FTP session opened."); while (TRUE) { pr_signals_handle(); if (pr_netio_telnet_gets(buf, sizeof(buf)-1, session.c->instrm, session.c->outstrm) == NULL) { if (PR_NETIO_ERRNO(session.c->instrm) == EINTR) /* Simple interrupted syscall */ continue; #ifndef PR_DEVEL_NO_DAEMON /* Otherwise, EOF */ end_login(0); #else return; #endif /* PR_DEVEL_NO_DAEMON */ } /* Data received, reset idle timer */ if (TimeoutIdle) pr_timer_reset(TIMER_IDLE, NULL); if (cmd_buf_size == -1) { long *buf_size = get_param_ptr(main_server->conf, "CommandBufferSize", FALSE); if (buf_size == NULL || *buf_size <= 0) cmd_buf_size = 512; else if (*buf_size + 1 > sizeof(buf)) { pr_log_pri(PR_LOG_WARNING, "Invalid CommandBufferSize size given. " "Resetting to 512."); cmd_buf_size = 512; } } buf[cmd_buf_size - 1] = '\0'; i = strlen(buf); if (i && (buf[i-1] == '\n' || buf[i-1] == '\r')) { buf[i-1] = '\0'; i--; if (i && (buf[i-1] == '\n' || buf[i-1] =='\r')) buf[i-1] = '\0'; } cp = buf; if (*cp == '\r') cp++; if (*cp) { cmd_rec *cmd; int flags = 0; /* If this is a SITE command, preserve embedded whitespace in the * command parameters, in order to handle file names that have multiple * spaces in the names. Arguably this should be handled in the SITE * command handlers themselves, via cmd->arg. This small hack * reduces the burden on SITE module developers, however. */ if (strncasecmp(cp, C_SITE, 4) == 0) flags |= PR_STR_FL_PRESERVE_WHITESPACE; cmd = make_ftp_cmd(session.pool, cp, flags); if (cmd) { pr_cmd_dispatch(cmd); destroy_pool(cmd->pool); } else pr_response_send(R_500, "Invalid command: try being more creative"); } /* release any working memory allocated in inet */ pr_inet_clear(); } } void pr_rehash_register_handler(void *data, void (*fp)(void*)) { struct rehash *r = (struct rehash*)pcalloc(permanent_pool, sizeof(struct rehash)); r->data = data; r->rehash = fp; r->next = rehash_list; rehash_list = r; } static void core_rehash_cb(void *d1, void *d2, void *d3, void *d4) { struct rehash *rh = NULL; if (is_master && mpid) { int maxfd; fd_set childfds; pr_log_pri(PR_LOG_NOTICE, "received SIGHUP -- master server rehashing " "configuration file"); /* Make sure none of our children haven't completed start up */ FD_ZERO(&childfds); maxfd = -1; maxfd = semaphore_fds(&childfds, maxfd); if (maxfd > -1) { pr_log_pri(PR_LOG_NOTICE, "waiting for child processes to complete " "initialization"); while (maxfd != -1) { int i; i = select(maxfd + 1, &childfds, NULL, NULL, NULL); if (i > 0) { pr_child_t *ch; for (ch = child_get(NULL); ch; ch = child_get(ch)) { if (ch->ch_pipefd != -1 && FD_ISSET(ch->ch_pipefd, &childfds)) { close(ch->ch_pipefd); ch->ch_pipefd = -1; } } } FD_ZERO(&childfds); maxfd = -1; maxfd = semaphore_fds(&childfds, maxfd); } } free_bindings(); /* Run through the list of registered rehash callbacks. */ pr_event_generate("core.restart", NULL); for (rh = rehash_list; rh; rh = rh->next) rh->rehash(rh->data); init_log(); init_class(); init_config(); pr_parser_prepare(NULL, NULL); PRIVS_ROOT if (pr_parser_parse_file(NULL, config_filename, NULL, 0) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "Fatal: unable to read configuration file '%s': %s", config_filename, strerror(errno)); end_login(1); } PRIVS_RELINQUISH if (pr_parser_cleanup() < 0) { pr_log_pri(PR_LOG_ERR, "Fatal: error processing configuration file '%s': " "unclosed configuration section", config_filename); end_login(1); } /* After configuration is complete, make sure that passwd, group * aren't held open (unnecessary fds for master daemon) */ endpwent(); endgrent(); /* Set the (possibly new) resource limits. */ set_daemon_rlimits(); /* XXX What should be done if fixup_servers() returns -1? */ fixup_servers(server_list); pr_event_generate("core.postparse", NULL); /* Recreate the listen connection. Can an inetd-spawned server accept * and process HUP? */ init_bindings(); } else /* Child process -- cannot rehash, log error */ pr_log_pri(PR_LOG_ERR, "received SIGHUP, cannot rehash child process"); } #ifndef PR_DEVEL_NO_FORK static int dup_low_fd(int fd) { int i,need_close[3] = {-1, -1, -1}; for (i = 0; i < 3; i++) if (fd == i) { fd = dup(fd); need_close[i] = 1; } for (i = 0; i < 3; i++) if (need_close[i] > -1) close(i); return fd; } #endif /* PR_DEVEL_NO_FORK */ static void set_server_privs(void) { uid_t server_uid, current_euid = geteuid(); gid_t server_gid, current_egid = getegid(); unsigned char switch_server_id = FALSE; uid_t *uid = get_param_ptr(main_server->conf, "UserID", FALSE); gid_t *gid = get_param_ptr(main_server->conf, "GroupID", FALSE); if (uid) { server_uid = *uid; switch_server_id = TRUE; } else server_uid = current_euid; if (gid) { server_gid = *gid; switch_server_id = TRUE; } else server_gid = current_egid; if (switch_server_id) { PRIVS_ROOT /* Note: will it be necessary to double check this switch, as is done * in elsewhere in this file? */ PRIVS_SETUP(server_uid, server_gid); } } static void fork_server(int fd, conn_t *l, unsigned char nofork) { conn_t *conn = NULL; unsigned char *ident_lookups = NULL; int i, rev; int semfds[2] = { -1, -1 }; int xerrno = 0; #ifndef PR_DEVEL_NO_FORK pid_t pid; sigset_t sig_set; if (!nofork) { /* A race condition exists on heavily loaded servers where the parent * catches SIGHUP and attempts to close/re-open the main listening * socket(s), however the children haven't finished closing them * (EADDRINUSE). We use a semaphore pipe here to flag the parent once * the child has closed all former listening sockets. */ if (pipe(semfds) == -1) { pr_log_pri(PR_LOG_ERR, "pipe(): %s", strerror(errno)); close(fd); return; } /* Need to make sure the child (writer) end of the pipe isn't * < 2 (stdio/stdout/stderr) as this will cause problems later. */ if (semfds[1] < 3) semfds[1] = dup_low_fd(semfds[1]); /* We block SIGCHLD to prevent a race condition if the child * dies before we can record it's pid. Also block SIGTERM to * prevent sig_terminate() from examining the child list */ sigemptyset(&sig_set); sigaddset(&sig_set, SIGTERM); sigaddset(&sig_set, SIGCHLD); sigaddset(&sig_set, SIGUSR1); sigaddset(&sig_set, SIGUSR2); sigprocmask(SIG_BLOCK, &sig_set, NULL); switch ((pid = fork())) { case 0: /* child */ /* No longer the master process. */ is_master = FALSE; sigprocmask(SIG_UNBLOCK, &sig_set, NULL); /* No longer need the read side of the semaphore pipe. */ close(semfds[0]); break; case -1: sigprocmask(SIG_UNBLOCK, &sig_set, NULL); pr_log_pri(PR_LOG_ERR, "fork(): %s", strerror(errno)); /* The parent doesn't need the socket open. */ close(fd); close(semfds[0]); close(semfds[1]); return; default: /* parent */ /* The parent doesn't need the socket open */ close(fd); child_add(pid, semfds[0]); close(semfds[1]); /* Unblock the signals now as sig_child() will catch * an "immediate" death and remove the pid from the children list */ sigprocmask(SIG_UNBLOCK, &sig_set, NULL); return; } } /* No longer need any listening fds. */ pr_ipbind_close_listeners(); /* There would appear to be no useful purpose behind setting the process * group of the newly forked child. In daemon/inetd mode, we should have no * controlling tty and either have the process group of the parent or of * inetd. In non-daemon mode (-n), doing this may cause SIGTTOU to be * raised on output to the terminal (stderr logging). * * #ifdef HAVE_SETPGID * setpgid(0,getpid()); * #else * # ifdef SETPGRP_VOID * setpgrp(); * # else * setpgrp(0,getpid()); * # endif * #endif * */ /* Reseed pseudo-randoms */ srand(time(NULL)); #endif /* PR_DEVEL_NO_FORK */ /* Child is running here */ signal(SIGUSR1, sig_disconnect); signal(SIGUSR2, sig_evnt); signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_IGN); /* From this point on, syslog stays open. We close it first so that the * logger will pick up our new PID. * * We have to delay calling log_opensyslog() until after pr_inet_openrw() * is called, otherwise the potential exists for the syslog FD to * be overwritten and the user to see logging information. * * This isn't that big of a deal because the logging functions will * just open it dynamically if they need to. */ log_closesyslog(); /* Specifically DO NOT perform reverse DNS at this point, to alleviate * the race condition mentioned above. Instead we do it after closing * all former listening sockets. */ conn = pr_inet_openrw(permanent_pool, l, NULL, PR_NETIO_STRM_CTRL, fd, STDIN_FILENO, STDOUT_FILENO, FALSE); /* Capture errno here, if necessary. */ if (!conn) xerrno = errno; /* Now do the permanent syslog open */ pr_signals_block(); PRIVS_ROOT log_opensyslog(NULL); PRIVS_RELINQUISH pr_signals_unblock(); if (!conn) { pr_log_pri(PR_LOG_ERR, "Fatal: unable to open incoming connection: %s", strerror(xerrno)); exit(1); } pr_inet_set_proto_opts(permanent_pool, conn, 0, 1, 1, 0, 0); pr_event_generate("core.connect", conn); /* Find the server for this connection. */ main_server = pr_ipbind_get_server(conn->local_addr, conn->local_port); /* The follow code was ostensibly used to conserve memory, to free all other * servers and associated configurations. However, when large numbers of * servers are configured, this process adds significant time to the * establishment of a session. More importantly, I do not think it is * really necessary; copy-on-write semantics mean that those portions of * memory won't actually be in this process' space until changed. And if * those configurations will never be reached, the only time the associated * memory would change is now, when it is attempted to be freed. * * s = main_server; * while (s) { * s_saved = s->next; * if (s != serv) { * if (s->listen && s->listen != l) { * if (s->listen->listen_fd == conn->rfd || * s->listen->listen_fd == conn->wfd) * s->listen->listen_fd = -1; * else * inet_close(s->pool,s->listen); * } * * if (s->listen) { * if (s->listen->listen_fd == conn->rfd || * s->listen->listen_fd == conn->wfd) * s->listen->listen_fd = -1; * } * * xaset_remove(server_list,(xasetmember_t*)s); * destroy_pool(s->pool); * } * s = s_saved; * } */ session.pool = make_sub_pool(permanent_pool); pr_pool_tag(session.pool, "Session Pool"); session.c = conn; session.data_port = conn->remote_port - 1; session.sf_flags = 0; session.sp_flags = 0; session.proc_prefix = "(connecting)"; /* Close the write side of the semaphore pipe to tell the parent * we are all grown up and have finished housekeeping (closing * former listen sockets). */ close(semfds[1]); /* Now perform reverse DNS lookups. */ if (ServerUseReverseDNS) { rev = pr_netaddr_set_reverse_dns(ServerUseReverseDNS); if (conn->remote_addr) conn->remote_name = pr_netaddr_get_dnsstr(conn->remote_addr); pr_netaddr_set_reverse_dns(rev); } pr_netaddr_set_sess_addrs(); /* Check and see if we are shutdown */ if (shutdownp) { time_t now; time(&now); if (!deny || deny <= now) { config_rec *c = NULL; char *reason = NULL; const char *serveraddress; serveraddress = (session.c && session.c->local_addr) ? pr_netaddr_get_ipstr(session.c->local_addr) : main_server->ServerAddress; if ((c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE)) != NULL) { pr_netaddr_t *masq_addr = (pr_netaddr_t *) c->argv[0]; serveraddress = pr_netaddr_get_ipstr(masq_addr); } reason = sreplace(permanent_pool, shutmsg, "%s", pstrdup(permanent_pool, pr_strtime(shut)), "%r", pstrdup(permanent_pool, pr_strtime(deny)), "%d", pstrdup(permanent_pool, pr_strtime(disc)), "%C", (session.cwd[0] ? session.cwd : "(none)"), "%L", serveraddress, "%R", (session.c && session.c->remote_name ? session.c->remote_name : "(unknown)"), "%T", pstrdup(permanent_pool, pr_strtime(now)), "%U", "NONE", "%V", main_server->ServerName, NULL ); pr_log_auth(PR_LOG_NOTICE, "connection refused (%s) from %s [%s]", reason, session.c->remote_name, pr_netaddr_get_ipstr(session.c->remote_addr)); pr_response_send(R_500, "FTP server shut down (%s) -- please try again " "later", reason); exit(0); } } /* If no server is configured to handle the addr the user is * connected to, drop them. */ if (!main_server) { pr_response_send(R_500, "Sorry, no server available to handle request on " "%s", pr_netaddr_get_dnsstr(conn->local_addr)); exit(0); } if (main_server->listen) { if (main_server->listen->listen_fd == conn->rfd || main_server->listen->listen_fd == conn->wfd) main_server->listen->listen_fd = -1; destroy_pool(main_server->listen->pool); main_server->listen = NULL; } /* Check config tree for directives */ if (!login_check_limits(main_server->conf, TRUE, FALSE, &i)) { pr_log_pri(PR_LOG_NOTICE, "Connection from %s [%s] denied.", session.c->remote_name, pr_netaddr_get_ipstr(session.c->remote_addr)); exit(0); } /* Set the ID/privs for the User/Group in this server */ set_server_privs(); /* Find the class for this session. */ session.class = pr_class_match_addr(session.c->remote_addr); if (session.class != NULL) pr_log_debug(DEBUG2, "FTP session requested from class '%s'", session.class->cls_name); else pr_log_debug(DEBUG2, "FTP session requested from unknown class"); /* Create a table for modules to use. */ session.notes = pr_table_alloc(session.pool, 0); /* Prepare the Timers API. */ timers_init(); /* Inform all the modules that we are now a child */ pr_log_debug(DEBUG7, "performing module session initializations"); if (modules_session_init() < 0) end_login(1); /* Use the ident protocol (RFC1413) to try to get remote ident_user */ if ((ident_lookups = get_param_ptr(main_server->conf, "IdentLookups", FALSE)) == NULL || *ident_lookups == TRUE) { pr_log_debug(DEBUG6, "performing ident lookup"); session.ident_lookups = TRUE; session.ident_user = pr_ident_lookup(session.pool, conn); pr_log_debug(DEBUG6, "ident lookup returned '%s'", session.ident_user); } else { pr_log_debug(DEBUG6, "ident lookup disabled"); session.ident_lookups = FALSE; session.ident_user = "UNKNOWN"; } pr_log_debug(DEBUG4, "connected - local : %s:%d", pr_netaddr_get_ipstr(session.c->local_addr), session.c->local_port); pr_log_debug(DEBUG4, "connected - remote : %s:%d", pr_netaddr_get_ipstr(session.c->remote_addr), session.c->remote_port); /* Set the per-child resource limits. */ set_session_rlimits(); cmd_loop(main_server, conn); #ifdef PR_DEVEL_NO_DAEMON /* Cleanup */ end_login_noexit(); main_server = NULL; free_pools(); free_proc_title(); #endif /* PR_DEVEL_NO_DAEMON */ } static void disc_children(void) { if (disc && disc <= time(NULL) && child_count()) { sigset_t sig_set; sigemptyset(&sig_set); sigaddset(&sig_set, SIGTERM); sigaddset(&sig_set, SIGCHLD); sigaddset(&sig_set, SIGUSR1); sigaddset(&sig_set, SIGUSR2); sigprocmask(SIG_BLOCK, &sig_set, NULL); PRIVS_ROOT child_signal(SIGUSR1); PRIVS_RELINQUISH sigprocmask(SIG_UNBLOCK, &sig_set, NULL); } } static void daemon_loop(void) { fd_set listenfds; conn_t *listen_conn; int fd, maxfd; int i, err_count = 0; unsigned long nconnects = 0UL; time_t last_error; struct timeval tv; static int running = 0; set_proc_title("(accepting connections)"); time(&last_error); while (TRUE) { run_schedule(); FD_ZERO(&listenfds); maxfd = 0; maxfd = pr_ipbind_listen(&listenfds); /* Monitor children pipes */ maxfd = semaphore_fds(&listenfds, maxfd); /* Check for ftp shutdown message file */ switch (check_shutmsg(&shut, &deny, &disc, shutmsg, sizeof(shutmsg))) { case 1: if (!shutdownp) disc_children(); shutdownp = 1; break; case 0: shutdownp = 0; deny = disc = (time_t) 0; break; } if (shutdownp) { tv.tv_sec = 5L; tv.tv_usec = 0L; } else { tv.tv_sec = PR_TUNABLE_SELECT_TIMEOUT; tv.tv_usec = 0L; } /* If running (a flag signaling whether proftpd is just starting up) * AND shutdownp (a flag signalling the present of /etc/shutmsg) are * true, then log an error stating this -- but don't stop the server. */ if (shutdownp && !running) { /* Check the value of the deny time_t struct w/ the current time. * If the deny time has passed, log that all incoming connections * will be refused. If not, note the date at which they will be * refused in the future. */ time_t now = time(NULL); if (difftime(deny, now) < 0.0) { pr_log_pri(PR_LOG_ERR, PR_SHUTMSG_PATH " present: all incoming connections will be refused."); } else { pr_log_pri(PR_LOG_ERR, PR_SHUTMSG_PATH " present: incoming connections " "will be denied starting %s", CHOP(ctime(&deny))); } } running = 1; i = select(maxfd + 1, &listenfds, NULL, NULL, &tv); if (i == -1 && errno == EINTR) { pr_signals_handle(); continue; } if (have_dead_child) { sigset_t sig_set; sigemptyset(&sig_set); sigaddset(&sig_set, SIGCHLD); sigaddset(&sig_set, SIGTERM); pr_alarms_block(); sigprocmask(SIG_BLOCK, &sig_set, NULL); have_dead_child = FALSE; child_update(); sigprocmask(SIG_UNBLOCK, &sig_set, NULL); pr_alarms_unblock(); } if (i == -1) { time_t this_error; time(&this_error); if ((this_error - last_error) <= 5 && err_count++ > 10) { pr_log_pri(PR_LOG_ERR, "Fatal: select() failing repeatedly, shutting " "down."); exit(1); } else if ((this_error - last_error) > 5) { last_error = this_error; err_count = 0; } pr_log_pri(PR_LOG_NOTICE, "select() failed in daemon_loop(): %s", strerror(errno)); } if (i == 0) continue; /* Reset the connection counter. Take into account this current * connection, which does not (yet) have an entry in the child list. */ nconnects = 1UL; /* See if child semaphore pipes have signaled */ if (child_count()) { pr_child_t *ch; time_t now = time(NULL); for (ch = child_get(NULL); ch; ch = child_get(ch)) { if (ch->ch_pipefd != -1 && FD_ISSET(ch->ch_pipefd, &listenfds)) { close(ch->ch_pipefd); ch->ch_pipefd = -1; } /* While we're looking, tally up the number of children forked in * the past interval. */ if (ch->ch_when >= (now - (unsigned long) max_connect_interval)) nconnects++; } } pr_signals_handle(); /* Accept the connection. */ listen_conn = pr_ipbind_accept_conn(&listenfds, &fd); /* Fork off servers to handle each connection our job is to get back to * answering connections asap, so leave the work of determining which * server the connection is for to our child. */ if (listen_conn) { /* Check for exceeded MaxInstances. */ if (ServerMaxInstances && (child_count() >= ServerMaxInstances)) { pr_event_generate("core.max-instances", NULL); pr_log_pri(PR_LOG_WARNING, "MaxInstances (%d) reached, new connection denied", ServerMaxInstances); close(fd); /* Check for exceeded MaxConnectionRate. */ } else if (max_connects && (nconnects > max_connects)) { pr_event_generate("core.max-connection-rate", NULL); pr_log_pri(PR_LOG_WARNING, "MaxConnectionRate (%lu/%u secs) reached, new connection denied", max_connects, max_connect_interval); close(fd); /* Fork off a child to handle the connection. */ } else fork_server(fd, listen_conn, FALSE); } #ifdef PR_DEVEL_NO_DAEMON /* Do not continue the while() loop here if not daemonizing. */ break; #endif /* PR_DEVEL_NO_DAEMON */ } } /* This function is to handle the dispatching of actions based on * signals received by the signal handlers, to avoid signal handler-based * race conditions. */ void pr_signals_handle(void) { while (recvd_signal_flags) { if (recvd_signal_flags & RECEIVED_SIG_ALRM) { recvd_signal_flags &= ~RECEIVED_SIG_ALRM; handle_alarm(); } if (recvd_signal_flags & RECEIVED_SIG_CHLD) { recvd_signal_flags &= ~RECEIVED_SIG_CHLD; handle_chld(); } if (recvd_signal_flags & RECEIVED_SIG_EVENT) { recvd_signal_flags &= ~RECEIVED_SIG_EVENT; handle_evnt(); } if (recvd_signal_flags & RECEIVED_SIG_SEGV) { recvd_signal_flags &= ~RECEIVED_SIG_SEGV; handle_terminate_other(); } if (recvd_signal_flags & RECEIVED_SIG_TERMINATE) { recvd_signal_flags &= ~RECEIVED_SIG_TERMINATE; handle_terminate(); } if (recvd_signal_flags & RECEIVED_SIG_TERM_OTHER) { recvd_signal_flags &= ~RECEIVED_SIG_TERM_OTHER; handle_terminate_other(); } if (recvd_signal_flags & RECEIVED_SIG_XCPU) { recvd_signal_flags &= ~RECEIVED_SIG_XCPU; handle_xcpu(); } if (recvd_signal_flags & RECEIVED_SIG_ABORT) { recvd_signal_flags &= RECEIVED_SIG_ABORT; handle_abort(); } if (recvd_signal_flags & RECEIVED_SIG_REHASH) { /* NOTE: should this be done here, rather than using a schedule? */ schedule(core_rehash_cb, 0, NULL, NULL, NULL, NULL); recvd_signal_flags &= ~RECEIVED_SIG_REHASH; } if (recvd_signal_flags & RECEIVED_SIG_EXIT) { session_exit(PR_LOG_NOTICE, "Parent process requested shutdown", 0, NULL); recvd_signal_flags &= ~RECEIVED_SIG_EXIT; } if (recvd_signal_flags & RECEIVED_SIG_SHUTDOWN) { /* NOTE: should this be done here, rather than using a schedule? */ schedule(shutdown_exit, 0, NULL, NULL, NULL, NULL); recvd_signal_flags &= ~RECEIVED_SIG_SHUTDOWN; } } } /* sig_rehash occurs in the master daemon when manually "kill -HUP" * in order to re-read configuration files, and is sent to all * children by the master. */ static RETSIGTYPE sig_rehash(int signo) { recvd_signal_flags |= RECEIVED_SIG_REHASH; signal(SIGHUP, sig_rehash); } static RETSIGTYPE sig_evnt(int signo) { recvd_signal_flags |= RECEIVED_SIG_EVENT; signal(SIGHUP, sig_evnt); } /* sig_disconnect is called in children when the parent daemon * detects that shutmsg has been created and ftp sessions should * be destroyed. If a file transfer is underway, the process simply * dies, otherwise a function is scheduled to attempt to display * the shutdown reason. */ static RETSIGTYPE sig_disconnect(int signo) { /* If this is an anonymous session, or a transfer is in progress, * perform the exit a little later... */ if ((session.sf_flags & SF_ANON) || (session.sf_flags & SF_XFER)) recvd_signal_flags |= RECEIVED_SIG_EXIT; else recvd_signal_flags |= RECEIVED_SIG_SHUTDOWN; signal(SIGUSR1, SIG_IGN); } static RETSIGTYPE sig_child(int signo) { recvd_signal_flags |= RECEIVED_SIG_CHLD; /* We make an exception here to the synchronous processing that is done * for other signals; SIGCHLD is handled asynchronously. This is made * necessary by two things. * * First, we need to support non-POSIX systems. Under POSIX, once a * signal handler has been configured for a given signal, that becomes * that signal's disposition, until explicitly changed later. Non-POSIX * systems, on the other hand, will restore the default disposition of * a signal after a custom signal handler has been configured. Thus, * to properly support non-POSIX systems, a call to signal(2) is necessary * as one of the last steps in our signal handlers. * * Second, SVR4 systems differ specifically in their semantics of signal(2) * and SIGCHLD. These systems will check for any unhandled SIGCHLD * signals, waiting to be reaped via wait(2) or waitpid(2), whenever * the disposition of SIGCHLD is changed. This means that if our process * handles SIGCHLD, but does not call wait(2) or waitpid(2), and then * calls signal(2), another SIGCHLD is generated; this loop repeats, * until the process runs out of stack space and terminates. * * Thus, in order to cover this interaction, we'll need to call handle_chld() * here, asynchronously. handle_chld() does the work of reaping dead * child processes, and does not seem to call any non-reentrant functions, * so it should be safe. */ handle_chld(); signal(SIGCHLD, sig_child); } #ifdef PR_DEVEL_COREDUMP static char *prepare_core(void) { static char dir[256] = {'\0'}; snprintf(dir, sizeof(dir), "%s/proftpd-core-%lu", PR_CORE_DIR, (unsigned long) getpid()); if (mkdir(dir, 0700) != -1) chdir(dir); else pr_log_pri(PR_LOG_ERR, "unable to create '%s': %s", dir, strerror(errno)); return dir; } #endif /* PR_DEVEL_COREDUMP */ static RETSIGTYPE sig_abort(int signo) { recvd_signal_flags |= RECEIVED_SIG_ABORT; signal(SIGABRT, SIG_DFL); } static void handle_abort(void) { #ifdef PR_DEVEL_COREDUMP pr_log_pri(PR_LOG_NOTICE, "ProFTPD received SIGABRT signal, generating core " "file in %s", prepare_core()); #else pr_log_pri(PR_LOG_NOTICE, "ProFTPD received SIGABRT signal, no core dump"); #endif /* PR_DEVEL_COREDUMP */ end_login_noexit(); abort(); } static RETSIGTYPE sig_terminate(int signo) { if (signo == SIGSEGV) { recvd_signal_flags |= RECEIVED_SIG_ABORT; /* Make sure the scoreboard slot is properly cleared. */ pr_scoreboard_del_entry(FALSE); /* This is probably not the safest thing to be doing, but since the * process is terminating anyway, why not? It helps when knowing/logging * that a segfault happened... */ pr_log_pri(PR_LOG_NOTICE, "ProFTPD terminating (signal 11)"); pr_log_pri(PR_LOG_INFO, "FTP session closed."); /* Restore the default signal handler. */ signal(SIGSEGV, SIG_DFL); } else if (signo == SIGTERM) recvd_signal_flags |= RECEIVED_SIG_TERMINATE; else if (signo == SIGXCPU) recvd_signal_flags |= RECEIVED_SIG_XCPU; else recvd_signal_flags |= RECEIVED_SIG_TERM_OTHER; /* Capture the signal number for later display purposes. */ term_signo = signo; } static void handle_chld(void) { sigset_t sig_set; pid_t pid; sigemptyset(&sig_set); sigaddset(&sig_set, SIGTERM); sigaddset(&sig_set, SIGCHLD); pr_alarms_block(); /* Block SIGTERM in here, so we don't create havoc with the child list * while modifying it. */ sigprocmask(SIG_BLOCK, &sig_set, NULL); while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { if (child_remove(pid) == 0) have_dead_child = TRUE; } sigprocmask(SIG_UNBLOCK, &sig_set, NULL); pr_alarms_unblock(); } #ifndef PR_USE_CTRLS static void debug_memory(const char *fmt, ...) { char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}; va_list msg; va_start(msg, fmt); vsnprintf(buf, sizeof(buf), fmt, msg); va_end(msg); buf[sizeof(buf)-1] = '\0'; pr_log_pri(PR_LOG_NOTICE, "%s", buf); } static void handle_evnt(void) { pr_pool_debug_memory(debug_memory); } #else static void handle_evnt(void) { pr_event_generate("core.signal.USR2", NULL); } #endif /* !PR_USE_CTRLS */ static void handle_xcpu(void) { pr_log_pri(PR_LOG_NOTICE, "ProFTPD CPU limit exceeded (signal %d)", SIGXCPU); finish_terminate(); } static void handle_terminate_other(void) { pr_log_pri(PR_LOG_ERR, "ProFTPD terminating (signal %d)", term_signo); finish_terminate(); } static void handle_terminate(void) { /* Do not log if we are a child that has been terminated. */ if (is_master) { /* Send a SIGTERM to all our children */ if (child_count()) { PRIVS_ROOT child_signal(SIGTERM); PRIVS_RELINQUISH } pr_log_pri(PR_LOG_NOTICE, "ProFTPD killed (signal %d)", term_signo); } finish_terminate(); } static void finish_terminate(void) { if (is_master && mpid == getpid()) { PRIVS_ROOT /* Do not need the pidfile any longer. */ if (is_standalone && !nodaemon) unlink(PidPath); /* Run any exit handlers registered in the master process here, so that * they may have the benefit of root privs. More than likely these * exit handlers were registered by modules' module initialization * functions, which also occur under root priv conditions. (If an * exit handler is registered after the fork(), it won't be run here -- * that registration occurs in a different process space. */ pr_event_generate("core.exit", NULL); /* Remove the registered exit handlers now, so that the ensuing * end_login() call (outside the root privs condition) does not call * the exit handlers for the master process again. */ pr_event_unregister(NULL, "core.exit", NULL); PRIVS_RELINQUISH if (is_standalone) { pr_log_pri(PR_LOG_NOTICE, "ProFTPD " PROFTPD_VERSION_TEXT " standalone mode SHUTDOWN"); /* Clean up the scoreboard */ PRIVS_ROOT pr_delete_scoreboard(); PRIVS_RELINQUISH } } end_login(1); } static void install_signal_handlers(void) { sigset_t sig_set; /* Should the master server (only applicable in standalone mode) * kill off children if we receive a signal that causes termination? * Hmmmm... maybe this needs to be rethought, but I've done it in * such a way as to only kill off our children if we receive a SIGTERM, * meaning that the admin wants us dead (and probably our kids too). */ /* The sub-pool for the child list is created the first time we fork * off a child. To conserve memory, the pool and list is destroyed * when our last child dies (to prevent the list from eating more and * more memory on long uptimes). */ sigemptyset(&sig_set); sigaddset(&sig_set, SIGCHLD); sigaddset(&sig_set, SIGINT); sigaddset(&sig_set, SIGQUIT); sigaddset(&sig_set, SIGILL); sigaddset(&sig_set, SIGABRT); sigaddset(&sig_set, SIGFPE); sigaddset(&sig_set, SIGSEGV); sigaddset(&sig_set, SIGALRM); sigaddset(&sig_set, SIGTERM); sigaddset(&sig_set, SIGHUP); sigaddset(&sig_set, SIGUSR2); #ifdef SIGSTKFLT sigaddset(&sig_set, SIGSTKFLT); #endif /* SIGSTKFLT */ #ifdef SIGIO sigaddset(&sig_set, SIGIO); #endif /* SIGIO */ #ifdef SIGBUS sigaddset(&sig_set, SIGBUS); #endif /* SIGBUS */ signal(SIGCHLD, sig_child); signal(SIGHUP, sig_rehash); signal(SIGINT, sig_terminate); signal(SIGQUIT, sig_terminate); signal(SIGILL, sig_terminate); signal(SIGABRT, sig_abort); signal(SIGFPE, sig_terminate); signal(SIGSEGV, sig_terminate); signal(SIGTERM, sig_terminate); signal(SIGXCPU, sig_terminate); signal(SIGURG, SIG_IGN); #ifdef SIGSTKFLT signal(SIGSTKFLT, sig_terminate); #endif /* SIGSTKFLT */ #ifdef SIGIO signal(SIGIO, SIG_IGN); #endif /* SIGIO */ #ifdef SIGBUS signal(SIGBUS, sig_terminate); #endif /* SIGBUS */ signal(SIGUSR2, sig_evnt); /* In case our parent left signals blocked (as happens under some * poor inetd implementations) */ sigprocmask(SIG_UNBLOCK, &sig_set, NULL); } void set_daemon_rlimits(void) { config_rec *c = NULL; struct rlimit rlim; if (getrlimit(RLIMIT_CORE, &rlim) == -1) pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_CORE): %s", strerror(errno)); else { #ifdef PR_DEVEL_COREDUMP rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; #else rlim.rlim_cur = rlim.rlim_max = 0; #endif /* PR_DEVEL_COREDUMP */ PRIVS_ROOT if (setrlimit(RLIMIT_CORE, &rlim) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_CORE): %s", strerror(errno)); return; } PRIVS_RELINQUISH } /* Now check for the configurable resource limits */ c = find_config(main_server->conf, CONF_PARAM, "RLimitCPU", FALSE); #ifdef RLIMIT_CPU while (c) { /* Does this limit apply to the daemon? */ if (c->argv[1] == NULL || !strcmp(c->argv[1], "daemon")) { struct rlimit *cpu_rlimit = (struct rlimit *) c->argv[0]; PRIVS_ROOT if (setrlimit(RLIMIT_CPU, cpu_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_CPU): %s", strerror(errno)); return; } PRIVS_RELINQUISH pr_log_debug(DEBUG2, "set RLimitCPU for daemon"); } c = find_config_next(c, c->next, CONF_PARAM, "RLimitCPU", FALSE); } #endif /* defined RLIMIT_CPU */ c = find_config(main_server->conf, CONF_PARAM, "RLimitMemory", FALSE); #if defined(RLIMIT_AS) || defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) while (c) { /* Does this limit apply to the daemon? */ if (c->argv[1] == NULL || !strcmp(c->argv[1], "daemon")) { struct rlimit *memory_rlimit = (struct rlimit *) c->argv[0]; PRIVS_ROOT # if defined(RLIMIT_AS) if (setrlimit(RLIMIT_AS, memory_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_AS): %s", strerror(errno)); return; } # elif defined(RLIMIT_DATA) if (setrlimit(RLIMIT_DATA, memory_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_DATA): %s", strerror(errno)); return; } # elif defined(RLIMIT_VMEM) if (setrlimit(RLIMIT_VMEM, memory_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_VMEM): %s", strerror(errno)); return; } # endif PRIVS_RELINQUISH pr_log_debug(DEBUG2, "set RLimitMemory for daemon"); } c = find_config_next(c, c->next, CONF_PARAM, "RLimitMemory", FALSE); } #endif /* no RLIMIT_AS || RLIMIT_DATA || RLIMIT_VMEM */ c = find_config(main_server->conf, CONF_PARAM, "RLimitOpenFiles", FALSE); #if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE) while (c) { /* Does this limit apply to the daemon? */ if (c->argv[1] == NULL || !strcmp(c->argv[1], "daemon")) { struct rlimit *nofile_rlimit = (struct rlimit *) c->argv[0]; PRIVS_ROOT # if defined(RLIMIT_NOFILE) if (setrlimit(RLIMIT_NOFILE, nofile_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_NOFILE): %s", strerror(errno)); return; } # elif defined(RLIMIT_OFILE) if (setrlimit(RLIMIT_OFILE, nofile_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_OFILE): %s", strerror(errno)); return; } # endif PRIVS_RELINQUISH pr_log_debug(DEBUG2, "set RLimitOpenFiles for daemon"); } c = find_config_next(c, c->next, CONF_PARAM, "RLimitOpenFiles", FALSE); } #endif /* defined RLIMIT_NOFILE or defined RLIMIT_OFILE */ } void set_session_rlimits(void) { config_rec *c = NULL; /* now check for the configurable rlimits */ c = find_config(main_server->conf, CONF_PARAM, "RLimitCPU", FALSE); #ifdef RLIMIT_CPU while (c) { /* Does this limit apply to the session? */ if (c->argv[1] == NULL || !strcmp(c->argv[1], "session")) { struct rlimit *cpu_rlimit = (struct rlimit *) c->argv[0]; PRIVS_ROOT if (setrlimit(RLIMIT_CPU, cpu_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_CPU): %s", strerror(errno)); return; } PRIVS_RELINQUISH pr_log_debug(DEBUG2, "set RLimitCPU for session"); } c = find_config_next(c, c->next, CONF_PARAM, "RLimitCPU", FALSE); } #endif /* defined RLIMIT_CPU */ c = find_config(main_server->conf, CONF_PARAM, "RLimitMemory", FALSE); #if defined(RLIMIT_AS) || defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) while (c) { /* Does this limit apply to the session? */ if (c->argv[1] == NULL || !strcmp(c->argv[1], "session")) { struct rlimit *memory_rlimit = (struct rlimit *) c->argv[0]; PRIVS_ROOT # if defined(RLIMIT_AS) if (setrlimit(RLIMIT_AS, memory_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_AS): %s", strerror(errno)); return; } # elif defined(RLIMIT_DATA) if (setrlimit(RLIMIT_DATA, memory_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_DATA): %s", strerror(errno)); return; } # elif defined(RLIMIT_VMEM) if (setrlimit(RLIMIT_VMEM, memory_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_VMEM): %s", strerror(errno)); return; } # endif PRIVS_RELINQUISH pr_log_debug(DEBUG2, "set RLimitMemory for session"); } c = find_config_next(c, c->next, CONF_PARAM, "RLimitMemory", FALSE); } #endif /* no RLIMIT_AS || RLIMIT_DATA || RLIMIT_VMEM */ c = find_config(main_server->conf, CONF_PARAM, "RLimitOpenFiles", FALSE); #if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE) while (c) { /* Does this limit apply to the session? */ if (c->argv[1] == NULL || !strcmp(c->argv[1], "session")) { struct rlimit *nofile_rlimit = (struct rlimit *) c->argv[0]; PRIVS_ROOT # if defined(RLIMIT_NOFILE) if (setrlimit(RLIMIT_NOFILE, nofile_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_NOFILE): %s", strerror(errno)); return; } # elif defined(RLIMIT_OFILE) if (setrlimit(RLIMIT_OFILE, nofile_rlimit) == -1) { PRIVS_RELINQUISH pr_log_pri(PR_LOG_ERR, "error: setrlimit(RLIMIT_OFILE): %s", strerror(errno)); return; } # endif /* defined RLIMIT_OFILE */ PRIVS_RELINQUISH pr_log_debug(DEBUG2, "set RLimitOpenFiles for session"); } c = find_config_next(c, c->next, CONF_PARAM, "RLimitOpenFiles", FALSE); } #endif /* defined RLIMIT_NOFILE or defined RLIMIT_OFILE */ } static void write_pid(void) { FILE *pidf = NULL; PidPath = get_param_ptr(main_server->conf, "PidFile", FALSE); if (!PidPath || !*PidPath) PidPath = PR_PID_FILE_PATH; PRIVS_ROOT pidf = fopen(PidPath, "w"); PRIVS_RELINQUISH if (pidf == NULL) { fprintf(stderr, "error opening PidFile '%s': %s\n", PidPath, strerror(errno)); exit(1); } fprintf(pidf, "%lu\n", (unsigned long) getpid()); fclose(pidf); pidf = NULL; } static void daemonize(void) { #ifndef HAVE_SETSID int ttyfd; #endif /* Fork off and have parent exit. */ switch (fork()) { case -1: perror("fork"); exit(1); case 0: break; default: exit(0); } #ifdef HAVE_SETSID /* setsid() is the preferred way to disassociate from the * controlling terminal */ setsid(); #else /* Open /dev/tty to access our controlling tty (if any) */ if ((ttyfd = open("/dev/tty", O_RDWR)) != -1) { if (ioctl(ttyfd, TIOCNOTTY, NULL) == -1) { perror("ioctl"); exit(1); } close(ttyfd); } #endif /* HAVE_SETSID */ /* Close the three big boys */ close(fileno(stdin)); close(fileno(stdout)); close(fileno(stderr)); /* Portable way to prevent re-acquiring a tty in the future */ #ifdef HAVE_SETPGID setpgid(0, getpid()); #else # ifdef SETPGRP_VOID setpgrp(); # else setpgrp(0, getpid()); # endif #endif pr_fsio_chdir("/", 0); } static void inetd_main(void) { int res = 0; /* Make sure the scoreboard file exists. */ PRIVS_ROOT if ((res = pr_open_scoreboard(O_RDWR)) < 0) { PRIVS_RELINQUISH switch (res) { case PR_SCORE_ERR_BAD_MAGIC: pr_log_pri(PR_LOG_ERR, "error opening scoreboard: bad/corrupted file"); return; case PR_SCORE_ERR_OLDER_VERSION: case PR_SCORE_ERR_NEWER_VERSION: pr_log_pri(PR_LOG_ERR, "error opening scoreboard: wrong version, " "writing new scoreboard"); /* Delete the scoreboard, then open it again (and assume that the * open succeeds). */ PRIVS_ROOT pr_delete_scoreboard(); pr_open_scoreboard(O_RDWR); break; default: pr_log_pri(PR_LOG_ERR, "error opening scoreboard: %s", strerror(errno)); return; } } PRIVS_RELINQUISH pr_close_scoreboard(); pr_event_generate("core.startup", NULL); init_bindings(); /* Check our shutdown status */ if (check_shutmsg(&shut, &deny, &disc, shutmsg, sizeof(shutmsg)) == 1) shutdownp = 1; /* Finally, call right into fork_server() to start servicing the * connection immediately. */ fork_server(STDIN_FILENO, main_server->listen, TRUE); } static void standalone_main(void) { int res = 0; is_standalone = TRUE; if (nodaemon) { log_stderr(quiet ? FALSE : TRUE); close(fileno(stdin)); close(fileno(stdout)); } else { log_stderr(FALSE); daemonize(); } mpid = getpid(); PRIVS_ROOT pr_delete_scoreboard(); if ((res = pr_open_scoreboard(O_RDWR)) < 0) { PRIVS_RELINQUISH switch (res) { case PR_SCORE_ERR_BAD_MAGIC: pr_log_pri(PR_LOG_ERR, "error opening scoreboard: bad/corrupted file"); return; case PR_SCORE_ERR_OLDER_VERSION: pr_log_pri(PR_LOG_ERR, "error opening scoreboard: bad version (too old)"); return; case PR_SCORE_ERR_NEWER_VERSION: pr_log_pri(PR_LOG_ERR, "error opening scoreboard: bad version (too new)"); return; default: pr_log_pri(PR_LOG_ERR, "error opening scoreboard: %s", strerror(errno)); return; } } PRIVS_RELINQUISH pr_close_scoreboard(); pr_event_generate("core.startup", NULL); init_bindings(); pr_log_pri(PR_LOG_NOTICE, "ProFTPD %s (built %s) standalone mode STARTUP", PROFTPD_VERSION_TEXT " " PR_STATUS, BUILD_STAMP); write_pid(); daemon_loop(); } extern char *optarg; extern int optind,opterr,optopt; #ifdef HAVE_GETOPT_LONG static struct option opts[] = { { "nocollision", 0, NULL, 'N' }, { "nodaemon", 0, NULL, 'n' }, { "quiet", 0, NULL, 'q' }, { "debug", 1, NULL, 'd' }, { "define", 1, NULL, 'D' }, { "config", 1, NULL, 'c' }, { "persistent", 1, NULL, 'p' }, { "list", 0, NULL, 'l' }, { "version", 0, NULL, 'v' }, { "settings", 0, NULL, 'V' }, { "version-status", 0, NULL, 1 }, { "configtest", 0, NULL, 't' }, { "help", 0, NULL, 'h' }, { NULL, 0, NULL, 0 } }; #endif /* HAVE_GETOPT_LONG */ static void show_settings(void) { printf("Compile-time Settings:\n"); printf(" Version: " PROFTPD_VERSION_TEXT "\n"); printf(" Platform: " PR_PLATFORM "\n"); printf(" Built With:\n configure " PR_BUILD_OPTS "\n"); printf("\n Files:\n"); printf(" Configuration File:\n"); printf(" " PR_CONFIG_FILE_PATH "\n"); printf(" Pid File:\n"); printf(" " PR_PID_FILE_PATH "\n"); printf(" Scoreboard File:\n"); printf(" " PR_RUN_DIR "/proftpd.scoreboard\n"); #ifdef PR_USE_DSO printf(" Shared Module Directory:\n"); printf(" " PR_LIBEXEC_DIR "\n"); #endif /* PR_USE_DSO */ /* Feature settings */ printf("\n Features:\n"); #ifdef PR_USE_AUTO_SHADOW printf(" + Autoshadow support\n"); #else printf(" - Autoshadow support\n"); #endif /* PR_USE_AUTO_SHADOW */ #ifdef PR_USE_CTRLS printf(" + Controls support\n"); #else printf(" - Controls support\n"); #endif /* PR_USE_CTRLS */ #ifdef PR_USE_CURSES printf(" + curses support\n"); #else printf(" - curses support\n"); #endif /* PR_USE_CURSES */ #ifdef PR_USE_DEVEL printf(" + Developer support\n"); #else printf(" - Developer support\n"); #endif /* PR_USE_DEVEL */ #ifdef PR_USE_DSO printf(" + DSO support\n"); #else printf(" - DSO support\n"); #endif /* PR_USE_DSO */ #ifdef PR_USE_IPV6 printf(" + IPv6 support\n"); #else printf(" - IPv6 support\n"); #endif /* PR_USE_IPV6 */ #ifdef PR_USE_LARGEFILES printf(" + Largefile support\n"); #else printf(" - Largefile support\n"); #endif /* PR_USE_LARGEFILES */ #ifdef PR_USE_NCURSES printf(" + ncurses support\n"); #else printf(" - ncurses support\n"); #endif /* PR_USE_NCURSES */ #ifdef PR_USE_FACL printf(" + POSIX ACL support\n"); #else printf(" - POSIX ACL support\n"); #endif /* PR_USE_FACL */ #ifdef PR_USE_SHADOW printf(" + Shadow file support\n"); #else printf(" - Shadow file suppport\n"); #endif /* PR_USE_SHADOW */ #ifdef PR_USE_SENDFILE printf(" + Sendfile support\n"); #else printf(" - Sendfile support\n"); #endif /* PR_USE_SENDFILE */ /* Tunable settings */ printf("\n Tunable Options:\n"); printf(" PR_TUNABLE_BUFFER_SIZE = %u\n", PR_TUNABLE_BUFFER_SIZE); printf(" PR_TUNABLE_GLOBBING_MAX = %u\n", PR_TUNABLE_GLOBBING_MAX); printf(" PR_TUNABLE_HASH_TABLE_SIZE = %u\n", PR_TUNABLE_HASH_TABLE_SIZE); printf(" PR_TUNABLE_NEW_POOL_SIZE = %u\n", PR_TUNABLE_NEW_POOL_SIZE); printf(" PR_TUNABLE_RCVBUFSZ = %u\n", PR_TUNABLE_RCVBUFSZ); printf(" PR_TUNABLE_SCOREBOARD_BUFFER_SIZE = %u\n", PR_TUNABLE_SCOREBOARD_BUFFER_SIZE); printf(" PR_TUNABLE_SCOREBOARD_SCRUB_TIMER = %u\n", PR_TUNABLE_SCOREBOARD_SCRUB_TIMER); printf(" PR_TUNABLE_SELECT_TIMEOUT = %u\n", PR_TUNABLE_SELECT_TIMEOUT); printf(" PR_TUNABLE_SNDBUFSZ = %u\n", PR_TUNABLE_SNDBUFSZ); printf(" PR_TUNABLE_TIMEOUTIDENT = %u\n", PR_TUNABLE_TIMEOUTIDENT); printf(" PR_TUNABLE_TIMEOUTIDLE = %u\n", PR_TUNABLE_TIMEOUTIDLE); printf(" PR_TUNABLE_TIMEOUTLINGER = %u\n", PR_TUNABLE_TIMEOUTLINGER); printf(" PR_TUNABLE_TIMEOUTLOGIN = %u\n", PR_TUNABLE_TIMEOUTLOGIN); printf(" PR_TUNABLE_TIMEOUTNOXFER = %u\n", PR_TUNABLE_TIMEOUTNOXFER); printf(" PR_TUNABLE_TIMEOUTSTALLED = %u\n", PR_TUNABLE_TIMEOUTSTALLED); printf(" PR_TUNABLE_XFER_BUFFER_SIZE = %u\n", PR_TUNABLE_XFER_BUFFER_SIZE); printf(" PR_TUNABLE_XFER_SCOREBOARD_UPDATES = %u\n\n", PR_TUNABLE_XFER_SCOREBOARD_UPDATES); } static struct option_help { const char *long_opt,*short_opt,*desc; } opts_help[] = { { "--help", "-h", "Display proftpd usage"}, { "--nocollision", "-N", "Disable address/port collision checking" }, { "--nodaemon", "-n", "Disable background daemon mode (and send all output to stderr)" }, { "--quiet", "-q", "Don't send output to stderr when running with -n or --nodaemon" }, { "--debug", "-d [level]", "Set debugging level (0-10, 10 = most debugging)" }, { "--define", "-D [definition]", "Set arbitrary IfDefine definition" }, { "--config", "-c [config-file]", "Specify alternate configuration file" }, { "--persistent", "-p [0|1]", "Enable/disable default persistent passwd support" }, { "--list", "-l", "List all compiled-in modules" }, { "--configtest", "-t", "Test the syntax of the specified config" }, { "--settings", "-V", "Print compile-time settings and exit" }, { "--version", "-v", "Print version number and exit" }, { "--version-status", "-vv", "Print extended version information and exit" }, { NULL, NULL, NULL } }; static void show_usage(int exit_code) { struct option_help *h; printf("usage: proftpd [options]\n"); for (h = opts_help; h->long_opt; h++) { #ifdef HAVE_GETOPT_LONG printf(" %s, %s\n ", h->short_opt, h->long_opt); #else /* HAVE_GETOPT_LONG */ printf(" %s\n", h->short_opt); #endif /* HAVE_GETOPT_LONG */ printf(" %s\n", h->desc); } exit(exit_code); } int main(int argc, char *argv[], char **envp) { int optc, show_version = 0; const char *cmdopts = "D:NVc:d:hlnp:qtv"; mode_t *main_umask = NULL; socklen_t peerlen; struct sockaddr peer; #ifdef DEBUG_MEMORY int logfd; extern int EF_PROTECT_BELOW; extern int EF_PROTECT_FREE; extern int EF_ALIGNMENT; EF_PROTECT_BELOW = 1;/* */ EF_PROTECT_FREE = 1; /* */ EF_ALIGNMENT = 0; /* */ /* Redirect stderr to somewhere appropriate. * Ideally, this would be syslog, but alas... */ if ((logfd = open(PR_RUN_DIR "/proftpd-memory.log", O_WRONLY | O_CREAT | O_APPEND, 0644))< 0) { pr_log_pri(PR_LOG_ERR, "Error opening error logfile: %s", strerror(errno)); exit(1); } close(fileno(stderr)); if (dup2(logfd, fileno(stderr)) == -1) { pr_log_pri(PR_LOG_ERR, "Error converting standard error to a logfile: %s", strerror(errno)); exit(1); } close(logfd); #endif /* DEBUG_MEMORY */ #ifdef HAVE_SET_AUTH_PARAMETERS (void) set_auth_parameters(argc, argv); #endif #ifdef HAVE_TZSET /* Preserve timezone information in jailed environments. */ tzset(); #endif memset(&session, 0, sizeof(session)); /* Initialize stuff for set_proc_title. */ init_proc_title(argc, argv, envp); /* Seed rand */ srand(time(NULL)); /* getpeername() fails if the fd isn't a socket */ peerlen = sizeof(peer); memset(&peer, 0, peerlen); if (getpeername(fileno(stdin), &peer, &peerlen) != -1) log_stderr(FALSE); /* Open the syslog */ log_opensyslog(NULL); /* Initialize the memory subsystem here */ init_pools(); /* Command line options supported: * * -D parameter set run-time configuration parameter * --define parameter * -V * --settings report compile-time settings * -c path set the configuration path * --config path * -d n set the debug level * --debug n * -q quiet mode; don't log to stderr when not daemonized * --quiet * -N disable address/port collision checks * --nocollision * -n standalone server does not daemonize, all logging * --nodaemon redirected to stderr * -t syntax check of the configuration file * --configtest * -v report version number * --version */ opterr = 0; while ((optc = #ifdef HAVE_GETOPT_LONG getopt_long(argc, argv, cmdopts, opts, NULL) #else /* HAVE_GETOPT_LONG */ getopt(argc, argv, cmdopts) #endif /* HAVE_GETOPT_LONG */ ) != -1) { switch (optc) { case 'D': if (!optarg) { pr_log_pri(PR_LOG_ERR, "Fatal: -D requires definition argument"); exit(1); } /* If this is the first time through, allocate an array_header * for these command-line definitions. */ if (!server_defines) server_defines = make_array(permanent_pool, 0, sizeof(char *)); *((char **) push_array(server_defines)) = pstrdup(permanent_pool, optarg); break; case 'V': show_settings(); exit(0); break; case 'N': AddressCollisionCheck = FALSE; break; case 'n': nodaemon++; break; case 'q': quiet++; break; case 'd': if (!optarg) { pr_log_pri(PR_LOG_ERR, "Fatal: -d requires debugging level argument."); exit(1); } pr_log_setdebuglevel(atoi(optarg)); break; case 'c': if (!optarg) { pr_log_pri(PR_LOG_ERR, "Fatal: -c requires configuration path argument."); exit(1); } /* Note: we delay sanity-checking the given path until after the FSIO * layer has been initialized. */ config_filename = strdup(optarg); break; case 'l': modules_list(); exit(0); break; case 't': syntax_check = 1; printf("Checking syntax of configuration file\n"); fflush(stdout); break; case 'p': { if (!optarg || ((persistent_passwd = atoi(optarg)) != 1 && persistent_passwd != 0)) { pr_log_pri(PR_LOG_ERR, "Fatal: -p requires boolean (0|1) argument."); exit(1); } break; } case 'v': show_version++; break; case 1: show_version = 2; break; case 'h': show_usage(0); case '?': pr_log_pri(PR_LOG_ERR, "unknown option: %c", (char)optopt); show_usage(1); } } /* If we have any leftover parameters, it's an error. */ if (argv[optind]) { pr_log_pri(PR_LOG_ERR, "unknown parameter: '%s'", argv[optind]); exit(1); } if (show_version) { if (show_version == 1) pr_log_pri(PR_LOG_NOTICE, "ProFTPD Version " PROFTPD_VERSION_TEXT); else { register unsigned int i; pr_log_pri(PR_LOG_NOTICE, "ProFTPD Version: %s", PROFTPD_VERSION_TEXT " " PR_STATUS); pr_log_pri(PR_LOG_NOTICE, " Scoreboard Version: %08x", PR_SCOREBOARD_VERSION); pr_log_pri(PR_LOG_NOTICE, " Built: %s", BUILD_STAMP); for (i = 0; static_modules[i]; i++) { char buf[256]; char *desc = static_modules[i]->module_version; if (!desc) { memset(buf, '\0', sizeof(buf)); snprintf(buf, sizeof(buf), "mod_%s.c", static_modules[i]->name); buf[sizeof(buf)-1] = '\0'; desc = buf; } pr_log_pri(PR_LOG_NOTICE, " Module: %s", desc); } } exit(0); } /* Initialize sub-systems */ init_pools(); init_regexp(); init_log(); init_inet(); init_netio(); init_fs(); init_class(); free_bindings(); init_config(); init_stash(); #ifdef PR_USE_CTRLS init_ctrls(); #endif /* PR_USE_CTRLS */ var_init(); modules_init(); /* Now, once the modules have had a chance to initialize themselves * but before the configuration stream is actually parsed, check * that the given configuration path is valid. */ if (pr_fs_valid_path(config_filename) < 0) { pr_log_pri(PR_LOG_ERR, "Fatal: -c requires an absolute path"); exit(1); } pr_parser_prepare(NULL, NULL); pr_event_generate("core.preparse", NULL); if (pr_parser_parse_file(NULL, config_filename, NULL, 0) == -1) { pr_log_pri(PR_LOG_ERR, "Fatal: unable to read configuration file '%s': %s", config_filename, strerror(errno)); exit(1); } if (pr_parser_cleanup() < 0) { pr_log_pri(PR_LOG_ERR, "Fatal: error processing configuration file '%s': " "unclosed configuration section", config_filename); exit(1); } if (fixup_servers(server_list) < 0) { pr_log_pri(PR_LOG_ERR, "Fatal: error processing configuration file '%s'", config_filename); exit(1); } pr_event_generate("core.postparse", NULL); /* We're only doing a syntax check of the configuration file. */ if (syntax_check) { printf("Syntax check complete.\n"); end_login(0); } /* After configuration is complete, make sure that passwd, group * aren't held open (unnecessary fds for master daemon) */ endpwent(); endgrent(); /* Security */ { uid_t *uid = (uid_t *) get_param_ptr(main_server->conf, "UserID", FALSE); gid_t *gid = (gid_t *) get_param_ptr(main_server->conf, "GroupID", FALSE); if (uid) daemon_uid = *uid; else daemon_uid = PR_ROOT_UID; if (gid) daemon_gid = *gid; else daemon_gid = PR_ROOT_GID; } if (daemon_uid != PR_ROOT_UID) { /* Allocate space for daemon supplemental groups. */ daemon_gids = make_array(permanent_pool, 2, sizeof(gid_t)); if (pr_auth_getgroups(permanent_pool, (const char *) get_param_ptr( main_server->conf, "UserName", FALSE), &daemon_gids, NULL) < 0) pr_log_debug(DEBUG2, "unable to retrieve daemon supplemental groups"); if (set_groups(permanent_pool, daemon_gid, daemon_gids) < 0) pr_log_pri(PR_LOG_ERR, "unable to set daemon groups: %s", strerror(errno)); } if ((main_umask = (mode_t *) get_param_ptr(main_server->conf, "Umask", FALSE)) == NULL) umask((mode_t) 0022); else umask(*main_umask); /* Give up root and save our uid/gid for later use (if supported) * If we aren't currently root, PRIVS_SETUP will get rid of setuid * granted root and prevent further uid switching from being attempted. */ PRIVS_SETUP(daemon_uid, daemon_gid) #ifndef PR_DEVEL_COREDUMP /* Test to make sure that our uid/gid is correct. Try to do this in * a portable fashion *gah!* */ if (geteuid() != daemon_uid) { pr_log_pri(PR_LOG_ERR, "unable to set uid to %lu, current uid: %lu", (unsigned long)daemon_uid,(unsigned long)geteuid()); exit(1); } if (getegid() != daemon_gid) { pr_log_pri(PR_LOG_ERR, "unable to set gid to %lu, current gid: %lu", (unsigned long)daemon_gid,(unsigned long)getegid()); exit(1); } #endif /* PR_DEVEL_COREDUMP */ /* Install signal handlers */ install_signal_handlers(); #ifndef PR_DEVEL_NO_DAEMON set_daemon_rlimits(); #endif /* PR_DEVEL_NO_DAEMON */ switch (ServerType) { case SERVER_STANDALONE: standalone_main(); break; case SERVER_INETD: inetd_main(); break; } #ifdef PR_DEVEL_NO_DAEMON PRIVS_ROOT chdir(PR_RUN_DIR); #endif /* PR_DEVEL_NO_DAEMON */ return 0; }