/* Unix SMB/CIFS implementation. SMB client socket context management functions Copyright (C) Andrew Tridgell 1994-2005 Copyright (C) James Myers 2003 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "system/network.h" #include "../lib/async_req/async_sock.h" #include "../lib/util/tevent_ntstatus.h" #include "lib/events/events.h" #include "libcli/raw/libcliraw.h" #include "libcli/composite/composite.h" #include "lib/socket/socket.h" #include "libcli/resolve/resolve.h" #include "param/param.h" #include "libcli/raw/raw_proto.h" #include "../libcli/smb/read_smb.h" struct smbcli_transport_connect_state { struct tevent_context *ev; struct socket_context *sock; struct tevent_req *io_req; uint8_t *request; struct iovec iov; uint8_t *response; }; static void smbcli_transport_connect_cleanup(struct tevent_req *req, enum tevent_req_state req_state); static void smbcli_transport_connect_writev_done(struct tevent_req *subreq); static void smbcli_transport_connect_read_smb_done(struct tevent_req *subreq); static struct tevent_req *smbcli_transport_connect_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct socket_context *sock, uint16_t port, uint32_t timeout_msec, struct nbt_name *calling, struct nbt_name *called) { struct tevent_req *req; struct smbcli_transport_connect_state *state; struct tevent_req *subreq; DATA_BLOB calling_blob, called_blob; uint8_t *p; NTSTATUS status; req = tevent_req_create(mem_ctx, &state, struct smbcli_transport_connect_state); if (req == NULL) { return NULL; } state->ev = ev; state->sock = sock; if (port != 139) { tevent_req_done(req); return tevent_req_post(req, ev); } tevent_req_set_cleanup_fn(req, smbcli_transport_connect_cleanup); status = nbt_name_to_blob(state, &calling_blob, calling); if (tevent_req_nterror(req, status)) { return tevent_req_post(req, ev); } status = nbt_name_to_blob(state, &called_blob, called); if (tevent_req_nterror(req, status)) { return tevent_req_post(req, ev); } state->request = talloc_array(state, uint8_t, NBT_HDR_SIZE + called_blob.length + calling_blob.length); if (tevent_req_nomem(state->request, req)) { return tevent_req_post(req, ev); } /* put in the destination name */ p = state->request + NBT_HDR_SIZE; memcpy(p, called_blob.data, called_blob.length); p += called_blob.length; memcpy(p, calling_blob.data, calling_blob.length); p += calling_blob.length; _smb_setlen_nbt(state->request, PTR_DIFF(p, state->request) - NBT_HDR_SIZE); SCVAL(state->request, 0, NBSSrequest); state->iov.iov_len = talloc_array_length(state->request); state->iov.iov_base = (void *)state->request; subreq = writev_send(state, ev, NULL, sock->fd, true, /* err_on_readability */ &state->iov, 1); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, smbcli_transport_connect_writev_done, req); state->io_req = subreq; if (timeout_msec > 0) { struct timeval endtime; endtime = timeval_current_ofs_msec(timeout_msec); if (!tevent_req_set_endtime(req, ev, endtime)) { return tevent_req_post(req, ev); } } return req; } static void smbcli_transport_connect_cleanup(struct tevent_req *req, enum tevent_req_state req_state) { struct smbcli_transport_connect_state *state = tevent_req_data(req, struct smbcli_transport_connect_state); TALLOC_FREE(state->io_req); if (state->sock == NULL) { return; } if (state->sock->fd == -1) { return; } if (req_state == TEVENT_REQ_DONE) { /* * we keep the socket open for the caller to use */ state->sock = NULL; return; } close(state->sock->fd); state->sock->fd = -1; state->sock = NULL; } static void smbcli_transport_connect_writev_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct smbcli_transport_connect_state *state = tevent_req_data(req, struct smbcli_transport_connect_state); ssize_t ret; int err; state->io_req = NULL; ret = writev_recv(subreq, &err); TALLOC_FREE(subreq); if (ret == -1) { NTSTATUS status = map_nt_error_from_unix_common(err); tevent_req_nterror(req, status); return; } subreq = read_smb_send(state, state->ev, state->sock->fd); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, smbcli_transport_connect_read_smb_done, req); state->io_req = subreq; } static void smbcli_transport_connect_read_smb_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct smbcli_transport_connect_state *state = tevent_req_data(req, struct smbcli_transport_connect_state); ssize_t ret; int err; NTSTATUS status; uint8_t error; state->io_req = NULL; ret = read_smb_recv(subreq, state, &state->response, &err); TALLOC_FREE(subreq); if (ret == -1) { status = map_nt_error_from_unix_common(err); tevent_req_nterror(req, status); return; } if (ret < 4) { tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); return; } switch (CVAL(state->response, 0)) { case NBSSpositive: tevent_req_done(req); return; case NBSSnegative: if (ret < 5) { tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); return; } error = CVAL(state->response, 4); switch (error) { case 0x80: case 0x81: status = NT_STATUS_REMOTE_NOT_LISTENING; break; case 0x82: status = NT_STATUS_RESOURCE_NAME_NOT_FOUND; break; case 0x83: status = NT_STATUS_REMOTE_RESOURCES; break; default: status = NT_STATUS_INVALID_NETWORK_RESPONSE; break; } break; case NBSSretarget: DEBUG(1,("Warning: session retarget not supported\n")); status = NT_STATUS_NOT_SUPPORTED; break; default: status = NT_STATUS_INVALID_NETWORK_RESPONSE; break; } tevent_req_nterror(req, status); } static NTSTATUS smbcli_transport_connect_recv(struct tevent_req *req) { return tevent_req_simple_recv_ntstatus(req); } struct sock_connect_state { struct composite_context *ctx; const char *host_name; int num_ports; uint16_t *ports; const char *socket_options; struct smbcli_socket *result; struct socket_connect_multi_ex multi_ex; struct nbt_name calling; struct nbt_name called; }; /* connect a smbcli_socket context to an IP/port pair if port is 0 then choose 445 then 139 */ static struct tevent_req *smbcli_sock_establish_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct socket_context *sock, struct socket_address *addr, void *private_data) { struct sock_connect_state *state = talloc_get_type_abort(private_data, struct sock_connect_state); uint32_t timeout_msec = 15 * 1000; return smbcli_transport_connect_send(state, ev, sock, addr->port, timeout_msec, &state->calling, &state->called); } static NTSTATUS smbcli_sock_establish_recv(struct tevent_req *req) { return smbcli_transport_connect_recv(req); } static void smbcli_sock_connect_recv_conn(struct composite_context *ctx); struct composite_context *smbcli_sock_connect_send(TALLOC_CTX *mem_ctx, const char *host_addr, const char **ports, const char *host_name, struct resolve_context *resolve_ctx, struct tevent_context *event_ctx, const char *socket_options, struct nbt_name *calling, struct nbt_name *called) { struct composite_context *result, *ctx; struct sock_connect_state *state; NTSTATUS status; int i; result = talloc_zero(mem_ctx, struct composite_context); if (result == NULL) goto failed; result->state = COMPOSITE_STATE_IN_PROGRESS; result->event_ctx = event_ctx; if (result->event_ctx == NULL) goto failed; state = talloc(result, struct sock_connect_state); if (state == NULL) goto failed; state->ctx = result; result->private_data = state; state->host_name = talloc_strdup(state, host_name); if (state->host_name == NULL) goto failed; state->num_ports = str_list_length(ports); state->ports = talloc_array(state, uint16_t, state->num_ports); if (state->ports == NULL) goto failed; for (i=0;ports[i];i++) { state->ports[i] = atoi(ports[i]); } state->socket_options = talloc_reference(state, socket_options); if (!host_addr) { host_addr = host_name; } state->multi_ex.private_data = state; state->multi_ex.establish_send = smbcli_sock_establish_send; state->multi_ex.establish_recv = smbcli_sock_establish_recv; status = nbt_name_dup(state, calling, &state->calling); if (!NT_STATUS_IS_OK(status)) { goto failed; } status = nbt_name_dup(state, called, &state->called); if (!NT_STATUS_IS_OK(status)) { goto failed; } ctx = socket_connect_multi_ex_send(state, host_addr, state->num_ports, state->ports, resolve_ctx, state->ctx->event_ctx, &state->multi_ex); if (ctx == NULL) goto failed; ctx->async.fn = smbcli_sock_connect_recv_conn; ctx->async.private_data = state; return result; failed: talloc_free(result); return NULL; } static void smbcli_sock_connect_recv_conn(struct composite_context *ctx) { struct sock_connect_state *state = talloc_get_type(ctx->async.private_data, struct sock_connect_state); struct socket_context *sock; uint16_t port; state->ctx->status = socket_connect_multi_ex_recv(ctx, state, &sock, &port); if (!composite_is_ok(state->ctx)) return; state->ctx->status = socket_set_option(sock, state->socket_options, NULL); if (!composite_is_ok(state->ctx)) return; state->result = talloc_zero(state, struct smbcli_socket); if (composite_nomem(state->result, state->ctx)) return; state->result->sock = talloc_steal(state->result, sock); state->result->port = port; state->result->hostname = talloc_steal(sock, state->host_name); state->result->event.ctx = state->ctx->event_ctx; if (composite_nomem(state->result->event.ctx, state->ctx)) return; composite_done(state->ctx); } /* finish a smbcli_sock_connect_send() operation */ NTSTATUS smbcli_sock_connect_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, struct smbcli_socket **result) { NTSTATUS status = composite_wait(c); if (NT_STATUS_IS_OK(status)) { struct sock_connect_state *state = talloc_get_type(c->private_data, struct sock_connect_state); *result = talloc_steal(mem_ctx, state->result); } talloc_free(c); return status; } /* connect a smbcli_socket context to an IP/port pair if port is 0 then choose the ports listed in smb.conf (normally 445 then 139) sync version of the function */ NTSTATUS smbcli_sock_connect(TALLOC_CTX *mem_ctx, const char *host_addr, const char **ports, const char *host_name, struct resolve_context *resolve_ctx, struct tevent_context *event_ctx, const char *socket_options, struct nbt_name *calling, struct nbt_name *called, struct smbcli_socket **result) { struct composite_context *c = smbcli_sock_connect_send(mem_ctx, host_addr, ports, host_name, resolve_ctx, event_ctx, socket_options, calling, called); return smbcli_sock_connect_recv(c, mem_ctx, result); }