From: Volker Lendecke Date: Mon, 24 Oct 2005 09:34:12 +0000 (+0000) Subject: r11274: Start a connection attempt to the DC's port 389. To do this properly, make X-Git-Tag: release-4-0-0alpha6~801^3~8407 X-Git-Url: http://git.samba.org/samba.git/?p=amitay%2Fsamba.git;a=commitdiff_plain;h=d6e070b74af8891c5e6ee15d57f8c0db3aac2f14 r11274: Start a connection attempt to the DC's port 389. To do this properly, make socket_connect and ldap_connect properly async. Volker (This used to be commit bcc71fc1deeed443d7cf00220ce264011ddf588d) --- diff --git a/source4/lib/socket/connect.c b/source4/lib/socket/connect.c index 04902442e3e..244fdcbb063 100644 --- a/source4/lib/socket/connect.c +++ b/source4/lib/socket/connect.c @@ -1,10 +1,11 @@ /* Unix SMB/CIFS implementation. - implements a non-blocking connect operation that is aware of the samba4 events - system + implements a non-blocking connect operation that is aware of the samba4 + events system Copyright (C) Andrew Tridgell 2005 + Copyright (C) Volker Lendecke 2005 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 @@ -25,87 +26,190 @@ #include "lib/socket/socket.h" #include "lib/events/events.h" #include "librpc/gen_ndr/nbt.h" +#include "libcli/composite/composite.h" + + +struct connect_state { + struct composite_context *ctx; + struct socket_context *sock; + const char *my_address; + int my_port; + const char *server_address; + int server_port; + uint32_t flags; + struct fd_event *connect_ev; +}; + +static void socket_connect_handler(struct event_context *ev, + struct fd_event *fde, + uint16_t flags, void *private); +static void socket_connect_recv_addr(struct composite_context *ctx); +static void socket_connect_recv_conn(struct composite_context *ctx); + +struct composite_context *socket_connect_send(struct socket_context *sock, + const char *my_address, + int my_port, + const char *server_address, + int server_port, + uint32_t flags, + struct event_context *event_ctx) +{ + struct composite_context *result, *ctx; + struct connect_state *state; + + result = talloc_zero(NULL, struct composite_context); + if (result == NULL) goto failed; + result->state = COMPOSITE_STATE_IN_PROGRESS; + result->async.fn = NULL; + result->event_ctx = event_ctx; + + state = talloc(result, struct connect_state); + if (state == NULL) goto failed; + state->ctx = result; + result->private_data = state; + + state->sock = talloc_reference(state, sock); + if (state->sock == NULL) goto failed; + state->my_address = talloc_strdup(state, my_address); + if (state->sock == NULL) goto failed; + state->my_port = my_port; + state->server_address = talloc_strdup(state, server_address); + if (state->sock == NULL) goto failed; + state->server_port = server_port; + state->flags = flags; + set_blocking(socket_get_fd(sock), False); -/* - async name resolution handler for socket_connect_ev, returnes either - an IP address or 'localhost' (which is specially recognised) -*/ -static NTSTATUS connect_resolve(TALLOC_CTX *mem_ctx, const char *address, - struct event_context *ev, const char **ret_address) -{ - struct nbt_name name; + if (strcmp(sock->backend_name, "ipv4") == 0) { + struct nbt_name name; + make_nbt_name_client(&name, server_address); + ctx = resolve_name_send(&name, result->event_ctx, + lp_name_resolve_order()); + if (ctx == NULL) goto failed; + ctx->async.fn = socket_connect_recv_addr; + ctx->async.private_data = state; + return result; + } - name.name = address; - name.scope = NULL; - name.type = NBT_NAME_CLIENT; + ctx = talloc_zero(state, struct composite_context); + if (ctx == NULL) goto failed; + ctx->state = COMPOSITE_STATE_IN_PROGRESS; + ctx->event_ctx = event_ctx; + ctx->async.fn = socket_connect_recv_conn; + ctx->async.private_data = state; + + state->ctx->status = socket_connect(sock, my_address, my_port, + server_address, server_port, + flags); + if (NT_STATUS_IS_ERR(state->ctx->status) && + !NT_STATUS_EQUAL(state->ctx->status, + NT_STATUS_MORE_PROCESSING_REQUIRED)) { + composite_trigger_error(state->ctx); + return result; + } - return resolve_name(&name, mem_ctx, ret_address, ev); -} + state->connect_ev = event_add_fd(state->ctx->event_ctx, state, + socket_get_fd(sock), EVENT_FD_WRITE, + socket_connect_handler, ctx); + if (state->connect_ev == NULL) { + state->ctx->status = NT_STATUS_NO_MEMORY; + composite_trigger_error(state->ctx); + return result; + } + return result; + + failed: + talloc_free(result); + return NULL; +} /* handle write events on connect completion */ -static void socket_connect_handler(struct event_context *ev, struct fd_event *fde, +static void socket_connect_handler(struct event_context *ev, + struct fd_event *fde, uint16_t flags, void *private) { - NTSTATUS *status = (NTSTATUS *)private; - *status = NT_STATUS_OK; -} + struct composite_context *ctx = + talloc_get_type(private, struct composite_context); + struct connect_state *state = + talloc_get_type(ctx->async.private_data, + struct connect_state); + ctx->status = socket_connect_complete(state->sock, state->flags); + if (!composite_is_ok(ctx)) return; -/* - just like socket_connect() but other events can happen while the - connect is ongoing. This isn't as good as making the calling code - fully async during its connect phase, but at least it means that any - calling code that uses this won't interfere with code that is - properly async - */ -NTSTATUS socket_connect_ev(struct socket_context *sock, - const char *my_address, int my_port, - const char *server_address, int server_port, - uint32_t flags, struct event_context *ev) -{ - TALLOC_CTX *tmp_ctx = talloc_new(sock); - NTSTATUS status; - - /* - we resolve separately to ensure that the name resolutions happens - asynchronously + composite_done(ctx); +} - the check for ipv4 is ugly, but how to do it better? We don't want - to try to resolve unix pathnames here - */ - if (strcmp(sock->backend_name, "ipv4") == 0) { - status = connect_resolve(tmp_ctx, server_address, ev, &server_address); - if (!NT_STATUS_IS_OK(status)) { - talloc_free(tmp_ctx); - return status; - } +static void socket_connect_recv_addr(struct composite_context *ctx) +{ + struct connect_state *state = + talloc_get_type(ctx->async.private_data, struct connect_state); + const char *addr; + + state->ctx->status = resolve_name_recv(ctx, state, &addr); + if (!composite_is_ok(state->ctx)) return; + + ctx = talloc_zero(state, struct composite_context); + if (composite_nomem(ctx, state->ctx)) return; + ctx->state = COMPOSITE_STATE_IN_PROGRESS; + ctx->event_ctx = state->ctx->event_ctx; + ctx->async.fn = socket_connect_recv_conn; + ctx->async.private_data = state; + + state->ctx->status = socket_connect(state->sock, + state->my_address, + state->my_port, + state->server_address, + state->server_port, + state->flags); + if (NT_STATUS_IS_ERR(state->ctx->status) && + !NT_STATUS_EQUAL(state->ctx->status, + NT_STATUS_MORE_PROCESSING_REQUIRED)) { + composite_error(state->ctx, state->ctx->status); + return; } - set_blocking(socket_get_fd(sock), False); + state->connect_ev = event_add_fd(ctx->event_ctx, state, + socket_get_fd(state->sock), + EVENT_FD_WRITE, + socket_connect_handler, ctx); + if (composite_nomem(state->connect_ev, state->ctx)) return; - status = socket_connect(sock, my_address, my_port, - server_address, server_port, flags); - if (NT_STATUS_IS_ERR(status) && - !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { - return status; - } + return; +} - event_add_fd(ev, tmp_ctx, socket_get_fd(sock), EVENT_FD_WRITE, - socket_connect_handler, &status); +static void socket_connect_recv_conn(struct composite_context *ctx) +{ + struct connect_state *state = + talloc_get_type(ctx->async.private_data, struct connect_state); - while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { - if (event_loop_once(ev) != 0) { - talloc_free(tmp_ctx); - return NT_STATUS_INTERNAL_ERROR; - } - } + state->ctx->status = ctx->status; + if (!composite_is_ok(state->ctx)) return; - status = socket_connect_complete(sock, flags); + /* We have to free the event context here because the continuation + * function might add an event for this socket directly. */ + talloc_free(state->connect_ev); - talloc_free(tmp_ctx); + composite_done(state->ctx); +} + +NTSTATUS socket_connect_recv(struct composite_context *ctx) +{ + NTSTATUS status = composite_wait(ctx); + talloc_free(ctx); return status; } + +NTSTATUS socket_connect_ev(struct socket_context *sock, + const char *my_address, int my_port, + const char *server_address, int server_port, + uint32_t flags, struct event_context *ev) +{ + struct composite_context *ctx; + ctx = socket_connect_send(sock, my_address, my_port, + server_address, server_port, flags, ev); + return socket_connect_recv(ctx); +} diff --git a/source4/lib/socket/socket.h b/source4/lib/socket/socket.h index 27a1dfe0418..8b109a3a423 100644 --- a/source4/lib/socket/socket.h +++ b/source4/lib/socket/socket.h @@ -140,6 +140,14 @@ BOOL socket_check_access(struct socket_context *sock, const char *service_name, const char **allow_list, const char **deny_list); +struct composite_context *socket_connect_send(struct socket_context *sock, + const char *my_address, + int my_port, + const char *server_address, + int server_port, + uint32_t flags, + struct event_context *event_ctx); +NTSTATUS socket_connect_recv(struct composite_context *ctx); NTSTATUS socket_connect_ev(struct socket_context *sock, const char *my_address, int my_port, const char *server_address, int server_port, diff --git a/source4/libcli/ldap/ldap_client.c b/source4/libcli/ldap/ldap_client.c index 6b4e73d44bb..d7cfd7c7e5c 100644 --- a/source4/libcli/ldap/ldap_client.c +++ b/source4/libcli/ldap/ldap_client.c @@ -31,6 +31,7 @@ #include "lib/tls/tls.h" #include "libcli/ldap/ldap.h" #include "libcli/ldap/ldap_client.h" +#include "libcli/composite/composite.h" /* @@ -393,45 +394,105 @@ static NTSTATUS ldap_parse_basic_url(TALLOC_CTX *mem_ctx, const char *url, /* connect to a ldap server */ -NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url) + +struct ldap_connect_state { + struct composite_context *ctx; + struct ldap_connection *conn; +}; + +static void ldap_connect_recv_conn(struct composite_context *ctx); + +struct composite_context *ldap_connect_send(struct ldap_connection *conn, + const char *url) { - NTSTATUS status; + struct composite_context *result, *ctx; + struct ldap_connect_state *state; - status = ldap_parse_basic_url(conn, url, &conn->host, - &conn->port, &conn->ldaps); - NT_STATUS_NOT_OK_RETURN(status); + result = talloc_zero(NULL, struct composite_context); + if (result == NULL) goto failed; + result->state = COMPOSITE_STATE_IN_PROGRESS; + result->async.fn = NULL; + result->event_ctx = conn->event.event_ctx; - status = socket_create("ipv4", SOCKET_TYPE_STREAM, &conn->sock, 0); - NT_STATUS_NOT_OK_RETURN(status); + state = talloc(result, struct ldap_connect_state); + if (state == NULL) goto failed; + state->ctx = result; + result->private_data = state; - talloc_steal(conn, conn->sock); + state->conn = conn; - /* connect in a event friendly way */ - status = socket_connect_ev(conn->sock, NULL, 0, conn->host, conn->port, 0, - conn->event.event_ctx); - if (!NT_STATUS_IS_OK(status)) { - talloc_free(conn->sock); - return status; + state->ctx->status = ldap_parse_basic_url(conn, url, &conn->host, + &conn->port, &conn->ldaps); + if (!NT_STATUS_IS_OK(state->ctx->status)) { + composite_trigger_error(state->ctx); + return result; } + state->ctx->status = socket_create("ipv4", SOCKET_TYPE_STREAM, + &conn->sock, 0); + if (!NT_STATUS_IS_OK(state->ctx->status)) { + composite_trigger_error(state->ctx); + return result; + } + + talloc_steal(conn, conn->sock); + + ctx = socket_connect_send(conn->sock, NULL, 0, conn->host, + conn->port, 0, conn->event.event_ctx); + if (ctx == NULL) goto failed; + + ctx->async.fn = ldap_connect_recv_conn; + ctx->async.private_data = state; + return result; + + failed: + talloc_free(result); + return NULL; +} + +static void ldap_connect_recv_conn(struct composite_context *ctx) +{ + struct ldap_connect_state *state = + talloc_get_type(ctx->async.private_data, + struct ldap_connect_state); + struct ldap_connection *conn = state->conn; + + state->ctx->status = socket_connect_recv(ctx); + if (!composite_is_ok(state->ctx)) return; + /* setup a handler for events on this socket */ conn->event.fde = event_add_fd(conn->event.event_ctx, conn->sock, socket_get_fd(conn->sock), EVENT_FD_READ, ldap_io_handler, conn); if (conn->event.fde == NULL) { - talloc_free(conn->sock); - return NT_STATUS_INTERNAL_ERROR; + composite_error(state->ctx, NT_STATUS_INTERNAL_ERROR); + return; } conn->tls = tls_init_client(conn->sock, conn->event.fde, conn->ldaps); if (conn->tls == NULL) { talloc_free(conn->sock); - return NT_STATUS_INTERNAL_ERROR; + return; } talloc_steal(conn, conn->tls); talloc_steal(conn->tls, conn->sock); - return NT_STATUS_OK; + composite_done(state->ctx); + + return; +} + +NTSTATUS ldap_connect_recv(struct composite_context *ctx) +{ + NTSTATUS status = composite_wait(ctx); + talloc_free(ctx); + return status; +} + +NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url) +{ + struct composite_context *ctx = ldap_connect_send(conn, url); + return ldap_connect_recv(ctx); } /* destroy an open ldap request */ diff --git a/source4/winbind/wb_async_helpers.c b/source4/winbind/wb_async_helpers.c index 35f3ec3bb72..29fd167a930 100644 --- a/source4/winbind/wb_async_helpers.c +++ b/source4/winbind/wb_async_helpers.c @@ -686,8 +686,7 @@ struct composite_context *wb_cmd_checkmachacc_send(struct wbsrv_call *call) state->domain = service->domains; - ctx = wb_init_domain_send(state->domain, result->event_ctx, - call->wbconn->conn->msg_ctx); + ctx = wb_init_domain_send(service, state->domain); if (ctx == NULL) goto failed; ctx->async.fn = cmd_checkmachacc_recv_init; ctx->async.private_data = state; diff --git a/source4/winbind/wb_domain_request.c b/source4/winbind/wb_domain_request.c index b516331f8a8..8c95c20a56c 100644 --- a/source4/winbind/wb_domain_request.c +++ b/source4/winbind/wb_domain_request.c @@ -102,9 +102,7 @@ static void domain_request_recv_domain(struct composite_context *ctx) state->domain->busy = True; if (!state->domain->initialized) { - ctx = wb_init_domain_send(state->domain, - state->service->task->event_ctx, - state->service->task->msg_ctx); + ctx = wb_init_domain_send(state->service, state->domain); composite_continue(state->ctx, ctx, domain_request_recv_init, state); return; diff --git a/source4/winbind/wb_init_domain.c b/source4/winbind/wb_init_domain.c index 7bed7e47ba8..eaafdfafbee 100644 --- a/source4/winbind/wb_init_domain.c +++ b/source4/winbind/wb_init_domain.c @@ -65,9 +65,11 @@ struct init_domain_state { struct composite_context *ctx; struct wbsrv_domain *domain; + struct wbsrv_service *service; int num_dcs; struct nbt_dc_name *dcs; + const char *dcaddr; struct smb_composite_connect conn; @@ -81,6 +83,8 @@ struct init_domain_state { struct policy_handle *samr_handle; struct policy_handle *domain_handle; + struct ldap_connection *ldap_conn; + struct lsa_QueryInfoPolicy queryinfo; }; @@ -91,11 +95,11 @@ static void init_domain_recv_netlogoncreds(struct composite_context *ctx); static void init_domain_recv_netlogonpipe(struct composite_context *ctx); static void init_domain_recv_lsa(struct composite_context *ctx); static void init_domain_recv_queryinfo(struct rpc_request *req); +static void init_domain_recv_ldapconn(struct composite_context *ctx); static void init_domain_recv_samr(struct composite_context *ctx); -struct composite_context *wb_init_domain_send(struct wbsrv_domain *domain, - struct event_context *event_ctx, - struct messaging_context *msg_ctx) +struct composite_context *wb_init_domain_send(struct wbsrv_service *service, + struct wbsrv_domain *domain) { struct composite_context *result, *ctx; struct init_domain_state *state; @@ -104,13 +108,14 @@ struct composite_context *wb_init_domain_send(struct wbsrv_domain *domain, if (result == NULL) goto failed; result->state = COMPOSITE_STATE_IN_PROGRESS; result->async.fn = NULL; - result->event_ctx = event_ctx; + result->event_ctx = service->task->event_ctx; state = talloc_zero(result, struct init_domain_state); if (state == NULL) goto failed; state->ctx = result; result->private_data = state; + state->service = service; state->domain = domain; if (domain->dcname != NULL) { @@ -136,7 +141,8 @@ struct composite_context *wb_init_domain_send(struct wbsrv_domain *domain, schannel_creds); if (!NT_STATUS_IS_OK(state->ctx->status)) goto failed; - ctx = wb_finddcs_send(domain->name, domain->sid, event_ctx, msg_ctx); + ctx = wb_finddcs_send(domain->name, domain->sid, result->event_ctx, + service->task->msg_ctx); if (ctx == NULL) goto failed; ctx->async.fn = init_domain_recv_dcs; @@ -163,6 +169,8 @@ static void init_domain_recv_dcs(struct composite_context *ctx) return; } + state->dcaddr = state->dcs[0].address; + state->conn.in.dest_host = state->dcs[0].address; state->conn.in.port = 0; state->conn.in.called_name = state->dcs[0].name; @@ -193,12 +201,11 @@ static void init_domain_recv_dcip(struct composite_context *ctx) struct init_domain_state *state = talloc_get_type(ctx->async.private_data, struct init_domain_state); - const char *dcaddr; - state->ctx->status = resolve_name_recv(ctx, state, &dcaddr); + state->ctx->status = resolve_name_recv(ctx, state, &state->dcaddr); if (!composite_is_ok(state->ctx)) return; - state->conn.in.dest_host = dcaddr; + state->conn.in.dest_host = state->dcaddr; state->conn.in.port = 0; state->conn.in.called_name = state->domain->dcname; state->conn.in.service = "IPC$"; @@ -218,24 +225,24 @@ static void init_domain_recv_tree(struct composite_context *ctx) struct init_domain_state *state = talloc_get_type(ctx->async.private_data, struct init_domain_state); - state->ctx->status = smb_composite_connect_recv(ctx, state); if (!composite_is_ok(state->ctx)) return; - if ((state->domain->schannel_creds == NULL) || - cli_credentials_is_anonymous(state->domain->schannel_creds)) { - /* No chance to open netlogon */ - ctx = wb_connect_lsa_send(state->conn.out.tree, NULL); + if ((state->domain->schannel_creds != NULL) && + (!cli_credentials_is_anonymous(state->domain->schannel_creds)) && + ((lp_server_role() == ROLE_DOMAIN_MEMBER) && + (dom_sid_equal(state->domain->sid, + state->service->primary_sid)))) { + ctx = wb_get_schannel_creds_send(state->domain->schannel_creds, + state->conn.out.tree, + state->ctx->event_ctx); composite_continue(state->ctx, ctx, - init_domain_recv_lsa, state); + init_domain_recv_netlogoncreds, state); return; } - ctx = wb_get_schannel_creds_send(state->domain->schannel_creds, - state->conn.out.tree, - state->ctx->event_ctx); - composite_continue(state->ctx, ctx, - init_domain_recv_netlogoncreds, state); + ctx = wb_connect_lsa_send(state->conn.out.tree, NULL); + composite_continue(state->ctx, ctx, init_domain_recv_lsa, state); } static void init_domain_recv_netlogoncreds(struct composite_context *ctx) @@ -337,6 +344,7 @@ static void init_domain_recv_queryinfo(struct rpc_request *req) talloc_get_type(req->async.private, struct init_domain_state); struct lsa_DomainInfo *dominfo; struct composite_context *ctx; + const char *ldap_url; state->ctx->status = dcerpc_ndr_request_recv(req); if (!composite_is_ok(state->ctx)) return; @@ -363,6 +371,26 @@ static void init_domain_recv_queryinfo(struct rpc_request *req) return; } + state->ldap_conn = ldap_new_connection(state, state->ctx->event_ctx); + composite_nomem(state->ldap_conn, state->ctx); + + ldap_url = talloc_asprintf(state, "ldap://%s/", state->dcaddr); + composite_nomem(ldap_url, state->ctx); + + ctx = ldap_connect_send(state->ldap_conn, ldap_url); + composite_continue(state->ctx, ctx, init_domain_recv_ldapconn, state); +} + +static void init_domain_recv_ldapconn(struct composite_context *ctx) +{ + struct init_domain_state *state = + talloc_get_type(ctx->async.private_data, + struct init_domain_state); + + state->ctx->status = ldap_connect_recv(ctx); + DEBUG(0, ("ldap_connect returned %s\n", + nt_errstr(state->ctx->status))); + state->samr_pipe = dcerpc_pipe_init(state, state->ctx->event_ctx); if (composite_nomem(state->samr_pipe, state->ctx)) return; @@ -370,8 +398,7 @@ static void init_domain_recv_queryinfo(struct rpc_request *req) state->domain->lsa_auth_type, state->domain->schannel_creds, state->domain->sid); - composite_continue(state->ctx, ctx, - init_domain_recv_samr, state); + composite_continue(state->ctx, ctx, init_domain_recv_samr, state); } static void init_domain_recv_samr(struct composite_context *ctx) @@ -431,11 +458,10 @@ NTSTATUS wb_init_domain_recv(struct composite_context *c) return status; } -NTSTATUS wb_init_domain(struct wbsrv_domain *domain, - struct event_context *event_ctx, - struct messaging_context *messaging_ctx) +NTSTATUS wb_init_domain(struct wbsrv_service *service, + struct wbsrv_domain *domain) { struct composite_context *c = - wb_init_domain_send(domain, event_ctx, messaging_ctx); + wb_init_domain_send(service, domain); return wb_init_domain_recv(c); } diff --git a/source4/winbind/wb_server.h b/source4/winbind/wb_server.h index 30737bccbfb..501924fc016 100644 --- a/source4/winbind/wb_server.h +++ b/source4/winbind/wb_server.h @@ -62,6 +62,8 @@ struct wbsrv_domain { struct policy_handle *samr_handle; struct policy_handle *domain_handle; + struct ldap_connection *ldap_conn; + struct dcerpc_pipe *netlogon_auth2_pipe; struct dcerpc_pipe *netlogon_pipe; struct cli_credentials *schannel_creds; diff --git a/source4/winbind/wb_sid2domain.c b/source4/winbind/wb_sid2domain.c index 8249d6c7d32..83e81e1cd06 100644 --- a/source4/winbind/wb_sid2domain.c +++ b/source4/winbind/wb_sid2domain.c @@ -110,9 +110,7 @@ struct composite_context *wb_sid2domain_send(struct wbsrv_service *service, if (state->result != NULL) { result->status = NT_STATUS_OK; if (!state->result->initialized) { - ctx = wb_init_domain_send(state->result, - service->task->event_ctx, - service->task->msg_ctx); + ctx = wb_init_domain_send(service, state->result); if (ctx == NULL) goto failed; ctx->async.fn = sid2domain_recv_init; ctx->async.private_data = state; @@ -186,14 +184,12 @@ static void sid2domain_recv_dcname(struct composite_context *ctx) state->result->schannel_creds = cli_credentials_init(state->result); if (composite_nomem(state->result->schannel_creds, state->ctx)) return; cli_credentials_set_conf(state->result->schannel_creds); - cli_credentials_set_anonymous(state->result->schannel_creds); + cli_credentials_set_machine_account(state->result->schannel_creds); talloc_steal(state->service, state->result); DLIST_ADD(state->service->domains, state->result); - ctx = wb_init_domain_send(state->result, - state->service->task->event_ctx, - state->service->task->msg_ctx); + ctx = wb_init_domain_send(state->service, state->result); composite_continue(state->ctx, ctx, sid2domain_recv_init, state); }