diff options
-rw-r--r-- | source4/smbd/service_named_pipe.c | 488 | ||||
-rw-r--r-- | source4/smbd/service_stream.c | 2 |
2 files changed, 279 insertions, 211 deletions
diff --git a/source4/smbd/service_named_pipe.c b/source4/smbd/service_named_pipe.c index c10f43273c6..18ae823dd27 100644 --- a/source4/smbd/service_named_pipe.c +++ b/source4/smbd/service_named_pipe.c @@ -25,9 +25,12 @@ #include "param/param.h" #include "auth/session.h" #include "auth/auth_sam_reply.h" -#include "lib/stream/packet.h" +#include "lib/socket/socket.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/util/tstream.h" #include "librpc/gen_ndr/ndr_named_pipe_auth.h" #include "system/passwd.h" +#include "system/network.h" #include "libcli/raw/smb.h" #include "auth/credentials/credentials.h" #include "auth/credentials/credentials_krb5.h" @@ -41,224 +44,293 @@ struct named_pipe_socket { struct named_pipe_connection { struct stream_connection *connection; - struct packet_context *packet; const struct named_pipe_socket *pipe_sock; - NTSTATUS status; + struct tstream_context *tstream; }; -static void named_pipe_handover_connection(void *private_data) +static void named_pipe_terminate_connection(struct named_pipe_connection *pipe_conn, const char *reason) { - struct named_pipe_connection *pipe_conn = talloc_get_type( - private_data, struct named_pipe_connection); - struct stream_connection *conn = pipe_conn->connection; + stream_terminate_connection(pipe_conn->connection, reason); +} + +static NTSTATUS named_pipe_full_request(void *private_data, DATA_BLOB blob, size_t *size) +{ + if (blob.length < 8) { + return STATUS_MORE_ENTRIES; + } + + if (memcmp(NAMED_PIPE_AUTH_MAGIC, &blob.data[4], 4) != 0) { + DEBUG(0,("named_pipe_full_request: wrong protocol\n")); + *size = blob.length; + /* the error will be handled in named_pipe_recv_auth_request */ + return NT_STATUS_OK; + } + + *size = 4 + RIVAL(blob.data, 0); + if (*size > blob.length) { + return STATUS_MORE_ENTRIES; + } + + return NT_STATUS_OK; +} - TEVENT_FD_NOT_WRITEABLE(conn->event.fde); +static void named_pipe_auth_request(struct tevent_req *subreq); - packet_set_socket(pipe_conn->packet, NULL); - packet_set_event_context(pipe_conn->packet, NULL); - packet_set_fde(pipe_conn->packet, NULL); - TALLOC_FREE(pipe_conn->packet); +static void named_pipe_accept(struct stream_connection *conn) +{ + struct named_pipe_socket *pipe_sock = talloc_get_type(conn->private_data, + struct named_pipe_socket); + struct named_pipe_connection *pipe_conn; + struct tevent_req *subreq; + int rc; - if (!NT_STATUS_IS_OK(pipe_conn->status)) { - stream_terminate_connection(conn, nt_errstr(pipe_conn->status)); + pipe_conn = talloc_zero(conn, struct named_pipe_connection); + if (pipe_conn == NULL) { + stream_terminate_connection(conn, + "named_pipe_accept: out of memory"); return; } - /* - * remove the named_pipe layer together with its packet layer - */ - conn->ops = pipe_conn->pipe_sock->ops; - conn->private_data = pipe_conn->pipe_sock->private_data; - talloc_unlink(conn, pipe_conn); + TALLOC_FREE(conn->event.fde); - /* we're now ready to start receiving events on this stream */ - TEVENT_FD_READABLE(conn->event.fde); + rc = tstream_bsd_existing_socket(pipe_conn->tstream, + socket_get_fd(conn->socket), + &pipe_conn->tstream); + if (rc < 0) { + stream_terminate_connection(conn, + "named_pipe_accept: out of memory"); + return; + } + + pipe_conn->connection = conn; + pipe_conn->pipe_sock = pipe_sock; + conn->private_data = pipe_conn; /* - * hand over to the real pipe implementation, - * now that we have setup the transport session_info + * The named pipe pdu's have the length as 8 byte (initial_read_size), + * named_pipe_full_request provides the pdu length then. */ - conn->ops->accept_connection(conn); - - DEBUG(10,("named_pipe_handover_connection[%s]: succeeded\n", - conn->ops->name)); + subreq = tstream_read_pdu_blob_send(pipe_conn, + pipe_conn->connection->event.ctx, + pipe_conn->tstream, + 8, /* initial_read_size */ + named_pipe_full_request, + pipe_conn); + if (subreq == NULL) { + named_pipe_terminate_connection(pipe_conn, + "named_pipe_accept: " + "no memory for tstream_read_pdu_blob_send"); + return; + } + tevent_req_set_callback(subreq, named_pipe_auth_request, pipe_conn); } -static NTSTATUS named_pipe_recv_auth_request(void *private_data, - DATA_BLOB req_blob) +struct named_pipe_call { + struct named_pipe_connection *pipe_conn; + DATA_BLOB in; + DATA_BLOB out; + struct iovec out_iov[1]; + NTSTATUS status; +}; + +static void named_pipe_handover_connection(struct tevent_req *subreq); + +static void named_pipe_auth_request(struct tevent_req *subreq) { - struct named_pipe_connection *pipe_conn = talloc_get_type( - private_data, struct named_pipe_connection); + struct named_pipe_connection *pipe_conn = tevent_req_callback_data(subreq, + struct named_pipe_connection); struct stream_connection *conn = pipe_conn->connection; + struct named_pipe_call *call; enum ndr_err_code ndr_err; - struct named_pipe_auth_req req; union netr_Validation val; struct auth_serversupplied_info *server_info; - struct named_pipe_auth_rep rep; - DATA_BLOB rep_blob; + struct named_pipe_auth_req pipe_request; + struct named_pipe_auth_rep pipe_reply; NTSTATUS status; - /* - * make sure nothing happens on the socket untill the - * real implementation takes over - */ - packet_recv_disable(pipe_conn->packet); + call = talloc(pipe_conn, struct named_pipe_call); + if (call == NULL) { + named_pipe_terminate_connection(pipe_conn, + "named_pipe_auth_request: " + "no memory for named_pipe_call"); + return; + } + call->pipe_conn = pipe_conn; + + status = tstream_read_pdu_blob_recv(subreq, + call, + &call->in); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + const char *reason; + + reason = talloc_asprintf(call, "named_pipe_call_loop: " + "tstream_read_pdu_blob_recv() - %s", + nt_errstr(status)); + if (reason == NULL) { + reason = nt_errstr(status); + } + + named_pipe_terminate_connection(pipe_conn, reason); + return; + } + + DEBUG(10,("Received named_pipe packet of length %lu from %s\n", + (long) call->in.length, + tsocket_address_string(pipe_conn->connection->remote_address, call))); + dump_data(11, call->in.data, call->in.length); /* * TODO: check it's a root (uid == 0) pipe */ - ZERO_STRUCT(rep); - rep.level = 0; - rep.status = NT_STATUS_INTERNAL_ERROR; - - DEBUG(10,("named_pipe_auth: req_blob.length[%u]\n", - (unsigned int)req_blob.length)); - dump_data(11, req_blob.data, req_blob.length); + ZERO_STRUCT(pipe_reply); + pipe_reply.level = 0; + pipe_reply.status = NT_STATUS_INTERNAL_ERROR; /* parse the passed credentials */ ndr_err = ndr_pull_struct_blob_all( - &req_blob, + &call->in, pipe_conn, lp_iconv_convenience(conn->lp_ctx), - &req, - (ndr_pull_flags_fn_t)ndr_pull_named_pipe_auth_req); + &pipe_request, + (ndr_pull_flags_fn_t) ndr_pull_named_pipe_auth_req); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { - rep.status = ndr_map_error2ntstatus(ndr_err); + pipe_reply.status = ndr_map_error2ntstatus(ndr_err); DEBUG(2, ("Could not unmarshall named_pipe_auth_req: %s\n", - nt_errstr(rep.status))); + nt_errstr(pipe_reply.status))); goto reply; } if (DEBUGLVL(10)) { - NDR_PRINT_DEBUG(named_pipe_auth_req, &req); + NDR_PRINT_DEBUG(named_pipe_auth_req, &pipe_request); } - if (strcmp(NAMED_PIPE_AUTH_MAGIC, req.magic) != 0) { + if (strcmp(NAMED_PIPE_AUTH_MAGIC, pipe_request.magic) != 0) { DEBUG(2, ("named_pipe_auth_req: invalid magic '%s' != %s\n", - req.magic, NAMED_PIPE_AUTH_MAGIC)); - rep.status = NT_STATUS_INVALID_PARAMETER; + pipe_request.magic, NAMED_PIPE_AUTH_MAGIC)); + pipe_reply.status = NT_STATUS_INVALID_PARAMETER; goto reply; } - switch (req.level) { + switch (pipe_request.level) { case 0: /* * anon connection, we don't create a session info * and leave it NULL */ - rep.level = 0; - rep.status = NT_STATUS_OK; + pipe_reply.level = 0; + pipe_reply.status = NT_STATUS_OK; break; case 1: - val.sam3 = &req.info.info1; - - rep.level = 1; - rep.status = make_server_info_netlogon_validation(pipe_conn, - "TODO", - 3, &val, - &server_info); - if (!NT_STATUS_IS_OK(rep.status)) { + val.sam3 = &pipe_request.info.info1; + + pipe_reply.level = 1; + pipe_reply.status = make_server_info_netlogon_validation(pipe_conn, + "TODO", + 3, &val, + &server_info); + if (!NT_STATUS_IS_OK(pipe_reply.status)) { DEBUG(2, ("make_server_info_netlogon_validation returned " - "%s\n", nt_errstr(rep.status))); + "%s\n", nt_errstr(pipe_reply.status))); goto reply; } /* setup the session_info on the connection */ - rep.status = auth_generate_session_info(conn, - conn->event.ctx, - conn->lp_ctx, - server_info, - &conn->session_info); - if (!NT_STATUS_IS_OK(rep.status)) { + pipe_reply.status = auth_generate_session_info(conn, + conn->event.ctx, + conn->lp_ctx, + server_info, + &conn->session_info); + if (!NT_STATUS_IS_OK(pipe_reply.status)) { DEBUG(2, ("auth_generate_session_info failed: %s\n", - nt_errstr(rep.status))); + nt_errstr(pipe_reply.status))); goto reply; } break; case 2: - rep.level = 2; - rep.info.info2.file_type = FILE_TYPE_MESSAGE_MODE_PIPE; - rep.info.info2.device_state = 0xff | 0x0400 | 0x0100; - rep.info.info2.allocation_size = 4096; + pipe_reply.level = 2; + pipe_reply.info.info2.file_type = FILE_TYPE_MESSAGE_MODE_PIPE; + pipe_reply.info.info2.device_state = 0xff | 0x0400 | 0x0100; + pipe_reply.info.info2.allocation_size = 4096; - if (!req.info.info2.sam_info3) { + if (pipe_request.info.info2.sam_info3 == NULL) { /* * anon connection, we don't create a session info * and leave it NULL */ - rep.status = NT_STATUS_OK; + pipe_reply.status = NT_STATUS_OK; break; } - val.sam3 = req.info.info2.sam_info3; + val.sam3 = pipe_request.info.info2.sam_info3; - rep.status = make_server_info_netlogon_validation(pipe_conn, + pipe_reply.status = make_server_info_netlogon_validation(pipe_conn, val.sam3->base.account_name.string, 3, &val, &server_info); - if (!NT_STATUS_IS_OK(rep.status)) { + if (!NT_STATUS_IS_OK(pipe_reply.status)) { DEBUG(2, ("make_server_info_netlogon_validation returned " - "%s\n", nt_errstr(rep.status))); + "%s\n", nt_errstr(pipe_reply.status))); goto reply; } /* setup the session_info on the connection */ - rep.status = auth_generate_session_info(conn, + pipe_reply.status = auth_generate_session_info(conn, conn->event.ctx, conn->lp_ctx, server_info, &conn->session_info); - if (!NT_STATUS_IS_OK(rep.status)) { + if (!NT_STATUS_IS_OK(pipe_reply.status)) { DEBUG(2, ("auth_generate_session_info failed: %s\n", - nt_errstr(rep.status))); + nt_errstr(pipe_reply.status))); goto reply; } - conn->session_info->session_key = data_blob_const(req.info.info2.session_key, - req.info.info2.session_key_length); - talloc_steal(conn->session_info, req.info.info2.session_key); + conn->session_info->session_key = data_blob_const(pipe_request.info.info2.session_key, + pipe_request.info.info2.session_key_length); + talloc_steal(conn->session_info, pipe_request.info.info2.session_key); break; case 3: - rep.level = 3; - rep.info.info3.file_type = FILE_TYPE_MESSAGE_MODE_PIPE; - rep.info.info3.device_state = 0xff | 0x0400 | 0x0100; - rep.info.info3.allocation_size = 4096; + pipe_reply.level = 3; + pipe_reply.info.info3.file_type = FILE_TYPE_MESSAGE_MODE_PIPE; + pipe_reply.info.info3.device_state = 0xff | 0x0400 | 0x0100; + pipe_reply.info.info3.allocation_size = 4096; - if (!req.info.info3.sam_info3) { + if (pipe_request.info.info3.sam_info3 == NULL) { /* * anon connection, we don't create a session info * and leave it NULL */ - rep.status = NT_STATUS_OK; + pipe_reply.status = NT_STATUS_OK; break; } - val.sam3 = req.info.info3.sam_info3; + val.sam3 = pipe_request.info.info3.sam_info3; - rep.status = make_server_info_netlogon_validation(pipe_conn, + pipe_reply.status = make_server_info_netlogon_validation(pipe_conn, val.sam3->base.account_name.string, 3, &val, &server_info); - if (!NT_STATUS_IS_OK(rep.status)) { + if (!NT_STATUS_IS_OK(pipe_reply.status)) { DEBUG(2, ("make_server_info_netlogon_validation returned " - "%s\n", nt_errstr(rep.status))); + "%s\n", nt_errstr(pipe_reply.status))); goto reply; } /* setup the session_info on the connection */ - rep.status = auth_generate_session_info(conn, - conn->event.ctx, - conn->lp_ctx, - server_info, - &conn->session_info); - if (!NT_STATUS_IS_OK(rep.status)) { + pipe_reply.status = auth_generate_session_info(conn, + conn->event.ctx, + conn->lp_ctx, + server_info, + &conn->session_info); + if (!NT_STATUS_IS_OK(pipe_reply.status)) { DEBUG(2, ("auth_generate_session_info failed: %s\n", - nt_errstr(rep.status))); + nt_errstr(pipe_reply.status))); goto reply; } - if (req.info.info3.gssapi_delegated_creds_length) { + if (pipe_request.info.info3.gssapi_delegated_creds_length) { OM_uint32 minor_status; gss_buffer_desc cred_token; gss_cred_id_t cred_handle; @@ -266,20 +338,20 @@ static NTSTATUS named_pipe_recv_auth_request(void *private_data, DEBUG(10, ("named_pipe_auth: delegated credentials supplied by client\n")); - cred_token.value = req.info.info3.gssapi_delegated_creds; - cred_token.length = req.info.info3.gssapi_delegated_creds_length; + cred_token.value = pipe_request.info.info3.gssapi_delegated_creds; + cred_token.length = pipe_request.info.info3.gssapi_delegated_creds_length; ret = gss_import_cred(&minor_status, &cred_token, &cred_handle); if (ret != GSS_S_COMPLETE) { - rep.status = NT_STATUS_INTERNAL_ERROR; + pipe_reply.status = NT_STATUS_INTERNAL_ERROR; goto reply; } conn->session_info->credentials = cli_credentials_init(conn->session_info); - if (!conn->session_info->credentials) { - rep.status = NT_STATUS_NO_MEMORY; + if (conn->session_info->credentials == NULL) { + pipe_reply.status = NT_STATUS_NO_MEMORY; goto reply; } @@ -294,7 +366,7 @@ static NTSTATUS named_pipe_recv_auth_request(void *private_data, cred_handle, CRED_SPECIFIED); if (ret) { - rep.status = NT_STATUS_INTERNAL_ERROR; + pipe_reply.status = NT_STATUS_INTERNAL_ERROR; goto reply; } @@ -303,140 +375,136 @@ static NTSTATUS named_pipe_recv_auth_request(void *private_data, CRED_MUST_USE_KERBEROS); } - conn->session_info->session_key = data_blob_const(req.info.info3.session_key, - req.info.info3.session_key_length); - talloc_steal(conn->session_info, req.info.info3.session_key); + conn->session_info->session_key = data_blob_const(pipe_request.info.info3.session_key, + pipe_request.info.info3.session_key_length); + talloc_steal(conn->session_info, pipe_request.info.info3.session_key); break; default: - DEBUG(2, ("named_pipe_auth_req: unknown level %u\n", - req.level)); - rep.level = 0; - rep.status = NT_STATUS_INVALID_LEVEL; + DEBUG(0, ("named_pipe_auth_req: unknown level %u\n", + pipe_request.level)); + pipe_reply.level = 0; + pipe_reply.status = NT_STATUS_INVALID_LEVEL; goto reply; } reply: /* create the output */ - ndr_err = ndr_push_struct_blob(&rep_blob, pipe_conn, + ndr_err = ndr_push_struct_blob(&call->out, pipe_conn, lp_iconv_convenience(conn->lp_ctx), - &rep, + &pipe_reply, (ndr_push_flags_fn_t)ndr_push_named_pipe_auth_rep); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + const char *reason; status = ndr_map_error2ntstatus(ndr_err); - DEBUG(2, ("Could not marshall named_pipe_auth_rep: %s\n", - nt_errstr(status))); - return status; + + reason = talloc_asprintf(pipe_conn, "named_pipe_auth_request: could not marshall named_pipe_auth_rep: %s\n", + nt_errstr(status)); + if (reason == NULL) { + reason = "named_pipe_auth_request: could not marshall named_pipe_auth_rep"; + } + named_pipe_terminate_connection(pipe_conn, reason); + return; } - DEBUG(10,("named_pipe_auth reply[%u]\n", (unsigned)rep_blob.length)); - dump_data(11, rep_blob.data, rep_blob.length); + DEBUG(10,("named_pipe_auth_request: named_pipe_auth reply[%u]\n", + (unsigned) call->out.length)); + dump_data(11, call->out.data, call->out.length); if (DEBUGLVL(10)) { - NDR_PRINT_DEBUG(named_pipe_auth_rep, &rep); + NDR_PRINT_DEBUG(named_pipe_auth_rep, &pipe_reply); } - pipe_conn->status = rep.status; - status = packet_send_callback(pipe_conn->packet, rep_blob, - named_pipe_handover_connection, - pipe_conn); - if (!NT_STATUS_IS_OK(status)) { - DEBUG(0, ("packet_send_callback returned %s\n", - nt_errstr(status))); - return status; + call->status = pipe_reply.status; + + call->out_iov[0].iov_base = call->out.data; + call->out_iov[0].iov_len = call->out.length; + + subreq = tstream_writev_send(call, + pipe_conn->connection->event.ctx, + pipe_conn->tstream, + call->out_iov, 1); + if (subreq == NULL) { + named_pipe_terminate_connection(pipe_conn, "named_pipe_auth_request: " + "no memory for tstream_writev_send"); + return; } - return NT_STATUS_OK; + tevent_req_set_callback(subreq, named_pipe_handover_connection, call); } -/* - called when a pipe socket becomes readable -*/ -static void named_pipe_recv(struct stream_connection *conn, uint16_t flags) +static void named_pipe_handover_connection(struct tevent_req *subreq) { - struct named_pipe_connection *pipe_conn = talloc_get_type( - conn->private_data, struct named_pipe_connection); + struct named_pipe_call *call = tevent_req_callback_data(subreq, + struct named_pipe_call); + struct named_pipe_connection *pipe_conn = call->pipe_conn; + struct stream_connection *conn = pipe_conn->connection; + int sys_errno; + int rc; + + rc = tstream_writev_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (rc == -1) { + const char *reason; + + reason = talloc_asprintf(call, "named_pipe_handover_connection: " + "tstream_writev_recv() - %d:%s", + sys_errno, strerror(sys_errno)); + if (reason == NULL) { + reason = "named_pipe_handover_connection: " + "tstream_writev_recv() failed"; + } + + named_pipe_terminate_connection(pipe_conn, reason); + return; + } + + if (!NT_STATUS_IS_OK(call->status)) { + named_pipe_terminate_connection(pipe_conn, + nt_errstr(call->status)); + return; + } + + /* + * remove the named_pipe layer together with its packet layer + */ + conn->ops = pipe_conn->pipe_sock->ops; + conn->private_data = pipe_conn->pipe_sock->private_data; + talloc_unlink(conn, pipe_conn); + + /* + * hand over to the real pipe implementation, + * now that we have setup the transport session_info + */ + conn->ops->accept_connection(conn); - DEBUG(10,("named_pipe_recv\n")); + DEBUG(10,("named_pipe_handover_connection[%s]: succeeded\n", + conn->ops->name)); - packet_recv(pipe_conn->packet); + /* we don't have to free call here as the connection got closed */ } /* - called when a pipe socket becomes writable + called when a pipe socket becomes readable */ -static void named_pipe_send(struct stream_connection *conn, uint16_t flags) +static void named_pipe_recv(struct stream_connection *conn, uint16_t flags) { struct named_pipe_connection *pipe_conn = talloc_get_type( conn->private_data, struct named_pipe_connection); - packet_queue_run(pipe_conn->packet); + named_pipe_terminate_connection(pipe_conn, + "named_pipe_recv: called"); } /* - handle socket recv errors + called when a pipe socket becomes writable */ -static void named_pipe_recv_error(void *private_data, NTSTATUS status) +static void named_pipe_send(struct stream_connection *conn, uint16_t flags) { struct named_pipe_connection *pipe_conn = talloc_get_type( - private_data, struct named_pipe_connection); - - stream_terminate_connection(pipe_conn->connection, nt_errstr(status)); -} - -static NTSTATUS named_pipe_full_request(void *private_data, DATA_BLOB blob, size_t *size) -{ - if (blob.length < 8) { - return STATUS_MORE_ENTRIES; - } - - if (memcmp(NAMED_PIPE_AUTH_MAGIC, &blob.data[4], 4) != 0) { - DEBUG(0,("named_pipe_full_request: wrong protocol\n")); - *size = blob.length; - /* the error will be handled in named_pipe_recv_auth_request */ - return NT_STATUS_OK; - } - - *size = 4 + RIVAL(blob.data, 0); - if (*size > blob.length) { - return STATUS_MORE_ENTRIES; - } - - return NT_STATUS_OK; -} - -static void named_pipe_accept(struct stream_connection *conn) -{ - struct named_pipe_socket *pipe_sock = talloc_get_type( - conn->private_data, struct named_pipe_socket); - struct named_pipe_connection *pipe_conn; - - DEBUG(5,("named_pipe_accept\n")); - - pipe_conn = talloc_zero(conn, struct named_pipe_connection); - if (!pipe_conn) { - stream_terminate_connection(conn, "out of memory"); - return; - } - - pipe_conn->packet = packet_init(pipe_conn); - if (!pipe_conn->packet) { - stream_terminate_connection(conn, "out of memory"); - return; - } - packet_set_private(pipe_conn->packet, pipe_conn); - packet_set_socket(pipe_conn->packet, conn->socket); - packet_set_callback(pipe_conn->packet, named_pipe_recv_auth_request); - packet_set_full_request(pipe_conn->packet, named_pipe_full_request); - packet_set_error_handler(pipe_conn->packet, named_pipe_recv_error); - packet_set_event_context(pipe_conn->packet, conn->event.ctx); - packet_set_fde(pipe_conn->packet, conn->event.fde); - packet_set_serialise(pipe_conn->packet); - packet_set_initial_read(pipe_conn->packet, 8); - - pipe_conn->pipe_sock = pipe_sock; + conn->private_data, struct named_pipe_connection); - pipe_conn->connection = conn; - conn->private_data = pipe_conn; + named_pipe_terminate_connection(pipe_conn, + "named_pipe_send: called"); } static const struct stream_server_ops named_pipe_stream_ops = { diff --git a/source4/smbd/service_stream.c b/source4/smbd/service_stream.c index 2d8b9559f4e..14387e9558a 100644 --- a/source4/smbd/service_stream.c +++ b/source4/smbd/service_stream.c @@ -98,7 +98,7 @@ static void stream_io_handler(struct stream_connection *conn, uint16_t flags) } } -static void stream_io_handler_fde(struct tevent_context *ev, struct tevent_fd *fde, +void stream_io_handler_fde(struct tevent_context *ev, struct tevent_fd *fde, uint16_t flags, void *private_data) { struct stream_connection *conn = talloc_get_type(private_data, |