named_pipe_auth: implement tstream_npa_accept_existing_send/recv
[ira/wip.git] / libcli / named_pipe_auth / npa_tstream.c
index 20228a2df27e8093967db25d90ff4d58350b8c84..0834c7dda68b140c4dbc6f3358da07c93c32fbb9 100644 (file)
@@ -1077,3 +1077,458 @@ int _tstream_npa_existing_socket(TALLOC_CTX *mem_ctx,
        return 0;
 }
 
+
+struct tstream_npa_accept_state {
+       struct tevent_context *ev;
+       struct tstream_context *plain;
+       uint16_t file_type;
+       uint16_t device_state;
+       uint64_t alloc_size;
+
+       DATA_BLOB npa_blob;
+       struct iovec out_iov;
+
+       /* results */
+       NTSTATUS accept_status;
+       struct tsocket_address *client;
+       char *client_name;
+       struct tsocket_address *server;
+       char *server_name;
+       struct netr_SamInfo3 *info3;
+       DATA_BLOB session_key;
+       DATA_BLOB delegated_creds;
+};
+
+static int tstream_npa_accept_next_vector(struct tstream_context *unix_stream,
+                                         void *private_data,
+                                         TALLOC_CTX *mem_ctx,
+                                         struct iovec **_vector,
+                                         size_t *_count);
+static void tstream_npa_accept_existing_reply(struct tevent_req *subreq);
+static void tstream_npa_accept_existing_done(struct tevent_req *subreq);
+
+struct tevent_req *tstream_npa_accept_existing_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       struct tstream_context *plain,
+                                       uint16_t file_type,
+                                       uint16_t device_state,
+                                       uint64_t allocation_size)
+{
+       struct tstream_npa_accept_state *state;
+       struct tevent_req *req, *subreq;
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct tstream_npa_accept_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       switch (file_type) {
+       case FILE_TYPE_BYTE_MODE_PIPE:
+               break;
+       case FILE_TYPE_MESSAGE_MODE_PIPE:
+               break;
+       default:
+               tevent_req_error(req, EINVAL);
+               goto post;
+       }
+
+       ZERO_STRUCTP(state);
+
+       state->ev = ev;
+       state->plain = plain;
+       state->file_type = file_type;
+       state->device_state = device_state;
+       state->alloc_size = allocation_size;
+
+       /*
+        * The named pipe pdu's have the length as 8 byte (initial_read_size),
+        * named_pipe_full_request provides the pdu length then.
+        */
+       subreq = tstream_readv_pdu_send(state, ev, plain,
+                                       tstream_npa_accept_next_vector,
+                                       state);
+       if (tevent_req_nomem(subreq, req)) {
+               goto post;
+       }
+
+       tevent_req_set_callback(subreq,
+                               tstream_npa_accept_existing_reply, req);
+
+       return req;
+
+post:
+       tevent_req_post(req, ev);
+       return req;
+}
+
+static int tstream_npa_accept_next_vector(struct tstream_context *unix_stream,
+                                         void *private_data,
+                                         TALLOC_CTX *mem_ctx,
+                                         struct iovec **_vector,
+                                         size_t *_count)
+{
+       struct tstream_npa_accept_state *state =
+               talloc_get_type_abort(private_data,
+                                       struct tstream_npa_accept_state);
+       struct iovec *vector;
+       size_t count;
+       off_t ofs = 0;
+
+       if (state->npa_blob.length == 0) {
+               state->npa_blob = data_blob_talloc(state, NULL, 4);
+               if (!state->npa_blob.data) {
+                       return -1;
+               }
+       } else if (state->npa_blob.length == 4) {
+               uint32_t msg_len;
+
+               ofs = 4;
+
+               msg_len = RIVAL(state->npa_blob.data, 0);
+
+               if (msg_len > 0x00FFFFFF) {
+                       errno = EMSGSIZE;
+                       return -1;
+               }
+
+               if (msg_len == 0) {
+                       errno = EMSGSIZE;
+                       return -1;
+               }
+
+               msg_len += ofs;
+
+               state->npa_blob.data = talloc_realloc(state,
+                                                     state->npa_blob.data,
+                                                     uint8_t, msg_len);
+               if (!state->npa_blob.data) {
+                       return -1;
+               }
+               state->npa_blob.length = msg_len;
+       } else {
+               if (memcmp(&state->npa_blob.data[4],
+                          NAMED_PIPE_AUTH_MAGIC, 4) != 0) {
+                       DEBUG(0, ("Wrong protocol\n"));
+#if defined(EPROTONOSUPPORT)
+                       errno = EPROTONOSUPPORT;
+#elif defined(EPROTO)
+                       errno = EPROTO;
+#else
+                       errno = EINVAL;
+#endif
+                       return -1;
+               }
+               *_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->npa_blob.data + ofs;
+       vector[0].iov_len = state->npa_blob.length - ofs;
+       count = 1;
+
+       *_vector = vector;
+       *_count = count;
+       return 0;
+}
+
+static void tstream_npa_accept_existing_reply(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+                       tevent_req_callback_data(subreq, struct tevent_req);
+       struct tstream_npa_accept_state *state =
+                       tevent_req_data(req, struct tstream_npa_accept_state);
+       struct named_pipe_auth_req *pipe_request;
+       struct named_pipe_auth_rep pipe_reply;
+       struct named_pipe_auth_req_info3 i3;
+       enum ndr_err_code ndr_err;
+       DATA_BLOB out;
+       int sys_errno;
+       int ret;
+
+       ret = tstream_readv_pdu_recv(subreq, &sys_errno);
+       TALLOC_FREE(subreq);
+       if (ret == -1) {
+               tevent_req_error(req, sys_errno);
+               return;
+       }
+
+       DEBUG(10, ("Received packet of length %lu\n",
+                  (long)state->npa_blob.length));
+       dump_data(11, state->npa_blob.data, state->npa_blob.length);
+
+       ZERO_STRUCT(pipe_reply);
+       pipe_reply.level = 0;
+       pipe_reply.status = NT_STATUS_INTERNAL_ERROR;
+       /*
+        * TODO: check it's a root (uid == 0) pipe
+        */
+
+       pipe_request = talloc(state, struct named_pipe_auth_req);
+       if (!pipe_request) {
+               DEBUG(0, ("Out of memory!\n"));
+               goto reply;
+       }
+
+       /* parse the passed credentials */
+       ndr_err = ndr_pull_struct_blob_all(
+                       &state->npa_blob, pipe_request, pipe_request,
+                       (ndr_pull_flags_fn_t)ndr_pull_named_pipe_auth_req);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               pipe_reply.status = ndr_map_error2ntstatus(ndr_err);
+               DEBUG(2, ("Could not unmarshall named_pipe_auth_req: %s\n",
+                         nt_errstr(pipe_reply.status)));
+               goto reply;
+       }
+
+       if (DEBUGLVL(10)) {
+               NDR_PRINT_DEBUG(named_pipe_auth_req, pipe_request);
+       }
+
+       ZERO_STRUCT(i3);
+
+       switch (pipe_request->level) {
+       case 0:
+               pipe_reply.level = 0;
+               pipe_reply.status = NT_STATUS_OK;
+
+               /* we need to force byte mode in this level */
+               state->file_type = FILE_TYPE_BYTE_MODE_PIPE;
+               break;
+
+       case 1:
+               pipe_reply.level = 1;
+               pipe_reply.status = NT_STATUS_OK;
+
+               /* We must copy net3_SamInfo3, so that
+                * info3 is an actual talloc pointer, then we steal
+                * pipe_request on info3 so that all the allocated memory
+                * pointed by the structrue members is preserved */
+               state->info3 = (struct netr_SamInfo3 *)talloc_memdup(state,
+                                               &pipe_request->info.info1,
+                                               sizeof(struct netr_SamInfo3));
+               if (!state->info3) {
+                       pipe_reply.status = NT_STATUS_NO_MEMORY;
+                       DEBUG(0, ("Out of memory!\n"));
+                       goto reply;
+               }
+               talloc_move(state->info3, &pipe_request);
+
+               /* we need to force byte mode in this level */
+               state->file_type = FILE_TYPE_BYTE_MODE_PIPE;
+               break;
+
+       case 2:
+               pipe_reply.level = 2;
+               pipe_reply.status = NT_STATUS_OK;
+               pipe_reply.info.info2.file_type = state->file_type;
+               pipe_reply.info.info2.device_state = state->device_state;
+               pipe_reply.info.info2.allocation_size = state->alloc_size;
+
+               i3.client_name = pipe_request->info.info2.client_name;
+               i3.client_addr = pipe_request->info.info2.client_addr;
+               i3.client_port = pipe_request->info.info2.client_port;
+               i3.server_name = pipe_request->info.info2.server_name;
+               i3.server_addr = pipe_request->info.info2.server_addr;
+               i3.server_port = pipe_request->info.info2.server_port;
+               i3.sam_info3 = pipe_request->info.info2.sam_info3;
+               i3.session_key_length =
+                               pipe_request->info.info2.session_key_length;
+               i3.session_key = pipe_request->info.info2.session_key;
+               break;
+
+       case 3:
+               pipe_reply.level = 3;
+               pipe_reply.status = NT_STATUS_OK;
+               pipe_reply.info.info3.file_type = state->file_type;
+               pipe_reply.info.info3.device_state = state->device_state;
+               pipe_reply.info.info3.allocation_size = state->alloc_size;
+
+               i3 = pipe_request->info.info3;
+               break;
+
+       default:
+               DEBUG(0, ("Unknown level %u\n", pipe_request->level));
+               pipe_reply.level = 0;
+               pipe_reply.status = NT_STATUS_INVALID_LEVEL;
+               goto reply;
+       }
+
+       if (pipe_reply.level >=2) {
+
+               if (i3.server_addr == NULL) {
+                       pipe_reply.status = NT_STATUS_INVALID_ADDRESS;
+                       DEBUG(2, ("Missing server address\n"));
+                       goto reply;
+               }
+               if (i3.client_addr == NULL) {
+                       pipe_reply.status = NT_STATUS_INVALID_ADDRESS;
+                       DEBUG(2, ("Missing client address\n"));
+                       goto reply;
+               }
+
+               state->server_name = discard_const_p(char,
+                                       talloc_move(state, &i3.server_name));
+               ret = tsocket_address_inet_from_strings(state, "ip",
+                                                       i3.server_addr,
+                                                       i3.server_port,
+                                                       &state->server);
+               if (ret != 0) {
+                       DEBUG(2, ("Invalid server address[%s:%u] - %s\n",
+                                 i3.server_addr, i3.server_port,
+                                 strerror(errno)));
+                       pipe_reply.status = NT_STATUS_INVALID_ADDRESS;
+                       goto reply;
+               }
+
+               state->client_name = discard_const_p(char,
+                                       talloc_move(state, &i3.client_name));
+               ret = tsocket_address_inet_from_strings(state, "ip",
+                                                       i3.client_addr,
+                                                       i3.client_port,
+                                                       &state->client);
+               if (ret != 0) {
+                       DEBUG(2, ("Invalid server address[%s:%u] - %s\n",
+                                 i3.client_addr, i3.client_port,
+                                 strerror(errno)));
+                       pipe_reply.status = NT_STATUS_INVALID_ADDRESS;
+                       goto reply;
+               }
+
+               state->info3 = talloc_move(state, &i3.sam_info3);
+               state->session_key.data = talloc_move(state, &i3.session_key);
+               state->session_key.length = i3.session_key_length;
+       }
+
+       if (pipe_reply.level >= 3) {
+               state->delegated_creds.data =
+                       talloc_move(state, &i3.gssapi_delegated_creds);
+               state->delegated_creds.length =
+                       i3.gssapi_delegated_creds_length;
+       }
+
+reply:
+       /* create the output */
+       ndr_err = ndr_push_struct_blob(&out, state, &pipe_reply,
+                       (ndr_push_flags_fn_t)ndr_push_named_pipe_auth_rep);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               DEBUG(2, ("Error encoding structure: %s",
+                         ndr_map_error2string(ndr_err)));
+               tevent_req_error(req, EIO);
+               return;
+       }
+
+       DEBUG(10, ("named_pipe_auth reply[%u]\n", (unsigned)out.length));
+       dump_data(11, out.data, out.length);
+
+       if (DEBUGLVL(10)) {
+               NDR_PRINT_DEBUG(named_pipe_auth_rep, &pipe_reply);
+       }
+
+       state->accept_status = pipe_reply.status;
+
+       state->out_iov.iov_base = out.data;
+       state->out_iov.iov_len = out.length;
+
+       subreq = tstream_writev_send(state, state->ev,
+                                    state->plain,
+                                    &state->out_iov, 1);
+       if (tevent_req_nomem(subreq, req)) {
+               DEBUG(0, ("no memory for tstream_writev_send"));
+               return;
+       }
+
+       tevent_req_set_callback(subreq, tstream_npa_accept_existing_done, req);
+}
+
+static void tstream_npa_accept_existing_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+                       tevent_req_callback_data(subreq, struct tevent_req);
+       int sys_errno;
+       int ret;
+
+       ret = tstream_writev_recv(subreq, &sys_errno);
+       TALLOC_FREE(subreq);
+       if (ret == -1) {
+               tevent_req_error(req, sys_errno);
+               return;
+       }
+
+       tevent_req_done(req);
+}
+
+int _tstream_npa_accept_existing_recv(struct tevent_req *req,
+                                     int *perrno,
+                                     TALLOC_CTX *mem_ctx,
+                                     struct tstream_context **stream,
+                                     struct tsocket_address **client,
+                                     char **_client_name,
+                                     struct tsocket_address **server,
+                                     char **server_name,
+                                     struct netr_SamInfo3 **info3,
+                                     DATA_BLOB *session_key,
+                                     DATA_BLOB *delegated_creds,
+                                     const char *location)
+{
+       struct tstream_npa_accept_state *state =
+                       tevent_req_data(req, struct tstream_npa_accept_state);
+       struct tstream_npa *npas;
+       int ret;
+
+       ret = tsocket_simple_int_recv(req, perrno);
+       if (ret != 0) {
+               DEBUG(2, ("Failed to accept named pipe conection: %s\n",
+                         strerror(*perrno)));
+               tevent_req_received(req);
+               return -1;
+       }
+
+       if (!NT_STATUS_IS_OK(state->accept_status)) {
+#if defined(EPROTONOSUPPORT)
+               *perrno = EPROTONOSUPPORT;
+#elif defined(EPROTO)
+               *perrno = EPROTO;
+#else
+               *perrno = EINVAL;
+#endif
+               DEBUG(2, ("Failed to accept named pipe conection: %s => %s\n",
+                         nt_errstr(state->accept_status),
+                         strerror(*perrno)));
+               tevent_req_received(req);
+               return -1;
+       }
+
+       *stream = tstream_context_create(mem_ctx,
+                                        &tstream_npa_ops,
+                                        &npas,
+                                        struct tstream_npa,
+                                        location);
+       if (!*stream) {
+               *perrno = ENOMEM;
+               tevent_req_received(req);
+               return -1;
+       }
+       ZERO_STRUCTP(npas);
+       npas->unix_stream = state->plain;
+       npas->file_type = state->file_type;
+
+       *client = talloc_move(mem_ctx, &state->client);
+       *_client_name = talloc_move(mem_ctx, &state->client_name);
+       *server = talloc_move(mem_ctx, &state->server);
+       *server_name = talloc_move(mem_ctx, &state->server_name);
+       *info3 = talloc_move(mem_ctx, &state->info3);
+       *session_key = state->session_key;
+       talloc_move(mem_ctx, &state->session_key.data);
+       *delegated_creds = state->delegated_creds;
+       talloc_move(mem_ctx, &state->delegated_creds.data);
+
+       tevent_req_received(req);
+       return 0;
+}