1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-12-04 14:46:47 -05:00

Bug 844: Remove src/protocol/smb/smb.c, superseded by smb2.c.

This commit is contained in:
Kalle Olavi Niemitalo 2007-02-25 23:53:53 +02:00 committed by Kalle Olavi Niemitalo
parent 3a3cf8b44f
commit 174a6459e6

View File

@ -1,721 +0,0 @@
/* Internal SMB protocol implementation */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* Needed for asprintf() */
#endif
#error SMB protocol support is vulnerable to CVE-2006-5925. Do not use.
#error If you want to use SMB, please vote for bug 844 or post a patch.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h> /* OS/2 needs this after sys/types.h */
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "elinks.h"
#include "cache/cache.h"
#include "config/options.h"
#include "intl/gettext/libintl.h"
#include "main/module.h"
#include "main/select.h"
#include "network/connection.h"
#include "network/socket.h"
#include "osdep/osdep.h"
#include "protocol/common.h"
#include "protocol/protocol.h"
#include "protocol/smb/smb.h"
#include "protocol/uri.h"
#include "util/memory.h"
#include "util/snprintf.h"
#include "util/string.h"
/* XXX: Nice cleanup target --pasky */
/* FIXME: we rely on smbclient output which may change in future,
* so i think we should use libsmbclient instead (or better in addition)
* This stuff is a quick hack, but it works ;). --Zas */
enum smb_list_type {
SMB_LIST_NONE,
SMB_LIST_SHARES,
SMB_LIST_DIR,
};
struct smb_connection_info {
enum smb_list_type list_type;
/* If this is 1, it means one socket is already off. The second one
* should call end_smb_connection() when it goes off as well. */
int closing;
size_t textlen;
unsigned char text[1];
};
static void end_smb_connection(struct connection *conn);
struct option_info smb_options[] = {
INIT_OPT_TREE("protocol", N_("SMB"),
"smb", 0,
N_("SAMBA specific options.")),
INIT_OPT_STRING("protocol.smb", N_("Credentials"),
"credentials", 0, "",
N_("Credentials file passed to smbclient via -A option.")),
NULL_OPTION_INFO,
};
struct module smb_protocol_module = struct_module(
/* name: */ N_("SMB"),
/* options: */ smb_options,
/* hooks: */ NULL,
/* submodules: */ NULL,
/* data: */ NULL,
/* init: */ NULL,
/* done: */ NULL
);
/* Return 0 if @conn->cached was set. */
static int
smb_get_cache(struct connection *conn)
{
if (conn->cached) return 0;
conn->cached = get_cache_entry(conn->uri);
if (conn->cached) return 0;
abort_connection(conn, S_OUT_OF_MEM);
return -1;
}
#define READ_SIZE 4096
static ssize_t
smb_read_data(struct connection *conn, int sock, unsigned char *dst)
{
ssize_t r;
struct smb_connection_info *si = conn->info;
r = read(sock, dst, READ_SIZE);
if (r == -1) {
retry_connection(conn, -errno);
return -1;
}
if (r == 0) {
if (!si->closing) {
si->closing = 1;
clear_handlers(sock);
return 0;
}
end_smb_connection(conn);
return 0;
}
return r;
}
static void
smb_read_text(struct connection *conn, int sock)
{
ssize_t r;
struct smb_connection_info *si = conn->info;
/* We add 2 here to handle LF and NUL chars that are added in
* smb_end_connection(). */
si = mem_realloc(si, sizeof(*si) + si->textlen
+ READ_SIZE + 2);
if (!si) {
abort_connection(conn, S_OUT_OF_MEM);
return;
}
conn->info = si;
r = smb_read_data(conn, sock, si->text + si->textlen);
if (r <= 0) return;
if (!conn->from) set_connection_state(conn, S_GETH);
si->textlen += r;
}
static void
smb_got_data(struct connection *conn)
{
struct smb_connection_info *si = conn->info;
unsigned char buffer[READ_SIZE];
ssize_t r;
if (si->list_type != SMB_LIST_NONE) {
smb_read_text(conn, conn->data_socket->fd);
return;
}
r = smb_read_data(conn, conn->data_socket->fd, buffer);
if (r <= 0) return;
set_connection_state(conn, S_TRANS);
if (smb_get_cache(conn)) return;
conn->received += r;
if (add_fragment(conn->cached, conn->from, buffer, r) == 1)
conn->tries = 0;
conn->from += r;
}
#undef READ_SIZE
static void
smb_got_text(struct connection *conn)
{
smb_read_text(conn, conn->socket->fd);
}
/* Search for @str1 followed by @str2 in @line.
* It returns NULL if not found, or pointer to start
* of @str2 in @line if found. */
static unsigned char *
find_strs(unsigned char *line, unsigned char *str1,
unsigned char *str2)
{
unsigned char *p = strstr(line, str1);
if (!p) return NULL;
p = strstr(p + strlen(str1), str2);
return p;
}
static void
parse_smbclient_output(struct uri *uri, struct smb_connection_info *si,
struct string *page)
{
unsigned char *line_start, *line_end;
enum {
SMB_TYPE_NONE,
SMB_TYPE_SHARE,
SMB_TYPE_SERVER,
SMB_TYPE_WORKGROUP
} type = SMB_TYPE_NONE;
size_t pos = 0;
int stop;
assert(uri && si && page);
if_assert_failed return;
add_to_string(page, "<html><head><title>/");
add_bytes_to_string(page, uri->data, uri->datalen);
add_to_string(page, "</title></head><body><pre>");
line_start = si->text;
stop = !si->textlen; /* Nothing to parse. */
while (!stop && (line_end = strchr(line_start, ASCII_LF))) {
unsigned char *line = line_start;
size_t line_len;
size_t start_offset = 0;
/* Handle \r\n case. Normally, do not occur on *nix. */
if (line_end > line_start && line_end[-1] == ASCII_CR)
line_end--, start_offset++;
line_len = line_end - line_start;
/* Here we modify si->text content, this should not be
* a problem as it is only used here. This prevents
* allocation of memory for the line. */
*line_end = '\0';
/* And I got bored here with cleaning it up. --pasky */
if (si->list_type == SMB_LIST_SHARES) {
unsigned char *ll, *lll, *found;
if (!*line) type = SMB_TYPE_NONE;
found = find_strs(line, "Sharename", "Type");
if (found) {
pos = found - line;
type = SMB_TYPE_SHARE;
goto print_as_is;
}
found = find_strs(line, "Server", "Comment");
if (found) {
type = SMB_TYPE_SERVER;
goto print_as_is;
}
found = find_strs(line, "Workgroup", "Master");
if (found) {
pos = found - line;
type = SMB_TYPE_WORKGROUP;
goto print_as_is;
}
if (type == SMB_TYPE_NONE)
goto print_as_is;
for (ll = line; *ll; ll++)
if (!isspace(*ll) && *ll != '-')
goto print_next;
goto print_as_is;
print_next:
for (ll = line; *ll; ll++)
if (!isspace(*ll))
break;
for (lll = ll; *lll; lll++)
if (isspace(*lll))
break;
switch (type) {
case SMB_TYPE_SHARE:
{
unsigned char *llll;
if (!strstr(lll, "Disk"))
goto print_as_is;
if (pos && pos < line_len
&& isspace(*(llll = line + pos - 1))
&& llll > ll) {
while (llll > ll && isspace(*llll))
llll--;
if (!isspace(*llll))
lll = llll + 1;
}
add_bytes_to_string(page, line, ll - line);
add_to_string(page, "<a href=\"");
add_bytes_to_string(page, ll, lll - ll);
add_to_string(page, "/\">");
add_bytes_to_string(page, ll, lll - ll);
add_to_string(page, "</a>");
add_to_string(page, lll);
break;
}
case SMB_TYPE_WORKGROUP:
if (pos < line_len && pos
&& isspace(line[pos - 1])
&& !isspace(line[pos])) {
ll = line + pos;
} else {
for (ll = lll; *ll; ll++)
if (!isspace(*ll))
break;
}
for (lll = ll; *lll; lll++)
if (isspace(*lll))
break;
/* Fall-through */
case SMB_TYPE_SERVER:
add_bytes_to_string(page, line, ll - line);
add_to_string(page, "<a href=\"smb://");
add_bytes_to_string(page, ll, lll - ll);
add_to_string(page, "/\">");
add_bytes_to_string(page, ll, lll - ll);
add_to_string(page, "</a>");
add_to_string(page, lll);
break;
case SMB_TYPE_NONE:
goto print_as_is;
}
} else if (si->list_type == SMB_LIST_DIR) {
if (strstr(line, "NT_STATUS")) {
/* Error, stop after message. */
stop = 1;
goto print_as_is;
}
if (line_end - line_start >= 5
&& line_start[0] == ' '
&& line_start[1] == ' '
&& line_start[2] != ' ') {
int dir = 0;
int may_be_dir = 0;
unsigned char *p = line_end;
unsigned char *url = line_start + 2;
/* smbclient list parser
* The boring thing is that output is
* ambiguous in many ways:
* filenames with more than one space,
* etc...
* This bloated code tries to do a not
* so bad job. --Zas */
/* directory D 0 Fri May 7 11:23:18 2004 */
/* filename 2444 Thu Feb 19 15:52:46 2004 */
/* Skip end of line */
while (p > url && !isdigit(*p)) p--;
if (p == url) goto print_as_is;
/* FIXME: Use parse_date()? */
/* year */
while (p > url && isdigit(*p)) p--;
if (p == url || !isspace(*p)) goto print_as_is;
while (p > url && isspace(*p)) p--;
/* seconds */
while (p > url && isdigit(*p)) p--;
if (p == url || *p != ':') goto print_as_is;
p--;
/* minutes */
while (p > url && isdigit(*p)) p--;
if (p == url || *p != ':') goto print_as_is;
p--;
/* hours */
while (p > url && isdigit(*p)) p--;
if (p == url || !isspace(*p)) goto print_as_is;
p--;
/* day as number */
while (p > url && isdigit(*p)) p--;
while (p > url && isspace(*p)) p--;
if (p == url) goto print_as_is;
/* month */
while (p > url && !isspace(*p)) p--;
if (p == url || !isspace(*p)) goto print_as_is;
p--;
/* day name */
while (p > url && !isspace(*p)) p--;
if (p == url || !isspace(*p)) goto print_as_is;
while (p > url && isspace(*p)) p--;
/* file size */
if (p == url || !isdigit(*p)) goto print_as_is;
if (*p == '0' && isspace(*(p - 1))) may_be_dir = 1;
while (p > url && isdigit(*p)) p--;
if (p == url) goto print_as_is;
/* Magic to determine if we have a
* filename or a dirname. Thanks to
* smbclient ambiguous output. */
{
unsigned char *pp = p;
while (pp > url && isspace(*pp)) pp--;
if (p - pp <= 8) {
while (pp > url
&& (*pp == 'D'
|| *pp == 'H'
|| *pp == 'A'
|| *pp == 'S'
|| *pp == 'R'
|| *pp == 'V')) {
if (*pp == 'D' && may_be_dir)
dir = 1;
pp--;
}
}
while (pp > url && isspace(*pp)) pp--;
p = pp;
}
/* Don't display '.' directory */
if (p == url && *url == '.') goto ignored;
p++;
add_to_string(page, " <a href=\"");
add_bytes_to_string(page, url, p - url);
if (dir) add_char_to_string(page, '/');
add_to_string(page, "\">");
add_bytes_to_string(page, url, p - url);
add_to_string(page, "</a>");
add_bytes_to_string(page, p, line_end - p);
} else {
goto print_as_is;
}
} else {
print_as_is:
add_bytes_to_string(page, line_start, line_len);
}
add_char_to_string(page, ASCII_LF);
ignored:
line_start = line_end + start_offset + 1;
}
add_to_string(page, "</pre></body></html>");
}
static void
end_smb_connection(struct connection *conn)
{
struct smb_connection_info *si = conn->info;
struct uri *uri;
enum connection_state state = S_OK;
if (smb_get_cache(conn)) return;
if (conn->from)
goto bye;
/* Ensure termination by LF + NUL chars, memory for this
* was reserved by smb_read_text(). */
if (si->textlen && si->text[si->textlen - 1] != ASCII_LF)
si->text[si->textlen++] = ASCII_LF;
si->text[si->textlen] = '\0';
uri = conn->uri;
if (uri->datalen
&& uri->data[uri->datalen - 1] != '/'
&& uri->data[uri->datalen - 1] != '\\'
&& (strstr(si->text, "NT_STATUS_FILE_IS_A_DIRECTORY")
|| strstr(si->text, "NT_STATUS_ACCESS_DENIED")
|| strstr(si->text, "ERRbadfile"))) {
redirect_cache(conn->cached, "/", 1, 0);
} else {
struct string page;
if (!init_string(&page)) {
state = S_OUT_OF_MEM;
goto bye;
}
parse_smbclient_output(uri, si, &page);
add_fragment(conn->cached, 0, page.source, page.length);
conn->from += page.length;
done_string(&page);
mem_free_set(&conn->cached->content_type, stracpy("text/html"));
}
bye:
close_socket(conn->socket);
close_socket(conn->data_socket);
abort_connection(conn, state);
}
void
smb_protocol_handler(struct connection *conn)
{
int out_pipe[2] = { -1, -1 };
int err_pipe[2] = { -1, -1 };
unsigned char *share, *dir;
unsigned char *p;
pid_t cpid;
int dirlen;
struct smb_connection_info *si;
struct uri *uri;
si = mem_calloc(1, sizeof(*si) + 2);
if (!si) {
abort_connection(conn, S_OUT_OF_MEM);
return;
}
conn->info = si;
uri = conn->uri;
p = strchr(uri->data, '/');
if (p && p - uri->data < uri->datalen) {
share = memacpy(uri->data, p - uri->data);
dir = p + 1;
/* FIXME: ensure @dir do not contain dangerous chars. --Zas */
} else if (uri->datalen) {
if (smb_get_cache(conn)) return;
redirect_cache(conn->cached, "/", 1, 0);
abort_connection(conn, S_OK);
return;
} else {
share = stracpy("");
dir = "";
}
if (!share) {
abort_connection(conn, S_OUT_OF_MEM);
return;
}
dirlen = strlen(dir);
if (!*share) {
si->list_type = SMB_LIST_SHARES;
} else if (!dirlen || dir[dirlen - 1] == '/'
|| dir[dirlen - 1] == '\\') {
si->list_type = SMB_LIST_DIR;
}
if (c_pipe(out_pipe) || c_pipe(err_pipe)) {
int s_errno = errno;
if (out_pipe[0] >= 0) close(out_pipe[0]);
if (out_pipe[1] >= 0) close(out_pipe[1]);
mem_free(share);
abort_connection(conn, -s_errno);
return;
}
conn->from = 0;
cpid = fork();
if (cpid == -1) {
int s_errno = errno;
close(out_pipe[0]);
close(out_pipe[1]);
close(err_pipe[0]);
close(err_pipe[1]);
mem_free(share);
retry_connection(conn, -s_errno);
return;
}
if (!cpid) {
#define SMBCLIENT "smbclient"
#define MAX_SMBCLIENT_ARGS 32
int n = 0;
unsigned char *v[MAX_SMBCLIENT_ARGS];
unsigned char *optstr;
close(1);
dup2(out_pipe[1], 1);
close(2);
dup2(err_pipe[1], 2);
close(0);
dup2(open("/dev/null", O_RDONLY), 0);
close_all_non_term_fd();
close(out_pipe[0]);
close(err_pipe[0]);
/* Usage: smbclient service <password> [options] */
v[n++] = SMBCLIENT;
/* FIXME: handle alloc failures. */
/* At this point, we are the child process.
* Maybe we just don't care if the child kills itself
* dereferencing a NULL pointer... -- Miciah */
/* Leaving random core files after itself is not what a nice
* program does. Also, the user might also want to know, why
* the hell does he see nothing on the screen. --pasky */
if (*share) {
/* Construct service path. */
asprintf((char **) &v[n++], "//%.*s/%s",
uri->hostlen, uri->host, share);
/* Add password if any. */
if (uri->passwordlen && !uri->userlen) {
v[n++] = memacpy(uri->password, uri->passwordlen);
}
} else {
/* Get a list of shares available on a host. */
v[n++] = "-L";
v[n++] = memacpy(uri->host, uri->hostlen);
}
v[n++] = "-N"; /* Don't ask for a password. */
v[n++] = "-E"; /* Write messages to stderr instead of stdout. */
v[n++] = "-d 0"; /* Disable debug mode. */
if (uri->portlen) {
/* Connect to the specified port. */
v[n++] = "-p";
v[n++] = memacpy(uri->port, uri->portlen);
}
if (uri->userlen) {
/* Set the network username. */
v[n++] = "-U";
if (!uri->passwordlen) {
/* No password. */
v[n++] = memacpy(uri->user, uri->userlen);
} else {
/* With password. */
asprintf((char **) &v[n++], "%.*s%%%.*s",
uri->userlen, uri->user,
uri->passwordlen, uri->password);
}
}
if (*share) {
/* FIXME: use si->list_type here ?? --Zas */
if (!dirlen || dir[dirlen - 1] == '/' || dir[dirlen - 1] == '\\') {
if (dirlen) {
/* Initial directory. */
v[n++] = "-D";
v[n++] = dir;
}
v[n++] = "-c"; /* Execute semicolon separated commands. */
v[n++] = "ls"; /* List files. */
} else {
/* Copy remote file to stdout. */
unsigned char *s = straconcat("get \"", dir, "\" -", NULL);
unsigned char *ss = s;
v[n++] = "-c"; /* Execute semicolon separated commands. */
while ((ss = strchr(ss, '/'))) *ss = '\\'; /* Escape '/' */
v[n++] = s;
}
}
/* Optionally add SMB credentials file. */
optstr = get_opt_str("protocol.smb.credentials");
if (optstr[0]) {
v[n++] = "-A";
v[n++] = optstr;
}
v[n++] = NULL; /* End of arguments list. */
assert(n < MAX_SMBCLIENT_ARGS);
execvp(SMBCLIENT, (char **) v);
/* FIXME: this message will never be displayed, since execvp()
* failed. */
fprintf(stderr, SMBCLIENT " not found in $PATH");
_exit(1);
#undef MAX_SMBCLIENT_ARGS
#undef SMBCLIENT
}
mem_free(share);
conn->data_socket->fd = out_pipe[0];
conn->socket->fd = err_pipe[0];
close(out_pipe[1]);
close(err_pipe[1]);
set_handlers(out_pipe[0], (select_handler_T) smb_got_data, NULL, NULL, conn);
set_handlers(err_pipe[0], (select_handler_T) smb_got_text, NULL, NULL, conn);
set_connection_state(conn, S_CONN);
}