1
0
mirror of https://github.com/rkd77/elinks.git synced 2025-02-02 15:09:23 -05:00
Kalle Olavi Niemitalo fb966b87a3 Merge branch 'elinks-0.12' into elinks-0.13
Conflicts:

	doc/Doxyfile.in
2008-03-15 19:24:51 +02:00

603 lines
15 KiB
C

/* SMB protocol implementation */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#ifdef HAVE_LIBSMBCLIENT_H
#include <libsmbclient.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.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/auth/auth.h"
#include "protocol/common.h"
#include "protocol/protocol.h"
#include "protocol/smb/smb.h"
#include "protocol/uri.h"
#include "util/conv.h"
#include "util/memory.h"
#include "util/string.h"
/* These options are not used. */
#if 0
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,
};
#endif
struct module smb_protocol_module = struct_module(
/* name: */ N_("SMB"),
/* options: */ NULL,
/* hooks: */ NULL,
/* submodules: */ NULL,
/* data: */ NULL,
/* init: */ NULL,
/* done: */ NULL
);
static FILE *header_out, *data_out;
/* The child process generally does not bother to free the memory it
* allocates. When the process exits, the operating system will free
* the memory anyway. There is no point in changing this, because the
* child process also inherits memory allocations from the parent
* process, and it would be very cumbersome to free those. */
static void
smb_error(int error)
{
fputs("text/x-error", header_out);
fprintf(data_out, "%d\n", error);
exit(1);
}
static int
compare(const void *a, const void *b)
{
const struct smbc_dirent **da = (const struct smbc_dirent **)a;
const struct smbc_dirent **db = (const struct smbc_dirent **)b;
int res = (*da)->smbc_type - (*db)->smbc_type;
if (res) {
return res;
}
return strcmp((*da)->name, (*db)->name);
}
static void
smb_add_link(struct string *string, const struct smbc_dirent *entry,
const unsigned char *text, const unsigned char dircolor[])
{
struct string uri_string;
if (!init_string(&uri_string)) return;
encode_uri_string(&uri_string, entry->name, entry->namelen, 0);
add_to_string(string, "<a href=\"");
add_html_to_string(string, uri_string.source, uri_string.length);
done_string(&uri_string);
add_to_string(string, "\">");
if (*dircolor) {
add_to_string(string, "<font color=\"");
add_to_string(string, dircolor);
add_to_string(string, "\"><b>");
}
add_html_to_string(string, entry->name, entry->namelen);
if (*dircolor) {
add_to_string(string, "</b></font>");
}
add_to_string(string, "</a>");
if (text) add_to_string(string, text);
}
static void
display_entry(const struct smbc_dirent *entry, const unsigned char dircolor[])
{
static const unsigned char zero = '\0';
struct string string;
if (!init_string(&string)) return;
switch (entry->smbc_type) {
case SMBC_WORKGROUP:
smb_add_link(&string, entry, " WORKGROUP ", dircolor);
break;
case SMBC_SERVER:
smb_add_link(&string, entry, " SERVER ", dircolor);
if (entry->comment) {
add_html_to_string(&string, entry->comment, entry->commentlen);
}
break;
case SMBC_FILE_SHARE:
smb_add_link(&string, entry, " FILE SHARE ", dircolor);
if (entry->comment) {
add_html_to_string(&string, entry->comment, entry->commentlen);
}
break;
case SMBC_PRINTER_SHARE:
add_html_to_string(&string, entry->name, entry->namelen);
add_to_string(&string, " PRINTER ");
if (entry->comment) {
add_html_to_string(&string, entry->comment, entry->commentlen);
}
break;
case SMBC_COMMS_SHARE:
add_bytes_to_string(&string, entry->name, entry->namelen);
add_to_string(&string, " COMM");
break;
case SMBC_IPC_SHARE:
add_bytes_to_string(&string, entry->name, entry->namelen);
add_to_string(&string, " IPC");
break;
case SMBC_DIR:
smb_add_link(&string, entry, NULL, dircolor);
break;
case SMBC_LINK:
smb_add_link(&string, entry, " Link", &zero);
break;
case SMBC_FILE:
smb_add_link(&string, entry, NULL, &zero);
break;
default:
/* unknown type */
break;
}
fputs(string.source, data_out);
fputc('\n', data_out);
done_string(&string);
}
static void
sort_and_display_entries(int dir, const unsigned char dircolor[])
{
struct smbc_dirent *fentry, **table = NULL;
int size = 0;
int i;
while ((fentry = smbc_readdir(dir))) {
struct smbc_dirent **new_table, *new_entry;
unsigned int commentlen = fentry->commentlen;
unsigned int namelen = fentry->namelen;
if (!strcmp(fentry->name, "."))
continue;
/* In libsmbclient 3.0.10, @smbc_dirent.namelen and
* @smbc_dirent.commentlen include the null characters
* (tested with GDB). In libsmbclient 3.0.24, they
* don't. This is related to Samba bug 3030. Adjust
* the lengths to exclude the null characters, so that
* other code need not care.
*
* Make all changes to local copies rather than
* directly to *@fentry, so that there's no chance of
* ELinks messing up whatever mechanism libsmbclient
* will use to free @fentry. */
if (commentlen > 0 && fentry->comment[commentlen - 1] == '\0')
commentlen--;
if (namelen > 0 && fentry->name[namelen - 1] == '\0')
namelen--;
/* libsmbclient seems to place the struct smbc_dirent,
* the name string, and the comment string all in one
* block of memory, which then is smbc_dirent.dirlen
* bytes long. This has however not been really
* documented, so ELinks should not assume copying
* fentry->dirlen bytes will copy the comment too.
* Yet, it would be wasteful to copy both dirlen bytes
* and then the comment string separately. What we do
* here is ignore fentry->dirlen and recompute the
* size based on namelen. */
new_entry = (struct smbc_dirent *)
memacpy((const unsigned char *) fentry,
offsetof(struct smbc_dirent, name)
+ namelen); /* memacpy appends '\0' */
if (!new_entry)
continue;
new_entry->namelen = namelen;
new_entry->commentlen = commentlen;
if (fentry->comment)
new_entry->comment = memacpy(fentry->comment, commentlen);
if (!new_entry->comment)
new_entry->commentlen = 0;
new_table = mem_realloc(table, (size + 1) * sizeof(*table));
if (!new_table)
continue;
table = new_table;
table[size] = new_entry;
size++;
}
/* If size==0, then table==NULL. According to ISO/IEC 9899:1999
* 7.20.5p1, the NULL must not be given to qsort. */
if (size > 0)
qsort(table, size, sizeof(*table), compare);
for (i = 0; i < size; i++) {
display_entry(table[i], dircolor);
}
}
static void
smb_directory(int dir, struct uri *uri)
{
struct string buf;
unsigned char dircolor[8] = "";
if (init_directory_listing(&buf, uri) != S_OK) {
smb_error(-S_OUT_OF_MEM);
}
fputs("text/html", header_out);
fclose(header_out);
fputs(buf.source, data_out);
fputc('\n', data_out);
if (get_opt_bool("document.browse.links.color_dirs", NULL)) {
color_to_string(get_opt_color("document.colors.dirs", NULL),
dircolor);
}
sort_and_display_entries(dir, dircolor);
fputs("</pre><hr/></body></html>\n", data_out);
smbc_closedir(dir);
exit(0);
}
static void
smb_auth(const char *srv, const char *shr, char *wg, int wglen, char *un,
int unlen, char *pw, int pwlen)
{
/* TODO */
}
#define READ_SIZE 4096
static void
do_smb(struct connection *conn)
{
struct uri *uri = conn->uri;
struct auth_entry *auth = find_auth(uri);
struct string string;
unsigned char *url;
int dir;
if ((uri->userlen && uri->passwordlen) || !auth) {
url = get_uri_string(uri, URI_BASE);
} else {
unsigned char *uri_string = get_uri_string(uri, URI_HOST | URI_PORT | URI_DATA);
if (!uri_string || !init_string(&string)) {
smb_error(-S_OUT_OF_MEM);
}
/* Must URI-encode the username and password to avoid
* ambiguity if they contain "/:@" characters.
* Libsmbclient then decodes them again, and the
* server gets them as they were in auth->user and
* auth->password, i.e. as the user typed them in the
* auth dialog. This implies that, if the username or
* password contains some characters or bytes that the
* user cannot directly type, then she cannot enter
* them. If that becomes an actual problem, it should
* be fixed in the auth dialog, e.g. by providing a
* hexadecimal input mode. */
add_to_string(&string, "smb://");
encode_uri_string(&string, auth->user, -1, 1);
add_char_to_string(&string, ':');
encode_uri_string(&string, auth->password, -1, 1);
add_char_to_string(&string, '@');
add_to_string(&string, uri_string);
url = string.source;
}
if (!url) {
smb_error(-S_OUT_OF_MEM);
}
if (smbc_init(smb_auth, 0)) {
smb_error(errno);
};
dir = smbc_opendir(url);
if (dir >= 0) {
smb_directory(dir, conn->uri);
} else {
const int errno_from_opendir = errno;
char buf[READ_SIZE];
struct stat sb;
int r, res, fdout;
int file = smbc_open(url, O_RDONLY, 0);
if (file < 0) {
/* If we're opening the list of shares without
* proper authentication, then smbc_opendir
* fails with EACCES and smbc_open fails with
* ENOENT. In this case, return the EACCES so
* that the parent ELinks process will prompt
* for credentials. */
if (errno == ENOENT && errno_from_opendir == EACCES)
errno = errno_from_opendir;
smb_error(errno);
}
res = smbc_fstat(file, &sb);
if (res) {
smb_error(res);
}
/* filesize */
fprintf(header_out, "%" OFF_PRINT_FORMAT,
(off_print_T) sb.st_size);
fclose(header_out);
fdout = fileno(data_out);
while ((r = smbc_read(file, buf, READ_SIZE)) > 0) {
if (safe_write(fdout, buf, r) <= 0)
break;
}
smbc_close(file);
exit(0);
}
}
#undef READ_SIZE
/* Kill the current connection and ask for a username/password for the next
* try. */
static void
prompt_username_pw(struct connection *conn)
{
add_auth_entry(conn->uri, "Samba", NULL, NULL, 0);
abort_connection(conn, S_OK);
}
static void
smb_got_error(struct socket *socket, struct read_buffer *rb)
{
int len = rb->length;
struct connection *conn = socket->conn;
int error;
if (len < 0) {
abort_connection(conn, -errno);
return;
}
/* There should be free space in the buffer, because
* @alloc_read_buffer allocated several kibibytes, and the
* child process wrote only an integer and a newline to the
* pipe. */
assert(rb->freespace >= 1);
if_assert_failed {
abort_connection(conn, S_INTERNAL);
return;
}
rb->data[len] = '\0';
error = atoi(rb->data);
kill_buffer_data(rb, len);
switch (error) {
case EACCES:
prompt_username_pw(conn);
break;
default:
abort_connection(conn, -error);
break;
}
}
static void
smb_got_data(struct socket *socket, struct read_buffer *rb)
{
int len = rb->length;
struct connection *conn = socket->conn;
if (len < 0) {
abort_connection(conn, -errno);
return;
}
if (!len) {
abort_connection(conn, S_OK);
return;
}
socket->state = SOCKET_END_ONCLOSE;
conn->received += len;
if (add_fragment(conn->cached, conn->from, rb->data, len) == 1)
conn->tries = 0;
conn->from += len;
kill_buffer_data(rb, len);
read_from_socket(socket, rb, S_TRANS, smb_got_data);
}
static void
smb_got_header(struct socket *socket, struct read_buffer *rb)
{
struct connection *conn = socket->conn;
struct read_buffer *buf;
int error = 0;
conn->cached = get_cache_entry(conn->uri);
if (!conn->cached) {
/* Even though these are pipes rather than real
* sockets, call close_socket instead of close, to
* ensure that abort_connection won't try to close the
* file descriptors again. (Could we skip the calls
* and assume abort_connection will do them?) */
close_socket(socket);
close_socket(conn->data_socket);
abort_connection(conn, S_OUT_OF_MEM);
return;
}
socket->state = SOCKET_END_ONCLOSE;
if (rb->length > 0) {
unsigned char *ctype = memacpy(rb->data, rb->length);
if (ctype && *ctype) {
if (!strcmp(ctype, "text/x-error")) {
error = 1;
mem_free(ctype);
} else {
if (ctype[0] >= '0' && ctype[0] <= '9') {
#ifdef HAVE_ATOLL
conn->est_length = (off_t)atoll(ctype);
#else
conn->est_length = (off_t)atol(ctype);
#endif
mem_free(ctype);
/* avoid error */
if (!conn->est_length) {
abort_connection(conn, S_OK);
return;
}
}
else mem_free_set(&conn->cached->content_type, ctype);
}
} else {
mem_free_if(ctype);
}
}
buf = alloc_read_buffer(conn->data_socket);
if (!buf) {
close_socket(socket);
close_socket(conn->data_socket);
abort_connection(conn, S_OUT_OF_MEM);
return;
}
if (error) {
mem_free_set(&conn->cached->content_type, stracpy("text/html"));
read_from_socket(conn->data_socket, buf, S_CONN, smb_got_error);
} else {
read_from_socket(conn->data_socket, buf, S_CONN, smb_got_data);
}
}
static void
close_all_fds_but_two(int header, int data)
{
int n;
int max = 1024;
#ifdef RLIMIT_NOFILE
struct rlimit lim;
if (!getrlimit(RLIMIT_NOFILE, &lim))
max = lim.rlim_max;
#endif
for (n = 3; n < max; n++) {
if (n != header && n != data) close(n);
}
}
void
smb_protocol_handler(struct connection *conn)
{
int smb_pipe[2] = { -1, -1 };
int header_pipe[2] = { -1, -1 };
pid_t cpid;
if (c_pipe(smb_pipe) || c_pipe(header_pipe)) {
int s_errno = errno;
if (smb_pipe[0] >= 0) close(smb_pipe[0]);
if (smb_pipe[1] >= 0) close(smb_pipe[1]);
if (header_pipe[0] >= 0) close(header_pipe[0]);
if (header_pipe[1] >= 0) close(header_pipe[1]);
abort_connection(conn, -s_errno);
return;
}
conn->from = 0;
conn->unrestartable = 1;
find_auth(conn->uri); /* remember username and password */
cpid = fork();
if (cpid == -1) {
int s_errno = errno;
close(smb_pipe[0]);
close(smb_pipe[1]);
close(header_pipe[0]);
close(header_pipe[1]);
retry_connection(conn, -s_errno);
return;
}
if (!cpid) {
dup2(open("/dev/null", O_RDONLY), 0);
close(1);
close(2);
data_out = fdopen(smb_pipe[1], "w");
header_out = fdopen(header_pipe[1], "w");
if (!data_out || !header_out) exit(1);
close(smb_pipe[0]);
close(header_pipe[0]);
/* There may be outgoing data in stdio buffers
* inherited from the parent process. The parent
* process is going to write this data, so the child
* process must not do that. Closing the file
* descriptors ensures this.
*
* FIXME: If something opens more files and gets the
* same file descriptors and does not close them
* before exit(), then stdio may attempt to write the
* buffers to the wrong files. This might happen for
* example if libsmbclient calls syslog(). */
close_all_fds_but_two(smb_pipe[1], header_pipe[1]);
do_smb(conn);
} else {
struct read_buffer *buf2;
conn->data_socket->fd = smb_pipe[0];
conn->socket->fd = header_pipe[0];
set_nonblocking_fd(conn->data_socket->fd);
set_nonblocking_fd(conn->socket->fd);
close(smb_pipe[1]);
close(header_pipe[1]);
buf2 = alloc_read_buffer(conn->socket);
if (!buf2) {
close_socket(conn->data_socket);
close_socket(conn->socket);
abort_connection(conn, S_OUT_OF_MEM);
return;
}
read_from_socket(conn->socket, buf2, S_CONN, smb_got_header);
}
}