static char *ip_address;
static char *tcp_port;
+static int hostname_lookup_done;
+
+static void lookup_hostname(void);
+
+static const char *get_canon_hostname(void)
+{
+ lookup_hostname();
+ return canon_hostname;
+}
+
+static const char *get_ip_address(void)
+{
+ lookup_hostname();
+ return ip_address;
+}
+
static void logreport(int priority, const char *err, va_list params)
{
if (log_syslog) {
exit(1);
}
+static void strbuf_addstr_or_null(struct strbuf *sb, const char *s)
+{
+ if (s)
+ strbuf_addstr(sb, s);
+}
+
+struct expand_path_context {
+ const char *directory;
+};
+
+static size_t expand_path(struct strbuf *sb, const char *placeholder, void *ctx)
+{
+ struct expand_path_context *context = ctx;
+
+ switch (placeholder[0]) {
+ case 'H':
+ strbuf_addstr_or_null(sb, hostname);
+ return 1;
+ case 'C':
+ if (placeholder[1] == 'H') {
+ strbuf_addstr_or_null(sb, get_canon_hostname());
+ return 2;
+ }
+ break;
+ case 'I':
+ if (placeholder[1] == 'P') {
+ strbuf_addstr_or_null(sb, get_ip_address());
+ return 2;
+ }
+ break;
+ case 'P':
+ strbuf_addstr_or_null(sb, tcp_port);
+ return 1;
+ case 'D':
+ strbuf_addstr(sb, context->directory);
+ return 1;
+ }
+ return 0;
+}
+
static const char *path_ok(const char *directory)
{
static char rpath[PATH_MAX];
}
else if (interpolated_path && saw_extended_args) {
struct strbuf expanded_path = STRBUF_INIT;
- struct strbuf_expand_dict_entry dict[6];
-
- dict[0].placeholder = "H"; dict[0].value = hostname;
- dict[1].placeholder = "CH"; dict[1].value = canon_hostname;
- dict[2].placeholder = "IP"; dict[2].value = ip_address;
- dict[3].placeholder = "P"; dict[3].value = tcp_port;
- dict[4].placeholder = "D"; dict[4].value = directory;
- dict[5].placeholder = NULL; dict[5].value = NULL;
+ struct expand_path_context context;
+
+ context.directory = directory;
+
if (*dir != '/') {
/* Allow only absolute */
logerror("'%s': Non-absolute path denied (interpolated-path active)", dir);
}
strbuf_expand(&expanded_path, interpolated_path,
- strbuf_expand_dict_cb, &dict);
+ expand_path, &context);
strlcpy(interp_path, expanded_path.buf, PATH_MAX);
strbuf_release(&expanded_path);
loginfo("Interpolated dir '%s'", interp_path);
*arg++ = service->name;
*arg++ = path;
*arg++ = STRARG(hostname);
- *arg++ = STRARG(canon_hostname);
- *arg++ = STRARG(ip_address);
+ *arg++ = STRARG(get_canon_hostname());
+ *arg++ = STRARG(get_ip_address());
*arg++ = STRARG(tcp_port);
*arg = NULL;
#undef STRARG
}
}
+/*
+ * Sanitize a string from the client so that it's OK to be inserted into a
+ * filesystem path. Specifically, we disallow slashes, runs of "..", and
+ * trailing and leading dots, which means that the client cannot escape
+ * our base path via ".." traversal.
+ */
+static void sanitize_client_strbuf(struct strbuf *out, const char *in)
+{
+ for (; *in; in++) {
+ if (*in == '/')
+ continue;
+ if (*in == '.' && (!out->len || out->buf[out->len - 1] == '.'))
+ continue;
+ strbuf_addch(out, *in);
+ }
+
+ while (out->len && out->buf[out->len - 1] == '.')
+ strbuf_setlen(out, out->len - 1);
+}
+
+static char *sanitize_client(const char *in)
+{
+ struct strbuf out = STRBUF_INIT;
+ sanitize_client_strbuf(&out, in);
+ return strbuf_detach(&out, NULL);
+}
+
+/*
+ * Like sanitize_client, but we also perform any canonicalization
+ * to make life easier on the admin.
+ */
+static char *canonicalize_client(const char *in)
+{
+ struct strbuf out = STRBUF_INIT;
+ sanitize_client_strbuf(&out, in);
+ strbuf_tolower(&out);
+ return strbuf_detach(&out, NULL);
+}
+
/*
* Read the host as supplied by the client connection.
*/
parse_host_and_port(val, &host, &port);
if (port) {
free(tcp_port);
- tcp_port = xstrdup(port);
+ tcp_port = sanitize_client(port);
}
free(hostname);
- hostname = xstrdup_tolower(host);
+ hostname = canonicalize_client(host);
+ hostname_lookup_done = 0;
}
/* On to the next one */
if (extra_args < end && *extra_args)
die("Invalid request");
}
+}
- /*
- * Locate canonical hostname and its IP address.
- */
- if (hostname) {
+/*
+ * Locate canonical hostname and its IP address.
+ */
+static void lookup_hostname(void)
+{
+ if (!hostname_lookup_done && hostname) {
#ifndef NO_IPV6
struct addrinfo hints;
struct addrinfo *ai;
ip_address = xstrdup(addrbuf);
free(canon_hostname);
- canon_hostname = xstrdup(ai->ai_canonname ?
- ai->ai_canonname : ip_address);
+ canon_hostname = ai->ai_canonname ?
+ sanitize_client(ai->ai_canonname) :
+ xstrdup(ip_address);
freeaddrinfo(ai);
}
static char addrbuf[HOST_NAME_MAX + 1];
hent = gethostbyname(hostname);
+ if (hent) {
+ ap = hent->h_addr_list;
+ memset(&sa, 0, sizeof sa);
+ sa.sin_family = hent->h_addrtype;
+ sa.sin_port = htons(0);
+ memcpy(&sa.sin_addr, *ap, hent->h_length);
+
+ inet_ntop(hent->h_addrtype, &sa.sin_addr,
+ addrbuf, sizeof(addrbuf));
- ap = hent->h_addr_list;
- memset(&sa, 0, sizeof sa);
- sa.sin_family = hent->h_addrtype;
- sa.sin_port = htons(0);
- memcpy(&sa.sin_addr, *ap, hent->h_length);
-
- inet_ntop(hent->h_addrtype, &sa.sin_addr,
- addrbuf, sizeof(addrbuf));
-
- free(canon_hostname);
- canon_hostname = xstrdup(hent->h_name);
- free(ip_address);
- ip_address = xstrdup(addrbuf);
+ free(canon_hostname);
+ canon_hostname = sanitize_client(hent->h_name);
+ free(ip_address);
+ ip_address = xstrdup(addrbuf);
+ }
#endif
+ hostname_lookup_done = 1;
}
}
static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
{
int socknum = 0;
- int maxfd = -1;
char pbuf[NI_MAXSERV];
struct addrinfo hints, *ai0, *ai;
int gai;
ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
socklist->list[socklist->nr++] = sockfd;
socknum++;
-
- if (maxfd < sockfd)
- maxfd = sockfd;
}
freeaddrinfo(ai0);
}
if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
- logerror("Could not listen to %s: %s",
+ logerror("Could not bind to %s: %s",
ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
strerror(errno));
close(sockfd);