From d399c809f7067758c73381b101b2504be19bb5d6 Mon Sep 17 00:00:00 2001 From: Witold Filipczyk Date: Wed, 24 Jan 2007 18:40:28 +0100 Subject: [PATCH] The SMB protocol handling using libsmbclient. --- configure.in | 40 ++-- src/protocol/common.c | 3 + src/protocol/smb/Makefile | 2 +- src/protocol/smb/smb2.c | 452 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 478 insertions(+), 19 deletions(-) create mode 100644 src/protocol/smb/smb2.c diff --git a/configure.in b/configure.in index 0426c414..cba0c815 100644 --- a/configure.in +++ b/configure.in @@ -277,7 +277,7 @@ AC_PROG_GCC_TRADITIONAL AC_FUNC_MEMCMP AC_FUNC_MMAP AC_FUNC_STRFTIME -AC_CHECK_FUNCS(cfmakeraw gethostbyaddr herror strerror) +AC_CHECK_FUNCS(atoll cfmakeraw gethostbyaddr herror strerror) AC_CHECK_FUNCS(popen uname access chmod alarm timegm mremap) AC_CHECK_FUNCS(strcasecmp strncasecmp strcasestr strstr strchr strrchr) AC_CHECK_FUNCS(memmove bcopy stpcpy strdup index isdigit mempcpy memrchr) @@ -1161,13 +1161,6 @@ AC_CHECK_HEADERS(execinfo.h, HAVE_EXECINFO=yes, HAVE_EXECINFO=no) # possible checks for other system-specific means go here -dnl =================================================================== -dnl SMB protocol support. -dnl =================================================================== - -AC_CHECK_PROG(HAVE_SMBCLIENT, smbclient, yes, no) - - dnl =================================================================== dnl Gettext grey zone. Beware. dnl =================================================================== @@ -1260,17 +1253,28 @@ EL_ARG_ENABLE(CONFIG_GOPHER, gopher, [Gopher protocol], EL_ARG_ENABLE(CONFIG_NNTP, nntp, [NNTP protocol], [ --enable-nntp enable nntp protocol support]) -dnl Force disable SMB before EL_ARG_DEPEND so that it logs the correct value. -if test "${enable_smb-no}" != no || test "${CONFIG_SMB-no}" != no; then - AC_MSG_WARN([Forcing --disable-smb because of vulnerability CVE-2006-5925. -If you want to use SMB, please see bug 844.]) +dnl =================================================================== +dnl SMB protocol support. +dnl =================================================================== +EL_SAVE_FLAGS + +if test "x${enable_smb}" != xno; then + AC_CHECK_HEADERS(libsmbclient.h, HAVE_SMBCLIENT=yes, HAVE_SMBCLIENT=no) + + if test "$HAVE_SMBCLIENT" = yes; then + AC_CHECK_LIB(smbclient, smbc_init, HAVE_SMBCLIENT=yes, HAVE_SMBCLIENT=no) + if test "$HAVE_SMBCLIENT" = yes; then + LIBS="$LIBS -lsmbclient" + fi + fi +fi + +EL_ARG_DEPEND(CONFIG_SMB, smb, [HAVE_SMBCLIENT:yes], [Samba protocol], + [ --enable-smb enable Samba protocol support]) + +if test "x$CONFIG_SMB" = xno; then + EL_RESTORE_FLAGS fi -enable_smb=no -CONFIG_SMB=no -EL_ARG_DEPEND(CONFIG_SMB, smb, [HAVE_SMBCLIENT:yes], [SMB protocol], - [ --enable-smb not currently allowed]) -dnl EL_ARG_DEPEND(CONFIG_SMB, smb, [HAVE_SMBCLIENT:yes], [SMB protocol], -dnl [ --disable-smb disable SMB protocol support (requires smbclient)]) EL_ARG_ENABLE(CONFIG_MOUSE, mouse, [Mouse handling], diff --git a/src/protocol/common.c b/src/protocol/common.c index 099b0adf..f3f1bb28 100644 --- a/src/protocol/common.c +++ b/src/protocol/common.c @@ -109,6 +109,9 @@ init_directory_listing(struct string *page, struct uri *uri) case PROTOCOL_GOPHER: info = "Gopher"; break; + case PROTOCOL_SMB: + info = "Samba"; + break; default: info = "?"; } diff --git a/src/protocol/smb/Makefile b/src/protocol/smb/Makefile index ef1f8b6f..9d49ef33 100644 --- a/src/protocol/smb/Makefile +++ b/src/protocol/smb/Makefile @@ -1,6 +1,6 @@ top_builddir=../../.. include $(top_builddir)/Makefile.config -OBJS = smb.o +OBJS = smb2.o include $(top_srcdir)/Makefile.lib diff --git a/src/protocol/smb/smb2.c b/src/protocol/smb/smb2.c new file mode 100644 index 00000000..9dd14625 --- /dev/null +++ b/src/protocol/smb/smb2.c @@ -0,0 +1,452 @@ +/* SMB protocol implementation */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* Needed for asprintf() */ +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef HAVE_LIBSMBCLIENT_H +#include +#endif +#include +#include +#include +#ifdef HAVE_FCNTL_H +#include /* OS/2 needs this after sys/types.h */ +#endif +#ifdef HAVE_UNISTD_H +#include +#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/snprintf.h" +#include "util/string.h" + +/* These options are not used. */ +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 +); + +static void +smb_error(int error) +{ + fprintf(stderr, "text/x-error"); + printf("%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 +display_entry(struct smbc_dirent *entry, unsigned char dircolor[]) +{ + switch (entry->smbc_type) { + case SMBC_WORKGROUP: + printf("", entry->name); + if (*dircolor) { + printf("", dircolor); + } + printf("%s", entry->name); + if (*dircolor) { + printf(""); + } + puts(" WORKGROUP"); + break; + case SMBC_SERVER: + printf("", entry->name); + if (*dircolor) { + printf("", dircolor); + } + printf("%s", entry->name); + if (*dircolor) { + printf(""); + } + puts(" SERVER"); + break; + case SMBC_FILE_SHARE: + printf("", entry->name); + if (*dircolor) { + printf("", dircolor); + } + printf("%s", entry->name); + if (*dircolor) { + printf(""); + } + puts(" FILE SHARE"); + case SMBC_PRINTER_SHARE: + printf("%s PRINTER\n", entry->name); + break; + case SMBC_COMMS_SHARE: + printf("%s COMM\n", entry->name); + break; + case SMBC_IPC_SHARE: + printf("%s IPC\n", entry->name); + break; + case SMBC_DIR: + printf("", entry->name); + if (*dircolor) { + printf("", dircolor); + } + printf("%s", entry->name); + if (*dircolor) { + printf(""); + } + puts(""); + break; + case SMBC_LINK: + printf("%s Link\n", entry->name, entry->name); + break; + case SMBC_FILE: + printf("%s\n", entry->name, entry->name); + break; + default: + /* unknown type */ + break; + } +} + +static void +sort_and_display_entries(int dir, 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; + int length = sizeof(*fentry) + fentry->namelen; + + if (!strcmp(fentry->name, ".")) + continue; + + new_entry = mem_alloc(length); + if (!new_entry) + continue; + new_table = mem_realloc(table, (size + 1) * sizeof(*table)); + if (!new_table) + continue; + memcpy(new_entry, fentry, length); + table = new_table; + table[size] = new_entry; + size++; + } + qsort(table, size, sizeof(*table), + (int (*)(const void *, const void *)) 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); + } + + fprintf(stderr, "text/html"); + fclose(stderr); + + puts(buf.source); + + if (get_opt_bool("document.browse.links.color_dirs")) { + color_to_string(get_opt_color("document.colors.dirs"), + (unsigned char *) &dircolor); + } + + sort_and_display_entries(dir, dircolor); + puts("
"); + 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) +{ + unsigned char url_data[1024]; + struct uri *uri = conn->uri; + struct auth_entry *auth = find_auth(uri); + unsigned char *url; + int dir; + + if ((uri->userlen && uri->passwordlen) || !auth || !auth->valid) { + url = get_uri_string(uri, URI_BASE); + } else { + snprintf(url_data, 1024, "smb://%s:%s@%s", auth->user, auth->password, + get_uri_string(uri, URI_HOST | URI_PORT | URI_DATA)); + url = url_data; + } + + 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 { + char buf[READ_SIZE]; + struct stat sb; + int r, res; + int file = smbc_open(url, O_RDONLY, 0); + + if (file < 0) { + smb_error(errno); + } + + res = smbc_fstat(file, &sb); + if (res) { + smb_error(res); + } + /* filesize */ + fprintf(stderr, "%" OFF_T_FORMAT, sb.st_size); + fclose(stderr); + + while ((r = smbc_read(file, buf, READ_SIZE)) > 0) { + if (safe_write(STDOUT_FILENO, 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; + } + + 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) { + close(socket->fd); + close(conn->data_socket->fd); + 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)atoi(ctype); +#endif + mem_free(ctype); + } + 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->fd); + close(conn->data_socket->fd); + 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); + } +} + +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; + + 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(smb_pipe[1], 1); + dup2(open("/dev/null", O_RDONLY), 0); + dup2(header_pipe[1], 2); + close(smb_pipe[0]); + close(header_pipe[0]); + + close_all_non_term_fd(); + do_smb(conn); + + } else { + struct read_buffer *buf2; + + conn->data_socket->fd = smb_pipe[0]; + conn->socket->fd = header_pipe[0]; + close(smb_pipe[1]); + close(header_pipe[1]); + buf2 = alloc_read_buffer(conn->socket); + if (!buf2) { + close(smb_pipe[0]); + close(header_pipe[0]); + abort_connection(conn, S_OUT_OF_MEM); + return; + } + read_from_socket(conn->socket, buf2, S_CONN, smb_got_header); + } +}