/* Unix SMB/CIFS implementation. Copyright (C) Stefan Metzmacher 2009 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 "../util/tevent_unix.h" #include "../lib/tsocket/tsocket.h" #include "../lib/tsocket/tsocket_internal.h" #include "../librpc/gen_ndr/ndr_named_pipe_auth.h" #include "../libcli/named_pipe_auth/npa_tstream.h" #include "libcli/raw/smb.h" static const struct tstream_context_ops tstream_npa_ops; struct tstream_npa { struct tstream_context *unix_stream; uint16_t file_type; struct iovec pending; }; struct tstream_npa_connect_state { struct { struct tevent_context *ev; struct smb_iconv_convenience *smb_iconv_c; } caller; const char *unix_path; struct tsocket_address *unix_laddr; struct tsocket_address *unix_raddr; struct tstream_context *unix_stream; struct named_pipe_auth_req auth_req; DATA_BLOB auth_req_blob; struct iovec auth_req_iov; struct named_pipe_auth_rep auth_rep; DATA_BLOB auth_rep_blob; }; static void tstream_npa_connect_unix_done(struct tevent_req *subreq); struct tevent_req *tstream_npa_connect_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct smb_iconv_convenience *smb_iconv_c, const char *directory, const char *npipe, const struct tsocket_address *client, const char *client_name_in, const struct tsocket_address *server, const char *server_name, const struct netr_SamInfo3 *info3, DATA_BLOB session_key) { struct tevent_req *req; struct tstream_npa_connect_state *state; struct tevent_req *subreq; int ret; enum ndr_err_code ndr_err; req = tevent_req_create(mem_ctx, &state, struct tstream_npa_connect_state); if (!req) { return NULL; } state->caller.ev = ev; state->caller.smb_iconv_c = smb_iconv_c; state->unix_path = talloc_asprintf(state, "%s/%s", directory, npipe); if (tevent_req_nomem(state->unix_path, req)) { goto post; } ret = tsocket_address_unix_from_path(state, "", &state->unix_laddr); if (ret == -1) { tevent_req_error(req, errno); goto post; } ret = tsocket_address_unix_from_path(state, state->unix_path, &state->unix_raddr); if (ret == -1) { tevent_req_error(req, errno); goto post; } ZERO_STRUCT(state->auth_req); if (client) { struct named_pipe_auth_req_info2 *info2; if (!server) { tevent_req_error(req, EINVAL); goto post; } state->auth_req.level = 2; info2 = &state->auth_req.info.info2; info2->client_name = client_name_in; info2->client_addr = tsocket_address_inet_addr_string(client, state); if (!info2->client_addr) { /* errno might be EINVAL */ tevent_req_error(req, errno); goto post; } info2->client_port = tsocket_address_inet_port(client); if (!info2->client_name) { info2->client_name = info2->client_addr; } info2->server_addr = tsocket_address_inet_addr_string(server, state); if (!info2->server_addr) { /* errno might be EINVAL */ tevent_req_error(req, errno); goto post; } info2->server_port = tsocket_address_inet_port(server); if (!info2->server_name) { info2->server_name = info2->server_addr; } info2->sam_info3 = discard_const_p(struct netr_SamInfo3, info3); info2->session_key_length = session_key.length; info2->session_key = session_key.data; } else if (info3) { state->auth_req.level = 1; state->auth_req.info.info1 = *info3; } else { state->auth_req.level = 0; } if (DEBUGLVL(10)) { NDR_PRINT_DEBUG(named_pipe_auth_req, &state->auth_req); } ndr_err = ndr_push_struct_blob(&state->auth_req_blob, state, smb_iconv_c, &state->auth_req, (ndr_push_flags_fn_t)ndr_push_named_pipe_auth_req); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { tevent_req_error(req, EINVAL); goto post; } state->auth_req_iov.iov_base = state->auth_req_blob.data; state->auth_req_iov.iov_len = state->auth_req_blob.length; subreq = tstream_unix_connect_send(state, state->caller.ev, state->unix_laddr, state->unix_raddr); if (tevent_req_nomem(subreq, req)) { goto post; } tevent_req_set_callback(subreq, tstream_npa_connect_unix_done, req); return req; post: tevent_req_post(req, ev); return req; } static void tstream_npa_connect_writev_done(struct tevent_req *subreq); static void tstream_npa_connect_unix_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct tstream_npa_connect_state *state = tevent_req_data(req, struct tstream_npa_connect_state); int ret; int sys_errno; ret = tstream_unix_connect_recv(subreq, &sys_errno, state, &state->unix_stream); TALLOC_FREE(subreq); if (ret == -1) { tevent_req_error(req, sys_errno); return; } subreq = tstream_writev_send(state, state->caller.ev, state->unix_stream, &state->auth_req_iov, 1); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, tstream_npa_connect_writev_done, req); } static int tstream_npa_connect_next_vector(struct tstream_context *unix_stream, void *private_data, TALLOC_CTX *mem_ctx, struct iovec **_vector, size_t *_count); static void tstream_npa_connect_readv_done(struct tevent_req *subreq); static void tstream_npa_connect_writev_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct tstream_npa_connect_state *state = tevent_req_data(req, struct tstream_npa_connect_state); int ret; int sys_errno; ret = tstream_writev_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret == -1) { tevent_req_error(req, sys_errno); return; } state->auth_rep_blob = data_blob_const(NULL, 0); subreq = tstream_readv_pdu_send(state, state->caller.ev, state->unix_stream, tstream_npa_connect_next_vector, state); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, tstream_npa_connect_readv_done, req); } static int tstream_npa_connect_next_vector(struct tstream_context *unix_stream, void *private_data, TALLOC_CTX *mem_ctx, struct iovec **_vector, size_t *_count) { struct tstream_npa_connect_state *state = talloc_get_type_abort(private_data, struct tstream_npa_connect_state); struct iovec *vector; size_t count; off_t ofs = 0; if (state->auth_rep_blob.length == 0) { state->auth_rep_blob = data_blob_talloc(state, NULL, 4); if (!state->auth_rep_blob.data) { return -1; } } else if (state->auth_rep_blob.length == 4) { uint32_t msg_len; ofs = 4; msg_len = RIVAL(state->auth_rep_blob.data, 0); if (msg_len > 0x00FFFFFF) { errno = EMSGSIZE; return -1; } if (msg_len == 0) { errno = EMSGSIZE; return -1; } msg_len += ofs; state->auth_rep_blob.data = talloc_realloc(state, state->auth_rep_blob.data, uint8_t, msg_len); if (!state->auth_rep_blob.data) { return -1; } state->auth_rep_blob.length = msg_len; } else { *_vector = NULL; *_count = 0; return 0; } /* we need to get a message header */ vector = talloc_array(mem_ctx, struct iovec, 1); if (!vector) { return -1; } vector[0].iov_base = state->auth_rep_blob.data + ofs; vector[0].iov_len = state->auth_rep_blob.length - ofs; count = 1; *_vector = vector; *_count = count; return 0; } static void tstream_npa_connect_readv_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct tstream_npa_connect_state *state = tevent_req_data(req, struct tstream_npa_connect_state); int ret; int sys_errno; enum ndr_err_code ndr_err; ret = tstream_readv_pdu_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret == -1) { tevent_req_error(req, sys_errno); return; } DEBUG(10,("name_pipe_auth_rep(client)[%u]\n", (uint32_t)state->auth_rep_blob.length)); dump_data(11, state->auth_rep_blob.data, state->auth_rep_blob.length); ndr_err = ndr_pull_struct_blob( &state->auth_rep_blob, state, state->caller.smb_iconv_c, &state->auth_rep, (ndr_pull_flags_fn_t)ndr_pull_named_pipe_auth_rep); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { DEBUG(0, ("ndr_pull_named_pipe_auth_rep failed: %s\n", ndr_map_error2string(ndr_err))); tevent_req_error(req, EIO); return; } if (DEBUGLVL(10)) { NDR_PRINT_DEBUG(named_pipe_auth_rep, &state->auth_rep); } if (state->auth_rep.length < 16) { DEBUG(0, ("req invalid length: %u < 16\n", state->auth_rep.length)); tevent_req_error(req, EIO); return; } if (strcmp(NAMED_PIPE_AUTH_MAGIC, state->auth_rep.magic) != 0) { DEBUG(0, ("req invalid magic: %s != %s\n", state->auth_rep.magic, NAMED_PIPE_AUTH_MAGIC)); tevent_req_error(req, EIO); return; } if (!NT_STATUS_IS_OK(state->auth_rep.status)) { DEBUG(0, ("req failed: %s\n", nt_errstr(state->auth_rep.status))); tevent_req_error(req, EACCES); return; } if (state->auth_rep.level != state->auth_req.level) { DEBUG(0, ("req invalid level: %u != %u\n", state->auth_rep.level, state->auth_req.level)); tevent_req_error(req, EIO); return; } tevent_req_done(req); } int _tstream_npa_connect_recv(struct tevent_req *req, int *perrno, TALLOC_CTX *mem_ctx, struct tstream_context **_stream, uint16_t *_file_type, uint16_t *_device_state, uint64_t *_allocation_size, const char *location) { struct tstream_npa_connect_state *state = tevent_req_data(req, struct tstream_npa_connect_state); struct tstream_context *stream; struct tstream_npa *npas; uint16_t device_state = 0; uint64_t allocation_size = 0; if (tevent_req_is_unix_error(req, perrno)) { tevent_req_received(req); return -1; } stream = tstream_context_create(mem_ctx, &tstream_npa_ops, &npas, struct tstream_npa, location); if (!stream) { return -1; } ZERO_STRUCTP(npas); npas->unix_stream = talloc_move(stream, &state->unix_stream); switch (state->auth_rep.level) { case 0: case 1: npas->file_type = FILE_TYPE_BYTE_MODE_PIPE; device_state = 0x00ff; allocation_size = 2048; break; case 2: npas->file_type = state->auth_rep.info.info2.file_type; device_state = state->auth_rep.info.info2.device_state; allocation_size = state->auth_rep.info.info2.allocation_size; break; } *_stream = stream; *_file_type = npas->file_type; *_device_state = device_state; *_allocation_size = allocation_size; tevent_req_received(req); return 0; } static ssize_t tstream_npa_pending_bytes(struct tstream_context *stream) { struct tstream_npa *npas = tstream_context_data(stream, struct tstream_npa); ssize_t ret; if (!npas->unix_stream) { errno = ENOTCONN; return -1; } switch (npas->file_type) { case FILE_TYPE_BYTE_MODE_PIPE: ret = tstream_pending_bytes(npas->unix_stream); break; case FILE_TYPE_MESSAGE_MODE_PIPE: ret = npas->pending.iov_len; break; } return ret; } struct tstream_npa_readv_state { struct tstream_context *stream; struct iovec *vector; size_t count; /* the header for message mode */ uint8_t hdr[2]; bool wait_for_hdr; int ret; }; static void tstream_npa_readv_byte_mode_handler(struct tevent_req *subreq); static int tstream_npa_readv_next_vector(struct tstream_context *stream, void *private_data, TALLOC_CTX *mem_ctx, struct iovec **_vector, size_t *_count); static void tstream_npa_readv_msg_mode_handler(struct tevent_req *subreq); static struct tevent_req *tstream_npa_readv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *stream, struct iovec *vector, size_t count) { struct tevent_req *req; struct tstream_npa_readv_state *state; struct tstream_npa *npas = tstream_context_data(stream, struct tstream_npa); struct tevent_req *subreq; off_t ofs; size_t left; uint8_t *pbase; req = tevent_req_create(mem_ctx, &state, struct tstream_npa_readv_state); if (!req) { return NULL; } state->stream = stream; state->ret = 0; if (!npas->unix_stream) { tevent_req_error(req, ENOTCONN); goto post; } switch (npas->file_type) { case FILE_TYPE_BYTE_MODE_PIPE: state->vector = vector; state->count = count; subreq = tstream_readv_send(state, ev, npas->unix_stream, state->vector, state->count); if (tevent_req_nomem(subreq,req)) { goto post; } tevent_req_set_callback(subreq, tstream_npa_readv_byte_mode_handler, req); return req; case FILE_TYPE_MESSAGE_MODE_PIPE: /* * we make a copy of the vector and prepend a header * with the length */ state->vector = talloc_array(state, struct iovec, count); if (tevent_req_nomem(state->vector, req)) { goto post; } memcpy(state->vector, vector, sizeof(struct iovec)*count); state->count = count; /* * copy the pending buffer first */ ofs = 0; left = npas->pending.iov_len; pbase = (uint8_t *)npas->pending.iov_base; while (left > 0 && state->count > 0) { uint8_t *base; base = (uint8_t *)state->vector[0].iov_base; if (left < state->vector[0].iov_len) { memcpy(base, pbase + ofs, left); base += left; state->vector[0].iov_base = base; state->vector[0].iov_len -= left; ofs += left; left = 0; TALLOC_FREE(pbase); ZERO_STRUCT(npas->pending); break; } memcpy(base, pbase + ofs, state->vector[0].iov_len); ofs += state->vector[0].iov_len; left -= state->vector[0].iov_len; state->vector += 1; state->count -= 1; if (left == 0) { TALLOC_FREE(pbase); ZERO_STRUCT(npas->pending); break; } } if (left > 0) { memmove(pbase, pbase + ofs, left); npas->pending.iov_base = pbase; npas->pending.iov_len = left; /* * this cannot fail and even if it * fails we can handle it */ pbase = talloc_realloc(npas, pbase, uint8_t, left); if (pbase) { npas->pending.iov_base = pbase; } pbase = NULL; } state->ret += ofs; if (state->count == 0) { tevent_req_done(req); goto post; } ZERO_STRUCT(state->hdr); state->wait_for_hdr = false; subreq = tstream_readv_pdu_send(state, ev, npas->unix_stream, tstream_npa_readv_next_vector, state); if (tevent_req_nomem(subreq, req)) { goto post; } tevent_req_set_callback(subreq, tstream_npa_readv_msg_mode_handler, req); return req; } /* this can't happen */ tevent_req_error(req, EINVAL); goto post; post: tevent_req_post(req, ev); return req; } static void tstream_npa_readv_byte_mode_handler(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct tstream_npa_readv_state *state = tevent_req_data(req, struct tstream_npa_readv_state); int ret; int sys_errno; ret = tstream_readv_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret == -1) { tevent_req_error(req, sys_errno); return; } state->ret = ret; tevent_req_done(req); } static int tstream_npa_readv_next_vector(struct tstream_context *unix_stream, void *private_data, TALLOC_CTX *mem_ctx, struct iovec **_vector, size_t *_count) { struct tstream_npa_readv_state *state = talloc_get_type_abort(private_data, struct tstream_npa_readv_state); struct tstream_npa *npas = tstream_context_data(state->stream, struct tstream_npa); struct iovec *vector; size_t count; uint16_t msg_len; size_t left; if (state->count == 0) { *_vector = NULL; *_count = 0; return 0; } if (!state->wait_for_hdr) { /* we need to get a message header */ vector = talloc_array(mem_ctx, struct iovec, 1); if (!vector) { return -1; } ZERO_STRUCT(state->hdr); vector[0].iov_base = state->hdr; vector[0].iov_len = sizeof(state->hdr); count = 1; state->wait_for_hdr = true; *_vector = vector; *_count = count; return 0; } /* and now fill the callers buffers and maybe the pending buffer */ state->wait_for_hdr = false; msg_len = SVAL(state->hdr, 0); if (msg_len == 0) { errno = EIO; return -1; } state->wait_for_hdr = false; /* +1 because we may need to fill the pending buffer */ vector = talloc_array(mem_ctx, struct iovec, state->count + 1); if (!vector) { return -1; } count = 0; left = msg_len; while (left > 0 && state->count > 0) { if (left < state->vector[0].iov_len) { uint8_t *base; base = (uint8_t *)state->vector[0].iov_base; vector[count].iov_base = base; vector[count].iov_len = left; count++; base += left; state->vector[0].iov_base = base; state->vector[0].iov_len -= left; break; } vector[count] = state->vector[0]; count++; left -= state->vector[0].iov_len; state->vector += 1; state->count -= 1; } if (left > 0) { /* * if the message if longer than the buffers the caller * requested, we need to consume the rest of the message * into the pending buffer, where the next readv can * be served from. */ npas->pending.iov_base = talloc_array(npas, uint8_t, left); if (!npas->pending.iov_base) { return -1; } npas->pending.iov_len = left; vector[count] = npas->pending; count++; } state->ret += (msg_len - left); *_vector = vector; *_count = count; return 0; } static void tstream_npa_readv_msg_mode_handler(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); int ret; int sys_errno; ret = tstream_readv_pdu_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret == -1) { tevent_req_error(req, sys_errno); return; } /* * we do not set state->ret here as ret includes the headr size. * we set it in tstream_npa_readv_pdu_next_vector() */ tevent_req_done(req); } static int tstream_npa_readv_recv(struct tevent_req *req, int *perrno) { struct tstream_npa_readv_state *state = tevent_req_data(req, struct tstream_npa_readv_state); int ret; ret = tsocket_simple_int_recv(req, perrno); if (ret == 0) { ret = state->ret; } tevent_req_received(req); return ret; } struct tstream_npa_writev_state { const struct iovec *vector; size_t count; /* the header for message mode */ uint8_t hdr[2]; int ret; }; static void tstream_npa_writev_handler(struct tevent_req *subreq); static struct tevent_req *tstream_npa_writev_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *stream, const struct iovec *vector, size_t count) { struct tevent_req *req; struct tstream_npa_writev_state *state; struct tstream_npa *npas = tstream_context_data(stream, struct tstream_npa); struct tevent_req *subreq; size_t msg_len; size_t i; struct iovec *new_vector; req = tevent_req_create(mem_ctx, &state, struct tstream_npa_writev_state); if (!req) { return NULL; } state->ret = 0; if (!npas->unix_stream) { tevent_req_error(req, ENOTCONN); goto post; } switch (npas->file_type) { case FILE_TYPE_BYTE_MODE_PIPE: state->vector = vector; state->count = count; break; case FILE_TYPE_MESSAGE_MODE_PIPE: /* * we make a copy of the vector and prepend a header * with the length */ new_vector = talloc_array(state, struct iovec, count + 1); if (tevent_req_nomem(new_vector, req)) { goto post; } new_vector[0].iov_base = state->hdr; new_vector[0].iov_len = sizeof(state->hdr); memcpy(new_vector + 1, vector, sizeof(struct iovec)*count); state->vector = new_vector; state->count = count + 1; msg_len = 0; for (i=0; i < count; i++) { msg_len += vector[i].iov_len; } if (msg_len > UINT16_MAX) { tevent_req_error(req, EMSGSIZE); goto post; } SSVAL(state->hdr, 0, msg_len); break; } subreq = tstream_writev_send(state, ev, npas->unix_stream, state->vector, state->count); if (tevent_req_nomem(subreq, req)) { goto post; } tevent_req_set_callback(subreq, tstream_npa_writev_handler, req); return req; post: tevent_req_post(req, ev); return req; } static void tstream_npa_writev_handler(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct tstream_npa_writev_state *state = tevent_req_data(req, struct tstream_npa_writev_state); int ret; int sys_errno; ret = tstream_writev_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret == -1) { tevent_req_error(req, sys_errno); return; } state->ret = ret; tevent_req_done(req); } static int tstream_npa_writev_recv(struct tevent_req *req, int *perrno) { struct tstream_npa_writev_state *state = tevent_req_data(req, struct tstream_npa_writev_state); int ret; ret = tsocket_simple_int_recv(req, perrno); if (ret == 0) { ret = state->ret; } tevent_req_received(req); return ret; } struct tstream_npa_disconnect_state { struct tstream_context *stream; }; static void tstream_npa_disconnect_handler(struct tevent_req *subreq); static struct tevent_req *tstream_npa_disconnect_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *stream) { struct tstream_npa *npas = tstream_context_data(stream, struct tstream_npa); struct tevent_req *req; struct tstream_npa_disconnect_state *state; struct tevent_req *subreq; req = tevent_req_create(mem_ctx, &state, struct tstream_npa_disconnect_state); if (req == NULL) { return NULL; } state->stream = stream; if (!npas->unix_stream) { tevent_req_error(req, ENOTCONN); goto post; } subreq = tstream_disconnect_send(state, ev, npas->unix_stream); if (tevent_req_nomem(subreq, req)) { goto post; } tevent_req_set_callback(subreq, tstream_npa_disconnect_handler, req); return req; post: tevent_req_post(req, ev); return req; } static void tstream_npa_disconnect_handler(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct tstream_npa_disconnect_state *state = tevent_req_data(req, struct tstream_npa_disconnect_state); struct tstream_context *stream = state->stream; struct tstream_npa *npas = tstream_context_data(stream, struct tstream_npa); int ret; int sys_errno; ret = tstream_disconnect_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret == -1) { tevent_req_error(req, sys_errno); return; } TALLOC_FREE(npas->unix_stream); tevent_req_done(req); } static int tstream_npa_disconnect_recv(struct tevent_req *req, int *perrno) { int ret; ret = tsocket_simple_int_recv(req, perrno); tevent_req_received(req); return ret; } static const struct tstream_context_ops tstream_npa_ops = { .name = "npa", .pending_bytes = tstream_npa_pending_bytes, .readv_send = tstream_npa_readv_send, .readv_recv = tstream_npa_readv_recv, .writev_send = tstream_npa_writev_send, .writev_recv = tstream_npa_writev_recv, .disconnect_send = tstream_npa_disconnect_send, .disconnect_recv = tstream_npa_disconnect_recv, }; int _tstream_npa_existing_socket(TALLOC_CTX *mem_ctx, int fd, uint16_t file_type, struct tstream_context **_stream, const char *location) { struct tstream_context *stream; struct tstream_npa *npas; int ret; switch (file_type) { case FILE_TYPE_BYTE_MODE_PIPE: break; case FILE_TYPE_MESSAGE_MODE_PIPE: break; default: errno = EINVAL; return -1; } stream = tstream_context_create(mem_ctx, &tstream_npa_ops, &npas, struct tstream_npa, location); if (!stream) { return -1; } ZERO_STRUCTP(npas); npas->file_type = file_type; ret = tstream_bsd_existing_socket(stream, fd, &npas->unix_stream); if (ret == -1) { int saved_errno = errno; talloc_free(stream); errno = saved_errno; return -1; } *_stream = stream; return 0; }