diff options
Diffstat (limited to 'source4/smbd')
-rw-r--r-- | source4/smbd/service_named_pipe.c | 488 | ||||
-rw-r--r-- | source4/smbd/service_stream.c | 2 |
2 files changed, 211 insertions, 279 deletions
diff --git a/source4/smbd/service_named_pipe.c b/source4/smbd/service_named_pipe.c index 18ae823dd2..c10f43273c 100644 --- a/source4/smbd/service_named_pipe.c +++ b/source4/smbd/service_named_pipe.c @@ -25,12 +25,9 @@ #include "param/param.h" #include "auth/session.h" #include "auth/auth_sam_reply.h" -#include "lib/socket/socket.h" -#include "lib/tsocket/tsocket.h" -#include "libcli/util/tstream.h" +#include "lib/stream/packet.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" @@ -44,293 +41,224 @@ struct named_pipe_socket { struct named_pipe_connection { struct stream_connection *connection; + struct packet_context *packet; const struct named_pipe_socket *pipe_sock; - struct tstream_context *tstream; + NTSTATUS status; }; -static void named_pipe_terminate_connection(struct named_pipe_connection *pipe_conn, const char *reason) -{ - stream_terminate_connection(pipe_conn->connection, reason); -} - -static NTSTATUS named_pipe_full_request(void *private_data, DATA_BLOB blob, size_t *size) +static void named_pipe_handover_connection(void *private_data) { - 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; -} + struct named_pipe_connection *pipe_conn = talloc_get_type( + private_data, struct named_pipe_connection); + struct stream_connection *conn = pipe_conn->connection; -static void named_pipe_auth_request(struct tevent_req *subreq); + TEVENT_FD_NOT_WRITEABLE(conn->event.fde); -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; + 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); - pipe_conn = talloc_zero(conn, struct named_pipe_connection); - if (pipe_conn == NULL) { - stream_terminate_connection(conn, - "named_pipe_accept: out of memory"); + if (!NT_STATUS_IS_OK(pipe_conn->status)) { + stream_terminate_connection(conn, nt_errstr(pipe_conn->status)); return; } - TALLOC_FREE(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; - } + /* + * 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); - pipe_conn->connection = conn; - pipe_conn->pipe_sock = pipe_sock; - conn->private_data = pipe_conn; + /* we're now ready to start receiving events on this stream */ + TEVENT_FD_READABLE(conn->event.fde); /* - * The named pipe pdu's have the length as 8 byte (initial_read_size), - * named_pipe_full_request provides the pdu length then. + * hand over to the real pipe implementation, + * now that we have setup the transport session_info */ - 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); -} - -struct named_pipe_call { - struct named_pipe_connection *pipe_conn; - DATA_BLOB in; - DATA_BLOB out; - struct iovec out_iov[1]; - NTSTATUS status; -}; + conn->ops->accept_connection(conn); -static void named_pipe_handover_connection(struct tevent_req *subreq); + DEBUG(10,("named_pipe_handover_connection[%s]: succeeded\n", + conn->ops->name)); +} -static void named_pipe_auth_request(struct tevent_req *subreq) +static NTSTATUS named_pipe_recv_auth_request(void *private_data, + DATA_BLOB req_blob) { - struct named_pipe_connection *pipe_conn = tevent_req_callback_data(subreq, - struct named_pipe_connection); + struct named_pipe_connection *pipe_conn = talloc_get_type( + private_data, 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_req pipe_request; - struct named_pipe_auth_rep pipe_reply; + struct named_pipe_auth_rep rep; + DATA_BLOB rep_blob; NTSTATUS status; - 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); + /* + * make sure nothing happens on the socket untill the + * real implementation takes over + */ + packet_recv_disable(pipe_conn->packet); /* * TODO: check it's a root (uid == 0) pipe */ - ZERO_STRUCT(pipe_reply); - pipe_reply.level = 0; - pipe_reply.status = NT_STATUS_INTERNAL_ERROR; + 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); /* parse the passed credentials */ ndr_err = ndr_pull_struct_blob_all( - &call->in, + &req_blob, pipe_conn, lp_iconv_convenience(conn->lp_ctx), - &pipe_request, - (ndr_pull_flags_fn_t) ndr_pull_named_pipe_auth_req); + &req, + (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); + rep.status = ndr_map_error2ntstatus(ndr_err); DEBUG(2, ("Could not unmarshall named_pipe_auth_req: %s\n", - nt_errstr(pipe_reply.status))); + nt_errstr(rep.status))); goto reply; } if (DEBUGLVL(10)) { - NDR_PRINT_DEBUG(named_pipe_auth_req, &pipe_request); + NDR_PRINT_DEBUG(named_pipe_auth_req, &req); } - if (strcmp(NAMED_PIPE_AUTH_MAGIC, pipe_request.magic) != 0) { + if (strcmp(NAMED_PIPE_AUTH_MAGIC, req.magic) != 0) { DEBUG(2, ("named_pipe_auth_req: invalid magic '%s' != %s\n", - pipe_request.magic, NAMED_PIPE_AUTH_MAGIC)); - pipe_reply.status = NT_STATUS_INVALID_PARAMETER; + req.magic, NAMED_PIPE_AUTH_MAGIC)); + rep.status = NT_STATUS_INVALID_PARAMETER; goto reply; } - switch (pipe_request.level) { + switch (req.level) { case 0: /* * anon connection, we don't create a session info * and leave it NULL */ - pipe_reply.level = 0; - pipe_reply.status = NT_STATUS_OK; + rep.level = 0; + rep.status = NT_STATUS_OK; break; case 1: - 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)) { + 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)) { DEBUG(2, ("make_server_info_netlogon_validation returned " - "%s\n", nt_errstr(pipe_reply.status))); + "%s\n", nt_errstr(rep.status))); goto reply; } /* setup the session_info on the connection */ - 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)) { + 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)) { DEBUG(2, ("auth_generate_session_info failed: %s\n", - nt_errstr(pipe_reply.status))); + nt_errstr(rep.status))); goto reply; } break; case 2: - 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; + 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; - if (pipe_request.info.info2.sam_info3 == NULL) { + if (!req.info.info2.sam_info3) { /* * anon connection, we don't create a session info * and leave it NULL */ - pipe_reply.status = NT_STATUS_OK; + rep.status = NT_STATUS_OK; break; } - val.sam3 = pipe_request.info.info2.sam_info3; + val.sam3 = req.info.info2.sam_info3; - pipe_reply.status = make_server_info_netlogon_validation(pipe_conn, + rep.status = make_server_info_netlogon_validation(pipe_conn, val.sam3->base.account_name.string, 3, &val, &server_info); - if (!NT_STATUS_IS_OK(pipe_reply.status)) { + if (!NT_STATUS_IS_OK(rep.status)) { DEBUG(2, ("make_server_info_netlogon_validation returned " - "%s\n", nt_errstr(pipe_reply.status))); + "%s\n", nt_errstr(rep.status))); goto reply; } /* setup the session_info on the connection */ - pipe_reply.status = auth_generate_session_info(conn, + rep.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)) { + if (!NT_STATUS_IS_OK(rep.status)) { DEBUG(2, ("auth_generate_session_info failed: %s\n", - nt_errstr(pipe_reply.status))); + nt_errstr(rep.status))); goto reply; } - 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); + 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); break; case 3: - 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; + 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; - if (pipe_request.info.info3.sam_info3 == NULL) { + if (!req.info.info3.sam_info3) { /* * anon connection, we don't create a session info * and leave it NULL */ - pipe_reply.status = NT_STATUS_OK; + rep.status = NT_STATUS_OK; break; } - val.sam3 = pipe_request.info.info3.sam_info3; + val.sam3 = req.info.info3.sam_info3; - pipe_reply.status = make_server_info_netlogon_validation(pipe_conn, + rep.status = make_server_info_netlogon_validation(pipe_conn, val.sam3->base.account_name.string, 3, &val, &server_info); - if (!NT_STATUS_IS_OK(pipe_reply.status)) { + if (!NT_STATUS_IS_OK(rep.status)) { DEBUG(2, ("make_server_info_netlogon_validation returned " - "%s\n", nt_errstr(pipe_reply.status))); + "%s\n", nt_errstr(rep.status))); goto reply; } /* setup the session_info on the connection */ - 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)) { + 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)) { DEBUG(2, ("auth_generate_session_info failed: %s\n", - nt_errstr(pipe_reply.status))); + nt_errstr(rep.status))); goto reply; } - if (pipe_request.info.info3.gssapi_delegated_creds_length) { + if (req.info.info3.gssapi_delegated_creds_length) { OM_uint32 minor_status; gss_buffer_desc cred_token; gss_cred_id_t cred_handle; @@ -338,20 +266,20 @@ static void named_pipe_auth_request(struct tevent_req *subreq) DEBUG(10, ("named_pipe_auth: delegated credentials supplied by client\n")); - cred_token.value = pipe_request.info.info3.gssapi_delegated_creds; - cred_token.length = pipe_request.info.info3.gssapi_delegated_creds_length; + cred_token.value = req.info.info3.gssapi_delegated_creds; + cred_token.length = req.info.info3.gssapi_delegated_creds_length; ret = gss_import_cred(&minor_status, &cred_token, &cred_handle); if (ret != GSS_S_COMPLETE) { - pipe_reply.status = NT_STATUS_INTERNAL_ERROR; + rep.status = NT_STATUS_INTERNAL_ERROR; goto reply; } conn->session_info->credentials = cli_credentials_init(conn->session_info); - if (conn->session_info->credentials == NULL) { - pipe_reply.status = NT_STATUS_NO_MEMORY; + if (!conn->session_info->credentials) { + rep.status = NT_STATUS_NO_MEMORY; goto reply; } @@ -366,7 +294,7 @@ static void named_pipe_auth_request(struct tevent_req *subreq) cred_handle, CRED_SPECIFIED); if (ret) { - pipe_reply.status = NT_STATUS_INTERNAL_ERROR; + rep.status = NT_STATUS_INTERNAL_ERROR; goto reply; } @@ -375,112 +303,49 @@ static void named_pipe_auth_request(struct tevent_req *subreq) CRED_MUST_USE_KERBEROS); } - 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); + 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); break; default: - DEBUG(0, ("named_pipe_auth_req: unknown level %u\n", - pipe_request.level)); - pipe_reply.level = 0; - pipe_reply.status = NT_STATUS_INVALID_LEVEL; + DEBUG(2, ("named_pipe_auth_req: unknown level %u\n", + req.level)); + rep.level = 0; + rep.status = NT_STATUS_INVALID_LEVEL; goto reply; } reply: /* create the output */ - ndr_err = ndr_push_struct_blob(&call->out, pipe_conn, + ndr_err = ndr_push_struct_blob(&rep_blob, pipe_conn, lp_iconv_convenience(conn->lp_ctx), - &pipe_reply, + &rep, (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); - - 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(2, ("Could not marshall named_pipe_auth_rep: %s\n", + nt_errstr(status))); + return status; } - 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); + DEBUG(10,("named_pipe_auth reply[%u]\n", (unsigned)rep_blob.length)); + dump_data(11, rep_blob.data, rep_blob.length); if (DEBUGLVL(10)) { - NDR_PRINT_DEBUG(named_pipe_auth_rep, &pipe_reply); + NDR_PRINT_DEBUG(named_pipe_auth_rep, &rep); } - 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; - } - - tevent_req_set_callback(subreq, named_pipe_handover_connection, call); -} - -static void named_pipe_handover_connection(struct tevent_req *subreq) -{ - 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; + 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; } - /* - * 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_handover_connection[%s]: succeeded\n", - conn->ops->name)); - - /* we don't have to free call here as the connection got closed */ + return NT_STATUS_OK; } /* @@ -491,8 +356,9 @@ 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); - named_pipe_terminate_connection(pipe_conn, - "named_pipe_recv: called"); + DEBUG(10,("named_pipe_recv\n")); + + packet_recv(pipe_conn->packet); } /* @@ -503,8 +369,74 @@ static void named_pipe_send(struct stream_connection *conn, uint16_t flags) struct named_pipe_connection *pipe_conn = talloc_get_type( conn->private_data, struct named_pipe_connection); - named_pipe_terminate_connection(pipe_conn, - "named_pipe_send: called"); + packet_queue_run(pipe_conn->packet); +} + +/* + handle socket recv errors +*/ +static void named_pipe_recv_error(void *private_data, NTSTATUS status) +{ + 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; + + pipe_conn->connection = conn; + conn->private_data = pipe_conn; } 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 14387e9558..2d8b9559f4 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) } } -void stream_io_handler_fde(struct tevent_context *ev, struct tevent_fd *fde, +static 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, |