int pr_memcache_conn_set_namespace(pr_memcache_t *mcache, module *m, const char *prefix) { if (mcache == NULL || m == NULL) { errno = EINVAL; return -1; } if (mcache->namespace_tab == NULL) { pr_table_t *tab; tab = pr_table_alloc(mcache->pool, 0); if (pr_table_ctl(tab, PR_TABLE_CTL_SET_KEY_CMP, modptr_cmp_cb) < 0) { pr_trace_msg(trace_channel, 4, "error setting key comparison callback for namespace table: %s", strerror(errno)); } if (pr_table_ctl(tab, PR_TABLE_CTL_SET_KEY_HASH, modptr_hash_cb) < 0) { pr_trace_msg(trace_channel, 4, "error setting key hash callback for namespace table: %s", strerror(errno)); } mcache->namespace_tab = tab; } if (prefix != NULL) { int count; size_t prefix_len; prefix_len = strlen(prefix); count = pr_table_kexists(mcache->namespace_tab, m, sizeof(module *)); if (count <= 0) { if (pr_table_kadd(mcache->namespace_tab, m, sizeof(module *), pstrndup(mcache->pool, prefix, prefix_len), prefix_len) < 0) { pr_trace_msg(trace_channel, 7, "error adding namespace prefix '%s' for module 'mod_%s.c': %s", prefix, m->name, strerror(errno)); } } else { if (pr_table_kset(mcache->namespace_tab, m, sizeof(module *), pstrndup(mcache->pool, prefix, prefix_len), prefix_len) < 0) { pr_trace_msg(trace_channel, 7, "error setting namespace prefix '%s' for module 'mod_%s.c': %s", prefix, m->name, strerror(errno)); } } } else { /* A NULL prefix means the caller is removing their namespace maping. */ (void) pr_table_kremove(mcache->namespace_tab, m, sizeof(module *), NULL); } return 0; }
static char *get_config_word(pool *p, char *word) { size_t wordlen; /* Should this word be replaced with a value from the environment? * If so, tmp will contain the expanded value, otherwise tmp will * contain a string duped from the given pool. */ wordlen = strlen(word); if (wordlen > 7) { char *ptr = NULL; /* Does the given word use the environment syntax? We handle this in a * while loop in order to handle a) multiple different variables, and b) * cases where the substituted value is itself a variable. Hopefully no * one is so clever as to want to actually _use_ the latter approach. */ ptr = strstr(word, "%{env:"); while (ptr != NULL) { char *env, *key, *ptr2, *var; unsigned int keylen; pr_signals_handle(); ptr2 = strchr(ptr + 6, '}'); if (ptr2 == NULL) { /* No terminating marker; continue on to the next potential * variable in the word. */ ptr2 = ptr + 6; ptr = strstr(ptr2, "%{env:"); continue; } keylen = (ptr2 - ptr - 6); var = pstrndup(p, ptr, (ptr2 - ptr) + 1); key = pstrndup(p, ptr + 6, keylen); env = pr_env_get(p, key); if (env == NULL) { /* No value in the environment; continue on to the next potential * variable in the word. */ ptr = strstr(ptr2, "%{env:"); continue; } word = (char *) sreplace(p, word, var, env, NULL); ptr = strstr(word, "%{env:"); } } return pstrdup(p, word); }
/* Many FTP servers (e.g. IIS) use the semicolon delimiter syntax, as used * for listing the MLSD/MLST facts, for other FEAT values (e.g. AUTH, PROT, * etc). * * NOTE: Should this return a table rather than an array, for easier lookup * of parsed values by callers? */ static int parse_feat(pool *p, const char *feat, array_header **res) { char *ptr, *ptr2 = NULL; array_header *vals; size_t len; vals = make_array(p, 1, sizeof(char *)); /* No semicolons in this value? No work to do...*/ ptr = strchr(feat, ';'); if (ptr == NULL) { *((char **) push_array(vals)) = pstrdup(p, feat); *res = vals; return vals->nelts; } len = ptr - feat; if (len > 0) { *((char **) push_array(vals)) = pstrndup(p, feat, len); } /* Watch for any sequences of just semicolons. */ while (*ptr == ';') { pr_signals_handle(); ptr++; } ptr2 = strchr(ptr, ';'); while (ptr2 != NULL) { pr_signals_handle(); len = ptr2 - ptr; if (len > 0) { *((char **) push_array(vals)) = pstrndup(p, ptr, len); } ptr = ptr2; while (*ptr == ';') { pr_signals_handle(); ptr++; } ptr2 = strchr(ptr, ';'); } /* Since the semicolon delimiter syntax uses a trailing semicolon, * we shouldn't need to worry about something like "...;FOO". Right? */ *res = vals; return vals->nelts; }
static const char *generate_secret(pool *p) { int res; unsigned char encoded[18], rnd[32]; size_t encoded_len; const char *secret; const unsigned char *ptr; if (RAND_bytes(rnd, sizeof(rnd)) != 1) { fprintf(stderr, "Error obtaining %lu bytes of random data:\n", (unsigned long) sizeof(rnd)); ERR_print_errors_fp(stderr); errno = EPERM; return NULL; } encoded_len = sizeof(encoded); ptr = encoded; res = auth_otp_base32_encode(p, rnd, sizeof(rnd), &ptr, &encoded_len); if (res < 0) { return NULL; } secret = pstrndup(p, (const char *) ptr, encoded_len); return secret; }
/************************************************************************ ** definstall - Install a new definition. id is in Reuse_1. ** p_text : ptr to the definition ** n : number of bytes in the definition (may contain embedded nulls) ** number : number of formals ************************************************************************/ void definstall(ptext_t p_text, int n, int number) { pdefn_t p; if(n == 0) { p_text = NULL; } if( strcmp (Reuse_1, "defined") == 0) { Msg_Temp = GET_MSG (4112); SET_MSG (Msg_Text, Msg_Temp, Reuse_1, "#define"); warning(4112);/* name reserved */ return; } if((p = get_defined()) != 0) { if(PRE_DEFINED(p)) { Msg_Temp = GET_MSG (4112); SET_MSG (Msg_Text, Msg_Temp, Reuse_1, "#define"); warning(4112);/* name reserved */ return; } else { if(redefn(p_text, DEFN_TEXT(p), n)) { Msg_Temp = GET_MSG (4005); SET_MSG (Msg_Text, Msg_Temp, Reuse_1); warning(4005);/* redefinition */ } else { return; } } } else { hln_t ident; HLN_NAME(ident) = Reuse_1; HLN_HASH(ident) = Reuse_1_hash; HLN_LENGTH(ident) = (UCHAR)Reuse_1_length; p = malloc(sizeof(defn_t)); if (p == NULL) { Msg_Temp = GET_MSG (1002); SET_MSG (Msg_Text, Msg_Temp); error(1002); return; } DEFN_IDENT(p) = HLN_TO_NAME(&ident); DEFN_NEXT(p) = Defn_level_0[Reuse_1_hash & LEVEL_0]; DEFN_TEXT(p) = (char*)NULL; DEFN_EXPANDING(p) = 0; Defn_level_0[Reuse_1_hash & LEVEL_0] = p; } if(n != 0) { DEFN_TEXT(p) = pstrndup(p_text, n); if(number == FROM_COMMAND) { /* special case from cmd line */ *(DEFN_TEXT(p) + n - 1) = EOS_DEFINITION; /* for handle_eos */ } } DEFN_NFORMALS(p) = (char)((number != FROM_COMMAND) ? number : 0); }
static int af_check_parent_dir(pool *p, const char *name, const char *path) { struct stat st; int res; char *dir_path, *ptr = NULL; ptr = strrchr(path, '/'); if (ptr != path) { dir_path = pstrndup(p, path, ptr - path); } else { dir_path = "/"; } res = stat(dir_path, &st); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to stat %s directory '%s': %s", name, dir_path, strerror(xerrno)); errno = xerrno; return -1; } if (st.st_mode & S_IWOTH) { int xerrno = EPERM; pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to use %s from world-writable directory '%s' (perms %04o): %s", name, dir_path, st.st_mode & ~S_IFMT, strerror(xerrno)); errno = xerrno; return -1; } return 0; }
void xtree_nput(xtree_t xt, void *value, int vlen, const char *key, int len) { node_t node, *pnode; if (xt == NULL || key == NULL || len == 0) return; if ((node = _xtree_node_find(xt, &pnode, key, len)) != NULL) { node->value = value; node->vlen = vlen; return; } if (value != NULL) { *pnode = node = (node_t) pmalloc(xt->p, sizeof(node_st)); node->key = pstrndup(xt->p, key, len); node->value = value; node->vlen = vlen; node->left = NULL; node->right = NULL; } }
static int parse_wildcard_config_path(pool *p, const char *path, unsigned int depth) { register unsigned int i; int res, xerrno; pool *tmp_pool; array_header *globbed_dirs = NULL; const char *component = NULL, *parent_path = NULL, *suffix_path = NULL; struct stat st; size_t path_len, component_len; char *name_pattern = NULL; void *dirh = NULL; struct dirent *dent = NULL; if (depth > PR_PARSER_INCLUDE_MAX_DEPTH) { pr_log_pri(PR_LOG_WARNING, "error: resolving wildcard pattern in '%s' " "exceeded maximum filesystem depth (%u)", path, (unsigned int) PR_PARSER_INCLUDE_MAX_DEPTH); errno = EINVAL; return -1; } path_len = strlen(path); if (path_len < 2) { pr_trace_msg(trace_channel, 7, "path '%s' too short to be wildcard path", path); /* The first character must be a slash, and we need at least one more * character in the path as a glob character. */ errno = EINVAL; return -1; } tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Include sub-pool"); /* We need to find the first component of the path which contains glob * characters. We then use the path up to the previous component as the * parent directory to open, and the glob-bearing component as the filter * for directories within the parent. */ component = path + 1; while (TRUE) { int last_component = FALSE; char *ptr; pr_signals_handle(); ptr = strchr(component, '/'); if (ptr != NULL) { component_len = ptr - component; } else { component_len = strlen(component); last_component = TRUE; } if (memchr(component, (int) '*', component_len) != NULL || memchr(component, (int) '?', component_len) != NULL || memchr(component, (int) '[', component_len) != NULL) { name_pattern = pstrndup(tmp_pool, component, component_len); if (parent_path == NULL) { parent_path = pstrndup(tmp_pool, "/", 1); } if (ptr != NULL) { suffix_path = pstrdup(tmp_pool, ptr + 1); } break; } if (parent_path != NULL) { parent_path = pdircat(tmp_pool, parent_path, pstrndup(tmp_pool, component, component_len), NULL); } else { parent_path = pstrndup(tmp_pool, "/", 1); } if (last_component) { break; } component = ptr + 1; } if (name_pattern == NULL) { pr_trace_msg(trace_channel, 4, "unable to process invalid, non-globbed path '%s'", path); errno = ENOENT; return -1; } pr_fs_clear_cache2(parent_path); if (pr_fsio_lstat(parent_path, &st) < 0) { xerrno = errno; pr_log_pri(PR_LOG_WARNING, "error: failed to check configuration path '%s': %s", parent_path, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (S_ISLNK(st.st_mode) && !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) { pr_log_pri(PR_LOG_WARNING, "error: cannot read configuration path '%s': Symbolic link", parent_path); destroy_pool(tmp_pool); errno = ENOTDIR; return -1; } pr_log_pri(PR_LOG_DEBUG, "processing configuration directory '%s' using pattern '%s', suffix '%s'", parent_path, name_pattern, suffix_path); dirh = pr_fsio_opendir(parent_path); if (dirh == NULL) { pr_log_pri(PR_LOG_WARNING, "error: unable to open configuration directory '%s': %s", parent_path, strerror(errno)); destroy_pool(tmp_pool); errno = EINVAL; return -1; } globbed_dirs = make_array(tmp_pool, 0, sizeof(char *)); while ((dent = pr_fsio_readdir(dirh)) != NULL) { pr_signals_handle(); if (strncmp(dent->d_name, ".", 2) == 0 || strncmp(dent->d_name, "..", 3) == 0) { continue; } if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) { if (is_tmp_file(dent->d_name) == TRUE) { pr_trace_msg(trace_channel, 19, "ignoring temporary file '%s' found in directory '%s'", dent->d_name, parent_path); continue; } } if (pr_fnmatch(name_pattern, dent->d_name, PR_FNM_PERIOD) == 0) { pr_trace_msg(trace_channel, 17, "matched '%s' path with wildcard pattern '%s'", dent->d_name, name_pattern); *((char **) push_array(globbed_dirs)) = pdircat(tmp_pool, parent_path, dent->d_name, suffix_path, NULL); } } pr_fsio_closedir(dirh); if (globbed_dirs->nelts == 0) { pr_log_pri(PR_LOG_WARNING, "error: no matches found for wildcard directory '%s'", path); destroy_pool(tmp_pool); errno = ENOENT; return -1; } depth++; qsort((void *) globbed_dirs->elts, globbed_dirs->nelts, sizeof(char *), config_filename_cmp); for (i = 0; i < globbed_dirs->nelts; i++) { const char *globbed_dir; globbed_dir = ((const char **) globbed_dirs->elts)[i]; res = parse_config_path2(p, globbed_dir, depth); if (res < 0) { xerrno = errno; pr_trace_msg(trace_channel, 7, "error parsing wildcard path '%s': %s", globbed_dir, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } } destroy_pool(tmp_pool); return 0; }
static char *get_config_word(pool *p, char *word) { size_t wordlen; /* Should this word be replaced with a value from the environment? * If so, tmp will contain the expanded value, otherwise tmp will * contain a string duped from the given pool. */ wordlen = strlen(word); if (wordlen > 7) { char *ptr = NULL; /* Does the given word use the environment syntax? * * In the simple (and most common) case, the entire word is the variable * syntax. But we also need to check for cases where the environment * variable syntax is embedded within the word string. */ if (strncmp(word, "%{env:", 6) == 0 && word[wordlen-1] == '}') { char *env; word[wordlen-1] = '\0'; env = pr_env_get(p, word + 6); return env ? pstrdup(p, env) : ""; } /* This is in a while loop in order to handle a) multiple different * variables, and b) cases where the substituted value is itself a * variable. (Hopefully no one is so clever as to want to actually * _use_ the latter approach.) */ ptr = strstr(word, "%{env:"); while (ptr != NULL) { char *env, *key, *ptr2, *var; unsigned int keylen; pr_signals_handle(); ptr2 = strchr(ptr + 6, '}'); if (ptr2 == NULL) { /* No terminating marker; continue on to the next potential * variable in the word. */ ptr2 = ptr + 6; ptr = strstr(ptr2, "%{env:"); continue; } keylen = (ptr2 - ptr - 6); var = pstrndup(p, ptr, (ptr2 - ptr) + 1); key = pstrndup(p, ptr + 6, keylen); env = pr_env_get(p, key); if (env == NULL) { /* No value in the environment; continue on to the next potential * variable in the word. */ ptr = strstr(ptr2, "%{env:"); continue; } word = (char *) sreplace(p, word, var, env, NULL); ptr = strstr(word, "%{env:"); } } return pstrdup(p, word); }
static int counter_sess_init(void) { config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "CounterEngine", FALSE); if (c != NULL) { counter_engine = *((int *) c->argv[0]); } if (counter_engine == FALSE) { return 0; } c = find_config(main_server->conf, CONF_PARAM, "CounterLog", FALSE); if (c != NULL) { const char *path = c->argv[0]; if (strcasecmp(path, "none") != 0) { int res, xerrno; PRIVS_ROOT res = pr_log_openfile(path, &counter_logfd, 0660); xerrno = errno; PRIVS_RELINQUISH; if (res < 0) { pr_log_debug(DEBUG2, MOD_COUNTER_VERSION ": error opening CounterLog '%s': %s", path, strerror(xerrno)); counter_logfd = -1; } } } /* Find all CounterFile directives for this vhost, and make sure they * have open handles. We need to do this here, and not in a POST_CMD * PASS handler because of the need to open handles that may be outside * of a chroot. */ c = find_config(main_server->conf, CONF_PARAM, "CounterFile", TRUE); while (c != NULL) { int xerrno = 0; const char *area = NULL, *path; pr_fh_t *fh; struct counter_fh *cfh; pr_signals_handle(); path = c->argv[0]; if (c->parent != NULL) { if (c->parent->config_type == CONF_ANON || c->parent->config_type == CONF_DIR) { area = c->parent->name; } else { (void) pr_log_writefile(counter_logfd, MOD_COUNTER_VERSION, "unhandled configuration parent type (%d) for CounterFile, skipping", c->parent->config_type); c = find_config_next(c, c->next, CONF_PARAM, "CounterFile", TRUE); continue; } } else { /* Toplevel CounterFile directive, in "server config" or <VirtualHost> * sections. */ area = "/"; } PRIVS_ROOT fh = pr_fsio_open(path, O_RDWR|O_CREAT); xerrno = errno; PRIVS_RELINQUISH if (fh == NULL) { pr_log_debug(DEBUG1, MOD_COUNTER_VERSION ": error opening CounterFile '%s': %s", path, strerror(xerrno)); counter_engine = FALSE; if (counter_fhs != NULL) { for (cfh = (struct counter_fh *) counter_fhs->xas_list; cfh; cfh = cfh->next) { (void) pr_fsio_close(cfh->fh); } } return 0; } (void) pr_log_writefile(counter_logfd, MOD_COUNTER_VERSION, "opened CounterFile '%s'", path); if (counter_fhs == NULL) { counter_fhs = xaset_create(counter_pool, NULL); } cfh = pcalloc(counter_pool, sizeof(struct counter_fh)); /* Ignore any trailing slash. */ cfh->arealen = strlen(area); if (cfh->arealen > 1 && area[cfh->arealen-1] == '/') { cfh->arealen--; } cfh->area = pstrndup(counter_pool, area, cfh->arealen); /* Mark any areas that use glob(3) characters. */ if (strpbrk(cfh->area, "[*?") != NULL) { cfh->isglob = TRUE; } cfh->fh = fh; xaset_insert(counter_fhs, (xasetmember_t *) cfh); c = find_config_next(c, c->next, CONF_PARAM, "CounterFile", TRUE); } if (counter_fhs == NULL) { (void) pr_log_writefile(counter_logfd, MOD_COUNTER_VERSION, "no CounterFiles configured, disabling module"); counter_engine = FALSE; return 0; } pr_event_register(&counter_module, "core.exit", counter_exit_ev, NULL); /* If mod_vroot is present, we need to do a little more magic to counter * the mod_vroot magic. */ if (pr_module_exists("mod_vroot.c") == TRUE) { pr_event_register(&counter_module, "core.chroot", counter_chroot_ev, NULL); } return 0; }
int proxy_ftp_sess_get_feat(pool *p, struct proxy_session *proxy_sess) { pool *tmp_pool; int res, xerrno = 0; cmd_rec *cmd; pr_response_t *resp; unsigned int resp_nlines = 0; char *feats, *token; size_t token_len = 0; tmp_pool = make_sub_pool(p); cmd = pr_cmd_alloc(tmp_pool, 1, C_FEAT); res = proxy_ftp_ctrl_send_cmd(tmp_pool, proxy_sess->backend_ctrl_conn, cmd); if (res < 0) { xerrno = errno; pr_trace_msg(trace_channel, 4, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } resp = proxy_ftp_ctrl_recv_resp(tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines); if (resp == NULL) { xerrno = errno; pr_trace_msg(trace_channel, 4, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (resp->num[0] != '2') { pr_trace_msg(trace_channel, 4, "received unexpected %s response code %s from backend", (char *) cmd->argv[0], resp->num); /* Note: If the UseProxyProtocol ProxyOption is enabled, AND if the * response message mentions a "PROXY" command, we might read an * error response here that is NOT actually for the FEAT command we just * sent. * * A backend FTP server which does not understand the PROXY protocol * will treat it as a normal FTP command, and respond. And that will * put us, the client, out of lockstep with the server, for how do we know * that we need to read that error response FIRST, then send another * command? */ destroy_pool(tmp_pool); errno = EPERM; return -1; } proxy_sess->backend_features = pr_table_nalloc(p, 0, 4); feats = resp->msg; token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len); while (token != NULL) { pr_signals_handle(); if (token_len > 0) { /* The FEAT response lines in which we are interested all start with * a single space, per RFC spec. Ignore any other lines. */ if (token[0] == ' ') { char *key, *val, *ptr; /* Find the next space in the string, to delimit our key/value pairs. */ ptr = strchr(token + 1, ' '); if (ptr != NULL) { key = pstrndup(p, token + 1, ptr - token - 1); val = pstrdup(p, ptr + 1); } else { key = pstrdup(p, token + 1); val = pstrdup(p, ""); } pr_table_add(proxy_sess->backend_features, key, val, 0); } } feats = token + token_len + 1; token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len); } destroy_pool(tmp_pool); return 0; }
static int forward_cmd_parse_dst(pool *p, const char *arg, char **name, struct proxy_conn **pconn) { const char *default_proto = NULL, *default_port = NULL, *proto = NULL, *port, *uri = NULL; char *host = NULL, *hostport = NULL, *host_ptr = NULL, *port_ptr = NULL; /* TODO: Revisit theses default once we start supporting other protocols. */ default_proto = "ftp"; default_port = "21"; /* First, look for the optional port. */ port_ptr = strrchr(arg, ':'); if (port_ptr == NULL) { port = default_port; } else { char *tmp2 = NULL; long num; num = strtol(port_ptr+1, &tmp2, 10); if (tmp2 && *tmp2) { /* Trailing garbage found in port number. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "malformed port number '%s' found in USER '%s', rejecting", port_ptr+1, arg); errno = EINVAL; return -1; } if (num < 0 || num > 65535) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid port number %ld found in USER '%s', rejecting", num, arg); errno = EINVAL; return -1; } port = pstrdup(p, port_ptr + 1); } /* Find the required '@' delimiter. */ host_ptr = strrchr(arg, '@'); if (host_ptr == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "missing required '@' delimiter in USER '%s', rejecting", arg); errno = EINVAL; return -1; } if (port_ptr == NULL) { host = pstrdup(p, host_ptr + 1); } else { host = pstrndup(p, host_ptr + 1, (port_ptr - host_ptr - 1)); } *name = pstrndup(p, arg, (host_ptr - arg)); proto = default_proto; hostport = pstrcat(p, host, ":", port, NULL); if (forward_dst_filter(p, hostport) < 0) { return -1; } uri = pstrcat(p, proto, "://", hostport, NULL); /* Note: We deliberately use proxy_pool, rather than the given pool, here * so that the created structure (especially the pr_netaddr_t) are * longer-lived. */ *pconn = proxy_conn_create(proxy_pool, uri); if (*pconn == NULL) { int xerrno = errno; pr_trace_msg(trace_channel, 1, "error handling URI '%.100s': %s", uri, strerror(xerrno)); errno = xerrno; return -1; } return 0; }
static char *uri_parse_host(pool *p, const char *orig_uri, const char *uri, char **remaining) { char *host = NULL, *ptr = NULL; /* We have either of: * * host<:...> * [host]<:...> * * Look for an opening square bracket, to see if we have an IPv6 address * in the URI. */ if (uri[0] == '[') { ptr = strchr(uri + 1, ']'); if (ptr == NULL) { /* If there is no ']', then it's a badly-formatted URI. */ pr_trace_msg(trace_channel, 4, "badly formatted IPv6 address in host info '%.100s'", orig_uri); errno = EINVAL; return NULL; } host = pstrndup(p, uri + 1, ptr - uri - 1); if (remaining != NULL) { size_t urilen; urilen = strlen(ptr); if (urilen > 0) { *remaining = ptr + 1; } else { *remaining = NULL; } } pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host, orig_uri); return host; } ptr = strchr(uri + 1, ':'); if (ptr == NULL) { if (remaining != NULL) { *remaining = NULL; } host = pstrdup(p, uri); pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host, orig_uri); return host; } if (remaining != NULL) { *remaining = ptr; } host = pstrndup(p, uri, ptr - uri); pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host, orig_uri); return host; }
int proxy_uri_parse(pool *p, const char *uri, char **scheme, char **host, unsigned int *port, char **username, char **password) { char *ptr, *ptr2; size_t idx, len; if (uri == NULL || scheme == NULL || host == NULL || port == NULL) { errno = EINVAL; return -1; } /* First, look for a ':' */ ptr = strchr(uri, ':'); if (ptr == NULL) { pr_trace_msg(trace_channel, 4, "missing colon in URI '%.100s'", uri); errno = EINVAL; return -1; } len = (ptr - uri); *scheme = pstrndup(p, uri, len); idx = strspn(*scheme, "abcdefghijklmnopqrstuvwxyz+.-"); if (idx < len && (*scheme)[idx] != '\0') { /* Invalid character in the scheme string, according to RFC 1738 rules. */ pr_trace_msg(trace_channel, 4, "invalid character (%c) at index %lu in scheme '%.100s'", (*scheme)[idx], (unsigned long) idx, *scheme); errno = EINVAL; return -1; } /* The double-slashes must immediately follow the colon. */ if (*(ptr + 1) != '/' || *(ptr + 2) != '/') { pr_trace_msg(trace_channel, 4, "missing required '//' following colon in URI '%.100s'", uri); errno = EINVAL; return -1; } ptr += 3; if (*ptr == '\0') { /* The given URL looked like "scheme://". */ pr_trace_msg(trace_channel, 4, "missing required authority following '//' in URI '%.100s'", uri); errno = EINVAL; return -1; } /* Possible URIs at this point: * * scheme://host:port/path/... * scheme://host:port/ * scheme://host:port * scheme://host * scheme://username:password@host... * * And, in the case where 'host' is an IPv6 address: * * scheme://[host]:port/path/... * scheme://[host]:port/ * scheme://[host]:port * scheme://[host] * scheme://username:password@[host]... */ /* We explicitly do NOT support URL-encoded characters in the URIs we * will handle. */ ptr2 = strchr(ptr, '%'); if (ptr2 != NULL) { pr_trace_msg(trace_channel, 4, "invalid character (%%) at index %ld in scheme-specific info '%.100s'", (long) (ptr2 - ptr), ptr); errno = EINVAL; return -1; } ptr = uri_parse_userinfo(p, uri, ptr, username, password); ptr2 = strchr(ptr, ':'); if (ptr2 == NULL) { *host = uri_parse_host(p, uri, ptr, NULL); if (strncmp(*scheme, "ftp", 4) == 0 || strncmp(*scheme, "ftps", 5) == 0) { *port = 21; } else if (strncmp(*scheme, "sftp", 5) == 0) { *port = 22; } else { pr_trace_msg(trace_channel, 4, "unable to determine port for scheme '%.100s'", *scheme); errno = EINVAL; return -1; } } else { *host = uri_parse_host(p, uri, ptr, &ptr2); } /* Optional port field present? */ if (ptr2 != NULL) { ptr2 = strchr(ptr2, ':'); } if (ptr2 == NULL) { /* XXX How to configure "implicit" FTPS, if at all? */ if (strncmp(*scheme, "ftp", 4) == 0 || strncmp(*scheme, "ftps", 5) == 0) { *port = 21; } else if (strncmp(*scheme, "sftp", 5) == 0) { *port = 22; } else { pr_trace_msg(trace_channel, 4, "unable to determine port for scheme '%.100s'", *scheme); errno = EINVAL; return -1; } } else { register unsigned int i; char *ptr3, *portspec; size_t portspeclen; /* Look for any possible trailing '/'. */ ptr3 = strchr(ptr2, '/'); if (ptr3 == NULL) { portspec = ptr2 + 1; portspeclen = strlen(portspec); } else { portspeclen = ptr3 - (ptr2 + 1); portspec = pstrndup(p, ptr2 + 1, portspeclen); } /* Ensure that only numeric characters appear in the portspec. */ for (i = 0; i < portspeclen; i++) { if (isdigit((int) portspec[i]) == 0) { pr_trace_msg(trace_channel, 4, "invalid character (%c) at index %d in port specification '%.100s'", portspec[i], i, portspec); errno = EINVAL; return -1; } } /* The above check will rule out any negative numbers, since it will * reject the minus character. Thus we only need to check for a zero * port, or a number that's outside the 1-65535 range. */ *port = atoi(portspec); if (*port == 0 || *port >= 65536) { pr_trace_msg(trace_channel, 4, "port specification '%.100s' yields invalid port number %d", portspec, *port); errno = EINVAL; return -1; } } return 0; }
/* Determine whether "username:password@" are present. If so, then parse it * out, and return a pointer to the portion of the URI after the parsed-out * userinfo. */ static char *uri_parse_userinfo(pool *p, const char *orig_uri, const char *uri, char **username, char **password) { char *ptr, *ptr2, *rem_uri = NULL, *userinfo, *user = NULL, *passwd = NULL; /* We have either: * * host<:...> * [host]<:...> * * thus no user info, OR: * * username:password@host... * username:password@[host]... * username:@host... * username:pass@word@host... * [email protected]:pass@word@host... * * all of which have at least one occurrence of the '@' character. */ ptr = strchr(uri, '@'); if (ptr == NULL) { /* No '@' character at all? No user info, then. */ if (username != NULL) { *username = NULL; } if (password != NULL) { *password = NULL; } return pstrdup(p, uri); } /* To handle the case where the password field might itself contain an * '@' character, we first search from the end for '@'. If found, then we * search for '@' from the beginning. If also found, AND if both ocurrences * are the same, then we have a plain "username:password@" string. * * Note that we can handle '@' characters within passwords (or usernames), * but we currently cannot handle ':' characters within usernames. */ ptr2 = strrchr(uri, '@'); if (ptr2 != NULL) { if (ptr != ptr2) { /* Use the last found '@' as the delimiter. */ ptr = ptr2; } } userinfo = pstrndup(p, uri, ptr - uri); rem_uri = ptr + 1; ptr = strchr(userinfo, ':'); if (ptr == NULL) { pr_trace_msg(trace_channel, 4, "badly formatted userinfo '%.100s' (missing ':' character) in " "URI '%.100s', ignoring", userinfo, orig_uri); if (username != NULL) { *username = NULL; } if (password != NULL) { *password = NULL; } return rem_uri; } user = pstrndup(p, userinfo, ptr - userinfo); if (username != NULL) { *username = user; } /* Watch for empty passwords. */ if (*(ptr+1) == '\0') { passwd = pstrdup(p, ""); } else { passwd = pstrdup(p, ptr + 1); } if (password != NULL) { *password = passwd; } pr_trace_msg(trace_channel, 17, "parsed username '%s', password '%s' out of URI '%s'", user, passwd, orig_uri); return rem_uri; }
const char *sftp_display_fh_get_msg(pool *p, pr_fh_t *fh) { struct stat st; char buf[PR_TUNABLE_BUFFER_SIZE], *msg = ""; int len, res; unsigned int *current_clients = NULL; unsigned int *max_clients = NULL; off_t fs_size = 0; void *v; const char *serverfqdn = main_server->ServerFQDN; char *outs, mg_size[12] = {'\0'}, mg_size_units[12] = {'\0'}, mg_max[12] = "unlimited"; char mg_class_limit[12] = {'\0'}, mg_cur[12] = {'\0'}, mg_cur_class[12] = {'\0'}; const char *mg_time; char *rfc1413_ident = NULL, *user = 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; } res = pr_fs_fgetsize(fh->fh_fd, &fs_size); if (res < 0 && errno != ENOSYS) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "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); mg_time = pr_strtime(time(NULL)); max_clients = get_param_ptr(main_server->conf, "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) { 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_max, sizeof(mg_max), "%u", max_clients ? *max_clients : 0); user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); if (user == NULL) user = ""; rfc1413_ident = pr_table_get(session.notes, "mod_ident.rfc1413-ident", NULL); if (rfc1413_ident == NULL) { rfc1413_ident = "UNKNOWN"; } memset(buf, '\0', sizeof(buf)); while (pr_fsio_gets(buf, sizeof(buf), fh) != NULL) { char *tmp; pr_signals_handle(); buf[sizeof(buf)-1] = '\0'; len = strlen(buf); while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) { pr_signals_handle(); buf[len-1] = '\0'; len--; } /* Check for any Variable-type strings. */ tmp = strstr(buf, "%{"); while (tmp) { char *key, *tmp2; const char *val; pr_signals_handle(); tmp2 = strchr(tmp, '}'); if (tmp2 == NULL) { /* No closing '}' found in this string, so no need to look for any * aother '%{' opening sequence. Just move on. */ tmp = NULL; break; } key = pstrndup(p, tmp, tmp2 - tmp + 1); /* There are a couple of special-case keys to watch for: * * env:$var * time:$fmt * * The Var API does not easily support returning values for keys * where part of the value depends on part of the key. That's why * these keys are handled here, instead of in pr_var_get(). */ if (strncmp(key, "%{time:", 7) == 0) { char time_str[128], *fmt; time_t now; struct tm *time_info; fmt = pstrndup(p, key + 7, strlen(key) - 8); now = time(NULL); time_info = pr_localtime(NULL, &now); memset(time_str, 0, sizeof(time_str)); strftime(time_str, sizeof(time_str), fmt, time_info); val = pstrdup(p, time_str); } else if (strncmp(key, "%{env:", 6) == 0) { char *env_var; env_var = pstrndup(p, key + 6, strlen(key) - 7); val = pr_env_get(p, env_var); if (val == NULL) { pr_trace_msg("var", 4, "no value set for environment variable '%s', using \"(none)\"", env_var); val = "(none)"; } } else { val = pr_var_get(key); if (val == NULL) { pr_trace_msg("var", 4, "no value set for name '%s', using \"(none)\"", key); val = "(none)"; } } outs = sreplace(p, buf, key, val, NULL); sstrncpy(buf, outs, sizeof(buf)); tmp = strstr(outs, "%{"); } outs = sreplace(p, buf, "%C", (session.cwd[0] ? session.cwd : "(none)"), "%E", main_server->ServerAdmin, "%F", mg_size, "%f", mg_size_units, "%i", "0", "%K", "0", "%k", "0B", "%L", serverfqdn, "%M", mg_max, "%N", mg_cur, "%o", "0", "%R", (session.c && session.c->remote_name ? session.c->remote_name : "(unknown)"), "%T", mg_time, "%t", "0", "%U", user, "%u", rfc1413_ident, "%V", main_server->ServerName, "%x", session.conn_class ? session.conn_class->cls_name : "(unknown)", "%y", mg_cur_class, "%z", mg_class_limit, NULL); /* Always make sure that the lines we send are CRLF-terminated. */ msg = pstrcat(p, msg, outs, "\r\n", NULL); /* Clear the buffer for the next read. */ memset(buf, '\0', sizeof(buf)); } return msg; }
static void filetab_parse_table(wrap2_table_t *filetab) { unsigned int lineno = 0; char buf[MOD_WRAP2_FILE_BUFFER_SIZE] = {'\0'}; while (pr_fsio_getline(buf, sizeof(buf), (pr_fh_t *) filetab->tab_handle, &lineno) != NULL) { char *ptr, *res = NULL, *service = NULL; size_t buflen = strlen(buf); if (buf[buflen-1] != '\n') { wrap2_log("file '%s': missing newline or line too long (%u) at line %u", filetab->tab_name, (unsigned int) buflen, lineno); continue; } if (buf[0] == '#' || buf[strspn(buf, " \t\r\n")] == 0) { continue; } buf[buflen-1] = '\0'; /* The list of daemons is from the start of the line to a ':' delimiter. * This list is assumed to be space-delimited; failure to match this * syntax will result in lack of desired results when doing the access * checks. */ ptr = strchr(buf, ':'); if (ptr == NULL) { wrap2_log("file '%s': badly formatted list of daemon/service names at " "line %u", filetab->tab_name, lineno); continue; } service = pstrndup(filetab->tab_pool, buf, (ptr - buf)); if (filetab_service_name && (strcasecmp(filetab_service_name, service) == 0 || strncasecmp("ALL", service, 4) == 0)) { if (filetab_daemons_list == NULL) { filetab_daemons_list = make_array(filetab->tab_pool, 0, sizeof(char *)); } *((char **) push_array(filetab_daemons_list)) = service; res = wrap2_strsplit(buf, ':'); if (res == NULL) { wrap2_log("file '%s': missing \":\" separator at %u", filetab->tab_name, lineno); continue; } if (filetab_clients_list == NULL) { filetab_clients_list = make_array(filetab->tab_pool, 0, sizeof(char *)); } /* Check for another ':' delimiter. If present, anything following that * delimiter is an option/shell command (as per the hosts_access(5) man * page syntax description). * * If there are commas or whitespace in the line, parse them as separate * client names. Otherwise, a comma- or space-delimited list of names * will be treated as a single name, and violate the principle of least * surprise for the site admin. * * NOTE: Disable support for options in the file syntax if IPv6 addresses * are present, since the parsing code below is not sufficient for * handling both IPv6 addresses AND options, e.g.: * * proftpd: [::1] [::2]: <options> */ ptr = strchr(res, ':'); if (ptr != NULL) { char *clients; size_t clients_len; clients_len = (ptr - res); clients = pstrndup(filetab->tab_pool, res, clients_len); if (strcspn(clients, "[]") == clients_len) { ptr = wrap2_strsplit(res, ':'); if (filetab_options_list == NULL) { filetab_options_list = make_array(filetab->tab_pool, 0, sizeof(char *)); } /* Skip redundant whitespaces */ while (*ptr == ' ' || *ptr == '\t') { pr_signals_handle(); ptr++; } *((char **) push_array(filetab_options_list)) = pstrdup(filetab->tab_pool, ptr); } else { /* Ignoring options and IPv6 addresses (Bug#4090) for now. */ } } else { /* No options present. */ ptr = res; } ptr = strpbrk(res, ", \t"); if (ptr != NULL) { char *dup_opts, *word; dup_opts = pstrdup(filetab->tab_pool, res); while ((word = pr_str_get_token(&dup_opts, ", \t")) != NULL) { size_t wordlen; pr_signals_handle(); wordlen = strlen(word); if (wordlen == 0) { continue; } /* Remove any trailing comma */ if (word[wordlen-1] == ',') { word[wordlen-1] = '\0'; wordlen--; } *((char **) push_array(filetab_clients_list)) = word; /* Skip redundant whitespaces */ while (*dup_opts == ' ' || *dup_opts == '\t') { pr_signals_handle(); dup_opts++; } } } else { *((char **) push_array(filetab_clients_list)) = pstrdup(filetab->tab_pool, res); } } else { wrap2_log("file '%s': skipping irrevelant daemon/service ('%s') line %u", filetab->tab_name, service, lineno); } } return; }
const pr_netaddr_t *proxy_ftp_msg_parse_ext_addr(pool *p, const char *msg, const pr_netaddr_t *addr, int cmd_id, const char *net_proto) { pr_netaddr_t *res = NULL, na; int family = 0; unsigned short port = 0; char delim, *msg_str, *ptr; size_t msglen; if (p == NULL || msg == NULL || addr == NULL) { errno = EINVAL; return NULL; } if (cmd_id == PR_CMD_EPSV_ID) { /* First, find the opening '(' character. */ ptr = strchr(msg, '('); if (ptr == NULL) { pr_trace_msg(trace_channel, 12, "missing starting '(' character for extended address in '%s'", msg); errno = EINVAL; return NULL; } /* Make sure that the last character is a closing ')'. */ msglen = strlen(ptr); if (ptr[msglen-1] != ')') { pr_trace_msg(trace_channel, 12, "missing ending ')' character for extended address in '%s'", msg); errno = EINVAL; return NULL; } msg_str = pstrndup(p, ptr+1, msglen-2); } else { msg_str = pstrdup(p, msg); } /* Format is <d>proto<d>ip address<d>port<d> (ASCII in network order), * where <d> is an arbitrary delimiter character. */ delim = *msg_str++; /* If the network protocol string (e.g. sent by client in EPSV command) is * null, then determine the protocol family from the address family we were * given. */ /* XXX Hack to skip "all", e.g. "EPSV ALL" commands. */ if (net_proto != NULL) { if (strncasecmp(net_proto, "all", 4) == 0) { net_proto = NULL; } } if (net_proto == NULL) { if (*msg_str == delim) { switch (pr_netaddr_get_family(addr)) { case AF_INET: family = 1; break; #ifdef PR_USE_IPV6 case AF_INET6: if (pr_netaddr_use_ipv6()) { family = 2; break; } #endif /* PR_USE_IPV6 */ default: break; } } else { family = atoi(msg_str); } } else { family = atoi(net_proto); } switch (family) { case 1: pr_trace_msg(trace_channel, 19, "parsed IPv4 address from '%s'", msg); break; #ifdef PR_USE_IPV6 case 2: pr_trace_msg(trace_channel, 19, "parsed IPv6 address from '%s'", msg); if (pr_netaddr_use_ipv6()) { break; } #endif /* PR_USE_IPV6 */ default: pr_trace_msg(trace_channel, 12, "unsupported network protocol %d", family); errno = EPROTOTYPE; return NULL; } /* Now, skip past those numeric characters that atoi() used. */ while (PR_ISDIGIT(*msg_str)) { msg_str++; } /* If the next character is not the delimiter, it's a badly formatted * parameter. */ if (*msg_str == delim) { msg_str++; } else { pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'", msg_str); errno = EPERM; return NULL; } pr_netaddr_clear(&na); /* If the next character IS the delimiter, then the address portion is * omitted (which is permissible). */ if (*msg_str == delim) { pr_netaddr_set_family(&na, pr_netaddr_get_family(addr)); pr_netaddr_set_sockaddr(&na, pr_netaddr_get_sockaddr(addr)); msg_str++; } else { ptr = strchr(msg_str, delim); if (ptr == NULL) { /* Badly formatted message. */ errno = EINVAL; return NULL; } /* Twiddle the string so that just the address portion will be processed * by pr_inet_pton(). */ *ptr = '\0'; /* Use pr_inet_pton() to translate the address string into the address * value. */ switch (family) { case 1: { struct sockaddr *sa = NULL; pr_netaddr_set_family(&na, AF_INET); sa = pr_netaddr_get_sockaddr(&na); if (sa) { sa->sa_family = AF_INET; } if (pr_inet_pton(AF_INET, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) { pr_trace_msg(trace_channel, 2, "error converting IPv4 address '%s': %s", msg_str, strerror(errno)); errno = EPERM; return NULL; } break; } case 2: { struct sockaddr *sa = NULL; pr_netaddr_set_family(&na, AF_INET6); sa = pr_netaddr_get_sockaddr(&na); if (sa) { sa->sa_family = AF_INET6; } if (pr_inet_pton(AF_INET6, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) { pr_trace_msg(trace_channel, 2, "error converting IPv6 address '%s': %s", msg_str, strerror(errno)); errno = EPERM; return NULL; } break; } } /* Advance past the address portion of the argument. */ msg_str = ++ptr; } port = atoi(msg_str); while (PR_ISDIGIT(*msg_str)) { msg_str++; } /* If the next character is not the delimiter, it's a badly formatted * parameter. */ if (*msg_str != delim) { pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'", msg_str); errno = EPERM; return NULL; } /* XXX Use a pool other than session.pool here, in the future. */ res = pr_netaddr_dup(session.pool, &na); pr_netaddr_set_port(res, htons(port)); return res; }
/* Performs chroot-aware handling of symlinks. */ int dir_readlink(pool *p, const char *path, char *buf, size_t bufsz, int flags) { int is_abs_dst, clean_flags, len, res = -1; size_t chroot_pathlen = 0, adj_pathlen = 0; char *dst_path, *adj_path; pool *tmp_pool; if (p == NULL || path == NULL || buf == NULL) { errno = EINVAL; return -1; } if (bufsz == 0) { return 0; } len = pr_fsio_readlink(path, buf, bufsz); if (len < 0) { return -1; } if (len == 0 || len == bufsz) { /* If we read nothing in, OR if the given buffer was completely * filled WITHOUT terminating NUL, there's really nothing we can/should * be doing. */ return len; } is_abs_dst = FALSE; if (*buf == '/') { is_abs_dst = TRUE; } if (session.chroot_path != NULL) { chroot_pathlen = strlen(session.chroot_path); } if (chroot_pathlen <= 1) { char *ptr; if (is_abs_dst == TRUE || !(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) { return len; } /* Since we have a relative destination path, we will concat it * with the source path's directory, then clean up that path. */ ptr = strrchr(path, '/'); if (ptr != NULL && ptr != path) { char *parent_dir; tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "dir_readlink pool"); parent_dir = pstrndup(tmp_pool, path, (ptr - path)); dst_path = pdircat(tmp_pool, parent_dir, buf, NULL); adj_pathlen = bufsz + 1; adj_path = pcalloc(tmp_pool, adj_pathlen); res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, 0); if (res == 0) { pr_trace_msg("fsio", 19, "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path); dst_path = adj_path; } pr_trace_msg("fsio", 19, "adjusted relative symlink path '%s', yielding '%s'", buf, dst_path); memset(buf, '\0', bufsz); sstrncpy(buf, dst_path, bufsz); len = strlen(buf); destroy_pool(tmp_pool); } return len; } if (is_abs_dst == FALSE) { /* If we are to ignore relative destination paths, return now. */ if (!(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) { return len; } } if (is_abs_dst == TRUE && len < chroot_pathlen) { /* If the destination path length is shorter than the chroot path, * AND the destination path is absolute, then by definition it CANNOT * point within the chroot. */ return len; } tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "dir_readlink pool"); dst_path = pstrdup(tmp_pool, buf); if (is_abs_dst == FALSE) { char *ptr; /* Since we have a relative destination path, we will concat it * with the source path's directory, then clean up that path. */ ptr = strrchr(path, '/'); if (ptr != NULL && ptr != path) { char *parent_dir; parent_dir = pstrndup(tmp_pool, path, (ptr - path)); dst_path = pdircat(tmp_pool, parent_dir, dst_path, NULL); } else { dst_path = pdircat(tmp_pool, path, dst_path, NULL); } } adj_pathlen = bufsz + 1; adj_path = pcalloc(tmp_pool, adj_pathlen); clean_flags = PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH; res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, clean_flags); if (res == 0) { pr_trace_msg("fsio", 19, "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path); dst_path = adj_path; memset(buf, '\0', bufsz); sstrncpy(buf, dst_path, bufsz); len = strlen(dst_path); } if (strncmp(dst_path, session.chroot_path, chroot_pathlen) == 0 && *(dst_path + chroot_pathlen) == '/') { char *ptr; ptr = dst_path + chroot_pathlen; if (is_abs_dst == FALSE && res == 0) { /* If we originally had a relative destination path, AND we cleaned * that adjusted path, then we should try to re-adjust the path * back to being a relative path. Within reason. */ ptr = pstrcat(tmp_pool, ".", ptr, NULL); } /* Since we are making the destination path shorter, the given buffer * (which was big enough for the original destination path) should * always be large enough for this adjusted, shorter version. Right? */ pr_trace_msg("fsio", 19, "adjusted symlink path '%s' for chroot '%s', yielding '%s'", dst_path, session.chroot_path, ptr); memset(buf, '\0', bufsz); sstrncpy(buf, ptr, bufsz); len = strlen(buf); } destroy_pool(tmp_pool); return len; }