From 90b4f2871f2352bb01113beddf089fa75916879c Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sun, 30 Jan 2005 02:55:30 +0000 Subject: r5104: - added support for task based servers. These are servers that within themselves are run as a single process, but run as a child of the main process when smbd is run in the standard model, and run as part of the main process when in the single mode. - rewrote the winbind template code to use the new task services. Also fixed the packet queueing - got rid of event_context_merge() as it is no longer needed --- source/build/smb_build/main.pm | 1 + source/include/structs.h | 1 + source/lib/events.c | 26 ----- source/smbd/config.mk | 3 +- source/smbd/process_model.h | 10 +- source/smbd/process_single.c | 18 ++- source/smbd/process_standard.c | 58 +++++++++- source/smbd/process_thread.c | 69 ++++++++++- source/smbd/service_stream.c | 4 +- source/smbd/service_task.c | 90 +++++++++++++++ source/smbd/service_task.h | 30 +++++ source/winbind/wb_server.c | 253 ++++++++++++++++++----------------------- 12 files changed, 377 insertions(+), 186 deletions(-) create mode 100644 source/smbd/service_task.c create mode 100644 source/smbd/service_task.h (limited to 'source') diff --git a/source/build/smb_build/main.pm b/source/build/smb_build/main.pm index 3911820edb5..af4220ec7ba 100644 --- a/source/build/smb_build/main.pm +++ b/source/build/smb_build/main.pm @@ -40,6 +40,7 @@ sub smb_build_main($) "smb_server/config.mk", "rpc_server/config.mk", "ldap_server/config.mk", + "winbind/config.mk", "libcli/auth/gensec.mk", "libcli/auth/config.mk", "libcli/ldap/config.mk", diff --git a/source/include/structs.h b/source/include/structs.h index cf59296f320..2fe069e949e 100644 --- a/source/include/structs.h +++ b/source/include/structs.h @@ -154,5 +154,6 @@ struct nbt_name_status; struct messaging_context; struct stream_connection; +struct task_server; struct model_ops; struct stream_server_ops; diff --git a/source/lib/events.c b/source/lib/events.c index d32a1344e0a..c09d9b50301 100644 --- a/source/lib/events.c +++ b/source/lib/events.c @@ -110,32 +110,6 @@ static void calc_maxfd(struct event_context *ev) } } -/* - move the event structures from ev2 into ev, upping the reference - count on ev. The event context ev2 is then destroyed. - - this is used by modules that need to call on the events of a lower module -*/ -struct event_context *event_context_merge(struct event_context *ev, - struct event_context *ev2) -{ - DLIST_CONCATENATE(ev->fd_events, ev2->fd_events, struct fd_event *); - DLIST_CONCATENATE(ev->timed_events, ev2->timed_events, struct timed_event *); - DLIST_CONCATENATE(ev->loop_events, ev2->loop_events, struct loop_event *); - - ev2->fd_events = NULL; - ev2->timed_events = NULL; - ev2->loop_events = NULL; - - talloc_steal(ev->events, ev2->events); - - event_context_destroy(ev2); - - calc_maxfd(ev); - - return ev; -} - /* to mark the ev->maxfd invalid * this means we need to recalculate it */ diff --git a/source/smbd/config.mk b/source/smbd/config.mk index 07847b7fc8d..4d5e929c795 100644 --- a/source/smbd/config.mk +++ b/source/smbd/config.mk @@ -45,7 +45,8 @@ REQUIRED_SUBSYSTEMS = \ [SUBSYSTEM::SERVER_SERVICE] INIT_OBJ_FILES = \ smbd/service.o \ - smbd/service_stream.o + smbd/service_stream.o \ + smbd/service_task.o REQUIRED_SUBSYSTEMS = \ MESSAGING # End SUBSYSTEM SERVER diff --git a/source/smbd/process_model.h b/source/smbd/process_model.h index 943538d7b21..2e064f42772 100644 --- a/source/smbd/process_model.h +++ b/source/smbd/process_model.h @@ -44,8 +44,14 @@ struct model_ops { uint32_t , void *), void *); - /* function to terminate a connection */ - void (*terminate_connection)(struct event_context *, const char *reason); + /* function to create a task */ + void (*new_task)(struct event_context *, + void (*)(struct event_context *, uint32_t, void *), + void *); + + /* function to terminate a task */ + void (*terminate)(struct event_context *, const char *reason); + }; /* this structure is used by modules to determine the size of some critical types */ diff --git a/source/smbd/process_single.c b/source/smbd/process_single.c index d6217d87125..7d43855f6c1 100644 --- a/source/smbd/process_single.c +++ b/source/smbd/process_single.c @@ -59,17 +59,29 @@ static void single_accept_connection(struct event_context *ev, new_conn(ev, sock2, socket_get_fd(sock), private); } +/* + called to startup a new task +*/ +static void single_new_task(struct event_context *ev, + void (*new_task)(struct event_context *, uint32_t, void *), + void *private) +{ + static uint32_t taskid = 0x10000000; + new_task(ev, taskid++, private); +} + -/* called when a connection goes down */ -static void single_terminate_connection(struct event_context *ev, const char *reason) +/* called when a task goes down */ +static void single_terminate(struct event_context *ev, const char *reason) { } static const struct model_ops single_ops = { .name = "single", .model_init = single_model_init, + .new_task = single_new_task, .accept_connection = single_accept_connection, - .terminate_connection = single_terminate_connection, + .terminate = single_terminate, }; /* diff --git a/source/smbd/process_standard.c b/source/smbd/process_standard.c index ee73cfadcf1..b7e9076e5d0 100644 --- a/source/smbd/process_standard.c +++ b/source/smbd/process_standard.c @@ -104,11 +104,60 @@ static void standard_accept_connection(struct event_context *ev, exit(0); } +/* + called to create a new server task +*/ +static void standard_new_task(struct event_context *ev, + void (*new_task)(struct event_context *, uint32_t , void *), + void *private) +{ + pid_t pid; + struct event_context *ev2; + + pid = fork(); + + if (pid != 0) { + /* parent or error code ... go back to the event loop */ + return; + } + + /* This is now the child code. We need a completely new event_context to work with */ + ev2 = event_context_init(NULL); + + /* the service has given us a private pointer that + encapsulates the context it needs for this new connection - + everything else will be freed */ + talloc_steal(ev2, private); + + /* this will free all the listening sockets and all state that + is not associated with this new connection */ + talloc_free(ev); + + /* tdb needs special fork handling */ + if (tdb_reopen_all() == -1) { + DEBUG(0,("standard_accept_connection: tdb_reopen_all failed.\n")); + } + + /* Ensure that the forked children do not expose identical random streams */ + set_need_random_reseed(); + + /* setup this new connection */ + new_task(ev2, getpid(), private); + + /* we can't return to the top level here, as that event context is gone, + so we now process events in the new event context until there are no + more to process */ + event_loop_wait(ev2); + + talloc_free(ev2); + exit(0); +} + -/* called when a connection goes down */ -static void standard_terminate_connection(struct event_context *ev, const char *reason) +/* called when a task goes down */ +static void standard_terminate(struct event_context *ev, const char *reason) { - DEBUG(2,("standard_terminate_connection: reason[%s]\n",reason)); + DEBUG(2,("standard_terminate: reason[%s]\n",reason)); /* this init_iconv() has the effect of freeing the iconv context memory, which makes leak checking easier */ @@ -128,7 +177,8 @@ static const struct model_ops standard_ops = { .name = "standard", .model_init = standard_model_init, .accept_connection = standard_accept_connection, - .terminate_connection = standard_terminate_connection, + .new_task = standard_new_task, + .terminate = standard_terminate, }; /* diff --git a/source/smbd/process_thread.c b/source/smbd/process_thread.c index 6b62ca413e1..223fb020850 100644 --- a/source/smbd/process_thread.c +++ b/source/smbd/process_thread.c @@ -105,10 +105,70 @@ static void thread_accept_connection(struct event_context *ev, } } -/* called when a SMB connection goes down */ -static void thread_terminate_connection(struct event_context *event_ctx, const char *reason) + +struct new_task_state { + struct event_context *ev; + void (*new_task)(struct event_context *, uint32_t , void *); + void *private; +}; + +static void *thread_task_fn(void *thread_parm) +{ + struct new_task_state *new_task = talloc_get_type(thread_parm, struct new_task_state); + + new_task->new_task(new_task->ev, pthread_self(), new_task->private); + + /* run this connection from here */ + event_loop_wait(new_task->ev); + + talloc_free(new_task); + + return NULL; +} + +/* + called when a new task is needed +*/ +static void thread_new_task(struct event_context *ev, + void (*new_task)(struct event_context *, uint32_t , void *), + void *private) +{ + int rc; + pthread_t thread_id; + pthread_attr_t thread_attr; + struct new_task_state *state; + struct event_context *ev2; + + ev2 = event_context_init(ev); + if (ev2 == NULL) return; + + state = talloc(ev2, struct new_task_state); + if (state == NULL) { + talloc_free(ev2); + return; + } + + state->new_task = new_task; + state->private = private; + state->ev = ev2; + + pthread_attr_init(&thread_attr); + pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); + rc = pthread_create(&thread_id, &thread_attr, thread_task_fn, state); + pthread_attr_destroy(&thread_attr); + if (rc == 0) { + DEBUG(4,("thread_new_task: created thread_id=%lu\n", + (unsigned long int)thread_id)); + } else { + DEBUG(0,("thread_new_task: thread create failed rc=%d\n", rc)); + talloc_free(ev2); + } +} + +/* called when a task goes down */ +static void thread_terminate(struct event_context *event_ctx, const char *reason) { - DEBUG(10,("thread_terminate_connection: reason[%s]\n",reason)); + DEBUG(10,("thread_terminate: reason[%s]\n",reason)); talloc_free(event_ctx); @@ -442,7 +502,8 @@ static const struct model_ops thread_ops = { .name = "thread", .model_init = thread_model_init, .accept_connection = thread_accept_connection, - .terminate_connection = thread_terminate_connection, + .new_task = thread_new_task, + .terminate = thread_terminate, }; /* diff --git a/source/smbd/service_stream.c b/source/smbd/service_stream.c index 72a6703995f..0d29c9fcb87 100644 --- a/source/smbd/service_stream.c +++ b/source/smbd/service_stream.c @@ -1,7 +1,7 @@ /* Unix SMB/CIFS implementation. - hepler functions for stream based servers + helper functions for stream based servers Copyright (C) Andrew Tridgell 2003-2005 Copyright (C) Stefan (metze) Metzmacher 2004 @@ -54,7 +54,7 @@ void stream_terminate_connection(struct stream_connection *srv_conn, const char struct event_ctx *event_ctx = srv_conn->event.ctx; const struct model_ops *model_ops = srv_conn->model_ops; talloc_free(srv_conn); - model_ops->terminate_connection(event_ctx, reason); + model_ops->terminate(event_ctx, reason); } /* diff --git a/source/smbd/service_task.c b/source/smbd/service_task.c new file mode 100644 index 00000000000..ea09edf3f41 --- /dev/null +++ b/source/smbd/service_task.c @@ -0,0 +1,90 @@ +/* + Unix SMB/CIFS implementation. + + helper functions for task based servers (nbtd, winbind etc) + + Copyright (C) Andrew Tridgell 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 + the Free Software Foundation; either version 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "process_model.h" +#include "events.h" +#include "smbd/service_task.h" + +/* + terminate a task service +*/ +void task_terminate(struct task_server *task, const char *reason) +{ + struct event_ctx *event_ctx = task->event_ctx; + const struct model_ops *model_ops = task->model_ops; + talloc_free(task); + model_ops->terminate(event_ctx, reason); +} + +/* used for the callback from the process model code */ +struct task_state { + void (*task_init)(struct task_server *); + const struct model_ops *model_ops; +}; + + +/* + called by the process model code when the new task starts up. This then calls + the server specific startup code +*/ +static void task_server_callback(struct event_context *event_ctx, uint32_t server_id, void *private) +{ + struct task_state *state = talloc_get_type(private, struct task_state); + struct task_server *task; + + task = talloc(event_ctx, struct task_server); + if (task == NULL) return; + + task->event_ctx = event_ctx; + task->model_ops = state->model_ops; + task->server_id = server_id; + + task->msg_ctx = messaging_init(task, task->server_id, task->event_ctx); + if (!task->msg_ctx) { + task_terminate(task, "messaging_init() failed"); + return; + } + + state->task_init(task); +} + +/* + startup a task based server +*/ +NTSTATUS task_server_startup(struct event_context *event_ctx, + const struct model_ops *model_ops, + void (*task_init)(struct task_server *)) +{ + struct task_state *state; + + state = talloc(event_ctx, struct task_state); + NT_STATUS_HAVE_NO_MEMORY(state); + + state->task_init = task_init; + state->model_ops = model_ops; + + model_ops->new_task(event_ctx, task_server_callback, state); + + return NT_STATUS_OK; +} + diff --git a/source/smbd/service_task.h b/source/smbd/service_task.h new file mode 100644 index 00000000000..e8d90d76314 --- /dev/null +++ b/source/smbd/service_task.h @@ -0,0 +1,30 @@ +/* + Unix SMB/CIFS implementation. + + structures for task based servers + + Copyright (C) Andrew Tridgell 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 + the Free Software Foundation; either version 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +struct task_server { + struct event_context *event_ctx; + const struct model_ops *model_ops; + struct messaging_context *msg_ctx; + uint32_t server_id; +}; + diff --git a/source/winbind/wb_server.c b/source/winbind/wb_server.c index b75cc897a8b..fb30b76a0ac 100644 --- a/source/winbind/wb_server.c +++ b/source/winbind/wb_server.c @@ -3,6 +3,7 @@ Main winbindd server routines Copyright (C) Stefan Metzmacher 2005 + Copyright (C) Andrew Tridgell 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 @@ -20,8 +21,10 @@ */ #include "includes.h" +#include "dlinklist.h" #include "events.h" -#include "system/time.h" +#include "smbd/service_task.h" +#include "smbd/service_stream.h" #define WINBINDD_DIR "/tmp/.winbindd/" #define WINBINDD_ECHO_SOCKET WINBINDD_DIR"echo" @@ -29,201 +32,163 @@ #define WINBINDD_ECHO_ADDR WINBINDD_ADDR_PREFIX"1" #define WINBINDD_ECHO_PORT 55555 -static void winbind_accept(struct server_connection *conn) -{ - DEBUG(10,("winbind_accept:\n")); -} +/* + state of an open winbind connection +*/ +struct wbserver_connection { + DATA_BLOB blob; + struct send_queue { + struct send_queue *next, *prev; + DATA_BLOB blob; + } *queue; +}; -static DATA_BLOB tmp_blob; -static void winbind_recv(struct server_connection *conn, struct timeval t, uint16_t flags) +/* + called when we get a new connection +*/ +static void winbind_accept(struct stream_connection *conn) { + struct wbserver_connection *wbconn; + wbconn = talloc_zero(conn, struct wbserver_connection); + wbconn->blob = data_blob_talloc(wbconn, NULL, 1024); + + conn->private = wbconn; +} +/* + receive some data on a winbind connection +*/ +static void winbind_recv(struct stream_connection *conn, struct timeval t, uint16_t flags) +{ + struct wbserver_connection *wbconn = talloc_get_type(conn->private, struct wbserver_connection); NTSTATUS status; size_t nread; + struct send_queue *q; -if (!tmp_blob.data) { - tmp_blob = data_blob_talloc(conn, NULL, 1024); - if (tmp_blob.data == NULL) { - return; - } -} - tmp_blob.length = 1024; - status = socket_recv(conn->socket, tmp_blob.data, tmp_blob.length, &nread, 0); + status = socket_recv(conn->socket, wbconn->blob.data, wbconn->blob.length, &nread, 0); if (NT_STATUS_IS_ERR(status)) { DEBUG(10,("socket_recv: %s\n",nt_errstr(status))); - talloc_free(tmp_blob.data); - server_terminate_connection(conn, "socket_recv: failed\n"); + stream_terminate_connection(conn, "socket_recv: failed\n"); return; } - tmp_blob.length = nread; -#if 0 -DEBUG(0,("winbind_recv:\n")); -dump_data(0, tmp_blob.data, tmp_blob.length); -#endif - conn->event.fde->flags |= EVENT_FD_WRITE; -} - -static void winbind_send(struct server_connection *conn, struct timeval t, uint16_t flags) -{ - NTSTATUS status; - size_t sendlen; - - if (tmp_blob.length > 1 && tmp_blob.data[0] == (uint8_t)'q') { - - } - status = socket_send(conn->socket, &tmp_blob, &sendlen, 0); - if (!NT_STATUS_IS_OK(status)) { - DEBUG(10,("socket_send() %s\n",nt_errstr(status))); - server_terminate_connection(conn, "socket_send: failed\n"); + /* just reflect the data back down the socket */ + q = talloc(wbconn, struct send_queue); + if (q == NULL) { + stream_terminate_connection(conn, "winbind_recv: out of memory\n"); return; } - if (tmp_blob.length > 1 && tmp_blob.data[0] == (uint8_t)'q') { - server_terminate_connection(conn, "winbind_send: user quit\n"); + q->blob = data_blob_talloc(q, wbconn->blob.data, nread); + if (q->blob.data == NULL) { + stream_terminate_connection(conn, "winbind_recv: out of memory\n"); return; } -#if 0 -DEBUG(0,("winbind_send:\n")); -dump_data(0, tmp_blob.data, tmp_blob.length); -#endif - tmp_blob.length -= sendlen; - - if (tmp_blob.length == 0) { - conn->event.fde->flags &= ~EVENT_FD_WRITE; - } -} -static void winbind_idle(struct server_connection *conn, struct timeval t) -{ - DEBUG(1,("winbind_idle: not implemented!\n")); - return; -} + DLIST_ADD_END(wbconn->queue, q, struct send_queue *); -static void winbind_close(struct server_connection *conn, const char *reason) -{ - DEBUG(10,("winbind_close: %s\n", reason)); -} - - - -static int winbind_task_server_contect_destructor(void *ptr) -{ - struct server_context *server = ptr; - - server_service_shutdown(server, "exit"); - - return 0; + conn->event.fde->flags |= EVENT_FD_WRITE; } -static void winbind_server_task_init(struct server_task *task) +/* + called when we can write to a connection +*/ +static void winbind_send(struct stream_connection *conn, struct timeval t, uint16_t flags) { - const char *wb_task_service[] = { "winbind_task", NULL }; - struct server_context *server; - - DEBUG(1,("winbindsrv_task_init\n")); - server = server_service_startup("single", wb_task_service); - if (!server) { - DEBUG(0,("Starting Services (winbind_task) failed.\n")); - return; + struct wbserver_connection *wbconn = talloc_get_type(conn->private, struct wbserver_connection); + + while (wbconn->queue) { + struct send_queue *q = wbconn->queue; + NTSTATUS status; + size_t sendlen; + + status = socket_send(conn->socket, &q->blob, &sendlen, 0); + if (NT_STATUS_IS_ERR(status)) { + DEBUG(10,("socket_send() %s\n",nt_errstr(status))); + stream_terminate_connection(conn, "socket_send: failed\n"); + return; + } + if (!NT_STATUS_IS_OK(status)) { + return; + } + + q->blob.length -= sendlen; + q->blob.data += sendlen; + + if (q->blob.length == 0) { + DLIST_REMOVE(wbconn->queue, q); + talloc_free(q); + } } - task->task.private_data = talloc_steal(task, server); - - task->event.ctx = event_context_merge(task->event.ctx, server->event.ctx); - server->event.ctx = talloc_reference(server, task->event.ctx); - - talloc_set_destructor(server, winbind_task_server_contect_destructor); - - /* wait for events */ - event_loop_wait(task->event.ctx); + conn->event.fde->flags &= ~EVENT_FD_WRITE; } -static const struct server_task_ops winbind_srver_task_ops = { - .name = "winbind_server_task", - .task_init = winbind_server_task_init -}; - -static const struct server_task_ops *winbind_srver_task_get_ops(void) -{ - return &winbind_srver_task_ops; -} - -static const struct server_stream_ops winbind_stream_ops = { - .name = "winbind", - .socket_init = NULL, +static const struct stream_server_ops winbind_stream_ops = { + .name = "smb", .accept_connection = winbind_accept, .recv_handler = winbind_recv, .send_handler = winbind_send, - .idle_handler = winbind_idle, - .close_connection = winbind_close }; -static const struct server_stream_ops *winbind_get_stream_ops(void) -{ - return &winbind_stream_ops; -} - -static void winbind_task_init(struct server_service *service) +/* + startup the winbind task +*/ +static void winbind_task_init(struct task_server *task) { - struct server_stream_socket *stream_socket; uint16_t port = 1; + const struct model_ops *model_ops; + NTSTATUS status; - DEBUG(1,("winbind_task_init\n")); + /* within the winbind task we want to be a single process, so + ask for the single process model ops and pass these to the + stream_setup_socket() call. */ + model_ops = process_model_byname("single"); + if (!model_ops) { + task_terminate(task, "Can't find 'single' process model_ops"); + return; + } /* Make sure the directory for NCALRPC exists */ if (!directory_exist(WINBINDD_DIR, NULL)) { mkdir(WINBINDD_DIR, 0755); } - stream_socket = service_setup_stream_socket(service, winbind_get_stream_ops(), "unix", WINBINDD_ECHO_SOCKET, &port); - if (!stream_socket) { - DEBUG(0,("service_setup_stream_socket(path=%s) failed\n",WINBINDD_ECHO_SOCKET)); + status = stream_setup_socket(task->event_ctx, model_ops, &winbind_stream_ops, + "unix", WINBINDD_ECHO_SOCKET, &port, NULL); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("service_setup_stream_socket(path=%s) failed - %s\n", + WINBINDD_ECHO_SOCKET, nt_errstr(status))); + task_terminate(task, "winbind Failed to find to ECHO unix socket"); return; } port = WINBINDD_ECHO_PORT; - stream_socket = service_setup_stream_socket(service, winbind_get_stream_ops(), "ipv4", WINBINDD_ECHO_ADDR, &port); - if (!stream_socket) { - DEBUG(0,("service_setup_stream_socket(address=%s,port=%u) failed\n",WINBINDD_ECHO_ADDR, port)); + + status = stream_setup_socket(task->event_ctx, model_ops, &winbind_stream_ops, + "ipv4", WINBINDD_ECHO_ADDR, &port, NULL); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("service_setup_stream_socket(address=%s,port=%u) failed - %s\n", + WINBINDD_ECHO_ADDR, port, nt_errstr(status))); + task_terminate(task, "winbind Failed to find to ECHO tcp socket"); return; } - - return; } -static const struct server_service_ops winbind_task_ops = { - .name = "winbind_task", - .service_init = winbind_task_init, -}; - -const struct server_service_ops *winbind_task_get_ops(void) +/* + initialise the winbind server + */ +static NTSTATUS winbind_init(struct event_context *event_ctx, const struct model_ops *model_ops) { - return &winbind_task_ops; -} - -static void winbind_init(struct server_service *service) -{ - DEBUG(1,("winbind_init\n")); - - server_run_task(service, winbind_srver_task_get_ops()); - - return; -} - -static const struct server_service_ops winbind_ops = { - .name = "winbind", - .service_init = winbind_init, -}; - -const struct server_service_ops *winbind_get_ops(void) -{ - return &winbind_ops; + return task_server_startup(event_ctx, model_ops, winbind_task_init); } +/* + register ourselves as a available server +*/ NTSTATUS server_service_winbind_init(void) { - return NT_STATUS_OK; + return register_server_service("winbind", winbind_init); } -- cgit