The updated patch is attached. -Bill
________________________________ This e-mail contains privileged and confidential information intended for the use of the addressees named above. If you are not the intended recipient of this e-mail, you are hereby notified that you must not disseminate, copy or take any action in respect of any information contained in it. If you have received this e-mail in error, please notify the sender immediately by e-mail and immediately destroy this e-mail and its attachments.
From 56ca4c7fc4c3c36176ac241304f6232093192201 Mon Sep 17 00:00:00 2001 From: Bill Nagel <wna...@tycoint.com> Date: Fri, 3 Oct 2014 09:57:58 -0400 Subject: [PATCH] basic smb/cifs implementation --- configure.ac | 21 ++ include/curl/curl.h | 2 + lib/Makefile.inc | 4 +- lib/smb.c | 694 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/smb.h | 52 ++++ lib/smb_data.h | 196 +++++++++++++++ lib/url.c | 7 + lib/urldata.h | 5 + lib/version.c | 6 + src/tool_libinfo.c | 2 + src/tool_paramhlp.c | 2 + src/tool_setopt.c | 2 + 12 files changed, 991 insertions(+), 2 deletions(-) create mode 100644 lib/smb.c create mode 100644 lib/smb.h create mode 100644 lib/smb_data.h diff --git a/configure.ac b/configure.ac index d51c7e5..dd2504d 100644 --- a/configure.ac +++ b/configure.ac @@ -573,6 +573,21 @@ AC_HELP_STRING([--disable-imap],[Disable IMAP support]), AC_MSG_RESULT(yes) ) +AC_MSG_CHECKING([whether to support smb]) +AC_ARG_ENABLE(smb, +AC_HELP_STRING([--enable-smb],[Enable SMB/CIFS support]) +AC_HELP_STRING([--disable-smb],[Disable SMB/CIFS support]), +[ case "$enableval" in + no) + AC_MSG_RESULT(no) + AC_DEFINE(CURL_DISABLE_SMB, 1, [to disable SMB/CIFS]) + AC_SUBST(CURL_DISABLE_SMB, [1]) + ;; + *) AC_MSG_RESULT(yes) + ;; + esac ], + AC_MSG_RESULT(yes) +) AC_MSG_CHECKING([whether to support smtp]) AC_ARG_ENABLE(smtp, @@ -3441,6 +3456,12 @@ if test "x$CURL_DISABLE_IMAP" != "x1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS IMAPS" fi fi +if test "x$CURL_DISABLE_SMB" != "x1"; then + SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMB" + if test "x$SSL_ENABLED" = "x1"; then + SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMBS" + fi +fi if test "x$CURL_DISABLE_SMTP" != "x1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMTP" if test "x$SSL_ENABLED" = "x1"; then diff --git a/include/curl/curl.h b/include/curl/curl.h index d40b2db..8836959 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -803,6 +803,8 @@ typedef enum { #define CURLPROTO_RTMPS (1<<23) #define CURLPROTO_RTMPTS (1<<24) #define CURLPROTO_GOPHER (1<<25) +#define CURLPROTO_SMB (1<<26) +#define CURLPROTO_SMBS (1<<27) #define CURLPROTO_ALL (~0) /* enable everything */ /* long may be 32 or 64 bits, but we should never depend on anything else diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 462d72a..594e8bc 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -45,7 +45,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ asyn-thread.c curl_gssapi.c curl_ntlm.c curl_ntlm_wb.c \ curl_ntlm_core.c curl_ntlm_msgs.c curl_sasl.c curl_multibyte.c \ hostcheck.c bundles.c conncache.c pipeline.c dotdot.c x509asn1.c \ - http2.c curl_sasl_sspi.c + http2.c curl_sasl_sspi.c smb.c LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \ @@ -63,7 +63,7 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ curl_ntlm.h curl_gssapi.h curl_ntlm_wb.h curl_ntlm_core.h \ curl_ntlm_msgs.h curl_sasl.h curl_multibyte.h hostcheck.h bundles.h \ conncache.h curl_setup_once.h multihandle.h setup-vms.h pipeline.h \ - dotdot.h x509asn1.h http2.h sigpipe.h + dotdot.h x509asn1.h http2.h sigpipe.h smb.h LIB_RCFILES = libcurl.rc diff --git a/lib/smb.c b/lib/smb.c new file mode 100644 index 0000000..d37db82 --- /dev/null +++ b/lib/smb.c @@ -0,0 +1,694 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2014, Bill Nagel <wna...@tycoint.com>, Exacq Technologies + * Copyright (C) 2014, Daniel Stenberg, <dan...@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "smb.h" +#include "smb_data.h" +#include "urldata.h" +#include "sendf.h" +#include "multiif.h" +#include "connect.h" +#include "progress.h" +#include "transfer.h" +#include "vtls/vtls.h" +#include "curl_ntlm_core.h" + +static CURLcode smb_setup(struct connectdata *conn); +static CURLcode smb_connect(struct connectdata *conn, bool *done); +static CURLcode smb_connection_state(struct connectdata *conn, bool *done); +static CURLcode smb_request_state(struct connectdata *conn, bool *done); +static CURLcode smb_done(struct connectdata *conn, CURLcode status, + bool premature); +static CURLcode smb_disconnect(struct connectdata *conn, bool dead); +static int smb_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks); + +const struct Curl_handler Curl_handler_smb = { + "SMB", /* scheme */ + smb_setup, /* setup_connection */ + ZERO_NULL, /* do_it */ + smb_done, /* done */ + ZERO_NULL, /* do_more */ + smb_connect, /* connect_it */ + smb_connection_state, /* connecting */ + smb_request_state, /* doing */ + smb_getsock, /* proto_getsock */ + smb_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + smb_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_SMB, /* defport */ + CURLPROTO_SMB, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +#ifdef USE_SSL +const struct Curl_handler Curl_handler_smbs = { + "SMBS", /* scheme */ + smb_setup, /* setup_connection */ + ZERO_NULL, /* do_it */ + smb_done, /* done */ + ZERO_NULL, /* do_more */ + smb_connect, /* connect_it */ + smb_connection_state, /* connecting */ + smb_request_state, /* doing */ + smb_getsock, /* proto_getsock */ + smb_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + smb_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_SMBS, /* defport */ + CURLPROTO_SMBS, /* protocol */ + PROTOPT_SSL /* flags */ +}; +#endif + +#define MAX_PAYLOAD_SIZE 0x8000 +#define MAX_MESSAGE_SIZE (MAX_PAYLOAD_SIZE + 0x1000) + +enum smb_req_state { + SMB_REQUESTING, + SMB_TREE_CONNECT, + SMB_OPEN, + SMB_DOWNLOAD, + SMB_UPLOAD, + SMB_CLOSE, + SMB_TREE_DISCONNECT, + SMB_DONE, +}; + +struct smb_request { + enum smb_req_state state; + char *share; + char *path; + uint16_t tid; /* even if we connect to same tree as another request, */ + uint16_t fid; /* tid will be different */ + CURLcode result; +}; + +static CURLcode smb_setup(struct connectdata *conn) +{ + struct smb_request *req; + char *slash, *path; + + /* initialize the request state */ + conn->data->req.protop = req = calloc(1, sizeof(struct smb_request)); + if(!req) + return CURLE_OUT_OF_MEMORY; + req->state = SMB_REQUESTING; + req->result = CURLE_OK; + + /* parse the share and path */ + path = conn->data->state.path; + req->share = strdup((*path == '/' || *path == '\\') ? path + 1 : path); + if(!req->share) + return CURLE_OUT_OF_MEMORY; + slash = strchr(req->share, '/'); + if(!slash) + slash = strchr(req->share, '\\'); + if(!slash) + return CURLE_URL_MALFORMAT; + *slash++ = 0; + req->path = slash; + for(; *slash; slash++) { + if(*slash == '/') + *slash = '\\'; + } + + return CURLE_OK; +} + +static CURLcode smb_connect(struct connectdata *conn, bool *done) +{ + struct smb_conn *smbc = &conn->proto.smbc; + char *slash; + + /* initialize connection state */ + memset(smbc, 0, sizeof(*smbc)); + smbc->state = SMB_CONNECTING; + smbc->send_buf = malloc(MAX_MESSAGE_SIZE); + smbc->recv_buf = malloc(MAX_MESSAGE_SIZE); + if(!smbc->send_buf || !smbc->recv_buf) + return CURLE_OUT_OF_MEMORY; + + /* multiple requests are allow with this connection */ + connkeep(conn, "SMB default"); + + /* parse the username, domain, and password */ + slash = strchr(conn->user, '/'); + if(!slash) + slash = strchr(conn->user, '\\'); + if(slash) { + smbc->user = slash + 1; + smbc->domain = strdup(conn->user); + if(!smbc->domain) + return CURLE_OUT_OF_MEMORY; + smbc->domain[slash - conn->user] = 0; + } + else { + smbc->user = conn->user; + smbc->domain = strdup(conn->host.name); + if(!smbc->domain) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static CURLcode smb_recv_message(struct connectdata *conn, void **msg) +{ + struct smb_conn *smbc = &conn->proto.smbc; + char *buf = smbc->recv_buf; + ssize_t bytes_read, nbt_size, msg_size; + ssize_t len = MAX_MESSAGE_SIZE - smbc->got; + CURLcode result; + result = Curl_read(conn, FIRSTSOCKET, buf + smbc->got, len, &bytes_read); + if(result) + return result; + if(bytes_read == 0) + return CURLE_OK; + smbc->got += bytes_read; + if(smbc->got < 4) + return CURLE_OK; + nbt_size = ntohs(*(uint16_t*)(buf + 2)) + 4; + if(smbc->got < nbt_size) + return CURLE_OK; + msg_size = sizeof(struct smb_header); + if(nbt_size >= msg_size + 1) { + /* add word count */ + msg_size += 1 + buf[msg_size] * 2; + if(nbt_size >= msg_size + 2) { + /* add byte count */ + msg_size += 2 + buf[msg_size] + (buf[msg_size + 1] << 8); + if(nbt_size < msg_size) + return CURLE_READ_ERROR; + } + } + *msg = buf; + return CURLE_OK; +} + +static void pop_message(struct connectdata *conn) +{ + struct smb_conn *smbc = &conn->proto.smbc; + smbc->got = 0; +} + +static void format_message(struct connectdata *conn, struct smb_header *h, + uint8_t cmd, int len) +{ + struct smb_conn *smbc = &conn->proto.smbc; + struct smb_request *req = conn->data->req.protop; + memset(h, 0, sizeof(*h)); + h->nbt_length = htons(sizeof(*h) - 4 + len); + strncpy((char*)h->magic, "\xffSMB", 4); + h->command = cmd; + h->flags = SMB_FLAGS_CANONICAL_PATHNAMES | SMB_FLAGS_CASELESS_PATHNAMES; + h->flags2 = SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_KNOWS_LONG_NAME; + h->uid = smbc->uid; + h->tid = req->tid; + uint32_t pid = getpid(); + h->pid_high = pid >> 16; + h->pid = pid; +} + +static CURLcode smb_send(struct connectdata *conn, ssize_t len) +{ + struct smb_conn *smbc = &conn->proto.smbc; + ssize_t bytes_written; + CURLcode result; + result = Curl_write(conn, FIRSTSOCKET, smbc->send_buf, len, &bytes_written); + if(result) + return result; + if(bytes_written != len) { + smbc->send_size = len; + smbc->sent = bytes_written; + } + return CURLE_OK; +} + +static CURLcode smb_flush(struct connectdata *conn) +{ + struct smb_conn *smbc = &conn->proto.smbc; + ssize_t bytes_written; + ssize_t len = smbc->send_size - smbc->sent; + CURLcode result; + if(!smbc->send_size) + return CURLE_OK; + result = Curl_write(conn, FIRSTSOCKET, smbc->send_buf + smbc->sent, + len, &bytes_written); + if(result) + return result; + if(bytes_written != len) + smbc->sent += bytes_written; + else + smbc->send_size = 0; + return CURLE_OK; +} + +static CURLcode smb_send_message(struct connectdata *conn, uint8_t cmd, + const void *msg, int msg_len) +{ + struct smb_conn *smbc = &conn->proto.smbc; + format_message(conn, (struct smb_header*)smbc->send_buf, cmd, msg_len); + memcpy(smbc->send_buf + sizeof(struct smb_header), msg, msg_len); + return smb_send(conn, sizeof(struct smb_header) + msg_len); +} + +static CURLcode smb_send_negotiate(struct connectdata *conn) +{ + const char *msg = "\x00\x0c\x00\x02NT LM 0.12"; + return smb_send_message(conn, SMB_COM_NEGOTIATE, msg, 15); +}; + +static CURLcode smb_send_setup(struct connectdata *conn) +{ + struct smb_conn *smbc = &conn->proto.smbc; + struct smb_setup setup; + char *p = setup.bytes; + unsigned char lm_hash[21], lm[24]; + unsigned char nt_hash[21], nt[24]; + + ssize_t byte_count = strlen(smbc->user) + strlen(smbc->domain) + 2; + byte_count += 24 + 24; /* lm/nt response size */ + byte_count += strlen(OS) + 6; /* os and client name sizes */ + if(byte_count > sizeof(setup.bytes)) + return CURLE_FILESIZE_EXCEEDED; + + Curl_ntlm_core_mk_lm_hash(conn->data, conn->passwd, lm_hash); + Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm); +#if USE_NTRESPONSES + Curl_ntlm_core_mk_nt_hash(conn->data, conn->passwd, nt_hash); + Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); +#else + memset(nt, 0, sizeof(nt)); +#endif + + memset(&setup, 0, sizeof(setup)); + setup.word_count = 13; + setup.andx.command = SMB_COM_NO_ANDX_COMMAND; + setup.max_buffer_size = 4096; + setup.max_mpx_count = 1; + setup.vc_number = 1; + setup.session_key = smbc->session_key; + setup.capabilities = 0x18; /* large file support */ + setup.lengths[0] = 24; + setup.lengths[1] = 24; + memcpy(p, lm, 24); p += 24; + memcpy(p, nt, 24); p += 24; + strcpy(p, smbc->user); p += strlen(smbc->user) + 1; + strcpy(p, smbc->domain); p += strlen(smbc->domain) + 1; + strcpy(p, OS); p += strlen(OS) + 1; + strcpy(p, "curl"); p += 5; /* client name */ + setup.byte_count = p - setup.bytes; + return smb_send_message(conn, SMB_COM_SETUP_ANDX, &setup, + sizeof(setup) - sizeof(setup.bytes) + + setup.byte_count); +} + +static CURLcode smb_send_tree_connect(struct connectdata *conn) +{ + struct smb_request *req = conn->data->req.protop; + struct smb_tree_connect tree; + char *p = tree.bytes; + + ssize_t byte_count = strlen(conn->host.name) + strlen(req->share) + 4; + byte_count += 6; /* service string */ + if(byte_count > sizeof(tree.bytes)) + return CURLE_FILESIZE_EXCEEDED; + + memset(&tree, 0, sizeof(tree)); + tree.word_count = 4; + tree.andx.command = SMB_COM_NO_ANDX_COMMAND; + tree.pw_len = 0; + strcpy(p, "\\\\"); p += 2; + strcpy(p, conn->host.name); p += strlen(conn->host.name); + *p++ = '\\'; + strcpy(p, req->share); p += strlen(req->share) + 1; + strcpy(p, "?????"); p += 6; /* match any type of service */ + tree.byte_count = p - tree.bytes; + return smb_send_message(conn, SMB_COM_TREE_CONNECT_ANDX, &tree, + sizeof(tree) - sizeof(tree.bytes) + tree.byte_count); +} + +static CURLcode smb_send_open(struct connectdata *conn) +{ + struct smb_request *req = conn->data->req.protop; + struct smb_nt_create open; + + if((strlen(req->path) + 1) > sizeof(open.bytes)) + return CURLE_FILESIZE_EXCEEDED; + + memset(&open, 0, sizeof(open)); + open.word_count = 0x18; + open.andx.command = SMB_COM_NO_ANDX_COMMAND; + open.name_length = strlen(req->path); + open.share_access = 7; + if(conn->data->set.upload) { + open.access = 0xc0000000; /* read/write */ + open.create_disposition = 5; /* create or truncate */ + } + else { + open.access = 0x80000000; /* read */ + open.create_disposition = 1; /* open if exists */ + } + open.byte_count = open.name_length + 1; + strcpy(open.bytes, req->path); + return smb_send_message(conn, SMB_COM_NT_CREATE_ANDX, &open, + sizeof(open) - sizeof(open.bytes) + open.byte_count); +} + +static CURLcode smb_send_close(struct connectdata *conn) +{ + struct smb_request *req = conn->data->req.protop; + struct smb_close close; + memset(&close, 0, sizeof(close)); + close.word_count = 3; + close.fid = req->fid; + return smb_send_message(conn, SMB_COM_CLOSE, &close, sizeof(close)); +} + +static CURLcode smb_send_tree_disconnect(struct connectdata *conn) +{ + struct smb_tree_disconnect tree; + memset(&tree, 0, sizeof(tree)); + return smb_send_message(conn, SMB_COM_TREE_DISCONNECT, &tree, sizeof(tree)); +} + +static CURLcode smb_send_read(struct connectdata *conn) +{ + struct smb_request *req = conn->data->req.protop; + curl_off_t offset = conn->data->req.offset; + struct smb_read read; + memset(&read, 0, sizeof(read)); + read.word_count = 0xc; + read.andx.command = SMB_COM_NO_ANDX_COMMAND; + read.fid = req->fid; + read.offset = offset; + read.offset_high = offset >> 32; + read.min_bytes = MAX_PAYLOAD_SIZE; + read.max_bytes = MAX_PAYLOAD_SIZE; + return smb_send_message(conn, SMB_COM_READ_ANDX, &read, sizeof(read)); +} + +static CURLcode smb_send_write(struct connectdata *conn) +{ + struct smb_conn *smbc = &conn->proto.smbc; + struct smb_write *write = (struct smb_write*)smbc->send_buf; + struct smb_request *req = conn->data->req.protop; + curl_off_t offset = conn->data->req.offset; + CURLcode result; + int nread; + + conn->data->req.upload_fromhere = smbc->send_buf + sizeof(*write); + result = Curl_fillreadbuffer(conn, MAX_PAYLOAD_SIZE, &nread); + if(result && result != CURLE_AGAIN) + return result; + if(nread == 0) + return CURLE_OK; + + memset(write, 0, sizeof(*write)); + write->word_count = 0xe; + write->andx.command = SMB_COM_NO_ANDX_COMMAND; + write->fid = req->fid; + write->offset = offset; + write->offset_high = offset >> 32; + write->data_length = nread; + write->data_offset = sizeof(*write) - 4; + format_message(conn, &write->h, SMB_COM_WRITE_ANDX, + sizeof(*write) - sizeof(write->h) + nread); + return smb_send(conn, sizeof(*write) + nread); +} + +static CURLcode smb_send_and_recv(struct connectdata *conn, void **msg) +{ + struct smb_conn *smbc = &conn->proto.smbc; + CURLcode result; + + /* check if there is still data to send */ + if(smbc->send_size) { + result = smb_flush(conn); + if(result) + return result; + } + + /* some data was sent, but not all */ + if(smbc->send_size) + return CURLE_AGAIN; + + return smb_recv_message(conn, msg); +} + +static CURLcode smb_connection_state(struct connectdata *conn, bool *done) +{ + struct smb_conn *smbc = &conn->proto.smbc; + struct smb_negotiate_response *nrsp; + struct smb_header *h; + CURLcode result; + void *msg = NULL; + + if(smbc->state == SMB_CONNECTING) { +#ifdef USE_SSL + if((conn->handler->flags & PROTOPT_SSL)) { + bool ssl_done; + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &ssl_done); + if(result && result != CURLE_AGAIN) + return result; + if(!ssl_done) + return CURLE_OK; + } +#endif + result = smb_send_negotiate(conn); + if(result) { + connclose(conn, "SMB: failed to send negotiate message"); + return result; + } + smbc->state = SMB_NEGOTIATE; + } + + /* send previous message and check for response */ + result = smb_send_and_recv(conn, &msg); + if(result && result != CURLE_AGAIN) { + connclose(conn, "SMB: failed to communicate"); + return result; + } + if(!msg) + return CURLE_OK; + h = msg; + + switch(smbc->state) { + case SMB_NEGOTIATE: + if(h->status) { + connclose(conn, "SMB: negotiation failed"); + return CURLE_COULDNT_CONNECT; + } + nrsp = msg; + memcpy(smbc->challenge, nrsp->bytes, sizeof(smbc->challenge)); + smbc->session_key = nrsp->session_key; + result = smb_send_setup(conn); + if(result) { + connclose(conn, "SMB: failed to send setup message"); + return result; + } + smbc->state = SMB_SETUP; + break; + case SMB_SETUP: + if(h->status) { + connclose(conn, "SMB: authentication failed"); + return CURLE_LOGIN_DENIED; + } + smbc->uid = h->uid; + smbc->state = SMB_CONNECTED; + *done = true; + break; + default: + pop_message(conn); + return CURLE_OK; /* ignore */ + } + pop_message(conn); + return CURLE_OK; +} + +static CURLcode smb_request_state(struct connectdata *conn, bool *done) +{ + struct smb_request *req = conn->data->req.protop; + struct smb_header *h; + enum smb_req_state next_state = SMB_DONE; + uint16_t len, off; + CURLcode result; + void *msg = NULL; + + /* start request */ + if(req->state == SMB_REQUESTING) { + result = smb_send_tree_connect(conn); + if(result) { + connclose(conn, "SMB: failed to send tree connect message"); + return result; + } + req->state = SMB_TREE_CONNECT; + } + + /* send previous message and check for response */ + result = smb_send_and_recv(conn, &msg); + if(result && result != CURLE_AGAIN) { + connclose(conn, "SMB: failed to communicate"); + return result; + } + if(!msg) + return CURLE_OK; + h = msg; + + switch(req->state) { + case SMB_TREE_CONNECT: + if(h->status) { + req->result = CURLE_REMOTE_FILE_NOT_FOUND; + if(h->status == SMB_ERR_NOACCESS) + req->result = CURLE_REMOTE_ACCESS_DENIED; + break; + } + req->tid = h->tid; + next_state = SMB_OPEN; + break; + case SMB_OPEN: + if(h->status) { + req->result = CURLE_REMOTE_FILE_NOT_FOUND; + next_state = SMB_TREE_DISCONNECT; + break; + } + req->fid = ((struct smb_nt_create_response*)msg)->fid; + conn->data->req.offset = 0; + if(conn->data->set.upload) { + conn->data->req.size = conn->data->state.infilesize; + Curl_pgrsSetUploadSize(conn->data, conn->data->req.size); + next_state = SMB_UPLOAD; + } + else { + conn->data->req.size = + ((struct smb_nt_create_response*)msg)->end_of_file; + Curl_pgrsSetDownloadSize(conn->data, conn->data->req.size); + next_state = SMB_DOWNLOAD; + } + break; + case SMB_DOWNLOAD: + if(h->status) { + req->result = CURLE_RECV_ERROR; + next_state = SMB_CLOSE; + break; + } + len = *(uint16_t*)((char*)msg + sizeof(struct smb_header) + 11); + off = *(uint16_t*)((char*)msg + sizeof(struct smb_header) + 13); + if(len > 0) + Curl_client_write(conn, CLIENTWRITE_BODY, (char*)msg + off + 4, len); + conn->data->req.bytecount += len; + conn->data->req.offset += len; + Curl_pgrsSetDownloadCounter(conn->data, conn->data->req.bytecount); + next_state = (len < MAX_PAYLOAD_SIZE) ? SMB_CLOSE : SMB_DOWNLOAD; + break; + case SMB_UPLOAD: + if(h->status) { + req->result = CURLE_UPLOAD_FAILED; + next_state = SMB_CLOSE; + break; + } + len = *(uint16_t*)((char*)msg + sizeof(struct smb_header) + 5); + conn->data->req.bytecount += len; + conn->data->req.offset += len; + Curl_pgrsSetUploadCounter(conn->data, conn->data->req.bytecount); + if(conn->data->req.bytecount >= conn->data->req.size) + next_state = SMB_CLOSE; + else + next_state = SMB_UPLOAD; + break; + case SMB_CLOSE: + /* don't care if close failed, proceed to tree disconnect anyway */ + next_state = SMB_TREE_DISCONNECT; + break; + case SMB_TREE_DISCONNECT: + next_state = SMB_DONE; + break; + default: + pop_message(conn); + return CURLE_OK; /* ignore */ + } + + pop_message(conn); + switch(next_state) { + case SMB_OPEN: result = smb_send_open(conn); break; + case SMB_DOWNLOAD: result = smb_send_read(conn); break; + case SMB_UPLOAD: result = smb_send_write(conn); break; + case SMB_CLOSE: result = smb_send_close(conn); break; + case SMB_TREE_DISCONNECT: result = smb_send_tree_disconnect(conn); break; + case SMB_DONE: + result = req->result; + *done = true; + break; + default: + break; + } + + if(result) { + connclose(conn, "SMB: failed to send message"); + return result; + } + req->state = next_state; + return CURLE_OK; +} + +static CURLcode smb_done(struct connectdata *conn, CURLcode status, + bool premature) +{ + struct smb_request *req = conn->data->req.protop; + Curl_safefree(req->share); + Curl_safefree(conn->data->req.protop); + return status; +} + +static CURLcode smb_disconnect(struct connectdata *conn, bool dead) +{ + struct smb_conn *smbc = &conn->proto.smbc; + struct smb_request *req = conn->data->req.protop; + Curl_safefree(smbc->domain); + Curl_safefree(smbc->send_buf); + Curl_safefree(smbc->recv_buf); + /* smb_done is not always called, so cleanup the request */ + if(req) { + Curl_safefree(req->share); + Curl_safefree(conn->data->req.protop); + } + return CURLE_OK; +} + +static int smb_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks) +{ + struct smb_conn *smbc = &conn->proto.smbc; + if(!numsocks) + return GETSOCK_BLANK; + socks[0] = conn->sock[FIRSTSOCKET]; + if(smbc->send_size) + return GETSOCK_WRITESOCK(0); + return GETSOCK_READSOCK(0); +} diff --git a/lib/smb.h b/lib/smb.h new file mode 100644 index 0000000..c2aca67 --- /dev/null +++ b/lib/smb.h @@ -0,0 +1,52 @@ +#ifndef HEADER_CURL_SMB_H +#define HEADER_CURL_SMB_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2014, Bill Nagel <wna...@tycoint.com>, Exacq Technologies + * Copyright (C) 2014, Daniel Stenberg, <dan...@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +enum smb_conn_state { + SMB_NOT_CONNECTED = 0, + SMB_CONNECTING, + SMB_NEGOTIATE, + SMB_SETUP, + SMB_CONNECTED, +}; + +struct smb_conn +{ + enum smb_conn_state state; + char *user; + char *domain; + unsigned char challenge[8]; + uint32_t session_key; + uint16_t uid; + char *send_buf; + char *recv_buf; + size_t send_size; + size_t sent; + size_t got; +}; + +extern const struct Curl_handler Curl_handler_smb; +extern const struct Curl_handler Curl_handler_smbs; + +#endif /* HEADER_CURL_SMB_H */ diff --git a/lib/smb_data.h b/lib/smb_data.h new file mode 100644 index 0000000..7f05260 --- /dev/null +++ b/lib/smb_data.h @@ -0,0 +1,196 @@ +#ifndef HEADER_CURL_SMB_DATA_H +#define HEADER_CURL_SMB_DATA_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2014, Bill Nagel <wna...@tycoint.com>, Exacq Technologies + * Copyright (C) 2014, Daniel Stenberg, <dan...@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#ifdef __GNUC__ +# define PACK __attribute__((packed)) +#elif defined(_MSC_VER) +# define PACK +# pragma pack(1) +#else +# define PACK +#endif + +#define SMB_COM_CLOSE 0x04 +#define SMB_COM_READ_ANDX 0x2e +#define SMB_COM_WRITE_ANDX 0x2f +#define SMB_COM_TREE_DISCONNECT 0x71 +#define SMB_COM_NEGOTIATE 0x72 +#define SMB_COM_SETUP_ANDX 0x73 +#define SMB_COM_TREE_CONNECT_ANDX 0x75 +#define SMB_COM_NT_CREATE_ANDX 0xa2 +#define SMB_COM_NO_ANDX_COMMAND 0xff + +#define SMB_FLAGS_CANONICAL_PATHNAMES 0x10 +#define SMB_FLAGS_CASELESS_PATHNAMES 0x08 +#define SMB_FLAGS2_UNICODE_STRINGS 0x8000 +#define SMB_FLAGS2_IS_LONG_NAME 0x0040 +#define SMB_FLAGS2_KNOWS_LONG_NAME 0x0001 + +#define SMB_ERR_NOACCESS 0x00050001 + +struct smb_header { + uint8_t nbt_type; + uint8_t nbt_flags; + uint16_t nbt_length; + uint8_t magic[4]; + uint8_t command; + uint32_t status; + uint8_t flags; + uint16_t flags2; + uint16_t pid_high; + uint8_t signature[8]; + uint16_t pad; + uint16_t tid; + uint16_t pid; + uint16_t uid; + uint16_t mid; +} PACK; + +struct smb_negotiate_response { + struct smb_header h; + uint8_t word_count; + uint16_t dialect_index; + uint8_t security_mode; + uint16_t max_mpx_count; + uint16_t max_number_vcs; + uint32_t max_buffer_size; + uint32_t max_raw_size; + uint32_t session_key; + uint32_t capabilities; + uint32_t system_time_low; + uint32_t system_time_high; + uint16_t server_time_zone; + uint8_t encryption_key_length; + uint16_t byte_count; + char bytes[1]; +} PACK; + +struct andx { + uint8_t command; + uint8_t pad; + uint16_t offset; +} PACK; + +struct smb_setup { + uint8_t word_count; + struct andx andx; + uint16_t max_buffer_size; + uint16_t max_mpx_count; + uint16_t vc_number; + uint32_t session_key; + uint16_t lengths[2]; + uint32_t pad; + uint32_t capabilities; + uint16_t byte_count; + char bytes[1024]; +} PACK; + +struct smb_tree_connect { + uint8_t word_count; + struct andx andx; + uint16_t flags; + uint16_t pw_len; + uint16_t byte_count; + char bytes[1024]; +} PACK; + +struct smb_nt_create { + uint8_t word_count; + struct andx andx; + uint8_t pad; + uint16_t name_length; + uint32_t flags; + uint32_t root_fid; + uint32_t access; + uint64_t allocation_size; + uint32_t ext_file_attributes; + uint32_t share_access; + uint32_t create_disposition; + uint32_t create_options; + uint32_t impersonation_level; + uint8_t security_flags; + uint16_t byte_count; + char bytes[1024]; +} PACK; + +struct smb_nt_create_response { + struct smb_header h; + uint8_t word_count; + struct andx andx; + uint8_t op_lock_level; + uint16_t fid; + uint32_t create_disposition; + uint64_t create_time; + uint64_t last_access_time; + uint64_t last_write_time; + uint64_t last_change_time; + uint32_t ext_file_attributes; + uint64_t allocation_size; + uint64_t end_of_file; +} PACK; + +struct smb_read { + uint8_t word_count; + struct andx andx; + uint16_t fid; + uint32_t offset; + uint16_t max_bytes; + uint16_t min_bytes; + uint32_t timeout; + uint16_t remaining; + uint32_t offset_high; + uint16_t byte_count; +} PACK; + +struct smb_write { + struct smb_header h; + uint8_t word_count; + struct andx andx; + uint16_t fid; + uint32_t offset; + uint32_t timeout; + uint16_t write_mode; + uint16_t remaining; + uint16_t pad; + uint16_t data_length; + uint16_t data_offset; + uint32_t offset_high; + uint16_t byte_count; + uint8_t pad2; +} PACK; + +struct smb_close { + uint8_t word_count; + uint16_t fid; + uint32_t last_mtime; + uint16_t byte_count; +} PACK; + +struct smb_tree_disconnect { + uint8_t word_count; + uint16_t byte_count; +} PACK; + +#endif /* HEADER_CURL_SMB_DATA_H */ diff --git a/lib/url.c b/lib/url.c index 0ee6fb0..2741053 100644 --- a/lib/url.c +++ b/lib/url.c @@ -215,6 +215,13 @@ static const struct Curl_handler * const protocols[] = { #endif #endif +#ifndef CURL_DISABLE_SMB + &Curl_handler_smb, +#ifdef USE_SSL + &Curl_handler_smbs, +#endif +#endif + #ifndef CURL_DISABLE_SMTP &Curl_handler_smtp, #ifdef USE_SSL diff --git a/lib/urldata.h b/lib/urldata.h index 8594c2f..9e0ace4 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -40,6 +40,8 @@ #define PORT_IMAPS 993 #define PORT_POP3 110 #define PORT_POP3S 995 +#define PORT_SMB 445 +#define PORT_SMBS 445 #define PORT_SMTP 25 #define PORT_SMTPS 465 /* sometimes called SSMTP */ #define PORT_RTSP 554 @@ -65,6 +67,7 @@ #define PROTO_FAMILY_FTP (CURLPROTO_FTP|CURLPROTO_FTPS) #define PROTO_FAMILY_POP3 (CURLPROTO_POP3|CURLPROTO_POP3S) #define PROTO_FAMILY_SMTP (CURLPROTO_SMTP|CURLPROTO_SMTPS) +#define PROTO_FAMILY_SMB (CURLPROTO_SMB|CURLPROTO_SMBS) #define DEFAULT_CONNCACHE_SIZE 5 @@ -189,6 +192,7 @@ #include "imap.h" #include "pop3.h" +#include "smb.h" #include "smtp.h" #include "ftp.h" #include "file.h" @@ -1049,6 +1053,7 @@ struct connectdata { struct tftp_state_data *tftpc; struct imap_conn imapc; struct pop3_conn pop3c; + struct smb_conn smbc; struct smtp_conn smtpc; struct rtsp_conn rtspc; void *generic; /* RTMP and LDAP use this */ diff --git a/lib/version.c b/lib/version.c index 788f3e9..0dae1ad 100644 --- a/lib/version.c +++ b/lib/version.c @@ -216,6 +216,12 @@ static const char * const protocols[] = { #ifdef USE_LIBSSH2 "sftp", #endif +#ifndef CURL_DISABLE_SMB + "smb", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMB) + "smbs", +#endif #ifndef CURL_DISABLE_SMTP "smtp", #endif diff --git a/src/tool_libinfo.c b/src/tool_libinfo.c index 81b6680..404d76e 100644 --- a/src/tool_libinfo.c +++ b/src/tool_libinfo.c @@ -67,6 +67,8 @@ CURLcode get_libcurl_info(void) { "rtsp", CURLPROTO_RTSP }, { "scp", CURLPROTO_SCP }, { "sftp", CURLPROTO_SFTP }, + { "smb", CURLPROTO_SMB }, + { "smbs", CURLPROTO_SMBS }, { "smtp", CURLPROTO_SMTP }, { "smtps", CURLPROTO_SMTPS }, { "telnet", CURLPROTO_TELNET }, diff --git a/src/tool_paramhlp.c b/src/tool_paramhlp.c index 18da026..a851e09 100644 --- a/src/tool_paramhlp.c +++ b/src/tool_paramhlp.c @@ -268,6 +268,8 @@ long proto2num(struct OperationConfig *config, long *val, const char *str) { "imaps", CURLPROTO_IMAPS }, { "pop3", CURLPROTO_POP3 }, { "pop3s", CURLPROTO_POP3S }, + { "smb", CURLPROTO_SMB }, + { "smbs", CURLPROTO_SMBS }, { "smtp", CURLPROTO_SMTP }, { "smtps", CURLPROTO_SMTPS }, { "rtsp", CURLPROTO_RTSP }, diff --git a/src/tool_setopt.c b/src/tool_setopt.c index 62d94a6..a53fdc8 100644 --- a/src/tool_setopt.c +++ b/src/tool_setopt.c @@ -134,6 +134,8 @@ const NameValue setopt_nv_CURLPROTO[] = { NV(CURLPROTO_RTSP), NV(CURLPROTO_SCP), NV(CURLPROTO_SFTP), + NV(CURLPROTO_SMB), + NV(CURLPROTO_SMBS), NV(CURLPROTO_SMTP), NV(CURLPROTO_SMTPS), NV(CURLPROTO_TELNET), -- 1.7.9.5
------------------------------------------------------------------- List admin: http://cool.haxx.se/list/listinfo/curl-library Etiquette: http://curl.haxx.se/mail/etiquette.html