diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source4/lib | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/lib')
75 files changed, 24063 insertions, 0 deletions
diff --git a/source4/lib/events/events.h b/source4/lib/events/events.h new file mode 100644 index 0000000..e9f5f4c --- /dev/null +++ b/source4/lib/events/events.h @@ -0,0 +1,6 @@ +#ifndef __LIB_EVENTS_H__ +#define __LIB_EVENTS_H__ +#include <tevent.h> +struct tevent_context *s4_event_context_init(TALLOC_CTX *mem_ctx); +void s4_event_context_set_default(struct tevent_context *ev); +#endif /* __LIB_EVENTS_H__ */ diff --git a/source4/lib/events/tevent_s4.c b/source4/lib/events/tevent_s4.c new file mode 100644 index 0000000..36a4d32 --- /dev/null +++ b/source4/lib/events/tevent_s4.c @@ -0,0 +1,41 @@ +/* + Unix SMB/CIFS implementation. + Copyright (C) Andrew Tridgell 2003 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#define TEVENT_DEPRECATED 1 +#include "lib/events/events.h" + +/* + create a event_context structure. This must be the first events + call, and all subsequent calls pass this event_context as the first + element. Event handlers also receive this as their first argument. + + This samba4 specific call sets the samba4 debug handler. +*/ +struct tevent_context *s4_event_context_init(TALLOC_CTX *mem_ctx) +{ + struct tevent_context *ev; + + ev = samba_tevent_context_init(mem_ctx); + if (ev) { + samba_tevent_set_debug(ev, "s4_tevent"); + tevent_loop_allow_nesting(ev); + } + return ev; +} + diff --git a/source4/lib/events/wscript_build b/source4/lib/events/wscript_build new file mode 100644 index 0000000..d08d5dd --- /dev/null +++ b/source4/lib/events/wscript_build @@ -0,0 +1,9 @@ +#!/usr/bin/env python + + +bld.SAMBA_LIBRARY('events', + source='tevent_s4.c', + deps='samba-util', + public_deps='tevent', + private_library=True + ) diff --git a/source4/lib/messaging/irpc.h b/source4/lib/messaging/irpc.h new file mode 100644 index 0000000..d6a5c46 --- /dev/null +++ b/source4/lib/messaging/irpc.h @@ -0,0 +1,87 @@ +/* + Unix SMB/CIFS implementation. + + Samba internal rpc code - header + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef IRPC_H +#define IRPC_H + +#include "lib/messaging/messaging.h" +#include "libcli/util/werror.h" +#include "librpc/gen_ndr/irpc.h" + +/* + an incoming irpc message +*/ +struct irpc_message { + struct server_id from; + void *private_data; + struct irpc_header header; + struct ndr_pull *ndr; + bool defer_reply; + bool no_reply; + struct imessaging_context *msg_ctx; + struct irpc_list *irpc; + void *data; +}; + +/* don't allow calls to take too long */ +#define IRPC_CALL_TIMEOUT 10 +/* wait for the calls as long as it takes */ +#define IRPC_CALL_TIMEOUT_INF 0 + + +/* the server function type */ +typedef NTSTATUS (*irpc_function_t)(struct irpc_message *, void *r); + +/* register a server function with the irpc messaging system */ +#define IRPC_REGISTER(msg_ctx, pipename, funcname, function, private_data) \ + irpc_register(msg_ctx, &ndr_table_ ## pipename, \ + NDR_ ## funcname, \ + (irpc_function_t)function, private_data) + +struct ndr_interface_table; + +NTSTATUS irpc_register(struct imessaging_context *msg_ctx, + const struct ndr_interface_table *table, + int call, irpc_function_t fn, void *private_data); + +struct dcerpc_binding_handle *irpc_binding_handle(TALLOC_CTX *mem_ctx, + struct imessaging_context *msg_ctx, + struct server_id server_id, + const struct ndr_interface_table *table); +struct dcerpc_binding_handle *irpc_binding_handle_by_name(TALLOC_CTX *mem_ctx, + struct imessaging_context *msg_ctx, + const char *dest_task, + const struct ndr_interface_table *table); +void irpc_binding_handle_add_security_token(struct dcerpc_binding_handle *h, + struct security_token *token); + +NTSTATUS irpc_add_name(struct imessaging_context *msg_ctx, const char *name); +NTSTATUS irpc_servers_byname(struct imessaging_context *msg_ctx, + TALLOC_CTX *mem_ctx, const char *name, + unsigned *num_servers, + struct server_id **servers); +struct irpc_name_records *irpc_all_servers(struct imessaging_context *msg_ctx, + TALLOC_CTX *mem_ctx); +void irpc_remove_name(struct imessaging_context *msg_ctx, const char *name); +NTSTATUS irpc_send_reply(struct irpc_message *m, NTSTATUS status); + +#endif + diff --git a/source4/lib/messaging/messaging.c b/source4/lib/messaging/messaging.c new file mode 100644 index 0000000..6d859f7 --- /dev/null +++ b/source4/lib/messaging/messaging.c @@ -0,0 +1,1521 @@ +/* + Unix SMB/CIFS implementation. + + Samba internal messaging functions + + Copyright (C) Andrew Tridgell 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/events/events.h" +#include "lib/util/server_id.h" +#include "system/filesys.h" +#include "messaging/messaging.h" +#include "messaging/messaging_internal.h" +#include "../lib/util/dlinklist.h" +#include "lib/socket/socket.h" +#include "librpc/gen_ndr/ndr_irpc.h" +#include "lib/messaging/irpc.h" +#include "../lib/util/unix_privs.h" +#include "librpc/rpc/dcerpc.h" +#include "cluster/cluster.h" +#include "../lib/util/tevent_ntstatus.h" +#include "lib/param/param.h" +#include "lib/util/server_id_db.h" +#include "lib/util/talloc_report_printf.h" +#include "lib/messaging/messages_dgm.h" +#include "lib/messaging/messages_dgm_ref.h" +#include "../source3/lib/messages_util.h" +#include <tdb.h> +#include "lib/util/idtree.h" + +/* change the message version with any incompatible changes in the protocol */ +#define IMESSAGING_VERSION 1 + +/* + a pending irpc call +*/ +struct irpc_request { + struct irpc_request *prev, *next; + struct imessaging_context *msg_ctx; + int callid; + struct { + void (*handler)(struct irpc_request *irpc, struct irpc_message *m); + void *private_data; + } incoming; +}; + +/* we have a linked list of dispatch handlers for each msg_type that + this messaging server can deal with */ +struct dispatch_fn { + struct dispatch_fn *next, *prev; + uint32_t msg_type; + void *private_data; + msg_callback_t fn; +}; + +/* an individual message */ + +static void irpc_handler(struct imessaging_context *, + void *, + uint32_t, + struct server_id, + size_t, + int *, + DATA_BLOB *); + + +/* + A useful function for testing the message system. +*/ +static void ping_message(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct server_id_buf idbuf; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + DEBUG(1,("INFO: Received PING message from server %s [%.*s]\n", + server_id_str_buf(src, &idbuf), (int)data->length, + data->data?(const char *)data->data:"")); + imessaging_send(msg, src, MSG_PONG, data); +} + +static void pool_message(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + FILE *f = NULL; + + if (num_fds != 1) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + f = fdopen(fds[0], "w"); + if (f == NULL) { + DBG_DEBUG("fopen failed: %s\n", strerror(errno)); + return; + } + + talloc_full_report_printf(NULL, f); + fclose(f); +} + +static void ringbuf_log_msg(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + char *log = debug_get_ringbuf(); + size_t logsize = debug_get_ringbuf_size(); + DATA_BLOB blob; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + if (log == NULL) { + log = discard_const_p(char, "*disabled*\n"); + logsize = strlen(log) + 1; + } + + blob.data = (uint8_t *)log; + blob.length = logsize; + + imessaging_send(msg, src, MSG_RINGBUF_LOG, &blob); +} + +/**************************************************************************** + Receive a "set debug level" message. +****************************************************************************/ + +static void debug_imessage(struct imessaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + const char *params_str = (const char *)data->data; + struct server_id_buf src_buf; + struct server_id dst = imessaging_get_server_id(msg_ctx); + struct server_id_buf dst_buf; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + /* Check, it's a proper string! */ + if (params_str[(data->length)-1] != '\0') { + DBG_ERR("Invalid debug message from pid %s to pid %s\n", + server_id_str_buf(src, &src_buf), + server_id_str_buf(dst, &dst_buf)); + return; + } + + DBG_ERR("INFO: Remote set of debug to `%s' (pid %s from pid %s)\n", + params_str, + server_id_str_buf(dst, &dst_buf), + server_id_str_buf(src, &src_buf)); + + debug_parse_levels(params_str); +} + +/**************************************************************************** + Return current debug level. +****************************************************************************/ + +static void debuglevel_imessage(struct imessaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + char *message = debug_list_class_names_and_levels(); + DATA_BLOB blob = data_blob_null; + struct server_id_buf src_buf; + struct server_id dst = imessaging_get_server_id(msg_ctx); + struct server_id_buf dst_buf; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + DBG_DEBUG("Received REQ_DEBUGLEVEL message (pid %s from pid %s)\n", + server_id_str_buf(dst, &dst_buf), + server_id_str_buf(src, &src_buf)); + + if (message == NULL) { + DBG_ERR("debug_list_class_names_and_levels returned NULL\n"); + return; + } + + blob = data_blob_string_const_null(message); + imessaging_send(msg_ctx, src, MSG_DEBUGLEVEL, &blob); + + TALLOC_FREE(message); +} + +/* + return uptime of messaging server via irpc +*/ +static NTSTATUS irpc_uptime(struct irpc_message *msg, + struct irpc_uptime *r) +{ + struct imessaging_context *ctx = talloc_get_type(msg->private_data, struct imessaging_context); + *r->out.start_time = timeval_to_nttime(&ctx->start_time); + return NT_STATUS_OK; +} + +static struct dispatch_fn *imessaging_find_dispatch( + struct imessaging_context *msg, uint32_t msg_type) +{ + /* temporary IDs use an idtree, the rest use a array of pointers */ + if (msg_type >= MSG_TMP_BASE) { + return (struct dispatch_fn *)idr_find(msg->dispatch_tree, + msg_type); + } + if (msg_type < msg->num_types) { + return msg->dispatch[msg_type]; + } + return NULL; +} + +/* + Register a dispatch function for a particular message type. +*/ +NTSTATUS imessaging_register(struct imessaging_context *msg, void *private_data, + uint32_t msg_type, msg_callback_t fn) +{ + struct dispatch_fn *d; + + /* possibly expand dispatch array */ + if (msg_type >= msg->num_types) { + struct dispatch_fn **dp; + uint32_t i; + dp = talloc_realloc(msg, msg->dispatch, struct dispatch_fn *, msg_type+1); + NT_STATUS_HAVE_NO_MEMORY(dp); + msg->dispatch = dp; + for (i=msg->num_types;i<=msg_type;i++) { + msg->dispatch[i] = NULL; + } + msg->num_types = msg_type+1; + } + + d = talloc_zero(msg->dispatch, struct dispatch_fn); + NT_STATUS_HAVE_NO_MEMORY(d); + d->msg_type = msg_type; + d->private_data = private_data; + d->fn = fn; + + DLIST_ADD(msg->dispatch[msg_type], d); + + return NT_STATUS_OK; +} + +/* + register a temporary message handler. The msg_type is allocated + above MSG_TMP_BASE +*/ +NTSTATUS imessaging_register_tmp(struct imessaging_context *msg, void *private_data, + msg_callback_t fn, uint32_t *msg_type) +{ + struct dispatch_fn *d; + int id; + + d = talloc_zero(msg->dispatch, struct dispatch_fn); + NT_STATUS_HAVE_NO_MEMORY(d); + d->private_data = private_data; + d->fn = fn; + + id = idr_get_new_above(msg->dispatch_tree, d, MSG_TMP_BASE, UINT16_MAX); + if (id == -1) { + talloc_free(d); + return NT_STATUS_TOO_MANY_CONTEXT_IDS; + } + + d->msg_type = (uint32_t)id; + (*msg_type) = d->msg_type; + + return NT_STATUS_OK; +} + +/* + De-register the function for a particular message type. Return the number of + functions deregistered. +*/ +size_t imessaging_deregister(struct imessaging_context *msg, uint32_t msg_type, void *private_data) +{ + struct dispatch_fn *d, *next; + size_t removed = 0; + + if (msg_type >= msg->num_types) { + d = (struct dispatch_fn *)idr_find(msg->dispatch_tree, + msg_type); + if (!d) return 0; + idr_remove(msg->dispatch_tree, msg_type); + talloc_free(d); + return 1; + } + + for (d = msg->dispatch[msg_type]; d; d = next) { + next = d->next; + if (d->private_data == private_data) { + DLIST_REMOVE(msg->dispatch[msg_type], d); + talloc_free(d); + ++removed; + } + } + + return removed; +} + +/* +*/ +int imessaging_cleanup(struct imessaging_context *msg) +{ + return 0; +} + +static void imessaging_dgm_recv(struct tevent_context *ev, + const uint8_t *buf, size_t buf_len, + int *fds, size_t num_fds, + void *private_data); + +/* Keep a list of imessaging contexts */ +static struct imessaging_context *msg_ctxs; + +/* + * A process has terminated, clean-up any names it has registered. + */ +NTSTATUS imessaging_process_cleanup( + struct imessaging_context *msg_ctx, + pid_t pid) +{ + struct irpc_name_records *names = NULL; + uint32_t i = 0; + uint32_t j = 0; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + + if (mem_ctx == NULL) { + DBG_ERR("OOM unable to clean up messaging for process (%d)\n", + pid); + return NT_STATUS_NO_MEMORY; + } + + names = irpc_all_servers(msg_ctx, mem_ctx); + if (names == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_OK; + } + for (i = 0; i < names->num_records; i++) { + for (j = 0; j < names->names[i]->count; j++) { + if (names->names[i]->ids[j].pid == pid) { + int ret = server_id_db_prune_name( + msg_ctx->names, + names->names[i]->name, + names->names[i]->ids[j]); + if (ret != 0 && ret != ENOENT) { + TALLOC_FREE(mem_ctx); + return map_nt_error_from_unix_common( + ret); + } + } + } + } + TALLOC_FREE(mem_ctx); + return NT_STATUS_OK; +} + +static int imessaging_context_destructor(struct imessaging_context *msg) +{ + struct irpc_request *irpc = NULL; + struct irpc_request *next = NULL; + + for (irpc = msg->requests; irpc != NULL; irpc = next) { + next = irpc->next; + + DLIST_REMOVE(msg->requests, irpc); + irpc->callid = -1; + } + + DLIST_REMOVE(msg_ctxs, msg); + TALLOC_FREE(msg->msg_dgm_ref); + return 0; +} + +/* + * Cleanup messaging dgm contexts on a specific event context. + * + * We must make sure to unref all messaging_dgm_ref's *before* the + * tevent context goes away. Only when the last ref is freed, the + * refcounted messaging dgm context will be freed. + */ +void imessaging_dgm_unref_ev(struct tevent_context *ev) +{ + struct imessaging_context *msg = NULL; + + for (msg = msg_ctxs; msg != NULL; msg = msg->next) { + if (msg->ev == ev) { + TALLOC_FREE(msg->msg_dgm_ref); + } + } +} + +static NTSTATUS imessaging_reinit(struct imessaging_context *msg) +{ + int ret = -1; + + TALLOC_FREE(msg->msg_dgm_ref); + + if (msg->discard_incoming) { + msg->num_incoming_listeners = 0; + } else { + msg->num_incoming_listeners = 1; + } + + msg->server_id.pid = getpid(); + + msg->msg_dgm_ref = messaging_dgm_ref(msg, + msg->ev, + &msg->server_id.unique_id, + msg->sock_dir, + msg->lock_dir, + imessaging_dgm_recv, + msg, + &ret); + + if (msg->msg_dgm_ref == NULL) { + DEBUG(2, ("messaging_dgm_ref failed: %s\n", + strerror(ret))); + return map_nt_error_from_unix_common(ret); + } + + server_id_db_reinit(msg->names, msg->server_id); + return NT_STATUS_OK; +} + +/* + * Must be called after a fork. + */ +NTSTATUS imessaging_reinit_all(void) +{ + struct imessaging_context *msg = NULL; + + for (msg = msg_ctxs; msg != NULL; msg = msg->next) { + NTSTATUS status = imessaging_reinit(msg); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + return NT_STATUS_OK; +} + +/* + create the listening socket and setup the dispatcher +*/ +static struct imessaging_context *imessaging_init_internal( + TALLOC_CTX *mem_ctx, + bool discard_incoming, + struct loadparm_context *lp_ctx, + struct server_id server_id, + struct tevent_context *ev) +{ + NTSTATUS status; + struct imessaging_context *msg; + bool ok; + int ret; + const char *lock_dir = NULL; + int tdb_flags = TDB_INCOMPATIBLE_HASH | TDB_CLEAR_IF_FIRST; + + if (ev == NULL) { + return NULL; + } + + msg = talloc_zero(mem_ctx, struct imessaging_context); + if (msg == NULL) { + return NULL; + } + msg->ev = ev; + msg->discard_incoming = discard_incoming; + if (msg->discard_incoming) { + msg->num_incoming_listeners = 0; + } else { + msg->num_incoming_listeners = 1; + } + + talloc_set_destructor(msg, imessaging_context_destructor); + + /* create the messaging directory if needed */ + + lock_dir = lpcfg_lock_directory(lp_ctx); + if (lock_dir == NULL) { + goto fail; + } + + msg->sock_dir = lpcfg_private_path(msg, lp_ctx, "msg.sock"); + if (msg->sock_dir == NULL) { + goto fail; + } + ok = directory_create_or_exist_strict(msg->sock_dir, geteuid(), 0700); + if (!ok) { + goto fail; + } + + msg->lock_dir = lpcfg_lock_path(msg, lp_ctx, "msg.lock"); + if (msg->lock_dir == NULL) { + goto fail; + } + ok = directory_create_or_exist_strict(msg->lock_dir, geteuid(), 0755); + if (!ok) { + goto fail; + } + + msg->msg_dgm_ref = messaging_dgm_ref( + msg, ev, &server_id.unique_id, msg->sock_dir, msg->lock_dir, + imessaging_dgm_recv, msg, &ret); + + if (msg->msg_dgm_ref == NULL) { + goto fail; + } + + msg->server_id = server_id; + msg->idr = idr_init(msg); + if (msg->idr == NULL) { + goto fail; + } + + msg->dispatch_tree = idr_init(msg); + if (msg->dispatch_tree == NULL) { + goto fail; + } + + msg->start_time = timeval_current(); + + tdb_flags |= lpcfg_tdb_flags(lp_ctx, 0); + + /* + * This context holds a destructor that cleans up any names + * registered on this context on talloc_free() + */ + msg->names = server_id_db_init(msg, server_id, lock_dir, 0, tdb_flags); + if (msg->names == NULL) { + goto fail; + } + + status = imessaging_register(msg, NULL, MSG_PING, ping_message); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + status = imessaging_register(msg, NULL, MSG_REQ_POOL_USAGE, + pool_message); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + status = imessaging_register(msg, NULL, MSG_IRPC, irpc_handler); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + status = imessaging_register(msg, NULL, MSG_REQ_RINGBUF_LOG, + ringbuf_log_msg); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + status = imessaging_register(msg, NULL, MSG_DEBUG, + debug_imessage); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + status = imessaging_register(msg, NULL, MSG_REQ_DEBUGLEVEL, + debuglevel_imessage); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + status = IRPC_REGISTER(msg, irpc, IRPC_UPTIME, irpc_uptime, msg); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } +#if defined(DEVELOPER) || defined(ENABLE_SELFTEST) + /* + * Register handlers for messages specific to developer and + * self test builds + */ + status = imessaging_register_extra_handlers(msg); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } +#endif /* defined(DEVELOPER) || defined(ENABLE_SELFTEST) */ + + DLIST_ADD(msg_ctxs, msg); + + return msg; +fail: + talloc_free(msg); + return NULL; +} + +/* + create the listening socket and setup the dispatcher +*/ +struct imessaging_context *imessaging_init(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct server_id server_id, + struct tevent_context *ev) +{ + bool discard_incoming = false; + return imessaging_init_internal(mem_ctx, + discard_incoming, + lp_ctx, + server_id, + ev); +} + +struct imessaging_context *imessaging_init_discard_incoming( + TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct server_id server_id, + struct tevent_context *ev) +{ + bool discard_incoming = true; + return imessaging_init_internal(mem_ctx, + discard_incoming, + lp_ctx, + server_id, + ev); +} + +struct imessaging_post_state { + struct imessaging_context *msg_ctx; + struct imessaging_post_state **busy_ref; + size_t buf_len; + uint8_t buf[]; +}; + +static int imessaging_post_state_destructor(struct imessaging_post_state *state) +{ + if (state->busy_ref != NULL) { + *state->busy_ref = NULL; + state->busy_ref = NULL; + } + return 0; +} + +static void imessaging_post_handler(struct tevent_context *ev, + struct tevent_immediate *ti, + void *private_data) +{ + struct imessaging_post_state *state = talloc_get_type_abort( + private_data, struct imessaging_post_state); + + if (state == NULL) { + return; + } + + /* + * In usecases like using messaging_client_init() with irpc processing + * we may free the imessaging_context during the messaging handler. + * imessaging_post_state is a child of imessaging_context and + * might be implicitly free'ed before the explicit TALLOC_FREE(state). + * + * The busy_ref pointer makes sure the destructor clears + * the local 'state' variable. + */ + + SMB_ASSERT(state->busy_ref == NULL); + state->busy_ref = &state; + + imessaging_dgm_recv(ev, state->buf, state->buf_len, NULL, 0, + state->msg_ctx); + + state->busy_ref = NULL; + TALLOC_FREE(state); +} + +static int imessaging_post_self(struct imessaging_context *msg, + const uint8_t *buf, size_t buf_len) +{ + struct tevent_immediate *ti; + struct imessaging_post_state *state; + + state = talloc_size( + msg, offsetof(struct imessaging_post_state, buf) + buf_len); + if (state == NULL) { + return ENOMEM; + } + talloc_set_name_const(state, "struct imessaging_post_state"); + + talloc_set_destructor(state, imessaging_post_state_destructor); + + ti = tevent_create_immediate(state); + if (ti == NULL) { + TALLOC_FREE(state); + return ENOMEM; + } + + state->msg_ctx = msg; + state->busy_ref = NULL; + state->buf_len = buf_len; + memcpy(state->buf, buf, buf_len); + + tevent_schedule_immediate(ti, msg->ev, imessaging_post_handler, + state); + + return 0; +} + +static void imessaging_dgm_recv(struct tevent_context *ev, + const uint8_t *buf, size_t buf_len, + int *fds, size_t num_fds, + void *private_data) +{ + struct imessaging_context *msg = talloc_get_type_abort( + private_data, struct imessaging_context); + uint32_t msg_type; + struct server_id src, dst; + struct server_id_buf srcbuf, dstbuf; + DATA_BLOB data; + + if (buf_len < MESSAGE_HDR_LENGTH) { + /* Invalid message, ignore */ + return; + } + + if (msg->num_incoming_listeners == 0) { + struct server_id_buf selfbuf; + + message_hdr_get(&msg_type, &src, &dst, buf); + + DBG_DEBUG("not listening - discarding message from " + "src[%s] to dst[%s] (self[%s]) type=0x%x " + "on %s event context\n", + server_id_str_buf(src, &srcbuf), + server_id_str_buf(dst, &dstbuf), + server_id_str_buf(msg->server_id, &selfbuf), + (unsigned)msg_type, + (ev != msg->ev) ? "different" : "main"); + return; + } + + if (ev != msg->ev) { + int ret; + ret = imessaging_post_self(msg, buf, buf_len); + if (ret != 0) { + DBG_WARNING("imessaging_post_self failed: %s\n", + strerror(ret)); + } + return; + } + + message_hdr_get(&msg_type, &src, &dst, buf); + + data.data = discard_const_p(uint8_t, buf + MESSAGE_HDR_LENGTH); + data.length = buf_len - MESSAGE_HDR_LENGTH; + + if ((cluster_id_equal(&dst, &msg->server_id)) || + ((dst.task_id == 0) && (msg->server_id.pid == 0))) { + struct dispatch_fn *d, *next; + + DEBUG(10, ("%s: dst %s matches my id: %s, type=0x%x\n", + __func__, + server_id_str_buf(dst, &dstbuf), + server_id_str_buf(msg->server_id, &srcbuf), + (unsigned)msg_type)); + + d = imessaging_find_dispatch(msg, msg_type); + + for (; d; d = next) { + next = d->next; + d->fn(msg, + d->private_data, + d->msg_type, + src, + num_fds, + fds, + &data); + } + } else { + DEBUG(10, ("%s: Ignoring type=0x%x dst %s, I am %s, \n", + __func__, (unsigned)msg_type, + server_id_str_buf(dst, &dstbuf), + server_id_str_buf(msg->server_id, &srcbuf))); + } +} + +/* + A hack, for the short term until we get 'client only' messaging in place +*/ +struct imessaging_context *imessaging_client_init(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct tevent_context *ev) +{ + struct server_id id; + ZERO_STRUCT(id); + id.pid = getpid(); + id.task_id = generate_random(); + id.vnn = NONCLUSTER_VNN; + + /* This is because we are not in the s3 serverid database */ + id.unique_id = SERVERID_UNIQUE_ID_NOT_TO_VERIFY; + + return imessaging_init_discard_incoming(mem_ctx, lp_ctx, id, ev); +} + +/* + a list of registered irpc server functions +*/ +struct irpc_list { + struct irpc_list *next, *prev; + struct GUID uuid; + const struct ndr_interface_table *table; + int callnum; + irpc_function_t fn; + void *private_data; +}; + + +/* + register a irpc server function +*/ +NTSTATUS irpc_register(struct imessaging_context *msg_ctx, + const struct ndr_interface_table *table, + int callnum, irpc_function_t fn, void *private_data) +{ + struct irpc_list *irpc; + + /* override an existing handler, if any */ + for (irpc=msg_ctx->irpc; irpc; irpc=irpc->next) { + if (irpc->table == table && irpc->callnum == callnum) { + break; + } + } + if (irpc == NULL) { + irpc = talloc(msg_ctx, struct irpc_list); + NT_STATUS_HAVE_NO_MEMORY(irpc); + DLIST_ADD(msg_ctx->irpc, irpc); + } + + irpc->table = table; + irpc->callnum = callnum; + irpc->fn = fn; + irpc->private_data = private_data; + irpc->uuid = irpc->table->syntax_id.uuid; + + return NT_STATUS_OK; +} + + +/* + handle an incoming irpc reply message +*/ +static void irpc_handler_reply(struct imessaging_context *msg_ctx, struct irpc_message *m) +{ + struct irpc_request *irpc; + + irpc = (struct irpc_request *)idr_find(msg_ctx->idr, m->header.callid); + if (irpc == NULL) return; + + irpc->incoming.handler(irpc, m); +} + +/* + send a irpc reply +*/ +NTSTATUS irpc_send_reply(struct irpc_message *m, NTSTATUS status) +{ + struct ndr_push *push; + DATA_BLOB packet; + enum ndr_err_code ndr_err; + + m->header.status = status; + + /* setup the reply */ + push = ndr_push_init_ctx(m->ndr); + if (push == NULL) { + status = NT_STATUS_NO_MEMORY; + goto failed; + } + + m->header.flags |= IRPC_FLAG_REPLY; + m->header.creds.token= NULL; + + /* construct the packet */ + ndr_err = ndr_push_irpc_header(push, NDR_SCALARS|NDR_BUFFERS, &m->header); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + goto failed; + } + + ndr_err = m->irpc->table->calls[m->irpc->callnum].ndr_push(push, NDR_OUT, m->data); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + goto failed; + } + + /* send the reply message */ + packet = ndr_push_blob(push); + status = imessaging_send(m->msg_ctx, m->from, MSG_IRPC, &packet); + if (!NT_STATUS_IS_OK(status)) goto failed; + +failed: + talloc_free(m); + return status; +} + +/* + handle an incoming irpc request message +*/ +static void irpc_handler_request(struct imessaging_context *msg_ctx, + struct irpc_message *m) +{ + struct irpc_list *i; + void *r; + enum ndr_err_code ndr_err; + + for (i=msg_ctx->irpc; i; i=i->next) { + if (GUID_equal(&i->uuid, &m->header.uuid) && + i->table->syntax_id.if_version == m->header.if_version && + i->callnum == m->header.callnum) { + break; + } + } + + if (i == NULL) { + /* no registered handler for this message */ + talloc_free(m); + return; + } + + /* allocate space for the structure */ + r = talloc_zero_size(m->ndr, i->table->calls[m->header.callnum].struct_size); + if (r == NULL) goto failed; + + m->ndr->flags |= LIBNDR_FLAG_REF_ALLOC; + + /* parse the request data */ + ndr_err = i->table->calls[i->callnum].ndr_pull(m->ndr, NDR_IN, r); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) goto failed; + + /* make the call */ + m->private_data= i->private_data; + m->defer_reply = false; + m->no_reply = false; + m->msg_ctx = msg_ctx; + m->irpc = i; + m->data = r; + + m->header.status = i->fn(m, r); + + if (m->no_reply) { + /* the server function won't ever be replying to this request */ + talloc_free(m); + return; + } + + if (m->defer_reply) { + /* the server function has asked to defer the reply to later */ + talloc_steal(msg_ctx, m); + return; + } + + irpc_send_reply(m, m->header.status); + return; + +failed: + talloc_free(m); +} + +/* + handle an incoming irpc message +*/ +static void irpc_handler(struct imessaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *packet) +{ + struct irpc_message *m; + enum ndr_err_code ndr_err; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + m = talloc(msg_ctx, struct irpc_message); + if (m == NULL) goto failed; + + m->from = src; + + m->ndr = ndr_pull_init_blob(packet, m); + if (m->ndr == NULL) goto failed; + + m->ndr->flags |= LIBNDR_FLAG_REF_ALLOC; + + ndr_err = ndr_pull_irpc_header(m->ndr, NDR_BUFFERS|NDR_SCALARS, &m->header); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) goto failed; + + if (m->header.flags & IRPC_FLAG_REPLY) { + irpc_handler_reply(msg_ctx, m); + } else { + irpc_handler_request(msg_ctx, m); + } + return; + +failed: + talloc_free(m); +} + + +/* + destroy a irpc request +*/ +static int irpc_destructor(struct irpc_request *irpc) +{ + if (irpc->callid != -1) { + DLIST_REMOVE(irpc->msg_ctx->requests, irpc); + idr_remove(irpc->msg_ctx->idr, irpc->callid); + if (irpc->msg_ctx->discard_incoming) { + SMB_ASSERT(irpc->msg_ctx->num_incoming_listeners > 0); + } else { + SMB_ASSERT(irpc->msg_ctx->num_incoming_listeners > 1); + } + irpc->msg_ctx->num_incoming_listeners -= 1; + irpc->callid = -1; + } + + return 0; +} + +/* + add a string name that this irpc server can be called on + + It will be removed from the DB either via irpc_remove_name or on + talloc_free(msg_ctx->names). +*/ +NTSTATUS irpc_add_name(struct imessaging_context *msg_ctx, const char *name) +{ + int ret; + + ret = server_id_db_add(msg_ctx->names, name); + if (ret != 0) { + return map_nt_error_from_unix_common(ret); + } + return NT_STATUS_OK; +} + +static int all_servers_func(const char *name, unsigned num_servers, + const struct server_id *servers, + void *private_data) +{ + struct irpc_name_records *name_records = talloc_get_type( + private_data, struct irpc_name_records); + struct irpc_name_record *name_record; + uint32_t i; + + name_records->names + = talloc_realloc(name_records, name_records->names, + struct irpc_name_record *, name_records->num_records+1); + if (!name_records->names) { + return -1; + } + + name_records->names[name_records->num_records] = name_record + = talloc(name_records->names, + struct irpc_name_record); + if (!name_record) { + return -1; + } + + name_records->num_records++; + + name_record->name = talloc_strdup(name_record, name); + if (!name_record->name) { + return -1; + } + + name_record->count = num_servers; + name_record->ids = talloc_array(name_record, struct server_id, + num_servers); + if (name_record->ids == NULL) { + return -1; + } + for (i=0;i<name_record->count;i++) { + name_record->ids[i] = servers[i]; + } + return 0; +} + +/* + return a list of server ids for a server name +*/ +struct irpc_name_records *irpc_all_servers(struct imessaging_context *msg_ctx, + TALLOC_CTX *mem_ctx) +{ + int ret; + struct irpc_name_records *name_records = talloc_zero(mem_ctx, struct irpc_name_records); + if (name_records == NULL) { + return NULL; + } + + ret = server_id_db_traverse_read(msg_ctx->names, all_servers_func, + name_records); + if (ret == -1) { + TALLOC_FREE(name_records); + return NULL; + } + + return name_records; +} + +/* + remove a name from a messaging context +*/ +void irpc_remove_name(struct imessaging_context *msg_ctx, const char *name) +{ + server_id_db_remove(msg_ctx->names, name); +} + +struct server_id imessaging_get_server_id(struct imessaging_context *msg_ctx) +{ + return msg_ctx->server_id; +} + +struct irpc_bh_state { + struct imessaging_context *msg_ctx; + struct server_id server_id; + const struct ndr_interface_table *table; + uint32_t timeout; + struct security_token *token; +}; + +static bool irpc_bh_is_connected(struct dcerpc_binding_handle *h) +{ + struct irpc_bh_state *hs = dcerpc_binding_handle_data(h, + struct irpc_bh_state); + + if (!hs->msg_ctx) { + return false; + } + + return true; +} + +static uint32_t irpc_bh_set_timeout(struct dcerpc_binding_handle *h, + uint32_t timeout) +{ + struct irpc_bh_state *hs = dcerpc_binding_handle_data(h, + struct irpc_bh_state); + uint32_t old = hs->timeout; + + hs->timeout = timeout; + + return old; +} + +struct irpc_bh_raw_call_state { + struct irpc_request *irpc; + uint32_t opnum; + DATA_BLOB in_data; + DATA_BLOB in_packet; + DATA_BLOB out_data; +}; + +static void irpc_bh_raw_call_incoming_handler(struct irpc_request *irpc, + struct irpc_message *m); + +static struct tevent_req *irpc_bh_raw_call_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h, + const struct GUID *object, + uint32_t opnum, + uint32_t in_flags, + const uint8_t *in_data, + size_t in_length) +{ + struct irpc_bh_state *hs = + dcerpc_binding_handle_data(h, + struct irpc_bh_state); + struct tevent_req *req; + struct irpc_bh_raw_call_state *state; + bool ok; + struct irpc_header header; + struct ndr_push *ndr; + NTSTATUS status; + enum ndr_err_code ndr_err; + + req = tevent_req_create(mem_ctx, &state, + struct irpc_bh_raw_call_state); + if (req == NULL) { + return NULL; + } + state->opnum = opnum; + state->in_data.data = discard_const_p(uint8_t, in_data); + state->in_data.length = in_length; + + ok = irpc_bh_is_connected(h); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED); + return tevent_req_post(req, ev); + } + + state->irpc = talloc_zero(state, struct irpc_request); + if (tevent_req_nomem(state->irpc, req)) { + return tevent_req_post(req, ev); + } + + state->irpc->msg_ctx = hs->msg_ctx; + state->irpc->callid = idr_get_new(hs->msg_ctx->idr, + state->irpc, UINT16_MAX); + if (state->irpc->callid == -1) { + tevent_req_nterror(req, NT_STATUS_INSUFFICIENT_RESOURCES); + return tevent_req_post(req, ev); + } + state->irpc->incoming.handler = irpc_bh_raw_call_incoming_handler; + state->irpc->incoming.private_data = req; + + /* make sure we accept incoming messages */ + SMB_ASSERT(state->irpc->msg_ctx->num_incoming_listeners < UINT64_MAX); + state->irpc->msg_ctx->num_incoming_listeners += 1; + DLIST_ADD_END(state->irpc->msg_ctx->requests, state->irpc); + talloc_set_destructor(state->irpc, irpc_destructor); + + /* setup the header */ + header.uuid = hs->table->syntax_id.uuid; + + header.if_version = hs->table->syntax_id.if_version; + header.callid = state->irpc->callid; + header.callnum = state->opnum; + header.flags = 0; + header.status = NT_STATUS_OK; + header.creds.token= hs->token; + + /* construct the irpc packet */ + ndr = ndr_push_init_ctx(state->irpc); + if (tevent_req_nomem(ndr, req)) { + return tevent_req_post(req, ev); + } + + ndr_err = ndr_push_irpc_header(ndr, NDR_SCALARS|NDR_BUFFERS, &header); + status = ndr_map_error2ntstatus(ndr_err); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + ndr_err = ndr_push_bytes(ndr, in_data, in_length); + status = ndr_map_error2ntstatus(ndr_err); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + /* and send it */ + state->in_packet = ndr_push_blob(ndr); + status = imessaging_send(hs->msg_ctx, hs->server_id, + MSG_IRPC, &state->in_packet); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + if (hs->timeout != IRPC_CALL_TIMEOUT_INF) { + /* set timeout-callback in case caller wants that */ + ok = tevent_req_set_endtime(req, ev, timeval_current_ofs(hs->timeout, 0)); + if (!ok) { + return tevent_req_post(req, ev); + } + } + + return req; +} + +static void irpc_bh_raw_call_incoming_handler(struct irpc_request *irpc, + struct irpc_message *m) +{ + struct tevent_req *req = + talloc_get_type_abort(irpc->incoming.private_data, + struct tevent_req); + struct irpc_bh_raw_call_state *state = + tevent_req_data(req, + struct irpc_bh_raw_call_state); + + talloc_steal(state, m); + + if (!NT_STATUS_IS_OK(m->header.status)) { + tevent_req_nterror(req, m->header.status); + return; + } + + state->out_data = data_blob_talloc(state, + m->ndr->data + m->ndr->offset, + m->ndr->data_size - m->ndr->offset); + if ((m->ndr->data_size - m->ndr->offset) > 0 && !state->out_data.data) { + tevent_req_oom(req); + return; + } + + tevent_req_done(req); +} + +static NTSTATUS irpc_bh_raw_call_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **out_data, + size_t *out_length, + uint32_t *out_flags) +{ + struct irpc_bh_raw_call_state *state = + tevent_req_data(req, + struct irpc_bh_raw_call_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out_data = talloc_move(mem_ctx, &state->out_data.data); + *out_length = state->out_data.length; + *out_flags = 0; + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct irpc_bh_disconnect_state { + uint8_t _dummy; +}; + +static struct tevent_req *irpc_bh_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h) +{ + struct irpc_bh_state *hs = dcerpc_binding_handle_data(h, + struct irpc_bh_state); + struct tevent_req *req; + struct irpc_bh_disconnect_state *state; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct irpc_bh_disconnect_state); + if (req == NULL) { + return NULL; + } + + ok = irpc_bh_is_connected(h); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED); + return tevent_req_post(req, ev); + } + + hs->msg_ctx = NULL; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS irpc_bh_disconnect_recv(struct tevent_req *req) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +static bool irpc_bh_ref_alloc(struct dcerpc_binding_handle *h) +{ + return true; +} + +static void irpc_bh_do_ndr_print(struct dcerpc_binding_handle *h, + ndr_flags_type ndr_flags, + const void *_struct_ptr, + const struct ndr_interface_call *call) +{ + void *struct_ptr = discard_const(_struct_ptr); + bool print_in = false; + bool print_out = false; + + if (DEBUGLEVEL >= 11) { + print_in = true; + print_out = true; + } + + if (ndr_flags & NDR_IN) { + if (print_in) { + ndr_print_function_debug(call->ndr_print, + call->name, + ndr_flags, + struct_ptr); + } + } + if (ndr_flags & NDR_OUT) { + if (print_out) { + ndr_print_function_debug(call->ndr_print, + call->name, + ndr_flags, + struct_ptr); + } + } +} + +static const struct dcerpc_binding_handle_ops irpc_bh_ops = { + .name = "wbint", + .is_connected = irpc_bh_is_connected, + .set_timeout = irpc_bh_set_timeout, + .raw_call_send = irpc_bh_raw_call_send, + .raw_call_recv = irpc_bh_raw_call_recv, + .disconnect_send = irpc_bh_disconnect_send, + .disconnect_recv = irpc_bh_disconnect_recv, + + .ref_alloc = irpc_bh_ref_alloc, + .do_ndr_print = irpc_bh_do_ndr_print, +}; + +/* initialise a irpc binding handle */ +struct dcerpc_binding_handle *irpc_binding_handle(TALLOC_CTX *mem_ctx, + struct imessaging_context *msg_ctx, + struct server_id server_id, + const struct ndr_interface_table *table) +{ + struct dcerpc_binding_handle *h; + struct irpc_bh_state *hs; + + h = dcerpc_binding_handle_create(mem_ctx, + &irpc_bh_ops, + NULL, + table, + &hs, + struct irpc_bh_state, + __location__); + if (h == NULL) { + return NULL; + } + hs->msg_ctx = msg_ctx; + hs->server_id = server_id; + hs->table = table; + hs->timeout = IRPC_CALL_TIMEOUT; + + return h; +} + +struct dcerpc_binding_handle *irpc_binding_handle_by_name(TALLOC_CTX *mem_ctx, + struct imessaging_context *msg_ctx, + const char *dest_task, + const struct ndr_interface_table *table) +{ + struct dcerpc_binding_handle *h; + unsigned num_sids; + struct server_id *sids; + struct server_id sid; + NTSTATUS status; + + /* find the server task */ + + status = irpc_servers_byname(msg_ctx, mem_ctx, dest_task, + &num_sids, &sids); + if (!NT_STATUS_IS_OK(status)) { + errno = EADDRNOTAVAIL; + return NULL; + } + sid = sids[0]; + talloc_free(sids); + + h = irpc_binding_handle(mem_ctx, msg_ctx, + sid, table); + if (h == NULL) { + return NULL; + } + + return h; +} + +void irpc_binding_handle_add_security_token(struct dcerpc_binding_handle *h, + struct security_token *token) +{ + struct irpc_bh_state *hs = + dcerpc_binding_handle_data(h, + struct irpc_bh_state); + + hs->token = token; +} diff --git a/source4/lib/messaging/messaging.h b/source4/lib/messaging/messaging.h new file mode 100644 index 0000000..76b99ca --- /dev/null +++ b/source4/lib/messaging/messaging.h @@ -0,0 +1,72 @@ +/* + Unix SMB/CIFS implementation. + messages.c header + Copyright (C) Andrew Tridgell 2000 + Copyright (C) 2001, 2002 by Martin Pool + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SOURCE4_LIB_MESSAGING_MESSAGES_H_ +#define _SOURCE4_LIB_MESSAGING_MESSAGES_H_ + +#include "librpc/gen_ndr/server_id.h" +#include "lib/util/data_blob.h" +#include "librpc/gen_ndr/messaging.h" + +struct loadparm_context; +struct imessaging_context; + +/* taskid for messaging of parent process */ +#define SAMBA_PARENT_TASKID 0 + +typedef void (*msg_callback_t)( + struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data); + +NTSTATUS imessaging_send(struct imessaging_context *msg, struct server_id server, + uint32_t msg_type, const DATA_BLOB *data); +NTSTATUS imessaging_register(struct imessaging_context *msg, void *private_data, + uint32_t msg_type, + msg_callback_t fn); +NTSTATUS imessaging_register_tmp(struct imessaging_context *msg, void *private_data, + msg_callback_t fn, uint32_t *msg_type); +struct imessaging_context *imessaging_init(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct server_id server_id, + struct tevent_context *ev); +struct imessaging_context *imessaging_init_discard_incoming( + TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct server_id server_id, + struct tevent_context *ev); +void imessaging_dgm_unref_ev(struct tevent_context *ev); +NTSTATUS imessaging_reinit_all(void); +int imessaging_cleanup(struct imessaging_context *msg); +struct imessaging_context *imessaging_client_init(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct tevent_context *ev); +NTSTATUS imessaging_send_ptr(struct imessaging_context *msg, struct server_id server, + uint32_t msg_type, void *ptr); +size_t imessaging_deregister(struct imessaging_context *msg, uint32_t msg_type, void *private_data); +struct server_id imessaging_get_server_id(struct imessaging_context *msg_ctx); +NTSTATUS imessaging_process_cleanup(struct imessaging_context *msg_ctx, + pid_t pid); + +#endif diff --git a/source4/lib/messaging/messaging_handlers.c b/source4/lib/messaging/messaging_handlers.c new file mode 100644 index 0000000..57e3e1c --- /dev/null +++ b/source4/lib/messaging/messaging_handlers.c @@ -0,0 +1,135 @@ +/* + Unix SMB/CIFS implementation. + + Handers for non core Samba internal messages + + Handlers for messages that are only included in developer and self test + builds. + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/util/server_id.h" +#include "messaging/messaging.h" +#include "messaging/messaging_internal.h" + +#if defined(DEVELOPER) || defined(ENABLE_SELFTEST) + +/* + * Inject a fault into the currently running process + */ +static void do_inject_fault(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + int sig; + struct server_id_buf tmp; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + if (data->length != sizeof(sig)) { + DBG_ERR("Process %s sent bogus signal injection request\n", + server_id_str_buf(src, &tmp)); + return; + } + + sig = *(int *)data->data; + if (sig == -1) { + DBG_ERR("Process %s requested an iternal failure, " + "calling exit(1)\n", + server_id_str_buf(src, &tmp)); + exit(1); + } + +#if HAVE_STRSIGNAL + DBG_ERR("Process %s requested injection of signal %d (%s)\n", + server_id_str_buf(src, &tmp), + sig, + strsignal(sig)); +#else + DBG_ERR("Process %s requested injection of signal %d\n", + server_id_str_buf(src, &tmp), + sig); +#endif + + kill(getpid(), sig); +} + +/* + * Cause the current process to sleep for a specified number of seconds + */ +static void do_sleep(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + unsigned int seconds; + struct server_id_buf tmp; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + if (data->length != sizeof(seconds)) { + DBG_ERR("Process %s sent bogus sleep request\n", + server_id_str_buf(src, &tmp)); + return; + } + + seconds = *(unsigned int *)data->data; + DBG_ERR("Process %s requested a sleep of %u seconds\n", + server_id_str_buf(src, &tmp), + seconds); + sleep(seconds); + DBG_ERR("Restarting after %u second sleep requested by process %s\n", + seconds, + server_id_str_buf(src, &tmp)); +} + +/* + * Register the extra messaging handlers + */ +NTSTATUS imessaging_register_extra_handlers(struct imessaging_context *msg) +{ + NTSTATUS status; + + status = imessaging_register( + msg, NULL, MSG_SMB_INJECT_FAULT, do_inject_fault); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = imessaging_register(msg, NULL, MSG_SMB_SLEEP, do_sleep); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +#endif /* defined(DEVELOPER) || defined(ENABLE_SELFTEST) */ diff --git a/source4/lib/messaging/messaging_internal.h b/source4/lib/messaging/messaging_internal.h new file mode 100644 index 0000000..6281bda --- /dev/null +++ b/source4/lib/messaging/messaging_internal.h @@ -0,0 +1,50 @@ +/* + Unix SMB/CIFS implementation. + + Samba internal messaging functions + + Copyright (C) Andrew Tridgell 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +struct irpc_request; + +struct imessaging_context { + struct imessaging_context *prev, *next; + struct tevent_context *ev; + struct server_id server_id; + const char *sock_dir; + const char *lock_dir; + struct dispatch_fn **dispatch; + uint32_t num_types; + struct idr_context *dispatch_tree; + struct irpc_list *irpc; + struct idr_context *idr; + struct irpc_request *requests; + struct server_id_db *names; + struct timeval start_time; + void *msg_dgm_ref; + /* + * The number of instances waiting for incoming + * messages. By default it's always greater than 0. + * + * If it's 0 we'll discard incoming messages, + * see imessaging_init_discard_imcoming(). + */ + bool discard_incoming; + uint64_t num_incoming_listeners; +}; + +NTSTATUS imessaging_register_extra_handlers(struct imessaging_context *msg); diff --git a/source4/lib/messaging/messaging_send.c b/source4/lib/messaging/messaging_send.c new file mode 100644 index 0000000..24cdce3 --- /dev/null +++ b/source4/lib/messaging/messaging_send.c @@ -0,0 +1,115 @@ +/* + Unix SMB/CIFS implementation. + + Samba internal messaging functions (send). + + Copyright (C) Andrew Tridgell 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "messaging/messaging.h" +#include "messaging/irpc.h" +#include "lib/messaging/messages_dgm.h" +#include "lib/messaging/messages_dgm_ref.h" +#include "../source3/lib/messages_util.h" +#include "messaging/messaging_internal.h" +#include "lib/util/server_id_db.h" +#include "cluster/cluster.h" +#include "../lib/util/unix_privs.h" + +/* + * This file is for functions that can be called from auth_log without + * depending on all of dcerpc and so cause dep loops. + */ + +/* + return a list of server ids for a server name +*/ +NTSTATUS irpc_servers_byname(struct imessaging_context *msg_ctx, + TALLOC_CTX *mem_ctx, const char *name, + unsigned *num_servers, + struct server_id **servers) +{ + int ret; + + ret = server_id_db_lookup(msg_ctx->names, name, mem_ctx, + num_servers, servers); + if (ret != 0) { + return map_nt_error_from_unix_common(ret); + } + return NT_STATUS_OK; +} + +/* + Send a message to a particular server +*/ +NTSTATUS imessaging_send(struct imessaging_context *msg, struct server_id server, + uint32_t msg_type, const DATA_BLOB *data) +{ + uint8_t hdr[MESSAGE_HDR_LENGTH]; + struct iovec iov[2]; + int num_iov, ret; + pid_t pid; + void *priv; + + if (!cluster_node_equal(&msg->server_id, &server)) { + /* No cluster in source4... */ + return NT_STATUS_OK; + } + + message_hdr_put(hdr, msg_type, msg->server_id, server); + + iov[0] = (struct iovec) { .iov_base = &hdr, .iov_len = sizeof(hdr) }; + num_iov = 1; + + if (data != NULL) { + iov[1] = (struct iovec) { .iov_base = data->data, + .iov_len = data->length }; + num_iov += 1; + } + + pid = server.pid; + if (pid == 0) { + pid = getpid(); + } + + ret = messaging_dgm_send(pid, iov, num_iov, NULL, 0); + + if (ret == EACCES) { + priv = root_privileges(); + ret = messaging_dgm_send(pid, iov, num_iov, NULL, 0); + TALLOC_FREE(priv); + } + + if (ret != 0) { + return map_nt_error_from_unix_common(ret); + } + return NT_STATUS_OK; +} + +/* + Send a message to a particular server, with the message containing a single pointer +*/ +NTSTATUS imessaging_send_ptr(struct imessaging_context *msg, struct server_id server, + uint32_t msg_type, void *ptr) +{ + DATA_BLOB blob; + + blob.data = (uint8_t *)&ptr; + blob.length = sizeof(void *); + + return imessaging_send(msg, server, msg_type, &blob); +} diff --git a/source4/lib/messaging/pymessaging.c b/source4/lib/messaging/pymessaging.c new file mode 100644 index 0000000..6b34306 --- /dev/null +++ b/source4/lib/messaging/pymessaging.c @@ -0,0 +1,576 @@ +/* + Unix SMB/CIFS implementation. + Copyright © Jelmer Vernooij <jelmer@samba.org> 2008 + + Based on the equivalent for EJS: + Copyright © Andrew Tridgell <tridge@samba.org> 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "lib/replace/system/python.h" +#include "python/py3compat.h" +#include "includes.h" +#include "python/modules.h" +#include "libcli/util/pyerrors.h" +#include "librpc/rpc/pyrpc_util.h" +#include "librpc/ndr/libndr.h" +#include "lib/messaging/messaging.h" +#include "lib/messaging/irpc.h" +#include "lib/events/events.h" +#include "cluster/cluster.h" +#include "param/param.h" +#include "param/pyparam.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/gen_ndr/server_id.h" +#include <pytalloc.h> +#include "messaging_internal.h" + + +extern PyTypeObject imessaging_Type; + +static bool server_id_from_py(PyObject *object, struct server_id *server_id) +{ + Py_ssize_t tuple_size; + + if (!PyTuple_Check(object)) { + if (!py_check_dcerpc_type(object, "samba.dcerpc.server_id", "server_id")) { + + PyErr_SetString(PyExc_ValueError, "Expected tuple or server_id"); + return false; + } + *server_id = *pytalloc_get_type(object, struct server_id); + return true; + } + + tuple_size = PyTuple_Size(object); + if (tuple_size == 3) { + unsigned long long pid; + int task_id, vnn; + + if (!PyArg_ParseTuple(object, "Kii", &pid, &task_id, &vnn)) { + return false; + } + server_id->pid = pid; + server_id->task_id = task_id; + server_id->vnn = vnn; + return true; + } else if (tuple_size == 2) { + unsigned long long pid; + int task_id; + if (!PyArg_ParseTuple(object, "Ki", &pid, &task_id)) + return false; + *server_id = cluster_id(pid, task_id); + return true; + } else if (tuple_size == 1) { + unsigned long long pid = getpid(); + int task_id; + if (!PyArg_ParseTuple(object, "i", &task_id)) + return false; + *server_id = cluster_id(pid, task_id); + return true; + } else { + PyErr_SetString(PyExc_ValueError, "Expected tuple containing one, two, or three elements"); + return false; + } +} + +typedef struct { + PyObject_HEAD + TALLOC_CTX *mem_ctx; + struct imessaging_context *msg_ctx; +} imessaging_Object; + +static PyObject *py_imessaging_connect(PyTypeObject *self, PyObject *args, PyObject *kwargs) +{ + struct tevent_context *ev; + const char *kwnames[] = { "own_id", "lp_ctx", NULL }; + PyObject *own_id = Py_None; + PyObject *py_lp_ctx = Py_None; + imessaging_Object *ret; + struct loadparm_context *lp_ctx; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", + discard_const_p(char *, kwnames), &own_id, &py_lp_ctx)) { + return NULL; + } + + ret = PyObject_New(imessaging_Object, &imessaging_Type); + if (ret == NULL) + return NULL; + + ret->mem_ctx = talloc_new(NULL); + + lp_ctx = lpcfg_from_py_object(ret->mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + PyErr_SetString(PyExc_RuntimeError, "unable to interpret loadparm_context"); + talloc_free(ret->mem_ctx); + return NULL; + } + + ev = s4_event_context_init(ret->mem_ctx); + + if (own_id != Py_None) { + struct server_id server_id; + + if (!server_id_from_py(own_id, &server_id)) { + talloc_free(ret->mem_ctx); + return NULL; + } + + ret->msg_ctx = imessaging_init(ret->mem_ctx, + lp_ctx, + server_id, + ev); + } else { + ret->msg_ctx = imessaging_client_init(ret->mem_ctx, + lp_ctx, + ev); + } + + if (ret->msg_ctx == NULL) { + PyErr_SetString(PyExc_RuntimeError, "unable to create a messaging context"); + talloc_free(ret->mem_ctx); + return NULL; + } + + return (PyObject *)ret; +} + +static void py_imessaging_dealloc(PyObject *self) +{ + imessaging_Object *iface = (imessaging_Object *)self; + talloc_free(iface->msg_ctx); + self->ob_type->tp_free(self); +} + +static PyObject *py_imessaging_send(PyObject *self, PyObject *args, PyObject *kwargs) +{ + imessaging_Object *iface = (imessaging_Object *)self; + uint32_t msg_type; + DATA_BLOB data; + PyObject *target; + NTSTATUS status; + struct server_id server; + const char *kwnames[] = { "target", "msg_type", "data", NULL }; + Py_ssize_t length; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OIs#:send", + discard_const_p(char *, kwnames), &target, &msg_type, &data.data, &length)) { + + return NULL; + } + + data.length = length; + + if (!server_id_from_py(target, &server)) + return NULL; + + status = imessaging_send(iface->msg_ctx, server, msg_type, &data); + if (NT_STATUS_IS_ERR(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static void py_msg_callback_wrapper(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + PyObject *py_server_id, *callback_and_tuple = (PyObject *)private_data; + PyObject *callback, *py_private; + PyObject *result = NULL; + + struct server_id *p_server_id = NULL; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + p_server_id = talloc(NULL, struct server_id); + if (!p_server_id) { + PyErr_NoMemory(); + return; + } + *p_server_id = server_id; + + py_server_id = py_return_ndr_struct("samba.dcerpc.server_id", "server_id", p_server_id, p_server_id); + talloc_unlink(NULL, p_server_id); + if (py_server_id == NULL) { + return; + } + + if (!PyArg_ParseTuple(callback_and_tuple, "OO", + &callback, + &py_private)) { + return; + } + + result = PyObject_CallFunction(callback, discard_const_p(char, "OiOs#"), + py_private, + msg_type, + py_server_id, + data->data, data->length); + Py_XDECREF(result); +} + +static PyObject *py_imessaging_register(PyObject *self, PyObject *args, PyObject *kwargs) +{ + imessaging_Object *iface = (imessaging_Object *)self; + int msg_type = -1; + PyObject *callback_and_context; + NTSTATUS status; + const char *kwnames[] = { "callback_and_context", "msg_type", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i:register", + discard_const_p(char *, kwnames), + &callback_and_context, &msg_type)) { + return NULL; + } + if (!PyTuple_Check(callback_and_context) + || PyTuple_Size(callback_and_context) != 2) { + PyErr_SetString(PyExc_ValueError, "Expected tuple of size 2 for callback_and_context"); + return NULL; + } + + Py_INCREF(callback_and_context); + + if (msg_type == -1) { + uint32_t msg_type32 = msg_type; + status = imessaging_register_tmp(iface->msg_ctx, callback_and_context, + py_msg_callback_wrapper, &msg_type32); + msg_type = msg_type32; + } else { + status = imessaging_register(iface->msg_ctx, callback_and_context, + msg_type, py_msg_callback_wrapper); + } + if (NT_STATUS_IS_ERR(status)) { + Py_DECREF(callback_and_context); + PyErr_SetNTSTATUS(status); + return NULL; + } + + return PyLong_FromLong(msg_type); +} + +static PyObject *py_imessaging_deregister(PyObject *self, PyObject *args, PyObject *kwargs) +{ + imessaging_Object *iface = (imessaging_Object *)self; + int msg_type = -1; + PyObject *callback; + const char *kwnames[] = { "callback", "msg_type", NULL }; + size_t removed; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i:deregister", + discard_const_p(char *, kwnames), &callback, &msg_type)) { + return NULL; + } + + removed = imessaging_deregister(iface->msg_ctx, msg_type, callback); + while (removed-- > 0) { + Py_DECREF(callback); + } + + Py_RETURN_NONE; +} + +static void simple_timer_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + return; +} + +static PyObject *py_imessaging_loop_once(PyObject *self, PyObject *args, PyObject *kwargs) +{ + imessaging_Object *iface = (imessaging_Object *)self; + double offset; + int seconds; + struct timeval next_event; + struct tevent_timer *timer = NULL; + const char *kwnames[] = { "timeout", NULL }; + + TALLOC_CTX *frame = talloc_stackframe(); + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "d", + discard_const_p(char *, kwnames), &offset)) { + TALLOC_FREE(frame); + return NULL; + } + + if (offset != 0.0) { + seconds = offset; + offset -= seconds; + next_event = tevent_timeval_current_ofs(seconds, (int)(offset*1000000)); + + timer = tevent_add_timer(iface->msg_ctx->ev, frame, next_event, simple_timer_handler, + NULL); + if (timer == NULL) { + PyErr_NoMemory(); + TALLOC_FREE(frame); + return NULL; + } + } + + tevent_loop_once(iface->msg_ctx->ev); + + TALLOC_FREE(frame); + + Py_RETURN_NONE; +} + +static PyObject *py_irpc_add_name(PyObject *self, PyObject *args) +{ + imessaging_Object *iface = (imessaging_Object *)self; + char *server_name; + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "s", &server_name)) { + return NULL; + } + + status = irpc_add_name(iface->msg_ctx, server_name); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_irpc_remove_name(PyObject *self, PyObject *args) +{ + imessaging_Object *iface = (imessaging_Object *)self; + char *server_name; + + if (!PyArg_ParseTuple(args, "s", &server_name)) { + return NULL; + } + + irpc_remove_name(iface->msg_ctx, server_name); + + Py_RETURN_NONE; +} + +static PyObject *py_irpc_servers_byname(PyObject *self, PyObject *args) +{ + imessaging_Object *iface = (imessaging_Object *)self; + char *server_name; + unsigned i, num_ids; + struct server_id *ids; + PyObject *pylist; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + NTSTATUS status; + + if (!mem_ctx) { + PyErr_NoMemory(); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &server_name)) { + TALLOC_FREE(mem_ctx); + return NULL; + } + + status = irpc_servers_byname(iface->msg_ctx, mem_ctx, server_name, + &num_ids, &ids); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(mem_ctx); + PyErr_SetString(PyExc_KeyError, "No such name"); + return NULL; + } + + pylist = PyList_New(num_ids); + if (pylist == NULL) { + TALLOC_FREE(mem_ctx); + PyErr_NoMemory(); + return NULL; + } + for (i = 0; i < num_ids; i++) { + PyObject *py_server_id; + struct server_id *p_server_id = talloc(NULL, struct server_id); + if (!p_server_id) { + TALLOC_FREE(mem_ctx); + PyErr_NoMemory(); + return NULL; + } + *p_server_id = ids[i]; + + py_server_id = py_return_ndr_struct("samba.dcerpc.server_id", "server_id", p_server_id, p_server_id); + if (!py_server_id) { + TALLOC_FREE(mem_ctx); + return NULL; + } + PyList_SetItem(pylist, i, py_server_id); + talloc_unlink(NULL, p_server_id); + } + TALLOC_FREE(mem_ctx); + return pylist; +} + +static PyObject *py_irpc_all_servers(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + imessaging_Object *iface = (imessaging_Object *)self; + PyObject *pylist; + int i; + struct irpc_name_records *records; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + if (!mem_ctx) { + PyErr_NoMemory(); + return NULL; + } + + records = irpc_all_servers(iface->msg_ctx, mem_ctx); + if (records == NULL) { + TALLOC_FREE(mem_ctx); + PyErr_NoMemory(); + return NULL; + } + + pylist = PyList_New(records->num_records); + if (pylist == NULL) { + TALLOC_FREE(mem_ctx); + PyErr_NoMemory(); + return NULL; + } + for (i = 0; i < records->num_records; i++) { + PyObject *py_name_record + = py_return_ndr_struct("samba.dcerpc.irpc", + "name_record", + records->names[i], + records->names[i]); + if (!py_name_record) { + TALLOC_FREE(mem_ctx); + return NULL; + } + PyList_SetItem(pylist, i, + py_name_record); + } + TALLOC_FREE(mem_ctx); + return pylist; +} + +static PyMethodDef py_imessaging_methods[] = { + { "send", PY_DISCARD_FUNC_SIG(PyCFunction, py_imessaging_send), + METH_VARARGS|METH_KEYWORDS, + "S.send(target, msg_type, data) -> None\nSend a message" }, + { "register", PY_DISCARD_FUNC_SIG(PyCFunction, py_imessaging_register), + METH_VARARGS|METH_KEYWORDS, + "S.register((callback, context), msg_type=None) -> msg_type\nRegister a message handler. " + "The callback and context must be supplied as a two-element tuple." }, + { "deregister", PY_DISCARD_FUNC_SIG(PyCFunction, + py_imessaging_deregister), + METH_VARARGS|METH_KEYWORDS, + "S.deregister((callback, context), msg_type) -> None\nDeregister a message handler " + "The callback and context must be supplied as the exact same two-element tuple " + "as was used at registration time." }, + { "loop_once", PY_DISCARD_FUNC_SIG(PyCFunction, + py_imessaging_loop_once), + METH_VARARGS|METH_KEYWORDS, + "S.loop_once(timeout) -> None\n" + "Loop on the internal event context until we get an event " + "(which might be a message calling the callback), " + "timeout after timeout seconds (if not 0)" }, + { "irpc_add_name", (PyCFunction)py_irpc_add_name, METH_VARARGS, + "S.irpc_add_name(name) -> None\n" + "Add this context to the list of server_id values that " + "are registered for a particular name" }, + { "irpc_remove_name", (PyCFunction)py_irpc_remove_name, METH_VARARGS, + "S.irpc_remove_name(name) -> None\n" + "Remove this context from the list of server_id values that " + "are registered for a particular name" }, + { "irpc_servers_byname", (PyCFunction)py_irpc_servers_byname, METH_VARARGS, + "S.irpc_servers_byname(name) -> list\nGet list of server_id values that are registered for a particular name" }, + { "irpc_all_servers", (PyCFunction)py_irpc_all_servers, METH_NOARGS, + "S.irpc_all_servers() -> list\n" + "Get list of all registered names and the associated server_id values" }, + { NULL, NULL, 0, NULL } +}; + +static PyObject *py_imessaging_server_id(PyObject *obj, void *closure) +{ + imessaging_Object *iface = (imessaging_Object *)obj; + PyObject *py_server_id; + struct server_id server_id = imessaging_get_server_id(iface->msg_ctx); + struct server_id *p_server_id = talloc(NULL, struct server_id); + if (!p_server_id) { + PyErr_NoMemory(); + return NULL; + } + *p_server_id = server_id; + + py_server_id = py_return_ndr_struct("samba.dcerpc.server_id", "server_id", p_server_id, p_server_id); + talloc_unlink(NULL, p_server_id); + + return py_server_id; +} + +static PyGetSetDef py_imessaging_getset[] = { + { + .name = discard_const_p(char, "server_id"), + .get = py_imessaging_server_id, + .doc = discard_const_p(char, "local server id") + }, + { .name = NULL }, +}; + + +PyTypeObject imessaging_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "messaging.Messaging", + .tp_basicsize = sizeof(imessaging_Object), + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + .tp_new = py_imessaging_connect, + .tp_dealloc = py_imessaging_dealloc, + .tp_methods = py_imessaging_methods, + .tp_getset = py_imessaging_getset, + .tp_doc = "Messaging(own_id=None, lp_ctx=None)\n" \ + "Create a new object that can be used to communicate with the peers in the specified messaging path.\n" +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "messaging", + .m_doc = "Internal RPC", + .m_size = -1, + .m_methods = NULL, +}; + +MODULE_INIT_FUNC(messaging) +{ + PyObject *mod; + + if (PyType_Ready(&imessaging_Type) < 0) + return NULL; + + mod = PyModule_Create(&moduledef); + if (mod == NULL) + return NULL; + + Py_INCREF((PyObject *)&imessaging_Type); + PyModule_AddObject(mod, "Messaging", (PyObject *)&imessaging_Type); + PyModule_AddObject(mod, "IRPC_CALL_TIMEOUT", PyLong_FromLong(IRPC_CALL_TIMEOUT)); + PyModule_AddObject(mod, "IRPC_CALL_TIMEOUT_INF", PyLong_FromLong(IRPC_CALL_TIMEOUT_INF)); + + return mod; +} diff --git a/source4/lib/messaging/tests/irpc.c b/source4/lib/messaging/tests/irpc.c new file mode 100644 index 0000000..466b47f --- /dev/null +++ b/source4/lib/messaging/tests/irpc.c @@ -0,0 +1,308 @@ +/* + Unix SMB/CIFS implementation. + + local test for irpc code + + Copyright (C) Andrew Tridgell 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/events/events.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_echo.h" +#include "librpc/gen_ndr/ndr_echo_c.h" +#include "torture/torture.h" +#include "cluster/cluster.h" +#include "param/param.h" +#include "torture/local/proto.h" + +const uint32_t MSG_ID1 = 1, MSG_ID2 = 2; + +static bool test_debug; + +struct irpc_test_data +{ + struct imessaging_context *msg_ctx1, *msg_ctx2; + struct tevent_context *ev; +}; + +/* + serve up AddOne over the irpc system +*/ +static NTSTATUS irpc_AddOne(struct irpc_message *irpc, struct echo_AddOne *r) +{ + *r->out.out_data = r->in.in_data + 1; + if (test_debug) { + printf("irpc_AddOne: in=%u in+1=%u out=%u\n", + r->in.in_data, r->in.in_data+1, *r->out.out_data); + } + return NT_STATUS_OK; +} + +/* + a deferred reply to echodata +*/ +static void deferred_echodata(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *private_data) +{ + struct irpc_message *irpc = talloc_get_type(private_data, struct irpc_message); + struct echo_EchoData *r = (struct echo_EchoData *)irpc->data; + r->out.out_data = (uint8_t *)talloc_memdup(r, r->in.in_data, r->in.len); + if (r->out.out_data == NULL) { + irpc_send_reply(irpc, NT_STATUS_NO_MEMORY); + } + printf("sending deferred reply\n"); + irpc_send_reply(irpc, NT_STATUS_OK); +} + + +/* + serve up EchoData over the irpc system +*/ +static NTSTATUS irpc_EchoData(struct irpc_message *irpc, struct echo_EchoData *r) +{ + struct irpc_test_data *data = talloc_get_type_abort(irpc->private_data, struct irpc_test_data); + irpc->defer_reply = true; + tevent_add_timer(data->ev, irpc, timeval_zero(), deferred_echodata, irpc); + return NT_STATUS_OK; +} + + +/* + test a addone call over the internal messaging system +*/ +static bool test_addone(struct torture_context *test, const void *_data, + const void *_value) +{ + struct echo_AddOne r; + NTSTATUS status; + const struct irpc_test_data *data = (const struct irpc_test_data *)_data; + uint32_t value = *(const uint32_t *)_value; + struct dcerpc_binding_handle *irpc_handle; + + irpc_handle = irpc_binding_handle(test, data->msg_ctx1, + cluster_id(0, MSG_ID2), + &ndr_table_rpcecho); + torture_assert(test, irpc_handle, "no memory"); + + /* make the call */ + r.in.in_data = value; + + test_debug = true; + /* + * Note: this makes use of nested event loops + * as client and server use the same loop. + */ + dcerpc_binding_handle_set_sync_ev(irpc_handle, data->ev); + status = dcerpc_echo_AddOne_r(irpc_handle, test, &r); + test_debug = false; + torture_assert_ntstatus_ok(test, status, "AddOne failed"); + + /* check the answer */ + torture_assert(test, *r.out.out_data == r.in.in_data + 1, + "AddOne wrong answer"); + + torture_comment(test, "%u + 1 = %u\n", r.in.in_data, *r.out.out_data); + return true; +} + +/* + test a echodata call over the internal messaging system +*/ +static bool test_echodata(struct torture_context *tctx, + const void *tcase_data, + const void *test_data) +{ + struct echo_EchoData r; + NTSTATUS status; + const struct irpc_test_data *data = (const struct irpc_test_data *)tcase_data; + TALLOC_CTX *mem_ctx = tctx; + struct dcerpc_binding_handle *irpc_handle; + + irpc_handle = irpc_binding_handle(mem_ctx, data->msg_ctx1, + cluster_id(0, MSG_ID2), + &ndr_table_rpcecho); + torture_assert(tctx, irpc_handle, "no memory"); + + /* make the call */ + r.in.in_data = (unsigned char *)talloc_strdup(mem_ctx, "0123456789"); + r.in.len = strlen((char *)r.in.in_data); + + /* + * Note: this makes use of nested event loops + * as client and server use the same loop. + */ + dcerpc_binding_handle_set_sync_ev(irpc_handle, data->ev); + status = dcerpc_echo_EchoData_r(irpc_handle, mem_ctx, &r); + torture_assert_ntstatus_ok(tctx, status, "EchoData failed"); + + /* check the answer */ + if (memcmp(r.out.out_data, r.in.in_data, r.in.len) != 0) { + NDR_PRINT_OUT_DEBUG(echo_EchoData, &r); + torture_fail(tctx, "EchoData wrong answer"); + } + + torture_comment(tctx, "Echo '%*.*s' -> '%*.*s'\n", + r.in.len, r.in.len, + r.in.in_data, + r.in.len, r.in.len, + r.out.out_data); + return true; +} + +struct irpc_callback_state { + struct echo_AddOne r; + int *pong_count; +}; + +static void irpc_callback(struct tevent_req *subreq) +{ + struct irpc_callback_state *s = + tevent_req_callback_data(subreq, + struct irpc_callback_state); + NTSTATUS status; + + status = dcerpc_echo_AddOne_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + printf("irpc call failed - %s\n", nt_errstr(status)); + } + if (*s->r.out.out_data != s->r.in.in_data + 1) { + printf("AddOne wrong answer - %u + 1 = %u should be %u\n", + s->r.in.in_data, *s->r.out.out_data, s->r.in.in_data+1); + } + (*s->pong_count)++; +} + +/* + test echo speed +*/ +static bool test_speed(struct torture_context *tctx, + const void *tcase_data, + const void *test_data) +{ + int ping_count = 0; + int pong_count = 0; + const struct irpc_test_data *data = (const struct irpc_test_data *)tcase_data; + struct timeval tv; + TALLOC_CTX *mem_ctx = tctx; + int timelimit = torture_setting_int(tctx, "timelimit", 10); + struct dcerpc_binding_handle *irpc_handle; + + irpc_handle = irpc_binding_handle(mem_ctx, data->msg_ctx1, + cluster_id(0, MSG_ID2), + &ndr_table_rpcecho); + torture_assert(tctx, irpc_handle, "no memory"); + + tv = timeval_current(); + + torture_comment(tctx, "Sending echo for %d seconds\n", timelimit); + while (timeval_elapsed(&tv) < timelimit) { + struct tevent_req *subreq; + struct irpc_callback_state *s; + + s = talloc_zero(mem_ctx, struct irpc_callback_state); + torture_assert(tctx, s != NULL, "no mem"); + + s->pong_count = &pong_count; + + subreq = dcerpc_echo_AddOne_r_send(mem_ctx, + tctx->ev, + irpc_handle, + &s->r); + torture_assert(tctx, subreq != NULL, "AddOne send failed"); + + tevent_req_set_callback(subreq, irpc_callback, s); + + ping_count++; + + while (ping_count > pong_count + 20) { + tevent_loop_once(data->ev); + } + } + + torture_comment(tctx, "waiting for %d remaining replies (done %d)\n", + ping_count - pong_count, pong_count); + while (timeval_elapsed(&tv) < 30 && pong_count < ping_count) { + tevent_loop_once(data->ev); + } + + torture_assert_int_equal(tctx, ping_count, pong_count, "ping test failed"); + + torture_comment(tctx, "echo rate of %.0f messages/sec\n", + (ping_count+pong_count)/timeval_elapsed(&tv)); + return true; +} + + +static bool irpc_setup(struct torture_context *tctx, void **_data) +{ + struct irpc_test_data *data; + + *_data = data = talloc(tctx, struct irpc_test_data); + + lpcfg_set_cmdline(tctx->lp_ctx, "pid directory", "piddir.tmp"); + + data->ev = tctx->ev; + torture_assert(tctx, data->msg_ctx1 = + imessaging_init(tctx, + tctx->lp_ctx, + cluster_id(0, MSG_ID1), + data->ev), + "Failed to init first messaging context"); + + torture_assert(tctx, data->msg_ctx2 = + imessaging_init(tctx, + tctx->lp_ctx, + cluster_id(0, MSG_ID2), + data->ev), + "Failed to init second messaging context"); + + /* register the server side function */ + IRPC_REGISTER(data->msg_ctx1, rpcecho, ECHO_ADDONE, irpc_AddOne, data); + IRPC_REGISTER(data->msg_ctx2, rpcecho, ECHO_ADDONE, irpc_AddOne, data); + + IRPC_REGISTER(data->msg_ctx1, rpcecho, ECHO_ECHODATA, irpc_EchoData, data); + IRPC_REGISTER(data->msg_ctx2, rpcecho, ECHO_ECHODATA, irpc_EchoData, data); + + return true; +} + +struct torture_suite *torture_local_irpc(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "irpc"); + struct torture_tcase *tcase = torture_suite_add_tcase(suite, "irpc"); + int i; + uint32_t *values = talloc_array(tcase, uint32_t, 5); + + values[0] = 0; + values[1] = 0x7FFFFFFE; + values[2] = 0xFFFFFFFE; + values[3] = 0xFFFFFFFF; + values[4] = random() & 0xFFFFFFFF; + + tcase->setup = irpc_setup; + + for (i = 0; i < 5; i++) { + torture_tcase_add_test_const(tcase, "addone", test_addone, + (void *)&values[i]); + } + + torture_tcase_add_test_const(tcase, "echodata", test_echodata, NULL); + torture_tcase_add_test_const(tcase, "speed", test_speed, NULL); + + return suite; +} diff --git a/source4/lib/messaging/tests/messaging.c b/source4/lib/messaging/tests/messaging.c new file mode 100644 index 0000000..dcbbc19 --- /dev/null +++ b/source4/lib/messaging/tests/messaging.c @@ -0,0 +1,694 @@ +/* + Unix SMB/CIFS implementation. + + local test for messaging code + + Copyright (C) Andrew Tridgell 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/events/events.h" +#include "lib/messaging/irpc.h" +#include "torture/torture.h" +#include "cluster/cluster.h" +#include "param/param.h" +#include "torture/local/proto.h" +#include "system/select.h" +#include "system/filesys.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +static uint32_t msg_pong; + +static void ping_message(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + NTSTATUS status; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + status = imessaging_send(msg, src, msg_pong, data); + if (!NT_STATUS_IS_OK(status)) { + printf("pong failed - %s\n", nt_errstr(status)); + } +} + +static void pong_message(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + int *count = (int *)private_data; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + (*count)++; +} + +static void exit_message(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + talloc_free(private_data); + exit(0); +} + +/* + test ping speed +*/ +static bool test_ping_speed(struct torture_context *tctx) +{ + struct tevent_context *ev; + struct imessaging_context *msg_client_ctx; + struct imessaging_context *msg_server_ctx; + int ping_count = 0; + int pong_count = 0; + struct timeval tv; + int timelimit = torture_setting_int(tctx, "timelimit", 10); + uint32_t msg_ping, msg_exit; + + lpcfg_set_cmdline(tctx->lp_ctx, "pid directory", "piddir.tmp"); + + ev = tctx->ev; + + msg_server_ctx = imessaging_init(tctx, + tctx->lp_ctx, cluster_id(0, 1), + ev); + + torture_assert(tctx, msg_server_ctx != NULL, "Failed to init ping messaging context"); + + imessaging_register_tmp(msg_server_ctx, NULL, ping_message, &msg_ping); + imessaging_register_tmp(msg_server_ctx, tctx, exit_message, &msg_exit); + + msg_client_ctx = imessaging_init(tctx, + tctx->lp_ctx, + cluster_id(0, 2), + ev); + + torture_assert(tctx, msg_client_ctx != NULL, + "msg_client_ctx imessaging_init() failed"); + + imessaging_register_tmp(msg_client_ctx, &pong_count, pong_message, &msg_pong); + + tv = timeval_current(); + + torture_comment(tctx, "Sending pings for %d seconds\n", timelimit); + while (timeval_elapsed(&tv) < timelimit) { + DATA_BLOB data; + NTSTATUS status1, status2; + + data.data = discard_const_p(uint8_t, "testing"); + data.length = strlen((const char *)data.data); + + status1 = imessaging_send(msg_client_ctx, cluster_id(0, 1), msg_ping, &data); + status2 = imessaging_send(msg_client_ctx, cluster_id(0, 1), msg_ping, NULL); + + torture_assert_ntstatus_ok(tctx, status1, "msg1 failed"); + ping_count++; + + torture_assert_ntstatus_ok(tctx, status2, "msg2 failed"); + ping_count++; + + while (ping_count > pong_count + 20) { + tevent_loop_once(ev); + } + } + + torture_comment(tctx, "waiting for %d remaining replies (done %d)\n", + ping_count - pong_count, pong_count); + while (timeval_elapsed(&tv) < 30 && pong_count < ping_count) { + tevent_loop_once(ev); + } + + torture_comment(tctx, "sending exit\n"); + imessaging_send(msg_client_ctx, cluster_id(0, 1), msg_exit, NULL); + + torture_assert_int_equal(tctx, ping_count, pong_count, "ping test failed"); + + torture_comment(tctx, "ping rate of %.0f messages/sec\n", + (ping_count+pong_count)/timeval_elapsed(&tv)); + + talloc_free(msg_client_ctx); + talloc_free(msg_server_ctx); + + return true; +} + +static bool test_messaging_overflow(struct torture_context *tctx) +{ + struct imessaging_context *msg_ctx; + ssize_t nwritten, nread; + pid_t child; + char c = 0; + int up_pipe[2], down_pipe[2]; + int i, ret, child_status; + + ret = pipe(up_pipe); + torture_assert(tctx, ret == 0, "pipe failed"); + ret = pipe(down_pipe); + torture_assert(tctx, ret == 0, "pipe failed"); + + child = fork(); + if (child < 0) { + torture_fail(tctx, "fork failed"); + } + + if (child == 0) { + ret = tevent_re_initialise(tctx->ev); + torture_assert(tctx, ret == 0, "tevent_re_initialise failed"); + + msg_ctx = imessaging_init(tctx, tctx->lp_ctx, + cluster_id(getpid(), 0), + tctx->ev); + torture_assert(tctx, msg_ctx != NULL, + "imessaging_init failed"); + + do { + nwritten = write(up_pipe[1], &c, 1); + } while ((nwritten == -1) && (errno == EINTR)); + + ret = close(down_pipe[1]); + torture_assert(tctx, ret == 0, "close failed"); + + do { + nread = read(down_pipe[0], &c, 1); + } while ((nread == -1) && (errno == EINTR)); + + exit(0); + } + + do { + nread = read(up_pipe[0], &c, 1); + } while ((nread == -1) && (errno == EINTR)); + + msg_ctx = imessaging_init(tctx, tctx->lp_ctx, cluster_id(getpid(), 0), + tctx->ev); + torture_assert(tctx, msg_ctx != NULL, "imessaging_init failed"); + + for (i=0; i<1000; i++) { + NTSTATUS status; + status = imessaging_send(msg_ctx, cluster_id(child, 0), + MSG_PING, NULL); + torture_assert_ntstatus_ok(tctx, status, + "imessaging_send failed"); + } + + tevent_loop_once(tctx->ev); + + talloc_free(msg_ctx); + + ret = close(down_pipe[1]); + torture_assert(tctx, ret == 0, "close failed"); + + ret = waitpid(child, &child_status, 0); + torture_assert(tctx, ret == child, "wrong child exited"); + torture_assert(tctx, child_status == 0, "child failed"); + + poll(NULL, 0, 500); + + return true; +} + +struct overflow_parent_child { + gnutls_hash_hd_t md5_hash_hnd; + bool done; +}; + +static void overflow_md5_child_handler(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct overflow_parent_child *state = private_data; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + if (data->length == 0) { + state->done = true; + return; + } + + gnutls_hash(state->md5_hash_hnd, data->data, data->length); +} + +struct overflow_child_parent { + uint8_t final[16]; + bool done; +}; + +static void overflow_md5_parent_handler(struct imessaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct overflow_child_parent *state = private_data; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + if (data->length != sizeof(state->final)) { + memset(state->final, 0, sizeof(state->final)); + state->done = true; + return; + } + memcpy(state->final, data->data, 16); + state->done = true; +} + +static bool test_messaging_overflow_check(struct torture_context *tctx) +{ + struct imessaging_context *msg_ctx; + ssize_t nwritten, nread; + pid_t child; + char c = 0; + int up_pipe[2], down_pipe[2]; + int i, ret, child_status; + gnutls_hash_hd_t hash_hnd; + uint8_t final[16]; + struct overflow_child_parent child_msg = { .done = false }; + NTSTATUS status; + + ret = pipe(up_pipe); + torture_assert(tctx, ret == 0, "pipe failed"); + ret = pipe(down_pipe); + torture_assert(tctx, ret == 0, "pipe failed"); + + child = fork(); + if (child < 0) { + torture_fail(tctx, "fork failed"); + } + + if (child == 0) { + struct overflow_parent_child child_state = { .done = false }; + DATA_BLOB retblob = { .data = final, .length = sizeof(final) }; + + ret = tevent_re_initialise(tctx->ev); + torture_assert(tctx, ret == 0, "tevent_re_initialise failed"); + + gnutls_hash_init(&child_state.md5_hash_hnd, GNUTLS_DIG_MD5); + + msg_ctx = imessaging_init(tctx, tctx->lp_ctx, + cluster_id(getpid(), 0), + tctx->ev); + torture_assert(tctx, msg_ctx != NULL, + "imessaging_init failed"); + + status = imessaging_register(msg_ctx, &child_state, + MSG_TMP_BASE-1, + overflow_md5_child_handler); + torture_assert(tctx, NT_STATUS_IS_OK(status), + "imessaging_register failed"); + + do { + nwritten = write(up_pipe[1], &c, 1); + } while ((nwritten == -1) && (errno == EINTR)); + + ret = close(down_pipe[1]); + torture_assert(tctx, ret == 0, "close failed"); + + do { + nread = read(down_pipe[0], &c, 1); + } while ((nread == -1) && (errno == EINTR)); + + while (!child_state.done) { + tevent_loop_once(tctx->ev); + } + + gnutls_hash_deinit(child_state.md5_hash_hnd, final); + + status = imessaging_send(msg_ctx, + cluster_id(getppid(), 0), + MSG_TMP_BASE-2, + &retblob); + torture_assert(tctx, NT_STATUS_IS_OK(status), + "imessaging_send failed"); + + exit(0); + } + + do { + nread = read(up_pipe[0], &c, 1); + } while ((nread == -1) && (errno == EINTR)); + + msg_ctx = imessaging_init(tctx, tctx->lp_ctx, cluster_id(getpid(), 0), + tctx->ev); + torture_assert(tctx, msg_ctx != NULL, "imessaging_init failed"); + + status = imessaging_register(msg_ctx, + &child_msg, + MSG_TMP_BASE-2, + overflow_md5_parent_handler); + torture_assert(tctx, + NT_STATUS_IS_OK(status), + "imessaging_register failed"); + + gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + + for (i=0; i<1000; i++) { + size_t len = ((random() % 100) + 1); + uint8_t buf[len]; + DATA_BLOB blob = { .data = buf, .length = len }; + + generate_random_buffer(buf, len); + + gnutls_hash(hash_hnd, buf, len); + + status = imessaging_send(msg_ctx, cluster_id(child, 0), + MSG_TMP_BASE-1, &blob); + torture_assert_ntstatus_ok(tctx, status, + "imessaging_send failed"); + } + + status = imessaging_send(msg_ctx, cluster_id(child, 0), + MSG_TMP_BASE-1, NULL); + torture_assert_ntstatus_ok(tctx, status, + "imessaging_send failed"); + + gnutls_hash_deinit(hash_hnd, final); + + do { + nwritten = write(down_pipe[1], &c, 1); + } while ((nwritten == -1) && (errno == EINTR)); + + while (!child_msg.done) { + tevent_loop_once(tctx->ev); + } + + ret = close(down_pipe[1]); + torture_assert(tctx, ret == 0, "close failed"); + + talloc_free(msg_ctx); + + ret = waitpid(child, &child_status, 0); + torture_assert(tctx, ret == child, "wrong child exited"); + torture_assert(tctx, child_status == 0, "child failed"); + + if (memcmp(final, child_msg.final, 16) != 0) { + dump_data_file(final, 16, false, stderr); + dump_data_file(child_msg.final, 16, false, stderr); + fflush(stderr); + torture_fail(tctx, "checksum comparison failed"); + } + + return true; +} + +struct test_multi_ctx { + struct torture_context *tctx; + struct imessaging_context *server_ctx; + struct imessaging_context *client_ctx[4]; + size_t num_missing; + bool got_server; + bool got_client_0_1; + bool got_client_2_3; + bool ok; +}; + +static void multi_ctx_server_handler(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct test_multi_ctx *state = private_data; + char *str = NULL; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + torture_assert_goto(state->tctx, state->num_missing >= 1, + state->ok, fail, + "num_missing should be at least 1."); + state->num_missing -= 1; + + torture_assert_goto(state->tctx, !state->got_server, + state->ok, fail, + "already got server."); + state->got_server = true; + + /* + * We free the context itself and most likely reuse + * the memory immediately. + */ + TALLOC_FREE(state->server_ctx); + str = generate_random_str(state->tctx, 128); + torture_assert_goto(state->tctx, str != NULL, + state->ok, fail, + "generate_random_str()"); + +fail: + return; +} + +static void multi_ctx_client_0_1_handler(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct test_multi_ctx *state = private_data; + char *str = NULL; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + torture_assert_goto(state->tctx, state->num_missing >= 2, + state->ok, fail, + "num_missing should be at least 2."); + state->num_missing -= 2; + + torture_assert_goto(state->tctx, !state->got_client_0_1, + state->ok, fail, + "already got client_0_1."); + state->got_client_0_1 = true; + + /* + * We free two contexts and most likely reuse + * the memory immediately. + */ + TALLOC_FREE(state->client_ctx[0]); + str = generate_random_str(state->tctx, 128); + torture_assert_goto(state->tctx, str != NULL, + state->ok, fail, + "generate_random_str()"); + TALLOC_FREE(state->client_ctx[1]); + str = generate_random_str(state->tctx, 128); + torture_assert_goto(state->tctx, str != NULL, + state->ok, fail, + "generate_random_str()"); + +fail: + return; +} + +static void multi_ctx_client_2_3_handler(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct test_multi_ctx *state = private_data; + char *str = NULL; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + torture_assert_goto(state->tctx, state->num_missing >= 2, + state->ok, fail, + "num_missing should be at least 2."); + state->num_missing -= 2; + + torture_assert_goto(state->tctx, !state->got_client_2_3, + state->ok, fail, + "already got client_2_3."); + state->got_client_2_3 = true; + + /* + * We free two contexts and most likely reuse + * the memory immediately. + */ + TALLOC_FREE(state->client_ctx[2]); + str = generate_random_str(state->tctx, 128); + torture_assert_goto(state->tctx, str != NULL, + state->ok, fail, + "generate_random_str()"); + TALLOC_FREE(state->client_ctx[3]); + str = generate_random_str(state->tctx, 128); + torture_assert_goto(state->tctx, str != NULL, + state->ok, fail, + "generate_random_str()"); + +fail: + return; +} + +static bool test_multi_ctx(struct torture_context *tctx) +{ + struct test_multi_ctx state = { + .tctx = tctx, + .ok = true, + }; + struct timeval tv; + NTSTATUS status; + + lpcfg_set_cmdline(tctx->lp_ctx, "pid directory", "piddir.tmp"); + + /* + * We use cluster_id(0, 0) as that gets for + * all task ids. + */ + state.server_ctx = imessaging_init(tctx, + tctx->lp_ctx, + cluster_id(0, 0), + tctx->ev); + torture_assert(tctx, state.server_ctx != NULL, + "Failed to init messaging context"); + + status = imessaging_register(state.server_ctx, &state, + MSG_TMP_BASE-1, + multi_ctx_server_handler); + torture_assert(tctx, NT_STATUS_IS_OK(status), "imessaging_register failed"); + + state.client_ctx[0] = imessaging_init(tctx, + tctx->lp_ctx, + cluster_id(0, 0), + tctx->ev); + torture_assert(tctx, state.client_ctx[0] != NULL, + "msg_client_ctx imessaging_init() failed"); + status = imessaging_register(state.client_ctx[0], &state, + MSG_TMP_BASE-1, + multi_ctx_client_0_1_handler); + torture_assert(tctx, NT_STATUS_IS_OK(status), "imessaging_register failed"); + state.client_ctx[1] = imessaging_init(tctx, + tctx->lp_ctx, + cluster_id(0, 0), + tctx->ev); + torture_assert(tctx, state.client_ctx[1] != NULL, + "msg_client_ctx imessaging_init() failed"); + status = imessaging_register(state.client_ctx[1], &state, + MSG_TMP_BASE-1, + multi_ctx_client_0_1_handler); + torture_assert(tctx, NT_STATUS_IS_OK(status), "imessaging_register failed"); + state.client_ctx[2] = imessaging_init(tctx, + tctx->lp_ctx, + cluster_id(0, 0), + tctx->ev); + torture_assert(tctx, state.client_ctx[2] != NULL, + "msg_client_ctx imessaging_init() failed"); + status = imessaging_register(state.client_ctx[2], &state, + MSG_TMP_BASE-1, + multi_ctx_client_2_3_handler); + torture_assert(tctx, NT_STATUS_IS_OK(status), "imessaging_register failed"); + state.client_ctx[3] = imessaging_init(tctx, + tctx->lp_ctx, + cluster_id(0, 0), + tctx->ev); + torture_assert(tctx, state.client_ctx[3] != NULL, + "msg_client_ctx imessaging_init() failed"); + status = imessaging_register(state.client_ctx[3], &state, + MSG_TMP_BASE-1, + multi_ctx_client_2_3_handler); + torture_assert(tctx, NT_STATUS_IS_OK(status), "imessaging_register failed"); + + /* + * Send one message that need to arrive on 3 ( 5 - 2 ) handlers. + */ + state.num_missing = 5; + + status = imessaging_send(state.server_ctx, + cluster_id(0, 0), + MSG_TMP_BASE-1, NULL); + torture_assert_ntstatus_ok(tctx, status, "msg failed"); + + tv = timeval_current(); + while (timeval_elapsed(&tv) < 30 && state.num_missing > 0 && state.ok) { + int ret; + + ret = tevent_loop_once(tctx->ev); + torture_assert_int_equal(tctx, ret, 0, "tevent_loop_once()"); + } + + if (!state.ok) { + return false; + } + + torture_assert_int_equal(tctx, state.num_missing, 0, + "wrong message count"); + + torture_assert(tctx, state.got_client_0_1, "got_client_0_1"); + torture_assert(tctx, state.got_client_2_3, "got_client_2_3"); + torture_assert(tctx, state.got_server, "got_server"); + + return true; +} + +struct torture_suite *torture_local_messaging(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *s = torture_suite_create(mem_ctx, "messaging"); + torture_suite_add_simple_test(s, "overflow", test_messaging_overflow); + torture_suite_add_simple_test(s, "overflow_check", + test_messaging_overflow_check); + torture_suite_add_simple_test(s, "ping_speed", test_ping_speed); + torture_suite_add_simple_test(s, "multi_ctx", test_multi_ctx); + return s; +} diff --git a/source4/lib/messaging/wscript_build b/source4/lib/messaging/wscript_build new file mode 100644 index 0000000..3408396 --- /dev/null +++ b/source4/lib/messaging/wscript_build @@ -0,0 +1,33 @@ +#!/usr/bin/env python + + +bld.SAMBA_LIBRARY('MESSAGING_SEND', + source='messaging_send.c', + public_deps='messages_util messages_dgm UNIX_PRIVS cluster server_id_db', + private_library=True + ) + +bld.SAMBA_LIBRARY('MESSAGING', + source='messaging.c messaging_handlers.c', + public_deps=''' + samba-util + NDR_IRPC + UNIX_PRIVS + cluster + ndr + dcerpc + messages_util + server_id_db + talloc_report_printf + ''', + private_library=True + ) + +pyparam_util = bld.pyembed_libname('pyparam_util') +pytalloc_util = bld.pyembed_libname('pytalloc-util') + +bld.SAMBA_PYTHON('python_messaging', + source='pymessaging.c', + deps='MESSAGING events %s %s' % (pyparam_util, pytalloc_util), + realname='samba/messaging.so' + ) diff --git a/source4/lib/policy/gp_filesys.c b/source4/lib/policy/gp_filesys.c new file mode 100644 index 0000000..bac4ae7 --- /dev/null +++ b/source4/lib/policy/gp_filesys.c @@ -0,0 +1,723 @@ +/* + * Unix SMB/CIFS implementation. + * Group Policy Object Support + * Copyright (C) Wilco Baan Hofman 2008-2010 + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ +#include "includes.h" +#include "system/dir.h" +#include "system/filesys.h" +#include "lib/policy/policy.h" +#include "libcli/raw/smb.h" +#include "libcli/libcli.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" +#include "libcli/raw/libcliraw.h" +#include <dirent.h> +#include <errno.h> + +#define GP_MAX_DEPTH 25 + +struct gp_file_entry { + bool is_directory; + const char *rel_path; +}; +struct gp_file_list { + uint32_t num_files; + struct gp_file_entry *files; +}; +struct gp_list_state { + struct smbcli_tree *tree; + uint8_t depth; + const char *cur_rel_path; + const char *share_path; + + struct gp_file_list list; +}; + +static NTSTATUS gp_do_list(const char *, struct gp_list_state *); + +/* Create a temporary policy directory */ +static const char *gp_tmpdir(TALLOC_CTX *mem_ctx) +{ + char *gp_dir = talloc_asprintf(mem_ctx, "%s/policy", tmpdir()); + struct stat st; + int rv; + + if (gp_dir == NULL) return NULL; + + if (stat(gp_dir, &st) != 0) { + rv = mkdir(gp_dir, 0755); + if (rv < 0) { + DEBUG(0, ("Failed to create directory %s: %s\n", + gp_dir, strerror(errno))); + talloc_free(gp_dir); + return NULL; + } + } + + return gp_dir; +} + +/* This function is called by the smbcli_list function */ +static void gp_list_helper (struct clilist_file_info *info, const char *mask, + void *list_state_ptr) +{ + struct gp_list_state *state = list_state_ptr; + const char *rel_path; + + /* Ignore . and .. directory entries */ + if (strcmp(info->name, ".") == 0 || strcmp(info->name, "..") == 0) { + return; + } + + /* Safety check against ../.. in filenames which may occur on non-POSIX + * platforms */ + if (strstr(info->name, "../")) { + return; + } + + rel_path = talloc_asprintf(state, "%s\\%s", state->cur_rel_path, info->name); + if (rel_path == NULL) return; + + /* Append entry to file list */ + state->list.files = talloc_realloc(state, state->list.files, + struct gp_file_entry, + state->list.num_files + 1); + if (state->list.files == NULL) return; + + state->list.files[state->list.num_files].rel_path = rel_path; + + /* Directory */ + if (info->attrib & FILE_ATTRIBUTE_DIRECTORY) { + state->list.files[state->list.num_files].is_directory = true; + state->list.num_files++; + + /* Recurse into this directory if the depth is below the maximum */ + if (state->depth < GP_MAX_DEPTH) { + gp_do_list(rel_path, state); + } + + return; + } + + state->list.files[state->list.num_files].is_directory = false; + state->list.num_files++; + + return; +} + +static NTSTATUS gp_do_list (const char *rel_path, struct gp_list_state *state) +{ + uint16_t attributes; + int rv; + char *mask; + const char *old_rel_path; + + attributes = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_DIRECTORY; + + /* Update the relative paths, while buffering the parent */ + old_rel_path = state->cur_rel_path; + state->cur_rel_path = rel_path; + state->depth++; + + /* Get the current mask */ + mask = talloc_asprintf(state, "%s%s\\*", state->share_path, rel_path); + NT_STATUS_HAVE_NO_MEMORY(mask); + rv = smbcli_list(state->tree, mask, attributes, gp_list_helper, state); + talloc_free(mask); + + /* Go back to the state of the parent */ + state->cur_rel_path = old_rel_path; + state->depth--; + + if (rv == -1) + return NT_STATUS_UNSUCCESSFUL; + + return NT_STATUS_OK; +} + +static NTSTATUS gp_cli_connect(struct gp_context *gp_ctx) +{ + struct smbcli_options options; + struct smbcli_session_options session_options; + + if (gp_ctx->cli != NULL) + return NT_STATUS_OK; + + gp_ctx->cli = smbcli_state_init(gp_ctx); + + lpcfg_smbcli_options(gp_ctx->lp_ctx, &options); + lpcfg_smbcli_session_options(gp_ctx->lp_ctx, &session_options); + + return smbcli_full_connection(gp_ctx, + &gp_ctx->cli, + gp_ctx->active_dc->name, + lpcfg_smb_ports(gp_ctx->lp_ctx), + "sysvol", + NULL, + lpcfg_socket_options(gp_ctx->lp_ctx), + gp_ctx->credentials, + lpcfg_resolve_context(gp_ctx->lp_ctx), + gp_ctx->ev_ctx, + &options, + &session_options, + lpcfg_gensec_settings(gp_ctx, gp_ctx->lp_ctx)); +} + +static char * gp_get_share_path(TALLOC_CTX *mem_ctx, const char *file_sys_path) +{ + unsigned int i, bkslash_cnt; + + /* Get the path from the share down (\\..\..\(this\stuff) */ + for (i = 0, bkslash_cnt = 0; file_sys_path[i] != '\0'; i++) { + if (file_sys_path[i] == '\\') + bkslash_cnt++; + + if (bkslash_cnt == 4) { + return talloc_strdup(mem_ctx, &file_sys_path[i]); + } + } + + return NULL; +} + +static NTSTATUS gp_get_file (struct smbcli_tree *tree, const char *remote_src, + const char *local_dst) +{ + int fh_remote, fh_local; + uint8_t *buf; + size_t nread = 0; + size_t buf_size = 1024; + size_t file_size; + uint16_t attr; + + /* Open the remote file */ + fh_remote = smbcli_open(tree, remote_src, O_RDONLY, DENY_NONE); + if (fh_remote == -1) { + DEBUG(0, ("Failed to open remote file: %s\n", remote_src)); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Open the local file */ + fh_local = open(local_dst, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fh_local == -1) { + DEBUG(0, ("Failed to open local file: %s\n", local_dst)); + smbcli_close(tree, fh_remote); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Get the remote file size for error checking */ + if (NT_STATUS_IS_ERR(smbcli_qfileinfo(tree, fh_remote, + &attr, &file_size, NULL, NULL, NULL, NULL, NULL)) && + NT_STATUS_IS_ERR(smbcli_getattrE(tree, fh_remote, + &attr, &file_size, NULL, NULL, NULL))) { + DEBUG(0, ("Failed to get remote file size: %s\n", smbcli_errstr(tree))); + smbcli_close(tree, fh_remote); + close(fh_local); + return NT_STATUS_UNSUCCESSFUL; + } + + buf = talloc_zero_array(tree, uint8_t, buf_size); + if (buf == NULL) { + smbcli_close(tree, fh_remote); + close(fh_local); + return NT_STATUS_NO_MEMORY; + } + + /* Copy the contents of the file */ + while (1) { + int n = smbcli_read(tree, fh_remote, buf, nread, buf_size); + + if (n <= 0) { + break; + } + + if (write(fh_local, buf, n) != n) { + DEBUG(0, ("Short write while copying file.\n")); + smbcli_close(tree, fh_remote); + close(fh_local); + talloc_free(buf); + return NT_STATUS_UNSUCCESSFUL; + } + nread += n; + } + + /* Close the files */ + smbcli_close(tree, fh_remote); + close(fh_local); + + talloc_free(buf); + + /* Bytes read should match the file size, or the copy was incomplete */ + if (nread != file_size) { + DEBUG(0, ("Remote/local file size mismatch after copying file: " + "%s (remote %zu, local %zu).\n", + remote_src, file_size, nread)); + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gp_get_files(struct smbcli_tree *tree, const char *share_path, + const char *local_path, struct gp_file_list *list) +{ + uint32_t i; + int rv; + char *local_rel_path, *full_local_path, *full_remote_path; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + + mem_ctx = talloc_new(tree); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + for (i = 0; i < list->num_files; i++) { + + /* Get local path by replacing backslashes with slashes */ + local_rel_path = talloc_strdup(mem_ctx, list->files[i].rel_path); + if (local_rel_path == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + string_replace(local_rel_path, '\\', '/'); + + full_local_path = talloc_asprintf(mem_ctx, "%s%s", local_path, + local_rel_path); + if (full_local_path == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* If the entry is a directory, create it. */ + if (list->files[i].is_directory == true) { + rv = mkdir(full_local_path, 0755); + if (rv < 0) { + DEBUG(0, ("Failed to create directory %s: %s\n", + full_local_path, strerror(errno))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + continue; + } + + full_remote_path = talloc_asprintf(mem_ctx, "%s%s", share_path, + list->files[i].rel_path); + if (full_remote_path == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Get the file */ + status = gp_get_file(tree, full_remote_path, full_local_path); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Error getting file.\n")); + talloc_free(mem_ctx); + return status; + } + } + + return NT_STATUS_OK; +} + +NTSTATUS gp_fetch_gpt (struct gp_context *gp_ctx, struct gp_object *gpo, + const char **ret_local_path) +{ + TALLOC_CTX *mem_ctx; + struct gp_list_state *state; + NTSTATUS status; + struct stat st; + int rv; + const char *local_path, *share_path; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + if (gp_ctx->cli == NULL) { + status = gp_cli_connect(gp_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to create cli connection to DC\n")); + talloc_free(mem_ctx); + return status; + } + } + + /* Get the remote path to copy from */ + share_path = gp_get_share_path(mem_ctx, gpo->file_sys_path); + if (share_path == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Get the local path to copy to */ + local_path = talloc_asprintf(gp_ctx, "%s/%s", gp_tmpdir(mem_ctx), gpo->name); + if (local_path == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Prepare the state structure */ + state = talloc_zero(mem_ctx, struct gp_list_state); + if (state == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + state->tree = gp_ctx->cli->tree; + state->share_path = share_path; + + /* Create the GPO dir if it does not exist */ + if (stat(local_path, &st) != 0) { + rv = mkdir(local_path, 0755); + if (rv < 0) { + DEBUG(0, ("Could not create local path\n")); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + } + + /* Get the file list */ + status = gp_do_list("", state); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Could not list GPO files on remote server\n")); + talloc_free(mem_ctx); + return status; + } + + /* If the list has no entries there is a problem. */ + if (state->list.num_files == 0) { + DEBUG(0, ("File list is has no entries. Is the GPT directory empty?\n")); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Fetch the files */ + status = gp_get_files(gp_ctx->cli->tree, share_path, local_path, &state->list); + + /* Return the local path to the gpo */ + *ret_local_path = local_path; + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} + +static NTSTATUS push_recursive (struct gp_context *gp_ctx, const char *local_path, + const char *remote_path, int depth) +{ + DIR *dir; + struct dirent *dirent; + char *entry_local_path = NULL; + char *entry_remote_path = NULL; + int local_fd = -1, remote_fd = -1; + char buf[4096]; + ssize_t nread, total_read; + ssize_t nwrite, total_write; + struct stat s; + NTSTATUS status; + + dir = opendir(local_path); + if (!dir) { + DEBUG(0, ("Failed to open directory: %s\n", local_path)); + return NT_STATUS_UNSUCCESSFUL; + } + + while ((dirent = readdir(dir)) != NULL) { + if (ISDOT(dirent->d_name) || ISDOTDOT(dirent->d_name)) { + continue; + } + + entry_local_path = talloc_asprintf(gp_ctx, "%s/%s", local_path, + dirent->d_name); + if (entry_local_path == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + entry_remote_path = talloc_asprintf(gp_ctx, "%s\\%s", + remote_path, dirent->d_name); + if (entry_remote_path == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + if (stat(entry_local_path, &s) != 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + if (s.st_mode & S_IFDIR) { + DEBUG(6, ("Pushing directory %s to %s on sysvol\n", + entry_local_path, entry_remote_path)); + status = smbcli_mkdir(gp_ctx->cli->tree, + entry_remote_path); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + if (depth < GP_MAX_DEPTH) { + status = push_recursive(gp_ctx, + entry_local_path, + entry_remote_path, + depth + 1); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + } + } else { + DEBUG(6, ("Pushing file %s to %s on sysvol\n", + entry_local_path, entry_remote_path)); + remote_fd = smbcli_open(gp_ctx->cli->tree, + entry_remote_path, + O_WRONLY | O_CREAT, + 0); + if (remote_fd < 0) { + DEBUG(0, ("Failed to create remote file: %s\n", + entry_remote_path)); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + local_fd = open(entry_local_path, O_RDONLY); + if (local_fd < 0) { + DEBUG(0, ("Failed to open local file: %s\n", + entry_local_path)); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + total_read = 0; + total_write = 0; + while ((nread = read(local_fd, buf, sizeof(buf)))) { + if (nread == -1) { + DBG_ERR("read failed with errno %s\n", + strerror(errno)); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + nwrite = smbcli_write(gp_ctx->cli->tree, + remote_fd, 0, buf, + total_read, nread); + if (nwrite < 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + total_read += nread; + total_write += nwrite; + } + if (total_read != total_write) { + /* Weird and should not happen */ + status = NT_STATUS_UNEXPECTED_IO_ERROR; + goto done; + } + + close(local_fd); + local_fd = -1; + smbcli_close(gp_ctx->cli->tree, remote_fd); + remote_fd = -1; + } + TALLOC_FREE(entry_local_path); + TALLOC_FREE(entry_remote_path); + } + + status = NT_STATUS_OK; +done: + if (local_fd != -1) { + close(local_fd); + } + if (remote_fd != -1) { + smbcli_close(gp_ctx->cli->tree, remote_fd); + } + talloc_free(entry_local_path); + talloc_free(entry_remote_path); + + closedir(dir); + + return status; +} + + + +NTSTATUS gp_push_gpt(struct gp_context *gp_ctx, const char *local_path, + const char *file_sys_path) +{ + NTSTATUS status; + char *share_path; + + if (gp_ctx->cli == NULL) { + status = gp_cli_connect(gp_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to create cli connection to DC\n")); + return status; + } + } + share_path = gp_get_share_path(gp_ctx, file_sys_path); + + DEBUG(5, ("Copying %s to %s on sysvol\n", local_path, share_path)); + + smbcli_mkdir(gp_ctx->cli->tree, share_path); + + status = push_recursive(gp_ctx, local_path, share_path, 0); + + talloc_free(share_path); + return status; +} + +NTSTATUS gp_create_gpt(struct gp_context *gp_ctx, const char *name, + const char *file_sys_path) +{ + TALLOC_CTX *mem_ctx; + const char *tmp_dir, *policy_dir, *tmp_str; + int rv; + int fd; + NTSTATUS status; + const char *file_content = "[General]\r\nVersion=0\r\n"; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + tmp_dir = gp_tmpdir(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(tmp_dir); + policy_dir = talloc_asprintf(mem_ctx, "%s/%s", tmp_dir, name); + NT_STATUS_HAVE_NO_MEMORY(policy_dir); + + /* Create the directories */ + + rv = mkdir(policy_dir, 0755); + if (rv < 0) { + DEBUG(0, ("Could not create the policy dir: %s\n", policy_dir)); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + tmp_str = talloc_asprintf(mem_ctx, "%s/User", policy_dir); + NT_STATUS_HAVE_NO_MEMORY(tmp_str); + rv = mkdir(tmp_str, 0755); + if (rv < 0) { + DEBUG(0, ("Could not create the User dir: %s\n", tmp_str)); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + tmp_str = talloc_asprintf(mem_ctx, "%s/Machine", policy_dir); + NT_STATUS_HAVE_NO_MEMORY(tmp_str); + rv = mkdir(tmp_str, 0755); + if (rv < 0) { + DEBUG(0, ("Could not create the Machine dir: %s\n", tmp_str)); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Create a GPT.INI with version 0 */ + + tmp_str = talloc_asprintf(mem_ctx, "%s/GPT.INI", policy_dir); + NT_STATUS_HAVE_NO_MEMORY(tmp_str); + fd = open(tmp_str, O_CREAT | O_WRONLY, 0644); + if (fd < 0) { + DEBUG(0, ("Could not create the GPT.INI: %s\n", tmp_str)); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + rv = write(fd, file_content, strlen(file_content)); + close(fd); + if (rv != strlen(file_content)) { + DEBUG(0, ("Short write in GPT.INI\n")); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Upload the GPT to the sysvol share on a DC */ + status = gp_push_gpt(gp_ctx, policy_dir, file_sys_path); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + return status; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} + +NTSTATUS gp_set_gpt_security_descriptor(struct gp_context *gp_ctx, + struct gp_object *gpo, + struct security_descriptor *sd) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + union smb_setfileinfo fileinfo; + union smb_open io; + union smb_close io_close; + + /* Create a connection to sysvol if it is not already there */ + if (gp_ctx->cli == NULL) { + status = gp_cli_connect(gp_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to create cli connection to DC\n")); + return status; + } + } + + /* Create a forked memory context which can be freed easily */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + /* Open the directory with NTCreate AndX call */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid.fnum = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = gp_get_share_path(mem_ctx, gpo->file_sys_path); + status = smb_raw_open(gp_ctx->cli->tree, mem_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Can't open GPT directory\n")); + talloc_free(mem_ctx); + return status; + } + + /* Set the security descriptor on the directory */ + fileinfo.generic.level = RAW_SFILEINFO_SEC_DESC; + fileinfo.set_secdesc.in.file.fnum = io.ntcreatex.out.file.fnum; + fileinfo.set_secdesc.in.secinfo_flags = SECINFO_PROTECTED_DACL | + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + fileinfo.set_secdesc.in.sd = sd; + status = smb_raw_setfileinfo(gp_ctx->cli->tree, &fileinfo); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to set security descriptor on the GPT\n")); + talloc_free(mem_ctx); + return status; + } + + /* Close the directory */ + io_close.close.level = RAW_CLOSE_CLOSE; + io_close.close.in.file.fnum = io.ntcreatex.out.file.fnum; + io_close.close.in.write_time = 0; + status = smb_raw_close(gp_ctx->cli->tree, &io_close); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to close directory\n")); + talloc_free(mem_ctx); + return status; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} diff --git a/source4/lib/policy/gp_ini.c b/source4/lib/policy/gp_ini.c new file mode 100644 index 0000000..da2f5f4 --- /dev/null +++ b/source4/lib/policy/gp_ini.c @@ -0,0 +1,133 @@ + +/* + * Unix SMB/CIFS implementation. + * Group Policy Object Support + * Copyright (C) Wilco Baan Hofman 2010 + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ +#include "includes.h" +#include "lib/util/samba_util.h" +#include "lib/policy/policy.h" + +struct gp_parse_context { + struct gp_ini_context *ini; + int32_t cur_section; +}; + + +static bool gp_add_ini_section(const char *name, void *callback_data) +{ + struct gp_parse_context *parse = callback_data; + struct gp_ini_context *ini = parse->ini; + + ini->sections = talloc_realloc(ini, ini->sections, struct gp_ini_section, ini->num_sections+1); + if (ini->sections == NULL) return false; + ini->sections[ini->num_sections].name = talloc_strdup(ini, name); + if (ini->sections[ini->num_sections].name == NULL) return false; + parse->cur_section = ini->num_sections; + ini->num_sections++; + + return true; +} + +static bool gp_add_ini_param(const char *name, const char *value, void *callback_data) +{ + struct gp_parse_context *parse = callback_data; + struct gp_ini_context *ini = parse->ini; + struct gp_ini_section *section; + + if (parse->cur_section == -1) { + return false; + } + + section = &ini->sections[parse->cur_section]; + + section->params = talloc_realloc(ini, ini->sections[parse->cur_section].params, struct gp_ini_param, section->num_params+1); + if (section->params == NULL) return false; + section->params[section->num_params].name = talloc_strdup(ini, name); + if (section->params[section->num_params].name == NULL) return false; + section->params[section->num_params].value = talloc_strdup(ini, value); + if (section->params[section->num_params].value == NULL) return false; + section->num_params++; + + return true; +} + +NTSTATUS gp_parse_ini(TALLOC_CTX *mem_ctx, struct gp_context *gp_ctx, const char *filename, struct gp_ini_context **ret) +{ + struct gp_parse_context parse; + bool rv; + + parse.ini = talloc_zero(mem_ctx, struct gp_ini_context); + NT_STATUS_HAVE_NO_MEMORY(parse.ini); + parse.cur_section = -1; + + rv = pm_process(filename, gp_add_ini_section, gp_add_ini_param, &parse); + if (!rv) { + DEBUG(0, ("Error while processing ini file %s\n", filename)); + return NT_STATUS_UNSUCCESSFUL; + } + + *ret = parse.ini; + return NT_STATUS_OK; +} + +NTSTATUS gp_get_ini_string(struct gp_ini_context *ini, const char *section, const char *name, char **ret) +{ + uint16_t i; + int32_t cur_sec = -1; + for (i = 0; i < ini->num_sections; i++) { + if (strcmp(ini->sections[i].name, section) == 0) { + cur_sec = i; + break; + } + } + + if (cur_sec == -1) { + return NT_STATUS_NOT_FOUND; + } + + for (i = 0; i < ini->sections[cur_sec].num_params; i++) { + if (strcmp(ini->sections[cur_sec].params[i].name, name) == 0) { + *ret = ini->sections[cur_sec].params[i].value; + return NT_STATUS_OK; + } + } + return NT_STATUS_NOT_FOUND; +} + +NTSTATUS gp_get_ini_uint(struct gp_ini_context *ini, const char *section, const char *name, uint32_t *ret) +{ + uint16_t i; + int32_t cur_sec = -1; + for (i = 0; i < ini->num_sections; i++) { + if (strcmp(ini->sections[i].name, section) == 0) { + cur_sec = i; + break; + } + } + + if (cur_sec == -1) { + return NT_STATUS_NOT_FOUND; + } + + for (i = 0; i < ini->sections[cur_sec].num_params; i++) { + if (strcmp(ini->sections[cur_sec].params[i].name, name) == 0) { + *ret = atol(ini->sections[cur_sec].params[i].value); + return NT_STATUS_OK; + } + } + return NT_STATUS_NOT_FOUND; +} diff --git a/source4/lib/policy/gp_ldap.c b/source4/lib/policy/gp_ldap.c new file mode 100644 index 0000000..67b329b --- /dev/null +++ b/source4/lib/policy/gp_ldap.c @@ -0,0 +1,1130 @@ +/* + * Unix SMB/CIFS implementation. + * Group Policy Object Support + * Copyright (C) Jelmer Vernooij 2008 + * Copyright (C) Wilco Baan Hofman 2008-2010 + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ +#include "includes.h" +#include "param/param.h" +#include <ldb.h> +#include "lib/ldb-samba/ldb_wrap.h" +#include "auth/credentials/credentials.h" +#include "../librpc/gen_ndr/nbt.h" +#include "libcli/libcli.h" +#include "libnet/libnet.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "../libcli/security/security.h" +#include "libcli/ldap/ldap_ndr.h" +#include "../lib/talloc/talloc.h" +#include "lib/policy/policy.h" + +struct gpo_stringmap { + const char *str; + uint32_t flags; +}; +static const struct gpo_stringmap gplink_options [] = { + { "GPLINK_OPT_DISABLE", GPLINK_OPT_DISABLE }, + { "GPLINK_OPT_ENFORCE", GPLINK_OPT_ENFORCE }, + { NULL, 0 } +}; +static const struct gpo_stringmap gpo_flags [] = { + { "GPO_FLAG_USER_DISABLE", GPO_FLAG_USER_DISABLE }, + { "GPO_FLAG_MACHINE_DISABLE", GPO_FLAG_MACHINE_DISABLE }, + { NULL, 0 } +}; + +static NTSTATUS parse_gpo(TALLOC_CTX *mem_ctx, struct ldb_message *msg, struct gp_object **ret) +{ + struct gp_object *gpo = talloc(mem_ctx, struct gp_object); + enum ndr_err_code ndr_err; + const DATA_BLOB *data; + + NT_STATUS_HAVE_NO_MEMORY(gpo); + + gpo->dn = talloc_strdup(mem_ctx, ldb_dn_get_linearized(msg->dn)); + if (gpo->dn == NULL) { + TALLOC_FREE(gpo); + return NT_STATUS_NO_MEMORY; + } + + DEBUG(9, ("Parsing GPO LDAP data for %s\n", gpo->dn)); + + gpo->display_name = talloc_strdup(gpo, ldb_msg_find_attr_as_string(msg, "displayName", "")); + if (gpo->display_name == NULL) { + TALLOC_FREE(gpo); + return NT_STATUS_NO_MEMORY; + } + + gpo->name = talloc_strdup(gpo, ldb_msg_find_attr_as_string(msg, "name", "")); + if (gpo->name == NULL) { + TALLOC_FREE(gpo); + return NT_STATUS_NO_MEMORY; + } + + gpo->flags = ldb_msg_find_attr_as_uint(msg, "flags", 0); + gpo->version = ldb_msg_find_attr_as_uint(msg, "versionNumber", 0); + + gpo->file_sys_path = talloc_strdup(gpo, ldb_msg_find_attr_as_string(msg, "gPCFileSysPath", "")); + if (gpo->file_sys_path == NULL) { + TALLOC_FREE(gpo); + return NT_STATUS_NO_MEMORY; + } + + /* Pull the security descriptor through the NDR library */ + data = ldb_msg_find_ldb_val(msg, "nTSecurityDescriptor"); + gpo->security_descriptor = talloc(gpo, struct security_descriptor); + if (gpo->security_descriptor == NULL) { + TALLOC_FREE(gpo); + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_pull_struct_blob(data, + mem_ctx, + gpo->security_descriptor, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + *ret = gpo; + return NT_STATUS_OK; +} + +NTSTATUS gp_get_gpo_flags(TALLOC_CTX *mem_ctx, uint32_t flags, const char ***ret) +{ + unsigned int i, count=0; + const char **flag_strs = talloc_array(mem_ctx, const char *, 1); + + NT_STATUS_HAVE_NO_MEMORY(flag_strs); + + flag_strs[0] = NULL; + + for (i = 0; gpo_flags[i].str != NULL; i++) { + if (flags & gpo_flags[i].flags) { + flag_strs = talloc_realloc(mem_ctx, flag_strs, const char *, count+2); + NT_STATUS_HAVE_NO_MEMORY(flag_strs); + flag_strs[count] = gpo_flags[i].str; + flag_strs[count+1] = NULL; + count++; + } + } + *ret = flag_strs; + return NT_STATUS_OK; +} + +NTSTATUS gp_get_gplink_options(TALLOC_CTX *mem_ctx, uint32_t options, const char ***ret) +{ + unsigned int i, count=0; + const char **flag_strs = talloc_array(mem_ctx, const char *, 1); + + NT_STATUS_HAVE_NO_MEMORY(flag_strs); + flag_strs[0] = NULL; + + for (i = 0; gplink_options[i].str != NULL; i++) { + if (options & gplink_options[i].flags) { + flag_strs = talloc_realloc(mem_ctx, flag_strs, const char *, count+2); + NT_STATUS_HAVE_NO_MEMORY(flag_strs); + flag_strs[count] = gplink_options[i].str; + flag_strs[count+1] = NULL; + count++; + } + } + *ret = flag_strs; + return NT_STATUS_OK; +} + +NTSTATUS gp_init(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct cli_credentials *credentials, + struct tevent_context *ev_ctx, + struct gp_context **gp_ctx) +{ + + struct libnet_LookupDCs *io; + char *url; + struct libnet_context *net_ctx; + struct ldb_context *ldb_ctx; + NTSTATUS rv; + + /* Initialise the libnet context */ + net_ctx = libnet_context_init(ev_ctx, lp_ctx); + net_ctx->cred = credentials; + + /* Prepare libnet lookup structure for looking a DC (PDC is correct). */ + io = talloc_zero(mem_ctx, struct libnet_LookupDCs); + NT_STATUS_HAVE_NO_MEMORY(io); + io->in.name_type = NBT_NAME_PDC; + io->in.domain_name = lpcfg_workgroup(lp_ctx); + + /* Find Active DC's */ + rv = libnet_LookupDCs(net_ctx, mem_ctx, io); + if (!NT_STATUS_IS_OK(rv)) { + DEBUG(0, ("Failed to lookup DCs in domain\n")); + return rv; + } + + /* Connect to ldap://DC_NAME with all relevant contexts*/ + url = talloc_asprintf(mem_ctx, "ldap://%s", io->out.dcs[0].name); + NT_STATUS_HAVE_NO_MEMORY(url); + ldb_ctx = ldb_wrap_connect(mem_ctx, net_ctx->event_ctx, lp_ctx, + url, NULL, net_ctx->cred, 0); + if (ldb_ctx == NULL) { + DEBUG(0, ("Can't connect to DC's LDAP with url %s\n", url)); + return NT_STATUS_UNSUCCESSFUL; + } + + *gp_ctx = talloc_zero(mem_ctx, struct gp_context); + NT_STATUS_HAVE_NO_MEMORY(gp_ctx); + + (*gp_ctx)->lp_ctx = lp_ctx; + (*gp_ctx)->credentials = credentials; + (*gp_ctx)->ev_ctx = ev_ctx; + (*gp_ctx)->ldb_ctx = ldb_ctx; + (*gp_ctx)->active_dc = talloc_reference(*gp_ctx, &io->out.dcs[0]); + + /* We don't need to keep the libnet context */ + talloc_free(net_ctx); + return NT_STATUS_OK; +} + +NTSTATUS gp_list_all_gpos(struct gp_context *gp_ctx, struct gp_object ***ret) +{ + struct ldb_result *result; + int rv; + NTSTATUS status; + TALLOC_CTX *mem_ctx; + struct ldb_dn *dn; + struct gp_object **gpo; + unsigned int i; /* same as in struct ldb_result */ + const char **attrs; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + /* Create full ldb dn of the policies base object */ + dn = ldb_get_default_basedn(gp_ctx->ldb_ctx); + rv = ldb_dn_add_child(dn, ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, "CN=Policies,CN=System")); + if (!rv) { + DEBUG(0, ("Can't append subtree to DN\n")); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + DEBUG(10, ("Searching for policies in DN: %s\n", ldb_dn_get_linearized(dn))); + + attrs = talloc_array(mem_ctx, const char *, 7); + if (attrs == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + attrs[0] = "nTSecurityDescriptor"; + attrs[1] = "versionNumber"; + attrs[2] = "flags"; + attrs[3] = "name"; + attrs[4] = "displayName"; + attrs[5] = "gPCFileSysPath"; + attrs[6] = NULL; + + rv = ldb_search(gp_ctx->ldb_ctx, mem_ctx, &result, dn, LDB_SCOPE_ONELEVEL, attrs, "(objectClass=groupPolicyContainer)"); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB search failed: %s\n%s\n", ldb_strerror(rv), ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + gpo = talloc_array(gp_ctx, struct gp_object *, result->count+1); + if (gpo == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + gpo[result->count] = NULL; + + for (i = 0; i < result->count; i++) { + status = parse_gpo(gp_ctx, result->msgs[i], &gpo[i]); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to parse GPO.\n")); + talloc_free(mem_ctx); + return status; + } + } + + talloc_free(mem_ctx); + + *ret = gpo; + return NT_STATUS_OK; +} + +NTSTATUS gp_get_gpo_info(struct gp_context *gp_ctx, const char *dn_str, struct gp_object **ret) +{ + struct ldb_result *result; + struct ldb_dn *dn; + struct gp_object *gpo; + int rv; + NTSTATUS status; + TALLOC_CTX *mem_ctx; + const char **attrs; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + /* Create an ldb dn struct for the dn string */ + dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, dn_str); + + attrs = talloc_array(mem_ctx, const char *, 7); + if (attrs == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + attrs[0] = "nTSecurityDescriptor"; + attrs[1] = "versionNumber"; + attrs[2] = "flags"; + attrs[3] = "name"; + attrs[4] = "displayName"; + attrs[5] = "gPCFileSysPath"; + attrs[6] = NULL; + + rv = ldb_search(gp_ctx->ldb_ctx, + mem_ctx, + &result, + dn, + LDB_SCOPE_BASE, + attrs, + "objectClass=groupPolicyContainer"); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB search failed: %s\n%s\n", ldb_strerror(rv), ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* We expect exactly one record */ + if (result->count != 1) { + DEBUG(0, ("Could not find GPC with dn %s\n", dn_str)); + talloc_free(mem_ctx); + return NT_STATUS_NOT_FOUND; + } + + status = parse_gpo(gp_ctx, result->msgs[0], &gpo); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to parse GPO.\n")); + talloc_free(mem_ctx); + return status; + } + + talloc_free(mem_ctx); + + *ret = gpo; + return NT_STATUS_OK; +} + +static NTSTATUS parse_gplink (TALLOC_CTX *mem_ctx, const char *gplink_str, struct gp_link ***ret) +{ + int start, idx=0; + int pos; + struct gp_link **gplinks; + char *buf, *end; + const char *gplink_start = "[LDAP://"; + + gplinks = talloc_array(mem_ctx, struct gp_link *, 1); + NT_STATUS_HAVE_NO_MEMORY(gplinks); + + gplinks[0] = NULL; + + /* Assuming every gPLink starts with "[LDAP://" */ + start = strlen(gplink_start); + + for (pos = start; pos < strlen(gplink_str); pos++) { + if (gplink_str[pos] == ';') { + gplinks = talloc_realloc(mem_ctx, gplinks, struct gp_link *, idx+2); + NT_STATUS_HAVE_NO_MEMORY(gplinks); + gplinks[idx] = talloc(mem_ctx, struct gp_link); + NT_STATUS_HAVE_NO_MEMORY(gplinks[idx]); + gplinks[idx]->dn = talloc_strndup(mem_ctx, + gplink_str + start, + pos - start); + if (gplinks[idx]->dn == NULL) { + TALLOC_FREE(gplinks); + return NT_STATUS_NO_MEMORY; + } + + for (start = pos + 1; gplink_str[pos] != ']'; pos++); + + buf = talloc_strndup(gplinks, gplink_str + start, pos - start); + if (buf == NULL) { + TALLOC_FREE(gplinks); + return NT_STATUS_NO_MEMORY; + } + gplinks[idx]->options = (uint32_t) strtoll(buf, &end, 0); + talloc_free(buf); + + /* Set the last entry in the array to be NULL */ + gplinks[idx + 1] = NULL; + + /* Increment the array index, the string position past + the next "[LDAP://", and set the start reference */ + idx++; + pos += strlen(gplink_start)+1; + start = pos; + } + } + + *ret = gplinks; + return NT_STATUS_OK; +} + + +NTSTATUS gp_get_gplinks(struct gp_context *gp_ctx, const char *dn_str, struct gp_link ***ret) +{ + TALLOC_CTX *mem_ctx; + struct ldb_dn *dn; + struct ldb_result *result; + struct gp_link **gplinks; + char *gplink_str; + int rv; + unsigned int i; + NTSTATUS status; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, dn_str); + + rv = ldb_search(gp_ctx->ldb_ctx, mem_ctx, &result, dn, LDB_SCOPE_BASE, NULL, "(objectclass=*)"); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB search failed: %s\n%s\n", ldb_strerror(rv), ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + for (i = 0; i < result->count; i++) { + struct ldb_message_element *element = \ + ldb_msg_find_element(result->msgs[i], "gPLink"); + if (element != NULL) { + SMB_ASSERT(element->num_values > 0); + gplink_str = talloc_strdup( + mem_ctx, + (char *) element->values[0].data); + if (gplink_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + goto found; + } + } + gplink_str = talloc_strdup(mem_ctx, ""); + if (gplink_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + found: + + status = parse_gplink(gp_ctx, gplink_str, &gplinks); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to parse gPLink\n")); + return status; + } + + talloc_free(mem_ctx); + + *ret = gplinks; + return NT_STATUS_OK; +} + +NTSTATUS gp_list_gpos(struct gp_context *gp_ctx, struct security_token *token, const char ***ret) +{ + TALLOC_CTX *mem_ctx; + const char **gpos; + struct ldb_result *result; + char *sid; + struct ldb_dn *dn; + struct ldb_message_element *element; + bool inherit; + const char *attrs[] = { "objectClass", NULL }; + int rv; + NTSTATUS status; + unsigned int count = 0; + unsigned int i; + enum { + ACCOUNT_TYPE_USER = 0, + ACCOUNT_TYPE_MACHINE = 1 + } account_type; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + sid = ldap_encode_ndr_dom_sid(mem_ctx, + &token->sids[PRIMARY_USER_SID_INDEX]); + NT_STATUS_HAVE_NO_MEMORY(sid); + + /* Find the user DN and objectclass via the sid from the security token */ + rv = ldb_search(gp_ctx->ldb_ctx, + mem_ctx, + &result, + ldb_get_default_basedn(gp_ctx->ldb_ctx), + LDB_SCOPE_SUBTREE, + attrs, + "(&(objectclass=user)(objectSid=%s))", sid); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB search failed: %s\n%s\n", ldb_strerror(rv), + ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + if (result->count != 1) { + DEBUG(0, ("Could not find user with sid %s.\n", sid)); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + DEBUG(10,("Found DN for this user: %s\n", ldb_dn_get_linearized(result->msgs[0]->dn))); + + element = ldb_msg_find_element(result->msgs[0], "objectClass"); + + /* We need to know if this account is a user or machine. */ + account_type = ACCOUNT_TYPE_USER; + for (i = 0; i < element->num_values; i++) { + if (strcmp((char *)element->values[i].data, "computer") == 0) { + account_type = ACCOUNT_TYPE_MACHINE; + DEBUG(10, ("This user is a machine\n")); + } + } + + gpos = talloc_array(gp_ctx, const char *, 1); + if (gpos == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + gpos[0] = NULL; + + /* Walk through the containers until we hit the root */ + inherit = 1; + dn = ldb_dn_get_parent(mem_ctx, result->msgs[0]->dn); + while (ldb_dn_compare_base(ldb_get_default_basedn(gp_ctx->ldb_ctx), dn) == 0) { + const char *gpo_attrs[] = { "gPLink", "gPOptions", NULL }; + struct gp_link **gplinks; + enum gpo_inheritance gpoptions; + + DEBUG(10, ("Getting gPLinks for DN: %s\n", ldb_dn_get_linearized(dn))); + + /* Get the gPLink and gPOptions attributes from the container */ + rv = ldb_search(gp_ctx->ldb_ctx, + mem_ctx, + &result, + dn, + LDB_SCOPE_BASE, + gpo_attrs, + "objectclass=*"); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB search failed: %s\n%s\n", ldb_strerror(rv), + ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Parse the gPLink attribute, put it into a nice struct array */ + status = parse_gplink(mem_ctx, ldb_msg_find_attr_as_string(result->msgs[0], "gPLink", ""), &gplinks); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to parse gPLink\n")); + talloc_free(mem_ctx); + return status; + } + + /* Check all group policy links on this container */ + for (i = 0; gplinks[i] != NULL; i++) { + struct gp_object *gpo; + uint32_t access_granted; + + /* If inheritance was blocked at a higher level and this + * gplink is not enforced, it should not be applied */ + if (!inherit && !(gplinks[i]->options & GPLINK_OPT_ENFORCE)) + continue; + + /* Don't apply disabled links */ + if (gplinks[i]->options & GPLINK_OPT_DISABLE) + continue; + + /* Get GPO information */ + status = gp_get_gpo_info(gp_ctx, gplinks[i]->dn, &gpo); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to get gpo information for %s\n", gplinks[i]->dn)); + talloc_free(mem_ctx); + return status; + } + + /* If the account does not have read access, this GPO does not apply + * to this account */ + status = se_access_check(gpo->security_descriptor, + token, + (SEC_STD_READ_CONTROL | SEC_ADS_LIST | SEC_ADS_READ_PROP), + &access_granted); + if (!NT_STATUS_IS_OK(status)) { + continue; + } + + /* If the account is a user and the GPO has user disabled flag, or + * a machine and the GPO has machine disabled flag, this GPO does + * not apply to this account */ + if ((account_type == ACCOUNT_TYPE_USER && + (gpo->flags & GPO_FLAG_USER_DISABLE)) || + (account_type == ACCOUNT_TYPE_MACHINE && + (gpo->flags & GPO_FLAG_MACHINE_DISABLE))) { + continue; + } + + /* Add the GPO to the list */ + gpos = talloc_realloc(gp_ctx, gpos, const char *, count+2); + if (gpos == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + gpos[count] = talloc_strdup(gp_ctx, gplinks[i]->dn); + if (gpos[count] == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + gpos[count+1] = NULL; + count++; + + /* Clean up */ + talloc_free(gpo); + } + + /* If inheritance is blocked, then we should only add enforced gPLinks + * higher up */ + gpoptions = ldb_msg_find_attr_as_uint(result->msgs[0], "gPOptions", 0); + if (gpoptions == GPO_BLOCK_INHERITANCE) { + inherit = 0; + } + dn = ldb_dn_get_parent(mem_ctx, dn); + } + + talloc_free(mem_ctx); + + *ret = gpos; + return NT_STATUS_OK; +} + +NTSTATUS gp_set_gplink(struct gp_context *gp_ctx, const char *dn_str, struct gp_link *gplink) +{ + TALLOC_CTX *mem_ctx; + struct ldb_result *result; + struct ldb_dn *dn; + struct ldb_message *msg; + const char *attrs[] = { "gPLink", NULL }; + const char *gplink_str; + int rv; + char *start; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, dn_str); + + rv = ldb_search(gp_ctx->ldb_ctx, mem_ctx, &result, dn, LDB_SCOPE_BASE, attrs, "(objectclass=*)"); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB search failed: %s\n%s\n", ldb_strerror(rv), ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + if (result->count != 1) { + talloc_free(mem_ctx); + return NT_STATUS_NOT_FOUND; + } + + gplink_str = ldb_msg_find_attr_as_string(result->msgs[0], "gPLink", ""); + + /* If this GPO link already exists, alter the options, else add it */ + if ((start = strcasestr(gplink_str, gplink->dn)) != NULL) { + start += strlen(gplink->dn); + *start = '\0'; + start++; + while (*start != ']' && *start != '\0') { + start++; + } + gplink_str = talloc_asprintf(mem_ctx, "%s;%d%s", gplink_str, gplink->options, start); + if (gplink_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + } else { + /* Prepend the new GPO link to the string. This list is backwards in priority. */ + gplink_str = talloc_asprintf(mem_ctx, "[LDAP://%s;%d]%s", gplink->dn, gplink->options, gplink_str); + if (gplink_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + } + + + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = dn; + + rv = ldb_msg_add_string(msg, "gPLink", gplink_str); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB message add string failed: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + msg->elements[0].flags = LDB_FLAG_MOD_REPLACE; + + rv = ldb_modify(gp_ctx->ldb_ctx, msg); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB modify failed: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} + +NTSTATUS gp_del_gplink(struct gp_context *gp_ctx, const char *dn_str, const char *gplink_dn) +{ + TALLOC_CTX *mem_ctx; + struct ldb_result *result; + struct ldb_dn *dn; + struct ldb_message *msg; + const char *attrs[] = { "gPLink", NULL }; + const char *gplink_str, *search_string; + int rv; + char *p; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, dn_str); + + rv = ldb_search(gp_ctx->ldb_ctx, mem_ctx, &result, dn, LDB_SCOPE_BASE, attrs, "(objectclass=*)"); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB search failed: %s\n%s\n", ldb_strerror(rv), ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + if (result->count != 1) { + talloc_free(mem_ctx); + return NT_STATUS_NOT_FOUND; + } + + gplink_str = ldb_msg_find_attr_as_string(result->msgs[0], "gPLink", ""); + + /* If this GPO link already exists, alter the options, else add it */ + search_string = talloc_asprintf(mem_ctx, "[LDAP://%s]", gplink_dn); + if (search_string == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + p = strcasestr(gplink_str, search_string); + if (p == NULL) { + talloc_free(mem_ctx); + return NT_STATUS_NOT_FOUND; + } + + *p = '\0'; + p++; + while (*p != ']' && *p != '\0') { + p++; + } + p++; + gplink_str = talloc_asprintf(mem_ctx, "%s%s", gplink_str, p); + if (gplink_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = dn; + + if (strcmp(gplink_str, "") == 0) { + rv = ldb_msg_add_empty(msg, "gPLink", LDB_FLAG_MOD_DELETE, NULL); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB message add empty element failed: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + } else { + rv = ldb_msg_add_string(msg, "gPLink", gplink_str); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB message add string failed: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + msg->elements[0].flags = LDB_FLAG_MOD_REPLACE; + } + rv = ldb_modify(gp_ctx->ldb_ctx, msg); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB modify failed: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} + +NTSTATUS gp_get_inheritance(struct gp_context *gp_ctx, const char *dn_str, enum gpo_inheritance *inheritance) +{ + TALLOC_CTX *mem_ctx; + struct ldb_result *result; + struct ldb_dn *dn; + const char *attrs[] = { "gPOptions", NULL }; + int rv; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, dn_str); + + rv = ldb_search(gp_ctx->ldb_ctx, mem_ctx, &result, dn, LDB_SCOPE_BASE, attrs, "(objectclass=*)"); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB search failed: %s\n%s\n", ldb_strerror(rv), ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + if (result->count != 1) { + talloc_free(mem_ctx); + return NT_STATUS_NOT_FOUND; + } + + *inheritance = ldb_msg_find_attr_as_uint(result->msgs[0], "gPOptions", 0); + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} + +NTSTATUS gp_set_inheritance(struct gp_context *gp_ctx, const char *dn_str, enum gpo_inheritance inheritance) +{ + char *inheritance_string; + struct ldb_message *msg; + int rv; + + msg = ldb_msg_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(msg); + + msg->dn = ldb_dn_new(msg, gp_ctx->ldb_ctx, dn_str); + + inheritance_string = talloc_asprintf(msg, "%d", inheritance); + if (inheritance_string == NULL) { + TALLOC_FREE(msg); + return NT_STATUS_NO_MEMORY; + } + + rv = ldb_msg_add_string(msg, "gPOptions", inheritance_string); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB message add string failed: %s\n", ldb_strerror(rv))); + talloc_free(msg); + return NT_STATUS_UNSUCCESSFUL; + } + msg->elements[0].flags = LDB_FLAG_MOD_REPLACE; + + rv = ldb_modify(gp_ctx->ldb_ctx, msg); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB modify failed: %s\n", ldb_strerror(rv))); + talloc_free(msg); + return NT_STATUS_UNSUCCESSFUL; + } + + talloc_free(msg); + return NT_STATUS_OK; +} + +NTSTATUS gp_create_ldap_gpo(struct gp_context *gp_ctx, struct gp_object *gpo) +{ + struct ldb_message *msg; + TALLOC_CTX *mem_ctx; + int rv; + char *dn_str, *flags_str, *version_str; + struct ldb_dn *child_dn, *gpo_dn; + + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + /* CN={GUID} */ + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_get_default_basedn(gp_ctx->ldb_ctx); + dn_str = talloc_asprintf(mem_ctx, "CN=%s,CN=Policies,CN=System", gpo->name); + if (dn_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + child_dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, dn_str); + rv = ldb_dn_add_child(msg->dn, child_dn); + if (!rv) goto ldb_msg_add_error; + + flags_str = talloc_asprintf(mem_ctx, "%d", gpo->flags); + if (flags_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + version_str = talloc_asprintf(mem_ctx, "%d", gpo->version); + if (version_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + rv = ldb_msg_add_string(msg, "objectClass", "top"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "objectClass", "container"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "objectClass", "groupPolicyContainer"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "displayName", gpo->display_name); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "name", gpo->name); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "CN", gpo->name); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "gPCFileSysPath", gpo->file_sys_path); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "flags", flags_str); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "versionNumber", version_str); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "showInAdvancedViewOnly", "TRUE"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "gpCFunctionalityVersion", "2"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + + rv = ldb_add(gp_ctx->ldb_ctx, msg); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB add error: %s\n", ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + gpo_dn = msg->dn; + + /* CN=User */ + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_copy(mem_ctx, gpo_dn); + child_dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, "CN=User"); + rv = ldb_dn_add_child(msg->dn, child_dn); + if (!rv) goto ldb_msg_add_error; + + rv = ldb_msg_add_string(msg, "objectClass", "top"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "objectClass", "container"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "showInAdvancedViewOnly", "TRUE"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "CN", "User"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "name", "User"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + + rv = ldb_add(gp_ctx->ldb_ctx, msg); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB add error: %s\n", ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* CN=Machine */ + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_copy(mem_ctx, gpo_dn); + child_dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, "CN=Machine"); + rv = ldb_dn_add_child(msg->dn, child_dn); + if (!rv) goto ldb_msg_add_error; + + rv = ldb_msg_add_string(msg, "objectClass", "top"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "objectClass", "container"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "showInAdvancedViewOnly", "TRUE"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "CN", "Machine"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + rv = ldb_msg_add_string(msg, "name", "Machine"); + if (rv != LDB_SUCCESS) goto ldb_msg_add_error; + + rv = ldb_add(gp_ctx->ldb_ctx, msg); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB add error: %s\n", ldb_errstring(gp_ctx->ldb_ctx))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + gpo->dn = talloc_strdup(gpo, ldb_dn_get_linearized(gpo_dn)); + if (gpo->dn == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; + + ldb_msg_add_error: + DEBUG(0, ("LDB Error adding element to ldb message\n")); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; +} + +NTSTATUS gp_set_ads_acl (struct gp_context *gp_ctx, const char *dn_str, const struct security_descriptor *sd) +{ + TALLOC_CTX *mem_ctx; + DATA_BLOB data; + enum ndr_err_code ndr_err; + struct ldb_message *msg; + int rv; + + /* Create a forked memory context to clean up easily */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + /* Push the security descriptor through the NDR library */ + ndr_err = ndr_push_struct_blob(&data, + mem_ctx, + sd, + (ndr_push_flags_fn_t)ndr_push_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + + /* Create a LDB message */ + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, dn_str); + + rv = ldb_msg_add_value(msg, "nTSecurityDescriptor", &data, NULL); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB message add element failed for adding nTSecurityDescriptor: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + msg->elements[0].flags = LDB_FLAG_MOD_REPLACE; + + rv = ldb_modify(gp_ctx->ldb_ctx, msg); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB modify failed: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} + +/* This function sets flags, version and displayName on a GPO */ +NTSTATUS gp_set_ldap_gpo(struct gp_context *gp_ctx, struct gp_object *gpo) +{ + int rv; + TALLOC_CTX *mem_ctx; + struct ldb_message *msg; + char *version_str, *flags_str; + + mem_ctx = talloc_new(gp_ctx); + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_new(mem_ctx, gp_ctx->ldb_ctx, gpo->dn); + + version_str = talloc_asprintf(mem_ctx, "%d", gpo->version); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + flags_str = talloc_asprintf(mem_ctx, "%d", gpo->flags); + if (msg == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + rv = ldb_msg_add_string(msg, "flags", flags_str); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB message add string failed for flags: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + msg->elements[0].flags = LDB_FLAG_MOD_REPLACE; + + rv = ldb_msg_add_string(msg, "version", version_str); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB message add string failed for version: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + msg->elements[1].flags = LDB_FLAG_MOD_REPLACE; + + rv = ldb_msg_add_string(msg, "displayName", gpo->display_name); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB message add string failed for displayName: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + msg->elements[2].flags = LDB_FLAG_MOD_REPLACE; + + rv = ldb_modify(gp_ctx->ldb_ctx, msg); + if (rv != LDB_SUCCESS) { + DEBUG(0, ("LDB modify failed: %s\n", ldb_strerror(rv))); + talloc_free(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} diff --git a/source4/lib/policy/gp_manage.c b/source4/lib/policy/gp_manage.c new file mode 100644 index 0000000..d8feadb --- /dev/null +++ b/source4/lib/policy/gp_manage.c @@ -0,0 +1,330 @@ +/* + * Unix SMB/CIFS implementation. + * Group Policy Object Support + * Copyright (C) Wilco Baan Hofman 2010 + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ +#include "includes.h" +#include "../libcli/security/dom_sid.h" +#include "../libcli/security/security_descriptor.h" +#include "../librpc/ndr/libndr.h" +#include "../lib/util/charset/charset.h" +#include "param/param.h" +#include "lib/policy/policy.h" + +uint32_t gp_ads_to_dir_access_mask(uint32_t access_mask) +{ + uint32_t fs_mask; + + /* Copy the standard access mask */ + fs_mask = access_mask & 0x001F0000; + + /* When READ_PROP and LIST_CONTENTS are set, read access is granted on the GPT */ + if (access_mask & SEC_ADS_READ_PROP && access_mask & SEC_ADS_LIST) { + fs_mask |= SEC_STD_SYNCHRONIZE | SEC_DIR_LIST | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_READ_EA | SEC_DIR_TRAVERSE; + } + + /* When WRITE_PROP is set, full write access is granted on the GPT */ + if (access_mask & SEC_ADS_WRITE_PROP) { + fs_mask |= SEC_STD_SYNCHRONIZE | SEC_DIR_WRITE_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_DIR_ADD_FILE | + SEC_DIR_ADD_SUBDIR; + } + + /* Map CREATE_CHILD to add file and add subdir */ + if (access_mask & SEC_ADS_CREATE_CHILD) + fs_mask |= SEC_DIR_ADD_FILE | SEC_DIR_ADD_SUBDIR; + + /* Map ADS delete child to dir delete child */ + if (access_mask & SEC_ADS_DELETE_CHILD) + fs_mask |= SEC_DIR_DELETE_CHILD; + + return fs_mask; +} + +NTSTATUS gp_create_gpt_security_descriptor (TALLOC_CTX *mem_ctx, struct security_descriptor *ds_sd, struct security_descriptor **ret) +{ + struct security_descriptor *fs_sd; + NTSTATUS status; + uint32_t i; + + /* Allocate the file system security descriptor */ + fs_sd = talloc(mem_ctx, struct security_descriptor); + NT_STATUS_HAVE_NO_MEMORY(fs_sd); + + /* Copy the basic information from the directory server security descriptor */ + fs_sd->owner_sid = talloc_memdup(fs_sd, ds_sd->owner_sid, sizeof(struct dom_sid)); + if (fs_sd->owner_sid == NULL) { + TALLOC_FREE(fs_sd); + return NT_STATUS_NO_MEMORY; + } + + fs_sd->group_sid = talloc_memdup(fs_sd, ds_sd->group_sid, sizeof(struct dom_sid)); + if (fs_sd->group_sid == NULL) { + TALLOC_FREE(fs_sd); + return NT_STATUS_NO_MEMORY; + } + + fs_sd->type = ds_sd->type; + fs_sd->revision = ds_sd->revision; + + /* Copy the sacl */ + fs_sd->sacl = security_acl_dup(fs_sd, ds_sd->sacl); + if (fs_sd->sacl == NULL) { + TALLOC_FREE(fs_sd); + return NT_STATUS_NO_MEMORY; + } + + /* Copy the dacl */ + fs_sd->dacl = talloc_zero(fs_sd, struct security_acl); + if (fs_sd->dacl == NULL) { + TALLOC_FREE(fs_sd); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < ds_sd->dacl->num_aces; i++) { + char *trustee = dom_sid_string(fs_sd, &ds_sd->dacl->aces[i].trustee); + struct security_ace *ace; + + /* Don't add the allow for SID_BUILTIN_PREW2K */ + if ((ds_sd->dacl->aces[i].type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT || + ds_sd->dacl->aces[i].type == SEC_ACE_TYPE_ACCESS_ALLOWED) && + strcmp(trustee, SID_BUILTIN_PREW2K) == 0) { + talloc_free(trustee); + continue; + } + + /* Copy the ace from the directory server security descriptor */ + ace = talloc_memdup(fs_sd, &ds_sd->dacl->aces[i], sizeof(struct security_ace)); + if (ace == NULL) { + TALLOC_FREE(fs_sd); + return NT_STATUS_NO_MEMORY; + } + + /* Set specific inheritance flags for within the GPO */ + ace->flags |= SEC_ACE_FLAG_OBJECT_INHERIT | SEC_ACE_FLAG_CONTAINER_INHERIT; + if (strcmp(trustee, SID_CREATOR_OWNER) == 0) { + ace->flags |= SEC_ACE_FLAG_INHERIT_ONLY; + } + + /* Get a directory access mask from the assigned access mask on the LDAP object */ + ace->access_mask = gp_ads_to_dir_access_mask(ace->access_mask); + + /* Add the ace to the security descriptor DACL */ + status = security_descriptor_dacl_add(fs_sd, ace); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to add a dacl to file system security descriptor\n")); + TALLOC_FREE(fs_sd); + return status; + } + + /* Clean up the allocated data in this iteration */ + talloc_free(trustee); + } + + *ret = fs_sd; + return NT_STATUS_OK; +} + + +NTSTATUS gp_create_gpo (struct gp_context *gp_ctx, const char *display_name, struct gp_object **ret) +{ + struct GUID guid_struct; + char *guid_str; + char *name; + struct security_descriptor *sd; + TALLOC_CTX *mem_ctx; + struct gp_object *gpo; + NTSTATUS status; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + /* Create the gpo struct to return later */ + gpo = talloc(gp_ctx, struct gp_object); + if (gpo == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Generate a GUID */ + guid_struct = GUID_random(); + guid_str = GUID_string2(mem_ctx, &guid_struct); + if (guid_str == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + name = strupper_talloc(mem_ctx, guid_str); + if (name == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Prepare the GPO struct */ + gpo->dn = NULL; + gpo->name = name; + gpo->flags = 0; + gpo->version = 0; + gpo->display_name = talloc_strdup(gpo, display_name); + if (gpo->display_name == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + gpo->file_sys_path = talloc_asprintf(gpo, "\\\\%s\\sysvol\\%s\\Policies\\%s", lpcfg_dnsdomain(gp_ctx->lp_ctx), lpcfg_dnsdomain(gp_ctx->lp_ctx), name); + if (gpo->file_sys_path == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Create the GPT */ + status = gp_create_gpt(gp_ctx, name, gpo->file_sys_path); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to create GPT\n")); + talloc_free(mem_ctx); + return status; + } + + + /* Create the LDAP GPO, including CN=User and CN=Machine */ + status = gp_create_ldap_gpo(gp_ctx, gpo); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to create LDAP group policy object\n")); + talloc_free(mem_ctx); + return status; + } + + /* Get the new security descriptor */ + status = gp_get_gpo_info(gp_ctx, gpo->dn, &gpo); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to fetch LDAP group policy object\n")); + talloc_free(mem_ctx); + return status; + } + + /* Create matching file and DS security descriptors */ + status = gp_create_gpt_security_descriptor(mem_ctx, gpo->security_descriptor, &sd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to convert ADS security descriptor to filesystem security descriptor\n")); + talloc_free(mem_ctx); + return status; + } + + /* Set the security descriptor on the filesystem for this GPO */ + status = gp_set_gpt_security_descriptor(gp_ctx, gpo, sd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to set security descriptor (ACL) on the file system\n")); + talloc_free(mem_ctx); + return status; + } + + talloc_free(mem_ctx); + + *ret = gpo; + return NT_STATUS_OK; +} + +NTSTATUS gp_set_acl (struct gp_context *gp_ctx, const char *dn_str, const struct security_descriptor *sd) +{ + TALLOC_CTX *mem_ctx; + struct security_descriptor *fs_sd; + struct gp_object *gpo; + NTSTATUS status; + + /* Create a forked memory context, as a base for everything here */ + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + /* Set the ACL on LDAP database */ + status = gp_set_ads_acl(gp_ctx, dn_str, sd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to set ACL on ADS\n")); + talloc_free(mem_ctx); + return status; + } + + /* Get the group policy object information, for filesystem location and merged sd */ + status = gp_get_gpo_info(gp_ctx, dn_str, &gpo); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to set ACL on ADS\n")); + talloc_free(mem_ctx); + return status; + } + + /* Create matching file and DS security descriptors */ + status = gp_create_gpt_security_descriptor(mem_ctx, gpo->security_descriptor, &fs_sd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to convert ADS security descriptor to filesystem security descriptor\n")); + talloc_free(mem_ctx); + return status; + } + + /* Set the security descriptor on the filesystem for this GPO */ + status = gp_set_gpt_security_descriptor(gp_ctx, gpo, fs_sd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to set security descriptor (ACL) on the file system\n")); + talloc_free(mem_ctx); + return status; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} + +NTSTATUS gp_push_gpo (struct gp_context *gp_ctx, const char *local_path, struct gp_object *gpo) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx; + struct gp_ini_context *ini; + char *filename; + + mem_ctx = talloc_new(gp_ctx); + NT_STATUS_HAVE_NO_MEMORY(mem_ctx); + + /* Get version from ini file */ + /* FIXME: The local file system may be case sensitive */ + filename = talloc_asprintf(mem_ctx, "%s/%s", local_path, "GPT.INI"); + if (filename == NULL) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + status = gp_parse_ini(mem_ctx, gp_ctx, local_path, &ini); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to parse GPT.INI.\n")); + talloc_free(mem_ctx); + return status; + } + + /* Push the GPT to the remote sysvol */ + status = gp_push_gpt(gp_ctx, local_path, gpo->file_sys_path); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to push GPT to DC's sysvol share.\n")); + talloc_free(mem_ctx); + return status; + } + + /* Write version to LDAP */ + status = gp_set_ldap_gpo(gp_ctx, gpo); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to set GPO options in DC's LDAP.\n")); + talloc_free(mem_ctx); + return status; + } + + talloc_free(mem_ctx); + return NT_STATUS_OK; +} diff --git a/source4/lib/policy/policy.h b/source4/lib/policy/policy.h new file mode 100644 index 0000000..3d8a0dc --- /dev/null +++ b/source4/lib/policy/policy.h @@ -0,0 +1,125 @@ +/* + * Unix SMB/CIFS implementation. + * Group Policy Object Support + * Copyright (C) Guenther Deschner 2005-2008 (from samba 3 gpo.h) + * Copyright (C) Wilco Baan Hofman 2010 + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __POLICY_H__ +#define __POLICY_H__ + +#define GPLINK_OPT_DISABLE (1 << 0) +#define GPLINK_OPT_ENFORCE (1 << 1) + +#define GPO_FLAG_USER_DISABLE (1 << 0) +#define GPO_FLAG_MACHINE_DISABLE (1 << 1) + +struct security_token; +struct nbt_dc_name; + +enum gpo_inheritance { + GPO_INHERIT = 0, + GPO_BLOCK_INHERITANCE = 1, +}; + +struct gp_context { + struct ldb_context *ldb_ctx; + struct loadparm_context *lp_ctx; + struct cli_credentials *credentials; + struct tevent_context *ev_ctx; + struct smbcli_state *cli; + struct nbt_dc_name *active_dc; +}; + +struct gp_object { + uint32_t version; + uint32_t flags; + const char *display_name; + const char *name; + const char *dn; + const char *file_sys_path; + struct security_descriptor *security_descriptor; +}; + + +struct gp_link { + uint32_t options; + const char *dn; +}; + +struct gp_ini_param { + char *name; + char *value; +}; + +struct gp_ini_section { + char *name; + uint16_t num_params; + struct gp_ini_param *params; +}; + +struct gp_ini_context { + uint16_t num_sections; + struct gp_ini_section *sections; +}; + +NTSTATUS gp_init(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct cli_credentials *creds, + struct tevent_context *ev_ctx, + struct gp_context **gp_ctx); + + +/* LDAP functions */ +NTSTATUS gp_list_all_gpos(struct gp_context *gp_ctx, struct gp_object ***ret); +NTSTATUS gp_get_gplinks(struct gp_context *gp_ctx, const char *req_dn, struct gp_link ***ret); +NTSTATUS gp_list_gpos(struct gp_context *gp_ctx, struct security_token *token, const char ***ret); + +NTSTATUS gp_get_gpo_info(struct gp_context *gp_ctx, const char *dn_str, struct gp_object **ret); + + +NTSTATUS gp_get_gplink_options(TALLOC_CTX *mem_ctx, uint32_t flags, const char ***ret); +NTSTATUS gp_get_gpo_flags(TALLOC_CTX *mem_ctx, uint32_t flags, const char ***ret); + +NTSTATUS gp_set_gplink(struct gp_context *gp_ctx, const char *dn_str, struct gp_link *gplink); +NTSTATUS gp_del_gplink(struct gp_context *gp_ctx, const char *dn_str, const char *gp_dn); +NTSTATUS gp_get_inheritance(struct gp_context *gp_ctx, const char *dn_str, enum gpo_inheritance *inheritance); +NTSTATUS gp_set_inheritance(struct gp_context *gp_ctx, const char *dn_str, enum gpo_inheritance inheritance); + +NTSTATUS gp_create_ldap_gpo(struct gp_context *gp_ctx, struct gp_object *gpo); +NTSTATUS gp_set_ads_acl (struct gp_context *gp_ctx, const char *dn_str, const struct security_descriptor *sd); +NTSTATUS gp_push_gpo (struct gp_context *gp_ctx, const char *local_path, struct gp_object *gpo); +NTSTATUS gp_set_ldap_gpo(struct gp_context *gp_ctx, struct gp_object *gpo); + +/* File system functions */ +NTSTATUS gp_fetch_gpt (struct gp_context *gp_ctx, struct gp_object *gpo, const char **path); +NTSTATUS gp_create_gpt(struct gp_context *gp_ctx, const char *name, const char *file_sys_path); +NTSTATUS gp_set_gpt_security_descriptor(struct gp_context *gp_ctx, struct gp_object *gpo, struct security_descriptor *sd); +NTSTATUS gp_push_gpt(struct gp_context *gp_ctx, const char *local_path, + const char *file_sys_path); + +/* Ini functions */ +NTSTATUS gp_parse_ini(TALLOC_CTX *mem_ctx, struct gp_context *gp_ctx, const char *filename, struct gp_ini_context **ret); +NTSTATUS gp_get_ini_string(struct gp_ini_context *ini, const char *section, const char *name, char **ret); +NTSTATUS gp_get_ini_uint(struct gp_ini_context *ini, const char *section, const char *name, uint32_t *ret); + +/* Managing functions */ +NTSTATUS gp_create_gpo (struct gp_context *gp_ctx, const char *display_name, struct gp_object **ret); +NTSTATUS gp_create_gpt_security_descriptor (TALLOC_CTX *mem_ctx, struct security_descriptor *ds_sd, struct security_descriptor **ret); +NTSTATUS gp_set_acl (struct gp_context *gp_ctx, const char *dn_str, const struct security_descriptor *sd); +uint32_t gp_ads_to_dir_access_mask(uint32_t access_mask); + +#endif diff --git a/source4/lib/policy/pypolicy.c b/source4/lib/policy/pypolicy.c new file mode 100644 index 0000000..f85ea85 --- /dev/null +++ b/source4/lib/policy/pypolicy.c @@ -0,0 +1,174 @@ +/* + * Unix SMB/CIFS implementation. + * Python bindings for libpolicy + * Copyright (C) Jelmer Vernooij 2010 + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include "lib/replace/system/python.h" +#include "includes.h" +#include "python/py3compat.h" +#include "policy.h" +#include "libcli/util/pyerrors.h" + +void initpolicy(void); + +static PyObject *py_get_gpo_flags(PyObject *self, PyObject *args) +{ + int flags; + PyObject *py_ret; + const char **ret; + TALLOC_CTX *mem_ctx; + int i; + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "i", &flags)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + status = gp_get_gpo_flags(mem_ctx, flags, &ret); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + + py_ret = PyList_New(0); + for (i = 0; ret[i]; i++) { + int res = 0; + PyObject *item = PyUnicode_FromString(ret[i]); + if (item == NULL) { + talloc_free(mem_ctx); + Py_DECREF(py_ret); + PyErr_NoMemory(); + return NULL; + } + res = PyList_Append(py_ret, item); + Py_CLEAR(item); + if (res == -1) { + Py_DECREF(py_ret); + talloc_free(mem_ctx); + return NULL; + } + } + + talloc_free(mem_ctx); + + return py_ret; +} + +static PyObject *py_get_gplink_options(PyObject *self, PyObject *args) +{ + int flags; + PyObject *py_ret; + const char **ret; + TALLOC_CTX *mem_ctx; + int i; + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "i", &flags)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + status = gp_get_gplink_options(mem_ctx, flags, &ret); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + + py_ret = PyList_New(0); + for (i = 0; ret[i]; i++) { + int res = 0; + PyObject *item = PyUnicode_FromString(ret[i]); + if (item == NULL) { + talloc_free(mem_ctx); + Py_DECREF(py_ret); + PyErr_NoMemory(); + return NULL; + } + res = PyList_Append(py_ret, item); + Py_CLEAR(item); + if (res == -1) { + Py_DECREF(py_ret); + talloc_free(mem_ctx); + return NULL; + } + } + + talloc_free(mem_ctx); + + return py_ret; +} + +static PyObject *py_ads_to_dir_access_mask(PyObject *self, PyObject *args) +{ + uint32_t access_mask, dir_mask; + + if (! PyArg_ParseTuple(args, "I", &access_mask)) + return NULL; + + dir_mask = gp_ads_to_dir_access_mask(access_mask); + + return Py_BuildValue("I", dir_mask); +} + + +static PyMethodDef py_policy_methods[] = { + { "get_gpo_flags", (PyCFunction)py_get_gpo_flags, METH_VARARGS, + "get_gpo_flags(flags) -> list" }, + { "get_gplink_options", (PyCFunction)py_get_gplink_options, METH_VARARGS, + "get_gplink_options(options) -> list" }, + { "ads_to_dir_access_mask", (PyCFunction)py_ads_to_dir_access_mask, METH_VARARGS, + "ads_to_dir_access_mask(access_mask) -> dir_mask" }, + {0} +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "policy", + .m_doc = "(Group) Policy manipulation", + .m_size = -1, + .m_methods = py_policy_methods, +}; + +MODULE_INIT_FUNC(policy) +{ + PyObject *m = NULL; + + m = PyModule_Create(&moduledef); + if (!m) + return m; + + PyModule_AddObject(m, "GPO_FLAG_USER_DISABLE", + PyLong_FromLong(GPO_FLAG_USER_DISABLE)); + PyModule_AddObject(m, "GPO_MACHINE_USER_DISABLE", + PyLong_FromLong(GPO_FLAG_MACHINE_DISABLE)); + PyModule_AddObject(m, "GPLINK_OPT_DISABLE", + PyLong_FromLong(GPLINK_OPT_DISABLE )); + PyModule_AddObject(m, "GPLINK_OPT_ENFORCE ", + PyLong_FromLong(GPLINK_OPT_ENFORCE )); + return m; +} diff --git a/source4/lib/policy/samba-policy.pc.in b/source4/lib/policy/samba-policy.pc.in new file mode 100644 index 0000000..3247ae3 --- /dev/null +++ b/source4/lib/policy/samba-policy.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: samba-policy +Description: Active Directory Group Policy library +Requires: talloc +Requires.private: ldb +Version: @PACKAGE_VERSION@ +Libs: @LIB_RPATH@ -L${libdir} -lsamba-policy +Cflags: -I${includedir} -DHAVE_IMMEDIATE_STRUCTURES=1 diff --git a/source4/lib/policy/wscript_build b/source4/lib/policy/wscript_build new file mode 100644 index 0000000..027d4be --- /dev/null +++ b/source4/lib/policy/wscript_build @@ -0,0 +1,22 @@ +#!/usr/bin/env python + + + +pytalloc_util = bld.pyembed_libname('pytalloc-util') +samba_policy = bld.pyembed_libname('samba-policy') +samba_net = bld.pyembed_libname('samba-net') +bld.SAMBA_LIBRARY(samba_policy, + source='gp_ldap.c gp_filesys.c gp_manage.c gp_ini.c', + pc_files='samba-policy.pc', + public_deps='ldb %s' % samba_net, + vnum='0.0.1', + pyembed=True, + public_headers='policy.h', + enabled=bld.PYTHON_BUILD_IS_ENABLED() + ) +bld.SAMBA_PYTHON( + 'py_policy', + source='pypolicy.c', + public_deps='%s %s' % (samba_policy, pytalloc_util), + realname='samba/policy.so' + ) diff --git a/source4/lib/registry/Doxyfile b/source4/lib/registry/Doxyfile new file mode 100644 index 0000000..efc01cd --- /dev/null +++ b/source4/lib/registry/Doxyfile @@ -0,0 +1,24 @@ +PROJECT_NAME = REGISTRY +OUTPUT_DIRECTORY = apidocs +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +OPTIMIZE_OUTPUT_FOR_C = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +GENERATE_TODOLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +SHOW_USED_FILES = NO +SHOW_DIRECTORIES = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +INPUT = . +FILE_PATTERNS = *.c *.h *.dox +GENERATE_HTML = YES +HTML_OUTPUT = html +GENERATE_MAN = YES +ALWAYS_DETAILED_SEC = YES +JAVADOC_AUTOBRIEF = YES diff --git a/source4/lib/registry/README b/source4/lib/registry/README new file mode 100644 index 0000000..07b2c01 --- /dev/null +++ b/source4/lib/registry/README @@ -0,0 +1,42 @@ +This is the registry library. The registry is basically a bunch of +hives, each of which is loaded from a file. When using a local registry, +it is possible to specify where hives should be loaded from, etc. + +There are separate APIs for accessing the data in a hive and the +data in the registry itself. Each supports different backends. + +The following "full registry" backends are currently provided: + + * Remote (over DCE/RPC) + * Local (allows "mounting" hives) + * Wine (uses the wine plain-text file) + +The following hive backends are supported: + + - ldb + - regf (NTUSER.DAT-style files) + - rpc (Remote individual hives) + - directory + +reg_open_samba() loads a set of hives based on smb.conf settings. +Lines in smb.conf should have the following syntax: + +registry:<hivename> = <backend>:<location> + +So an example usage could be: + +registry:HKEY_CURRENT_USER = regf:NTUSER.DAT +registry:HKEY_LOCAL_MACHINE = ldb:tdb://registry.tdb + +WERR_NOT_SUPPORTED will be returned for all hives that haven't been set. + +On Windows the various registry hives are loaded from: + +HKEY_CURRENT_CONFIG: %SystemRoot%\System32\Config\System +HKEY_CURRENT_USER: %Profile%\NTUser.dat +HKEY_LOCAL_MACHINE\SAM: %SystemRoot%\System32\Config\Sam +HKEY_LOCAL_MACHINE\Security: %SystemRoot%\System32\Config\Security +HKEY_LOCAL_MACHINE\Software: %SystemRoot%\System32\Config\Software +HKEY_LOCAL_MACHINE\System: %SystemRoot%\System32\Config\System +HKEY_USERS\.DEFAULT: %SystemRoot%\System32\Config\Default +HKEY_LOCAL_MACHINE\HARDWARE: is autogenerated diff --git a/source4/lib/registry/TODO b/source4/lib/registry/TODO new file mode 100644 index 0000000..b5809b8 --- /dev/null +++ b/source4/lib/registry/TODO @@ -0,0 +1,5 @@ +- ..\..\, \bla\blie support in regshell +- finish rpc_server + +regshell: + - support for security descriptors diff --git a/source4/lib/registry/hive.c b/source4/lib/registry/hive.c new file mode 100644 index 0000000..1f0672d --- /dev/null +++ b/source4/lib/registry/hive.c @@ -0,0 +1,176 @@ + +/* + Unix SMB/CIFS implementation. + Registry hive interface + Copyright (C) Jelmer Vernooij 2003-2007. + + 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 3 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 "registry.h" +#include "system/filesys.h" +#include "param/param.h" + +/** Open a registry file/host/etc */ +_PUBLIC_ WERROR reg_open_hive(TALLOC_CTX *parent_ctx, const char *location, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct hive_key **root) +{ + int fd, num; + char peek[20]; + + fd = open(location, O_RDWR); + if (fd == -1) { + if (errno == ENOENT) + return WERR_FILE_NOT_FOUND; + return WERR_FILE_NOT_FOUND; + } + + num = read(fd, peek, 20); + close(fd); + if (num == -1) { + return WERR_FILE_NOT_FOUND; + } + + if (!strncmp(peek, "regf", 4)) { + return reg_open_regf_file(parent_ctx, location, root); + } else if (!strncmp(peek, "TDB file", 8)) { + return reg_open_ldb_file(parent_ctx, location, session_info, + credentials, ev_ctx, lp_ctx, root); + } + + return WERR_FILE_NOT_FOUND; +} + +_PUBLIC_ WERROR hive_key_get_info(TALLOC_CTX *mem_ctx, + const struct hive_key *key, + const char **classname, uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_change_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize) +{ + return key->ops->get_key_info(mem_ctx, key, classname, num_subkeys, + num_values, last_change_time, + max_subkeynamelen, + max_valnamelen, max_valbufsize); +} + +_PUBLIC_ WERROR hive_key_add_name(TALLOC_CTX *ctx, + const struct hive_key *parent_key, + const char *name, const char *classname, + struct security_descriptor *desc, + struct hive_key **key) +{ + SMB_ASSERT(strchr(name, '\\') == NULL); + + return parent_key->ops->add_key(ctx, parent_key, name, classname, + desc, key); +} + +_PUBLIC_ WERROR hive_key_del(TALLOC_CTX *mem_ctx, const struct hive_key *key, + const char *name) +{ + return key->ops->del_key(mem_ctx, key, name); +} + +_PUBLIC_ WERROR hive_get_key_by_name(TALLOC_CTX *mem_ctx, + const struct hive_key *key, + const char *name, + struct hive_key **subkey) +{ + return key->ops->get_key_by_name(mem_ctx, key, name, subkey); +} + +WERROR hive_enum_key(TALLOC_CTX *mem_ctx, + const struct hive_key *key, uint32_t idx, + const char **name, + const char **classname, + NTTIME *last_mod_time) +{ + return key->ops->enum_key(mem_ctx, key, idx, name, classname, + last_mod_time); +} + +WERROR hive_key_set_value(struct hive_key *key, const char *name, uint32_t type, + const DATA_BLOB data) +{ + if (key->ops->set_value == NULL) + return WERR_NOT_SUPPORTED; + + return key->ops->set_value(key, name, type, data); +} + +WERROR hive_get_value(TALLOC_CTX *mem_ctx, + struct hive_key *key, const char *name, + uint32_t *type, DATA_BLOB *data) +{ + if (key->ops->get_value_by_name == NULL) + return WERR_NOT_SUPPORTED; + + return key->ops->get_value_by_name(mem_ctx, key, name, type, data); +} + +WERROR hive_get_value_by_index(TALLOC_CTX *mem_ctx, + struct hive_key *key, uint32_t idx, + const char **name, + uint32_t *type, DATA_BLOB *data) +{ + if (key->ops->enum_value == NULL) + return WERR_NOT_SUPPORTED; + + return key->ops->enum_value(mem_ctx, key, idx, name, type, data); +} + +WERROR hive_get_sec_desc(TALLOC_CTX *mem_ctx, + struct hive_key *key, + struct security_descriptor **security) +{ + if (key->ops->get_sec_desc == NULL) + return WERR_NOT_SUPPORTED; + + return key->ops->get_sec_desc(mem_ctx, key, security); +} + +WERROR hive_set_sec_desc(struct hive_key *key, + const struct security_descriptor *security) +{ + if (key->ops->set_sec_desc == NULL) + return WERR_NOT_SUPPORTED; + + return key->ops->set_sec_desc(key, security); +} + +WERROR hive_key_del_value(TALLOC_CTX *mem_ctx, struct hive_key *key, + const char *name) +{ + if (key->ops->delete_value == NULL) + return WERR_NOT_SUPPORTED; + + return key->ops->delete_value(mem_ctx, key, name); +} + +WERROR hive_key_flush(struct hive_key *key) +{ + if (key->ops->flush_key == NULL) + return WERR_OK; + + return key->ops->flush_key(key); +} diff --git a/source4/lib/registry/interface.c b/source4/lib/registry/interface.c new file mode 100644 index 0000000..2900c10 --- /dev/null +++ b/source4/lib/registry/interface.c @@ -0,0 +1,298 @@ +/* + Unix SMB/CIFS implementation. + Transparent registry backend handling + Copyright (C) Jelmer Vernooij 2003-2007. + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "../lib/util/dlinklist.h" +#include "lib/registry/registry.h" +#include "system/filesys.h" + +#undef strcasecmp + +/** + * @file + * @brief Main registry functions + */ + +const struct reg_predefined_key reg_predefined_keys[] = { + {HKEY_CLASSES_ROOT,"HKEY_CLASSES_ROOT" }, + {HKEY_CURRENT_USER,"HKEY_CURRENT_USER" }, + {HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE" }, + {HKEY_PERFORMANCE_DATA, "HKEY_PERFORMANCE_DATA" }, + {HKEY_USERS, "HKEY_USERS" }, + {HKEY_CURRENT_CONFIG, "HKEY_CURRENT_CONFIG" }, + {HKEY_DYN_DATA, "HKEY_DYN_DATA" }, + {HKEY_PERFORMANCE_TEXT, "HKEY_PERFORMANCE_TEXT" }, + {HKEY_PERFORMANCE_NLSTEXT, "HKEY_PERFORMANCE_NLSTEXT" }, + { 0, NULL } +}; + +/** Obtain name of specific hkey. */ +_PUBLIC_ const char *reg_get_predef_name(uint32_t hkey) +{ + unsigned int i; + for (i = 0; reg_predefined_keys[i].name; i++) { + if (reg_predefined_keys[i].handle == hkey) + return reg_predefined_keys[i].name; + } + + return NULL; +} + +/** Get predefined key by name. */ +_PUBLIC_ WERROR reg_get_predefined_key_by_name(struct registry_context *ctx, + const char *name, + struct registry_key **key) +{ + unsigned int i; + + for (i = 0; reg_predefined_keys[i].name; i++) { + if (!strcasecmp(reg_predefined_keys[i].name, name)) + return reg_get_predefined_key(ctx, + reg_predefined_keys[i].handle, + key); + } + + DEBUG(1, ("No predefined key with name '%s'\n", name)); + + return WERR_FILE_NOT_FOUND; +} + +/** Get predefined key by id. */ +_PUBLIC_ WERROR reg_get_predefined_key(struct registry_context *ctx, + uint32_t hkey, struct registry_key **key) +{ + return ctx->ops->get_predefined_key(ctx, hkey, key); +} + +/** + * Open a key + * First tries to use the open_key function from the backend + * then falls back to get_subkey_by_name and later get_subkey_by_index + */ +_PUBLIC_ WERROR reg_open_key(TALLOC_CTX *mem_ctx, struct registry_key *parent, + const char *name, struct registry_key **result) +{ + if (parent == NULL) { + DEBUG(0, ("Invalid parent key specified for open of '%s'\n", + name)); + return WERR_INVALID_PARAMETER; + } + + if (parent->context->ops->open_key == NULL) { + DEBUG(0, ("Registry backend doesn't have open_key!\n")); + return WERR_NOT_SUPPORTED; + } + + return parent->context->ops->open_key(mem_ctx, parent, name, result); +} + +/** + * Get value by index + */ +_PUBLIC_ WERROR reg_key_get_value_by_index(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + uint32_t idx, const char **name, + uint32_t *type, DATA_BLOB *data) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + if (key->context->ops->enum_value == NULL) + return WERR_NOT_SUPPORTED; + + return key->context->ops->enum_value(mem_ctx, key, idx, name, + type, data); +} + +/** + * Get the number of subkeys. + */ +_PUBLIC_ WERROR reg_key_get_info(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char **classname, + uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_change_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + if (key->context->ops->get_key_info == NULL) + return WERR_NOT_SUPPORTED; + + return key->context->ops->get_key_info(mem_ctx, + key, classname, num_subkeys, + num_values, last_change_time, + max_subkeynamelen, + max_valnamelen, max_valbufsize); +} + +/** + * Get subkey by index. + */ +_PUBLIC_ WERROR reg_key_get_subkey_by_index(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + uint32_t idx, const char **name, + const char **keyclass, + NTTIME *last_changed_time) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + if (key->context->ops->enum_key == NULL) + return WERR_NOT_SUPPORTED; + + return key->context->ops->enum_key(mem_ctx, key, idx, name, + keyclass, last_changed_time); +} + +/** + * Get value by name. + */ +_PUBLIC_ WERROR reg_key_get_value_by_name(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char *name, + uint32_t *type, + DATA_BLOB *data) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + if (key->context->ops->get_value == NULL) + return WERR_NOT_SUPPORTED; + + return key->context->ops->get_value(mem_ctx, key, name, type, data); +} + +/** + * Delete a key. + */ +_PUBLIC_ WERROR reg_key_del(TALLOC_CTX *mem_ctx, struct registry_key *parent, + const char *name) +{ + if (parent == NULL) + return WERR_INVALID_PARAMETER; + + if (parent->context->ops->delete_key == NULL) + return WERR_NOT_SUPPORTED; + + return parent->context->ops->delete_key(mem_ctx, parent, name); +} + +/** + * Add a key. + */ +_PUBLIC_ WERROR reg_key_add_name(TALLOC_CTX *mem_ctx, + struct registry_key *parent, + const char *path, const char *key_class, + struct security_descriptor *desc, + struct registry_key **newkey) +{ + if (parent == NULL) + return WERR_INVALID_PARAMETER; + + if (parent->context->ops->create_key == NULL) { + DEBUG(1, ("Backend '%s' doesn't support method add_key\n", + parent->context->ops->name)); + return WERR_NOT_SUPPORTED; + } + + return parent->context->ops->create_key(mem_ctx, parent, path, + key_class, desc, newkey); +} + +/** + * Set a value. + */ +_PUBLIC_ WERROR reg_val_set(struct registry_key *key, const char *value, + uint32_t type, const DATA_BLOB data) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + /* A 'real' set function has preference */ + if (key->context->ops->set_value == NULL) { + DEBUG(1, ("Backend '%s' doesn't support method set_value\n", + key->context->ops->name)); + return WERR_NOT_SUPPORTED; + } + + return key->context->ops->set_value(key, value, type, data); +} + +/** + * Get the security descriptor on a key. + */ +_PUBLIC_ WERROR reg_get_sec_desc(TALLOC_CTX *ctx, + const struct registry_key *key, + struct security_descriptor **secdesc) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + /* A 'real' set function has preference */ + if (key->context->ops->get_sec_desc == NULL) + return WERR_NOT_SUPPORTED; + + return key->context->ops->get_sec_desc(ctx, key, secdesc); +} + +/** + * Delete a value. + */ +_PUBLIC_ WERROR reg_del_value(TALLOC_CTX *mem_ctx, struct registry_key *key, + const char *valname) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + if (key->context->ops->delete_value == NULL) + return WERR_NOT_SUPPORTED; + + return key->context->ops->delete_value(mem_ctx, key, valname); +} + +/** + * Flush a key to disk. + */ +_PUBLIC_ WERROR reg_key_flush(struct registry_key *key) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + if (key->context->ops->flush_key == NULL) + return WERR_NOT_SUPPORTED; + + return key->context->ops->flush_key(key); +} + +_PUBLIC_ WERROR reg_set_sec_desc(struct registry_key *key, + const struct security_descriptor *security) +{ + if (key == NULL) + return WERR_INVALID_PARAMETER; + + if (key->context->ops->set_sec_desc == NULL) + return WERR_NOT_SUPPORTED; + + return key->context->ops->set_sec_desc(key, security); +} diff --git a/source4/lib/registry/ldb.c b/source4/lib/registry/ldb.c new file mode 100644 index 0000000..db383a5 --- /dev/null +++ b/source4/lib/registry/ldb.c @@ -0,0 +1,1018 @@ +/* + Unix SMB/CIFS implementation. + Registry interface + Copyright (C) 2004-2007, Jelmer Vernooij, jelmer@samba.org + Copyright (C) 2008-2010, Matthias Dieter Wallnöfer, mdw@samba.org + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "registry.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "ldb_wrap.h" +#include "librpc/gen_ndr/winreg.h" +#include "param/param.h" +#include "lib/util/smb_strtox.h" + +#undef strcasecmp + +static struct hive_operations reg_backend_ldb; + +struct ldb_key_data +{ + struct hive_key key; + struct ldb_context *ldb; + struct ldb_dn *dn; + struct ldb_message **subkeys, **values; + unsigned int subkey_count, value_count; + const char *classname; +}; + +static void reg_ldb_unpack_value(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + const char **name, uint32_t *type, + DATA_BLOB *data) +{ + const struct ldb_val *val; + uint32_t value_type; + + if (name != NULL) { + *name = talloc_strdup(mem_ctx, + ldb_msg_find_attr_as_string(msg, "value", + "")); + } + + value_type = ldb_msg_find_attr_as_uint(msg, "type", 0); + *type = value_type; + + val = ldb_msg_find_ldb_val(msg, "data"); + + switch (value_type) + { + case REG_SZ: + case REG_EXPAND_SZ: + if (val != NULL) { + /* The data should be provided as UTF16 string */ + convert_string_talloc(mem_ctx, CH_UTF8, CH_UTF16, + val->data, val->length, + (void **)&data->data, &data->length); + } else { + data->data = NULL; + data->length = 0; + } + break; + + case REG_DWORD: + case REG_DWORD_BIG_ENDIAN: + if (val != NULL) { + int error = 0; + /* The data is a plain DWORD */ + uint32_t tmp; + + tmp = smb_strtoul((char *)val->data, + NULL, + 0, + &error, + SMB_STR_STANDARD); + if (error != 0) { + data->data = NULL; + data->length = 0; + break; + } + data->data = talloc_size(mem_ctx, sizeof(uint32_t)); + if (data->data != NULL) { + SIVAL(data->data, 0, tmp); + } + data->length = sizeof(uint32_t); + } else { + data->data = NULL; + data->length = 0; + } + break; + + case REG_QWORD: + if (val != NULL) { + int error = 0; + /* The data is a plain QWORD */ + uint64_t tmp; + + tmp = smb_strtoull((char *)val->data, + NULL, + 0, + &error, + SMB_STR_STANDARD); + if (error != 0) { + data->data = NULL; + data->length = 0; + break; + } + data->data = talloc_size(mem_ctx, sizeof(uint64_t)); + if (data->data != NULL) { + SBVAL(data->data, 0, tmp); + } + data->length = sizeof(uint64_t); + } else { + data->data = NULL; + data->length = 0; + } + break; + + case REG_BINARY: + default: + if (val != NULL) { + data->data = talloc_memdup(mem_ctx, val->data, + val->length); + data->length = val->length; + } else { + data->data = NULL; + data->length = 0; + } + break; + } +} + +static struct ldb_message *reg_ldb_pack_value(struct ldb_context *ctx, + TALLOC_CTX *mem_ctx, + const char *name, + uint32_t type, DATA_BLOB data) +{ + struct ldb_message *msg; + char *name_dup, *type_str; + int ret; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NULL; + } + + name_dup = talloc_strdup(msg, name); + if (name_dup == NULL) { + talloc_free(msg); + return NULL; + } + + ret = ldb_msg_add_string(msg, "value", name_dup); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return NULL; + } + + switch (type) { + case REG_SZ: + case REG_EXPAND_SZ: + if ((data.length > 0) && (data.data != NULL)) { + struct ldb_val *val; + bool ret2 = false; + + val = talloc_zero(msg, struct ldb_val); + if (val == NULL) { + talloc_free(msg); + return NULL; + } + + /* The data is provided as UTF16 string */ + ret2 = convert_string_talloc(mem_ctx, CH_UTF16, CH_UTF8, + (void *)data.data, data.length, + (void **)&val->data, &val->length); + if (ret2) { + ret = ldb_msg_add_value(msg, "data", val, NULL); + } else { + /* workaround for non-standard data */ + ret = ldb_msg_add_empty(msg, "data", LDB_FLAG_MOD_DELETE, NULL); + } + } else { + ret = ldb_msg_add_empty(msg, "data", LDB_FLAG_MOD_DELETE, NULL); + } + break; + + case REG_DWORD: + case REG_DWORD_BIG_ENDIAN: + if ((data.length > 0) && (data.data != NULL)) { + if (data.length == sizeof(uint32_t)) { + char *conv_str; + + conv_str = talloc_asprintf(msg, "0x%8.8x", + IVAL(data.data, 0)); + if (conv_str == NULL) { + talloc_free(msg); + return NULL; + } + ret = ldb_msg_add_string(msg, "data", conv_str); + } else { + /* workaround for non-standard data */ + talloc_free(msg); + return NULL; + } + } else { + ret = ldb_msg_add_empty(msg, "data", LDB_FLAG_MOD_DELETE, NULL); + } + break; + + case REG_QWORD: + if ((data.length > 0) && (data.data != NULL)) { + if (data.length == sizeof(uint64_t)) { + char *conv_str; + + conv_str = talloc_asprintf(msg, "0x%16.16llx", + (unsigned long long)BVAL(data.data, 0)); + if (conv_str == NULL) { + talloc_free(msg); + return NULL; + } + ret = ldb_msg_add_string(msg, "data", conv_str); + } else { + /* workaround for non-standard data */ + talloc_free(msg); + return NULL; + + } + } else { + ret = ldb_msg_add_empty(msg, "data", LDB_FLAG_MOD_DELETE, NULL); + } + break; + + case REG_BINARY: + default: + if ((data.length > 0) && (data.data != NULL)) { + ret = ldb_msg_add_value(msg, "data", &data, NULL); + } else { + ret = ldb_msg_add_empty(msg, "data", LDB_FLAG_MOD_DELETE, NULL); + } + break; + } + + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return NULL; + } + + type_str = talloc_asprintf(mem_ctx, "%u", type); + if (type_str == NULL) { + talloc_free(msg); + return NULL; + } + + ret = ldb_msg_add_string(msg, "type", type_str); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return NULL; + } + + return msg; +} + +static char *reg_ldb_escape(TALLOC_CTX *mem_ctx, const char *value) +{ + struct ldb_val val; + + val.data = discard_const_p(uint8_t, value); + val.length = strlen(value); + + return ldb_dn_escape_value(mem_ctx, val); +} + +static int reg_close_ldb_key(struct ldb_key_data *key) +{ + if (key->subkeys != NULL) { + talloc_free(key->subkeys); + key->subkeys = NULL; + } + + if (key->values != NULL) { + talloc_free(key->values); + key->values = NULL; + } + return 0; +} + +static struct ldb_dn *reg_path_to_ldb(TALLOC_CTX *mem_ctx, + const struct hive_key *from, + const char *path, const char *add) +{ + struct ldb_dn *ret; + char *mypath; + char *begin; + struct ldb_key_data *kd = talloc_get_type(from, struct ldb_key_data); + struct ldb_context *ldb = kd->ldb; + + mypath = talloc_strdup(mem_ctx, path); + if (mypath == NULL) { + return NULL; + } + + ret = ldb_dn_new(mem_ctx, ldb, add); + if (!ldb_dn_validate(ret)) { + talloc_free(ret); + return NULL; + } + + if (!ldb_dn_add_base(ret, kd->dn)) { + talloc_free(ret); + return NULL; + } + + while (mypath[0] != '\0') { + begin = strchr(mypath, '\\'); + if (begin != NULL) { + *begin = '\0'; + } + + if (!ldb_dn_add_child_fmt(ret, "key=%s", + reg_ldb_escape(mem_ctx, mypath))) { + talloc_free(ret); + return NULL; + } + + if (begin != NULL) { + mypath = begin + 1; + } else { + break; + } + } + + return ret; +} + +static WERROR cache_subkeys(struct ldb_key_data *kd) +{ + struct ldb_context *c = kd->ldb; + struct ldb_result *res; + int ret; + + ret = ldb_search(c, c, &res, kd->dn, LDB_SCOPE_ONELEVEL, + NULL, "(key=*)"); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Error getting subkeys for '%s': %s\n", + ldb_dn_get_linearized(kd->dn), ldb_errstring(c))); + return WERR_FOOBAR; + } + + kd->subkey_count = res->count; + kd->subkeys = talloc_steal(kd, res->msgs); + talloc_free(res); + + return WERR_OK; +} + +static WERROR cache_values(struct ldb_key_data *kd) +{ + struct ldb_context *c = kd->ldb; + struct ldb_result *res; + int ret; + + ret = ldb_search(c, c, &res, kd->dn, LDB_SCOPE_ONELEVEL, + NULL, "(value=*)"); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Error getting values for '%s': %s\n", + ldb_dn_get_linearized(kd->dn), ldb_errstring(c))); + return WERR_FOOBAR; + } + + kd->value_count = res->count; + kd->values = talloc_steal(kd, res->msgs); + talloc_free(res); + + return WERR_OK; +} + + +static WERROR ldb_get_subkey_by_id(TALLOC_CTX *mem_ctx, + const struct hive_key *k, uint32_t idx, + const char **name, + const char **classname, + NTTIME *last_mod_time) +{ + struct ldb_key_data *kd = talloc_get_type(k, struct ldb_key_data); + + /* Initialization */ + if (name != NULL) + *name = NULL; + if (classname != NULL) + *classname = NULL; + if (last_mod_time != NULL) + *last_mod_time = 0; /* TODO: we need to add this to the + ldb backend properly */ + + /* Do a search if necessary */ + if (kd->subkeys == NULL) { + W_ERROR_NOT_OK_RETURN(cache_subkeys(kd)); + } + + if (idx >= kd->subkey_count) + return WERR_NO_MORE_ITEMS; + + if (name != NULL) + *name = talloc_strdup(mem_ctx, + ldb_msg_find_attr_as_string(kd->subkeys[idx], "key", NULL)); + if (classname != NULL) + *classname = talloc_strdup(mem_ctx, + ldb_msg_find_attr_as_string(kd->subkeys[idx], "classname", NULL)); + + return WERR_OK; +} + +static WERROR ldb_get_default_value(TALLOC_CTX *mem_ctx, + const struct hive_key *k, + const char **name, uint32_t *data_type, + DATA_BLOB *data) +{ + struct ldb_key_data *kd = talloc_get_type(k, struct ldb_key_data); + struct ldb_context *c = kd->ldb; + const char* attrs[] = { "data", "type", NULL }; + struct ldb_result *res; + int ret; + + ret = ldb_search(c, mem_ctx, &res, kd->dn, LDB_SCOPE_BASE, attrs, + NULL); + + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Error getting default value for '%s': %s\n", + ldb_dn_get_linearized(kd->dn), ldb_errstring(c))); + return WERR_FOOBAR; + } + + if (res->count == 0 || res->msgs[0]->num_elements == 0) { + talloc_free(res); + return WERR_FILE_NOT_FOUND; + } + + if ((data_type != NULL) && (data != NULL)) { + reg_ldb_unpack_value(mem_ctx, res->msgs[0], name, data_type, + data); + } + + talloc_free(res); + + return WERR_OK; +} + +static WERROR ldb_get_value_by_id(TALLOC_CTX *mem_ctx, struct hive_key *k, + uint32_t idx, const char **name, + uint32_t *data_type, DATA_BLOB *data) +{ + struct ldb_key_data *kd = talloc_get_type(k, struct ldb_key_data); + + /* if the default value exists, give it back */ + if (W_ERROR_IS_OK(ldb_get_default_value(mem_ctx, k, name, data_type, + data))) { + if (idx == 0) + return WERR_OK; + else + --idx; + } + + /* Do the search if necessary */ + if (kd->values == NULL) { + W_ERROR_NOT_OK_RETURN(cache_values(kd)); + } + + if (idx >= kd->value_count) + return WERR_NO_MORE_ITEMS; + + reg_ldb_unpack_value(mem_ctx, kd->values[idx], name, data_type, data); + + return WERR_OK; +} + +static WERROR ldb_get_value(TALLOC_CTX *mem_ctx, struct hive_key *k, + const char *name, uint32_t *data_type, + DATA_BLOB *data) +{ + struct ldb_key_data *kd = talloc_get_type(k, struct ldb_key_data); + const char *res_name; + uint32_t idx; + + /* the default value was requested, give it back */ + if (name[0] == '\0') { + return ldb_get_default_value(mem_ctx, k, NULL, data_type, data); + } + + /* Do the search if necessary */ + if (kd->values == NULL) { + W_ERROR_NOT_OK_RETURN(cache_values(kd)); + } + + for (idx = 0; idx < kd->value_count; idx++) { + res_name = ldb_msg_find_attr_as_string(kd->values[idx], "value", + ""); + if (ldb_attr_cmp(name, res_name) == 0) { + reg_ldb_unpack_value(mem_ctx, kd->values[idx], NULL, + data_type, data); + return WERR_OK; + } + } + + return WERR_FILE_NOT_FOUND; +} + +static WERROR ldb_open_key(TALLOC_CTX *mem_ctx, const struct hive_key *h, + const char *name, struct hive_key **key) +{ + struct ldb_result *res; + struct ldb_dn *ldb_path; + int ret; + struct ldb_key_data *newkd; + struct ldb_key_data *kd = talloc_get_type(h, struct ldb_key_data); + struct ldb_context *c = kd->ldb; + + ldb_path = reg_path_to_ldb(mem_ctx, h, name, NULL); + W_ERROR_HAVE_NO_MEMORY(ldb_path); + + ret = ldb_search(c, mem_ctx, &res, ldb_path, LDB_SCOPE_BASE, NULL, + NULL); + + if (ret != LDB_SUCCESS) { + DEBUG(3, ("Error opening key '%s': %s\n", + ldb_dn_get_linearized(ldb_path), ldb_errstring(c))); + return WERR_FOOBAR; + } else if (res->count == 0) { + DEBUG(3, ("Key '%s' not found\n", + ldb_dn_get_linearized(ldb_path))); + talloc_free(res); + return WERR_FILE_NOT_FOUND; + } + + newkd = talloc_zero(mem_ctx, struct ldb_key_data); + W_ERROR_HAVE_NO_MEMORY(newkd); + newkd->key.ops = ®_backend_ldb; + newkd->ldb = talloc_reference(newkd, kd->ldb); + newkd->dn = ldb_dn_copy(newkd, res->msgs[0]->dn); + newkd->classname = talloc_steal(newkd, + ldb_msg_find_attr_as_string(res->msgs[0], "classname", NULL)); + + talloc_free(res); + + *key = (struct hive_key *)newkd; + + return WERR_OK; +} + +WERROR reg_open_ldb_file(TALLOC_CTX *parent_ctx, const char *location, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct hive_key **k) +{ + struct ldb_key_data *kd; + struct ldb_context *wrap; + struct ldb_message *attrs_msg; + + if (location == NULL) + return WERR_INVALID_PARAMETER; + + wrap = ldb_wrap_connect(parent_ctx, ev_ctx, lp_ctx, + location, session_info, credentials, 0); + + if (wrap == NULL) { + DEBUG(1, (__FILE__": unable to connect\n")); + return WERR_FOOBAR; + } + + attrs_msg = ldb_msg_new(wrap); + W_ERROR_HAVE_NO_MEMORY(attrs_msg); + attrs_msg->dn = ldb_dn_new(attrs_msg, wrap, "@ATTRIBUTES"); + W_ERROR_HAVE_NO_MEMORY(attrs_msg->dn); + ldb_msg_add_string(attrs_msg, "key", "CASE_INSENSITIVE"); + ldb_msg_add_string(attrs_msg, "value", "CASE_INSENSITIVE"); + + ldb_add(wrap, attrs_msg); + + ldb_set_debug_stderr(wrap); + + kd = talloc_zero(parent_ctx, struct ldb_key_data); + kd->key.ops = ®_backend_ldb; + kd->ldb = talloc_reference(kd, wrap); + talloc_set_destructor (kd, reg_close_ldb_key); + kd->dn = ldb_dn_new(kd, wrap, "hive=NONE"); + + *k = (struct hive_key *)kd; + + return WERR_OK; +} + +static WERROR ldb_add_key(TALLOC_CTX *mem_ctx, const struct hive_key *parent, + const char *name, const char *classname, + struct security_descriptor *sd, + struct hive_key **newkey) +{ + struct ldb_key_data *parentkd = discard_const_p(struct ldb_key_data, parent); + struct ldb_dn *ldb_path; + struct ldb_message *msg; + struct ldb_key_data *newkd; + int ret; + + ldb_path = reg_path_to_ldb(mem_ctx, parent, name, NULL); + W_ERROR_HAVE_NO_MEMORY(ldb_path); + + msg = ldb_msg_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(msg); + + msg->dn = ldb_path; + + ldb_msg_add_string(msg, "key", name); + if (classname != NULL) { + ldb_msg_add_string(msg, "classname", classname); + } + + ret = ldb_add(parentkd->ldb, msg); + + talloc_free(msg); + + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + return WERR_ALREADY_EXISTS; + } + + if (ret != LDB_SUCCESS) { + DEBUG(1, ("ldb_add: %s\n", ldb_errstring(parentkd->ldb))); + return WERR_FOOBAR; + } + + DEBUG(2, ("key added: %s\n", ldb_dn_get_linearized(ldb_path))); + + newkd = talloc_zero(mem_ctx, struct ldb_key_data); + W_ERROR_HAVE_NO_MEMORY(newkd); + newkd->ldb = talloc_reference(newkd, parentkd->ldb); + newkd->key.ops = ®_backend_ldb; + newkd->dn = talloc_steal(newkd, ldb_path); + newkd->classname = talloc_steal(newkd, classname); + + *newkey = (struct hive_key *)newkd; + + /* reset cache */ + talloc_free(parentkd->subkeys); + parentkd->subkeys = NULL; + + return WERR_OK; +} + +static WERROR ldb_del_value(TALLOC_CTX *mem_ctx, struct hive_key *key, + const char *child) +{ + int ret; + struct ldb_key_data *kd = talloc_get_type(key, struct ldb_key_data); + struct ldb_message *msg; + struct ldb_dn *childdn; + + if (child[0] == '\0') { + /* default value */ + msg = ldb_msg_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(msg); + msg->dn = ldb_dn_copy(msg, kd->dn); + W_ERROR_HAVE_NO_MEMORY(msg->dn); + ret = ldb_msg_add_empty(msg, "data", LDB_FLAG_MOD_DELETE, NULL); + if (ret != LDB_SUCCESS) { + return WERR_FOOBAR; + } + ret = ldb_msg_add_empty(msg, "type", LDB_FLAG_MOD_DELETE, + NULL); + if (ret != LDB_SUCCESS) { + return WERR_FOOBAR; + } + + ret = ldb_modify(kd->ldb, msg); + + talloc_free(msg); + + if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) { + return WERR_FILE_NOT_FOUND; + } else if (ret != LDB_SUCCESS) { + DEBUG(1, ("ldb_del_value: %s\n", ldb_errstring(kd->ldb))); + return WERR_FOOBAR; + } + } else { + /* normal value */ + childdn = ldb_dn_copy(kd->ldb, kd->dn); + if (!ldb_dn_add_child_fmt(childdn, "value=%s", + reg_ldb_escape(childdn, child))) + { + talloc_free(childdn); + return WERR_FOOBAR; + } + + ret = ldb_delete(kd->ldb, childdn); + + talloc_free(childdn); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + return WERR_FILE_NOT_FOUND; + } else if (ret != LDB_SUCCESS) { + DEBUG(1, ("ldb_del_value: %s\n", ldb_errstring(kd->ldb))); + return WERR_FOOBAR; + } + } + + /* reset cache */ + talloc_free(kd->values); + kd->values = NULL; + + return WERR_OK; +} + +static WERROR ldb_del_key(TALLOC_CTX *mem_ctx, const struct hive_key *key, + const char *name) +{ + unsigned int i; + int ret; + struct ldb_key_data *parentkd = talloc_get_type(key, struct ldb_key_data); + struct ldb_dn *ldb_path; + struct ldb_context *c = parentkd->ldb; + struct ldb_result *res_keys; + struct ldb_result *res_vals; + WERROR werr; + struct hive_key *hk; + + /* Verify key exists by opening it */ + werr = ldb_open_key(mem_ctx, key, name, &hk); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + ldb_path = reg_path_to_ldb(mem_ctx, key, name, NULL); + W_ERROR_HAVE_NO_MEMORY(ldb_path); + + /* Search for subkeys */ + ret = ldb_search(c, mem_ctx, &res_keys, ldb_path, LDB_SCOPE_ONELEVEL, + NULL, "(key=*)"); + + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Error getting subkeys for '%s': %s\n", + ldb_dn_get_linearized(ldb_path), ldb_errstring(c))); + return WERR_FOOBAR; + } + + /* Search for values */ + ret = ldb_search(c, mem_ctx, &res_vals, ldb_path, LDB_SCOPE_ONELEVEL, + NULL, "(value=*)"); + + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Error getting values for '%s': %s\n", + ldb_dn_get_linearized(ldb_path), ldb_errstring(c))); + return WERR_FOOBAR; + } + + /* Start an explicit transaction */ + ret = ldb_transaction_start(c); + + if (ret != LDB_SUCCESS) { + DEBUG(0, ("ldb_transaction_start: %s\n", ldb_errstring(c))); + return WERR_FOOBAR; + } + + if (res_keys->count || res_vals->count) + { + /* Delete any subkeys */ + for (i = 0; i < res_keys->count; i++) + { + werr = ldb_del_key(mem_ctx, hk, + ldb_msg_find_attr_as_string( + res_keys->msgs[i], + "key", NULL)); + if (!W_ERROR_IS_OK(werr)) { + ret = ldb_transaction_cancel(c); + return werr; + } + } + + /* Delete any values */ + for (i = 0; i < res_vals->count; i++) + { + werr = ldb_del_value(mem_ctx, hk, + ldb_msg_find_attr_as_string( + res_vals->msgs[i], + "value", NULL)); + if (!W_ERROR_IS_OK(werr)) { + ret = ldb_transaction_cancel(c); + return werr; + } + } + } + talloc_free(res_keys); + talloc_free(res_vals); + + /* Delete the key itself */ + ret = ldb_delete(c, ldb_path); + + if (ret != LDB_SUCCESS) + { + DEBUG(1, ("ldb_del_key: %s\n", ldb_errstring(c))); + ret = ldb_transaction_cancel(c); + return WERR_FOOBAR; + } + + /* Commit the transaction */ + ret = ldb_transaction_commit(c); + + if (ret != LDB_SUCCESS) + { + DEBUG(0, ("ldb_transaction_commit: %s\n", ldb_errstring(c))); + ret = ldb_transaction_cancel(c); + return WERR_FOOBAR; + } + + /* reset cache */ + talloc_free(parentkd->subkeys); + parentkd->subkeys = NULL; + + return WERR_OK; +} + +static WERROR ldb_set_value(struct hive_key *parent, + const char *name, uint32_t type, + const DATA_BLOB data) +{ + struct ldb_message *msg; + struct ldb_key_data *kd = talloc_get_type(parent, struct ldb_key_data); + unsigned int i; + int ret; + TALLOC_CTX *mem_ctx = talloc_init("ldb_set_value"); + + msg = reg_ldb_pack_value(kd->ldb, mem_ctx, name, type, data); + W_ERROR_HAVE_NO_MEMORY(msg); + + msg->dn = ldb_dn_copy(msg, kd->dn); + W_ERROR_HAVE_NO_MEMORY(msg->dn); + + if (name[0] != '\0') { + /* For a default value, we add/overwrite the attributes to/of the hive. + For a normal value, we create a new child. */ + if (!ldb_dn_add_child_fmt(msg->dn, "value=%s", + reg_ldb_escape(mem_ctx, name))) + { + talloc_free(mem_ctx); + return WERR_FOOBAR; + } + } + + /* Try first a "modify" and if this doesn't work do try an "add" */ + for (i = 0; i < msg->num_elements; i++) { + if (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) != LDB_FLAG_MOD_DELETE) { + msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + } + ret = ldb_modify(kd->ldb, msg); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + i = 0; + while (i < msg->num_elements) { + if (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) == LDB_FLAG_MOD_DELETE) { + ldb_msg_remove_element(msg, &msg->elements[i]); + } else { + ++i; + } + } + ret = ldb_add(kd->ldb, msg); + } + if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) { + /* ignore this -> the value didn't exist and also now doesn't */ + ret = LDB_SUCCESS; + } + + talloc_free(msg); + + if (ret != LDB_SUCCESS) { + DEBUG(1, ("ldb_set_value: %s\n", ldb_errstring(kd->ldb))); + talloc_free(mem_ctx); + return WERR_FOOBAR; + } + + /* reset cache */ + talloc_free(kd->values); + kd->values = NULL; + + talloc_free(mem_ctx); + return WERR_OK; +} + +static WERROR ldb_get_key_info(TALLOC_CTX *mem_ctx, + const struct hive_key *key, + const char **classname, + uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_change_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize) +{ + struct ldb_key_data *kd = talloc_get_type(key, struct ldb_key_data); + uint32_t default_value_type = REG_NONE; + DATA_BLOB default_value = { NULL, 0 }; + WERROR werr; + + /* Initialization */ + if (classname != NULL) + *classname = NULL; + if (num_subkeys != NULL) + *num_subkeys = 0; + if (num_values != NULL) + *num_values = 0; + if (last_change_time != NULL) + *last_change_time = 0; + if (max_subkeynamelen != NULL) + *max_subkeynamelen = 0; + if (max_valnamelen != NULL) + *max_valnamelen = 0; + if (max_valbufsize != NULL) + *max_valbufsize = 0; + + /* We need this to get the default value (if it exists) for counting + * the values under the key and for finding out the longest value buffer + * size. If no default value exists the DATA_BLOB "default_value" will + * remain { NULL, 0 }. */ + werr = ldb_get_default_value(mem_ctx, key, NULL, &default_value_type, + &default_value); + if ((!W_ERROR_IS_OK(werr)) && (!W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND))) { + return werr; + } + + if (kd->subkeys == NULL) { + W_ERROR_NOT_OK_RETURN(cache_subkeys(kd)); + } + if (kd->values == NULL) { + W_ERROR_NOT_OK_RETURN(cache_values(kd)); + } + + if (classname != NULL) { + *classname = kd->classname; + } + + if (num_subkeys != NULL) { + *num_subkeys = kd->subkey_count; + } + if (num_values != NULL) { + *num_values = kd->value_count; + /* also consider the default value if it exists */ + if (default_value.data != NULL) { + ++(*num_values); + } + } + + + if (max_subkeynamelen != NULL) { + unsigned int i; + struct ldb_message_element *el; + + for (i = 0; i < kd->subkey_count; i++) { + el = ldb_msg_find_element(kd->subkeys[i], "key"); + *max_subkeynamelen = MAX(*max_subkeynamelen, el->values[0].length); + } + } + + if (max_valnamelen != NULL || max_valbufsize != NULL) { + unsigned int i; + struct ldb_message_element *el; + W_ERROR_NOT_OK_RETURN(cache_values(kd)); + + /* also consider the default value if it exists */ + if ((max_valbufsize != NULL) && (default_value.data != NULL)) { + *max_valbufsize = MAX(*max_valbufsize, + default_value.length); + } + + for (i = 0; i < kd->value_count; i++) { + if (max_valnamelen != NULL) { + el = ldb_msg_find_element(kd->values[i], "value"); + *max_valnamelen = MAX(*max_valnamelen, el->values[0].length); + } + + if (max_valbufsize != NULL) { + uint32_t data_type; + DATA_BLOB data; + reg_ldb_unpack_value(mem_ctx, + kd->values[i], NULL, + &data_type, &data); + *max_valbufsize = MAX(*max_valbufsize, data.length); + talloc_free(data.data); + } + } + } + + talloc_free(default_value.data); + + return WERR_OK; +} + +static struct hive_operations reg_backend_ldb = { + .name = "ldb", + .add_key = ldb_add_key, + .del_key = ldb_del_key, + .get_key_by_name = ldb_open_key, + .enum_value = ldb_get_value_by_id, + .enum_key = ldb_get_subkey_by_id, + .set_value = ldb_set_value, + .get_value_by_name = ldb_get_value, + .delete_value = ldb_del_value, + .get_key_info = ldb_get_key_info, +}; diff --git a/source4/lib/registry/local.c b/source4/lib/registry/local.c new file mode 100644 index 0000000..4b00953 --- /dev/null +++ b/source4/lib/registry/local.c @@ -0,0 +1,408 @@ +/* + Unix SMB/CIFS implementation. + Transparent registry backend handling + Copyright (C) Jelmer Vernooij 2003-2007. + + 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 3 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 "../lib/util/dlinklist.h" +#include "lib/registry/registry.h" +#include "system/filesys.h" + +struct reg_key_path { + uint32_t predefined_key; + const char **elements; +}; + +struct registry_local { + const struct registry_operations *ops; + + struct mountpoint { + struct reg_key_path path; + struct hive_key *key; + struct mountpoint *prev, *next; + } *mountpoints; +}; + +struct local_key { + struct registry_key global; + struct reg_key_path path; + struct hive_key *hive_key; +}; + + +struct registry_key *reg_import_hive_key(struct registry_context *ctx, + struct hive_key *hive, + uint32_t predefined_key, + const char **elements) +{ + struct local_key *local_key; + struct reg_key_path parent_path; + + parent_path.predefined_key = predefined_key; + parent_path.elements = elements; + + local_key = talloc(ctx, struct local_key); + if (local_key != NULL) { + local_key->hive_key = talloc_reference(local_key, hive); + local_key->global.context = talloc_reference(local_key, ctx); + local_key->path = parent_path; + } + + return (struct registry_key *)local_key; +} + + +static WERROR local_open_key(TALLOC_CTX *mem_ctx, + struct registry_key *parent, + const char *path, + struct registry_key **result) +{ + char *orig, *curbegin, *curend; + struct local_key *local_parent = talloc_get_type(parent, + struct local_key); + struct hive_key *curkey = local_parent->hive_key; + WERROR error; + const char **elements = NULL; + int el; + + if (path == NULL || path[0] == '\0') { + return WERR_INVALID_PARAMETER; + } + + orig = talloc_strdup(mem_ctx, path); + W_ERROR_HAVE_NO_MEMORY(orig); + curbegin = orig; + curend = strchr(orig, '\\'); + + if (local_parent->path.elements != NULL) { + elements = talloc_array(mem_ctx, const char *, + str_list_length(local_parent->path.elements) + 1); + W_ERROR_HAVE_NO_MEMORY(elements); + for (el = 0; local_parent->path.elements[el] != NULL; el++) { + elements[el] = talloc_reference(elements, + local_parent->path.elements[el]); + } + elements[el] = NULL; + } else { + elements = NULL; + el = 0; + } + + do { + if (curend != NULL) + *curend = '\0'; + elements = talloc_realloc(mem_ctx, elements, const char *, el+2); + W_ERROR_HAVE_NO_MEMORY(elements); + elements[el] = talloc_strdup(elements, curbegin); + W_ERROR_HAVE_NO_MEMORY(elements[el]); + el++; + elements[el] = NULL; + error = hive_get_key_by_name(mem_ctx, curkey, + curbegin, &curkey); + if (!W_ERROR_IS_OK(error)) { + DEBUG(2, ("Opening key %s failed: %s\n", curbegin, + win_errstr(error))); + talloc_free(orig); + return error; + } + if (curend == NULL) + break; + curbegin = curend + 1; + curend = strchr(curbegin, '\\'); + } while (curbegin[0] != '\0'); + talloc_free(orig); + + *result = reg_import_hive_key(local_parent->global.context, curkey, + local_parent->path.predefined_key, + talloc_steal(curkey, elements)); + + return WERR_OK; +} + +WERROR local_get_predefined_key(struct registry_context *ctx, + uint32_t key_id, struct registry_key **key) +{ + struct registry_local *rctx = talloc_get_type(ctx, + struct registry_local); + struct mountpoint *mp; + + for (mp = rctx->mountpoints; mp != NULL; mp = mp->next) { + if (mp->path.predefined_key == key_id && + mp->path.elements == NULL) + break; + } + + if (mp == NULL) + return WERR_FILE_NOT_FOUND; + + *key = reg_import_hive_key(ctx, mp->key, + mp->path.predefined_key, + mp->path.elements); + + return WERR_OK; +} + +static WERROR local_enum_key(TALLOC_CTX *mem_ctx, + const struct registry_key *key, uint32_t idx, + const char **name, + const char **keyclass, + NTTIME *last_changed_time) +{ + const struct local_key *local = (const struct local_key *)key; + + return hive_enum_key(mem_ctx, local->hive_key, idx, name, keyclass, + last_changed_time); +} + +static WERROR local_create_key(TALLOC_CTX *mem_ctx, + struct registry_key *parent, + const char *path, + const char *key_class, + struct security_descriptor *security, + struct registry_key **result) +{ + char *orig, *curbegin, *curend; + struct local_key *local_parent = talloc_get_type(parent, + struct local_key); + struct hive_key *curkey = local_parent->hive_key; + WERROR error; + const char **elements = NULL; + int el; + + if (path == NULL || path[0] == '\0') { + return WERR_INVALID_PARAMETER; + } + + orig = talloc_strdup(mem_ctx, path); + W_ERROR_HAVE_NO_MEMORY(orig); + curbegin = orig; + curend = strchr(orig, '\\'); + + if (local_parent->path.elements != NULL) { + elements = talloc_array(mem_ctx, const char *, + str_list_length(local_parent->path.elements) + 1); + W_ERROR_HAVE_NO_MEMORY(elements); + for (el = 0; local_parent->path.elements[el] != NULL; el++) { + elements[el] = talloc_reference(elements, + local_parent->path.elements[el]); + } + elements[el] = NULL; + } else { + elements = NULL; + el = 0; + } + + do { + if (curend != NULL) + *curend = '\0'; + elements = talloc_realloc(mem_ctx, elements, const char *, el+2); + W_ERROR_HAVE_NO_MEMORY(elements); + elements[el] = talloc_strdup(elements, curbegin); + W_ERROR_HAVE_NO_MEMORY(elements[el]); + el++; + elements[el] = NULL; + error = hive_get_key_by_name(mem_ctx, curkey, + curbegin, &curkey); + if (W_ERROR_EQUAL(error, WERR_FILE_NOT_FOUND)) { + error = hive_key_add_name(mem_ctx, curkey, curbegin, + key_class, security, + &curkey); + } + if (!W_ERROR_IS_OK(error)) { + DEBUG(2, ("Open/Creation of key %s failed: %s\n", + curbegin, win_errstr(error))); + talloc_free(orig); + return error; + } + if (curend == NULL) + break; + curbegin = curend + 1; + curend = strchr(curbegin, '\\'); + } while (curbegin[0] != '\0'); + talloc_free(orig); + + *result = reg_import_hive_key(local_parent->global.context, curkey, + local_parent->path.predefined_key, + talloc_steal(curkey, elements)); + + return WERR_OK; +} + +static WERROR local_set_value(struct registry_key *key, const char *name, + uint32_t type, const DATA_BLOB data) +{ + struct local_key *local = (struct local_key *)key; + + if (name == NULL) { + return WERR_INVALID_PARAMETER; + } + + return hive_key_set_value(local->hive_key, name, type, data); +} + +static WERROR local_get_value(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char *name, uint32_t *type, DATA_BLOB *data) +{ + const struct local_key *local = (const struct local_key *)key; + + if (name == NULL) { + return WERR_INVALID_PARAMETER; + } + + return hive_get_value(mem_ctx, local->hive_key, name, type, data); +} + +static WERROR local_enum_value(TALLOC_CTX *mem_ctx, + const struct registry_key *key, uint32_t idx, + const char **name, + uint32_t *type, + DATA_BLOB *data) +{ + const struct local_key *local = (const struct local_key *)key; + + return hive_get_value_by_index(mem_ctx, local->hive_key, idx, + name, type, data); +} + +static WERROR local_delete_key(TALLOC_CTX *mem_ctx, struct registry_key *key, + const char *name) +{ + const struct local_key *local = (const struct local_key *)key; + + if (name == NULL) { + return WERR_INVALID_PARAMETER; + } + + return hive_key_del(mem_ctx, local->hive_key, name); +} + +static WERROR local_delete_value(TALLOC_CTX *mem_ctx, struct registry_key *key, + const char *name) +{ + const struct local_key *local = (const struct local_key *)key; + + if (name == NULL) { + return WERR_INVALID_PARAMETER; + } + + return hive_key_del_value(mem_ctx, local->hive_key, name); +} + +static WERROR local_flush_key(struct registry_key *key) +{ + const struct local_key *local = (const struct local_key *)key; + + return hive_key_flush(local->hive_key); +} + +static WERROR local_get_key_info(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char **classname, + uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_change_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize) +{ + const struct local_key *local = (const struct local_key *)key; + + return hive_key_get_info(mem_ctx, local->hive_key, + classname, num_subkeys, num_values, + last_change_time, max_subkeynamelen, + max_valnamelen, max_valbufsize); +} +static WERROR local_get_sec_desc(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + struct security_descriptor **security) +{ + const struct local_key *local = (const struct local_key *)key; + + return hive_get_sec_desc(mem_ctx, local->hive_key, security); +} +static WERROR local_set_sec_desc(struct registry_key *key, + const struct security_descriptor *security) +{ + const struct local_key *local = (const struct local_key *)key; + + return hive_set_sec_desc(local->hive_key, security); +} +const static struct registry_operations local_ops = { + .name = "local", + .open_key = local_open_key, + .get_predefined_key = local_get_predefined_key, + .enum_key = local_enum_key, + .create_key = local_create_key, + .set_value = local_set_value, + .get_value = local_get_value, + .enum_value = local_enum_value, + .delete_key = local_delete_key, + .delete_value = local_delete_value, + .flush_key = local_flush_key, + .get_key_info = local_get_key_info, + .get_sec_desc = local_get_sec_desc, + .set_sec_desc = local_set_sec_desc, +}; + +WERROR reg_open_local(TALLOC_CTX *mem_ctx, struct registry_context **ctx) +{ + struct registry_local *ret = talloc_zero(mem_ctx, + struct registry_local); + + W_ERROR_HAVE_NO_MEMORY(ret); + + ret->ops = &local_ops; + + *ctx = (struct registry_context *)ret; + + return WERR_OK; +} + +WERROR reg_mount_hive(struct registry_context *rctx, + struct hive_key *hive_key, + uint32_t key_id, + const char **elements) +{ + struct registry_local *reg_local = talloc_get_type(rctx, + struct registry_local); + struct mountpoint *mp; + unsigned int i = 0; + + mp = talloc(rctx, struct mountpoint); + W_ERROR_HAVE_NO_MEMORY(mp); + mp->path.predefined_key = key_id; + mp->prev = mp->next = NULL; + mp->key = hive_key; + if (elements != NULL && elements[0] != NULL) { + mp->path.elements = talloc_array(mp, const char *, + str_list_length(elements)); + W_ERROR_HAVE_NO_MEMORY(mp->path.elements); + for (i = 0; elements[i] != NULL; i++) { + mp->path.elements[i] = talloc_reference(mp->path.elements, + elements[i]); + } + mp->path.elements[i] = NULL; + } else { + mp->path.elements = NULL; + } + + DLIST_ADD(reg_local->mountpoints, mp); + + return WERR_OK; +} diff --git a/source4/lib/registry/man/regdiff.1.xml b/source4/lib/registry/man/regdiff.1.xml new file mode 100644 index 0000000..23aae34 --- /dev/null +++ b/source4/lib/registry/man/regdiff.1.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<refentry id="regdiff.1"> + +<refmeta> + <refentrytitle>regdiff</refentrytitle> + <manvolnum>1</manvolnum> + <refmiscinfo class="source">Samba</refmiscinfo> + <refmiscinfo class="manual">System Administration tools</refmiscinfo> + <refmiscinfo class="version">4.0</refmiscinfo> +</refmeta> + + +<refnamediv> + <refname>regdiff</refname> + <refpurpose>Diff program for Windows registry files</refpurpose> +</refnamediv> + +<refsynopsisdiv> + <cmdsynopsis> + <command>regdiff</command> + <arg choice="opt">--help</arg> + <arg choice="opt">--backend=BACKEND</arg> + <arg choice="opt">--credentials=CREDENTIALS</arg> + <arg choice="opt">location</arg> + </cmdsynopsis> +</refsynopsisdiv> + +<refsect1> + <title>DESCRIPTION</title> + + <para>regdiff compares two Windows registry files key by key + and value by value and generates a text file that contains the + differences between the two files.</para> + + <para>A file generated by regdiff can later be applied to a + registry file by the regpatch utility. </para> + + <para>regdiff and regpatch use the same file format as + the regedit32.exe utility from Windows.</para> + +</refsect1> + +<refsect1> + <title>OPTIONS</title> + + <variablelist> + <varlistentry> + <term>--help</term> + <listitem><para> + Show list of available options.</para></listitem> + </varlistentry> + + <varlistentry> + <term>--backend BACKEND</term> + <listitem><para>Name of backend to load. Possible values are: + creg, regf, dir and rpc. The default is <emphasis>dir</emphasis>. + </para> + <para> + This argument can be specified twice: once for the first + registry file and once for the second. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>--credentials=CREDENTIALS</term> + <listitem><para> + Credentials to use, if any. Password should be separated from user name by a percent sign. + </para> + <para> + This argument can be specified twice: once for the first + registry file and once for the second. + </para></listitem> + </varlistentry> + </variablelist> +</refsect1> + +<refsect1> + <title>VERSION</title> + + <para>This man page is correct for version 4.0 of the Samba suite.</para> +</refsect1> + +<refsect1> + <title>SEE ALSO</title> + + <para>gregedit, regshell, regpatch, regtree, samba, patch, diff</para> + +</refsect1> + +<refsect1> + <title>AUTHOR</title> + + <para>This utility is part of the <ulink url="http://www.samba.org/">Samba</ulink> suite, which is developed by the global <ulink url="http://www.samba.org/samba/team/">Samba Team</ulink>.</para> + + <para>This manpage and regdiff were written by Jelmer Vernooij. </para> + +</refsect1> + +</refentry> diff --git a/source4/lib/registry/man/regpatch.1.xml b/source4/lib/registry/man/regpatch.1.xml new file mode 100644 index 0000000..3a15082 --- /dev/null +++ b/source4/lib/registry/man/regpatch.1.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<refentry id="regpatch.1"> + +<refmeta> + <refentrytitle>regpatch</refentrytitle> + <manvolnum>1</manvolnum> + <refmiscinfo class="source">Samba</refmiscinfo> + <refmiscinfo class="manual">System Administration tools</refmiscinfo> + <refmiscinfo class="version">4.0</refmiscinfo> +</refmeta> + + +<refnamediv> + <refname>regpatch</refname> + <refpurpose>Applies registry patches to registry files</refpurpose> +</refnamediv> + +<refsynopsisdiv> + <cmdsynopsis> + <command>regpatch</command> + <arg choice="opt">--help</arg> + <arg choice="opt">--backend=BACKEND</arg> + <arg choice="opt">--credentials=CREDENTIALS</arg> + <arg choice="opt">location</arg> + <arg choice="opt">patch-file</arg> + </cmdsynopsis> +</refsynopsisdiv> + +<refsect1> + <title>DESCRIPTION</title> + + <para>The regpatch utility applies registry patches to Windows registry + files. The patch files should have the same format as is being used + by the regdiff utility and regedit32.exe from Windows.</para> + + <para>If no patch file is specified on the command line, + regpatch attempts to read it from standard input.</para> +</refsect1> + + +<refsect1> + <title>OPTIONS</title> + + <variablelist> + <varlistentry> + <term>--help</term> + <listitem><para> + Show list of available options.</para></listitem> + </varlistentry> + + <varlistentry> + <term>--backend BACKEND</term> + <listitem><para>Name of backend to load. Possible values are: + creg, regf, dir and rpc. The default is <emphasis>dir</emphasis>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>--credentials=CREDENTIALS</term> + <listitem><para> + Credentials to use, if any. Password should be separated from user name by a percent sign.</para></listitem> + </varlistentry> + </variablelist> +</refsect1> + +<refsect1> + <title>VERSION</title> + + <para>This man page is correct for version 4.0 of the Samba suite.</para> +</refsect1> + +<refsect1> + <title>SEE ALSO</title> + + <para>regdiff, regtree, regshell, gregedit, samba, diff, patch</para> + +</refsect1> + +<refsect1> + <title>AUTHOR</title> + + <para>This utility is part of the <ulink url="http://www.samba.org/">Samba</ulink> suite, which is developed by the global <ulink url="http://www.samba.org/samba/team/">Samba Team</ulink>.</para> + + <para>This manpage and regpatch were written by Jelmer Vernooij. </para> + +</refsect1> + +</refentry> diff --git a/source4/lib/registry/man/regshell.1.xml b/source4/lib/registry/man/regshell.1.xml new file mode 100644 index 0000000..4653fbb --- /dev/null +++ b/source4/lib/registry/man/regshell.1.xml @@ -0,0 +1,189 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<refentry id="regshell.1"> + +<refmeta> + <refentrytitle>regshell</refentrytitle> + <manvolnum>1</manvolnum> + <refmiscinfo class="source">Samba</refmiscinfo> + <refmiscinfo class="manual">System Administration tools</refmiscinfo> + <refmiscinfo class="version">4.0</refmiscinfo> +</refmeta> + + +<refnamediv> + <refname>regshell</refname> + <refpurpose>Windows registry file browser using readline</refpurpose> +</refnamediv> + +<refsynopsisdiv> + <cmdsynopsis> + <command>regshell</command> + <arg choice="opt">--help</arg> + <arg choice="opt">--backend=BACKEND</arg> + <arg choice="opt">--credentials=CREDENTIALS</arg> + <arg choice="opt">location</arg> + </cmdsynopsis> +</refsynopsisdiv> + +<refsect1> + <title>DESCRIPTION</title> + + <para> + regshell is a utility that lets you browse thru a Windows registry + file as if you were using a regular unix shell to browse thru a + file system. + </para> + +</refsect1> + + +<refsect1> + <title>OPTIONS</title> + + <variablelist> + <varlistentry> + <term>--help</term> + <listitem><para> + Show list of available options.</para></listitem> + </varlistentry> + + <varlistentry> + <term>--backend BACKEND</term> + <listitem><para>Name of backend to load. Possible values are: + creg, regf, dir and rpc. The default is <emphasis>dir</emphasis>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>--credentials=CREDENTIALS</term> + <listitem><para> + Credentials to use, if any. Password should be separated from user name by a percent sign.</para></listitem> + </varlistentry> + </variablelist> +</refsect1> + +<refsect1> + <title>COMMANDS</title> + + <variablelist> + <varlistentry> + <term>ck|cd <keyname></term> + <listitem><para> + Go to the specified subkey. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>ch|predef [predefined-key-name]</term> + <listitem><para> + Go to the specified predefined key. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>list|ls</term> + <listitem><para> + List subkeys and values of the current key. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>mkkey|mkdir <keyname></term> + <listitem><para> + Create a key with the specified <replaceable>keyname</replaceable> as a subkey of the current key. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>rmval|rm <valname></term> + <listitem><para> + Delete the specified value. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>rmkey|rmdir <keyname></term> + <listitem><para> + Delete the specified subkey recursively. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>pwd|pwk</term> + <listitem><para>Print the full name of the current key.</para></listitem> + </varlistentry> + + <varlistentry> + <term>set|update</term> + <listitem><para>Update the value of a key value. Not implemented at the moment.</para></listitem> + </varlistentry> + + <varlistentry> + <term>help|?</term> + <listitem><para>Print a list of available commands.</para></listitem> + </varlistentry> + <varlistentry> + <term>exit|quit</term> + <listitem><para>Leave regshell.</para></listitem> + </varlistentry> + </variablelist> +</refsect1> + +<refsect1> + <title>EXAMPLES</title> + + <para>Browsing thru a nt4 registry file</para> + <screen> +<userinput>regshell -b nt4 NTUSER.DAT</userinput> +$$$PROTO.HIV> <userinput>ls</userinput> +K AppEvents +K Console +K Control Panel +K Environment +K Identities +K Keyboard Layout +K Network +K Printers +K Software +K UNICODE Program Groups +K Windows 3.1 Migration Status +$$$PROTO.HIV> <userinput>exit</userinput> +</screen> + +<para>Listing the subkeys of HKEY_CURRENT_USER\AppEvents on a remote computer:</para> +<screen> +<userinput>regshell --remote=ncacn_np:aurelia -c "jelmer%secret"</userinput> +HKEY_CURRENT_MACHINE> <userinput>predef HKEY_CURRENT_USER</userinput> +HKEY_CURRENT_USER> <userinput>cd AppEvents</userinput> +Current path is: HKEY_CURRENT_USER\AppEvents +HKEY_CURRENT_USER\AppEvents> <userinput>ls</userinput> +K EventLabels +K Schemes +HKEY_CURRENT_USER\AppEvents> <userinput>exit</userinput> +</screen> +</refsect1> + +<refsect1> + <title>VERSION</title> + + <para>This man page is correct for version 4.0 of the Samba suite.</para> +</refsect1> + +<refsect1> + <title>SEE ALSO</title> + + <para>regtree, regdiff, regpatch, gregedit, samba</para> + +</refsect1> + +<refsect1> + <title>AUTHOR</title> + + <para>This utility is part of the <ulink url="http://www.samba.org/">Samba</ulink> suite, which is developed by the global <ulink url="http://www.samba.org/samba/team/">Samba Team</ulink>.</para> + + <para>This manpage and regshell were written by Jelmer Vernooij. </para> + +</refsect1> + +</refentry> diff --git a/source4/lib/registry/man/regtree.1.xml b/source4/lib/registry/man/regtree.1.xml new file mode 100644 index 0000000..0d798e4 --- /dev/null +++ b/source4/lib/registry/man/regtree.1.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<refentry id="regtree.1"> + +<refmeta> + <refentrytitle>regtree</refentrytitle> + <manvolnum>1</manvolnum> + <refmiscinfo class="source">Samba</refmiscinfo> + <refmiscinfo class="manual">System Administration tools</refmiscinfo> + <refmiscinfo class="version">4.0</refmiscinfo> +</refmeta> + + +<refnamediv> + <refname>regtree</refname> + <refpurpose>Text-mode registry viewer</refpurpose> +</refnamediv> + +<refsynopsisdiv> + <cmdsynopsis> + <command>regtree</command> + <arg choice="opt">--help</arg> + <arg choice="opt">--backend=BACKEND</arg> + <arg choice="opt">--fullpath</arg> + <arg choice="opt">--no-values</arg> + <arg choice="opt">--credentials=CREDENTIALS</arg> + <arg choice="opt">location</arg> + </cmdsynopsis> +</refsynopsisdiv> + +<refsect1> + <title>DESCRIPTION</title> + + <para>The regtree utility prints out all the contents of a + Windows registry file. Subkeys are printed with one level + more indentation than their parents. </para> + +</refsect1> + + +<refsect1> + <title>OPTIONS</title> + + <variablelist> + <varlistentry> + <term>--help</term> + <listitem><para> + Show list of available options.</para></listitem> + </varlistentry> + + <varlistentry> + <term>--backend BACKEND</term> + <listitem><para>Name of backend to load. Possible values are: + creg, regf, dir and rpc. The default is <emphasis>dir</emphasis>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>--credentials=CREDENTIALS</term> + <listitem><para> + Credentials to use, if any. Password should be separated from user name by a percent sign.</para></listitem> + </varlistentry> + + <varlistentry> + <term>--fullpath</term> + <listitem><para> + Print the full path to each key instead of only its name. + </para> + </listitem> + </varlistentry> + + <varlistentry><term>--no-values</term> + <listitem><para>Don't print values, just keys.</para></listitem> + </varlistentry> + </variablelist> + +</refsect1> + +<refsect1> + <title>VERSION</title> + + <para>This man page is correct for version 4.0 of the Samba suite.</para> +</refsect1> + +<refsect1> + <title>SEE ALSO</title> + + <para>gregedit, regshell, regdiff, regpatch, samba</para> + +</refsect1> + +<refsect1> + <title>AUTHOR</title> + + <para>This utility is part of the <ulink url="http://www.samba.org/">Samba</ulink> suite, which is developed by the global <ulink url="http://www.samba.org/samba/team/">Samba Team</ulink>.</para> + + <para>This manpage and regtree were written by Jelmer Vernooij. </para> + +</refsect1> + +</refentry> diff --git a/source4/lib/registry/patchfile.c b/source4/lib/registry/patchfile.c new file mode 100644 index 0000000..8069ed7 --- /dev/null +++ b/source4/lib/registry/patchfile.c @@ -0,0 +1,543 @@ +/* + Unix SMB/CIFS implementation. + Reading registry patch files + + Copyright (C) Jelmer Vernooij 2004-2007 + Copyright (C) Wilco Baan Hofman 2006 + Copyright (C) Matthias Dieter Wallnöfer 2008-2010 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/registry/registry.h" +#include "system/filesys.h" + + +_PUBLIC_ WERROR reg_preg_diff_load(int fd, + const struct reg_diff_callbacks *callbacks, + void *callback_data); + +_PUBLIC_ WERROR reg_dotreg_diff_load(int fd, + const struct reg_diff_callbacks *callbacks, + void *callback_data); + +/* + * Generate difference between two keys + */ +WERROR reg_generate_diff_key(struct registry_key *oldkey, + struct registry_key *newkey, + const char *path, + const struct reg_diff_callbacks *callbacks, + void *callback_data) +{ + unsigned int i; + struct registry_key *t1 = NULL, *t2 = NULL; + char *tmppath; + const char *keyname1; + WERROR error, error1, error2; + TALLOC_CTX *mem_ctx = talloc_init("writediff"); + uint32_t old_num_subkeys, old_num_values, + new_num_subkeys, new_num_values; + + if (oldkey != NULL) { + error = reg_key_get_info(mem_ctx, oldkey, NULL, + &old_num_subkeys, &old_num_values, + NULL, NULL, NULL, NULL); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error occurred while getting key info: %s\n", + win_errstr(error))); + talloc_free(mem_ctx); + return error; + } + } else { + old_num_subkeys = 0; + old_num_values = 0; + } + + /* Subkeys that were changed or deleted */ + for (i = 0; i < old_num_subkeys; i++) { + error1 = reg_key_get_subkey_by_index(mem_ctx, oldkey, i, + &keyname1, NULL, NULL); + if (!W_ERROR_IS_OK(error1)) { + DEBUG(0, ("Error occurred while getting subkey by index: %s\n", + win_errstr(error1))); + continue; + } + + if (newkey != NULL) { + error2 = reg_open_key(mem_ctx, newkey, keyname1, &t2); + } else { + error2 = WERR_FILE_NOT_FOUND; + t2 = NULL; + } + + if (!W_ERROR_IS_OK(error2) && !W_ERROR_EQUAL(error2, WERR_FILE_NOT_FOUND)) { + DEBUG(0, ("Error occurred while getting subkey by name: %s\n", + win_errstr(error2))); + talloc_free(mem_ctx); + return error2; + } + + /* if "error2" is going to be "WERR_FILE_NOT_FOUND", then newkey */ + /* didn't have such a subkey and therefore add a del diff */ + tmppath = talloc_asprintf(mem_ctx, "%s\\%s", path, keyname1); + if (tmppath == NULL) { + DEBUG(0, ("Out of memory\n")); + talloc_free(mem_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + if (!W_ERROR_IS_OK(error2)) + callbacks->del_key(callback_data, tmppath); + + /* perform here also the recursive invocation */ + error1 = reg_open_key(mem_ctx, oldkey, keyname1, &t1); + if (!W_ERROR_IS_OK(error1)) { + DEBUG(0, ("Error occurred while getting subkey by name: %s\n", + win_errstr(error1))); + talloc_free(mem_ctx); + return error1; + } + reg_generate_diff_key(t1, t2, tmppath, callbacks, callback_data); + + talloc_free(tmppath); + } + + if (newkey != NULL) { + error = reg_key_get_info(mem_ctx, newkey, NULL, + &new_num_subkeys, &new_num_values, + NULL, NULL, NULL, NULL); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error occurred while getting key info: %s\n", + win_errstr(error))); + talloc_free(mem_ctx); + return error; + } + } else { + new_num_subkeys = 0; + new_num_values = 0; + } + + /* Subkeys that were added */ + for(i = 0; i < new_num_subkeys; i++) { + error1 = reg_key_get_subkey_by_index(mem_ctx, newkey, i, + &keyname1, NULL, NULL); + if (!W_ERROR_IS_OK(error1)) { + DEBUG(0, ("Error occurred while getting subkey by index: %s\n", + win_errstr(error1))); + talloc_free(mem_ctx); + return error1; + } + + if (oldkey != NULL) { + error2 = reg_open_key(mem_ctx, oldkey, keyname1, &t1); + + if (W_ERROR_IS_OK(error2)) + continue; + } else { + error2 = WERR_FILE_NOT_FOUND; + t1 = NULL; + } + + if (!W_ERROR_EQUAL(error2, WERR_FILE_NOT_FOUND)) { + DEBUG(0, ("Error occurred while getting subkey by name: %s\n", + win_errstr(error2))); + talloc_free(mem_ctx); + return error2; + } + + /* oldkey didn't have such a subkey, add a add diff */ + tmppath = talloc_asprintf(mem_ctx, "%s\\%s", path, keyname1); + if (tmppath == NULL) { + DEBUG(0, ("Out of memory\n")); + talloc_free(mem_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + callbacks->add_key(callback_data, tmppath); + + /* perform here also the recursive invocation */ + error1 = reg_open_key(mem_ctx, newkey, keyname1, &t2); + if (!W_ERROR_IS_OK(error1)) { + DEBUG(0, ("Error occurred while getting subkey by name: %s\n", + win_errstr(error1))); + talloc_free(mem_ctx); + return error1; + } + reg_generate_diff_key(t1, t2, tmppath, callbacks, callback_data); + + talloc_free(tmppath); + } + + /* Values that were added or changed */ + for(i = 0; i < new_num_values; i++) { + const char *name; + uint32_t type1, type2; + DATA_BLOB contents1 = { NULL, 0 }, contents2 = { NULL, 0 }; + + error1 = reg_key_get_value_by_index(mem_ctx, newkey, i, + &name, &type1, &contents1); + if (!W_ERROR_IS_OK(error1)) { + DEBUG(0, ("Unable to get value by index: %s\n", + win_errstr(error1))); + talloc_free(mem_ctx); + return error1; + } + + if (oldkey != NULL) { + error2 = reg_key_get_value_by_name(mem_ctx, oldkey, + name, &type2, + &contents2); + } else + error2 = WERR_FILE_NOT_FOUND; + + if (!W_ERROR_IS_OK(error2) + && !W_ERROR_EQUAL(error2, WERR_FILE_NOT_FOUND)) { + DEBUG(0, ("Error occurred while getting value by name: %s\n", + win_errstr(error2))); + talloc_free(mem_ctx); + return error2; + } + + if (W_ERROR_IS_OK(error2) + && (data_blob_cmp(&contents1, &contents2) == 0) + && (type1 == type2)) { + talloc_free(discard_const_p(char, name)); + talloc_free(contents1.data); + talloc_free(contents2.data); + continue; + } + + callbacks->set_value(callback_data, path, name, + type1, contents1); + + talloc_free(discard_const_p(char, name)); + talloc_free(contents1.data); + talloc_free(contents2.data); + } + + /* Values that were deleted */ + for (i = 0; i < old_num_values; i++) { + const char *name; + uint32_t type; + DATA_BLOB contents = { NULL, 0 }; + + error1 = reg_key_get_value_by_index(mem_ctx, oldkey, i, &name, + &type, &contents); + if (!W_ERROR_IS_OK(error1)) { + DEBUG(0, ("Unable to get value by index: %s\n", + win_errstr(error1))); + talloc_free(mem_ctx); + return error1; + } + + if (newkey != NULL) + error2 = reg_key_get_value_by_name(mem_ctx, newkey, + name, &type, &contents); + else + error2 = WERR_FILE_NOT_FOUND; + + if (W_ERROR_IS_OK(error2)) { + talloc_free(discard_const_p(char, name)); + talloc_free(contents.data); + continue; + } + + if (!W_ERROR_EQUAL(error2, WERR_FILE_NOT_FOUND)) { + DEBUG(0, ("Error occurred while getting value by name: %s\n", + win_errstr(error2))); + talloc_free(mem_ctx); + return error2; + } + + callbacks->del_value(callback_data, path, name); + + talloc_free(discard_const_p(char, name)); + talloc_free(contents.data); + } + + talloc_free(mem_ctx); + return WERR_OK; +} + +/** + * Generate diff between two registry contexts + */ +_PUBLIC_ WERROR reg_generate_diff(struct registry_context *ctx1, + struct registry_context *ctx2, + const struct reg_diff_callbacks *callbacks, + void *callback_data) +{ + unsigned int i; + WERROR error; + + for (i = 0; reg_predefined_keys[i].name; i++) { + struct registry_key *r1 = NULL, *r2 = NULL; + + error = reg_get_predefined_key(ctx1, + reg_predefined_keys[i].handle, &r1); + if (!W_ERROR_IS_OK(error) && + !W_ERROR_EQUAL(error, WERR_FILE_NOT_FOUND)) { + DEBUG(0, ("Unable to open hive %s for backend 1\n", + reg_predefined_keys[i].name)); + continue; + } + + error = reg_get_predefined_key(ctx2, + reg_predefined_keys[i].handle, &r2); + if (!W_ERROR_IS_OK(error) && + !W_ERROR_EQUAL(error, WERR_FILE_NOT_FOUND)) { + DEBUG(0, ("Unable to open hive %s for backend 2\n", + reg_predefined_keys[i].name)); + continue; + } + + /* if "r1" is NULL (old hive) and "r2" isn't (new hive) then + * the hive doesn't exist yet and we have to generate an add + * diff */ + if ((r1 == NULL) && (r2 != NULL)) { + callbacks->add_key(callback_data, + reg_predefined_keys[i].name); + } + /* if "r1" isn't NULL (old hive) and "r2" is (new hive) then + * the hive shouldn't exist anymore and we have to generate a + * del diff */ + if ((r1 != NULL) && (r2 == NULL)) { + callbacks->del_key(callback_data, + reg_predefined_keys[i].name); + } + + error = reg_generate_diff_key(r1, r2, + reg_predefined_keys[i].name, callbacks, + callback_data); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Unable to determine diff: %s\n", + win_errstr(error))); + return error; + } + } + if (callbacks->done != NULL) { + callbacks->done(callback_data); + } + return WERR_OK; +} + +/** + * Load diff file + */ +_PUBLIC_ WERROR reg_diff_load(const char *filename, + const struct reg_diff_callbacks *callbacks, + void *callback_data) +{ + int fd; + char hdr[4]; + + fd = open(filename, O_RDONLY, 0); + if (fd == -1) { + DEBUG(0, ("Error opening registry patch file `%s'\n", + filename)); + return WERR_GEN_FAILURE; + } + + if (read(fd, &hdr, 4) != 4) { + DEBUG(0, ("Error reading registry patch file `%s'\n", + filename)); + close(fd); + return WERR_GEN_FAILURE; + } + + /* Reset position in file */ + lseek(fd, 0, SEEK_SET); +#if 0 /* These backends are not supported yet. */ + if (strncmp(hdr, "CREG", 4) == 0) { + /* Must be a W9x CREG Config.pol file */ + return reg_creg_diff_load(diff, fd); + } else if (strncmp(hdr, "regf", 4) == 0) { + /* Must be a REGF NTConfig.pol file */ + return reg_regf_diff_load(diff, fd); + } else +#endif + if (strncmp(hdr, "PReg", 4) == 0) { + /* Must be a GPO Registry.pol file */ + return reg_preg_diff_load(fd, callbacks, callback_data); + } else { + /* Must be a normal .REG file */ + return reg_dotreg_diff_load(fd, callbacks, callback_data); + } +} + +/** + * The reg_diff_apply functions + */ +static WERROR reg_diff_apply_add_key(void *_ctx, const char *key_name) +{ + struct registry_context *ctx = (struct registry_context *)_ctx; + struct registry_key *tmp; + char *buf, *buf_ptr; + WERROR error; + + /* Recursively create the path */ + buf = talloc_strdup(ctx, key_name); + W_ERROR_HAVE_NO_MEMORY(buf); + buf_ptr = buf; + + while (*buf_ptr++ != '\0' ) { + if (*buf_ptr == '\\') { + *buf_ptr = '\0'; + error = reg_key_add_abs(ctx, ctx, buf, 0, NULL, &tmp); + + if (!W_ERROR_EQUAL(error, WERR_ALREADY_EXISTS) && + !W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error adding new key '%s': %s\n", + key_name, win_errstr(error))); + return error; + } + *buf_ptr++ = '\\'; + talloc_free(tmp); + } + } + + talloc_free(buf); + + /* Add the key */ + error = reg_key_add_abs(ctx, ctx, key_name, 0, NULL, &tmp); + + if (!W_ERROR_EQUAL(error, WERR_ALREADY_EXISTS) && + !W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error adding new key '%s': %s\n", + key_name, win_errstr(error))); + return error; + } + talloc_free(tmp); + + return WERR_OK; +} + +static WERROR reg_diff_apply_del_key(void *_ctx, const char *key_name) +{ + struct registry_context *ctx = (struct registry_context *)_ctx; + + /* We can't proof here for success, because a common superkey could */ + /* have been deleted before the subkey's (diff order). This removed */ + /* therefore all children recursively and the "WERR_FILE_NOT_FOUND" result is */ + /* expected. */ + + reg_key_del_abs(ctx, key_name); + + return WERR_OK; +} + +static WERROR reg_diff_apply_set_value(void *_ctx, const char *path, + const char *value_name, + uint32_t value_type, DATA_BLOB value) +{ + struct registry_context *ctx = (struct registry_context *)_ctx; + struct registry_key *tmp; + WERROR error; + + /* Open key */ + error = reg_open_key_abs(ctx, ctx, path, &tmp); + + if (W_ERROR_EQUAL(error, WERR_FILE_NOT_FOUND)) { + DEBUG(0, ("Error opening key '%s'\n", path)); + return error; + } + + /* Set value */ + error = reg_val_set(tmp, value_name, + value_type, value); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error setting value '%s'\n", value_name)); + return error; + } + + talloc_free(tmp); + + return WERR_OK; +} + +static WERROR reg_diff_apply_del_value(void *_ctx, const char *key_name, + const char *value_name) +{ + struct registry_context *ctx = (struct registry_context *)_ctx; + struct registry_key *tmp; + WERROR error; + + /* Open key */ + error = reg_open_key_abs(ctx, ctx, key_name, &tmp); + + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error opening key '%s'\n", key_name)); + return error; + } + + error = reg_del_value(ctx, tmp, value_name); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error deleting value '%s'\n", value_name)); + return error; + } + + talloc_free(tmp); + + return WERR_OK; +} + +static WERROR reg_diff_apply_del_all_values(void *_ctx, const char *key_name) +{ + struct registry_context *ctx = (struct registry_context *)_ctx; + struct registry_key *key; + WERROR error; + const char *value_name; + + error = reg_open_key_abs(ctx, ctx, key_name, &key); + + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error opening key '%s'\n", key_name)); + return error; + } + + W_ERROR_NOT_OK_RETURN(reg_key_get_info(ctx, key, NULL, + NULL, NULL, NULL, NULL, NULL, NULL)); + + while (W_ERROR_IS_OK(reg_key_get_value_by_index( + ctx, key, 0, &value_name, NULL, NULL))) { + error = reg_del_value(ctx, key, value_name); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error deleting value '%s'\n", value_name)); + return error; + } + talloc_free(discard_const_p(char, value_name)); + } + + talloc_free(key); + + return WERR_OK; +} + +/** + * Apply diff to a registry context + */ +_PUBLIC_ WERROR reg_diff_apply(struct registry_context *ctx, + const char *filename) +{ + struct reg_diff_callbacks callbacks; + + callbacks.add_key = reg_diff_apply_add_key; + callbacks.del_key = reg_diff_apply_del_key; + callbacks.set_value = reg_diff_apply_set_value; + callbacks.del_value = reg_diff_apply_del_value; + callbacks.del_all_values = reg_diff_apply_del_all_values; + callbacks.done = NULL; + + return reg_diff_load(filename, &callbacks, ctx); +} diff --git a/source4/lib/registry/patchfile_dotreg.c b/source4/lib/registry/patchfile_dotreg.c new file mode 100644 index 0000000..5fb342b --- /dev/null +++ b/source4/lib/registry/patchfile_dotreg.c @@ -0,0 +1,435 @@ +/* + Unix SMB/CIFS implementation. + Reading .REG files + + Copyright (C) Jelmer Vernooij 2004-2007 + Copyright (C) Wilco Baan Hofman 2006-2010 + + 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 3 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. +*/ + +/* FIXME: + * - Newer .REG files, created by Windows XP and above use unicode UCS-2 + * - @="" constructions should write value with empty name. +*/ + +#include "includes.h" +#include "lib/registry/registry.h" +#include "system/filesys.h" + +/** + * @file + * @brief Registry patch files + */ + +#define HEADER_STRING "REGEDIT4" + +struct dotreg_data { + int fd; +}; + +/* + * This is basically a copy of data_blob_hex_string_upper, but with comma's + * between the bytes in hex. + */ +static char *dotreg_data_blob_hex_string(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob) +{ + size_t i; + char *hex_string; + + hex_string = talloc_array(mem_ctx, char, (blob->length*3)+1); + if (!hex_string) { + return NULL; + } + + for (i = 0; i < blob->length; i++) + slprintf(&hex_string[i*3], 4, "%02X,", blob->data[i]); + + /* Remove last comma and NULL-terminate the string */ + hex_string[(blob->length*3)-1] = '\0'; + return hex_string; +} + +/* + * This is basically a copy of reg_val_data_string, except that this function + * has no 0x for dwords, everything else is regarded as binary, and binary + * strings are represented with bytes comma-separated. + */ +static char *reg_val_dotreg_string(TALLOC_CTX *mem_ctx, uint32_t type, + const DATA_BLOB data) +{ + size_t converted_size = 0; + char *ret = NULL; + + if (data.length == 0) + return talloc_strdup(mem_ctx, ""); + + switch (type) { + case REG_EXPAND_SZ: + case REG_SZ: + convert_string_talloc(mem_ctx, + CH_UTF16, CH_UNIX, data.data, data.length, + (void **)&ret, &converted_size); + break; + case REG_DWORD: + case REG_DWORD_BIG_ENDIAN: + SMB_ASSERT(data.length == sizeof(uint32_t)); + ret = talloc_asprintf(mem_ctx, "%08x", + IVAL(data.data, 0)); + break; + default: /* default means treat as binary */ + case REG_BINARY: + ret = dotreg_data_blob_hex_string(mem_ctx, &data); + break; + } + + return ret; +} + +static WERROR reg_dotreg_diff_add_key(void *_data, const char *key_name) +{ + struct dotreg_data *data = (struct dotreg_data *)_data; + + fdprintf(data->fd, "\n[%s]\n", key_name); + + return WERR_OK; +} + +static WERROR reg_dotreg_diff_del_key(void *_data, const char *key_name) +{ + struct dotreg_data *data = (struct dotreg_data *)_data; + + fdprintf(data->fd, "\n[-%s]\n", key_name); + + return WERR_OK; +} + +static WERROR reg_dotreg_diff_set_value(void *_data, const char *path, + const char *value_name, + uint32_t value_type, DATA_BLOB value) +{ + struct dotreg_data *data = (struct dotreg_data *)_data; + char *data_string = reg_val_dotreg_string(NULL, + value_type, value); + char *data_incl_type; + + W_ERROR_HAVE_NO_MEMORY(data_string); + + switch (value_type) { + case REG_SZ: + data_incl_type = talloc_asprintf(data_string, "\"%s\"", + data_string); + break; + case REG_DWORD: + data_incl_type = talloc_asprintf(data_string, + "dword:%s", data_string); + break; + case REG_BINARY: + data_incl_type = talloc_asprintf(data_string, "hex:%s", + data_string); + break; + default: + data_incl_type = talloc_asprintf(data_string, "hex(%x):%s", + value_type, data_string); + break; + } + + if (value_name[0] == '\0') { + fdprintf(data->fd, "@=%s\n", data_incl_type); + } else { + fdprintf(data->fd, "\"%s\"=%s\n", + value_name, data_incl_type); + } + + talloc_free(data_string); + + return WERR_OK; +} + +static WERROR reg_dotreg_diff_del_value(void *_data, const char *path, + const char *value_name) +{ + struct dotreg_data *data = (struct dotreg_data *)_data; + + fdprintf(data->fd, "\"%s\"=-\n", value_name); + + return WERR_OK; +} + +static WERROR reg_dotreg_diff_done(void *_data) +{ + struct dotreg_data *data = (struct dotreg_data *)_data; + + close(data->fd); + talloc_free(data); + + return WERR_OK; +} + +static WERROR reg_dotreg_diff_del_all_values(void *callback_data, + const char *key_name) +{ + return WERR_NOT_SUPPORTED; +} + +/** + * Save registry diff + */ +_PUBLIC_ WERROR reg_dotreg_diff_save(TALLOC_CTX *ctx, const char *filename, + struct reg_diff_callbacks **callbacks, + void **callback_data) +{ + struct dotreg_data *data; + + data = talloc_zero(ctx, struct dotreg_data); + *callback_data = data; + + if (filename) { + data->fd = open(filename, O_CREAT|O_WRONLY, 0755); + if (data->fd < 0) { + DEBUG(0, ("Unable to open %s\n", filename)); + return WERR_FILE_NOT_FOUND; + } + } else { + data->fd = STDOUT_FILENO; + } + + fdprintf(data->fd, "%s\n\n", HEADER_STRING); + + *callbacks = talloc(ctx, struct reg_diff_callbacks); + + (*callbacks)->add_key = reg_dotreg_diff_add_key; + (*callbacks)->del_key = reg_dotreg_diff_del_key; + (*callbacks)->set_value = reg_dotreg_diff_set_value; + (*callbacks)->del_value = reg_dotreg_diff_del_value; + (*callbacks)->del_all_values = reg_dotreg_diff_del_all_values; + (*callbacks)->done = reg_dotreg_diff_done; + + return WERR_OK; +} + +/** + * Load diff file + */ +_PUBLIC_ WERROR reg_dotreg_diff_load(int fd, + const struct reg_diff_callbacks *callbacks, + void *callback_data) +{ + char *line, *p, *q; + char *curkey = NULL; + TALLOC_CTX *mem_ctx = talloc_init("reg_dotreg_diff_load"); + WERROR error; + uint32_t value_type; + DATA_BLOB data; + bool result; + char *type_str = NULL; + char *data_str = NULL; + char *value = NULL; + bool continue_next_line = 0; + + line = afdgets(fd, mem_ctx, 0); + if (!line) { + DEBUG(0, ("Can't read from file.\n")); + talloc_free(mem_ctx); + close(fd); + return WERR_GEN_FAILURE; + } + + while ((line = afdgets(fd, mem_ctx, 0))) { + /* Remove '\r' if it's a Windows text file */ + if (strlen(line) && line[strlen(line)-1] == '\r') { + line[strlen(line)-1] = '\0'; + } + + /* Ignore comments and empty lines */ + if (strlen(line) == 0 || line[0] == ';') { + talloc_free(line); + + if (curkey) { + talloc_free(curkey); + } + curkey = NULL; + continue; + } + + /* Start of key */ + if (line[0] == '[') { + if (line[strlen(line)-1] != ']') { + DEBUG(0, ("Missing ']' on line: %s\n", line)); + talloc_free(line); + continue; + } + + /* Deleting key */ + if (line[1] == '-') { + curkey = talloc_strndup(line, line+2, strlen(line)-3); + W_ERROR_HAVE_NO_MEMORY(curkey); + + error = callbacks->del_key(callback_data, + curkey); + + if (!W_ERROR_IS_OK(error)) { + DEBUG(0,("Error deleting key %s\n", + curkey)); + talloc_free(mem_ctx); + return error; + } + + talloc_free(line); + curkey = NULL; + continue; + } + curkey = talloc_strndup(mem_ctx, line+1, strlen(line)-2); + W_ERROR_HAVE_NO_MEMORY(curkey); + + error = callbacks->add_key(callback_data, curkey); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0,("Error adding key %s\n", curkey)); + talloc_free(mem_ctx); + return error; + } + + talloc_free(line); + continue; + } + + /* Deleting/Changing value */ + if (continue_next_line) { + continue_next_line = 0; + + /* Continued data start with two whitespaces */ + if (line[0] != ' ' || line[1] != ' ') { + DEBUG(0, ("Malformed line: %s\n", line)); + talloc_free(line); + continue; + } + p = line + 2; + + /* Continue again if line ends with a backslash */ + if (line[strlen(line)-1] == '\\') { + line[strlen(line)-1] = '\0'; + continue_next_line = 1; + data_str = talloc_strdup_append(data_str, p); + talloc_free(line); + continue; + } + data_str = talloc_strdup_append(data_str, p); + } else { + p = strchr_m(line, '='); + if (p == NULL) { + DEBUG(0, ("Malformed line: %s\n", line)); + talloc_free(line); + continue; + } + + *p = '\0'; p++; + + + if (curkey == NULL) { + DEBUG(0, ("Value change without key\n")); + talloc_free(line); + continue; + } + + /* Values should be double-quoted */ + if (line[0] != '"') { + DEBUG(0, ("Malformed line\n")); + talloc_free(line); + continue; + } + + /* Chop of the quotes and store as value */ + value = talloc_strndup(mem_ctx, line+1,strlen(line)-2); + + /* Delete value */ + if (p[0] == '-') { + error = callbacks->del_value(callback_data, + curkey, value); + + /* Ignore if key does not exist (WERR_FILE_NOT_FOUND) + * Consistent with Windows behaviour */ + if (!W_ERROR_IS_OK(error) && + !W_ERROR_EQUAL(error, WERR_FILE_NOT_FOUND)) { + DEBUG(0, ("Error deleting value %s in key %s\n", + value, curkey)); + talloc_free(mem_ctx); + return error; + } + + talloc_free(line); + talloc_free(value); + continue; + } + + /* Do not look for colons in strings */ + if (p[0] == '"') { + q = NULL; + data_str = talloc_strndup(mem_ctx, p+1,strlen(p)-2); + } else { + /* Split the value type from the data */ + q = strchr_m(p, ':'); + if (q) { + *q = '\0'; + q++; + type_str = talloc_strdup(mem_ctx, p); + data_str = talloc_strdup(mem_ctx, q); + } else { + data_str = talloc_strdup(mem_ctx, p); + } + } + + /* Backslash before the CRLF means continue on next line */ + if (data_str[strlen(data_str)-1] == '\\') { + data_str[strlen(data_str)-1] = '\0'; + talloc_free(line); + continue_next_line = 1; + continue; + } + } + DEBUG(9, ("About to write %s with type %s, length %ld: %s\n", value, type_str, (long) strlen(data_str), data_str)); + result = reg_string_to_val(value, + type_str?type_str:"REG_SZ", data_str, + &value_type, &data); + if (!result) { + DEBUG(0, ("Error converting string to value for line:\n%s\n", + line)); + return WERR_GEN_FAILURE; + } + + error = callbacks->set_value(callback_data, curkey, value, + value_type, data); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error setting value for %s in %s\n", + value, curkey)); + talloc_free(mem_ctx); + return error; + } + + /* Clean up buffers */ + if (type_str != NULL) { + talloc_free(type_str); + type_str = NULL; + } + talloc_free(data_str); + talloc_free(value); + talloc_free(line); + } + + close(fd); + + talloc_free(mem_ctx); + + return WERR_OK; +} diff --git a/source4/lib/registry/patchfile_preg.c b/source4/lib/registry/patchfile_preg.c new file mode 100644 index 0000000..50bd141 --- /dev/null +++ b/source4/lib/registry/patchfile_preg.c @@ -0,0 +1,387 @@ +/* + Unix SMB/CIFS implementation. + Reading Registry.pol PReg registry files + + Copyright (C) Wilco Baan Hofman 2006-2008 + + 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 3 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 "lib/registry/registry.h" +#include "system/filesys.h" +#include "librpc/gen_ndr/winreg.h" +#include "lib/util/sys_rw.h" + +#undef strcasecmp +#undef strncasecmp + +struct preg_data { + int fd; + TALLOC_CTX *ctx; +}; + +static WERROR preg_read_utf16(int fd, char *c) +{ + uint16_t v; + + if (read(fd, &v, sizeof(uint16_t)) < sizeof(uint16_t)) { + return WERR_GEN_FAILURE; + } + push_codepoint(c, v); + return WERR_OK; +} +static WERROR preg_write_utf16(int fd, const char *string) +{ + uint16_t v; + size_t i, size; + + for (i = 0; i < strlen(string); i+=size) { + v = next_codepoint(&string[i], &size); + if (write(fd, &v, sizeof(uint16_t)) < sizeof(uint16_t)) { + return WERR_GEN_FAILURE; + } + } + return WERR_OK; +} +/* PReg does not support adding keys. */ +static WERROR reg_preg_diff_add_key(void *_data, const char *key_name) +{ + return WERR_OK; +} + +static WERROR reg_preg_diff_set_value(void *_data, const char *key_name, + const char *value_name, + uint32_t value_type, DATA_BLOB value_data) +{ + struct preg_data *data = (struct preg_data *)_data; + uint32_t buf; + + preg_write_utf16(data->fd, "["); + preg_write_utf16(data->fd, key_name); + preg_write_utf16(data->fd, ";"); + preg_write_utf16(data->fd, value_name); + preg_write_utf16(data->fd, ";"); + SIVAL(&buf, 0, value_type); + sys_write_v(data->fd, &buf, sizeof(uint32_t)); + preg_write_utf16(data->fd, ";"); + SIVAL(&buf, 0, value_data.length); + sys_write_v(data->fd, &buf, sizeof(uint32_t)); + preg_write_utf16(data->fd, ";"); + sys_write_v(data->fd, value_data.data, value_data.length); + preg_write_utf16(data->fd, "]"); + + return WERR_OK; +} + +static WERROR reg_preg_diff_del_key(void *_data, const char *key_name) +{ + struct preg_data *data = (struct preg_data *)_data; + char *parent_name; + DATA_BLOB blob; + WERROR werr; + + parent_name = talloc_strndup(data->ctx, key_name, + strrchr(key_name, '\\')-key_name); + W_ERROR_HAVE_NO_MEMORY(parent_name); + blob.data = (uint8_t*)talloc_strndup(data->ctx, + key_name+(strrchr(key_name, '\\')-key_name)+1, + strlen(key_name)-(strrchr(key_name, '\\')-key_name)); + W_ERROR_HAVE_NO_MEMORY(blob.data); + blob.length = strlen((char *)blob.data)+1; + + + /* FIXME: These values should be accumulated to be written at done(). */ + werr = reg_preg_diff_set_value(data, parent_name, "**DeleteKeys", + REG_SZ, blob); + + talloc_free(parent_name); + talloc_free(blob.data); + + return werr; +} + +static WERROR reg_preg_diff_del_value(void *_data, const char *key_name, + const char *value_name) +{ + struct preg_data *data = (struct preg_data *)_data; + char *val; + DATA_BLOB blob; + WERROR werr; + + val = talloc_asprintf(data->ctx, "**Del.%s", value_name); + W_ERROR_HAVE_NO_MEMORY(val); + blob.data = (uint8_t *)talloc(data->ctx, uint32_t); + W_ERROR_HAVE_NO_MEMORY(blob.data); + SIVAL(blob.data, 0, 0); + blob.length = sizeof(uint32_t); + + werr = reg_preg_diff_set_value(data, key_name, val, REG_DWORD, blob); + + talloc_free(val); + talloc_free(blob.data); + + return werr; +} + +static WERROR reg_preg_diff_del_all_values(void *_data, const char *key_name) +{ + struct preg_data *data = (struct preg_data *)_data; + DATA_BLOB blob; + WERROR werr; + + blob.data = (uint8_t *)talloc(data->ctx, uint32_t); + W_ERROR_HAVE_NO_MEMORY(blob.data); + SIVAL(blob.data, 0, 0); + blob.length = sizeof(uint32_t); + + werr = reg_preg_diff_set_value(data, key_name, "**DelVals.", REG_DWORD, + blob); + + talloc_free(blob.data); + + return werr; +} + +static WERROR reg_preg_diff_done(void *_data) +{ + struct preg_data *data = (struct preg_data *)_data; + + close(data->fd); + talloc_free(data); + return WERR_OK; +} + +/** + * Save registry diff + */ +_PUBLIC_ WERROR reg_preg_diff_save(TALLOC_CTX *ctx, const char *filename, + struct reg_diff_callbacks **callbacks, + void **callback_data) +{ + struct preg_data *data; + struct { + char hdr[4]; + uint32_t version; + } preg_header; + + + data = talloc_zero(ctx, struct preg_data); + *callback_data = data; + + if (filename) { + data->fd = open(filename, O_CREAT|O_WRONLY, 0755); + if (data->fd < 0) { + DEBUG(0, ("Unable to open %s\n", filename)); + return WERR_FILE_NOT_FOUND; + } + } else { + data->fd = STDOUT_FILENO; + } + + memcpy(preg_header.hdr, "PReg", sizeof(preg_header.hdr)); + SIVAL(&preg_header.version, 0, 1); + sys_write_v(data->fd, (uint8_t *)&preg_header, sizeof(preg_header)); + + data->ctx = ctx; + + *callbacks = talloc(ctx, struct reg_diff_callbacks); + + (*callbacks)->add_key = reg_preg_diff_add_key; + (*callbacks)->del_key = reg_preg_diff_del_key; + (*callbacks)->set_value = reg_preg_diff_set_value; + (*callbacks)->del_value = reg_preg_diff_del_value; + (*callbacks)->del_all_values = reg_preg_diff_del_all_values; + (*callbacks)->done = reg_preg_diff_done; + + return WERR_OK; +} +/** + * Load diff file + */ +_PUBLIC_ WERROR reg_preg_diff_load(int fd, + const struct reg_diff_callbacks *callbacks, + void *callback_data) +{ + struct { + char hdr[4]; + uint32_t version; + } preg_header; + char *buf; + size_t buf_size = 1024; + char *buf_ptr; + TALLOC_CTX *mem_ctx = talloc_init("reg_preg_diff_load"); + WERROR ret = WERR_OK; + + buf = talloc_array(mem_ctx, char, buf_size); + buf_ptr = buf; + + /* Read first 8 bytes (the header) */ + if (read(fd, &preg_header, sizeof(preg_header)) != sizeof(preg_header)) { + DEBUG(0, ("Could not read PReg file: %s\n", + strerror(errno))); + ret = WERR_GEN_FAILURE; + goto cleanup; + } + preg_header.version = IVAL(&preg_header.version, 0); + + if (strncmp(preg_header.hdr, "PReg", 4) != 0) { + DEBUG(0, ("This file is not a valid preg registry file\n")); + ret = WERR_GEN_FAILURE; + goto cleanup; + } + if (preg_header.version > 1) { + DEBUG(0, ("Warning: file format version is higher than expected.\n")); + } + + /* Read the entries */ + while(1) { + uint32_t value_type, length; + char *key = NULL; + char *value_name = NULL; + DATA_BLOB data = {NULL, 0}; + + if (!W_ERROR_IS_OK(preg_read_utf16(fd, buf_ptr))) { + break; + } + if (*buf_ptr != '[') { + DEBUG(0, ("Error in PReg file.\n")); + ret = WERR_GEN_FAILURE; + goto cleanup; + } + + /* Get the path */ + buf_ptr = buf; + while (W_ERROR_IS_OK(preg_read_utf16(fd, buf_ptr)) && + *buf_ptr != ';' && buf_ptr-buf < buf_size) { + buf_ptr++; + } + buf[buf_ptr-buf] = '\0'; + key = talloc_strdup(mem_ctx, buf); + + /* Get the name */ + buf_ptr = buf; + while (W_ERROR_IS_OK(preg_read_utf16(fd, buf_ptr)) && + *buf_ptr != ';' && buf_ptr-buf < buf_size) { + buf_ptr++; + } + buf[buf_ptr-buf] = '\0'; + value_name = talloc_strdup(mem_ctx, buf); + + /* Get the type */ + if (read(fd, &value_type, sizeof(uint32_t)) < sizeof(uint32_t)) { + DEBUG(0, ("Error while reading PReg\n")); + ret = WERR_GEN_FAILURE; + goto cleanup; + } + value_type = IVAL(&value_type, 0); + + /* Read past delimiter */ + buf_ptr = buf; + if (!(W_ERROR_IS_OK(preg_read_utf16(fd, buf_ptr)) && + *buf_ptr == ';') && buf_ptr-buf < buf_size) { + DEBUG(0, ("Error in PReg file.\n")); + ret = WERR_GEN_FAILURE; + goto cleanup; + } + + /* Get data length */ + if (read(fd, &length, sizeof(uint32_t)) < sizeof(uint32_t)) { + DEBUG(0, ("Error while reading PReg\n")); + ret = WERR_GEN_FAILURE; + goto cleanup; + } + length = IVAL(&length, 0); + + /* Read past delimiter */ + buf_ptr = buf; + if (!(W_ERROR_IS_OK(preg_read_utf16(fd, buf_ptr)) && + *buf_ptr == ';') && buf_ptr-buf < buf_size) { + DEBUG(0, ("Error in PReg file.\n")); + ret = WERR_GEN_FAILURE; + goto cleanup; + } + + /* Get the data */ + buf_ptr = buf; + if (length < buf_size && + read(fd, buf_ptr, length) != length) { + DEBUG(0, ("Error while reading PReg\n")); + ret = WERR_GEN_FAILURE; + goto cleanup; + } + data = data_blob_talloc(mem_ctx, buf, length); + + /* Check if delimiter is in place (whine if it isn't) */ + buf_ptr = buf; + if (!(W_ERROR_IS_OK(preg_read_utf16(fd, buf_ptr)) && + *buf_ptr == ']') && buf_ptr-buf < buf_size) { + DEBUG(0, ("Warning: Missing ']' in PReg file, expected ']', got '%c' 0x%x.\n", + *buf_ptr, *buf_ptr)); + } + + if (strcasecmp(value_name, "**DelVals") == 0) { + callbacks->del_all_values(callback_data, key); + } else if (strncasecmp(value_name, "**Del.",6) == 0) { + char *p = value_name+6; + + callbacks->del_value(callback_data, key, p); + } else if (strcasecmp(value_name, "**DeleteValues") == 0) { + char *p, *q; + + p = (char *) data.data; + + while ((q = strchr_m(p, ';'))) { + *q = '\0'; + q++; + + callbacks->del_value(callback_data, key, p); + + p = q; + } + callbacks->del_value(callback_data, key, p); + } else if (strcasecmp(value_name, "**DeleteKeys") == 0) { + char *p, *q, *full_key; + + p = (char *) data.data; + + while ((q = strchr_m(p, ';'))) { + *q = '\0'; + q++; + + full_key = talloc_asprintf(mem_ctx, "%s\\%s", + key, p); + callbacks->del_key(callback_data, full_key); + talloc_free(full_key); + + p = q; + } + full_key = talloc_asprintf(mem_ctx, "%s\\%s", key, p); + callbacks->del_key(callback_data, full_key); + talloc_free(full_key); + } else { + callbacks->add_key(callback_data, key); + callbacks->set_value(callback_data, key, value_name, + value_type, data); + } + TALLOC_FREE(key); + TALLOC_FREE(value_name); + data_blob_free(&data); + } +cleanup: + close(fd); + TALLOC_FREE(mem_ctx); + return ret; +} diff --git a/source4/lib/registry/pyregistry.c b/source4/lib/registry/pyregistry.c new file mode 100644 index 0000000..de40d2a --- /dev/null +++ b/source4/lib/registry/pyregistry.c @@ -0,0 +1,494 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008 + Copyright (C) Wilco Baan Hofman <wilco@baanhofman.nl> 2010 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "lib/replace/system/python.h" +#include "python/py3compat.h" +#include "includes.h" +#include "python/modules.h" +#include "libcli/util/pyerrors.h" +#include "lib/registry/registry.h" +#include <pytalloc.h> +#include "lib/events/events.h" +#include "auth/credentials/pycredentials.h" +#include "param/pyparam.h" + +extern PyTypeObject PyRegistryKey; +extern PyTypeObject PyRegistry; +extern PyTypeObject PyHiveKey; + +/*#define PyRegistryKey_AsRegistryKey(obj) pytalloc_get_type(obj, struct registry_key)*/ +#define PyRegistry_AsRegistryContext(obj) ((struct registry_context *)pytalloc_get_ptr(obj)) +#define PyHiveKey_AsHiveKey(obj) ((struct hive_key*)pytalloc_get_ptr(obj)) + + +static PyObject *py_get_predefined_key_by_name(PyObject *self, PyObject *args) +{ + char *name; + WERROR result; + struct registry_context *ctx = PyRegistry_AsRegistryContext(self); + struct registry_key *key; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + result = reg_get_predefined_key_by_name(ctx, name, &key); + PyErr_WERROR_NOT_OK_RAISE(result); + + return pytalloc_steal(&PyRegistryKey, key); +} + +static PyObject *py_key_del_abs(PyObject *self, PyObject *args) +{ + char *path; + WERROR result; + struct registry_context *ctx = PyRegistry_AsRegistryContext(self); + + if (!PyArg_ParseTuple(args, "s", &path)) + return NULL; + + result = reg_key_del_abs(ctx, path); + PyErr_WERROR_NOT_OK_RAISE(result); + + Py_RETURN_NONE; +} + +static PyObject *py_get_predefined_key(PyObject *self, PyObject *args) +{ + uint32_t hkey; + struct registry_context *ctx = PyRegistry_AsRegistryContext(self); + WERROR result; + struct registry_key *key; + + if (!PyArg_ParseTuple(args, "I", &hkey)) + return NULL; + + result = reg_get_predefined_key(ctx, hkey, &key); + PyErr_WERROR_NOT_OK_RAISE(result); + + return pytalloc_steal(&PyRegistryKey, key); +} + +static PyObject *py_diff_apply(PyObject *self, PyObject *args) +{ + char *filename; + WERROR result; + struct registry_context *ctx = PyRegistry_AsRegistryContext(self); + if (!PyArg_ParseTuple(args, "s", &filename)) + return NULL; + + result = reg_diff_apply(ctx, filename); + PyErr_WERROR_NOT_OK_RAISE(result); + + Py_RETURN_NONE; +} + +static PyObject *py_mount_hive(PyObject *self, PyObject *args) +{ + struct registry_context *ctx = PyRegistry_AsRegistryContext(self); + uint32_t hkey; + PyObject *py_hivekey, *py_elements = Py_None; + const char **elements; + WERROR result; + + if (!PyArg_ParseTuple(args, "OI|O", &py_hivekey, &hkey, &py_elements)) + return NULL; + + if (!PyList_Check(py_elements) && py_elements != Py_None) { + PyErr_SetString(PyExc_TypeError, "Expected list of elements"); + return NULL; + } + + if (py_elements == Py_None) { + elements = NULL; + } else { + int i; + elements = talloc_array(NULL, const char *, PyList_Size(py_elements)); + for (i = 0; i < PyList_Size(py_elements); i++) + elements[i] = PyUnicode_AsUTF8(PyList_GetItem(py_elements, i)); + } + + SMB_ASSERT(ctx != NULL); + + result = reg_mount_hive(ctx, PyHiveKey_AsHiveKey(py_hivekey), hkey, elements); + PyErr_WERROR_NOT_OK_RAISE(result); + + Py_RETURN_NONE; +} + +static PyObject *registry_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + WERROR result; + struct registry_context *ctx; + result = reg_open_local(NULL, &ctx); + PyErr_WERROR_NOT_OK_RAISE(result); + return pytalloc_steal(&PyRegistry, ctx); +} + +static PyMethodDef registry_methods[] = { + { "get_predefined_key_by_name", py_get_predefined_key_by_name, METH_VARARGS, + "S.get_predefined_key_by_name(name) -> key\n" + "Find a predefined key by name" }, + { "key_del_abs", py_key_del_abs, METH_VARARGS, "S.key_del_abs(name) -> None\n" + "Delete a key by absolute path." }, + { "get_predefined_key", py_get_predefined_key, METH_VARARGS, "S.get_predefined_key(hkey_id) -> key\n" + "Find a predefined key by id" }, + { "diff_apply", py_diff_apply, METH_VARARGS, "S.diff_apply(filename) -> None\n" + "Apply the diff from the specified file" }, + { "mount_hive", py_mount_hive, METH_VARARGS, "S.mount_hive(key, key_id, elements=None) -> None\n" + "Mount the specified key at the specified path." }, + {0} +}; + +PyTypeObject PyRegistry = { + .tp_name = "Registry", + .tp_methods = registry_methods, + .tp_new = registry_new, + .tp_flags = Py_TPFLAGS_DEFAULT, +}; + +static PyObject *py_hive_key_del(PyObject *self, PyObject *args) +{ + char *name; + struct hive_key *key = PyHiveKey_AsHiveKey(self); + WERROR result; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + result = hive_key_del(NULL, key, name); + + PyErr_WERROR_NOT_OK_RAISE(result); + + Py_RETURN_NONE; +} + +static PyObject *py_hive_key_flush(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + WERROR result; + struct hive_key *key = PyHiveKey_AsHiveKey(self); + + result = hive_key_flush(key); + PyErr_WERROR_NOT_OK_RAISE(result); + + Py_RETURN_NONE; +} + +static PyObject *py_hive_key_del_value(PyObject *self, PyObject *args) +{ + char *name; + WERROR result; + struct hive_key *key = PyHiveKey_AsHiveKey(self); + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + result = hive_key_del_value(NULL, key, name); + + PyErr_WERROR_NOT_OK_RAISE(result); + + Py_RETURN_NONE; +} + +static PyObject *py_hive_key_set_value(PyObject *self, PyObject *args) +{ + char *name; + uint32_t type; + DATA_BLOB value; + Py_ssize_t value_length = 0; + WERROR result; + struct hive_key *key = PyHiveKey_AsHiveKey(self); + + if (!PyArg_ParseTuple(args, "sIz#", &name, &type, &value.data, &value_length)) { + return NULL; + } + value.length = value_length; + + if (value.data != NULL) + result = hive_key_set_value(key, name, type, value); + else + result = hive_key_del_value(NULL, key, name); + + PyErr_WERROR_NOT_OK_RAISE(result); + + Py_RETURN_NONE; +} + +static PyMethodDef hive_key_methods[] = { + { "del", py_hive_key_del, METH_VARARGS, "S.del(name) -> None\n" + "Delete a subkey" }, + { "flush", (PyCFunction)py_hive_key_flush, METH_NOARGS, "S.flush() -> None\n" + "Flush this key to disk" }, + { "del_value", py_hive_key_del_value, METH_VARARGS, "S.del_value(name) -> None\n" + "Delete a value" }, + { "set_value", py_hive_key_set_value, METH_VARARGS, "S.set_value(name, type, data) -> None\n" + "Set a value" }, + {0} +}; + +static PyObject *hive_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { + Py_RETURN_NONE; +} + +static PyObject *py_open_hive(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "location", "lp_ctx", "session_info", "credentials", NULL }; + WERROR result; + struct loadparm_context *lp_ctx; + PyObject *py_lp_ctx = Py_None; + PyObject *py_session_info = Py_None; + PyObject *py_credentials = Py_None; + struct auth_session_info *session_info; + struct cli_credentials *credentials; + char *location; + struct hive_key *hive_key; + TALLOC_CTX *mem_ctx; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|OOO", + discard_const_p(char *, kwnames), + &location, + &py_lp_ctx, &py_session_info, + &py_credentials)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected loadparm context"); + talloc_free(mem_ctx); + return NULL; + } + + credentials = cli_credentials_from_py_object(py_credentials); + if (credentials == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected credentials"); + talloc_free(mem_ctx); + return NULL; + } + session_info = NULL; + + result = reg_open_hive(NULL, location, session_info, credentials, + samba_tevent_context_init(NULL), + lp_ctx, &hive_key); + talloc_free(mem_ctx); + PyErr_WERROR_NOT_OK_RAISE(result); + + return pytalloc_steal(&PyHiveKey, hive_key); +} + +PyTypeObject PyHiveKey = { + .tp_name = "HiveKey", + .tp_methods = hive_key_methods, + .tp_new = hive_new, + .tp_flags = Py_TPFLAGS_DEFAULT, +}; + +PyTypeObject PyRegistryKey = { + .tp_name = "RegistryKey", + .tp_flags = Py_TPFLAGS_DEFAULT, +}; + +static PyObject *py_open_samba(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "lp_ctx", "session_info", NULL }; + struct registry_context *reg_ctx; + WERROR result; + struct loadparm_context *lp_ctx; + PyObject *py_lp_ctx = Py_None; + PyObject *py_session_info = Py_None; + PyObject *py_credentials = Py_None; + struct auth_session_info *session_info; + struct cli_credentials *credentials; + TALLOC_CTX *mem_ctx; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO", + discard_const_p(char *, kwnames), + &py_lp_ctx, &py_session_info, + &py_credentials)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected loadparm context"); + talloc_free(mem_ctx); + return NULL; + } + + credentials = cli_credentials_from_py_object(py_credentials); + if (credentials == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected credentials"); + talloc_free(mem_ctx); + return NULL; + } + + session_info = NULL; /* FIXME */ + + result = reg_open_samba(NULL, ®_ctx, NULL, + lp_ctx, session_info, credentials); + talloc_free(mem_ctx); + if (!W_ERROR_IS_OK(result)) { + PyErr_SetWERROR(result); + return NULL; + } + + return pytalloc_steal(&PyRegistry, reg_ctx); +} + +static PyObject *py_open_ldb_file(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "location", "session_info", "credentials", "lp_ctx", NULL }; + PyObject *py_session_info = Py_None, *py_credentials = Py_None, *py_lp_ctx = Py_None; + WERROR result; + char *location; + struct loadparm_context *lp_ctx; + struct cli_credentials *credentials; + struct hive_key *key; + struct auth_session_info *session_info; + TALLOC_CTX *mem_ctx; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|OOO", + discard_const_p(char *, kwnames), + &location, &py_session_info, + &py_credentials, &py_lp_ctx)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected loadparm context"); + talloc_free(mem_ctx); + return NULL; + } + + credentials = cli_credentials_from_py_object(py_credentials); + if (credentials == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected credentials"); + talloc_free(mem_ctx); + return NULL; + } + + session_info = NULL; /* FIXME */ + + result = reg_open_ldb_file(NULL, location, session_info, credentials, + s4_event_context_init(NULL), lp_ctx, &key); + talloc_free(mem_ctx); + PyErr_WERROR_NOT_OK_RAISE(result); + + return pytalloc_steal(&PyHiveKey, key); +} + +static PyObject *py_str_regtype(PyObject *self, PyObject *args) +{ + int regtype; + + if (!PyArg_ParseTuple(args, "i", ®type)) + return NULL; + + return PyUnicode_FromString(str_regtype(regtype)); +} + +static PyObject *py_get_predef_name(PyObject *self, PyObject *args) +{ + uint32_t hkey; + const char *str; + + if (!PyArg_ParseTuple(args, "I", &hkey)) + return NULL; + + str = reg_get_predef_name(hkey); + if (str == NULL) + Py_RETURN_NONE; + return PyUnicode_FromString(str); +} + +static PyMethodDef py_registry_methods[] = { + { "open_samba", PY_DISCARD_FUNC_SIG(PyCFunction, py_open_samba), + METH_VARARGS|METH_KEYWORDS, "open_samba() -> reg" }, + { "open_ldb", PY_DISCARD_FUNC_SIG(PyCFunction, py_open_ldb_file), + METH_VARARGS|METH_KEYWORDS, "open_ldb(location, session_info=None, credentials=None, loadparm_context=None) -> key" }, + { "open_hive", PY_DISCARD_FUNC_SIG(PyCFunction, py_open_hive), + METH_VARARGS|METH_KEYWORDS, "open_hive(location, session_info=None, credentials=None, loadparm_context=None) -> key" }, + { "str_regtype", py_str_regtype, METH_VARARGS, "str_regtype(int) -> str" }, + { "get_predef_name", py_get_predef_name, METH_VARARGS, "get_predef_name(hkey) -> str" }, + {0} +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "registry", + .m_doc = "Registry", + .m_size = -1, + .m_methods = py_registry_methods, +}; + +MODULE_INIT_FUNC(registry) +{ + PyObject *m; + + if (pytalloc_BaseObject_PyType_Ready(&PyHiveKey) < 0) + return NULL; + + if (pytalloc_BaseObject_PyType_Ready(&PyRegistry) < 0) + return NULL; + + if (pytalloc_BaseObject_PyType_Ready(&PyRegistryKey) < 0) + return NULL; + + m = PyModule_Create(&moduledef); + if (m == NULL) + return NULL; + + PyModule_AddObject(m, "HKEY_CLASSES_ROOT", PyLong_FromLong(HKEY_CLASSES_ROOT)); + PyModule_AddObject(m, "HKEY_CURRENT_USER", PyLong_FromLong(HKEY_CURRENT_USER)); + PyModule_AddObject(m, "HKEY_LOCAL_MACHINE", PyLong_FromLong(HKEY_LOCAL_MACHINE)); + PyModule_AddObject(m, "HKEY_USERS", PyLong_FromLong(HKEY_USERS)); + PyModule_AddObject(m, "HKEY_PERFORMANCE_DATA", PyLong_FromLong(HKEY_PERFORMANCE_DATA)); + PyModule_AddObject(m, "HKEY_CURRENT_CONFIG", PyLong_FromLong(HKEY_CURRENT_CONFIG)); + PyModule_AddObject(m, "HKEY_DYN_DATA", PyLong_FromLong(HKEY_DYN_DATA)); + PyModule_AddObject(m, "HKEY_PERFORMANCE_TEXT", PyLong_FromLong(HKEY_PERFORMANCE_TEXT)); + PyModule_AddObject(m, "HKEY_PERFORMANCE_NLSTEXT", PyLong_FromLong(HKEY_PERFORMANCE_NLSTEXT)); + + Py_INCREF(&PyRegistry); + PyModule_AddObject(m, "Registry", (PyObject *)&PyRegistry); + + Py_INCREF(&PyHiveKey); + PyModule_AddObject(m, "HiveKey", (PyObject *)&PyHiveKey); + + Py_INCREF(&PyRegistryKey); + PyModule_AddObject(m, "RegistryKey", (PyObject *)&PyRegistryKey); + + return m; +} diff --git a/source4/lib/registry/regf.c b/source4/lib/registry/regf.c new file mode 100644 index 0000000..d0fc2a9 --- /dev/null +++ b/source4/lib/registry/regf.c @@ -0,0 +1,2319 @@ +/* + Samba CIFS implementation + Registry backend for REGF files + Copyright (C) 2005-2007 Jelmer Vernooij, jelmer@samba.org + Copyright (C) 2006-2010 Wilco Baan Hofman, wilco@baanhofman.nl + + 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 3 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, see <http://www.gnu.org/licenses/>. */ + +#include "includes.h" +#include "system/filesys.h" +#include "system/time.h" +#include "lib/registry/tdr_regf.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/winreg.h" +#include "lib/registry/registry.h" +#include "libcli/security/security.h" + +#undef strcasecmp + +static struct hive_operations reg_backend_regf; + +/** + * There are several places on the web where the REGF format is explained; + * + * TODO: Links + */ + +/* TODO: + * - Return error codes that make more sense + * - Locking + * - do more things in-memory + */ + +/* + * Read HBIN blocks into memory + */ + +struct regf_data { + int fd; + struct hbin_block **hbins; + struct regf_hdr *header; + time_t last_write; +}; + +static WERROR regf_save_hbin(struct regf_data *data, bool flush); + +struct regf_key_data { + struct hive_key key; + struct regf_data *hive; + uint32_t offset; + struct nk_block *nk; +}; + +static struct hbin_block *hbin_by_offset(const struct regf_data *data, + uint32_t offset, uint32_t *rel_offset) +{ + unsigned int i; + + for (i = 0; data->hbins[i]; i++) { + if (offset >= data->hbins[i]->offset_from_first && + offset < data->hbins[i]->offset_from_first+ + data->hbins[i]->offset_to_next) { + if (rel_offset != NULL) + *rel_offset = offset - data->hbins[i]->offset_from_first - 0x20; + return data->hbins[i]; + } + } + + return NULL; +} + +/** + * Validate a regf header + * For now, do nothing, but we should check the checksum + */ +static uint32_t regf_hdr_checksum(const uint8_t *buffer) +{ + uint32_t checksum = 0, x; + unsigned int i; + + for (i = 0; i < 0x01FB; i+= 4) { + x = IVAL(buffer, i); + checksum ^= x; + } + + return checksum; +} + +/** + * Obtain the contents of a HBIN block + */ +static DATA_BLOB hbin_get(const struct regf_data *data, uint32_t offset) +{ + DATA_BLOB ret; + struct hbin_block *hbin; + uint32_t rel_offset; + + ret.data = NULL; + ret.length = 0; + + hbin = hbin_by_offset(data, offset, &rel_offset); + + if (hbin == NULL) { + DEBUG(1, ("Can't find HBIN at 0x%04x\n", offset)); + return ret; + } + + ret.length = IVAL(hbin->data, rel_offset); + if (!(ret.length & 0x80000000)) { + DEBUG(0, ("Trying to use dirty block at 0x%04x\n", offset)); + return ret; + } + + /* remove high bit */ + ret.length = (ret.length ^ 0xffffffff) + 1; + + ret.length -= 4; /* 4 bytes for the length... */ + ret.data = hbin->data + + (offset - hbin->offset_from_first - 0x20) + 4; + + return ret; +} + +static bool hbin_get_tdr(struct regf_data *regf, uint32_t offset, + TALLOC_CTX *ctx, tdr_pull_fn_t pull_fn, void *p) +{ + struct tdr_pull *pull = tdr_pull_init(regf); + + pull->data = hbin_get(regf, offset); + if (!pull->data.data) { + DEBUG(1, ("Unable to get data at 0x%04x\n", offset)); + talloc_free(pull); + return false; + } + + if (NT_STATUS_IS_ERR(pull_fn(pull, ctx, p))) { + DEBUG(1, ("Error parsing record at 0x%04x using tdr\n", + offset)); + talloc_free(pull); + return false; + } + talloc_free(pull); + + return true; +} + +/* Allocate some new data */ +static DATA_BLOB hbin_alloc(struct regf_data *data, uint32_t size, + uint32_t *offset) +{ + DATA_BLOB ret; + uint32_t rel_offset = (uint32_t) -1; /* Relative offset ! */ + struct hbin_block *hbin = NULL; + unsigned int i; + + if (offset != NULL) { + *offset = 0; + } + + if (size == 0) + return data_blob(NULL, 0); + + size += 4; /* Need to include int32 for the length */ + + /* Allocate as a multiple of 8 */ + size = (size + 7) & ~7; + + ret.data = NULL; + ret.length = 0; + + for (i = 0; (hbin = data->hbins[i]); i++) { + int j; + int32_t my_size; + for (j = 0; j < hbin->offset_to_next-0x20; j+= my_size) { + my_size = IVALS(hbin->data, j); + + if (my_size == 0x0) { + DEBUG(0, ("Invalid zero-length block! File is corrupt.\n")); + return ret; + } + + if (my_size % 8 != 0) { + DEBUG(0, ("Encountered non-aligned block!\n")); + } + + if (my_size < 0) { /* Used... */ + my_size = -my_size; + } else if (my_size == size) { /* exact match */ + rel_offset = j; + DEBUG(4, ("Found free block of exact size %d in middle of HBIN\n", + size)); + break; + } else if (my_size > size) { /* data will remain */ + rel_offset = j; + /* Split this block and mark the next block as free */ + SIVAL(hbin->data, rel_offset+size, my_size-size); + DEBUG(4, ("Found free block of size %d (needing %d) in middle of HBIN\n", + my_size, size)); + break; + } + } + + if (rel_offset != -1) + break; + } + + /* No space available in previous hbins, + * allocate new one */ + if (data->hbins[i] == NULL) { + DEBUG(4, ("No space available in other HBINs for block of size %d, allocating new HBIN\n", + size)); + + /* Add extra hbin block */ + data->hbins = talloc_realloc(data, data->hbins, + struct hbin_block *, i+2); + hbin = talloc(data->hbins, struct hbin_block); + SMB_ASSERT(hbin != NULL); + + data->hbins[i] = hbin; + data->hbins[i+1] = NULL; + + /* Set hbin data */ + hbin->HBIN_ID = talloc_strdup(hbin, "hbin"); + hbin->offset_from_first = (i == 0?0:data->hbins[i-1]->offset_from_first+data->hbins[i-1]->offset_to_next); + hbin->offset_to_next = 0x1000; + hbin->unknown[0] = 0; + hbin->unknown[1] = 0; + unix_to_nt_time(&hbin->last_change, time(NULL)); + hbin->block_size = hbin->offset_to_next; + hbin->data = talloc_zero_array(hbin, uint8_t, hbin->block_size - 0x20); + /* Update the regf header */ + data->header->last_block += hbin->offset_to_next; + + /* Set the next block to it's proper size and set the + * rel_offset for this block */ + SIVAL(hbin->data, size, hbin->block_size - size - 0x20); + rel_offset = 0x0; + } + + /* Set size and mark as used */ + SIVAL(hbin->data, rel_offset, -size); + + ret.data = hbin->data + rel_offset + 0x4; /* Skip past length */ + ret.length = size - 0x4; + if (offset) { + uint32_t new_rel_offset = 0; + *offset = hbin->offset_from_first + rel_offset + 0x20; + SMB_ASSERT(hbin_by_offset(data, *offset, &new_rel_offset) == hbin); + SMB_ASSERT(new_rel_offset == rel_offset); + } + + return ret; +} + +/* Store a data blob. Return the offset at which it was stored */ +static uint32_t hbin_store (struct regf_data *data, DATA_BLOB blob) +{ + uint32_t ret; + DATA_BLOB dest = hbin_alloc(data, blob.length, &ret); + + memcpy(dest.data, blob.data, blob.length); + + /* Make sure that we have no tailing garbage in the block */ + if (dest.length > blob.length) { + memset(dest.data + blob.length, 0, dest.length - blob.length); + } + + return ret; +} + +static uint32_t hbin_store_tdr(struct regf_data *data, + tdr_push_fn_t push_fn, void *p) +{ + struct tdr_push *push = tdr_push_init(data); + uint32_t ret; + + if (NT_STATUS_IS_ERR(push_fn(push, p))) { + DEBUG(0, ("Error during push\n")); + return -1; + } + + ret = hbin_store(data, push->data); + + talloc_free(push); + + return ret; +} + + +/* Free existing data */ +static void hbin_free (struct regf_data *data, uint32_t offset) +{ + int32_t size; + uint32_t rel_offset; + int32_t next_size; + struct hbin_block *hbin; + + SMB_ASSERT (offset > 0); + + hbin = hbin_by_offset(data, offset, &rel_offset); + + if (hbin == NULL) + return; + + /* Get original size */ + size = IVALS(hbin->data, rel_offset); + + if (size > 0) { + DEBUG(1, ("Trying to free already freed block at 0x%04x\n", + offset)); + return; + } + /* Mark as unused */ + size = -size; + + /* If the next block is free, merge into big free block */ + if (rel_offset + size < hbin->offset_to_next - 0x20) { + next_size = IVALS(hbin->data, rel_offset+size); + if (next_size > 0) { + size += next_size; + } + } + + /* Write block size */ + SIVALS(hbin->data, rel_offset, size); +} + +/** + * Store a data blob data was already stored, but has changed in size + * Will try to save it at the current location if possible, otherwise + * does a free + store */ +static uint32_t hbin_store_resize(struct regf_data *data, + uint32_t orig_offset, DATA_BLOB blob) +{ + uint32_t rel_offset; + struct hbin_block *hbin = hbin_by_offset(data, orig_offset, + &rel_offset); + int32_t my_size; + int32_t orig_size; + int32_t needed_size; + int32_t possible_size; + unsigned int i; + + SMB_ASSERT(orig_offset > 0); + + if (!hbin) + return hbin_store(data, blob); + + /* Get original size */ + orig_size = -IVALS(hbin->data, rel_offset); + + needed_size = blob.length + 4; /* Add int32 containing length */ + needed_size = (needed_size + 7) & ~7; /* Align */ + + /* Fits into current allocated block */ + if (orig_size >= needed_size) { + memcpy(hbin->data + rel_offset + 0x4, blob.data, blob.length); + /* If the difference in size is greater than 0x4, split the block + * and free/merge it */ + if (orig_size - needed_size > 0x4) { + SIVALS(hbin->data, rel_offset, -needed_size); + SIVALS(hbin->data, rel_offset + needed_size, + needed_size-orig_size); + hbin_free(data, orig_offset + needed_size); + } + return orig_offset; + } + + possible_size = orig_size; + + /* Check if it can be combined with the next few free records */ + for (i = rel_offset; i < hbin->offset_to_next - 0x20; i += my_size) { + if (IVALS(hbin->data, i) < 0) /* Used */ + break; + + my_size = IVALS(hbin->data, i); + + if (my_size == 0x0) { + DEBUG(0, ("Invalid zero-length block! File is corrupt.\n")); + break; + } else { + possible_size += my_size; + } + + if (possible_size >= blob.length) { + SIVAL(hbin->data, rel_offset, -possible_size); + memcpy(hbin->data + rel_offset + 0x4, + blob.data, blob.length); + return orig_offset; + } + } + + hbin_free(data, orig_offset); + return hbin_store(data, blob); +} + +static uint32_t hbin_store_tdr_resize(struct regf_data *regf, + tdr_push_fn_t push_fn, + uint32_t orig_offset, void *p) +{ + struct tdr_push *push = tdr_push_init(regf); + uint32_t ret; + + if (NT_STATUS_IS_ERR(push_fn(push, p))) { + DEBUG(0, ("Error during push\n")); + return -1; + } + + ret = hbin_store_resize(regf, orig_offset, push->data); + + talloc_free(push); + + return ret; +} + +static uint32_t regf_create_lh_hash(const char *name) +{ + char *hash_name; + uint32_t ret = 0; + uint16_t i; + + hash_name = strupper_talloc(NULL, name); + for (i = 0; *(hash_name + i) != 0; i++) { + ret *= 37; + ret += *(hash_name + i); + } + talloc_free(hash_name); + return ret; +} + +static WERROR regf_get_info(TALLOC_CTX *mem_ctx, + const struct hive_key *key, + const char **classname, + uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_mod_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + + if (num_subkeys != NULL) + *num_subkeys = private_data->nk->num_subkeys; + + if (num_values != NULL) + *num_values = private_data->nk->num_values; + + if (classname != NULL) { + if (private_data->nk->clsname_offset != -1) { + DATA_BLOB data = hbin_get(private_data->hive, + private_data->nk->clsname_offset); + *classname = talloc_strndup(mem_ctx, + (char*)data.data, + private_data->nk->clsname_length); + W_ERROR_HAVE_NO_MEMORY(*classname); + } else + *classname = NULL; + } + + /* TODO: Last mod time */ + + /* TODO: max valnamelen */ + + /* TODO: max valbufsize */ + + /* TODO: max subkeynamelen */ + + return WERR_OK; +} + +static struct regf_key_data *regf_get_key(TALLOC_CTX *ctx, + struct regf_data *regf, + uint32_t offset) +{ + struct nk_block *nk; + struct regf_key_data *ret; + + ret = talloc_zero(ctx, struct regf_key_data); + ret->key.ops = ®_backend_regf; + ret->hive = talloc_reference(ret, regf); + ret->offset = offset; + nk = talloc(ret, struct nk_block); + if (nk == NULL) + return NULL; + + ret->nk = nk; + + if (!hbin_get_tdr(regf, offset, nk, + (tdr_pull_fn_t)tdr_pull_nk_block, nk)) { + DEBUG(0, ("Unable to find HBIN data for offset 0x%x\n", offset)); + return NULL; + } + + if (strcmp(nk->header, "nk") != 0) { + DEBUG(0, ("Expected nk record, got %s\n", nk->header)); + talloc_free(ret); + return NULL; + } + + return ret; +} + + +static WERROR regf_get_value(TALLOC_CTX *ctx, struct hive_key *key, + uint32_t idx, const char **name, + uint32_t *data_type, DATA_BLOB *data) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + struct vk_block *vk; + struct regf_data *regf = private_data->hive; + uint32_t vk_offset; + DATA_BLOB tmp; + + if (idx >= private_data->nk->num_values) + return WERR_NO_MORE_ITEMS; + + tmp = hbin_get(regf, private_data->nk->values_offset); + if (!tmp.data) { + DEBUG(0, ("Unable to find value list at 0x%x\n", + private_data->nk->values_offset)); + return WERR_GEN_FAILURE; + } + + if (tmp.length < private_data->nk->num_values * 4) { + DEBUG(1, ("Value counts mismatch\n")); + } + + vk_offset = IVAL(tmp.data, idx * 4); + + vk = talloc(NULL, struct vk_block); + W_ERROR_HAVE_NO_MEMORY(vk); + + if (!hbin_get_tdr(regf, vk_offset, vk, + (tdr_pull_fn_t)tdr_pull_vk_block, vk)) { + DEBUG(0, ("Unable to get VK block at 0x%x\n", vk_offset)); + talloc_free(vk); + return WERR_GEN_FAILURE; + } + + /* FIXME: name character set ?*/ + if (name != NULL) { + *name = talloc_strndup(ctx, vk->data_name, vk->name_length); + W_ERROR_HAVE_NO_MEMORY(*name); + } + + if (data_type != NULL) + *data_type = vk->data_type; + + if (vk->data_length & 0x80000000) { + /* this is data of type "REG_DWORD" or "REG_DWORD_BIG_ENDIAN" */ + data->data = talloc_size(ctx, sizeof(uint32_t)); + W_ERROR_HAVE_NO_MEMORY(data->data); + SIVAL(data->data, 0, vk->data_offset); + data->length = sizeof(uint32_t); + } else { + *data = hbin_get(regf, vk->data_offset); + } + + if (data->length < vk->data_length) { + DEBUG(1, ("Read data less than indicated data length!\n")); + } + + talloc_free(vk); + + return WERR_OK; +} + +static WERROR regf_get_value_by_name(TALLOC_CTX *mem_ctx, + struct hive_key *key, const char *name, + uint32_t *type, DATA_BLOB *data) +{ + unsigned int i; + const char *vname; + WERROR error; + + /* FIXME: Do binary search? Is this list sorted at all? */ + + for (i = 0; W_ERROR_IS_OK(error = regf_get_value(mem_ctx, key, i, + &vname, type, data)); + i++) { + if (!strcmp(vname, name)) + return WERR_OK; + } + + if (W_ERROR_EQUAL(error, WERR_NO_MORE_ITEMS)) + return WERR_FILE_NOT_FOUND; + + return error; +} + + +static WERROR regf_get_subkey_by_index(TALLOC_CTX *ctx, + const struct hive_key *key, + uint32_t idx, const char **name, + const char **classname, + NTTIME *last_mod_time) +{ + DATA_BLOB data; + struct regf_key_data *ret; + const struct regf_key_data *private_data = (const struct regf_key_data *)key; + struct nk_block *nk = private_data->nk; + uint32_t key_off=0; + + if (idx >= nk->num_subkeys) + return WERR_NO_MORE_ITEMS; + + /* Make sure that we don't crash if the key is empty */ + if (nk->subkeys_offset == -1) { + return WERR_NO_MORE_ITEMS; + } + + data = hbin_get(private_data->hive, nk->subkeys_offset); + if (!data.data) { + DEBUG(0, ("Unable to find subkey list at 0x%x\n", + nk->subkeys_offset)); + return WERR_GEN_FAILURE; + } + + if (!strncmp((char *)data.data, "li", 2)) { + struct li_block li; + struct tdr_pull *pull = tdr_pull_init(private_data->hive); + + DEBUG(10, ("Subkeys in LI list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, nk, &li))) { + DEBUG(0, ("Error parsing LI list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + if (li.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + key_off = li.nk_offset[idx]; + + } else if (!strncmp((char *)data.data, "lf", 2)) { + struct lf_block lf; + struct tdr_pull *pull = tdr_pull_init(private_data->hive); + + DEBUG(10, ("Subkeys in LF list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull, nk, &lf))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lf.header, "lf", 2)); + + if (lf.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + + key_off = lf.hr[idx].nk_offset; + } else if (!strncmp((char *)data.data, "lh", 2)) { + struct lh_block lh; + struct tdr_pull *pull = tdr_pull_init(private_data->hive); + + DEBUG(10, ("Subkeys in LH list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, nk, &lh))) { + DEBUG(0, ("Error parsing LH list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + if (lh.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + key_off = lh.hr[idx].nk_offset; + } else if (!strncmp((char *)data.data, "ri", 2)) { + struct ri_block ri; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i; + uint16_t sublist_count = 0; + + DEBUG(10, ("Subkeys in RI list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_ri_block(pull, nk, &ri))) { + DEBUG(0, ("Error parsing RI list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(ri.header, "ri", 2)); + + for (i = 0; i < ri.key_count; i++) { + DATA_BLOB list_data; + + /* Get sublist data blob */ + list_data = hbin_get(private_data->hive, ri.offset[i]); + if (!list_data.data) { + DEBUG(0, ("Error getting RI list.\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + + pull->data = list_data; + + if (!strncmp((char *)list_data.data, "li", 2)) { + struct li_block li; + + DEBUG(10, ("Subkeys in RI->LI list\n")); + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, + nk, + &li))) { + DEBUG(0, ("Error parsing LI list from RI\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + /* Advance to next sublist if necessary */ + if (idx >= sublist_count + li.key_count) { + sublist_count += li.key_count; + continue; + } + key_off = li.nk_offset[idx - sublist_count]; + sublist_count += li.key_count; + break; + } else if (!strncmp((char *)list_data.data, "lh", 2)) { + struct lh_block lh; + + DEBUG(10, ("Subkeys in RI->LH list\n")); + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, + nk, + &lh))) { + DEBUG(0, ("Error parsing LH list from RI\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + /* Advance to next sublist if necessary */ + if (idx >= sublist_count + lh.key_count) { + sublist_count += lh.key_count; + continue; + } + key_off = lh.hr[idx - sublist_count].nk_offset; + sublist_count += lh.key_count; + break; + } else { + DEBUG(0,("Unknown sublist in ri block\n")); + talloc_free(pull); + + return WERR_GEN_FAILURE; + } + + } + talloc_free(pull); + + + if (idx > sublist_count) { + return WERR_NO_MORE_ITEMS; + } + + } else { + DEBUG(0, ("Unknown type for subkey list (0x%04x): %c%c\n", + nk->subkeys_offset, data.data[0], data.data[1])); + return WERR_GEN_FAILURE; + } + + ret = regf_get_key (ctx, private_data->hive, key_off); + + if (classname != NULL) { + if (ret->nk->clsname_offset != -1) { + DATA_BLOB db = hbin_get(ret->hive, + ret->nk->clsname_offset); + *classname = talloc_strndup(ctx, + (char*)db.data, + ret->nk->clsname_length); + W_ERROR_HAVE_NO_MEMORY(*classname); + } else + *classname = NULL; + } + + if (last_mod_time != NULL) + *last_mod_time = ret->nk->last_change; + + if (name != NULL) + *name = talloc_steal(ctx, ret->nk->key_name); + + talloc_free(ret); + + return WERR_OK; +} + +static WERROR regf_match_subkey_by_name(TALLOC_CTX *ctx, + const struct hive_key *key, + uint32_t offset, + const char *name, uint32_t *ret) +{ + DATA_BLOB subkey_data; + struct nk_block subkey; + struct tdr_pull *pull; + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + + subkey_data = hbin_get(private_data->hive, offset); + if (!subkey_data.data) { + DEBUG(0, ("Unable to retrieve subkey HBIN\n")); + return WERR_GEN_FAILURE; + } + + pull = tdr_pull_init(ctx); + + pull->data = subkey_data; + + if (NT_STATUS_IS_ERR(tdr_pull_nk_block(pull, ctx, &subkey))) { + DEBUG(0, ("Error parsing NK structure.\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + + if (strncmp(subkey.header, "nk", 2)) { + DEBUG(0, ("Not an NK structure.\n")); + return WERR_GEN_FAILURE; + } + + if (!strcasecmp(subkey.key_name, name)) { + *ret = offset; + } else { + *ret = 0; + } + return WERR_OK; +} + +static WERROR regf_get_subkey_by_name(TALLOC_CTX *ctx, + const struct hive_key *key, + const char *name, + struct hive_key **ret) +{ + DATA_BLOB data; + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + struct nk_block *nk = private_data->nk; + uint32_t key_off = 0; + + /* Make sure that we don't crash if the key is empty */ + if (nk->subkeys_offset == -1) { + return WERR_FILE_NOT_FOUND; + } + + data = hbin_get(private_data->hive, nk->subkeys_offset); + if (!data.data) { + DEBUG(0, ("Unable to find subkey list\n")); + return WERR_GEN_FAILURE; + } + + if (!strncmp((char *)data.data, "li", 2)) { + struct li_block li; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i; + + DEBUG(10, ("Subkeys in LI list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, nk, &li))) { + DEBUG(0, ("Error parsing LI list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + if (li.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + + for (i = 0; i < li.key_count; i++) { + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, key, + li.nk_offset[i], + name, + &key_off)); + if (key_off != 0) + break; + } + if (key_off == 0) + return WERR_FILE_NOT_FOUND; + } else if (!strncmp((char *)data.data, "lf", 2)) { + struct lf_block lf; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i; + + DEBUG(10, ("Subkeys in LF list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull, nk, &lf))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lf.header, "lf", 2)); + + if (lf.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + + for (i = 0; i < lf.key_count; i++) { + if (strncmp(lf.hr[i].hash, name, 4)) { + continue; + } + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, + key, + lf.hr[i].nk_offset, + name, + &key_off)); + if (key_off != 0) + break; + } + if (key_off == 0) + return WERR_FILE_NOT_FOUND; + } else if (!strncmp((char *)data.data, "lh", 2)) { + struct lh_block lh; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i; + uint32_t hash; + + DEBUG(10, ("Subkeys in LH list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, nk, &lh))) { + DEBUG(0, ("Error parsing LH list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + if (lh.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + + hash = regf_create_lh_hash(name); + for (i = 0; i < lh.key_count; i++) { + if (lh.hr[i].base37 != hash) { + continue; + } + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, + key, + lh.hr[i].nk_offset, + name, + &key_off)); + if (key_off != 0) + break; + } + if (key_off == 0) + return WERR_FILE_NOT_FOUND; + } else if (!strncmp((char *)data.data, "ri", 2)) { + struct ri_block ri; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i, j; + + DEBUG(10, ("Subkeys in RI list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_ri_block(pull, nk, &ri))) { + DEBUG(0, ("Error parsing RI list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(ri.header, "ri", 2)); + + for (i = 0; i < ri.key_count; i++) { + DATA_BLOB list_data; + + /* Get sublist data blob */ + list_data = hbin_get(private_data->hive, ri.offset[i]); + if (list_data.data == NULL) { + DEBUG(0, ("Error getting RI list.\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + + pull->data = list_data; + + if (!strncmp((char *)list_data.data, "li", 2)) { + struct li_block li; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, + nk, + &li))) { + DEBUG(0, ("Error parsing LI list from RI\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + for (j = 0; j < li.key_count; j++) { + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, key, + li.nk_offset[j], + name, + &key_off)); + if (key_off) + break; + } + } else if (!strncmp((char *)list_data.data, "lh", 2)) { + struct lh_block lh; + uint32_t hash; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, + nk, + &lh))) { + DEBUG(0, ("Error parsing LH list from RI\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + hash = regf_create_lh_hash(name); + for (j = 0; j < lh.key_count; j++) { + if (lh.hr[j].base37 != hash) { + continue; + } + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, key, + lh.hr[j].nk_offset, + name, + &key_off)); + if (key_off) + break; + } + } + if (key_off) + break; + } + talloc_free(pull); + if (!key_off) + return WERR_FILE_NOT_FOUND; + } else { + DEBUG(0, ("Unknown subkey list type.\n")); + return WERR_GEN_FAILURE; + } + + *ret = (struct hive_key *)regf_get_key(ctx, private_data->hive, + key_off); + return WERR_OK; +} + +static WERROR regf_set_sec_desc(struct hive_key *key, + const struct security_descriptor *sec_desc) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + struct sk_block cur_sk, sk, new_sk; + struct regf_data *regf = private_data->hive; + struct nk_block root; + DATA_BLOB data; + uint32_t sk_offset, cur_sk_offset; + bool update_cur_sk = false; + + /* Get the root nk */ + hbin_get_tdr(regf, regf->header->data_offset, regf, + (tdr_pull_fn_t) tdr_pull_nk_block, &root); + + /* Push the security descriptor to a blob */ + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(&data, regf, + sec_desc, (ndr_push_flags_fn_t)ndr_push_security_descriptor))) { + DEBUG(0, ("Unable to push security descriptor\n")); + return WERR_GEN_FAILURE; + } + + /* Get the current security descriptor for the key */ + if (!hbin_get_tdr(regf, private_data->nk->sk_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &cur_sk)) { + DEBUG(0, ("Unable to find security descriptor for current key\n")); + return WERR_FILE_NOT_FOUND; + } + /* If there's no change, change nothing. */ + if (memcmp(data.data, cur_sk.sec_desc, + MIN(data.length, cur_sk.rec_size)) == 0) { + return WERR_OK; + } + + /* Delete the current sk if only this key is using it */ + if (cur_sk.ref_cnt == 1) { + /* Get the previous security descriptor for the key */ + if (!hbin_get_tdr(regf, cur_sk.prev_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find prev security descriptor for current key\n")); + return WERR_FILE_NOT_FOUND; + } + /* Change and store the previous security descriptor */ + sk.next_offset = cur_sk.next_offset; + hbin_store_tdr_resize(regf, (tdr_push_fn_t) tdr_push_sk_block, + cur_sk.prev_offset, &sk); + + /* Get the next security descriptor for the key */ + if (!hbin_get_tdr(regf, cur_sk.next_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find next security descriptor for current key\n")); + return WERR_FILE_NOT_FOUND; + } + /* Change and store the next security descriptor */ + sk.prev_offset = cur_sk.prev_offset; + hbin_store_tdr_resize(regf, (tdr_push_fn_t) tdr_push_sk_block, + cur_sk.next_offset, &sk); + + hbin_free(regf, private_data->nk->sk_offset); + } else { + /* This key will no longer be referring to this sk */ + cur_sk.ref_cnt--; + update_cur_sk = true; + } + + sk_offset = root.sk_offset; + + do { + cur_sk_offset = sk_offset; + if (!hbin_get_tdr(regf, sk_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find security descriptor\n")); + return WERR_FILE_NOT_FOUND; + } + if (memcmp(data.data, sk.sec_desc, MIN(data.length, sk.rec_size)) == 0) { + private_data->nk->sk_offset = sk_offset; + sk.ref_cnt++; + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + sk_offset, &sk); + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_nk_block, + private_data->offset, + private_data->nk); + return WERR_OK; + } + sk_offset = sk.next_offset; + } while (sk_offset != root.sk_offset); + + ZERO_STRUCT(new_sk); + new_sk.header = "sk"; + new_sk.prev_offset = cur_sk_offset; + new_sk.next_offset = root.sk_offset; + new_sk.ref_cnt = 1; + new_sk.rec_size = data.length; + new_sk.sec_desc = data.data; + + sk_offset = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_sk_block, + &new_sk); + if (sk_offset == -1) { + DEBUG(0, ("Error storing sk block\n")); + return WERR_GEN_FAILURE; + } + private_data->nk->sk_offset = sk_offset; + + if (update_cur_sk) { + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + private_data->nk->sk_offset, &cur_sk); + } + + /* Get the previous security descriptor for the key */ + if (!hbin_get_tdr(regf, new_sk.prev_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find security descriptor for previous key\n")); + return WERR_FILE_NOT_FOUND; + } + /* Change and store the previous security descriptor */ + sk.next_offset = sk_offset; + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + cur_sk.prev_offset, &sk); + + /* Get the next security descriptor for the key (always root, as we append) */ + if (!hbin_get_tdr(regf, new_sk.next_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find security descriptor for current key\n")); + return WERR_FILE_NOT_FOUND; + } + /* Change and store the next security descriptor (always root, as we append) */ + sk.prev_offset = sk_offset; + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + root.sk_offset, &sk); + + + /* Store the nk. */ + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + private_data->offset, private_data->nk); + return WERR_OK; +} + +static WERROR regf_get_sec_desc(TALLOC_CTX *ctx, const struct hive_key *key, + struct security_descriptor **sd) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + struct sk_block sk; + struct regf_data *regf = private_data->hive; + DATA_BLOB data; + + if (!hbin_get_tdr(regf, private_data->nk->sk_offset, ctx, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find security descriptor\n")); + return WERR_GEN_FAILURE; + } + + if (strcmp(sk.header, "sk") != 0) { + DEBUG(0, ("Expected 'sk', got '%s'\n", sk.header)); + return WERR_GEN_FAILURE; + } + + *sd = talloc(ctx, struct security_descriptor); + W_ERROR_HAVE_NO_MEMORY(*sd); + + data.data = sk.sec_desc; + data.length = sk.rec_size; + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_pull_struct_blob(&data, ctx, *sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor))) { + DEBUG(0, ("Error parsing security descriptor\n")); + return WERR_GEN_FAILURE; + } + + return WERR_OK; +} + +static WERROR regf_sl_add_entry(struct regf_data *regf, uint32_t list_offset, + const char *name, + uint32_t key_offset, uint32_t *ret) +{ + DATA_BLOB data; + + /* Create a new key if necessary */ + if (list_offset == -1) { + if (regf->header->version.major != 1) { + DEBUG(0, ("Can't store keys in unknown registry format\n")); + return WERR_NOT_SUPPORTED; + } + if (regf->header->version.minor < 3) { + /* Store LI */ + struct li_block li; + ZERO_STRUCT(li); + li.header = "li"; + li.key_count = 1; + + li.nk_offset = talloc_array(regf, uint32_t, 1); + W_ERROR_HAVE_NO_MEMORY(li.nk_offset); + li.nk_offset[0] = key_offset; + + *ret = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_li_block, + &li); + + talloc_free(li.nk_offset); + } else if (regf->header->version.minor == 3 || + regf->header->version.minor == 4) { + /* Store LF */ + struct lf_block lf; + ZERO_STRUCT(lf); + lf.header = "lf"; + lf.key_count = 1; + + lf.hr = talloc_array(regf, struct hash_record, 1); + W_ERROR_HAVE_NO_MEMORY(lf.hr); + lf.hr[0].nk_offset = key_offset; + lf.hr[0].hash = talloc_strndup(lf.hr, name, 4); + W_ERROR_HAVE_NO_MEMORY(lf.hr[0].hash); + + *ret = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_lf_block, + &lf); + + talloc_free(lf.hr); + } else if (regf->header->version.minor == 5) { + /* Store LH */ + struct lh_block lh; + ZERO_STRUCT(lh); + lh.header = "lh"; + lh.key_count = 1; + + lh.hr = talloc_array(regf, struct lh_hash, 1); + W_ERROR_HAVE_NO_MEMORY(lh.hr); + lh.hr[0].nk_offset = key_offset; + lh.hr[0].base37 = regf_create_lh_hash(name); + + *ret = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_lh_block, + &lh); + + talloc_free(lh.hr); + } + return WERR_OK; + } + + data = hbin_get(regf, list_offset); + if (!data.data) { + DEBUG(0, ("Unable to find subkey list\n")); + return WERR_FILE_NOT_FOUND; + } + + if (!strncmp((char *)data.data, "li", 2)) { + struct tdr_pull *pull = tdr_pull_init(regf); + struct li_block li; + struct nk_block sub_nk; + int32_t i, j; + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, regf, &li))) { + DEBUG(0, ("Error parsing LI list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + + if (strncmp(li.header, "li", 2) != 0) { + abort(); + DEBUG(0, ("LI header corrupt\n")); + return WERR_FILE_NOT_FOUND; + } + + /* + * Find the position to store the pointer + * Extensive testing reveils that at least on windows 7 subkeys + * *MUST* be stored in alphabetical order + */ + for (i = 0; i < li.key_count; i++) { + /* Get the nk */ + hbin_get_tdr(regf, li.nk_offset[i], regf, + (tdr_pull_fn_t) tdr_pull_nk_block, &sub_nk); + if (strcasecmp(name, sub_nk.key_name) < 0) { + break; + } + } + + li.nk_offset = talloc_realloc(regf, li.nk_offset, + uint32_t, li.key_count+1); + W_ERROR_HAVE_NO_MEMORY(li.nk_offset); + + /* Move everything behind this offset */ + for (j = li.key_count - 1; j >= i; j--) { + li.nk_offset[j+1] = li.nk_offset[j]; + } + + li.nk_offset[i] = key_offset; + li.key_count++; + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t)tdr_push_li_block, + list_offset, &li); + + talloc_free(li.nk_offset); + } else if (!strncmp((char *)data.data, "lf", 2)) { + struct tdr_pull *pull = tdr_pull_init(regf); + struct lf_block lf; + struct nk_block sub_nk; + int32_t i, j; + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull, regf, &lf))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lf.header, "lf", 2)); + + /* + * Find the position to store the hash record + * Extensive testing reveils that at least on windows 7 subkeys + * *MUST* be stored in alphabetical order + */ + for (i = 0; i < lf.key_count; i++) { + /* Get the nk */ + hbin_get_tdr(regf, lf.hr[i].nk_offset, regf, + (tdr_pull_fn_t) tdr_pull_nk_block, &sub_nk); + if (strcasecmp(name, sub_nk.key_name) < 0) { + break; + } + } + + lf.hr = talloc_realloc(regf, lf.hr, struct hash_record, + lf.key_count+1); + W_ERROR_HAVE_NO_MEMORY(lf.hr); + + /* Move everything behind this hash record */ + for (j = lf.key_count - 1; j >= i; j--) { + lf.hr[j+1] = lf.hr[j]; + } + + lf.hr[i].nk_offset = key_offset; + lf.hr[i].hash = talloc_strndup(lf.hr, name, 4); + W_ERROR_HAVE_NO_MEMORY(lf.hr[lf.key_count].hash); + lf.key_count++; + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t)tdr_push_lf_block, + list_offset, &lf); + + talloc_free(lf.hr); + } else if (!strncmp((char *)data.data, "lh", 2)) { + struct tdr_pull *pull = tdr_pull_init(regf); + struct lh_block lh; + struct nk_block sub_nk; + int32_t i, j; + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, regf, &lh))) { + DEBUG(0, ("Error parsing LH list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + /* + * Find the position to store the hash record + * Extensive testing reveils that at least on windows 7 subkeys + * *MUST* be stored in alphabetical order + */ + for (i = 0; i < lh.key_count; i++) { + /* Get the nk */ + hbin_get_tdr(regf, lh.hr[i].nk_offset, regf, + (tdr_pull_fn_t) tdr_pull_nk_block, &sub_nk); + if (strcasecmp(name, sub_nk.key_name) < 0) { + break; + } + } + + lh.hr = talloc_realloc(regf, lh.hr, struct lh_hash, + lh.key_count+1); + W_ERROR_HAVE_NO_MEMORY(lh.hr); + + /* Move everything behind this hash record */ + for (j = lh.key_count - 1; j >= i; j--) { + lh.hr[j+1] = lh.hr[j]; + } + + lh.hr[i].nk_offset = key_offset; + lh.hr[i].base37 = regf_create_lh_hash(name); + lh.key_count++; + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t)tdr_push_lh_block, + list_offset, &lh); + + talloc_free(lh.hr); + } else if (!strncmp((char *)data.data, "ri", 2)) { + /* FIXME */ + DEBUG(0, ("Adding to 'ri' subkey list is not supported yet.\n")); + return WERR_NOT_SUPPORTED; + } else { + DEBUG(0, ("Cannot add to unknown subkey list\n")); + return WERR_FILE_NOT_FOUND; + } + + return WERR_OK; +} + +static WERROR regf_sl_del_entry(struct regf_data *regf, uint32_t list_offset, + uint32_t key_offset, uint32_t *ret) +{ + DATA_BLOB data; + + data = hbin_get(regf, list_offset); + if (!data.data) { + DEBUG(0, ("Unable to find subkey list\n")); + return WERR_FILE_NOT_FOUND; + } + + if (strncmp((char *)data.data, "li", 2) == 0) { + struct li_block li; + struct tdr_pull *pull = tdr_pull_init(regf); + uint16_t i; + bool found_offset = false; + + DEBUG(10, ("Subkeys in LI list\n")); + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, regf, &li))) { + DEBUG(0, ("Error parsing LI list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + for (i = 0; i < li.key_count; i++) { + if (found_offset) { + li.nk_offset[i-1] = li.nk_offset[i]; + } + if (li.nk_offset[i] == key_offset) { + found_offset = true; + continue; + } + } + if (!found_offset) { + DEBUG(2, ("Subkey not found\n")); + return WERR_FILE_NOT_FOUND; + } + li.key_count--; + + /* If the there are no entries left, free the subkey list */ + if (li.key_count == 0) { + hbin_free(regf, list_offset); + *ret = -1; + } + + /* Store li block */ + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_li_block, + list_offset, &li); + } else if (strncmp((char *)data.data, "lf", 2) == 0) { + struct lf_block lf; + struct tdr_pull *pull = tdr_pull_init(regf); + uint16_t i; + bool found_offset = false; + + DEBUG(10, ("Subkeys in LF list\n")); + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull, regf, &lf))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + + SMB_ASSERT(!strncmp(lf.header, "lf", 2)); + + for (i = 0; i < lf.key_count; i++) { + if (found_offset) { + lf.hr[i-1] = lf.hr[i]; + continue; + } + if (lf.hr[i].nk_offset == key_offset) { + found_offset = 1; + continue; + } + } + if (!found_offset) { + DEBUG(2, ("Subkey not found\n")); + return WERR_FILE_NOT_FOUND; + } + lf.key_count--; + + /* If the there are no entries left, free the subkey list */ + if (lf.key_count == 0) { + hbin_free(regf, list_offset); + *ret = -1; + return WERR_OK; + } + + /* Store lf block */ + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_lf_block, + list_offset, &lf); + } else if (strncmp((char *)data.data, "lh", 2) == 0) { + struct lh_block lh; + struct tdr_pull *pull = tdr_pull_init(regf); + uint16_t i; + bool found_offset = false; + + DEBUG(10, ("Subkeys in LH list\n")); + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, regf, &lh))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + for (i = 0; i < lh.key_count; i++) { + if (found_offset) { + lh.hr[i-1] = lh.hr[i]; + continue; + } + if (lh.hr[i].nk_offset == key_offset) { + found_offset = 1; + continue; + } + } + if (!found_offset) { + DEBUG(0, ("Subkey not found\n")); + return WERR_FILE_NOT_FOUND; + } + lh.key_count--; + + /* If the there are no entries left, free the subkey list */ + if (lh.key_count == 0) { + hbin_free(regf, list_offset); + *ret = -1; + return WERR_OK; + } + + /* Store lh block */ + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_lh_block, + list_offset, &lh); + } else if (strncmp((char *)data.data, "ri", 2) == 0) { + /* FIXME */ + DEBUG(0, ("Sorry, deletion from ri block is not supported yet.\n")); + return WERR_NOT_SUPPORTED; + } else { + DEBUG (0, ("Unknown header found in subkey list.\n")); + return WERR_FILE_NOT_FOUND; + } + return WERR_OK; +} + +static WERROR regf_del_value(TALLOC_CTX *mem_ctx, struct hive_key *key, + const char *name) +{ + struct regf_key_data *private_data = (struct regf_key_data *)key; + struct regf_data *regf = private_data->hive; + struct nk_block *nk = private_data->nk; + struct vk_block vk; + uint32_t vk_offset; + bool found_offset = false; + DATA_BLOB values; + unsigned int i; + + if (nk->values_offset == -1) { + return WERR_FILE_NOT_FOUND; + } + + values = hbin_get(regf, nk->values_offset); + + for (i = 0; i < nk->num_values; i++) { + if (found_offset) { + ((uint32_t *)values.data)[i-1] = ((uint32_t *) values.data)[i]; + } else { + vk_offset = IVAL(values.data, i * 4); + if (!hbin_get_tdr(regf, vk_offset, private_data, + (tdr_pull_fn_t)tdr_pull_vk_block, + &vk)) { + DEBUG(0, ("Unable to get VK block at %d\n", + vk_offset)); + return WERR_FILE_NOT_FOUND; + } + if (strcmp(vk.data_name, name) == 0) { + hbin_free(regf, vk_offset); + found_offset = true; + } + } + } + if (!found_offset) { + return WERR_FILE_NOT_FOUND; + } else { + nk->num_values--; + values.length = (nk->num_values)*4; + } + + /* Store values list and nk */ + if (nk->num_values == 0) { + hbin_free(regf, nk->values_offset); + nk->values_offset = -1; + } else { + nk->values_offset = hbin_store_resize(regf, + nk->values_offset, + values); + } + hbin_store_tdr_resize(regf, (tdr_push_fn_t) tdr_push_nk_block, + private_data->offset, nk); + + return regf_save_hbin(private_data->hive, 0); +} + + +static WERROR regf_del_key(TALLOC_CTX *mem_ctx, const struct hive_key *parent, + const char *name) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)parent; + struct regf_key_data *key; + struct nk_block *parent_nk; + WERROR error; + + SMB_ASSERT(private_data); + + parent_nk = private_data->nk; + + if (parent_nk->subkeys_offset == -1) { + DEBUG(4, ("Subkey list is empty, this key cannot contain subkeys.\n")); + return WERR_FILE_NOT_FOUND; + } + + /* Find the key */ + if (!W_ERROR_IS_OK(regf_get_subkey_by_name(parent_nk, parent, name, + (struct hive_key **)&key))) { + DEBUG(2, ("Key '%s' not found\n", name)); + return WERR_FILE_NOT_FOUND; + } + + if (key->nk->subkeys_offset != -1) { + struct hive_key *sk = (struct hive_key *)key; + unsigned int i = key->nk->num_subkeys; + while (i--) { + char *sk_name; + const char *p = NULL; + + /* Get subkey information. */ + error = regf_get_subkey_by_index(parent_nk, sk, 0, + &p, + NULL, NULL); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't retrieve subkey by index.\n")); + return error; + } + sk_name = discard_const_p(char, p); + + /* Delete subkey. */ + error = regf_del_key(NULL, sk, sk_name); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't delete key '%s'.\n", sk_name)); + return error; + } + + talloc_free(sk_name); + } + } + + if (key->nk->values_offset != -1) { + struct hive_key *sk = (struct hive_key *)key; + DATA_BLOB data; + unsigned int i = key->nk->num_values; + while (i--) { + char *val_name; + const char *p = NULL; + + /* Get value information. */ + error = regf_get_value(parent_nk, sk, 0, + &p, + NULL, &data); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't retrieve value by index.\n")); + return error; + } + val_name = discard_const_p(char, p); + + /* Delete value. */ + error = regf_del_value(NULL, sk, val_name); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't delete value '%s'.\n", val_name)); + return error; + } + + talloc_free(val_name); + } + } + + /* Delete it from the subkey list. */ + error = regf_sl_del_entry(private_data->hive, parent_nk->subkeys_offset, + key->offset, &parent_nk->subkeys_offset); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't store new subkey list for parent key. Won't delete.\n")); + return error; + } + + /* Re-store parent key */ + parent_nk->num_subkeys--; + hbin_store_tdr_resize(private_data->hive, + (tdr_push_fn_t) tdr_push_nk_block, + private_data->offset, parent_nk); + + if (key->nk->clsname_offset != -1) { + hbin_free(private_data->hive, key->nk->clsname_offset); + } + hbin_free(private_data->hive, key->offset); + + return regf_save_hbin(private_data->hive, 0); +} + +static WERROR regf_add_key(TALLOC_CTX *ctx, const struct hive_key *parent, + const char *name, const char *classname, + struct security_descriptor *sec_desc, + struct hive_key **ret) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)parent; + struct nk_block *parent_nk = private_data->nk, nk; + struct nk_block *root; + struct regf_data *regf = private_data->hive; + uint32_t offset; + WERROR error; + + nk.header = "nk"; + nk.type = REG_SUB_KEY; + unix_to_nt_time(&nk.last_change, time(NULL)); + nk.uk1 = 0; + nk.parent_offset = private_data->offset; + nk.num_subkeys = 0; + nk.uk2 = 0; + nk.subkeys_offset = -1; + nk.unknown_offset = -1; + nk.num_values = 0; + nk.values_offset = -1; + memset(nk.unk3, 0, sizeof(nk.unk3)); + nk.clsname_offset = -1; /* FIXME: fill in */ + nk.clsname_length = 0; + nk.key_name = name; + + /* Get the security descriptor of the root key */ + root = talloc_zero(ctx, struct nk_block); + W_ERROR_HAVE_NO_MEMORY(root); + + if (!hbin_get_tdr(regf, regf->header->data_offset, root, + (tdr_pull_fn_t)tdr_pull_nk_block, root)) { + DEBUG(0, ("Unable to find HBIN data for offset 0x%x\n", + regf->header->data_offset)); + return WERR_GEN_FAILURE; + } + nk.sk_offset = root->sk_offset; + talloc_free(root); + + /* Store the new nk key */ + offset = hbin_store_tdr(regf, (tdr_push_fn_t) tdr_push_nk_block, &nk); + + error = regf_sl_add_entry(regf, parent_nk->subkeys_offset, name, offset, + &parent_nk->subkeys_offset); + if (!W_ERROR_IS_OK(error)) { + hbin_free(regf, offset); + return error; + } + + parent_nk->num_subkeys++; + + /* Since the subkey offset of the parent can change, store it again */ + hbin_store_tdr_resize(regf, (tdr_push_fn_t) tdr_push_nk_block, + nk.parent_offset, parent_nk); + + *ret = (struct hive_key *)regf_get_key(ctx, regf, offset); + + DEBUG(9, ("Storing key %s\n", name)); + return regf_save_hbin(private_data->hive, 0); +} + +static WERROR regf_set_value(struct hive_key *key, const char *name, + uint32_t type, const DATA_BLOB data) +{ + struct regf_key_data *private_data = (struct regf_key_data *)key; + struct regf_data *regf = private_data->hive; + struct nk_block *nk = private_data->nk; + struct vk_block vk; + uint32_t i; + uint32_t tmp_vk_offset, vk_offset, old_vk_offset = (uint32_t) -1; + DATA_BLOB values = {0}; + + ZERO_STRUCT(vk); + + /* find the value offset, if it exists */ + if (nk->values_offset != -1) { + values = hbin_get(regf, nk->values_offset); + + for (i = 0; i < nk->num_values; i++) { + tmp_vk_offset = IVAL(values.data, i * 4); + if (!hbin_get_tdr(regf, tmp_vk_offset, private_data, + (tdr_pull_fn_t)tdr_pull_vk_block, + &vk)) { + DEBUG(0, ("Unable to get VK block at 0x%x\n", + tmp_vk_offset)); + return WERR_GEN_FAILURE; + } + if (strcmp(vk.data_name, name) == 0) { + old_vk_offset = tmp_vk_offset; + break; + } + } + } + + /* If it's new, create the vk struct, if it's old, free the old data. */ + if (old_vk_offset == -1) { + vk.header = "vk"; + if (name != NULL && name[0] != '\0') { + vk.flag = 1; + vk.data_name = name; + vk.name_length = strlen(name); + } else { + vk.flag = 0; + vk.data_name = NULL; + vk.name_length = 0; + } + } else { + /* Free data, if any */ + if (!(vk.data_length & 0x80000000)) { + hbin_free(regf, vk.data_offset); + } + } + + /* Set the type and data */ + vk.data_length = data.length; + vk.data_type = type; + if ((type == REG_DWORD) || (type == REG_DWORD_BIG_ENDIAN)) { + if (vk.data_length != sizeof(uint32_t)) { + DEBUG(0, ("DWORD or DWORD_BIG_ENDIAN value with size other than 4 byte!\n")); + return WERR_NOT_SUPPORTED; + } + vk.data_length |= 0x80000000; + vk.data_offset = IVAL(data.data, 0); + } else { + /* Store data somewhere */ + vk.data_offset = hbin_store(regf, data); + } + if (old_vk_offset == -1) { + /* Store new vk */ + vk_offset = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_vk_block, + &vk); + } else { + /* Store vk at offset */ + vk_offset = hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_vk_block, + old_vk_offset ,&vk); + } + + /* Re-allocate the value list */ + if (nk->values_offset == -1) { + nk->values_offset = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_uint32, + &vk_offset); + nk->num_values = 1; + } else { + + /* Change if we're changing, otherwise we're adding the value */ + if (old_vk_offset != -1) { + /* Find and overwrite the offset. */ + for (i = 0; i < nk->num_values; i++) { + if (IVAL(values.data, i * 4) == old_vk_offset) { + SIVAL(values.data, i * 4, vk_offset); + break; + } + } + } else { + /* Create a new value list */ + DATA_BLOB value_list; + + value_list.length = (nk->num_values+1)*4; + value_list.data = (uint8_t *)talloc_array(private_data, + uint32_t, + nk->num_values+1); + W_ERROR_HAVE_NO_MEMORY(value_list.data); + memcpy(value_list.data, values.data, nk->num_values * 4); + + SIVAL(value_list.data, nk->num_values * 4, vk_offset); + nk->num_values++; + nk->values_offset = hbin_store_resize(regf, + nk->values_offset, + value_list); + } + + } + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_nk_block, + private_data->offset, nk); + return regf_save_hbin(private_data->hive, 0); +} + +static WERROR regf_save_hbin(struct regf_data *regf, bool flush) +{ + struct tdr_push *push = tdr_push_init(regf); + unsigned int i; + + W_ERROR_HAVE_NO_MEMORY(push); + + /* Only write once every 5 seconds, or when flush is set */ + if (!flush && regf->last_write + 5 >= time(NULL)) { + return WERR_OK; + } + + regf->last_write = time(NULL); + + if (lseek(regf->fd, 0, SEEK_SET) == -1) { + DEBUG(0, ("Error lseeking in regf file\n")); + return WERR_GEN_FAILURE; + } + + /* Recompute checksum */ + if (NT_STATUS_IS_ERR(tdr_push_regf_hdr(push, regf->header))) { + DEBUG(0, ("Failed to push regf header\n")); + return WERR_GEN_FAILURE; + } + regf->header->chksum = regf_hdr_checksum(push->data.data); + talloc_free(push); + + if (NT_STATUS_IS_ERR(tdr_push_to_fd(regf->fd, + (tdr_push_fn_t)tdr_push_regf_hdr, + regf->header))) { + DEBUG(0, ("Error writing registry file header\n")); + return WERR_GEN_FAILURE; + } + + if (lseek(regf->fd, 0x1000, SEEK_SET) == -1) { + DEBUG(0, ("Error lseeking to 0x1000 in regf file\n")); + return WERR_GEN_FAILURE; + } + + for (i = 0; regf->hbins[i]; i++) { + if (NT_STATUS_IS_ERR(tdr_push_to_fd(regf->fd, + (tdr_push_fn_t)tdr_push_hbin_block, + regf->hbins[i]))) { + DEBUG(0, ("Error writing HBIN block\n")); + return WERR_GEN_FAILURE; + } + } + + return WERR_OK; +} + +WERROR reg_create_regf_file(TALLOC_CTX *parent_ctx, + const char *location, + int minor_version, struct hive_key **key) +{ + struct regf_data *regf; + struct regf_hdr *regf_hdr; + struct nk_block nk; + struct sk_block sk; + WERROR error; + DATA_BLOB data; + struct security_descriptor *sd; + uint32_t sk_offset; + + regf = (struct regf_data *)talloc_zero(NULL, struct regf_data); + + W_ERROR_HAVE_NO_MEMORY(regf); + + DEBUG(5, ("Attempting to create registry file\n")); + + /* Get the header */ + regf->fd = creat(location, 0644); + + if (regf->fd == -1) { + DEBUG(0,("Could not create file: %s, %s\n", location, + strerror(errno))); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + regf_hdr = talloc_zero(regf, struct regf_hdr); + W_ERROR_HAVE_NO_MEMORY(regf_hdr); + regf_hdr->REGF_ID = "regf"; + unix_to_nt_time(®f_hdr->modtime, time(NULL)); + regf_hdr->version.major = 1; + regf_hdr->version.minor = minor_version; + regf_hdr->last_block = 0x1000; /* Block size */ + regf_hdr->description = talloc_strdup(regf_hdr, + "Registry created by Samba 4"); + W_ERROR_HAVE_NO_MEMORY(regf_hdr->description); + regf_hdr->chksum = 0; + + regf->header = regf_hdr; + + /* Create all hbin blocks */ + regf->hbins = talloc_array(regf, struct hbin_block *, 1); + W_ERROR_HAVE_NO_MEMORY(regf->hbins); + regf->hbins[0] = NULL; + + nk.header = "nk"; + nk.type = REG_ROOT_KEY; + unix_to_nt_time(&nk.last_change, time(NULL)); + nk.uk1 = 0; + nk.parent_offset = -1; + nk.num_subkeys = 0; + nk.uk2 = 0; + nk.subkeys_offset = -1; + nk.unknown_offset = -1; + nk.num_values = 0; + nk.values_offset = -1; + memset(nk.unk3, 0, 5 * sizeof(uint32_t)); + nk.clsname_offset = -1; + nk.clsname_length = 0; + nk.sk_offset = 0x80; + nk.key_name = "SambaRootKey"; + + /* + * It should be noted that changing the key_name to something shorter + * creates a shorter nk block, which makes the position of the sk block + * change. All Windows registries I've seen have the sk at 0x80. + * I therefore recommend that our regf files share that offset -- Wilco + */ + + /* Create a security descriptor. */ + sd = security_descriptor_dacl_create(regf, + 0, + NULL, NULL, + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + + /* Push the security descriptor to a blob */ + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(&data, regf, + sd, (ndr_push_flags_fn_t)ndr_push_security_descriptor))) { + DEBUG(0, ("Unable to push security descriptor\n")); + return WERR_GEN_FAILURE; + } + + ZERO_STRUCT(sk); + sk.header = "sk"; + sk.prev_offset = 0x80; + sk.next_offset = 0x80; + sk.ref_cnt = 1; + sk.rec_size = data.length; + sk.sec_desc = data.data; + + /* Store the new nk key */ + regf->header->data_offset = hbin_store_tdr(regf, + (tdr_push_fn_t)tdr_push_nk_block, + &nk); + /* Store the sk block */ + sk_offset = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_sk_block, + &sk); + if (sk_offset != 0x80) { + DEBUG(0, ("Error storing sk block, should be at 0x80, stored at 0x%x\n", nk.sk_offset)); + return WERR_GEN_FAILURE; + } + + + *key = (struct hive_key *)regf_get_key(parent_ctx, regf, + regf->header->data_offset); + + error = regf_save_hbin(regf, 1); + if (!W_ERROR_IS_OK(error)) { + return error; + } + + /* We can drop our own reference now that *key will have created one */ + talloc_unlink(NULL, regf); + + return WERR_OK; +} + +static WERROR regf_flush_key(struct hive_key *key) +{ + struct regf_key_data *private_data = (struct regf_key_data *)key; + struct regf_data *regf = private_data->hive; + WERROR error; + + error = regf_save_hbin(regf, 1); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Failed to flush regf to disk\n")); + return error; + } + + return WERR_OK; +} + +static int regf_destruct(struct regf_data *regf) +{ + WERROR error; + + /* Write to disk */ + error = regf_save_hbin(regf, 1); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Failed to flush registry to disk\n")); + return -1; + } + + /* Close file descriptor */ + close(regf->fd); + + return 0; +} + +WERROR reg_open_regf_file(TALLOC_CTX *parent_ctx, const char *location, + struct hive_key **key) +{ + struct regf_data *regf; + struct regf_hdr *regf_hdr; + struct tdr_pull *pull; + unsigned int i; + + regf = (struct regf_data *)talloc_zero(parent_ctx, struct regf_data); + W_ERROR_HAVE_NO_MEMORY(regf); + + talloc_set_destructor(regf, regf_destruct); + + DEBUG(5, ("Attempting to load registry file\n")); + + /* Get the header */ + regf->fd = open(location, O_RDWR); + + if (regf->fd == -1) { + DEBUG(0,("Could not load file: %s, %s\n", location, + strerror(errno))); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + pull = tdr_pull_init(regf); + + pull->data.data = (uint8_t*)fd_load(regf->fd, &pull->data.length, 0, regf); + + if (pull->data.data == NULL) { + DEBUG(0, ("Error reading data from file: %s\n", location)); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + regf_hdr = talloc(regf, struct regf_hdr); + W_ERROR_HAVE_NO_MEMORY(regf_hdr); + + if (NT_STATUS_IS_ERR(tdr_pull_regf_hdr(pull, regf_hdr, regf_hdr))) { + DEBUG(0, ("Failed to pull regf header from file: %s\n", location)); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + regf->header = regf_hdr; + + if (strcmp(regf_hdr->REGF_ID, "regf") != 0) { + DEBUG(0, ("Unrecognized NT registry header id: %s, %s\n", + regf_hdr->REGF_ID, location)); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + /* Validate the header ... */ + if (regf_hdr_checksum(pull->data.data) != regf_hdr->chksum) { + DEBUG(0, ("Registry file checksum error: %s: %d,%d\n", + location, regf_hdr->chksum, + regf_hdr_checksum(pull->data.data))); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + pull->offset = 0x1000; + + i = 0; + /* Read in all hbin blocks */ + regf->hbins = talloc_array(regf, struct hbin_block *, 1); + W_ERROR_HAVE_NO_MEMORY(regf->hbins); + + regf->hbins[0] = NULL; + + while (pull->offset < pull->data.length && + pull->offset <= regf->header->last_block) { + struct hbin_block *hbin = talloc(regf->hbins, + struct hbin_block); + + W_ERROR_HAVE_NO_MEMORY(hbin); + + if (NT_STATUS_IS_ERR(tdr_pull_hbin_block(pull, hbin, hbin))) { + DEBUG(0, ("[%d] Error parsing HBIN block\n", i)); + talloc_free(regf); + return WERR_FOOBAR; + } + + if (strcmp(hbin->HBIN_ID, "hbin") != 0) { + DEBUG(0, ("[%d] Expected 'hbin', got '%s'\n", + i, hbin->HBIN_ID)); + talloc_free(regf); + return WERR_FOOBAR; + } + + regf->hbins[i] = hbin; + i++; + regf->hbins = talloc_realloc(regf, regf->hbins, + struct hbin_block *, i+2); + regf->hbins[i] = NULL; + } + + talloc_free(pull); + + DEBUG(1, ("%d HBIN blocks read\n", i)); + + *key = (struct hive_key *)regf_get_key(parent_ctx, regf, + regf->header->data_offset); + + /* We can drop our own reference now that *key will have created one */ + talloc_unlink(parent_ctx, regf); + + return WERR_OK; +} + +static struct hive_operations reg_backend_regf = { + .name = "regf", + .get_key_info = regf_get_info, + .enum_key = regf_get_subkey_by_index, + .get_key_by_name = regf_get_subkey_by_name, + .get_value_by_name = regf_get_value_by_name, + .enum_value = regf_get_value, + .get_sec_desc = regf_get_sec_desc, + .set_sec_desc = regf_set_sec_desc, + .add_key = regf_add_key, + .set_value = regf_set_value, + .del_key = regf_del_key, + .delete_value = regf_del_value, + .flush_key = regf_flush_key +}; diff --git a/source4/lib/registry/regf.idl b/source4/lib/registry/regf.idl new file mode 100644 index 0000000..064aaf0 --- /dev/null +++ b/source4/lib/registry/regf.idl @@ -0,0 +1,167 @@ +/* + Definitions for the REGF registry file format as used by + Windows NT4 and above. + + Copyright (C) 2005 Jelmer Vernooij, jelmer@samba.org + Copyright (C) 2006 Wilco Baan Hofman, wilco@baanhofman.nl + + Based on two files from Samba 3: + regedit.c by Richard Sharpe + regfio.c by Jerry Carter + +*/ + +interface regf +{ + const int REGF_OFFSET_NONE = 0xffffffff; + + /* + * Registry version number + * 1.2.0.1 for WinNT 3.51 + * 1.3.0.1 for WinNT 4 + * 1.5.0.1 for WinXP + */ + + [noprint] struct regf_version { + [value(1)] uint32 major; + uint32 minor; + [value(0)] uint32 release; + [value(1)] uint32 build; + }; + + /* + "regf" is obviously the abbreviation for "Registry file". "regf" is the + signature of the header-block which is always 4kb in size, although only + the first 64 bytes seem to be used and a checksum is calculated over + the first 0x200 bytes only! + */ + + [public,noprint] struct regf_hdr { + [charset(DOS)] uint8 REGF_ID[4]; /* 'regf' */ + uint32 update_counter1; + uint32 update_counter2; + NTTIME modtime; + regf_version version; + uint32 data_offset; + uint32 last_block; + [value(1)] uint32 uk7; /* 1 */ + [charset(UTF16)] uint16 description[0x20]; + uint32 padding[99]; /* Padding */ + /* Checksum of first 0x200 bytes XOR-ed */ + uint32 chksum; + }; + + /* + hbin probably means hive-bin (i.e. hive-container) + This block is always a multiple + of 4kb in size. + */ + [public,noprint] struct hbin_block { + [charset(DOS)] uint8 HBIN_ID[4]; /* hbin */ + uint32 offset_from_first; /* Offset from 1st hbin-Block */ + uint32 offset_to_next; /* Offset to the next hbin-Block */ + uint32 unknown[2]; + NTTIME last_change; + uint32 block_size; /* Block size (including the header!) */ + uint8 data[offset_to_next-0x20]; + /* data is filled with: + uint32 length; + Negative if in use, positive otherwise + Always a multiple of 8 + uint8_t data[length]; + Free space marker if 0xffffffff + */ + }; + + [noprint] enum reg_key_type { + REG_ROOT_KEY = 0x2C, + REG_SUB_KEY = 0x20, + REG_SYM_LINK = 0x10 + }; + + /* + The nk-record can be treated as a combination of tree-record and + key-record of the win 95 registry. + */ + [public,noprint] struct nk_block { + [charset(DOS)] uint8 header[2]; + reg_key_type type; + NTTIME last_change; + uint32 uk1; + uint32 parent_offset; + uint32 num_subkeys; + uint32 uk2; + uint32 subkeys_offset; + uint32 unknown_offset; + uint32 num_values; + uint32 values_offset; /* Points to a list of offsets of vk-records */ + uint32 sk_offset; + uint32 clsname_offset; + uint32 unk3[5]; + [value(strlen(key_name))] uint16 name_length; + uint16 clsname_length; + [charset(DOS)] uint8 key_name[name_length]; + }; + + /* sk (? Security Key ?) is the ACL of the registry. */ + [noprint,public] struct sk_block { + [charset(DOS)] uint8 header[2]; + uint16 tag; + uint32 prev_offset; + uint32 next_offset; + uint32 ref_cnt; + uint32 rec_size; + uint8 sec_desc[rec_size]; + }; + + [noprint] struct lh_hash { + uint32 nk_offset; + uint32 base37; /* base37 of key name */ + }; + + /* Subkey listing with hash of first 4 characters */ + [public,noprint] struct lh_block { + [charset(DOS)] uint8 header[2]; + uint16 key_count; + lh_hash hr[key_count]; + }; + + [public,noprint] struct li_block { + [charset(DOS)] uint8 header[2]; + uint16 key_count; + uint32 nk_offset[key_count]; + }; + + [public,noprint] struct ri_block { + [charset(DOS)] uint8 header[2]; + uint16 key_count; + uint32 offset[key_count]; /* li/lh offset */ + }; + + /* The vk-record consists information to a single value (value key). */ + [public,noprint] struct vk_block { + [charset(DOS)] uint8 header[2]; + [value(strlen(data_name))] uint16 name_length; + uint32 data_length; /* If top-bit set, offset contains the data */ + uint32 data_offset; + uint32 data_type; + uint16 flag; /* =1, has name, else no name (=Default). */ + uint16 unk1; + [charset(DOS)] uint8 data_name[name_length]; + }; + + [noprint] struct hash_record { + uint32 nk_offset; + [charset(DOS)] uint8 hash[4]; + }; + + /* + The lf-record is the counterpart to the RGKN-record (the + hash-function) + */ + [public,noprint] struct lf_block { + [charset(DOS)] uint8 header[2]; + uint16 key_count; + hash_record hr[key_count]; /* Array of hash records, depending on key_count */ + }; +} diff --git a/source4/lib/registry/registry.h b/source4/lib/registry/registry.h new file mode 100644 index 0000000..84173cb --- /dev/null +++ b/source4/lib/registry/registry.h @@ -0,0 +1,531 @@ +/* + Unix SMB/CIFS implementation. + Registry interface + Copyright (C) Gerald Carter 2002. + Copyright (C) Jelmer Vernooij 2003-2007. + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _REGISTRY_H /* _REGISTRY_H */ +#define _REGISTRY_H + +struct registry_context; +struct loadparm_context; + +#include <talloc.h> +#include "libcli/util/werror.h" +#include "librpc/gen_ndr/security.h" +#include "libcli/util/ntstatus.h" +#include "../lib/util/time.h" +#include "../lib/util/data_blob.h" + +/** + * The hive API. This API is generally used for + * reading a specific file that contains just one hive. + * + * Good examples are .DAT (NTUSER.DAT) files. + * + * This API does not have any notification support (that + * should be provided by the registry implementation), nor + * does it understand what predefined keys are. + */ + +struct hive_key { + const struct hive_operations *ops; +}; + +struct hive_operations { + const char *name; + + /** + * Open a specific subkey + */ + WERROR (*enum_key) (TALLOC_CTX *mem_ctx, + const struct hive_key *key, uint32_t idx, + const char **name, + const char **classname, + NTTIME *last_mod_time); + + /** + * Open a subkey by name + */ + WERROR (*get_key_by_name) (TALLOC_CTX *mem_ctx, + const struct hive_key *key, const char *name, + struct hive_key **subkey); + + /** + * Add a new key. + */ + WERROR (*add_key) (TALLOC_CTX *ctx, + const struct hive_key *parent_key, const char *path, + const char *classname, + struct security_descriptor *desc, + struct hive_key **key); + /** + * Remove an existing key. + */ + WERROR (*del_key) (TALLOC_CTX *mem_ctx, + const struct hive_key *key, const char *name); + + /** + * Force write of a key to disk. + */ + WERROR (*flush_key) (struct hive_key *key); + + /** + * Retrieve a registry value with a specific index. + */ + WERROR (*enum_value) (TALLOC_CTX *mem_ctx, + struct hive_key *key, uint32_t idx, + const char **name, uint32_t *type, + DATA_BLOB *data); + + /** + * Retrieve a registry value with the specified name + */ + WERROR (*get_value_by_name) (TALLOC_CTX *mem_ctx, + struct hive_key *key, const char *name, + uint32_t *type, DATA_BLOB *data); + + /** + * Set a value on the specified registry key. + */ + WERROR (*set_value) (struct hive_key *key, const char *name, + uint32_t type, const DATA_BLOB data); + + /** + * Remove a value. + */ + WERROR (*delete_value) (TALLOC_CTX *mem_ctx, + struct hive_key *key, const char *name); + + /* Security Descriptors */ + + /** + * Change the security descriptor on a registry key. + * + * This should return WERR_NOT_SUPPORTED if the underlying + * format does not have a mechanism for storing + * security descriptors. + */ + WERROR (*set_sec_desc) (struct hive_key *key, + const struct security_descriptor *desc); + + /** + * Retrieve the security descriptor on a registry key. + * + * This should return WERR_NOT_SUPPORTED if the underlying + * format does not have a mechanism for storing + * security descriptors. + */ + WERROR (*get_sec_desc) (TALLOC_CTX *ctx, + const struct hive_key *key, + struct security_descriptor **desc); + + /** + * Retrieve general information about a key. + */ + WERROR (*get_key_info) (TALLOC_CTX *mem_ctx, + const struct hive_key *key, + const char **classname, + uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_change_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize); +}; + +struct cli_credentials; +struct auth_session_info; +struct tevent_context; + +WERROR reg_open_hive(TALLOC_CTX *parent_ctx, const char *location, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct hive_key **root); +WERROR hive_key_get_info(TALLOC_CTX *mem_ctx, const struct hive_key *key, + const char **classname, uint32_t *num_subkeys, + uint32_t *num_values, NTTIME *last_change_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, uint32_t *max_valbufsize); +WERROR hive_key_add_name(TALLOC_CTX *ctx, const struct hive_key *parent_key, + const char *name, const char *classname, + struct security_descriptor *desc, + struct hive_key **key); +WERROR hive_key_del(TALLOC_CTX *mem_ctx, + const struct hive_key *key, const char *name); +WERROR hive_get_key_by_name(TALLOC_CTX *mem_ctx, + const struct hive_key *key, const char *name, + struct hive_key **subkey); +WERROR hive_enum_key(TALLOC_CTX *mem_ctx, + const struct hive_key *key, uint32_t idx, + const char **name, + const char **classname, + NTTIME *last_mod_time); + +WERROR hive_key_set_value(struct hive_key *key, const char *name, + uint32_t type, const DATA_BLOB data); + +WERROR hive_get_value(TALLOC_CTX *mem_ctx, + struct hive_key *key, const char *name, + uint32_t *type, DATA_BLOB *data); +WERROR hive_get_value_by_index(TALLOC_CTX *mem_ctx, + struct hive_key *key, uint32_t idx, + const char **name, + uint32_t *type, DATA_BLOB *data); +WERROR hive_get_sec_desc(TALLOC_CTX *mem_ctx, + struct hive_key *key, + struct security_descriptor **security); + +WERROR hive_set_sec_desc(struct hive_key *key, + const struct security_descriptor *security); + +WERROR hive_key_del_value(TALLOC_CTX *mem_ctx, + struct hive_key *key, const char *name); + +WERROR hive_key_flush(struct hive_key *key); + + +/* Individual backends */ +WERROR reg_open_directory(TALLOC_CTX *parent_ctx, + const char *location, struct hive_key **key); +WERROR reg_open_regf_file(TALLOC_CTX *parent_ctx, + const char *location, struct hive_key **key); +WERROR reg_open_ldb_file(TALLOC_CTX *parent_ctx, const char *location, + struct auth_session_info *session_info, + struct cli_credentials *credentials, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct hive_key **k); + + +WERROR reg_create_directory(TALLOC_CTX *parent_ctx, + const char *location, struct hive_key **key); +WERROR reg_create_regf_file(TALLOC_CTX *parent_ctx, + const char *location, + int major_version, + struct hive_key **key); + + + +/* Handles for the predefined keys */ +#define HKEY_CLASSES_ROOT 0x80000000 +#define HKEY_CURRENT_USER 0x80000001 +#define HKEY_LOCAL_MACHINE 0x80000002 +#define HKEY_USERS 0x80000003 +#define HKEY_PERFORMANCE_DATA 0x80000004 +#define HKEY_CURRENT_CONFIG 0x80000005 +#define HKEY_DYN_DATA 0x80000006 +#define HKEY_PERFORMANCE_TEXT 0x80000050 +#define HKEY_PERFORMANCE_NLSTEXT 0x80000060 + +#define HKEY_FIRST HKEY_CLASSES_ROOT +#define HKEY_LAST HKEY_PERFORMANCE_NLSTEXT + +struct reg_predefined_key { + uint32_t handle; + const char *name; +}; + +extern const struct reg_predefined_key reg_predefined_keys[]; + +#define REG_DELETE -1 + +/* + * The general idea here is that every backend provides a 'hive'. Combining + * various hives gives you a complete registry like windows has + */ + +#define REGISTRY_INTERFACE_VERSION 1 + +struct reg_key_operations; + +/* structure to store the registry handles */ +struct registry_key +{ + struct registry_context *context; +}; + +struct registry_value +{ + const char *name; + unsigned int data_type; + DATA_BLOB data; +}; + +/* FIXME */ +typedef void (*reg_key_notification_function) (void); +typedef void (*reg_value_notification_function) (void); + +struct cli_credentials; + +struct registry_operations { + const char *name; + + WERROR (*get_key_info) (TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char **classname, + uint32_t *numsubkeys, + uint32_t *numvalues, + NTTIME *last_change_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize); + + WERROR (*flush_key) (struct registry_key *key); + + WERROR (*get_predefined_key) (struct registry_context *ctx, + uint32_t key_id, + struct registry_key **key); + + WERROR (*open_key) (TALLOC_CTX *mem_ctx, + struct registry_key *parent, + const char *path, + struct registry_key **key); + + WERROR (*create_key) (TALLOC_CTX *mem_ctx, + struct registry_key *parent, + const char *name, + const char *key_class, + struct security_descriptor *security, + struct registry_key **key); + + WERROR (*delete_key) (TALLOC_CTX *mem_ctx, + struct registry_key *key, const char *name); + + WERROR (*delete_value) (TALLOC_CTX *mem_ctx, + struct registry_key *key, const char *name); + + WERROR (*enum_key) (TALLOC_CTX *mem_ctx, + const struct registry_key *key, uint32_t idx, + const char **name, + const char **keyclass, + NTTIME *last_changed_time); + + WERROR (*enum_value) (TALLOC_CTX *mem_ctx, + const struct registry_key *key, uint32_t idx, + const char **name, + uint32_t *type, + DATA_BLOB *data); + + WERROR (*get_sec_desc) (TALLOC_CTX *mem_ctx, + const struct registry_key *key, + struct security_descriptor **security); + + WERROR (*set_sec_desc) (struct registry_key *key, + const struct security_descriptor *security); + + WERROR (*load_key) (struct registry_key *key, + const char *key_name, + const char *path); + + WERROR (*unload_key) (struct registry_key *key, const char *name); + + WERROR (*notify_value_change) (struct registry_key *key, + reg_value_notification_function fn); + + WERROR (*get_value) (TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char *name, + uint32_t *type, + DATA_BLOB *data); + + WERROR (*set_value) (struct registry_key *key, + const char *name, + uint32_t type, + const DATA_BLOB data); +}; + +/** + * Handle to a full registry + * contains zero or more hives + */ +struct registry_context { + const struct registry_operations *ops; +}; + +struct auth_session_info; +struct tevent_context; +struct loadparm_context; + +/** + * Open the locally defined registry. + */ +WERROR reg_open_local(TALLOC_CTX *mem_ctx, + struct registry_context **ctx); + +WERROR reg_open_samba(TALLOC_CTX *mem_ctx, + struct registry_context **ctx, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + struct cli_credentials *credentials); + +/** + * Open the registry on a remote machine. + */ +WERROR reg_open_remote(TALLOC_CTX *mem_ctx, + struct registry_context **ctx, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx, + const char *location, struct tevent_context *ev); + +WERROR reg_open_wine(struct registry_context **ctx, const char *path); + +const char *reg_get_predef_name(uint32_t hkey); +WERROR reg_get_predefined_key_by_name(struct registry_context *ctx, + const char *name, + struct registry_key **key); +WERROR reg_get_predefined_key(struct registry_context *ctx, + uint32_t hkey, + struct registry_key **key); + +WERROR reg_open_key(TALLOC_CTX *mem_ctx, struct registry_key *parent, + const char *name, struct registry_key **result); + +WERROR reg_key_get_value_by_index(TALLOC_CTX *mem_ctx, + const struct registry_key *key, uint32_t idx, + const char **name, + uint32_t *type, + DATA_BLOB *data); +WERROR reg_key_get_info(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char **class_name, + uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_change_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize); +WERROR reg_key_get_subkey_by_index(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + uint32_t idx, + const char **name, + const char **classname, + NTTIME *last_mod_time); +WERROR reg_key_get_subkey_by_name(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char *name, + struct registry_key **subkey); +WERROR reg_key_get_value_by_name(TALLOC_CTX *mem_ctx, + const struct registry_key *key, + const char *name, + uint32_t *type, + DATA_BLOB *data); +WERROR reg_key_del(TALLOC_CTX *mem_ctx, + struct registry_key *parent, const char *name); +WERROR reg_key_add_name(TALLOC_CTX *mem_ctx, + struct registry_key *parent, const char *name, + const char *classname, + struct security_descriptor *desc, + struct registry_key **newkey); +WERROR reg_val_set(struct registry_key *key, const char *value, + uint32_t type, DATA_BLOB data); +WERROR reg_get_sec_desc(TALLOC_CTX *ctx, const struct registry_key *key, + struct security_descriptor **secdesc); +WERROR reg_del_value(TALLOC_CTX *mem_ctx, + struct registry_key *key, const char *valname); +WERROR reg_key_flush(struct registry_key *key); +WERROR reg_create_key(TALLOC_CTX *mem_ctx, + struct registry_key *parent, + const char *name, + const char *key_class, + struct security_descriptor *security, + struct registry_key **key); + +/* Utility functions */ +const char *str_regtype(int type); +bool push_reg_sz(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, const char *s); +bool push_reg_multi_sz(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, const char **a); +bool pull_reg_sz(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, const char **s); +bool pull_reg_multi_sz(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, const char ***a); +int regtype_by_string(const char *str); +char *reg_val_data_string(TALLOC_CTX *mem_ctx, uint32_t type, const DATA_BLOB data); +char *reg_val_description(TALLOC_CTX *mem_ctx, const char *name, + uint32_t type, const DATA_BLOB data); +bool reg_string_to_val(TALLOC_CTX *mem_ctx, const char *type_str, + const char *data_str, uint32_t *type, DATA_BLOB *data); +WERROR reg_open_key_abs(TALLOC_CTX *mem_ctx, struct registry_context *handle, + const char *name, struct registry_key **result); +WERROR reg_key_del_abs(struct registry_context *ctx, const char *path); +WERROR reg_key_add_abs(TALLOC_CTX *mem_ctx, struct registry_context *ctx, + const char *path, uint32_t access_mask, + struct security_descriptor *sec_desc, + struct registry_key **result); +WERROR reg_load_key(struct registry_context *ctx, struct registry_key *key, + const char *name, const char *filename); + +WERROR reg_mount_hive(struct registry_context *rctx, + struct hive_key *hive_key, + uint32_t key_id, + const char **elements); + +struct registry_key *reg_import_hive_key(struct registry_context *ctx, + struct hive_key *hive, + uint32_t predef_key, + const char **elements); +WERROR reg_set_sec_desc(struct registry_key *key, + const struct security_descriptor *security); + +struct reg_diff_callbacks { + WERROR (*add_key) (void *callback_data, const char *key_name); + WERROR (*set_value) (void *callback_data, const char *key_name, + const char *value_name, uint32_t value_type, + DATA_BLOB value); + WERROR (*del_value) (void *callback_data, const char *key_name, + const char *value_name); + WERROR (*del_key) (void *callback_data, const char *key_name); + WERROR (*del_all_values) (void *callback_data, const char *key_name); + WERROR (*done) (void *callback_data); +}; + +WERROR reg_diff_apply(struct registry_context *ctx, + const char *filename); + +WERROR reg_generate_diff(struct registry_context *ctx1, + struct registry_context *ctx2, + const struct reg_diff_callbacks *callbacks, + void *callback_data); +WERROR reg_dotreg_diff_save(TALLOC_CTX *ctx, const char *filename, + struct reg_diff_callbacks **callbacks, + void **callback_data); +WERROR reg_preg_diff_save(TALLOC_CTX *ctx, const char *filename, + struct reg_diff_callbacks **callbacks, + void **callback_data); +WERROR reg_generate_diff_key(struct registry_key *oldkey, + struct registry_key *newkey, + const char *path, + const struct reg_diff_callbacks *callbacks, + void *callback_data); +WERROR reg_diff_load(const char *filename, + const struct reg_diff_callbacks *callbacks, + void *callback_data); + +WERROR reg_dotreg_diff_load(int fd, + const struct reg_diff_callbacks *callbacks, + void *callback_data); + +WERROR reg_preg_diff_load(int fd, + const struct reg_diff_callbacks *callbacks, + void *callback_data); + +WERROR local_get_predefined_key(struct registry_context *ctx, + uint32_t key_id, struct registry_key **key); + + +#endif /* _REGISTRY_H */ diff --git a/source4/lib/registry/rpc.c b/source4/lib/registry/rpc.c new file mode 100644 index 0000000..a0c959f --- /dev/null +++ b/source4/lib/registry/rpc.c @@ -0,0 +1,578 @@ +/* + Samba Unix/Linux SMB implementation + RPC backend for the registry library + Copyright (C) 2003-2007 Jelmer Vernooij, jelmer@samba.org + Copyright (C) 2008 Matthias Dieter Wallnöfer, mwallnoefer@yahoo.de + + 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 3 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, see <http://www.gnu.org/licenses/>. */ + +#include "includes.h" +#include "registry.h" +#include "librpc/gen_ndr/ndr_winreg_c.h" + +#define MAX_NAMESIZE 512 +#define MAX_VALSIZE 32768 + +struct rpc_key { + struct registry_key key; + struct policy_handle pol; + struct dcerpc_binding_handle *binding_handle; + const char* classname; + uint32_t num_subkeys; + uint32_t max_subkeylen; + uint32_t max_classlen; + uint32_t num_values; + uint32_t max_valnamelen; + uint32_t max_valbufsize; + uint32_t secdescsize; + NTTIME last_changed_time; +}; + +struct rpc_registry_context { + struct registry_context context; + struct dcerpc_pipe *pipe; + struct dcerpc_binding_handle *binding_handle; +}; + +static struct registry_operations reg_backend_rpc; + +/** + * This is the RPC backend for the registry library. + */ + +#define openhive(u) static WERROR open_ ## u(struct dcerpc_binding_handle *b, TALLOC_CTX *mem_ctx, struct policy_handle *hnd) \ +{ \ + struct winreg_Open ## u r; \ + NTSTATUS status; \ +\ + ZERO_STRUCT(r); \ + r.in.system_name = NULL; \ + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; \ + r.out.handle = hnd;\ +\ + status = dcerpc_winreg_Open ## u ## _r(b, mem_ctx, &r); \ +\ + if (!NT_STATUS_IS_OK(status)) { \ + DEBUG(1, ("OpenHive failed - %s\n", nt_errstr(status))); \ + return ntstatus_to_werror(status); \ + } \ +\ + return r.out.result;\ +} + +openhive(HKLM) +openhive(HKCU) +openhive(HKPD) +openhive(HKU) +openhive(HKCR) +openhive(HKDD) +openhive(HKCC) + +static struct { + uint32_t hkey; + WERROR (*open) (struct dcerpc_binding_handle *b, TALLOC_CTX *, + struct policy_handle *h); +} known_hives[] = { + { HKEY_LOCAL_MACHINE, open_HKLM }, + { HKEY_CURRENT_USER, open_HKCU }, + { HKEY_CLASSES_ROOT, open_HKCR }, + { HKEY_PERFORMANCE_DATA, open_HKPD }, + { HKEY_USERS, open_HKU }, + { HKEY_DYN_DATA, open_HKDD }, + { HKEY_CURRENT_CONFIG, open_HKCC }, + { 0, NULL } +}; + +static WERROR rpc_query_key(TALLOC_CTX *mem_ctx, const struct registry_key *k); + +static WERROR rpc_get_predefined_key(struct registry_context *ctx, + uint32_t hkey_type, + struct registry_key **k) +{ + int n; + struct rpc_key *mykeydata; + struct rpc_registry_context *rctx = talloc_get_type(ctx, struct rpc_registry_context); + + *k = NULL; + + for(n = 0; known_hives[n].hkey; n++) { + if(known_hives[n].hkey == hkey_type) + break; + } + + if (known_hives[n].open == NULL) { + DEBUG(1, ("No such hive %d\n", hkey_type)); + return WERR_NO_MORE_ITEMS; + } + + mykeydata = talloc_zero(ctx, struct rpc_key); + W_ERROR_HAVE_NO_MEMORY(mykeydata); + mykeydata->key.context = ctx; + mykeydata->binding_handle = rctx->binding_handle; + mykeydata->num_values = -1; + mykeydata->num_subkeys = -1; + *k = (struct registry_key *)mykeydata; + return known_hives[n].open(mykeydata->binding_handle, mykeydata, &mykeydata->pol); +} + +#if 0 +static WERROR rpc_key_put_rpc_data(TALLOC_CTX *mem_ctx, struct registry_key *k) +{ + struct winreg_OpenKey r; + struct rpc_key_data *mykeydata; + + k->backend_data = mykeydata = talloc_zero(mem_ctx, struct rpc_key_data); + mykeydata->num_values = -1; + mykeydata->num_subkeys = -1; + + /* Then, open the handle using the hive */ + + ZERO_STRUCT(r); + r.in.handle = &(((struct rpc_key_data *)k->hive->root->backend_data)->pol); + r.in.keyname.name = k->path; + r.in.unknown = 0x00000000; + r.in.access_mask = 0x02000000; + r.out.handle = &mykeydata->pol; + + dcerpc_winreg_OpenKey((struct dcerpc_pipe *)k->hive->backend_data, + mem_ctx, &r); + + return r.out.result; +} +#endif + +static WERROR rpc_open_key(TALLOC_CTX *mem_ctx, struct registry_key *h, + const char *name, struct registry_key **key) +{ + struct rpc_key *parentkeydata = talloc_get_type(h, struct rpc_key), + *mykeydata; + struct winreg_OpenKey r; + NTSTATUS status; + + mykeydata = talloc_zero(mem_ctx, struct rpc_key); + W_ERROR_HAVE_NO_MEMORY(mykeydata); + mykeydata->key.context = parentkeydata->key.context; + mykeydata->binding_handle = parentkeydata->binding_handle; + mykeydata->num_values = -1; + mykeydata->num_subkeys = -1; + *key = (struct registry_key *)mykeydata; + + /* Then, open the handle using the hive */ + ZERO_STRUCT(r); + r.in.parent_handle = &parentkeydata->pol; + r.in.keyname.name = name; + r.in.options = 0x00000000; + r.in.access_mask = 0x02000000; + r.out.handle = &mykeydata->pol; + + status = dcerpc_winreg_OpenKey_r(mykeydata->binding_handle, mem_ctx, &r); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("OpenKey failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + return r.out.result; +} + +static WERROR rpc_get_value_by_index(TALLOC_CTX *mem_ctx, + const struct registry_key *parent, + uint32_t n, + const char **value_name, + uint32_t *type, + DATA_BLOB *data) +{ + struct rpc_key *mykeydata = talloc_get_type(parent, struct rpc_key); + struct winreg_EnumValue r; + struct winreg_ValNameBuf name; + uint8_t value; + uint32_t val_size = MAX_VALSIZE; + uint32_t zero = 0; + WERROR error; + NTSTATUS status; + + if (mykeydata->num_values == -1) { + error = rpc_query_key(mem_ctx, parent); + if(!W_ERROR_IS_OK(error)) return error; + } + + name.name = ""; + name.size = MAX_NAMESIZE; + + ZERO_STRUCT(r); + r.in.handle = &mykeydata->pol; + r.in.enum_index = n; + r.in.name = &name; + r.in.type = (enum winreg_Type *) type; + r.in.value = &value; + r.in.size = &val_size; + r.in.length = &zero; + r.out.name = &name; + r.out.type = (enum winreg_Type *) type; + r.out.value = &value; + r.out.size = &val_size; + r.out.length = &zero; + + status = dcerpc_winreg_EnumValue_r(mykeydata->binding_handle, mem_ctx, &r); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("EnumValue failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + *value_name = talloc_steal(mem_ctx, r.out.name->name); + *type = *(r.out.type); + *data = data_blob_talloc(mem_ctx, r.out.value, *r.out.length); + + return r.out.result; +} + +static WERROR rpc_get_value_by_name(TALLOC_CTX *mem_ctx, + const struct registry_key *parent, + const char *value_name, + uint32_t *type, + DATA_BLOB *data) +{ + struct rpc_key *mykeydata = talloc_get_type(parent, struct rpc_key); + struct winreg_QueryValue r; + struct winreg_String name; + uint8_t value; + uint32_t val_size = MAX_VALSIZE; + uint32_t zero = 0; + WERROR error; + NTSTATUS status; + + if (mykeydata->num_values == -1) { + error = rpc_query_key(mem_ctx, parent); + if(!W_ERROR_IS_OK(error)) return error; + } + + name.name = value_name; + + ZERO_STRUCT(r); + r.in.handle = &mykeydata->pol; + r.in.value_name = &name; + r.in.type = (enum winreg_Type *) type; + r.in.data = &value; + r.in.data_size = &val_size; + r.in.data_length = &zero; + r.out.type = (enum winreg_Type *) type; + r.out.data = &value; + r.out.data_size = &val_size; + r.out.data_length = &zero; + + status = dcerpc_winreg_QueryValue_r(mykeydata->binding_handle, mem_ctx, &r); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("QueryValue failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + *type = *(r.out.type); + *data = data_blob_talloc(mem_ctx, r.out.data, *r.out.data_length); + + return r.out.result; +} + +static WERROR rpc_set_value(struct registry_key *key, const char *value_name, + uint32_t type, const DATA_BLOB data) +{ + struct rpc_key *mykeydata = talloc_get_type(key, struct rpc_key); + struct winreg_SetValue r; + struct winreg_String name; + NTSTATUS status; + + name = (struct winreg_String) { .name = value_name }; + + ZERO_STRUCT(r); + r.in.handle = &mykeydata->pol; + r.in.name = name; + r.in.type = (enum winreg_Type)type; + r.in.data = data.data; + r.in.size = data.length; + + status = dcerpc_winreg_SetValue_r(mykeydata->binding_handle, key, &r); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("SetValue failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + return r.out.result; +} + +static WERROR rpc_del_value(TALLOC_CTX *mem_ctx, struct registry_key *key, + const char *value_name) +{ + struct rpc_key *mykeydata = talloc_get_type(key, struct rpc_key); + struct winreg_DeleteValue r; + struct winreg_String name; + NTSTATUS status; + + name = (struct winreg_String) { .name = value_name }; + + ZERO_STRUCT(r); + r.in.handle = &mykeydata->pol; + r.in.value = name; + + status = dcerpc_winreg_DeleteValue_r(mykeydata->binding_handle, + mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("DeleteValue failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + return r.out.result; +} + +static WERROR rpc_get_subkey_by_index(TALLOC_CTX *mem_ctx, + const struct registry_key *parent, + uint32_t n, + const char **name, + const char **keyclass, + NTTIME *last_changed_time) +{ + struct winreg_EnumKey r; + struct rpc_key *mykeydata = talloc_get_type(parent, struct rpc_key); + struct winreg_StringBuf namebuf, classbuf; + NTTIME change_time = 0; + NTSTATUS status; + + namebuf.name = ""; + namebuf.size = MAX_NAMESIZE; + classbuf.name = NULL; + classbuf.size = 0; + + ZERO_STRUCT(r); + r.in.handle = &mykeydata->pol; + r.in.enum_index = n; + r.in.name = &namebuf; + r.in.keyclass = &classbuf; + r.in.last_changed_time = &change_time; + r.out.name = &namebuf; + r.out.keyclass = &classbuf; + r.out.last_changed_time = &change_time; + + status = dcerpc_winreg_EnumKey_r(mykeydata->binding_handle, mem_ctx, &r); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("EnumKey failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + if (name != NULL) + *name = talloc_steal(mem_ctx, r.out.name->name); + if (keyclass != NULL) + *keyclass = talloc_steal(mem_ctx, r.out.keyclass->name); + if (last_changed_time != NULL) + *last_changed_time = *(r.out.last_changed_time); + + return r.out.result; +} + +static WERROR rpc_add_key(TALLOC_CTX *mem_ctx, + struct registry_key *parent, const char *path, + const char *key_class, + struct security_descriptor *sec, + struct registry_key **key) +{ + struct winreg_CreateKey r; + struct rpc_key *parentkd = talloc_get_type(parent, struct rpc_key); + struct rpc_key *rpck = talloc_zero(mem_ctx, struct rpc_key); + + NTSTATUS status; + + if (rpck == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + rpck->key.context = parentkd->key.context; + rpck->binding_handle = parentkd->binding_handle; + rpck->num_values = -1; + rpck->num_subkeys = -1; + + ZERO_STRUCT(r); + r.in.handle = &parentkd->pol; + r.in.name.name = path; + r.in.keyclass.name = NULL; + r.in.options = 0; + r.in.access_mask = 0x02000000; + r.in.secdesc = NULL; + r.in.action_taken = NULL; + r.out.new_handle = &rpck->pol; + r.out.action_taken = NULL; + + status = dcerpc_winreg_CreateKey_r(parentkd->binding_handle, mem_ctx, &r); + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(rpck); + DEBUG(1, ("CreateKey failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + rpck->binding_handle = parentkd->binding_handle; + *key = (struct registry_key *)rpck; + + return r.out.result; +} + +static WERROR rpc_query_key(TALLOC_CTX *mem_ctx, const struct registry_key *k) +{ + struct winreg_QueryInfoKey r; + struct rpc_key *mykeydata = talloc_get_type(k, struct rpc_key); + struct winreg_String classname; + NTSTATUS status; + + classname.name = NULL; + + ZERO_STRUCT(r); + r.in.handle = &mykeydata->pol; + r.in.classname = &classname; + r.out.classname = &classname; + r.out.num_subkeys = &mykeydata->num_subkeys; + r.out.max_subkeylen = &mykeydata->max_subkeylen; + r.out.max_classlen = &mykeydata->max_classlen; + r.out.num_values = &mykeydata->num_values; + r.out.max_valnamelen = &mykeydata->max_valnamelen; + r.out.max_valbufsize = &mykeydata->max_valbufsize; + r.out.secdescsize = &mykeydata->secdescsize; + r.out.last_changed_time = &mykeydata->last_changed_time; + + status = dcerpc_winreg_QueryInfoKey_r(mykeydata->binding_handle, mem_ctx, &r); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("QueryInfoKey failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + mykeydata->classname = talloc_steal(mem_ctx, r.out.classname->name); + + return r.out.result; +} + +static WERROR rpc_del_key(TALLOC_CTX *mem_ctx, struct registry_key *parent, + const char *name) +{ + NTSTATUS status; + struct rpc_key *mykeydata = talloc_get_type(parent, struct rpc_key); + struct winreg_DeleteKey r; + + ZERO_STRUCT(r); + r.in.handle = &mykeydata->pol; + r.in.key.name = name; + + status = dcerpc_winreg_DeleteKey_r(mykeydata->binding_handle, mem_ctx, &r); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("DeleteKey failed - %s\n", nt_errstr(status))); + return ntstatus_to_werror(status); + } + + return r.out.result; +} + +static WERROR rpc_get_info(TALLOC_CTX *mem_ctx, const struct registry_key *key, + const char **classname, + uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_changed_time, + uint32_t *max_subkeylen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize) +{ + struct rpc_key *mykeydata = talloc_get_type(key, struct rpc_key); + WERROR error; + + if (mykeydata->num_values == -1) { + error = rpc_query_key(mem_ctx, key); + if(!W_ERROR_IS_OK(error)) return error; + } + + if (classname != NULL) + *classname = mykeydata->classname; + + if (num_subkeys != NULL) + *num_subkeys = mykeydata->num_subkeys; + + if (num_values != NULL) + *num_values = mykeydata->num_values; + + if (last_changed_time != NULL) + *last_changed_time = mykeydata->last_changed_time; + + if (max_subkeylen != NULL) + *max_subkeylen = mykeydata->max_subkeylen; + + if (max_valnamelen != NULL) + *max_valnamelen = mykeydata->max_valnamelen; + + if (max_valbufsize != NULL) + *max_valbufsize = mykeydata->max_valbufsize; + + return WERR_OK; +} + +static struct registry_operations reg_backend_rpc = { + .name = "rpc", + .open_key = rpc_open_key, + .get_predefined_key = rpc_get_predefined_key, + .enum_key = rpc_get_subkey_by_index, + .enum_value = rpc_get_value_by_index, + .get_value = rpc_get_value_by_name, + .set_value = rpc_set_value, + .delete_value = rpc_del_value, + .create_key = rpc_add_key, + .delete_key = rpc_del_key, + .get_key_info = rpc_get_info, +}; + +_PUBLIC_ WERROR reg_open_remote(TALLOC_CTX *mem_ctx, + struct registry_context **ctx, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx, + const char *location, struct tevent_context *ev) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + struct rpc_registry_context *rctx; + + dcerpc_init(); + + rctx = talloc(mem_ctx, struct rpc_registry_context); + W_ERROR_HAVE_NO_MEMORY(rctx); + + /* Default to local smbd if no connection is specified */ + if (!location) { + location = talloc_strdup(rctx, "ncalrpc:"); + } + + status = dcerpc_pipe_connect(rctx /* TALLOC_CTX */, + &p, location, + &ndr_table_winreg, + credentials, ev, lp_ctx); + if(NT_STATUS_IS_ERR(status)) { + DEBUG(1, ("Unable to open '%s': %s\n", location, + nt_errstr(status))); + talloc_free(rctx); + *ctx = NULL; + return ntstatus_to_werror(status); + } + + rctx->pipe = p; + rctx->binding_handle = p->binding_handle; + + *ctx = (struct registry_context *)rctx; + (*ctx)->ops = ®_backend_rpc; + + return WERR_OK; +} diff --git a/source4/lib/registry/samba.c b/source4/lib/registry/samba.c new file mode 100644 index 0000000..ff52297 --- /dev/null +++ b/source4/lib/registry/samba.c @@ -0,0 +1,100 @@ +/* + Unix SMB/CIFS implementation. + Copyright (C) Jelmer Vernooij 2004-2007. + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "registry.h" +#include "param/param.h" + +/** + * @file + * @brief Samba-specific registry functions + */ + +static WERROR mount_samba_hive(struct registry_context *ctx, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct auth_session_info *auth_info, + struct cli_credentials *creds, + const char *name, + uint32_t hive_id) +{ + WERROR error; + struct hive_key *hive; + const char *location; + + location = talloc_asprintf(ctx, "%s/%s.ldb", + lpcfg_private_dir(lp_ctx), + name); + W_ERROR_HAVE_NO_MEMORY(location); + + error = reg_open_hive(ctx, location, auth_info, creds, event_ctx, lp_ctx, &hive); + + if (W_ERROR_EQUAL(error, WERR_FILE_NOT_FOUND)) + error = reg_open_ldb_file(ctx, location, auth_info, + creds, event_ctx, lp_ctx, &hive); + + talloc_free(discard_const_p(char, location)); + + if (!W_ERROR_IS_OK(error)) + return error; + + return reg_mount_hive(ctx, hive, hive_id, NULL); +} + + +_PUBLIC_ WERROR reg_open_samba(TALLOC_CTX *mem_ctx, + struct registry_context **ctx, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + struct cli_credentials *credentials) +{ + WERROR result; + + result = reg_open_local(mem_ctx, ctx); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + mount_samba_hive(*ctx, ev_ctx, lp_ctx, session_info, credentials, + "hklm", HKEY_LOCAL_MACHINE); + + mount_samba_hive(*ctx, ev_ctx, lp_ctx, session_info, credentials, + "hkcr", HKEY_CLASSES_ROOT); + + /* FIXME: Should be mounted from NTUSER.DAT in the home directory of the + * current user */ + mount_samba_hive(*ctx, ev_ctx, lp_ctx, session_info, credentials, + "hkcu", HKEY_CURRENT_USER); + + mount_samba_hive(*ctx, ev_ctx, lp_ctx, session_info, credentials, + "hku", HKEY_USERS); + + /* FIXME: Different hive backend for HKEY_CLASSES_ROOT: merged view of HKEY_LOCAL_MACHINE\Software\Classes + * and HKEY_CURRENT_USER\Software\Classes */ + + /* FIXME: HKEY_CURRENT_CONFIG is an alias for HKEY_LOCAL_MACHINE\System\CurrentControlSet\Hardware Profiles\Current */ + + /* FIXME: HKEY_PERFORMANCE_DATA is dynamically generated */ + + /* FIXME: HKEY_LOCAL_MACHINE\Hardware is autogenerated */ + + /* FIXME: HKEY_LOCAL_MACHINE\Security\SAM is an alias for HKEY_LOCAL_MACHINE\SAM */ + + return WERR_OK; +} diff --git a/source4/lib/registry/tests/diff.c b/source4/lib/registry/tests/diff.c new file mode 100644 index 0000000..35f968a --- /dev/null +++ b/source4/lib/registry/tests/diff.c @@ -0,0 +1,291 @@ +/* + Unix SMB/CIFS implementation. + + local testing of registry diff functionality + + Copyright (C) Jelmer Vernooij 2007 + Copyright (C) Wilco Baan Hofman 2008 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/registry/registry.h" +#include "torture/torture.h" +#include "librpc/gen_ndr/winreg.h" +#include "param/param.h" +#include "lib/registry/tests/proto.h" + +struct diff_tcase_data { + struct registry_context *r1_ctx; + struct registry_context *r2_ctx; + struct reg_diff_callbacks *callbacks; + void *callback_data; + char *tempdir; + char *filename; +}; + +static bool test_generate_diff(struct torture_context *tctx, void *tcase_data) +{ + WERROR error; + struct diff_tcase_data *td = tcase_data; + + error = reg_generate_diff(td->r1_ctx, td->r2_ctx, + td->callbacks, + td->callback_data); + torture_assert_werr_ok(tctx, error, "reg_generate_diff"); + + return true; +} + +#if 0 +static bool test_diff_load(struct torture_context *tctx, void *tcase_data) +{ + struct diff_tcase_data *td = tcase_data; + struct reg_diff_callbacks *callbacks; + void *data; + WERROR error; + + error = reg_diff_load(td->filename, callbacks, data); + torture_assert_werr_ok(tctx, error, "reg_diff_load"); + + return true; +} +#endif +static bool test_diff_apply(struct torture_context *tctx, void *tcase_data) +{ + struct diff_tcase_data *td = tcase_data; + struct registry_key *key; + WERROR error; + + error = reg_diff_apply(td->r1_ctx, td->filename); + torture_assert_werr_ok(tctx, error, "reg_diff_apply"); + + error = td->r1_ctx->ops->get_predefined_key(td->r1_ctx, HKEY_LOCAL_MACHINE, &key); + torture_assert_werr_ok(tctx, error, "Opening HKEY_LOCAL_MACHINE failed"); + + /* If this generates an error it could be that the apply doesn't work, + * but also that the reg_generate_diff didn't work. */ + error = td->r1_ctx->ops->open_key(td->r1_ctx, key, "Software", &key); + torture_assert_werr_ok(tctx, error, "Opening HKLM\\Software failed"); + error = td->r1_ctx->ops->open_key(td->r1_ctx, key, "Microsoft", &key); + torture_assert_werr_ok(tctx, error, "Opening HKLM\\Software\\Microsoft failed"); + error = td->r1_ctx->ops->open_key(td->r1_ctx, key, "Windows", &key); + torture_assert_werr_ok(tctx, error, "Opening HKLM\\..\\Microsoft\\Windows failed"); + error = td->r1_ctx->ops->open_key(td->r1_ctx, key, "CurrentVersion", &key); + torture_assert_werr_ok(tctx, error, "Opening HKLM\\..\\Windows\\CurrentVersion failed"); + error = td->r1_ctx->ops->open_key(td->r1_ctx, key, "Policies", &key); + torture_assert_werr_ok(tctx, error, "Opening HKLM\\..\\CurrentVersion\\Policies failed"); + error = td->r1_ctx->ops->open_key(td->r1_ctx, key, "Explorer", &key); + torture_assert_werr_ok(tctx, error, "Opening HKLM\\..\\Policies\\Explorer failed"); + + return true; +} + +static const char *added_key = NULL; + +static WERROR test_add_key(void *callback_data, const char *key_name) +{ + added_key = talloc_strdup(callback_data, key_name); + + return WERR_OK; +} + +static bool test_generate_diff_key_add(struct torture_context *tctx, void *tcase_data) +{ + struct reg_diff_callbacks cb; + struct registry_key rk; + + return true; + + ZERO_STRUCT(cb); + + cb.add_key = test_add_key; + + if (W_ERROR_IS_OK(reg_generate_diff_key(&rk, NULL, "bla", &cb, tctx))) + return false; + + torture_assert_str_equal(tctx, added_key, "bla", "key added"); + + return true; +} + +static bool test_generate_diff_key_null(struct torture_context *tctx, void *tcase_data) +{ + struct reg_diff_callbacks cb; + + ZERO_STRUCT(cb); + + if (!W_ERROR_IS_OK(reg_generate_diff_key(NULL, NULL, "", &cb, NULL))) + return false; + + return true; +} + +static void tcase_add_tests (struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "test_generate_diff_key_add", + test_generate_diff_key_add); + torture_tcase_add_simple_test(tcase, "test_generate_diff_key_null", + test_generate_diff_key_null); + torture_tcase_add_simple_test(tcase, "test_generate_diff", + test_generate_diff); + torture_tcase_add_simple_test(tcase, "test_diff_apply", + test_diff_apply); +/* torture_tcase_add_simple_test(tcase, "test_diff_load", + test_diff_load); +*/ +} + +static bool diff_setup_tcase(struct torture_context *tctx, void **data) +{ + struct registry_context *r1_ctx, *r2_ctx; + WERROR error; + NTSTATUS status; + struct hive_key *r1_hklm, *r1_hkcu; + struct hive_key *r2_hklm, *r2_hkcu; + const char *filename; + struct diff_tcase_data *td; + struct registry_key *key, *newkey; + DATA_BLOB blob; + + td = talloc(tctx, struct diff_tcase_data); + + /* Create two registry contexts */ + error = reg_open_local(tctx, &r1_ctx); + torture_assert_werr_ok(tctx, error, "Opening registry 1 for patch tests failed"); + + error = reg_open_local(tctx, &r2_ctx); + torture_assert_werr_ok(tctx, error, "Opening registry 2 for patch tests failed"); + + /* Create temp directory */ + status = torture_temp_dir(tctx, "patchfile", &td->tempdir); + torture_assert_ntstatus_ok(tctx, status, "Creating temp dir failed"); + + /* Create and mount HKLM and HKCU hives for registry 1 */ + filename = talloc_asprintf(tctx, "%s/r1_local_machine.ldb", td->tempdir); + error = reg_open_ldb_file(tctx, filename, NULL, NULL, tctx->ev, tctx->lp_ctx, &r1_hklm); + torture_assert_werr_ok(tctx, error, "Opening local machine file failed"); + + error = reg_mount_hive(r1_ctx, r1_hklm, HKEY_LOCAL_MACHINE, NULL); + torture_assert_werr_ok(tctx, error, "Mounting hive failed"); + + filename = talloc_asprintf(tctx, "%s/r1_current_user.ldb", td->tempdir); + error = reg_open_ldb_file(tctx, filename, NULL, NULL, tctx->ev, tctx->lp_ctx, &r1_hkcu); + torture_assert_werr_ok(tctx, error, "Opening current user file failed"); + + error = reg_mount_hive(r1_ctx, r1_hkcu, HKEY_CURRENT_USER, NULL); + torture_assert_werr_ok(tctx, error, "Mounting hive failed"); + + /* Create and mount HKLM and HKCU hives for registry 2 */ + filename = talloc_asprintf(tctx, "%s/r2_local_machine.ldb", td->tempdir); + error = reg_open_ldb_file(tctx, filename, NULL, NULL, tctx->ev, tctx->lp_ctx, &r2_hklm); + torture_assert_werr_ok(tctx, error, "Opening local machine file failed"); + + error = reg_mount_hive(r2_ctx, r2_hklm, HKEY_LOCAL_MACHINE, NULL); + torture_assert_werr_ok(tctx, error, "Mounting hive failed"); + + filename = talloc_asprintf(tctx, "%s/r2_current_user.ldb", td->tempdir); + error = reg_open_ldb_file(tctx, filename, NULL, NULL, tctx->ev, tctx->lp_ctx, &r2_hkcu); + torture_assert_werr_ok(tctx, error, "Opening current user file failed"); + + error = reg_mount_hive(r2_ctx, r2_hkcu, HKEY_CURRENT_USER, NULL); + torture_assert_werr_ok(tctx, error, "Mounting hive failed"); + + error = r1_ctx->ops->get_predefined_key(r1_ctx, HKEY_CURRENT_USER, &key); + torture_assert_werr_ok(tctx, error, "Opening HKEY_CURRENT_USER failed"); + error = r1_ctx->ops->create_key(r1_ctx, key, "Network", NULL, NULL, &newkey); + torture_assert_werr_ok(tctx, error, "Opening HKCU\\Network failed"); + error = r1_ctx->ops->create_key(r1_ctx, newkey, "L", NULL, NULL, &newkey); + torture_assert_werr_ok(tctx, error, "Opening HKCU\\Network\\L failed"); + + error = r2_ctx->ops->get_predefined_key(r2_ctx, HKEY_LOCAL_MACHINE, &key); + torture_assert_werr_ok(tctx, error, "Opening HKEY_LOCAL_MACHINE failed"); + error = r2_ctx->ops->create_key(r2_ctx, key, "Software", NULL, NULL, &newkey); + torture_assert_werr_ok(tctx, error, "Creating HKLM\\Software failed"); + error = r2_ctx->ops->create_key(r2_ctx, newkey, "Microsoft", NULL, NULL, &newkey); + torture_assert_werr_ok(tctx, error, "Creating HKLM\\Software\\Microsoft failed"); + error = r2_ctx->ops->create_key(r2_ctx, newkey, "Windows", NULL, NULL, &newkey); + torture_assert_werr_ok(tctx, error, "Creating HKLM\\Software\\Microsoft\\Windows failed"); + error = r2_ctx->ops->create_key(r2_ctx, newkey, "CurrentVersion", NULL, NULL, &newkey); + torture_assert_werr_ok(tctx, error, "Creating HKLM\\..\\Windows\\CurrentVersion failed"); + error = r2_ctx->ops->create_key(r2_ctx, newkey, "Policies", NULL, NULL, &newkey); + torture_assert_werr_ok(tctx, error, "Creating HKLM\\..\\CurrentVersion\\Policies failed"); + error = r2_ctx->ops->create_key(r2_ctx, newkey, "Explorer", NULL, NULL, &newkey); + torture_assert_werr_ok(tctx, error, "Creating HKLM\\..\\Policies\\Explorer failed"); + + blob.data = talloc_array(r2_ctx, uint8_t, 4); + /* set "0x03FFFFFF" in little endian format */ + blob.data[0] = 0xFF; blob.data[1] = 0xFF; + blob.data[2] = 0xFF; blob.data[3] = 0x03; + blob.length = 4; + + r1_ctx->ops->set_value(newkey, "NoDrives", REG_DWORD, blob); + + /* Set test case data */ + td->r1_ctx = r1_ctx; + td->r2_ctx = r2_ctx; + + *data = td; + + return true; +} + +static bool diff_setup_preg_tcase (struct torture_context *tctx, void **data) +{ + struct diff_tcase_data *td; + WERROR error; + + diff_setup_tcase(tctx, data); + td = *data; + + td->filename = talloc_asprintf(tctx, "%s/test.pol", td->tempdir); + error = reg_preg_diff_save(tctx, td->filename, &td->callbacks, + &td->callback_data); + torture_assert_werr_ok(tctx, error, "reg_preg_diff_save"); + + return true; +} + +static bool diff_setup_dotreg_tcase (struct torture_context *tctx, void **data) +{ + struct diff_tcase_data *td; + WERROR error; + + diff_setup_tcase(tctx, data); + td = *data; + + td->filename = talloc_asprintf(tctx, "%s/test.reg", td->tempdir); + error = reg_dotreg_diff_save(tctx, td->filename, &td->callbacks, + &td->callback_data); + torture_assert_werr_ok(tctx, error, "reg_dotreg_diff_save"); + + return true; +} + +struct torture_suite *torture_registry_diff(TALLOC_CTX *mem_ctx) +{ + struct torture_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "diff"); + + tcase = torture_suite_add_tcase(suite, "PReg"); + torture_tcase_set_fixture(tcase, diff_setup_preg_tcase, NULL); + tcase_add_tests(tcase); + + tcase = torture_suite_add_tcase(suite, "dotreg"); + torture_tcase_set_fixture(tcase, diff_setup_dotreg_tcase, NULL); + tcase_add_tests(tcase); + + return suite; +} diff --git a/source4/lib/registry/tests/generic.c b/source4/lib/registry/tests/generic.c new file mode 100644 index 0000000..2ef6f84 --- /dev/null +++ b/source4/lib/registry/tests/generic.c @@ -0,0 +1,179 @@ +/* + Unix SMB/CIFS implementation. + + local testing of registry library + + Copyright (C) Jelmer Vernooij 2005-2007 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/registry/registry.h" +#include "torture/torture.h" +#include "librpc/gen_ndr/winreg.h" +#include "param/param.h" +#include "lib/registry/tests/proto.h" + +static bool test_str_regtype(struct torture_context *ctx) +{ + torture_assert_str_equal(ctx, str_regtype(0), + "REG_NONE", "REG_NONE failed"); + torture_assert_str_equal(ctx, str_regtype(1), + "REG_SZ", "REG_SZ failed"); + torture_assert_str_equal(ctx, str_regtype(2), + "REG_EXPAND_SZ", "REG_EXPAND_SZ failed"); + torture_assert_str_equal(ctx, str_regtype(3), + "REG_BINARY", "REG_BINARY failed"); + torture_assert_str_equal(ctx, str_regtype(4), + "REG_DWORD", "REG_DWORD failed"); + torture_assert_str_equal(ctx, str_regtype(5), + "REG_DWORD_BIG_ENDIAN", "REG_DWORD_BIG_ENDIAN failed"); + torture_assert_str_equal(ctx, str_regtype(6), + "REG_LINK", "REG_LINK failed"); + torture_assert_str_equal(ctx, str_regtype(7), + "REG_MULTI_SZ", "REG_MULTI_SZ failed"); + torture_assert_str_equal(ctx, str_regtype(8), + "REG_RESOURCE_LIST", "REG_RESOURCE_LIST failed"); + torture_assert_str_equal(ctx, str_regtype(9), + "REG_FULL_RESOURCE_DESCRIPTOR", "REG_FULL_RESOURCE_DESCRIPTOR failed"); + torture_assert_str_equal(ctx, str_regtype(10), + "REG_RESOURCE_REQUIREMENTS_LIST", "REG_RESOURCE_REQUIREMENTS_LIST failed"); + torture_assert_str_equal(ctx, str_regtype(11), + "REG_QWORD", "REG_QWORD failed"); + + return true; +} + + +static bool test_reg_val_data_string_dword(struct torture_context *ctx) +{ + uint8_t d[] = { 0x20, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 4 }; + torture_assert_str_equal(ctx, "0x00000020", + reg_val_data_string(ctx, REG_DWORD, db), + "dword failed"); + return true; +} + +static bool test_reg_val_data_string_dword_big_endian(struct torture_context *ctx) +{ + uint8_t d[] = { 0x20, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 4 }; + torture_assert_str_equal(ctx, "0x00000020", + reg_val_data_string(ctx, REG_DWORD_BIG_ENDIAN, db), + "dword failed"); + return true; +} + +static bool test_reg_val_data_string_qword(struct torture_context *ctx) +{ + uint8_t d[] = { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 8 }; + torture_assert_str_equal(ctx, "0x0000000000000020", + reg_val_data_string(ctx, REG_QWORD, db), + "qword failed"); + return true; +} + +static bool test_reg_val_data_string_sz(struct torture_context *ctx) +{ + DATA_BLOB db; + convert_string_talloc(ctx, CH_UTF8, CH_UTF16, + "bla", 3, (void **)&db.data, &db.length); + torture_assert_str_equal(ctx, "bla", + reg_val_data_string(ctx, REG_SZ, db), + "sz failed"); + db.length = 4; + torture_assert_str_equal(ctx, "bl", + reg_val_data_string(ctx, REG_SZ, db), + "sz failed"); + return true; +} + +static bool test_reg_val_data_string_binary(struct torture_context *ctx) +{ + uint8_t x[] = { 0x1, 0x2, 0x3, 0x4 }; + DATA_BLOB db = { x, 4 }; + torture_assert_str_equal(ctx, "01020304", + reg_val_data_string(ctx, REG_BINARY, db), + "binary failed"); + return true; +} + + +static bool test_reg_val_data_string_empty(struct torture_context *ctx) +{ + DATA_BLOB db = { NULL, 0 }; + torture_assert_str_equal(ctx, "", + reg_val_data_string(ctx, REG_BINARY, db), + "empty failed"); + return true; +} + +static bool test_reg_val_description(struct torture_context *ctx) +{ + DATA_BLOB data; + convert_string_talloc(ctx, CH_UTF8, CH_UTF16, + "stationary traveller", + strlen("stationary traveller"), + (void **)&data.data, &data.length); + torture_assert_str_equal(ctx, "camel = REG_SZ : stationary traveller", + reg_val_description(ctx, "camel", REG_SZ, data), + "reg_val_description failed"); + return true; +} + + +static bool test_reg_val_description_nullname(struct torture_context *ctx) +{ + DATA_BLOB data; + convert_string_talloc(ctx, CH_UTF8, CH_UTF16, + "west berlin", + strlen("west berlin"), + (void **)&data.data, &data.length); + torture_assert_str_equal(ctx, "<No Name> = REG_SZ : west berlin", + reg_val_description(ctx, NULL, REG_SZ, data), + "description with null name failed"); + return true; +} + +struct torture_suite *torture_registry(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "registry"); + torture_suite_add_simple_test(suite, "str_regtype", + test_str_regtype); + torture_suite_add_simple_test(suite, "reg_val_data_string dword", + test_reg_val_data_string_dword); + torture_suite_add_simple_test(suite, "reg_val_data_string dword_big_endian", + test_reg_val_data_string_dword_big_endian); + torture_suite_add_simple_test(suite, "reg_val_data_string qword", + test_reg_val_data_string_qword); + torture_suite_add_simple_test(suite, "reg_val_data_string sz", + test_reg_val_data_string_sz); + torture_suite_add_simple_test(suite, "reg_val_data_string binary", + test_reg_val_data_string_binary); + torture_suite_add_simple_test(suite, "reg_val_data_string empty", + test_reg_val_data_string_empty); + torture_suite_add_simple_test(suite, "reg_val_description", + test_reg_val_description); + torture_suite_add_simple_test(suite, "reg_val_description null", + test_reg_val_description_nullname); + + torture_suite_add_suite(suite, torture_registry_hive(suite)); + torture_suite_add_suite(suite, torture_registry_registry(suite)); + torture_suite_add_suite(suite, torture_registry_diff(suite)); + + return suite; +} diff --git a/source4/lib/registry/tests/hive.c b/source4/lib/registry/tests/hive.c new file mode 100644 index 0000000..aca5cff --- /dev/null +++ b/source4/lib/registry/tests/hive.c @@ -0,0 +1,440 @@ +/* + Unix SMB/CIFS implementation. + + local testing of registry library - hives + + Copyright (C) Jelmer Vernooij 2005-2007 + + 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 3 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 "lib/registry/registry.h" +#include "torture/torture.h" +#include "librpc/gen_ndr/winreg.h" +#include "system/filesys.h" +#include "param/param.h" +#include "libcli/security/security.h" +#include "lib/registry/tests/proto.h" + +static bool test_del_nonexistent_key(struct torture_context *tctx, + const void *test_data) +{ + const struct hive_key *root = (const struct hive_key *)test_data; + WERROR error = hive_key_del(tctx, root, "bla"); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "invalid return code"); + + return true; +} + +static bool test_keyinfo_root(struct torture_context *tctx, + const void *test_data) +{ + uint32_t num_subkeys, num_values; + const struct hive_key *root = (const struct hive_key *)test_data; + WERROR error; + + /* This is a new backend. There should be no subkeys and no + * values */ + error = hive_key_get_info(tctx, root, NULL, &num_subkeys, &num_values, + NULL, NULL, NULL, NULL); + torture_assert_werr_ok(tctx, error, "reg_key_num_subkeys()"); + + torture_assert_int_equal(tctx, num_subkeys, 0, + "New key has non-zero subkey count"); + + torture_assert_werr_ok(tctx, error, "reg_key_num_values"); + + torture_assert_int_equal(tctx, num_values, 0, + "New key has non-zero value count"); + + return true; +} + +static bool test_keyinfo_nums(struct torture_context *tctx, void *test_data) +{ + uint32_t num_subkeys, num_values; + struct hive_key *root = (struct hive_key *)test_data; + WERROR error; + struct hive_key *subkey; + uint8_t d[] = { 0x42, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 4 }; + + error = hive_key_add_name(tctx, root, "Nested Keyll", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + error = hive_key_set_value(root, "Answer", REG_DWORD, db); + torture_assert_werr_ok(tctx, error, "hive_key_set_value"); + + /* This is a new backend. There should be no subkeys and no + * values */ + error = hive_key_get_info(tctx, root, NULL, &num_subkeys, &num_values, + NULL, NULL, NULL, NULL); + torture_assert_werr_ok(tctx, error, "reg_key_num_subkeys()"); + + torture_assert_int_equal(tctx, num_subkeys, 1, "subkey count"); + + torture_assert_werr_ok(tctx, error, "reg_key_num_values"); + + torture_assert_int_equal(tctx, num_values, 1, "value count"); + + return true; +} + +static bool test_add_subkey(struct torture_context *tctx, + const void *test_data) +{ + WERROR error; + struct hive_key *subkey; + const struct hive_key *root = (const struct hive_key *)test_data; + TALLOC_CTX *mem_ctx = tctx; + + error = hive_key_add_name(mem_ctx, root, "Nested Key", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + error = hive_key_del(mem_ctx, root, "Nested Key"); + torture_assert_werr_ok(tctx, error, "reg_key_del"); + + return true; +} + +static bool test_del_recursive(struct torture_context *tctx, + const void *test_data) +{ + WERROR error; + struct hive_key *subkey; + struct hive_key *subkey2; + const struct hive_key *root = (const struct hive_key *)test_data; + TALLOC_CTX *mem_ctx = tctx; + uint8_t d[] = { 0x42, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 4 }; + + /* Create a new key under the root */ + error = hive_key_add_name(mem_ctx, root, "Parent Key", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + /* Create a new key under "Parent Key" */ + error = hive_key_add_name(mem_ctx, subkey, "Child Key", NULL, + NULL, &subkey2); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + /* Create a new value under "Child Key" */ + error = hive_key_set_value(subkey2, "Answer Recursive", REG_DWORD, db); + torture_assert_werr_ok(tctx, error, "hive_key_set_value"); + + /* Deleting "Parent Key" will also delete "Child Key" and the value. */ + error = hive_key_del(mem_ctx, root, "Parent Key"); + torture_assert_werr_ok(tctx, error, "hive_key_del"); + + return true; +} + +static bool test_flush_key(struct torture_context *tctx, void *test_data) +{ + struct hive_key *root = (struct hive_key *)test_data; + + torture_assert_werr_ok(tctx, hive_key_flush(root), "flush key"); + + return true; +} + +static bool test_del_key(struct torture_context *tctx, const void *test_data) +{ + WERROR error; + struct hive_key *subkey; + const struct hive_key *root = (const struct hive_key *)test_data; + TALLOC_CTX *mem_ctx = tctx; + + error = hive_key_add_name(mem_ctx, root, "Nested Key", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + error = hive_key_del(mem_ctx, root, "Nested Key"); + torture_assert_werr_ok(tctx, error, "reg_key_del"); + + error = hive_key_del(mem_ctx, root, "Nested Key"); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, "reg_key_del"); + + return true; +} + +static bool test_set_value(struct torture_context *tctx, + const void *test_data) +{ + WERROR error; + struct hive_key *subkey; + const struct hive_key *root = (const struct hive_key *)test_data; + TALLOC_CTX *mem_ctx = tctx; + uint8_t d[] = { 0x42, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 4 }; + + error = hive_key_add_name(mem_ctx, root, "YA Nested Key", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + error = hive_key_set_value(subkey, "Answer", REG_DWORD, db); + torture_assert_werr_ok(tctx, error, "hive_key_set_value"); + + return true; +} + +static bool test_get_value(struct torture_context *tctx, const void *test_data) +{ + WERROR error; + struct hive_key *subkey; + const struct hive_key *root = (const struct hive_key *)test_data; + TALLOC_CTX *mem_ctx = tctx; + uint32_t type; + uint8_t d[] = { 0x42, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 4 }, data; + + error = hive_key_add_name(mem_ctx, root, "EYA Nested Key", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + error = hive_get_value(mem_ctx, subkey, "Answer", &type, &data); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "getting missing value"); + + error = hive_key_set_value(subkey, "Answer", REG_DWORD, db); + torture_assert_werr_ok(tctx, error, "hive_key_set_value"); + + error = hive_get_value(mem_ctx, subkey, "Answer", &type, &data); + torture_assert_werr_ok(tctx, error, "getting value"); + + torture_assert_int_equal(tctx, data.length, 4, "value length"); + torture_assert_int_equal(tctx, type, REG_DWORD, "value type"); + + torture_assert_mem_equal(tctx, data.data, db.data, 4, "value data"); + + return true; +} + +static bool test_del_value(struct torture_context *tctx, const void *test_data) +{ + WERROR error; + struct hive_key *subkey; + const struct hive_key *root = (const struct hive_key *)test_data; + TALLOC_CTX *mem_ctx = tctx; + uint32_t type; + uint8_t d[] = { 0x42, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 4 }; + + error = hive_key_add_name(mem_ctx, root, "EEYA Nested Key", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + error = hive_key_set_value(subkey, "Answer", REG_DWORD, db); + torture_assert_werr_ok(tctx, error, "hive_key_set_value"); + + error = hive_key_del_value(mem_ctx, subkey, "Answer"); + torture_assert_werr_ok(tctx, error, "deleting value"); + + error = hive_get_value(mem_ctx, subkey, "Answer", &type, &db); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, "getting value"); + + error = hive_key_del_value(mem_ctx, subkey, "Answer"); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "deleting value"); + + return true; +} + +static bool test_list_values(struct torture_context *tctx, + const void *test_data) +{ + WERROR error; + struct hive_key *subkey; + const struct hive_key *root = (const struct hive_key *)test_data; + TALLOC_CTX *mem_ctx = tctx; + uint32_t type; + uint8_t d[] = { 0x42, 0x00, 0x00, 0x00 }; + DATA_BLOB db = { d, 4 }, data; + const char *name; + + error = hive_key_add_name(mem_ctx, root, "AYAYA Nested Key", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + error = hive_key_set_value(subkey, "Answer", REG_DWORD, db); + torture_assert_werr_ok(tctx, error, "hive_key_set_value"); + + error = hive_get_value_by_index(mem_ctx, subkey, 0, &name, + &type, &data); + torture_assert_werr_ok(tctx, error, "getting value"); + + torture_assert_str_equal(tctx, name, "Answer", "value name"); + + torture_assert_int_equal(tctx, data.length, 4, "value length"); + torture_assert_int_equal(tctx, type, REG_DWORD, "value type"); + + torture_assert_mem_equal(tctx, data.data, db.data, 4, "value data"); + + error = hive_get_value_by_index(mem_ctx, subkey, 1, &name, + &type, &data); + torture_assert_werr_equal(tctx, error, WERR_NO_MORE_ITEMS, + "getting missing value"); + + return true; +} + +static bool test_hive_security(struct torture_context *tctx, const void *_data) +{ + struct hive_key *subkey = NULL; + const struct hive_key *root = _data; + WERROR error; + struct security_descriptor *osd, *nsd; + + osd = security_descriptor_dacl_create(tctx, + 0, + NULL, NULL, + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + + + error = hive_key_add_name(tctx, root, "SecurityKey", NULL, + osd, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + error = hive_get_sec_desc(tctx, subkey, &nsd); + torture_assert_werr_ok (tctx, error, "getting security descriptor"); + + torture_assert(tctx, security_descriptor_equal(osd, nsd), + "security descriptor changed!"); + + /* Create a fresh security descriptor */ + talloc_free(osd); + osd = security_descriptor_dacl_create(tctx, + 0, + NULL, NULL, + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + + error = hive_set_sec_desc(subkey, osd); + torture_assert_werr_ok(tctx, error, "setting security descriptor"); + + error = hive_get_sec_desc(tctx, subkey, &nsd); + torture_assert_werr_ok (tctx, error, "getting security descriptor"); + + torture_assert(tctx, security_descriptor_equal(osd, nsd), + "security descriptor changed!"); + + return true; +} + +static void tcase_add_tests(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test_const(tcase, "del_nonexistent_key", + test_del_nonexistent_key); + torture_tcase_add_simple_test_const(tcase, "add_subkey", + test_add_subkey); + torture_tcase_add_simple_test(tcase, "flush_key", + test_flush_key); + /* test_del_recursive() test must run before test_keyinfo_root(). + test_keyinfo_root() checks the number of subkeys, which verifies + the recursive delete worked properly. */ + torture_tcase_add_simple_test_const(tcase, "del_recursive", + test_del_recursive); + torture_tcase_add_simple_test_const(tcase, "get_info", + test_keyinfo_root); + torture_tcase_add_simple_test(tcase, "get_info_nums", + test_keyinfo_nums); + torture_tcase_add_simple_test_const(tcase, "set_value", + test_set_value); + torture_tcase_add_simple_test_const(tcase, "get_value", + test_get_value); + torture_tcase_add_simple_test_const(tcase, "list_values", + test_list_values); + torture_tcase_add_simple_test_const(tcase, "del_key", + test_del_key); + torture_tcase_add_simple_test_const(tcase, "del_value", + test_del_value); + torture_tcase_add_simple_test_const(tcase, "check hive security", + test_hive_security); +} + +static bool hive_setup_ldb(struct torture_context *tctx, void **data) +{ + struct hive_key *key; + WERROR error; + char *dirname; + NTSTATUS status; + + status = torture_temp_dir(tctx, "hive-ldb", &dirname); + if (!NT_STATUS_IS_OK(status)) + return false; + + rmdir(dirname); + + error = reg_open_ldb_file(tctx, dirname, NULL, NULL, tctx->ev, tctx->lp_ctx, &key); + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Unable to initialize ldb hive\n"); + return false; + } + + *data = key; + + return true; +} + +static bool hive_setup_regf(struct torture_context *tctx, void **data) +{ + struct hive_key *key; + WERROR error; + char *dirname; + NTSTATUS status; + + status = torture_temp_dir(tctx, "hive-regf", &dirname); + if (!NT_STATUS_IS_OK(status)) + return false; + + rmdir(dirname); + + error = reg_create_regf_file(tctx, dirname, 5, &key); + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Unable to create new regf file\n"); + return false; + } + + *data = key; + + return true; +} + +struct torture_suite *torture_registry_hive(TALLOC_CTX *mem_ctx) +{ + struct torture_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "hive"); + + tcase = torture_suite_add_tcase(suite, "ldb"); + torture_tcase_set_fixture(tcase, hive_setup_ldb, NULL); + tcase_add_tests(tcase); + + tcase = torture_suite_add_tcase(suite, "regf"); + torture_tcase_set_fixture(tcase, hive_setup_regf, NULL); + tcase_add_tests(tcase); + + return suite; +} diff --git a/source4/lib/registry/tests/registry.c b/source4/lib/registry/tests/registry.c new file mode 100644 index 0000000..4d94b5a --- /dev/null +++ b/source4/lib/registry/tests/registry.c @@ -0,0 +1,645 @@ +/* + Unix SMB/CIFS implementation. + + local testing of registry library - registry backend + + Copyright (C) Jelmer Vernooij 2005-2007 + + 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 3 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 "lib/registry/registry.h" +#include "torture/torture.h" +#include "librpc/gen_ndr/winreg.h" +#include "libcli/security/security.h" +#include "system/filesys.h" +#include "lib/registry/tests/proto.h" + +/** + * Test obtaining a predefined key. + */ +static bool test_get_predefined(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root; + WERROR error; + + error = reg_get_predefined_key(rctx, HKEY_CLASSES_ROOT, &root); + torture_assert_werr_ok(tctx, error, + "getting predefined key failed"); + return true; +} + +/** + * Test obtaining a predefined key. + */ +static bool test_get_predefined_unknown(struct torture_context *tctx, + void *_data) +{ + struct registry_context *rctx = _data; + struct registry_key *root; + WERROR error; + + error = reg_get_predefined_key(rctx, 1337, &root); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "getting predefined key failed"); + return true; +} + +static bool test_predef_key_by_name(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root; + WERROR error; + + error = reg_get_predefined_key_by_name(rctx, "HKEY_CLASSES_ROOT", + &root); + torture_assert_werr_ok(tctx, error, + "getting predefined key failed"); + + error = reg_get_predefined_key_by_name(rctx, "HKEY_classes_ROOT", + &root); + torture_assert_werr_ok(tctx, error, + "getting predefined key case insensitively failed"); + + return true; +} + +static bool test_predef_key_by_name_invalid(struct torture_context *tctx, + void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root; + WERROR error; + + error = reg_get_predefined_key_by_name(rctx, "BLA", &root); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "getting predefined key failed"); + return true; +} + +/** + * Test creating a new subkey + */ +static bool test_create_subkey(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root, *newkey; + WERROR error; + + error = reg_get_predefined_key(rctx, HKEY_CLASSES_ROOT, &root); + torture_assert_werr_ok(tctx, error, + "getting predefined key failed"); + + error = reg_key_add_name(rctx, root, "Bad Bentheim", NULL, NULL, + &newkey); + torture_assert_werr_ok(tctx, error, "Creating key return code"); + torture_assert(tctx, newkey != NULL, "Creating new key"); + + return true; +} + +/** + * Test creating a new nested subkey + */ +static bool test_create_nested_subkey(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root, *newkey; + WERROR error; + + error = reg_get_predefined_key(rctx, HKEY_CLASSES_ROOT, &root); + torture_assert_werr_ok(tctx, error, + "getting predefined key failed"); + + error = reg_key_add_name(rctx, root, "Hamburg\\Hamburg", NULL, NULL, + &newkey); + torture_assert_werr_ok(tctx, error, "Creating key return code"); + torture_assert(tctx, newkey != NULL, "Creating new key"); + + return true; +} + +/** + * Test creating a new subkey + */ +static bool test_key_add_abs_top(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root; + WERROR error; + + error = reg_key_add_abs(tctx, rctx, "HKEY_CLASSES_ROOT", 0, NULL, + &root); + torture_assert_werr_equal(tctx, error, WERR_ALREADY_EXISTS, + "create top level"); + + return true; +} + +/** + * Test creating a new subkey + */ +static bool test_key_add_abs(struct torture_context *tctx, void *_data) +{ + WERROR error; + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root, *result1, *result2; + + error = reg_key_add_abs(tctx, rctx, "HKEY_CLASSES_ROOT\\bloe", 0, NULL, + &result1); + torture_assert_werr_ok(tctx, error, "create lowest"); + + error = reg_key_add_abs(tctx, rctx, "HKEY_CLASSES_ROOT\\bloe\\bla", 0, + NULL, &result1); + torture_assert_werr_ok(tctx, error, "create nested"); + + error = reg_get_predefined_key(rctx, HKEY_CLASSES_ROOT, &root); + torture_assert_werr_ok(tctx, error, + "getting predefined key failed"); + + error = reg_open_key(tctx, root, "bloe", &result2); + torture_assert_werr_ok(tctx, error, "opening key"); + + error = reg_open_key(tctx, root, "bloe\\bla", &result2); + torture_assert_werr_ok(tctx, error, "opening key"); + + return true; +} + + +static bool test_del_key(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root, *newkey; + WERROR error; + + error = reg_get_predefined_key(rctx, HKEY_CLASSES_ROOT, &root); + torture_assert_werr_ok(tctx, error, + "getting predefined key failed"); + + error = reg_key_add_name(rctx, root, "Polen", NULL, NULL, &newkey); + + torture_assert_werr_ok(tctx, error, "Creating key return code"); + torture_assert(tctx, newkey != NULL, "Creating new key"); + + error = reg_key_del(tctx, root, "Polen"); + torture_assert_werr_ok(tctx, error, "Delete key"); + + error = reg_key_del(tctx, root, "Polen"); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "Delete missing key"); + + return true; +} + +/** + * Convenience function for opening the HKEY_CLASSES_ROOT hive and + * creating a single key for testing purposes. + */ +static bool create_test_key(struct torture_context *tctx, + struct registry_context *rctx, + const char *name, + struct registry_key **root, + struct registry_key **subkey) +{ + WERROR error; + + error = reg_get_predefined_key(rctx, HKEY_CLASSES_ROOT, root); + torture_assert_werr_ok(tctx, error, + "getting predefined key failed"); + + error = reg_key_add_name(rctx, *root, name, NULL, NULL, subkey); + torture_assert_werr_ok(tctx, error, "Creating key return code"); + + return true; +} + + +static bool test_flush_key(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root, *subkey; + WERROR error; + + if (!create_test_key(tctx, rctx, "Bremen", &root, &subkey)) + return false; + + error = reg_key_flush(subkey); + torture_assert_werr_ok(tctx, error, "flush key"); + + torture_assert_werr_equal(tctx, reg_key_flush(NULL), + WERR_INVALID_PARAMETER, "flush key"); + + return true; +} + +static bool test_query_key(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root, *subkey; + WERROR error; + NTTIME last_changed_time; + uint32_t num_subkeys, num_values; + const char *classname; + const char *data = "temp"; + + if (!create_test_key(tctx, rctx, "Muenchen", &root, &subkey)) + return false; + + error = reg_key_get_info(tctx, subkey, &classname, + &num_subkeys, &num_values, + &last_changed_time, NULL, NULL, NULL); + + torture_assert_werr_ok(tctx, error, "get info key"); + torture_assert(tctx, classname == NULL, "classname"); + torture_assert_int_equal(tctx, num_subkeys, 0, "num subkeys"); + torture_assert_int_equal(tctx, num_values, 0, "num values"); + + error = reg_val_set(subkey, "", REG_SZ, + data_blob_string_const(data)); + torture_assert_werr_ok(tctx, error, "set default value"); + + error = reg_key_get_info(tctx, subkey, &classname, + &num_subkeys, &num_values, + &last_changed_time, NULL, NULL, NULL); + + torture_assert_werr_ok(tctx, error, "get info key"); + torture_assert(tctx, classname == NULL, "classname"); + torture_assert_int_equal(tctx, num_subkeys, 0, "num subkeys"); + torture_assert_int_equal(tctx, num_values, 1, "num values"); + + return true; +} + +static bool test_query_key_nums(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *root, *subkey1, *subkey2; + WERROR error; + uint32_t num_subkeys, num_values; + char data[4]; + SIVAL(data, 0, 42); + + if (!create_test_key(tctx, rctx, "Berlin", &root, &subkey1)) + return false; + + error = reg_key_add_name(rctx, subkey1, "Bentheim", NULL, NULL, + &subkey2); + torture_assert_werr_ok(tctx, error, "Creating key return code"); + + error = reg_val_set(subkey1, "Answer", REG_DWORD, + data_blob_talloc(tctx, &data, sizeof(data))); + torture_assert_werr_ok(tctx, error, "set value"); + + error = reg_key_get_info(tctx, subkey1, NULL, &num_subkeys, + &num_values, NULL, NULL, NULL, NULL); + + torture_assert_werr_ok(tctx, error, "get info key"); + torture_assert_int_equal(tctx, num_subkeys, 1, "num subkeys"); + torture_assert_int_equal(tctx, num_values, 1, "num values"); + + return true; +} + +/** + * Test that the subkeys of a key can be enumerated, that + * the returned parameters for get_subkey_by_index are optional and + * that enumerating the parents of a non-top-level node works. + */ +static bool test_list_subkeys(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *subkey = NULL, *root; + WERROR error; + NTTIME last_mod_time; + const char *classname, *name; + + if (!create_test_key(tctx, rctx, "Goettingen", &root, &subkey)) + return false; + + error = reg_key_get_subkey_by_index(tctx, root, 0, &name, &classname, + &last_mod_time); + + torture_assert_werr_ok(tctx, error, "Enum keys return code"); + torture_assert_str_equal(tctx, name, "Goettingen", "Enum keys data"); + + + error = reg_key_get_subkey_by_index(tctx, root, 0, NULL, NULL, NULL); + + torture_assert_werr_ok(tctx, error, + "Enum keys with NULL arguments return code"); + + error = reg_key_get_subkey_by_index(tctx, root, 1, NULL, NULL, NULL); + + torture_assert_werr_equal(tctx, error, WERR_NO_MORE_ITEMS, + "Invalid error for no more items"); + + error = reg_key_get_subkey_by_index(tctx, subkey, 0, NULL, NULL, NULL); + + torture_assert_werr_equal(tctx, error, WERR_NO_MORE_ITEMS, + "Invalid error for no more items"); + + return true; +} + +/** + * Test setting a value + */ +static bool test_set_value(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *subkey = NULL, *root; + WERROR error; + char data[4]; + + SIVAL(data, 0, 42); + + if (!create_test_key(tctx, rctx, "Dusseldorf", &root, &subkey)) + return false; + + error = reg_val_set(subkey, "Answer", REG_DWORD, + data_blob_talloc(tctx, data, sizeof(data))); + torture_assert_werr_ok (tctx, error, "setting value"); + + return true; +} + +/** + * Test getting/setting security descriptors + */ +static bool test_security(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *subkey = NULL, *root; + WERROR error; + struct security_descriptor *osd, *nsd; + + if (!create_test_key(tctx, rctx, "Düsseldorf", &root, &subkey)) + return false; + + osd = security_descriptor_dacl_create(tctx, + 0, + NULL, NULL, + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + + error = reg_set_sec_desc(subkey, osd); + torture_assert_werr_ok(tctx, error, "setting security descriptor"); + + error = reg_get_sec_desc(tctx, subkey, &nsd); + torture_assert_werr_ok (tctx, error, "getting security descriptor"); + + torture_assert(tctx, security_descriptor_equal(osd, nsd), + "security descriptor changed!"); + + return true; +} + +/** + * Test getting a value + */ +static bool test_get_value(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *subkey = NULL, *root; + WERROR error; + DATA_BLOB data; + char value[4]; + uint32_t type; + const char *data_val = "temp"; + + SIVAL(value, 0, 42); + + if (!create_test_key(tctx, rctx, "Duisburg", &root, &subkey)) + return false; + + error = reg_key_get_value_by_name(tctx, subkey, __FUNCTION__, &type, + &data); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "getting missing value"); + + error = reg_val_set(subkey, __FUNCTION__, REG_DWORD, + data_blob_talloc(tctx, value, sizeof(value))); + torture_assert_werr_ok(tctx, error, "setting value"); + + error = reg_key_get_value_by_name(tctx, subkey, __FUNCTION__, &type, + &data); + torture_assert_werr_ok(tctx, error, "getting value"); + + torture_assert_int_equal(tctx, sizeof(value), data.length, "value length ok"); + torture_assert_mem_equal(tctx, data.data, value, sizeof(value), + "value content ok"); + torture_assert_int_equal(tctx, REG_DWORD, type, "value type"); + + error = reg_val_set(subkey, "", REG_SZ, + data_blob_talloc(tctx, data_val, + strlen(data_val))); + torture_assert_werr_ok(tctx, error, "set default value"); + + error = reg_key_get_value_by_name(tctx, subkey, "", &type, + &data); + torture_assert_werr_ok(tctx, error, "getting default value"); + torture_assert_int_equal(tctx, REG_SZ, type, "value type ok"); + torture_assert_int_equal(tctx, strlen(data_val), data.length, "value length ok"); + torture_assert_str_equal(tctx, data_val, (char *)data.data, "value ok"); + + return true; +} + +/** + * Test unsetting a value + */ +static bool test_del_value(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx =(struct registry_context *)_data; + struct registry_key *subkey = NULL, *root; + WERROR error; + DATA_BLOB data; + uint32_t type; + char value[4]; + const char *data_val = "temp"; + + SIVAL(value, 0, 42); + + if (!create_test_key(tctx, rctx, "Warschau", &root, &subkey)) + return false; + + error = reg_key_get_value_by_name(tctx, subkey, __FUNCTION__, &type, + &data); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "getting missing value"); + + error = reg_val_set(subkey, __FUNCTION__, REG_DWORD, + data_blob_talloc(tctx, value, sizeof(value))); + torture_assert_werr_ok (tctx, error, "setting value"); + + error = reg_del_value(tctx, subkey, __FUNCTION__); + torture_assert_werr_ok (tctx, error, "unsetting value"); + + error = reg_key_get_value_by_name(tctx, subkey, __FUNCTION__, + &type, &data); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "getting missing value"); + + error = reg_del_value(tctx, subkey, ""); + torture_assert_werr_equal(tctx, error, WERR_FILE_NOT_FOUND, + "unsetting missing default value"); + + error = reg_val_set(subkey, "", REG_SZ, + data_blob_talloc(tctx, data_val, + strlen(data_val))); + torture_assert_werr_ok(tctx, error, "set default value"); + + error = reg_del_value(tctx, subkey, ""); + torture_assert_werr_ok (tctx, error, "unsetting default value"); + + return true; +} + +/** + * Test listing values + */ +static bool test_list_values(struct torture_context *tctx, void *_data) +{ + struct registry_context *rctx = (struct registry_context *)_data; + struct registry_key *subkey = NULL, *root; + WERROR error; + DATA_BLOB data; + uint32_t type; + const char *name; + char value[4]; + const char *data_val = "temp"; + + SIVAL(value, 0, 42); + + if (!create_test_key(tctx, rctx, "Bonn", &root, &subkey)) + return false; + + error = reg_val_set(subkey, "bar", REG_DWORD, + data_blob_talloc(tctx, value, sizeof(value))); + torture_assert_werr_ok (tctx, error, "setting value"); + + error = reg_key_get_value_by_index(tctx, subkey, 0, &name, + &type, &data); + torture_assert_werr_ok(tctx, error, "getting value"); + + torture_assert_str_equal(tctx, name, "bar", "value name"); + torture_assert_int_equal(tctx, sizeof(value), data.length, "value length"); + torture_assert_mem_equal(tctx, data.data, value, sizeof(value), + "value content"); + torture_assert_int_equal(tctx, REG_DWORD, type, "value type"); + + error = reg_key_get_value_by_index(tctx, subkey, 1, &name, + &type, &data); + torture_assert_werr_equal(tctx, error, WERR_NO_MORE_ITEMS, + "getting missing value"); + + error = reg_val_set(subkey, "", REG_SZ, + data_blob_talloc(tctx, data_val, strlen(data_val))); + torture_assert_werr_ok(tctx, error, "set default value"); + + error = reg_key_get_value_by_index(tctx, subkey, 0, &name, + &type, &data); + torture_assert_werr_ok(tctx, error, "getting default value"); + torture_assert_int_equal(tctx, REG_SZ, type, "value type ok"); + torture_assert_int_equal(tctx, strlen(data_val), data.length, "value length ok"); + torture_assert_str_equal(tctx, data_val, (char *)data.data, "value ok"); + + return true; +} + +static bool setup_local_registry(struct torture_context *tctx, void **data) +{ + struct registry_context *rctx; + WERROR error; + char *tempdir; + NTSTATUS status; + struct hive_key *hive_key; + const char *filename; + + error = reg_open_local(tctx, &rctx); + torture_assert_werr_ok(tctx, error, "Opening local registry failed"); + + status = torture_temp_dir(tctx, "registry-local", &tempdir); + torture_assert_ntstatus_ok(tctx, status, "Creating temp dir failed"); + + filename = talloc_asprintf(tctx, "%s/classes_root.ldb", tempdir); + error = reg_open_ldb_file(tctx, filename, NULL, NULL, tctx->ev, tctx->lp_ctx, &hive_key); + torture_assert_werr_ok(tctx, error, "Opening classes_root file failed"); + + error = reg_mount_hive(rctx, hive_key, HKEY_CLASSES_ROOT, NULL); + torture_assert_werr_ok(tctx, error, "Mounting hive failed"); + + *data = rctx; + + return true; +} + +static void tcase_add_tests(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "list_subkeys", + test_list_subkeys); + torture_tcase_add_simple_test(tcase, "get_predefined_key", + test_get_predefined); + torture_tcase_add_simple_test(tcase, "get_predefined_key", + test_get_predefined_unknown); + torture_tcase_add_simple_test(tcase, "create_key", + test_create_subkey); + torture_tcase_add_simple_test(tcase, "create_key", + test_create_nested_subkey); + torture_tcase_add_simple_test(tcase, "key_add_abs", + test_key_add_abs); + torture_tcase_add_simple_test(tcase, "key_add_abs_top", + test_key_add_abs_top); + torture_tcase_add_simple_test(tcase, "set_value", + test_set_value); + torture_tcase_add_simple_test(tcase, "get_value", + test_get_value); + torture_tcase_add_simple_test(tcase, "list_values", + test_list_values); + torture_tcase_add_simple_test(tcase, "del_key", + test_del_key); + torture_tcase_add_simple_test(tcase, "del_value", + test_del_value); + torture_tcase_add_simple_test(tcase, "flush_key", + test_flush_key); + torture_tcase_add_simple_test(tcase, "query_key", + test_query_key); + torture_tcase_add_simple_test(tcase, "query_key_nums", + test_query_key_nums); + torture_tcase_add_simple_test(tcase, "test_predef_key_by_name", + test_predef_key_by_name); + torture_tcase_add_simple_test(tcase, "security", + test_security); + torture_tcase_add_simple_test(tcase,"test_predef_key_by_name_invalid", + test_predef_key_by_name_invalid); +} + +struct torture_suite *torture_registry_registry(TALLOC_CTX *mem_ctx) +{ + struct torture_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "registry"); + + tcase = torture_suite_add_tcase(suite, "local"); + torture_tcase_set_fixture(tcase, setup_local_registry, NULL); + tcase_add_tests(tcase); + + return suite; +} diff --git a/source4/lib/registry/tools/common.c b/source4/lib/registry/tools/common.c new file mode 100644 index 0000000..7fd5bd2 --- /dev/null +++ b/source4/lib/registry/tools/common.c @@ -0,0 +1,88 @@ +/* + Unix SMB/CIFS implementation. + Popt routines specifically for registry + + Copyright (C) Jelmer Vernooij 2007 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "auth/credentials/credentials.h" +#include "lib/registry/registry.h" +#include "lib/registry/tools/common.h" + +struct registry_context *reg_common_open_remote(const char *remote, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct cli_credentials *creds) +{ + struct registry_context *h = NULL; + WERROR error; + + error = reg_open_remote(NULL, &h, creds, lp_ctx, remote, ev_ctx); + + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Unable to open remote registry at %s:%s \n", + remote, win_errstr(error)); + return NULL; + } + + return h; +} + +struct registry_key *reg_common_open_file(const char *path, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct cli_credentials *creds) +{ + struct hive_key *hive_root; + struct registry_context *h = NULL; + WERROR error; + + error = reg_open_hive(ev_ctx, path, NULL, creds, ev_ctx, lp_ctx, &hive_root); + + if(!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Unable to open '%s': %s \n", + path, win_errstr(error)); + return NULL; + } + + error = reg_open_local(NULL, &h); + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Unable to initialize local registry: %s\n", + win_errstr(error)); + return NULL; + } + + return reg_import_hive_key(h, hive_root, -1, NULL); +} + +struct registry_context *reg_common_open_local(struct cli_credentials *creds, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx) +{ + WERROR error; + struct registry_context *h = NULL; + + error = reg_open_samba(NULL, &h, ev_ctx, lp_ctx, NULL, creds); + + if(!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Unable to open local registry:%s \n", + win_errstr(error)); + return NULL; + } + + return h; +} diff --git a/source4/lib/registry/tools/regdiff.c b/source4/lib/registry/tools/regdiff.c new file mode 100644 index 0000000..977a974 --- /dev/null +++ b/source4/lib/registry/tools/regdiff.c @@ -0,0 +1,182 @@ +/* + Unix SMB/CIFS implementation. + simple registry frontend + + Copyright (C) Jelmer Vernooij 2004-2007 + Copyright (C) Wilco Baan Hofman 2006 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/registry/registry.h" +#include "lib/events/events.h" +#include "lib/cmdline/cmdline.h" +#include "lib/registry/tools/common.h" +#include "param/param.h" + +enum reg_backend { REG_UNKNOWN, REG_LOCAL, REG_REMOTE, REG_NULL }; + +static struct registry_context *open_backend(TALLOC_CTX *mem_ctx, + poptContext pc, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + enum reg_backend backend, + const char *remote_host) +{ + WERROR error; + struct registry_context *ctx; + struct cli_credentials *creds = samba_cmdline_get_creds(); + + switch (backend) { + case REG_UNKNOWN: + poptPrintUsage(pc, stderr, 0); + return NULL; + case REG_LOCAL: + error = reg_open_samba(mem_ctx, &ctx, ev_ctx, lp_ctx, NULL, + creds); + break; + case REG_REMOTE: + error = reg_open_remote(mem_ctx, &ctx, creds, lp_ctx, + remote_host, ev_ctx); + break; + case REG_NULL: + error = reg_open_local(mem_ctx, &ctx); + break; + } + + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Error: %s\n", win_errstr(error)); + return NULL; + } + + return ctx; +} + +int main(int argc, char **argv) +{ + const char **argv_const = discard_const_p(const char *, argv); + int opt; + poptContext pc; + char *outputfile = NULL; + enum reg_backend backend1 = REG_UNKNOWN, backend2 = REG_UNKNOWN; + const char *remote1 = NULL, *remote2 = NULL; + struct registry_context *h1 = NULL, *h2 = NULL; + WERROR error; + struct poptOption long_options[] = { + POPT_AUTOHELP + {"output", 'o', POPT_ARG_STRING, &outputfile, 0, "output file to use", NULL }, + {"null", 'n', POPT_ARG_NONE, NULL, 'n', "Diff from NULL", NULL }, + {"remote", 'R', POPT_ARG_STRING, NULL, 'R', "Connect to remote server" , NULL }, + {"local", 'L', POPT_ARG_NONE, NULL, 'L', "Open local registry", NULL }, + POPT_COMMON_SAMBA + POPT_COMMON_CREDENTIALS + POPT_COMMON_VERSION + {0} + }; + TALLOC_CTX *ctx; + void *callback_data; + struct tevent_context *ev_ctx; + struct reg_diff_callbacks *callbacks; + struct loadparm_context *lp_ctx = NULL; + bool ok; + + ctx = talloc_init("regdiff"); + if (ctx == NULL) { + exit(ENOMEM); + } + + ok = samba_cmdline_init(ctx, + SAMBA_CMDLINE_CONFIG_CLIENT, + false /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(ctx); + exit(1); + } + + pc = samba_popt_get_context(getprogname(), + argc, + argv_const, + long_options, + 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(ctx); + exit(1); + } + + while((opt = poptGetNextOpt(pc)) != -1) { + error = WERR_OK; + switch(opt) { + case 'L': + if (backend1 == REG_UNKNOWN) + backend1 = REG_LOCAL; + else if (backend2 == REG_UNKNOWN) + backend2 = REG_LOCAL; + break; + case 'n': + if (backend1 == REG_UNKNOWN) + backend1 = REG_NULL; + else if (backend2 == REG_UNKNOWN) + backend2 = REG_NULL; + break; + case 'R': + if (backend1 == REG_UNKNOWN) { + backend1 = REG_REMOTE; + remote1 = poptGetOptArg(pc); + } else if (backend2 == REG_UNKNOWN) { + backend2 = REG_REMOTE; + remote2 = poptGetOptArg(pc); + } + break; + case POPT_ERROR_BADOPT: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + exit(1); + } + + } + + ev_ctx = s4_event_context_init(ctx); + lp_ctx = samba_cmdline_get_lp_ctx(); + + h1 = open_backend(ctx, pc, ev_ctx, lp_ctx, backend1, remote1); + if (h1 == NULL) + return 1; + + h2 = open_backend(ctx, pc, ev_ctx, lp_ctx, backend2, remote2); + if (h2 == NULL) + return 1; + + poptFreeContext(pc); + samba_cmdline_burn(argc, argv); + + error = reg_dotreg_diff_save(ctx, outputfile, &callbacks, &callback_data); + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Problem saving registry diff to '%s': %s\n", + outputfile, win_errstr(error)); + return -1; + } + + error = reg_generate_diff(h1, h2, callbacks, callback_data); + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Unable to generate diff between keys: %s\n", + win_errstr(error)); + return -1; + } + + return 0; +} diff --git a/source4/lib/registry/tools/regpatch.c b/source4/lib/registry/tools/regpatch.c new file mode 100644 index 0000000..eafaff6 --- /dev/null +++ b/source4/lib/registry/tools/regpatch.c @@ -0,0 +1,119 @@ +/* + Unix SMB/CIFS implementation. + simple registry frontend + + Copyright (C) 2004-2007 Jelmer Vernooij, jelmer@samba.org + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/events/events.h" +#include "lib/registry/registry.h" +#include "lib/cmdline/cmdline.h" +#include "lib/registry/tools/common.h" +#include "param/param.h" +#include "events/events.h" + +int main(int argc, char **argv) +{ + const char **argv_const = discard_const_p(const char *, argv); + bool ok; + TALLOC_CTX *mem_ctx = NULL; + int opt; + poptContext pc; + const char *patch; + struct registry_context *h; + const char *file = NULL; + const char *remote = NULL; + struct tevent_context *ev_ctx; + struct loadparm_context *lp_ctx = NULL; + struct cli_credentials *creds = NULL; + + struct poptOption long_options[] = { + POPT_AUTOHELP + {"remote", 'R', POPT_ARG_STRING, &remote, 0, "connect to specified remote server", NULL}, + {"file", 'F', POPT_ARG_STRING, &file, 0, "file path", NULL }, + POPT_COMMON_SAMBA + POPT_COMMON_CREDENTIALS + POPT_COMMON_VERSION + POPT_TABLEEND + }; + + mem_ctx = talloc_init("regtree.c/main"); + if (mem_ctx == NULL) { + exit(ENOMEM); + } + + ok = samba_cmdline_init(mem_ctx, + SAMBA_CMDLINE_CONFIG_CLIENT, + false /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(mem_ctx); + exit(1); + } + + pc = samba_popt_get_context(getprogname(), + argc, + argv_const, + long_options, + 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(mem_ctx); + exit(1); + } + + while((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case POPT_ERROR_BADOPT: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + exit(1); + } + } + + ev_ctx = s4_event_context_init(NULL); + lp_ctx = samba_cmdline_get_lp_ctx(); + creds = samba_cmdline_get_creds(); + + if (remote) { + h = reg_common_open_remote (remote, ev_ctx, lp_ctx, creds); + } else { + h = reg_common_open_local (creds, ev_ctx, lp_ctx); + } + + if (h == NULL) { + TALLOC_FREE(mem_ctx); + return 1; + } + + patch = talloc_strdup(mem_ctx, poptGetArg(pc)); + if (patch == NULL) { + poptPrintUsage(pc, stderr, 0); + TALLOC_FREE(mem_ctx); + return 1; + } + + poptFreeContext(pc); + samba_cmdline_burn(argc, argv); + + reg_diff_apply(h, patch); + + TALLOC_FREE(mem_ctx); + + return 0; +} diff --git a/source4/lib/registry/tools/regshell.c b/source4/lib/registry/tools/regshell.c new file mode 100644 index 0000000..dc7bf54 --- /dev/null +++ b/source4/lib/registry/tools/regshell.c @@ -0,0 +1,708 @@ +/* + Unix SMB/CIFS implementation. + simple registry frontend + + Copyright (C) Jelmer Vernooij 2004-2007 + Copyright (C) Wilco Baan Hofman 2009 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/registry/registry.h" +#include "lib/cmdline/cmdline.h" +#include "lib/events/events.h" +#include "system/time.h" +#include "../libcli/smbreadline/smbreadline.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "lib/registry/tools/common.h" +#include "param/param.h" + +struct regshell_context { + struct registry_context *registry; + char *path; + char *predef; + struct registry_key *current; + struct registry_key *root; +}; + +static WERROR get_full_path(struct regshell_context *ctx, const char *path, char **ret_path) +{ + const char *dir; + char *tmp; + char *new_path; + + if (path[0] == '\\') { + new_path = talloc_strdup(ctx, ""); + } else { + new_path = talloc_strdup(ctx, ctx->path); + } + + dir = strtok(discard_const_p(char, path), "\\"); + if (dir == NULL) { + *ret_path = new_path; + return WERR_OK; + } + do { + if (strcmp(dir, "..") == 0) { + if (strchr(new_path, '\\')) { + new_path[strrchr(new_path, '\\') - new_path] = '\0'; + } else { + tmp = new_path; + new_path = talloc_strdup(ctx, ""); + talloc_free(tmp); + } + continue; + } + if (strcmp(dir, ".") == 0) { + continue; + } + + tmp = new_path; + /* No prepending a backslash */ + if (strcmp(new_path, "") == 0) { + new_path = talloc_strdup(ctx, dir); + } else { + new_path = talloc_asprintf(ctx, "%s\\%s", new_path, dir); + } + talloc_free(tmp); + + } while ((dir = strtok(NULL, "\\"))); + + *ret_path = new_path; + return WERR_OK; +} + +/* * + * ck/cd - change key + * ls - list values/keys + * rmval/rm - remove value + * rmkey/rmdir - remove key + * mkkey/mkdir - make key + * ch - change hive + * info - show key info + * save - save hive + * print - print value + * help + * exit + */ + +static WERROR cmd_info(struct regshell_context *ctx, int argc, const char **argv) +{ + struct security_descriptor *sec_desc = NULL; + time_t last_mod; + WERROR error; + const char *classname = NULL; + NTTIME last_change; + uint32_t max_subkeynamelen; + uint32_t max_valnamelen; + uint32_t max_valbufsize; + uint32_t num_subkeys; + uint32_t num_values; + + error = reg_key_get_info(ctx, ctx->current, &classname, &num_subkeys, &num_values, + &last_change, &max_subkeynamelen, &max_valnamelen, &max_valbufsize); + if (!W_ERROR_IS_OK(error)) { + printf("Error getting key info: %s\n", win_errstr(error)); + return error; + } + + + printf("Name: %s\n", strchr(ctx->path, '\\')?strrchr(ctx->path, '\\')+1: + ctx->path); + printf("Full path: %s\n", ctx->path); + if (classname != NULL) + printf("Key Class: %s\n", classname); + last_mod = nt_time_to_unix(last_change); + printf("Time Last Modified: %s", ctime(&last_mod)); + printf("Number of subkeys: %d\n", num_subkeys); + printf("Number of values: %d\n", num_values); + + if (max_valnamelen > 0) + printf("Maximum value name length: %d\n", max_valnamelen); + + if (max_valbufsize > 0) + printf("Maximum value data length: %d\n", max_valbufsize); + + if (max_subkeynamelen > 0) + printf("Maximum sub key name length: %d\n", max_subkeynamelen); + + error = reg_get_sec_desc(ctx, ctx->current, &sec_desc); + if (!W_ERROR_IS_OK(error)) { + printf("Error getting security descriptor: %s\n", win_errstr(error)); + return WERR_OK; + } + NDR_PRINT_DEBUG(security_descriptor, sec_desc); + talloc_free(sec_desc); + + return WERR_OK; +} + +static WERROR cmd_predef(struct regshell_context *ctx, int argc, const char **argv) +{ + struct registry_key *ret = NULL; + if (argc < 2) { + fprintf(stderr, "Usage: predef predefined-key-name\n"); + } else if (!ctx) { + fprintf(stderr, "No full registry loaded, no predefined keys defined\n"); + } else { + WERROR error = reg_get_predefined_key_by_name(ctx->registry, + argv[1], &ret); + + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Error opening predefined key %s: %s\n", + argv[1], win_errstr(error)); + return error; + } + + ctx->predef = strupper_talloc(ctx, argv[1]); + ctx->current = ret; + ctx->root = ret; + } + + return WERR_OK; +} + +static WERROR cmd_pwd(struct regshell_context *ctx, + int argc, const char **argv) +{ + if (ctx->predef) { + printf("%s\\", ctx->predef); + } + printf("%s\n", ctx->path); + return WERR_OK; +} + +static WERROR cmd_set(struct regshell_context *ctx, int argc, const char **argv) +{ + struct registry_value val; + WERROR error; + + if (argc < 4) { + fprintf(stderr, "Usage: set value-name type value\n"); + return WERR_INVALID_PARAMETER; + } + + if (!reg_string_to_val(ctx, argv[2], argv[3], &val.data_type, &val.data)) { + fprintf(stderr, "Unable to interpret data\n"); + return WERR_INVALID_PARAMETER; + } + + error = reg_val_set(ctx->current, argv[1], val.data_type, val.data); + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Error setting value: %s\n", win_errstr(error)); + return error; + } + + return WERR_OK; +} + +static WERROR cmd_ck(struct regshell_context *ctx, int argc, const char **argv) +{ + struct registry_key *nkey = NULL; + char *full_path; + WERROR error; + + if(argc == 2) { + if (!W_ERROR_IS_OK(get_full_path(ctx, argv[1], &full_path))) { + fprintf(stderr, "Unable to parse the supplied path\n"); + return WERR_INVALID_PARAMETER; + } + error = reg_open_key(ctx->registry, ctx->root, full_path, + &nkey); + if(!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Error opening specified key: %s\n", + win_errstr(error))); + return error; + } + + talloc_free(ctx->path); + ctx->path = full_path; + + ctx->current = nkey; + } + printf("New path is: %s\\%s\n", ctx->predef?ctx->predef:"", ctx->path); + + return WERR_OK; +} + +static WERROR cmd_print(struct regshell_context *ctx, int argc, const char **argv) +{ + uint32_t value_type; + DATA_BLOB value_data; + WERROR error; + + if (argc != 2) { + fprintf(stderr, "Usage: print <valuename>\n"); + return WERR_INVALID_PARAMETER; + } + + error = reg_key_get_value_by_name(ctx, ctx->current, argv[1], + &value_type, &value_data); + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "No such value '%s'\n", argv[1]); + return error; + } + + printf("%s\n%s\n", str_regtype(value_type), + reg_val_data_string(ctx, value_type, value_data)); + + return WERR_OK; +} + +static WERROR cmd_ls(struct regshell_context *ctx, int argc, const char **argv) +{ + unsigned int i; + WERROR error; + uint32_t valuetype; + DATA_BLOB valuedata; + const char *name = NULL; + + for (i = 0; W_ERROR_IS_OK(error = reg_key_get_subkey_by_index(ctx, + ctx->current, + i, + &name, + NULL, + NULL)); i++) { + printf("K %s\n", name); + } + + if (!W_ERROR_EQUAL(error, WERR_NO_MORE_ITEMS)) { + fprintf(stderr, "Error occurred while browsing through keys: %s\n", + win_errstr(error)); + return error; + } + + for (i = 0; W_ERROR_IS_OK(error = reg_key_get_value_by_index(ctx, + ctx->current, i, &name, &valuetype, &valuedata)); i++) + printf("V \"%s\" %s %s\n", name, str_regtype(valuetype), + reg_val_data_string(ctx, valuetype, valuedata)); + + return WERR_OK; +} +static WERROR cmd_mkkey(struct regshell_context *ctx, int argc, const char **argv) +{ + struct registry_key *tmp; + WERROR error; + + if(argc < 2) { + fprintf(stderr, "Usage: mkkey <keyname>\n"); + return WERR_INVALID_PARAMETER; + } + + error = reg_key_add_name(ctx, ctx->current, argv[1], 0, NULL, &tmp); + + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Error adding new subkey '%s': %s\n", argv[1], + win_errstr(error)); + return error; + } + + return WERR_OK; +} + +static WERROR cmd_rmkey(struct regshell_context *ctx, + int argc, const char **argv) +{ + WERROR error; + + if(argc < 2) { + fprintf(stderr, "Usage: rmkey <name>\n"); + return WERR_INVALID_PARAMETER; + } + + error = reg_key_del(ctx, ctx->current, argv[1]); + if(!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Error deleting '%s'\n", argv[1]); + return error; + } else { + fprintf(stderr, "Successfully deleted '%s'\n", argv[1]); + } + + return WERR_OK; +} + +static WERROR cmd_rmval(struct regshell_context *ctx, int argc, const char **argv) +{ + WERROR error; + + if(argc < 2) { + fprintf(stderr, "Usage: rmval <valuename>\n"); + return WERR_INVALID_PARAMETER; + } + + error = reg_del_value(ctx, ctx->current, argv[1]); + if(!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Error deleting value '%s'\n", argv[1]); + return error; + } else { + fprintf(stderr, "Successfully deleted value '%s'\n", argv[1]); + } + + return WERR_OK; +} + +_NORETURN_ static WERROR cmd_exit(struct regshell_context *ctx, + int argc, const char **argv) +{ + exit(0); +} + +static WERROR cmd_help(struct regshell_context *ctx, int, const char **); + +static struct { + const char *name; + const char *alias; + const char *help; + WERROR (*handle)(struct regshell_context *ctx, int argc, const char **argv); +} regshell_cmds[] = { + {"ck", "cd", "Change current key", cmd_ck }, + {"info", "i", "Show detailed information of a key", cmd_info }, + {"list", "ls", "List values/keys in current key", cmd_ls }, + {"print", "p", "Print value", cmd_print }, + {"mkkey", "mkdir", "Make new key", cmd_mkkey }, + {"rmval", "rm", "Remove value", cmd_rmval }, + {"rmkey", "rmdir", "Remove key", cmd_rmkey }, + {"pwd", "pwk", "Printing current key", cmd_pwd }, + {"set", "update", "Update value", cmd_set }, + {"help", "?", "Help", cmd_help }, + {"exit", "quit", "Exit", cmd_exit }, + {"predef", "predefined", "Go to predefined key", cmd_predef }, + {0} +}; + +static WERROR cmd_help(struct regshell_context *ctx, + int argc, const char **argv) +{ + unsigned int i; + printf("Available commands:\n"); + for(i = 0; regshell_cmds[i].name; i++) { + printf("%s - %s\n", regshell_cmds[i].name, + regshell_cmds[i].help); + } + return WERR_OK; +} + +static WERROR process_cmd(struct regshell_context *ctx, + char *line) +{ + int argc; + const char **argv = NULL; + int ret, i; + + if ((ret = poptParseArgvString(line, &argc, &argv)) != 0) { + fprintf(stderr, "regshell: %s\n", poptStrerror(ret)); + return WERR_INVALID_PARAMETER; + } + + for(i = 0; regshell_cmds[i].name; i++) { + if(!strcmp(regshell_cmds[i].name, argv[0]) || + (regshell_cmds[i].alias && !strcmp(regshell_cmds[i].alias, argv[0]))) { + return regshell_cmds[i].handle(ctx, argc, argv); + } + } + + fprintf(stderr, "No such command '%s'\n", argv[0]); + + return WERR_INVALID_PARAMETER; +} + +#define MAX_COMPLETIONS 100 + +static struct registry_key *current_key = NULL; + +static char **reg_complete_command(const char *text, int start, int end) +{ + /* Complete command */ + char **matches; + size_t len, samelen=0; + size_t i, count = 1; + + matches = malloc_array_p(char *, MAX_COMPLETIONS); + if (!matches) return NULL; + matches[0] = NULL; + + len = strlen(text); + for (i=0;regshell_cmds[i].handle && count < MAX_COMPLETIONS-1;i++) { + if (strncmp(text, regshell_cmds[i].name, len) == 0) { + matches[count] = strdup(regshell_cmds[i].name); + if (!matches[count]) + goto cleanup; + if (count == 1) + samelen = strlen(matches[count]); + else + while (strncmp(matches[count], matches[count-1], samelen) != 0) + samelen--; + count++; + } + } + + switch (count) { + case 0: /* should never happen */ + case 1: + goto cleanup; + case 2: + matches[0] = strdup(matches[1]); + break; + default: + matches[0] = strndup(matches[1], samelen); + } + matches[count] = NULL; + return matches; + +cleanup: + for (i = 0; i < count; i++) { + free(matches[i]); + } + free(matches); + return NULL; +} + +static char **reg_complete_key(const char *text, int start, int end) +{ + struct registry_key *base; + const char *subkeyname; + unsigned int i, j = 1; + size_t len, samelen = 0; + char **matches; + const char *base_n = ""; + TALLOC_CTX *mem_ctx; + WERROR status; + int ret; + + matches = malloc_array_p(char *, MAX_COMPLETIONS); + if (!matches) return NULL; + matches[0] = NULL; + mem_ctx = talloc_init("completion"); + + base = current_key; + + len = strlen(text); + for(i = 0; j < MAX_COMPLETIONS-1; i++) { + status = reg_key_get_subkey_by_index(mem_ctx, base, i, + &subkeyname, NULL, NULL); + if(W_ERROR_IS_OK(status)) { + if(!strncmp(text, subkeyname, len)) { + matches[j] = strdup(subkeyname); + j++; + + if (j == 1) + samelen = strlen(matches[j]); + else + while (strncmp(matches[j], matches[j-1], samelen) != 0) + samelen--; + } + } else if(W_ERROR_EQUAL(status, WERR_NO_MORE_ITEMS)) { + break; + } else { + int n; + + printf("Error creating completion list: %s\n", + win_errstr(status)); + + for (n = j; n >= 0; n--) { + SAFE_FREE(matches[n]); + } + SAFE_FREE(matches); + talloc_free(mem_ctx); + return NULL; + } + } + + if (j == 1) { /* No matches at all */ + SAFE_FREE(matches); + talloc_free(mem_ctx); + return NULL; + } + + if (j == 2) { /* Exact match */ + ret = asprintf(&matches[0], "%s%s", base_n, matches[1]); + } else { + ret = asprintf(&matches[0], "%s%s", base_n, + talloc_strndup(mem_ctx, matches[1], samelen)); + } + talloc_free(mem_ctx); + if (ret == -1) { + SAFE_FREE(matches); + return NULL; + } + + matches[j] = NULL; + return matches; +} + +static char **reg_completion(const char *text, int start, int end) +{ + smb_readline_ca_char(' '); + + if (start == 0) { + return reg_complete_command(text, start, end); + } else { + return reg_complete_key(text, start, end); + } +} + +int main(int argc, char **argv) +{ + const char **argv_const = discard_const_p(const char *, argv); + int opt; + const char *file = NULL; + poptContext pc; + const char *remote = NULL; + TALLOC_CTX *mem_ctx = NULL; + struct loadparm_context *lp_ctx = NULL; + struct cli_credentials *creds = NULL; + struct regshell_context *ctx; + struct tevent_context *ev_ctx; + bool ret = true; + bool ok; + + struct poptOption long_options[] = { + POPT_AUTOHELP + {"file", 'F', POPT_ARG_STRING, &file, 0, "open hive file", NULL }, + {"remote", 'R', POPT_ARG_STRING, &remote, 0, "connect to specified remote server", NULL}, + POPT_COMMON_SAMBA + POPT_COMMON_CREDENTIALS + POPT_LEGACY_S4 + POPT_COMMON_VERSION + POPT_TABLEEND + }; + + mem_ctx = talloc_init("regshell.c/main"); + if (mem_ctx == NULL) { + exit(ENOMEM); + } + + ok = samba_cmdline_init(mem_ctx, + SAMBA_CMDLINE_CONFIG_CLIENT, + false /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(mem_ctx); + exit(1); + } + + pc = samba_popt_get_context(getprogname(), + argc, + argv_const, + long_options, + 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(mem_ctx); + exit(1); + } + + while((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case POPT_ERROR_BADOPT: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + exit(1); + } + } + + poptFreeContext(pc); + samba_cmdline_burn(argc, argv); + + ctx = talloc_zero(mem_ctx, struct regshell_context); + + ev_ctx = s4_event_context_init(ctx); + lp_ctx = samba_cmdline_get_lp_ctx(); + creds = samba_cmdline_get_creds(); + + if (remote != NULL) { + ctx->registry = reg_common_open_remote(remote, ev_ctx, + lp_ctx, + creds); + } else if (file != NULL) { + ctx->current = reg_common_open_file(file, ev_ctx, + lp_ctx, + creds); + if (ctx->current == NULL) { + TALLOC_FREE(mem_ctx); + exit(1); + } + ctx->registry = ctx->current->context; + ctx->path = talloc_strdup(ctx, ""); + ctx->predef = NULL; + ctx->root = ctx->current; + } else { + ctx->registry = reg_common_open_local(creds, + ev_ctx, + lp_ctx); + } + + if (ctx->registry == NULL) { + TALLOC_FREE(mem_ctx); + exit(1); + } + + if (ctx->current == NULL) { + unsigned int i; + + for (i = 0; (reg_predefined_keys[i].handle != 0) && + (ctx->current == NULL); i++) { + WERROR err; + err = reg_get_predefined_key(ctx->registry, + reg_predefined_keys[i].handle, + &ctx->current); + if (W_ERROR_IS_OK(err)) { + ctx->predef = talloc_strdup(ctx, + reg_predefined_keys[i].name); + ctx->path = talloc_strdup(ctx, ""); + ctx->root = ctx->current; + break; + } else { + ctx->current = NULL; + ctx->root = NULL; + } + } + } + + if (ctx->current == NULL) { + fprintf(stderr, "Unable to access any of the predefined keys\n"); + TALLOC_FREE(mem_ctx); + exit(1); + } + + while (true) { + char *line, *prompt; + + if (asprintf(&prompt, "%s\\%s> ", ctx->predef?ctx->predef:"", + ctx->path) < 0) { + ret = false; + break; + } + + current_key = ctx->current; /* No way to pass a void * pointer + via readline :-( */ + line = smb_readline(prompt, NULL, reg_completion); + + if (line == NULL) { + free(prompt); + break; + } + + if (line[0] != '\n') { + ret = W_ERROR_IS_OK(process_cmd(ctx, line)); + } + free(line); + free(prompt); + } + TALLOC_FREE(mem_ctx); + + return (ret?0:1); +} diff --git a/source4/lib/registry/tools/regtree.c b/source4/lib/registry/tools/regtree.c new file mode 100644 index 0000000..1f0dac2 --- /dev/null +++ b/source4/lib/registry/tools/regtree.c @@ -0,0 +1,209 @@ +/* + Unix SMB/CIFS implementation. + simple registry frontend + + Copyright (C) Jelmer Vernooij 2004-2007 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/registry/registry.h" +#include "lib/registry/tools/common.h" +#include "lib/events/events.h" +#include "lib/cmdline/cmdline.h" +#include "param/param.h" + +/** + * Print a registry key recursively + * + * @param level Level at which to print + * @param p Key to print + * @param fullpath Whether the full pat hshould be printed or just the last bit + * @param novals Whether values should not be printed + */ +static void print_tree(unsigned int level, struct registry_key *p, + const char *name, + bool fullpath, bool novals) +{ + struct registry_key *subkey; + const char *valuename, *keyname; + uint32_t valuetype; + DATA_BLOB valuedata; + struct security_descriptor *sec_desc; + WERROR error; + unsigned int i; + TALLOC_CTX *mem_ctx; + + for(i = 0; i < level; i++) putchar(' '); + puts(name); + + mem_ctx = talloc_init("print_tree"); + for (i = 0; W_ERROR_IS_OK(error = reg_key_get_subkey_by_index(mem_ctx, + p, + i, + &keyname, + NULL, + NULL)); i++) { + + SMB_ASSERT(strlen(keyname) > 0); + if (!W_ERROR_IS_OK(reg_open_key(mem_ctx, p, keyname, &subkey))) + continue; + + print_tree(level+1, subkey, (fullpath && strlen(name))? + talloc_asprintf(mem_ctx, "%s\\%s", + name, keyname): + keyname, fullpath, novals); + talloc_free(subkey); + } + talloc_free(mem_ctx); + + if(!W_ERROR_EQUAL(error, WERR_NO_MORE_ITEMS)) { + DEBUG(0, ("Error occurred while fetching subkeys for '%s': %s\n", + name, win_errstr(error))); + } + + if (!novals) { + mem_ctx = talloc_init("print_tree"); + for(i = 0; W_ERROR_IS_OK(error = reg_key_get_value_by_index( + mem_ctx, p, i, &valuename, &valuetype, &valuedata)); + i++) { + unsigned int j; + for(j = 0; j < level+1; j++) putchar(' '); + printf("%s\n", reg_val_description(mem_ctx, + valuename, valuetype, valuedata)); + } + talloc_free(mem_ctx); + + if(!W_ERROR_EQUAL(error, WERR_NO_MORE_ITEMS)) { + DEBUG(0, ("Error occurred while fetching values for '%s': %s\n", + name, win_errstr(error))); + } + } + + mem_ctx = talloc_init("sec_desc"); + if (!W_ERROR_IS_OK(reg_get_sec_desc(mem_ctx, p, &sec_desc))) { + DEBUG(0, ("Error getting security descriptor\n")); + } + talloc_free(mem_ctx); +} + +int main(int argc, char **argv) +{ + const char **argv_const = discard_const_p(const char *, argv); + bool ok; + TALLOC_CTX *mem_ctx = NULL; + int opt; + unsigned int i; + const char *file = NULL; + const char *remote = NULL; + poptContext pc; + struct registry_context *h = NULL; + struct registry_key *start_key = NULL; + struct tevent_context *ev_ctx; + struct loadparm_context *lp_ctx = NULL; + struct cli_credentials *creds = NULL; + WERROR error; + bool fullpath = false, no_values = false; + struct poptOption long_options[] = { + POPT_AUTOHELP + {"file", 'F', POPT_ARG_STRING, &file, 0, "file path", NULL }, + {"remote", 'R', POPT_ARG_STRING, &remote, 0, "connect to specified remote server", NULL }, + {"fullpath", 'f', POPT_ARG_NONE, &fullpath, 0, "show full paths", NULL}, + {"no-values", 'V', POPT_ARG_NONE, &no_values, 0, "don't show values", NULL}, + POPT_COMMON_SAMBA + POPT_COMMON_CREDENTIALS + POPT_COMMON_VERSION + POPT_TABLEEND + }; + + mem_ctx = talloc_init("regtree.c/main"); + if (mem_ctx == NULL) { + exit(ENOMEM); + } + + ok = samba_cmdline_init(mem_ctx, + SAMBA_CMDLINE_CONFIG_CLIENT, + false /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(mem_ctx); + exit(1); + } + + pc = samba_popt_get_context(getprogname(), + argc, + argv_const, + long_options, + 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(mem_ctx); + exit(1); + } + + while((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case POPT_ERROR_BADOPT: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + exit(1); + } + } + + poptFreeContext(pc); + samba_cmdline_burn(argc, argv); + + ev_ctx = s4_event_context_init(NULL); + lp_ctx = samba_cmdline_get_lp_ctx(); + creds = samba_cmdline_get_creds(); + + if (remote != NULL) { + h = reg_common_open_remote(remote, ev_ctx, lp_ctx, creds); + } else if (file != NULL) { + start_key = reg_common_open_file(file, ev_ctx, lp_ctx, creds); + } else { + h = reg_common_open_local(creds, ev_ctx, lp_ctx); + } + + if (h == NULL && start_key == NULL) { + TALLOC_FREE(mem_ctx); + return 1; + } + + error = WERR_OK; + + if (start_key != NULL) { + print_tree(0, start_key, "", fullpath, no_values); + } else { + for(i = 0; reg_predefined_keys[i].handle; i++) { + error = reg_get_predefined_key(h, + reg_predefined_keys[i].handle, + &start_key); + if (!W_ERROR_IS_OK(error)) { + fprintf(stderr, "Skipping %s: %s\n", + reg_predefined_keys[i].name, + win_errstr(error)); + continue; + } + SMB_ASSERT(start_key != NULL); + print_tree(0, start_key, reg_predefined_keys[i].name, + fullpath, no_values); + } + } + + TALLOC_FREE(mem_ctx); + return 0; +} diff --git a/source4/lib/registry/util.c b/source4/lib/registry/util.c new file mode 100644 index 0000000..1197adb --- /dev/null +++ b/source4/lib/registry/util.c @@ -0,0 +1,302 @@ +/* + Unix SMB/CIFS implementation. + Transparent registry backend handling + Copyright (C) Jelmer Vernooij 2003-2007. + Copyright (C) Wilco Baan Hofman 2010. + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/registry/registry.h" +#include "librpc/gen_ndr/winreg.h" +#include "lib/util/data_blob.h" + +_PUBLIC_ char *reg_val_data_string(TALLOC_CTX *mem_ctx, uint32_t type, + const DATA_BLOB data) +{ + size_t converted_size = 0; + char *ret = NULL; + + if (data.length == 0) + return talloc_strdup(mem_ctx, ""); + + switch (type) { + case REG_EXPAND_SZ: + case REG_SZ: + convert_string_talloc(mem_ctx, + CH_UTF16, CH_UNIX, + data.data, data.length, + (void **)&ret, &converted_size); + break; + case REG_DWORD: + case REG_DWORD_BIG_ENDIAN: + SMB_ASSERT(data.length == sizeof(uint32_t)); + ret = talloc_asprintf(mem_ctx, "0x%8.8x", + IVAL(data.data, 0)); + break; + case REG_QWORD: + SMB_ASSERT(data.length == sizeof(uint64_t)); + ret = talloc_asprintf(mem_ctx, "0x%16.16llx", + (long long)BVAL(data.data, 0)); + break; + case REG_BINARY: + ret = data_blob_hex_string_upper(mem_ctx, &data); + break; + case REG_NONE: + /* "NULL" is the right return value */ + break; + case REG_MULTI_SZ: + /* FIXME: We don't support this yet */ + break; + default: + /* FIXME */ + /* Other datatypes aren't supported -> return "NULL" */ + break; + } + + return ret; +} + +/** Generate a string that describes a registry value */ +_PUBLIC_ char *reg_val_description(TALLOC_CTX *mem_ctx, + const char *name, + uint32_t data_type, + const DATA_BLOB data) +{ + return talloc_asprintf(mem_ctx, "%s = %s : %s", name?name:"<No Name>", + str_regtype(data_type), + reg_val_data_string(mem_ctx, data_type, data)); +} + +/* + * This implements reading hex bytes that include comma's. + * It was previously handled by strhex_to_data_blob, but that did not cover + * the format used by windows. + */ +static DATA_BLOB reg_strhex_to_data_blob(TALLOC_CTX *mem_ctx, const char *str) +{ + DATA_BLOB ret; + const char *HEXCHARS = "0123456789ABCDEF"; + size_t i, j; + char *hi, *lo; + + ret = data_blob_talloc_zero(mem_ctx, (strlen(str)+(strlen(str) % 3))/3); + j = 0; + for (i = 0; i < strlen(str); i++) { + hi = strchr(HEXCHARS, toupper(str[i])); + if (hi == NULL) + continue; + + i++; + lo = strchr(HEXCHARS, toupper(str[i])); + if (lo == NULL) + break; + + ret.data[j] = PTR_DIFF(hi, HEXCHARS) << 4; + ret.data[j] += PTR_DIFF(lo, HEXCHARS); + j++; + + if (j > ret.length) { + DEBUG(0, ("Trouble converting hex string to bin\n")); + break; + } + } + return ret; +} + + +_PUBLIC_ bool reg_string_to_val(TALLOC_CTX *mem_ctx, const char *type_str, + const char *data_str, uint32_t *type, DATA_BLOB *data) +{ + char *tmp_type_str, *p, *q; + int result; + + *type = regtype_by_string(type_str); + + if (*type == -1) { + /* Normal windows format is hex, hex(type int as string), + dword or just a string. */ + if (strncmp(type_str, "hex(", 4) == 0) { + /* there is a hex string with the value type between + the braces */ + tmp_type_str = talloc_strdup(mem_ctx, type_str); + q = p = tmp_type_str + strlen("hex("); + + /* Go to the closing brace or end of the string */ + while (*q != ')' && *q != '\0') q++; + *q = '\0'; + + /* Convert hex string to int, store it in type */ + result = sscanf(p, "%x", type); + if (!result) { + DEBUG(0, ("Could not convert hex to int\n")); + return false; + } + talloc_free(tmp_type_str); + } else if (strcmp(type_str, "hex") == 0) { + *type = REG_BINARY; + } else if (strcmp(type_str, "dword") == 0) { + *type = REG_DWORD; + } + } + + if (*type == -1) + return false; + + /* Convert data appropriately */ + + switch (*type) { + case REG_SZ: + return convert_string_talloc(mem_ctx, + CH_UNIX, CH_UTF16, + data_str, strlen(data_str)+1, + (void **)&data->data, + &data->length); + break; + case REG_MULTI_SZ: + case REG_EXPAND_SZ: + case REG_BINARY: + *data = reg_strhex_to_data_blob(mem_ctx, data_str); + break; + case REG_DWORD: + case REG_DWORD_BIG_ENDIAN: { + uint32_t tmp = strtol(data_str, NULL, 16); + *data = data_blob_talloc(mem_ctx, NULL, sizeof(uint32_t)); + if (data->data == NULL) return false; + SIVAL(data->data, 0, tmp); + } + break; + case REG_QWORD: { + uint64_t tmp = strtoll(data_str, NULL, 16); + *data = data_blob_talloc(mem_ctx, NULL, sizeof(uint64_t)); + if (data->data == NULL) return false; + SBVAL(data->data, 0, tmp); + } + break; + case REG_NONE: + ZERO_STRUCTP(data); + break; + default: + /* FIXME */ + /* Other datatypes aren't supported -> return no success */ + return false; + } + return true; +} + +/** Open a key by name (including the predefined key name!) */ +WERROR reg_open_key_abs(TALLOC_CTX *mem_ctx, struct registry_context *handle, + const char *name, struct registry_key **result) +{ + struct registry_key *predef; + WERROR error; + size_t predeflength; + char *predefname; + + if (strchr(name, '\\') != NULL) + predeflength = strchr(name, '\\')-name; + else + predeflength = strlen(name); + + predefname = talloc_strndup(mem_ctx, name, predeflength); + W_ERROR_HAVE_NO_MEMORY(predefname); + error = reg_get_predefined_key_by_name(handle, predefname, &predef); + talloc_free(predefname); + + if (!W_ERROR_IS_OK(error)) { + return error; + } + + if (strchr(name, '\\')) { + return reg_open_key(mem_ctx, predef, strchr(name, '\\')+1, + result); + } else { + *result = predef; + return WERR_OK; + } +} + +static WERROR get_abs_parent(TALLOC_CTX *mem_ctx, struct registry_context *ctx, + const char *path, struct registry_key **parent, + const char **name) +{ + char *parent_name; + WERROR error; + + if (strchr(path, '\\') == NULL) { + return WERR_FOOBAR; + } + + parent_name = talloc_strndup(mem_ctx, path, strrchr(path, '\\')-path); + W_ERROR_HAVE_NO_MEMORY(parent_name); + error = reg_open_key_abs(mem_ctx, ctx, parent_name, parent); + talloc_free(parent_name); + if (!W_ERROR_IS_OK(error)) { + return error; + } + + *name = talloc_strdup(mem_ctx, strrchr(path, '\\')+1); + W_ERROR_HAVE_NO_MEMORY(*name); + + return WERR_OK; +} + +WERROR reg_key_del_abs(struct registry_context *ctx, const char *path) +{ + struct registry_key *parent; + const char *n; + TALLOC_CTX *mem_ctx = talloc_init("reg_key_del_abs"); + WERROR error; + + if (!strchr(path, '\\')) { + return WERR_FOOBAR; + } + + error = get_abs_parent(mem_ctx, ctx, path, &parent, &n); + if (W_ERROR_IS_OK(error)) { + error = reg_key_del(mem_ctx, parent, n); + } + + talloc_free(mem_ctx); + + return error; +} + +WERROR reg_key_add_abs(TALLOC_CTX *mem_ctx, struct registry_context *ctx, + const char *path, uint32_t access_mask, + struct security_descriptor *sec_desc, + struct registry_key **result) +{ + struct registry_key *parent; + const char *n; + WERROR error; + + *result = NULL; + + if (!strchr(path, '\\')) { + return WERR_ALREADY_EXISTS; + } + + error = get_abs_parent(mem_ctx, ctx, path, &parent, &n); + if (!W_ERROR_IS_OK(error)) { + DEBUG(2, ("Opening parent of %s failed with %s\n", path, + win_errstr(error))); + return error; + } + + error = reg_key_add_name(mem_ctx, parent, n, NULL, sec_desc, result); + + return error; +} diff --git a/source4/lib/registry/wine.c b/source4/lib/registry/wine.c new file mode 100644 index 0000000..77d2ce6 --- /dev/null +++ b/source4/lib/registry/wine.c @@ -0,0 +1,45 @@ +/* + Unix SMB/CIFS implementation. + Registry interface + Copyright (C) Jelmer Vernooij 2007. + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/registry/common/registry.h" +#include "windows/registry.h" + +static WERROR wine_open_reg (struct registry_hive *h, struct registry_key **key) +{ + /* FIXME: Open h->location and mmap it */ +} + +static REG_OPS reg_backend_wine = { + .name = "wine", + .open_hive = wine_open_reg, + +}; + +NTSTATUS registry_wine_init(void) +{ + register_backend("registry", ®_backend_wine); + return NT_STATUS_OK; +} + +WERROR reg_open_wine(struct registry_key **ctx) +{ + /* FIXME: Open ~/.wine/system.reg, etc */ + return WERR_NOT_SUPPORTED; +} diff --git a/source4/lib/registry/wscript_build b/source4/lib/registry/wscript_build new file mode 100644 index 0000000..2e01e43 --- /dev/null +++ b/source4/lib/registry/wscript_build @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +bld.SAMBA_PIDL('PIDL_REG', + source='regf.idl', + options='--header --tdr-parser') + +bld.SAMBA_SUBSYSTEM('TDR_REGF', + source='tdr_regf.c', + public_deps='TDR' + ) + + +bld.SAMBA_LIBRARY('registry', + source='interface.c util.c samba.c patchfile_dotreg.c patchfile_preg.c patchfile.c regf.c hive.c local.c ldb.c rpc.c', + public_deps='dcerpc samba-util TDR_REGF ldb RPC_NDR_WINREG ldbsamba util_reg', + private_headers='registry.h', + private_library=True + ) + + +bld.SAMBA_SUBSYSTEM('registry_common', + source='tools/common.c', + autoproto='tools/common.h', + public_deps='registry' + ) + + +bld.SAMBA_BINARY('regdiff', + source='tools/regdiff.c', + manpages='man/regdiff.1', + deps='samba-hostconfig registry popt CMDLINE_S4' + ) + + +bld.SAMBA_BINARY('regpatch', + source='tools/regpatch.c', + manpages='man/regpatch.1', + deps='samba-hostconfig registry popt CMDLINE_S4 registry_common' + ) + + +bld.SAMBA_BINARY('regshell', + source='tools/regshell.c', + manpages='man/regshell.1', + deps='samba-hostconfig popt registry CMDLINE_S4 SMBREADLINE registry_common' + ) + + +bld.SAMBA_BINARY('regtree', + source='tools/regtree.c', + manpages='man/regtree.1', + deps='samba-hostconfig popt registry CMDLINE_S4 registry_common' + ) + + +bld.SAMBA_SUBSYSTEM('torture_registry', + source='tests/generic.c tests/hive.c tests/diff.c tests/registry.c', + autoproto='tests/proto.h', + deps='torture registry' + ) + +pytalloc_util = bld.pyembed_libname('pytalloc-util') +pyparam_util = bld.pyembed_libname('pyparam_util') + +bld.SAMBA_PYTHON('py_registry', + source='pyregistry.c', + public_deps='registry %s %s' % (pytalloc_util, pyparam_util), + realname='samba/registry.so' + ) diff --git a/source4/lib/samba3/README b/source4/lib/samba3/README new file mode 100644 index 0000000..3f6553f --- /dev/null +++ b/source4/lib/samba3/README @@ -0,0 +1,5 @@ +This directory contains various files and functions for the purpose of +Samba3 import, migration and compatibility. + +For example, the first file in this directory (smbpasswd.c) handles +portions of the smbpasswd file format. diff --git a/source4/lib/samba3/samba3.h b/source4/lib/samba3/samba3.h new file mode 100644 index 0000000..f1c5d44 --- /dev/null +++ b/source4/lib/samba3/samba3.h @@ -0,0 +1,29 @@ +/* + Unix SMB/CIFS implementation. + Samba3 interfaces + Copyright (C) Jelmer Vernooij 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SAMBA3_H /* _SAMBA3_H */ +#define _SAMBA3_H + +#include "librpc/gen_ndr/security.h" +#include "librpc/gen_ndr/samr.h" + +struct samr_Password *smbpasswd_gethexpwd(TALLOC_CTX *mem_ctx, const char *p); +char *smbpasswd_sethexpwd(TALLOC_CTX *mem_ctx, struct samr_Password *pwd, uint16_t acb_info); + +#endif /* _SAMBA3_H */ diff --git a/source4/lib/samba3/smbpasswd.c b/source4/lib/samba3/smbpasswd.c new file mode 100644 index 0000000..ae361b7 --- /dev/null +++ b/source4/lib/samba3/smbpasswd.c @@ -0,0 +1,111 @@ +/* + Unix SMB/CIFS implementation. + smbpasswd file format routines + + Copyright (C) Andrew Tridgell 1992-1998 + Modified by Jeremy Allison 1995. + Modified by Gerald (Jerry) Carter 2000-2001 + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +/*! \file lib/smbpasswd.c + + The smbpasswd file is used to store encrypted passwords in a similar + fashion to the /etc/passwd file. The format is colon separated fields + with one user per line like so: + + <username>:<uid>:<lanman hash>:<nt hash>:<acb info>:<last change time> + + The username and uid must correspond to an entry in the /etc/passwd + file. The lanman and nt password hashes are 32 hex digits corresponding + to the 16-byte lanman and nt hashes respectively. + + The password last change time is stored as a string of the format + LCD-<change time> where the change time is expressed as an + + 'N' No password + 'D' Disabled + 'H' Homedir required + 'T' Temp account. + 'U' User account (normal) + 'M' MNS logon user account - what is this ? + 'W' Workstation account + 'S' Server account + 'L' Locked account + 'X' No Xpiry on password + 'I' Interdomain trust account + +*/ + +#include "includes.h" +#include "system/locale.h" +#include "lib/samba3/samba3.h" + +/*! Convert 32 hex characters into a 16 byte array. */ + +struct samr_Password *smbpasswd_gethexpwd(TALLOC_CTX *mem_ctx, const char *p) +{ + int i; + unsigned char lonybble, hinybble; + const char *hexchars = "0123456789ABCDEF"; + const char *p1, *p2; + struct samr_Password *pwd = talloc(mem_ctx, struct samr_Password); + + if (!p) return NULL; + + for (i = 0; i < (sizeof(pwd->hash) * 2); i += 2) + { + hinybble = toupper(p[i]); + lonybble = toupper(p[i + 1]); + + p1 = strchr_m(hexchars, hinybble); + p2 = strchr_m(hexchars, lonybble); + + if (!p1 || !p2) { + return NULL; + } + + hinybble = PTR_DIFF(p1, hexchars); + lonybble = PTR_DIFF(p2, hexchars); + + pwd->hash[i / 2] = (hinybble << 4) | lonybble; + } + return pwd; +} + +/*! Convert a 16-byte array into 32 hex characters. */ +char *smbpasswd_sethexpwd(TALLOC_CTX *mem_ctx, struct samr_Password *pwd, uint16_t acb_info) +{ + char *p; + if (pwd != NULL) { + int i; + p = talloc_array(mem_ctx, char, 33); + if (!p) { + return NULL; + } + + for (i = 0; i < sizeof(pwd->hash); i++) + slprintf(&p[i*2], 3, "%02X", pwd->hash[i]); + } else { + if (acb_info & ACB_PWNOTREQ) + p = talloc_strdup(mem_ctx, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX"); + else + p = talloc_strdup(mem_ctx, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); + } + return p; +} diff --git a/source4/lib/samba3/wscript_build b/source4/lib/samba3/wscript_build new file mode 100644 index 0000000..98248c9 --- /dev/null +++ b/source4/lib/samba3/wscript_build @@ -0,0 +1,9 @@ +#!/usr/bin/env python + + +bld.SAMBA_LIBRARY('smbpasswdparser', + source='smbpasswd.c', + deps='samba-util', + private_library=True + ) + diff --git a/source4/lib/socket/access.c b/source4/lib/socket/access.c new file mode 100644 index 0000000..c019fd6 --- /dev/null +++ b/source4/lib/socket/access.c @@ -0,0 +1,129 @@ +/* + Unix SMB/CIFS implementation. + + check access rules for socket connections + + Copyright (C) Andrew Tridgell 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + + +/* + This module is an adaption of code from the tcpd-1.4 package written + by Wietse Venema, Eindhoven University of Technology, The Netherlands. + + The code is used here with permission. + + The code has been considerably changed from the original. Bug reports + should be sent to samba-technical@lists.samba.org +*/ + +#include "includes.h" +#include "system/network.h" +#include "lib/socket/socket.h" +#include "lib/util/util_net.h" +#include "lib/util/access.h" + +/* return true if the char* contains ip addrs only. Used to avoid +gethostbyaddr() calls */ + +static bool only_ipaddrs_in_list(const char** list) +{ + bool only_ip = true; + + if (!list) + return true; + + for (; *list ; list++) { + /* factor out the special strings */ + if (strcmp(*list, "ALL")==0 || + strcmp(*list, "FAIL")==0 || + strcmp(*list, "EXCEPT")==0) { + continue; + } + + if (!is_ipaddress(*list)) { + /* + * if we failed, make sure that it was not because the token + * was a network/netmask pair. Only network/netmask pairs + * have a '/' in them + */ + if ((strchr(*list, '/')) == NULL) { + only_ip = false; + DEBUG(3,("only_ipaddrs_in_list: list has non-ip address (%s)\n", *list)); + break; + } + } + } + + return only_ip; +} + +/* return true if access should be allowed to a service for a socket */ +bool socket_check_access(struct socket_context *sock, + const char *service_name, + const char **allow_list, const char **deny_list) +{ + bool ret; + const char *name=""; + struct socket_address *addr; + TALLOC_CTX *mem_ctx; + + if ((!deny_list || *deny_list==0) && + (!allow_list || *allow_list==0)) { + return true; + } + + mem_ctx = talloc_init("socket_check_access"); + if (!mem_ctx) { + return false; + } + + addr = socket_get_peer_addr(sock, mem_ctx); + if (!addr) { + DEBUG(0,("socket_check_access: Denied connection from unknown host: could not get peer address from kernel\n")); + talloc_free(mem_ctx); + return false; + } + + /* bypass gethostbyaddr() calls if the lists only contain IP addrs */ + if (!only_ipaddrs_in_list(allow_list) || + !only_ipaddrs_in_list(deny_list)) { + name = socket_get_peer_name(sock, mem_ctx); + if (!name) { + name = addr->addr; + } + } + + if (!addr) { + DEBUG(0,("socket_check_access: Denied connection from unknown host\n")); + talloc_free(mem_ctx); + return false; + } + + ret = allow_access(deny_list, allow_list, name, addr->addr); + + if (ret) { + DEBUG(2,("socket_check_access: Allowed connection to '%s' from %s (%s)\n", + service_name, name, addr->addr)); + } else { + DEBUG(0,("socket_check_access: Denied connection to '%s' from %s (%s)\n", + service_name, name, addr->addr)); + } + + talloc_free(mem_ctx); + + return ret; +} diff --git a/source4/lib/socket/connect.c b/source4/lib/socket/connect.c new file mode 100644 index 0000000..1da8b41 --- /dev/null +++ b/source4/lib/socket/connect.c @@ -0,0 +1,158 @@ +/* + Unix SMB/CIFS implementation. + + 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 + the Free Software Foundation; either version 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "lib/events/events.h" +#include "libcli/composite/composite.h" + + +struct connect_state { + struct socket_context *sock; + const struct socket_address *my_address; + const struct socket_address *server_address; + uint32_t flags; +}; + +static void socket_connect_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *private_data); + +/* + call the real socket_connect() call, and setup event handler +*/ +static void socket_send_connect(struct composite_context *result) +{ + struct tevent_fd *fde; + struct connect_state *state = talloc_get_type(result->private_data, + struct connect_state); + + result->status = socket_connect(state->sock, + state->my_address, + state->server_address, + state->flags); + if (NT_STATUS_IS_ERR(result->status) && + !NT_STATUS_EQUAL(result->status, + NT_STATUS_MORE_PROCESSING_REQUIRED)) { + composite_error(result, result->status); + return; + } + + fde = tevent_add_fd(result->event_ctx, result, + socket_get_fd(state->sock), + TEVENT_FD_READ|TEVENT_FD_WRITE, + socket_connect_handler, result); + composite_nomem(fde, result); +} + + +/* + send a socket connect, potentially doing some name resolution first +*/ +struct composite_context *socket_connect_send(struct socket_context *sock, + struct socket_address *my_address, + struct socket_address *server_address, + uint32_t flags, + struct tevent_context *event_ctx) +{ + struct composite_context *result; + struct connect_state *state; + + result = composite_create(sock, event_ctx); + if (result == NULL) return NULL; + + state = talloc_zero(result, struct connect_state); + if (composite_nomem(state, result)) return result; + result->private_data = state; + + state->sock = talloc_reference(state, sock); + if (composite_nomem(state->sock, result)) return result; + + if (my_address) { + void *ref = talloc_reference(state, my_address); + if (composite_nomem(ref, result)) { + return result; + } + state->my_address = my_address; + } + + { + void *ref = talloc_reference(state, server_address); + if (composite_nomem(ref, result)) { + return result; + } + state->server_address = server_address; + } + + state->flags = flags; + + set_blocking(socket_get_fd(sock), false); + + socket_send_connect(result); + + return result; +} + +/* + handle write events on connect completion +*/ +static void socket_connect_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct composite_context *result = + talloc_get_type(private_data, struct composite_context); + struct connect_state *state = talloc_get_type(result->private_data, + struct connect_state); + + result->status = socket_connect_complete(state->sock, state->flags); + if (!composite_is_ok(result)) return; + + composite_done(result); +} + +/* + wait for a socket_connect_send() to finish +*/ +NTSTATUS socket_connect_recv(struct composite_context *result) +{ + NTSTATUS status = composite_wait(result); + talloc_free(result); + return status; +} + + +/* + like socket_connect() but takes an event context, doing a semi-async connect +*/ +NTSTATUS socket_connect_ev(struct socket_context *sock, + struct socket_address *my_address, + struct socket_address *server_address, + uint32_t flags, + struct tevent_context *ev) +{ + struct composite_context *ctx; + ctx = socket_connect_send(sock, my_address, + server_address, flags, ev); + return socket_connect_recv(ctx); +} diff --git a/source4/lib/socket/connect_multi.c b/source4/lib/socket/connect_multi.c new file mode 100644 index 0000000..b29fffb --- /dev/null +++ b/source4/lib/socket/connect_multi.c @@ -0,0 +1,392 @@ +/* + Unix SMB/CIFS implementation. + + Fire connect requests to a host and a number of ports, with a timeout + between the connect request. Return if the first connect comes back + successfully or return the last error. + + 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 + the Free Software Foundation; either version 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "lib/events/events.h" +#include "libcli/composite/composite.h" +#include "libcli/resolve/resolve.h" + +#define MULTI_PORT_DELAY 2000 /* microseconds */ + +/* + overall state +*/ +struct connect_multi_state { + struct socket_address **server_address; + unsigned num_address, current_address, current_port; + int num_ports; + uint16_t *ports; + + struct socket_context *sock; + uint16_t result_port; + + int num_connects_sent, num_connects_recv; + + struct socket_connect_multi_ex *ex; +}; + +/* + state of an individual socket_connect_send() call +*/ +struct connect_one_state { + struct composite_context *result; + struct socket_context *sock; + struct socket_address *addr; +}; + +static void continue_resolve_name(struct composite_context *creq); +static void connect_multi_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *p); +static void connect_multi_next_socket(struct composite_context *result); +static void continue_one(struct composite_context *creq); +static void continue_one_ex(struct tevent_req *subreq); + +/* + setup an async socket_connect, with multiple ports +*/ +_PUBLIC_ struct composite_context *socket_connect_multi_ex_send( + TALLOC_CTX *mem_ctx, + const char *server_name, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex) +{ + struct composite_context *result; + struct connect_multi_state *multi; + int i; + + struct nbt_name name; + struct composite_context *creq; + + result = talloc_zero(mem_ctx, struct composite_context); + if (result == NULL) return NULL; + result->state = COMPOSITE_STATE_IN_PROGRESS; + result->event_ctx = event_ctx; + + multi = talloc_zero(result, struct connect_multi_state); + if (composite_nomem(multi, result)) goto failed; + result->private_data = multi; + + multi->num_ports = num_server_ports; + multi->ports = talloc_array(multi, uint16_t, multi->num_ports); + if (composite_nomem(multi->ports, result)) goto failed; + + for (i=0; i<multi->num_ports; i++) { + multi->ports[i] = server_ports[i]; + } + + multi->ex = ex; + + /* + we don't want to do the name resolution separately + for each port, so start it now, then only start on + the real sockets once we have an IP + */ + make_nbt_name_server(&name, server_name); + + creq = resolve_name_all_send(resolve_ctx, multi, 0, multi->ports[0], &name, result->event_ctx); + if (composite_nomem(creq, result)) goto failed; + + composite_continue(result, creq, continue_resolve_name, result); + + return result; + + + failed: + composite_error(result, result->status); + return result; +} + +/* + start connecting to the next socket/port in the list +*/ +static void connect_multi_next_socket(struct composite_context *result) +{ + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + struct connect_one_state *state; + struct composite_context *creq; + int next = multi->num_connects_sent; + + if (next == multi->num_address * multi->num_ports) { + /* don't do anything, just wait for the existing ones to finish */ + return; + } + + if (multi->current_address == multi->num_address) { + multi->current_address = 0; + multi->current_port += 1; + } + multi->num_connects_sent += 1; + + if (multi->server_address == NULL || multi->server_address[multi->current_address] == NULL) { + composite_error(result, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + state = talloc(multi, struct connect_one_state); + if (composite_nomem(state, result)) return; + + state->result = result; + result->status = socket_create( + state, multi->server_address[multi->current_address]->family, + SOCKET_TYPE_STREAM, &state->sock, 0); + if (!composite_is_ok(result)) return; + + state->addr = socket_address_copy(state, multi->server_address[multi->current_address]); + if (composite_nomem(state->addr, result)) return; + + socket_address_set_port(state->addr, multi->ports[multi->current_port]); + + creq = socket_connect_send(state->sock, NULL, + state->addr, 0, + result->event_ctx); + if (composite_nomem(creq, result)) return; + talloc_steal(state, creq); + + multi->current_address++; + composite_continue(result, creq, continue_one, state); + + /* if there are more ports / addresses to go then setup a timer to fire when we have waited + for a couple of milli-seconds, when that goes off we try the next port regardless + of whether this port has completed */ + if (multi->num_ports * multi->num_address > multi->num_connects_sent) { + /* note that this timer is a child of the single + connect attempt state, so it will go away when this + request completes */ + tevent_add_timer(result->event_ctx, state, + timeval_current_ofs_usec(MULTI_PORT_DELAY), + connect_multi_timer, result); + } +} + +/* + a timer has gone off telling us that we should try the next port +*/ +static void connect_multi_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *p) +{ + struct composite_context *result = talloc_get_type(p, struct composite_context); + connect_multi_next_socket(result); +} + + +/* + recv name resolution reply then send the next connect +*/ +static void continue_resolve_name(struct composite_context *creq) +{ + struct composite_context *result = talloc_get_type(creq->async.private_data, + struct composite_context); + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + struct socket_address **addr; + unsigned i; + + result->status = resolve_name_all_recv(creq, multi, &addr, NULL); + if (!composite_is_ok(result)) return; + + for(i=0; addr[i]; i++); + multi->num_address = i; + multi->server_address = talloc_steal(multi, addr); + + connect_multi_next_socket(result); +} + +/* + one of our socket_connect_send() calls hash finished. If it got a + connection or there are none left then we are done +*/ +static void continue_one(struct composite_context *creq) +{ + struct connect_one_state *state = talloc_get_type(creq->async.private_data, + struct connect_one_state); + struct composite_context *result = state->result; + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + NTSTATUS status; + + status = socket_connect_recv(creq); + + if (multi->ex) { + struct tevent_req *subreq; + + subreq = multi->ex->establish_send(state, + result->event_ctx, + state->sock, + state->addr, + multi->ex->private_data); + if (composite_nomem(subreq, result)) return; + tevent_req_set_callback(subreq, continue_one_ex, state); + return; + } + + multi->num_connects_recv++; + + if (NT_STATUS_IS_OK(status)) { + multi->sock = talloc_steal(multi, state->sock); + multi->result_port = state->addr->port; + } + + talloc_free(state); + + if (NT_STATUS_IS_OK(status) || + multi->num_connects_recv == (multi->num_address * multi->num_ports)) { + result->status = status; + composite_done(result); + return; + } + + /* try the next port */ + connect_multi_next_socket(result); +} + +/* + one of our multi->ex->establish_send() calls hash finished. If it got a + connection or there are none left then we are done +*/ +static void continue_one_ex(struct tevent_req *subreq) +{ + struct connect_one_state *state = + tevent_req_callback_data(subreq, + struct connect_one_state); + struct composite_context *result = state->result; + struct connect_multi_state *multi = + talloc_get_type_abort(result->private_data, + struct connect_multi_state); + NTSTATUS status; + multi->num_connects_recv++; + + status = multi->ex->establish_recv(subreq); + TALLOC_FREE(subreq); + + if (NT_STATUS_IS_OK(status)) { + multi->sock = talloc_steal(multi, state->sock); + multi->result_port = state->addr->port; + } + + talloc_free(state); + + if (NT_STATUS_IS_OK(status) || + multi->num_connects_recv == (multi->num_address * multi->num_ports)) { + result->status = status; + composite_done(result); + return; + } + + /* try the next port */ + connect_multi_next_socket(result); +} + +/* + async recv routine for socket_connect_multi() + */ +_PUBLIC_ NTSTATUS socket_connect_multi_ex_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **sock, + uint16_t *port) +{ + NTSTATUS status = composite_wait(ctx); + if (NT_STATUS_IS_OK(status)) { + struct connect_multi_state *multi = + talloc_get_type(ctx->private_data, + struct connect_multi_state); + *sock = talloc_steal(mem_ctx, multi->sock); + *port = multi->result_port; + } + talloc_free(ctx); + return status; +} + +NTSTATUS socket_connect_multi_ex(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex, + struct socket_context **result, + uint16_t *result_port) +{ + struct composite_context *ctx = + socket_connect_multi_ex_send(mem_ctx, server_address, + num_server_ports, server_ports, + resolve_ctx, + event_ctx, + ex); + return socket_connect_multi_ex_recv(ctx, mem_ctx, result, result_port); +} + +/* + setup an async socket_connect, with multiple ports +*/ +_PUBLIC_ struct composite_context *socket_connect_multi_send( + TALLOC_CTX *mem_ctx, + const char *server_name, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx) +{ + return socket_connect_multi_ex_send(mem_ctx, + server_name, + num_server_ports, + server_ports, + resolve_ctx, + event_ctx, + NULL); /* ex */ +} + +/* + async recv routine for socket_connect_multi() + */ +_PUBLIC_ NTSTATUS socket_connect_multi_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **sock, + uint16_t *port) +{ + return socket_connect_multi_ex_recv(ctx, mem_ctx, sock, port); +} + +NTSTATUS socket_connect_multi(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_context **result, + uint16_t *result_port) +{ + return socket_connect_multi_ex(mem_ctx, + server_address, + num_server_ports, + server_ports, + resolve_ctx, + event_ctx, + NULL, /* ex */ + result, + result_port); +} diff --git a/source4/lib/socket/interface.c b/source4/lib/socket/interface.c new file mode 100644 index 0000000..65543c6 --- /dev/null +++ b/source4/lib/socket/interface.c @@ -0,0 +1,525 @@ +/* + Unix SMB/CIFS implementation. + + multiple interface handling + + Copyright (C) Andrew Tridgell 1992-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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "param/param.h" +#include "lib/socket/netif.h" +#include "../lib/util/util_net.h" +#include "../lib/util/dlinklist.h" +#include "lib/util/smb_strtox.h" + +/* used for network interfaces */ +struct interface { + struct interface *next, *prev; + char *name; + int flags; + struct sockaddr_storage ip; + struct sockaddr_storage netmask; + struct sockaddr_storage bcast; + const char *ip_s; + const char *bcast_s; + const char *nmask_s; +}; + +#define ALLONES ((uint32_t)0xFFFFFFFF) +/* + address construction based on a patch from fred@datalync.com +*/ +#define MKBCADDR(_IP, _NM) ((_IP & _NM) | (_NM ^ ALLONES)) +#define MKNETADDR(_IP, _NM) (_IP & _NM) + +/**************************************************************************** +Try and find an interface that matches an ip. If we cannot, return NULL + **************************************************************************/ +static struct interface *iface_list_find(struct interface *interfaces, + const struct sockaddr *ip, + bool check_mask) +{ + struct interface *i; + + if (is_address_any(ip)) { + return interfaces; + } + + for (i=interfaces;i;i=i->next) { + if (check_mask) { + if (same_net(ip, (struct sockaddr *)&i->ip, (struct sockaddr *)&i->netmask)) { + return i; + } + } else if (sockaddr_equal((struct sockaddr *)&i->ip, ip)) { + return i; + } + } + + return NULL; +} + +/**************************************************************************** +add an interface to the linked list of interfaces +****************************************************************************/ +static void add_interface(TALLOC_CTX *mem_ctx, const struct iface_struct *ifs, struct interface **interfaces) +{ + char addr[INET6_ADDRSTRLEN]; + struct interface *iface; + + if (iface_list_find(*interfaces, (const struct sockaddr *)&ifs->ip, false)) { + DEBUG(3,("add_interface: not adding duplicate interface %s\n", + print_sockaddr(addr, sizeof(addr), &ifs->ip) )); + return; + } + + if (ifs->ip.ss_family == AF_INET && + !(ifs->flags & (IFF_BROADCAST|IFF_LOOPBACK))) { + DEBUG(3,("not adding non-broadcast interface %s\n", + ifs->name )); + return; + } + + if (*interfaces != NULL) { + mem_ctx = *interfaces; + } + + iface = talloc_zero(mem_ctx, struct interface); + if (iface == NULL) { + return; + } + + iface->name = talloc_strdup(iface, ifs->name); + if (!iface->name) { + SAFE_FREE(iface); + return; + } + iface->flags = ifs->flags; + iface->ip = ifs->ip; + iface->netmask = ifs->netmask; + iface->bcast = ifs->bcast; + + /* keep string versions too, to avoid people tripping over the implied + static in inet_ntoa() */ + print_sockaddr(addr, sizeof(addr), &iface->ip); + DEBUG(4,("added interface %s ip=%s ", + iface->name, addr)); + iface->ip_s = talloc_strdup(iface, addr); + + print_sockaddr(addr, sizeof(addr), + &iface->bcast); + DEBUG(4,("bcast=%s ", addr)); + iface->bcast_s = talloc_strdup(iface, addr); + + print_sockaddr(addr, sizeof(addr), + &iface->netmask); + DEBUG(4,("netmask=%s\n", addr)); + iface->nmask_s = talloc_strdup(iface, addr); + + /* + this needs to be a ADD_END, as some tests (such as the + spoolss notify test) depend on the interfaces ordering + */ + DLIST_ADD_END(*interfaces, iface); +} + +/** +interpret a single element from a interfaces= config line + +This handles the following different forms: + +1) wildcard interface name +2) DNS name +3) IP/masklen +4) ip/mask +5) bcast/mask +**/ +static void interpret_interface(TALLOC_CTX *mem_ctx, + const char *token, + struct iface_struct *probed_ifaces, + int total_probed, + struct interface **local_interfaces) +{ + struct sockaddr_storage ss; + struct sockaddr_storage ss_mask; + struct sockaddr_storage ss_net; + struct sockaddr_storage ss_bcast; + struct iface_struct ifs; + char *p; + int i; + bool added=false; + bool goodaddr = false; + + /* first check if it is an interface name */ + for (i=0;i<total_probed;i++) { + if (gen_fnmatch(token, probed_ifaces[i].name) == 0) { + add_interface(mem_ctx, &probed_ifaces[i], + local_interfaces); + added = true; + } + } + if (added) { + return; + } + + p = strchr_m(token, ';'); + if (p != NULL) { + /* + * skip smbd-specific extra data: + * link speed, capabilities, and interface index + */ + *p = 0; + } + + /* maybe it is a DNS name */ + p = strchr_m(token,'/'); + if (p == NULL) { + if (!interpret_string_addr(&ss, token, 0)) { + DEBUG(2, ("interpret_interface: Can't find address " + "for %s\n", token)); + return; + } + + for (i=0;i<total_probed;i++) { + if (sockaddr_equal((struct sockaddr *)&ss, (struct sockaddr *)&probed_ifaces[i].ip)) { + add_interface(mem_ctx, &probed_ifaces[i], + local_interfaces); + return; + } + } + DEBUG(2,("interpret_interface: " + "can't determine interface for %s\n", + token)); + return; + } + + /* parse it into an IP address/netmasklength pair */ + *p = 0; + goodaddr = interpret_string_addr(&ss, token, 0); + *p++ = '/'; + + if (!goodaddr) { + DEBUG(2,("interpret_interface: " + "can't determine interface for %s\n", + token)); + return; + } + + if (strlen(p) > 2) { + goodaddr = interpret_string_addr(&ss_mask, p, 0); + if (!goodaddr) { + DEBUG(2,("interpret_interface: " + "can't determine netmask from %s\n", + p)); + return; + } + } else { + int error = 0; + + unsigned long val = smb_strtoul(p, + NULL, + 0, + &error, + SMB_STR_FULL_STR_CONV); + if (error != 0) { + DEBUG(2,("interpret_interface: " + "can't determine netmask value from %s\n", + p)); + return; + } + if (!make_netmask(&ss_mask, &ss, val)) { + DEBUG(2,("interpret_interface: " + "can't apply netmask value %lu from %s\n", + val, + p)); + return; + } + } + + make_bcast(&ss_bcast, &ss, &ss_mask); + make_net(&ss_net, &ss, &ss_mask); + + /* Maybe the first component was a broadcast address. */ + if (sockaddr_equal((struct sockaddr *)&ss_bcast, (struct sockaddr *)&ss) || + sockaddr_equal((struct sockaddr *)&ss_net, (struct sockaddr *)&ss)) { + for (i=0;i<total_probed;i++) { + if (same_net((struct sockaddr *)&ss, + (struct sockaddr *)&probed_ifaces[i].ip, + (struct sockaddr *)&ss_mask)) { + /* Temporarily replace netmask on + * the detected interface - user knows + * best.... */ + struct sockaddr_storage saved_mask = + probed_ifaces[i].netmask; + probed_ifaces[i].netmask = ss_mask; + DEBUG(2,("interpret_interface: " + "using netmask value %s from " + "config file on interface %s\n", + p, + probed_ifaces[i].name)); + add_interface(mem_ctx, &probed_ifaces[i], + local_interfaces); + probed_ifaces[i].netmask = saved_mask; + return; + } + } + DEBUG(2,("interpret_interface: Can't determine ip for " + "broadcast address %s\n", + token)); + return; + } + + /* Just fake up the interface definition. User knows best. */ + + DEBUG(2,("interpret_interface: Adding interface %s\n", + token)); + + ZERO_STRUCT(ifs); + (void)strlcpy(ifs.name, token, sizeof(ifs.name)); + ifs.flags = IFF_BROADCAST; + ifs.ip = ss; + ifs.netmask = ss_mask; + ifs.bcast = ss_bcast; + add_interface(mem_ctx, &ifs, local_interfaces); +} + + +/** +load the list of network interfaces +**/ +void load_interface_list(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx, struct interface **local_interfaces) +{ + const char **ptr = lpcfg_interfaces(lp_ctx); + int i; + struct iface_struct *ifaces = NULL; + int total_probed; + + *local_interfaces = NULL; + + /* probe the kernel for interfaces */ + total_probed = get_interfaces(mem_ctx, &ifaces); + + /* if we don't have a interfaces line then use all interfaces + except loopback */ + if (!ptr || !*ptr || !**ptr) { + if (total_probed <= 0) { + DEBUG(0,("ERROR: Could not determine network interfaces, you must use a interfaces config line\n")); + } + for (i=0;i<total_probed;i++) { + if (!is_loopback_addr((struct sockaddr *)&ifaces[i].ip)) { + add_interface(mem_ctx, &ifaces[i], local_interfaces); + } + } + } + + while (ptr && *ptr) { + interpret_interface(mem_ctx, *ptr, ifaces, total_probed, local_interfaces); + ptr++; + } + + if (!*local_interfaces) { + DEBUG(0,("WARNING: no network interfaces found\n")); + } + talloc_free(ifaces); +} + +/** + how many interfaces do we have + **/ +int iface_list_count(struct interface *ifaces) +{ + int ret = 0; + struct interface *i; + + for (i=ifaces;i;i=i->next) + ret++; + return ret; +} + +/** + return IP of the Nth interface + **/ +const char *iface_list_n_ip(struct interface *ifaces, int n) +{ + struct interface *i; + + for (i=ifaces;i && n;i=i->next) + n--; + + if (i) { + return i->ip_s; + } + return NULL; +} + + +/** + return the first IPv4 interface address we have registered + **/ +const char *iface_list_first_v4(struct interface *ifaces) +{ + struct interface *i; + + for (i=ifaces; i; i=i->next) { + if (i->ip.ss_family == AF_INET) { + return i->ip_s; + } + } + return NULL; +} + +/** + return the first IPv6 interface address we have registered + **/ +static const char *iface_list_first_v6(struct interface *ifaces) +{ + struct interface *i; + +#ifdef HAVE_IPV6 + for (i=ifaces; i; i=i->next) { + if (i->ip.ss_family == AF_INET6) { + return i->ip_s; + } + } +#endif + return NULL; +} + +/** + check if an interface is IPv4 + **/ +bool iface_list_n_is_v4(struct interface *ifaces, int n) +{ + struct interface *i; + + for (i=ifaces;i && n;i=i->next) + n--; + + if (i) { + return i->ip.ss_family == AF_INET; + } + return false; +} + +/** + return bcast of the Nth interface + **/ +const char *iface_list_n_bcast(struct interface *ifaces, int n) +{ + struct interface *i; + + for (i=ifaces;i && n;i=i->next) + n--; + + if (i) { + return i->bcast_s; + } + return NULL; +} + +/** + return netmask of the Nth interface + **/ +const char *iface_list_n_netmask(struct interface *ifaces, int n) +{ + struct interface *i; + + for (i=ifaces;i && n;i=i->next) + n--; + + if (i) { + return i->nmask_s; + } + return NULL; +} + +/** + return the local IP address that best matches a destination IP, or + our first interface if none match +*/ +const char *iface_list_best_ip(struct interface *ifaces, const char *dest) +{ + struct interface *iface; + struct sockaddr_storage ss; + + if (!interpret_string_addr(&ss, dest, AI_NUMERICHOST)) { + return iface_list_n_ip(ifaces, 0); + } + iface = iface_list_find(ifaces, (const struct sockaddr *)&ss, true); + if (iface) { + return iface->ip_s; + } +#ifdef HAVE_IPV6 + if (ss.ss_family == AF_INET6) { + return iface_list_first_v6(ifaces); + } +#endif + return iface_list_first_v4(ifaces); +} + +/** + return true if an IP is one one of our local networks +*/ +bool iface_list_is_local(struct interface *ifaces, const char *dest) +{ + struct sockaddr_storage ss; + + if (!interpret_string_addr(&ss, dest, AI_NUMERICHOST)) { + return false; + } + if (iface_list_find(ifaces, (const struct sockaddr *)&ss, true)) { + return true; + } + return false; +} + +/** + return true if a IP matches a IP/netmask pair +*/ +bool iface_list_same_net(const char *ip1, const char *ip2, const char *netmask) +{ + struct sockaddr_storage ip1_ss, ip2_ss, nm_ss; + + if (!interpret_string_addr(&ip1_ss, ip1, AI_NUMERICHOST)) { + return false; + } + if (!interpret_string_addr(&ip2_ss, ip2, AI_NUMERICHOST)) { + return false; + } + if (!interpret_string_addr(&nm_ss, netmask, AI_NUMERICHOST)) { + return false; + } + + return same_net((struct sockaddr *)&ip1_ss, + (struct sockaddr *)&ip2_ss, + (struct sockaddr *)&nm_ss); +} + +/** + return the list of wildcard interfaces + this will include the IPv4 0.0.0.0, and may include IPv6 :: +*/ +char **iface_list_wildcard(TALLOC_CTX *mem_ctx) +{ + char **ret; +#ifdef HAVE_IPV6 + ret = str_list_make(mem_ctx, "::,0.0.0.0", NULL); +#else + ret = str_list_make(mem_ctx, "0.0.0.0", NULL); +#endif + return ret; +} diff --git a/source4/lib/socket/netif.h b/source4/lib/socket/netif.h new file mode 100644 index 0000000..1d90a4f --- /dev/null +++ b/source4/lib/socket/netif.h @@ -0,0 +1,24 @@ +/* + Unix SMB/CIFS implementation. + + structures for lib/netif/ + + Copyright (C) Andrew Tridgell 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "system/network.h" +#include "lib/socket/interfaces.h" +#include "lib/socket/netif_proto.h" diff --git a/source4/lib/socket/socket.c b/source4/lib/socket/socket.c new file mode 100644 index 0000000..ef54029 --- /dev/null +++ b/source4/lib/socket/socket.c @@ -0,0 +1,640 @@ +/* + Unix SMB/CIFS implementation. + Socket functions + Copyright (C) Andrew Tridgell 1992-1998 + Copyright (C) Tim Potter 2000-2001 + Copyright (C) Stefan Metzmacher 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "system/filesys.h" +#include "system/network.h" +#include "param/param.h" +#include "../lib/tsocket/tsocket.h" +#include "lib/util/util_net.h" + +/* + auto-close sockets on free +*/ +static int socket_destructor(struct socket_context *sock) +{ + if (sock->ops->fn_close && + !(sock->flags & SOCKET_FLAG_NOCLOSE)) { + sock->ops->fn_close(sock); + } + return 0; +} + +_PUBLIC_ void socket_tevent_fd_close_fn(struct tevent_context *ev, + struct tevent_fd *fde, + int fd, + void *private_data) +{ + /* this might be the socket_wrapper swrap_close() */ + close(fd); +} + +_PUBLIC_ NTSTATUS socket_create_with_ops(TALLOC_CTX *mem_ctx, const struct socket_ops *ops, + struct socket_context **new_sock, + enum socket_type type, uint32_t flags) +{ + NTSTATUS status; + + (*new_sock) = talloc(mem_ctx, struct socket_context); + if (!(*new_sock)) { + return NT_STATUS_NO_MEMORY; + } + + (*new_sock)->type = type; + (*new_sock)->state = SOCKET_STATE_UNDEFINED; + (*new_sock)->flags = flags; + + (*new_sock)->fd = -1; + + (*new_sock)->private_data = NULL; + (*new_sock)->ops = ops; + (*new_sock)->backend_name = NULL; + + status = (*new_sock)->ops->fn_init((*new_sock)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(*new_sock); + return status; + } + + /* by enabling "testnonblock" mode, all socket receive and + send calls on non-blocking sockets will randomly recv/send + less data than requested */ + + if (type == SOCKET_TYPE_STREAM && + getenv("SOCKET_TESTNONBLOCK") != NULL) { + (*new_sock)->flags |= SOCKET_FLAG_TESTNONBLOCK; + } + + /* we don't do a connect() on dgram sockets, so need to set + non-blocking at socket create time */ + if (type == SOCKET_TYPE_DGRAM) { + set_blocking(socket_get_fd(*new_sock), false); + } + + talloc_set_destructor(*new_sock, socket_destructor); + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS socket_create(TALLOC_CTX *mem_ctx, + const char *name, enum socket_type type, + struct socket_context **new_sock, uint32_t flags) +{ + const struct socket_ops *ops; + + ops = socket_getops_byname(name, type); + if (!ops) { + return NT_STATUS_INVALID_PARAMETER; + } + + return socket_create_with_ops(mem_ctx, ops, new_sock, type, flags); +} + +_PUBLIC_ NTSTATUS socket_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *server_address, + uint32_t flags) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->state != SOCKET_STATE_UNDEFINED) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_connect) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_connect(sock, my_address, server_address, flags); +} + +_PUBLIC_ NTSTATUS socket_connect_complete(struct socket_context *sock, uint32_t flags) +{ + if (!sock->ops->fn_connect_complete) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return sock->ops->fn_connect_complete(sock, flags); +} + +_PUBLIC_ NTSTATUS socket_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->state != SOCKET_STATE_UNDEFINED) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_listen) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_listen(sock, my_address, queue_size, flags); +} + +_PUBLIC_ NTSTATUS socket_accept(struct socket_context *sock, struct socket_context **new_sock) +{ + NTSTATUS status; + + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->type != SOCKET_TYPE_STREAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (sock->state != SOCKET_STATE_SERVER_LISTEN) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_accept) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + status = sock->ops->fn_accept(sock, new_sock); + + if (NT_STATUS_IS_OK(status)) { + talloc_set_destructor(*new_sock, socket_destructor); + (*new_sock)->flags = 0; + } + + return status; +} + +_PUBLIC_ NTSTATUS socket_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->state != SOCKET_STATE_CLIENT_CONNECTED && + sock->state != SOCKET_STATE_SERVER_CONNECTED && + sock->type != SOCKET_TYPE_DGRAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_recv) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + if ((sock->flags & SOCKET_FLAG_TESTNONBLOCK) + && wantlen > 1) { + + if (random() % 10 == 0) { + *nread = 0; + return STATUS_MORE_ENTRIES; + } + return sock->ops->fn_recv(sock, buf, 1+(random() % wantlen), nread); + } + return sock->ops->fn_recv(sock, buf, wantlen, nread); +} + +_PUBLIC_ NTSTATUS socket_recvfrom(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread, + TALLOC_CTX *mem_ctx, struct socket_address **src_addr) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->type != SOCKET_TYPE_DGRAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_recvfrom) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_recvfrom(sock, buf, wantlen, nread, + mem_ctx, src_addr); +} + +_PUBLIC_ NTSTATUS socket_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->state != SOCKET_STATE_CLIENT_CONNECTED && + sock->state != SOCKET_STATE_SERVER_CONNECTED) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_send) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + if ((sock->flags & SOCKET_FLAG_TESTNONBLOCK) + && blob->length > 1) { + DATA_BLOB blob2 = *blob; + if (random() % 10 == 0) { + *sendlen = 0; + return STATUS_MORE_ENTRIES; + } + /* The random size sends are incompatible with TLS and SASL + * sockets, which require re-sends to be consistent */ + if (!(sock->flags & SOCKET_FLAG_ENCRYPT)) { + blob2.length = 1+(random() % blob2.length); + } else { + /* This is particularly stressful on buggy + * LDAP clients, that don't expect on LDAP + * packet in many SASL packets */ + blob2.length = 1 + blob2.length/2; + } + return sock->ops->fn_send(sock, &blob2, sendlen); + } + return sock->ops->fn_send(sock, blob, sendlen); +} + + +_PUBLIC_ NTSTATUS socket_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->type != SOCKET_TYPE_DGRAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (sock->state == SOCKET_STATE_CLIENT_CONNECTED || + sock->state == SOCKET_STATE_SERVER_CONNECTED) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_sendto) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_sendto(sock, blob, sendlen, dest_addr); +} + + +/* + ask for the number of bytes in a pending incoming packet +*/ +_PUBLIC_ NTSTATUS socket_pending(struct socket_context *sock, size_t *npending) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (!sock->ops->fn_pending) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return sock->ops->fn_pending(sock, npending); +} + + +_PUBLIC_ NTSTATUS socket_set_option(struct socket_context *sock, const char *option, const char *val) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (!sock->ops->fn_set_option) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_set_option(sock, option, val); +} + +_PUBLIC_ char *socket_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + if (!sock->ops->fn_get_peer_name) { + return NULL; + } + + return sock->ops->fn_get_peer_name(sock, mem_ctx); +} + +_PUBLIC_ struct socket_address *socket_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + if (!sock->ops->fn_get_peer_addr) { + return NULL; + } + + return sock->ops->fn_get_peer_addr(sock, mem_ctx); +} + +_PUBLIC_ struct socket_address *socket_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + if (!sock->ops->fn_get_my_addr) { + return NULL; + } + + return sock->ops->fn_get_my_addr(sock, mem_ctx); +} + +_PUBLIC_ struct tsocket_address *socket_address_to_tsocket_address(TALLOC_CTX *mem_ctx, + const struct socket_address *a) +{ + struct tsocket_address *r; + int ret; + + if (!a) { + return NULL; + } + if (a->sockaddr) { + ret = tsocket_address_bsd_from_sockaddr(mem_ctx, + a->sockaddr, + a->sockaddrlen, + &r); + } else { + ret = tsocket_address_inet_from_strings(mem_ctx, + a->family, + a->addr, + a->port, + &r); + } + + if (ret != 0) { + return NULL; + } + + return r; +} + +_PUBLIC_ void socket_address_set_port(struct socket_address *a, + uint16_t port) +{ + if (a->sockaddr) { + set_sockaddr_port(a->sockaddr, port); + } else { + a->port = port; + } + +} + +_PUBLIC_ struct socket_address *tsocket_address_to_socket_address(TALLOC_CTX *mem_ctx, + const struct tsocket_address *a) +{ + ssize_t ret; + struct sockaddr_storage ss; + size_t sslen = sizeof(ss); + + ret = tsocket_address_bsd_sockaddr(a, (struct sockaddr *)(void *)&ss, sslen); + if (ret < 0) { + return NULL; + } + + return socket_address_from_sockaddr(mem_ctx, (struct sockaddr *)(void *)&ss, ret); +} + +_PUBLIC_ struct tsocket_address *socket_get_remote_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct socket_address *a; + struct tsocket_address *r; + + a = socket_get_peer_addr(sock, mem_ctx); + if (a == NULL) { + return NULL; + } + + r = socket_address_to_tsocket_address(mem_ctx, a); + talloc_free(a); + return r; +} + +_PUBLIC_ struct tsocket_address *socket_get_local_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct socket_address *a; + struct tsocket_address *r; + + a = socket_get_my_addr(sock, mem_ctx); + if (a == NULL) { + return NULL; + } + + r = socket_address_to_tsocket_address(mem_ctx, a); + talloc_free(a); + return r; +} + +_PUBLIC_ int socket_get_fd(struct socket_context *sock) +{ + if (!sock->ops->fn_get_fd) { + return -1; + } + + return sock->ops->fn_get_fd(sock); +} + +/* + call dup() on a socket, and close the old fd. This is used to change + the fd to the lowest available number, to make select() more + efficient (select speed depends on the maximum fd number passed to + it) +*/ +_PUBLIC_ NTSTATUS socket_dup(struct socket_context *sock) +{ + int fd; + if (sock->fd == -1) { + return NT_STATUS_INVALID_HANDLE; + } + fd = dup(sock->fd); + if (fd == -1) { + return map_nt_error_from_unix_common(errno); + } + close(sock->fd); + sock->fd = fd; + return NT_STATUS_OK; + +} + +/* Create a new socket_address. The type must match the socket type. + * The host parameter may be an IP or a hostname + */ + +_PUBLIC_ struct socket_address *socket_address_from_strings(TALLOC_CTX *mem_ctx, + const char *family, + const char *host, + int port) +{ + struct socket_address *addr = talloc(mem_ctx, struct socket_address); + if (!addr) { + return NULL; + } + + if (strcmp(family, "ip") == 0 && is_ipaddress_v6(host)) { + /* leaving as "ip" would force IPv4 */ + family = "ipv6"; + } + + addr->family = family; + addr->addr = talloc_strdup(addr, host); + if (!addr->addr) { + talloc_free(addr); + return NULL; + } + addr->port = port; + addr->sockaddr = NULL; + addr->sockaddrlen = 0; + + return addr; +} + +/* Create a new socket_address. Copy the struct sockaddr into the new + * structure. Used for hooks in the kerberos libraries, where they + * supply only a struct sockaddr */ + +_PUBLIC_ struct socket_address *socket_address_from_sockaddr(TALLOC_CTX *mem_ctx, + struct sockaddr *sockaddr, + size_t sockaddrlen) +{ + struct socket_address *addr = talloc(mem_ctx, struct socket_address); + if (!addr) { + return NULL; + } + switch (sockaddr->sa_family) { + case AF_INET: + addr->family = "ipv4"; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + addr->family = "ipv6"; + break; +#endif + case AF_UNIX: + addr->family = "unix"; + break; + } + addr->addr = NULL; + addr->port = 0; + addr->sockaddr = (struct sockaddr *)talloc_memdup(addr, sockaddr, sockaddrlen); + if (!addr->sockaddr) { + talloc_free(addr); + return NULL; + } + addr->sockaddrlen = sockaddrlen; + return addr; +} + + +/* + Create a new socket_address from sockaddr_storage + */ +_PUBLIC_ struct socket_address *socket_address_from_sockaddr_storage(TALLOC_CTX *mem_ctx, + const struct sockaddr_storage *sockaddr, + uint16_t port) +{ + struct socket_address *addr = talloc_zero(mem_ctx, struct socket_address); + char addr_str[INET6_ADDRSTRLEN+1]; + const char *str; + + if (!addr) { + return NULL; + } + addr->port = port; + switch (sockaddr->ss_family) { + case AF_INET: + addr->family = "ipv4"; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + addr->family = "ipv6"; + break; +#endif + default: + talloc_free(addr); + return NULL; + } + + str = print_sockaddr(addr_str, sizeof(addr_str), sockaddr); + if (str == NULL) { + talloc_free(addr); + return NULL; + } + addr->addr = talloc_strdup(addr, str); + if (addr->addr == NULL) { + talloc_free(addr); + return NULL; + } + + return addr; +} + +/* Copy a socket_address structure */ +struct socket_address *socket_address_copy(TALLOC_CTX *mem_ctx, + const struct socket_address *oaddr) +{ + struct socket_address *addr = talloc_zero(mem_ctx, struct socket_address); + if (!addr) { + return NULL; + } + addr->family = oaddr->family; + if (oaddr->addr) { + addr->addr = talloc_strdup(addr, oaddr->addr); + if (!addr->addr) { + goto nomem; + } + } + addr->port = oaddr->port; + if (oaddr->sockaddr) { + addr->sockaddr = (struct sockaddr *)talloc_memdup(addr, + oaddr->sockaddr, + oaddr->sockaddrlen); + if (!addr->sockaddr) { + goto nomem; + } + addr->sockaddrlen = oaddr->sockaddrlen; + } + + return addr; + +nomem: + talloc_free(addr); + return NULL; +} + +_PUBLIC_ const struct socket_ops *socket_getops_byname(const char *family, enum socket_type type) +{ + extern const struct socket_ops *socket_ipv4_ops(enum socket_type); + extern const struct socket_ops *socket_ipv6_ops(enum socket_type); + extern const struct socket_ops *socket_unixdom_ops(enum socket_type); + + if (strcmp("ip", family) == 0 || + strcmp("ipv4", family) == 0) { + return socket_ipv4_ops(type); + } + +#ifdef HAVE_IPV6 + if (strcmp("ipv6", family) == 0) { + return socket_ipv6_ops(type); + } +#endif + + if (strcmp("unix", family) == 0) { + return socket_unixdom_ops(type); + } + + return NULL; +} + +/* + set some flags on a socket + */ +void socket_set_flags(struct socket_context *sock, unsigned flags) +{ + sock->flags |= flags; +} diff --git a/source4/lib/socket/socket.h b/source4/lib/socket/socket.h new file mode 100644 index 0000000..ba2c17e --- /dev/null +++ b/source4/lib/socket/socket.h @@ -0,0 +1,256 @@ +/* + Unix SMB/CIFS implementation. + Socket functions + Copyright (C) Stefan Metzmacher 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SAMBA_SOCKET_H +#define _SAMBA_SOCKET_H + +struct tevent_context; +struct tevent_fd; +struct socket_context; + +enum socket_type { + SOCKET_TYPE_STREAM, + SOCKET_TYPE_DGRAM +}; + +struct socket_address { + const char *family; + char *addr; + int port; + struct sockaddr *sockaddr; + size_t sockaddrlen; +}; + +struct socket_ops { + const char *name; + + NTSTATUS (*fn_init)(struct socket_context *sock); + + /* client ops */ + NTSTATUS (*fn_connect)(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *server_address, + uint32_t flags); + + /* complete a non-blocking connect */ + NTSTATUS (*fn_connect_complete)(struct socket_context *sock, + uint32_t flags); + + /* server ops */ + NTSTATUS (*fn_listen)(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags); + NTSTATUS (*fn_accept)(struct socket_context *sock, + struct socket_context **new_sock); + + /* general ops */ + NTSTATUS (*fn_recv)(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread); + NTSTATUS (*fn_send)(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen); + + NTSTATUS (*fn_sendto)(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr); + NTSTATUS (*fn_recvfrom)(struct socket_context *sock, + void *buf, size_t wantlen, size_t *nread, + TALLOC_CTX *addr_ctx, struct socket_address **src_addr); + NTSTATUS (*fn_pending)(struct socket_context *sock, size_t *npending); + + void (*fn_close)(struct socket_context *sock); + + NTSTATUS (*fn_set_option)(struct socket_context *sock, const char *option, const char *val); + + char *(*fn_get_peer_name)(struct socket_context *sock, TALLOC_CTX *mem_ctx); + struct socket_address *(*fn_get_peer_addr)(struct socket_context *sock, TALLOC_CTX *mem_ctx); + struct socket_address *(*fn_get_my_addr)(struct socket_context *sock, TALLOC_CTX *mem_ctx); + + int (*fn_get_fd)(struct socket_context *sock); +}; + +enum socket_state { + SOCKET_STATE_UNDEFINED, + + SOCKET_STATE_CLIENT_START, + SOCKET_STATE_CLIENT_CONNECTED, + SOCKET_STATE_CLIENT_STARTTLS, + SOCKET_STATE_CLIENT_ERROR, + + SOCKET_STATE_SERVER_LISTEN, + SOCKET_STATE_SERVER_CONNECTED, + SOCKET_STATE_SERVER_STARTTLS, + SOCKET_STATE_SERVER_ERROR +}; + +#define SOCKET_FLAG_PEEK 0x00000002 +#define SOCKET_FLAG_TESTNONBLOCK 0x00000004 +#define SOCKET_FLAG_ENCRYPT 0x00000008 /* This socket + * implementation requires + * that re-sends be + * consistent, because it + * is encrypting data. + * This modifies the + * TESTNONBLOCK case */ +#define SOCKET_FLAG_NOCLOSE 0x00000010 /* don't auto-close on free */ + + +struct socket_context { + enum socket_type type; + enum socket_state state; + uint32_t flags; + + int fd; + + void *private_data; + const struct socket_ops *ops; + const char *backend_name; + + /* specific to the ip backend */ + int family; +}; + +struct resolve_context; +struct tsocket_address; + +/* prototypes */ +NTSTATUS socket_create_with_ops(TALLOC_CTX *mem_ctx, const struct socket_ops *ops, + struct socket_context **new_sock, + enum socket_type type, uint32_t flags); +NTSTATUS socket_create(TALLOC_CTX *mem_ctx, + const char *name, enum socket_type type, + struct socket_context **new_sock, uint32_t flags); +NTSTATUS socket_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *server_address, + uint32_t flags); +NTSTATUS socket_connect_complete(struct socket_context *sock, uint32_t flags); +NTSTATUS socket_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags); +NTSTATUS socket_accept(struct socket_context *sock, struct socket_context **new_sock); +NTSTATUS socket_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread); +NTSTATUS socket_recvfrom(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread, + TALLOC_CTX *addr_ctx, struct socket_address **src_addr); +NTSTATUS socket_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen); +NTSTATUS socket_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr); +NTSTATUS socket_pending(struct socket_context *sock, size_t *npending); +NTSTATUS socket_set_option(struct socket_context *sock, const char *option, const char *val); +char *socket_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx); +struct socket_address *socket_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx); +struct socket_address *socket_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx); +struct tsocket_address *socket_address_to_tsocket_address(TALLOC_CTX *mem_ctx, + const struct socket_address *a); +struct socket_address *tsocket_address_to_socket_address(TALLOC_CTX *mem_ctx, + const struct tsocket_address *a); +struct tsocket_address *socket_get_remote_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx); +struct tsocket_address *socket_get_local_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx); +int socket_get_fd(struct socket_context *sock); +NTSTATUS socket_dup(struct socket_context *sock); +struct socket_address *socket_address_from_strings(TALLOC_CTX *mem_ctx, + const char *type, + const char *host, + int port); +struct socket_address *socket_address_from_sockaddr(TALLOC_CTX *mem_ctx, + struct sockaddr *sockaddr, + size_t addrlen); +struct sockaddr_storage; +struct socket_address *socket_address_from_sockaddr_storage(TALLOC_CTX *mem_ctx, + const struct sockaddr_storage *sockaddr, + uint16_t port); +_PUBLIC_ void socket_address_set_port(struct socket_address *a, + uint16_t port); +struct socket_address *socket_address_copy(TALLOC_CTX *mem_ctx, + const struct socket_address *oaddr); +const struct socket_ops *socket_getops_byname(const char *name, enum socket_type type); +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, + struct socket_address *my_address, + struct socket_address *server_address, + uint32_t flags, + struct tevent_context *event_ctx); +NTSTATUS socket_connect_recv(struct composite_context *ctx); +NTSTATUS socket_connect_ev(struct socket_context *sock, + struct socket_address *my_address, + struct socket_address *server_address, + uint32_t flags, + struct tevent_context *ev); + +struct socket_connect_multi_ex { + void *private_data; + struct tevent_req *(*establish_send)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct socket_context *sock, + struct socket_address *addr, + void *private_data); + NTSTATUS (*establish_recv)(struct tevent_req *req); +}; +struct composite_context *socket_connect_multi_ex_send(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex); +NTSTATUS socket_connect_multi_ex_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **result, + uint16_t *port); +NTSTATUS socket_connect_multi_ex(TALLOC_CTX *mem_ctx, const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex, + struct socket_context **result, + uint16_t *port); + +struct composite_context *socket_connect_multi_send(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx); +NTSTATUS socket_connect_multi_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **result, + uint16_t *port); +NTSTATUS socket_connect_multi(TALLOC_CTX *mem_ctx, const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_context **result, + uint16_t *port); +void set_socket_options(int fd, const char *options); +void socket_set_flags(struct socket_context *sock, unsigned flags); + +void socket_tevent_fd_close_fn(struct tevent_context *ev, + struct tevent_fd *fde, + int fd, + void *private_data); + +extern bool testnonblock; + +#endif /* _SAMBA_SOCKET_H */ diff --git a/source4/lib/socket/socket_ip.c b/source4/lib/socket/socket_ip.c new file mode 100644 index 0000000..62dbf1d --- /dev/null +++ b/source4/lib/socket/socket_ip.c @@ -0,0 +1,1033 @@ +/* + Unix SMB/CIFS implementation. + + Socket IPv4/IPv6 functions + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Tridgell 2004-2005 + Copyright (C) Jelmer Vernooij 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "lib/socket/socket.h" +#include "system/network.h" +#include "lib/util/util_net.h" + +#undef strcasecmp + +_PUBLIC_ const struct socket_ops *socket_ipv4_ops(enum socket_type type); +_PUBLIC_ const struct socket_ops *socket_ipv6_ops(enum socket_type type); + +static NTSTATUS ipv4_init(struct socket_context *sock) +{ + int type; + + switch (sock->type) { + case SOCKET_TYPE_STREAM: + type = SOCK_STREAM; + break; + case SOCKET_TYPE_DGRAM: + type = SOCK_DGRAM; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + sock->fd = socket(PF_INET, type, 0); + if (sock->fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + smb_set_close_on_exec(sock->fd); + + sock->backend_name = "ipv4"; + sock->family = AF_INET; + + return NT_STATUS_OK; +} + +static void ip_close(struct socket_context *sock) +{ + if (sock->fd != -1) { + close(sock->fd); + sock->fd = -1; + } +} + +static NTSTATUS ip_connect_complete(struct socket_context *sock, uint32_t flags) +{ + int error=0, ret; + socklen_t len = sizeof(error); + + /* check for any errors that may have occurred - this is needed + for non-blocking connect */ + ret = getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &error, &len); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + if (error != 0) { + return map_nt_error_from_unix_common(error); + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + sock->state = SOCKET_STATE_CLIENT_CONNECTED; + + return NT_STATUS_OK; +} + + +static NTSTATUS ipv4_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *srv_address, + uint32_t flags) +{ + struct sockaddr_in srv_addr; + struct in_addr my_ip; + struct in_addr srv_ip; + int ret; + + if (my_address && my_address->sockaddr) { + ret = bind(sock->fd, my_address->sockaddr, my_address->sockaddrlen); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } else if (my_address) { + my_ip = interpret_addr2(my_address->addr); + + if (my_ip.s_addr != 0 || my_address->port != 0) { + struct sockaddr_in my_addr; + ZERO_STRUCT(my_addr); +#ifdef HAVE_SOCK_SIN_LEN + my_addr.sin_len = sizeof(my_addr); +#endif + my_addr.sin_addr.s_addr = my_ip.s_addr; + my_addr.sin_port = htons(my_address->port); + my_addr.sin_family = PF_INET; + + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + } + + if (srv_address->sockaddr) { + ret = connect(sock->fd, srv_address->sockaddr, srv_address->sockaddrlen); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } else { + srv_ip = interpret_addr2(srv_address->addr); + if (!srv_ip.s_addr) { + return NT_STATUS_BAD_NETWORK_NAME; + } + + SMB_ASSERT(srv_address->port != 0); + + ZERO_STRUCT(srv_addr); +#ifdef HAVE_SOCK_SIN_LEN + srv_addr.sin_len = sizeof(srv_addr); +#endif + srv_addr.sin_addr.s_addr= srv_ip.s_addr; + srv_addr.sin_port = htons(srv_address->port); + srv_addr.sin_family = PF_INET; + + ret = connect(sock->fd, (const struct sockaddr *)&srv_addr, sizeof(srv_addr)); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + + return ip_connect_complete(sock, flags); +} + + +/* + note that for simplicity of the API, socket_listen() is also + use for DGRAM sockets, but in reality only a bind() is done +*/ +static NTSTATUS ipv4_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags) +{ + struct sockaddr_in my_addr; + struct in_addr ip_addr; + int ret; + + socket_set_option(sock, "SO_REUSEADDR=1", NULL); + + if (my_address->sockaddr) { + ret = bind(sock->fd, my_address->sockaddr, my_address->sockaddrlen); + } else { + ip_addr = interpret_addr2(my_address->addr); + + ZERO_STRUCT(my_addr); +#ifdef HAVE_SOCK_SIN_LEN + my_addr.sin_len = sizeof(my_addr); +#endif + my_addr.sin_addr.s_addr = ip_addr.s_addr; + my_addr.sin_port = htons(my_address->port); + my_addr.sin_family = PF_INET; + + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + } + + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + if (sock->type == SOCKET_TYPE_STREAM) { + ret = listen(sock->fd, queue_size); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + sock->state= SOCKET_STATE_SERVER_LISTEN; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv4_accept(struct socket_context *sock, struct socket_context **new_sock) +{ + struct sockaddr_in cli_addr; + socklen_t cli_addr_len = sizeof(cli_addr); + int new_fd, ret; + + if (sock->type != SOCKET_TYPE_STREAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + new_fd = accept(sock->fd, (struct sockaddr *)&cli_addr, &cli_addr_len); + if (new_fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + ret = set_blocking(new_fd, false); + if (ret == -1) { + close(new_fd); + return map_nt_error_from_unix_common(errno); + } + + smb_set_close_on_exec(new_fd); + + + /* TODO: we could add a 'accept_check' hook here + * which get the black/white lists via socket_set_accept_filter() + * or something like that + * --metze + */ + + (*new_sock) = talloc(NULL, struct socket_context); + if (!(*new_sock)) { + close(new_fd); + return NT_STATUS_NO_MEMORY; + } + + /* copy the socket_context */ + (*new_sock)->type = sock->type; + (*new_sock)->state = SOCKET_STATE_SERVER_CONNECTED; + (*new_sock)->flags = sock->flags; + + (*new_sock)->fd = new_fd; + + (*new_sock)->private_data = NULL; + (*new_sock)->ops = sock->ops; + (*new_sock)->backend_name = sock->backend_name; + + return NT_STATUS_OK; +} + +static NTSTATUS ip_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread) +{ + ssize_t gotlen; + + *nread = 0; + + gotlen = recv(sock->fd, buf, wantlen, 0); + if (gotlen == 0) { + return NT_STATUS_END_OF_FILE; + } else if (gotlen == -1) { + return map_nt_error_from_unix_common(errno); + } + + *nread = gotlen; + + return NT_STATUS_OK; +} + + +static NTSTATUS ipv4_recvfrom(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread, + TALLOC_CTX *addr_ctx, struct socket_address **_src) +{ + ssize_t gotlen; + struct sockaddr_in *from_addr; + socklen_t from_len = sizeof(*from_addr); + struct socket_address *src; + char addrstring[INET_ADDRSTRLEN]; + + src = talloc(addr_ctx, struct socket_address); + if (!src) { + return NT_STATUS_NO_MEMORY; + } + + src->family = sock->backend_name; + + from_addr = talloc(src, struct sockaddr_in); + if (!from_addr) { + talloc_free(src); + return NT_STATUS_NO_MEMORY; + } + + src->sockaddr = (struct sockaddr *)from_addr; + + *nread = 0; + + gotlen = recvfrom(sock->fd, buf, wantlen, 0, + src->sockaddr, &from_len); + if (gotlen == 0) { + talloc_free(src); + return NT_STATUS_END_OF_FILE; + } + if (gotlen == -1) { + talloc_free(src); + return map_nt_error_from_unix_common(errno); + } + + src->sockaddrlen = from_len; + + if (inet_ntop(AF_INET, &from_addr->sin_addr, addrstring, + sizeof(addrstring)) == NULL) { + talloc_free(src); + return NT_STATUS_INTERNAL_ERROR; + } + src->addr = talloc_strdup(src, addrstring); + if (src->addr == NULL) { + talloc_free(src); + return NT_STATUS_NO_MEMORY; + } + src->port = ntohs(from_addr->sin_port); + + *nread = gotlen; + *_src = src; + return NT_STATUS_OK; +} + +static NTSTATUS ip_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen) +{ + ssize_t len; + + *sendlen = 0; + + len = send(sock->fd, blob->data, blob->length, 0); + if (len == -1) { + return map_nt_error_from_unix_common(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv4_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr) +{ + ssize_t len; + + if (dest_addr->sockaddr) { + len = sendto(sock->fd, blob->data, blob->length, 0, + dest_addr->sockaddr, dest_addr->sockaddrlen); + } else { + struct sockaddr_in srv_addr; + struct in_addr addr; + + SMB_ASSERT(dest_addr->port != 0); + + ZERO_STRUCT(srv_addr); +#ifdef HAVE_SOCK_SIN_LEN + srv_addr.sin_len = sizeof(srv_addr); +#endif + addr = interpret_addr2(dest_addr->addr); + if (addr.s_addr == 0) { + return NT_STATUS_HOST_UNREACHABLE; + } + srv_addr.sin_addr.s_addr = addr.s_addr; + srv_addr.sin_port = htons(dest_addr->port); + srv_addr.sin_family = PF_INET; + + *sendlen = 0; + + len = sendto(sock->fd, blob->data, blob->length, 0, + (struct sockaddr *)&srv_addr, sizeof(srv_addr)); + } + if (len == -1) { + return map_nt_error_from_unix_common(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv4_set_option(struct socket_context *sock, const char *option, const char *val) +{ + set_socket_options(sock->fd, option); + return NT_STATUS_OK; +} + +static char *ipv4_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in peer_addr; + socklen_t len = sizeof(peer_addr); + struct hostent *he; + int ret; + + ret = getpeername(sock->fd, (struct sockaddr *)&peer_addr, &len); + if (ret == -1) { + return NULL; + } + + he = gethostbyaddr((char *)&peer_addr.sin_addr, sizeof(peer_addr.sin_addr), AF_INET); + if (he == NULL) { + return NULL; + } + + return talloc_strdup(mem_ctx, he->h_name); +} + +static struct socket_address *ipv4_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in *peer_addr; + socklen_t len = sizeof(*peer_addr); + struct socket_address *peer; + char addrstring[INET_ADDRSTRLEN]; + int ret; + + peer = talloc(mem_ctx, struct socket_address); + if (!peer) { + return NULL; + } + + peer->family = sock->backend_name; + peer_addr = talloc(peer, struct sockaddr_in); + if (!peer_addr) { + talloc_free(peer); + return NULL; + } + + peer->sockaddr = (struct sockaddr *)peer_addr; + + ret = getpeername(sock->fd, peer->sockaddr, &len); + if (ret == -1) { + talloc_free(peer); + return NULL; + } + + peer->sockaddrlen = len; + + if (inet_ntop(AF_INET, &peer_addr->sin_addr, addrstring, + sizeof(addrstring)) == NULL) { + talloc_free(peer); + return NULL; + } + peer->addr = talloc_strdup(peer, addrstring); + if (!peer->addr) { + talloc_free(peer); + return NULL; + } + peer->port = ntohs(peer_addr->sin_port); + + return peer; +} + +static struct socket_address *ipv4_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in *local_addr; + socklen_t len = sizeof(*local_addr); + struct socket_address *local; + char addrstring[INET_ADDRSTRLEN]; + int ret; + + local = talloc(mem_ctx, struct socket_address); + if (!local) { + return NULL; + } + + local->family = sock->backend_name; + local_addr = talloc(local, struct sockaddr_in); + if (!local_addr) { + talloc_free(local); + return NULL; + } + + local->sockaddr = (struct sockaddr *)local_addr; + + ret = getsockname(sock->fd, local->sockaddr, &len); + if (ret == -1) { + talloc_free(local); + return NULL; + } + + local->sockaddrlen = len; + + if (inet_ntop(AF_INET, &local_addr->sin_addr, addrstring, + sizeof(addrstring)) == NULL) { + talloc_free(local); + return NULL; + } + local->addr = talloc_strdup(local, addrstring); + if (!local->addr) { + talloc_free(local); + return NULL; + } + local->port = ntohs(local_addr->sin_port); + + return local; +} +static int ip_get_fd(struct socket_context *sock) +{ + return sock->fd; +} + +static NTSTATUS ip_pending(struct socket_context *sock, size_t *npending) +{ + int value = 0; + if (ioctl(sock->fd, FIONREAD, &value) == 0) { + *npending = value; + return NT_STATUS_OK; + } + return map_nt_error_from_unix_common(errno); +} + +static const struct socket_ops ipv4_ops = { + .name = "ipv4", + .fn_init = ipv4_init, + .fn_connect = ipv4_connect, + .fn_connect_complete = ip_connect_complete, + .fn_listen = ipv4_listen, + .fn_accept = ipv4_accept, + .fn_recv = ip_recv, + .fn_recvfrom = ipv4_recvfrom, + .fn_send = ip_send, + .fn_sendto = ipv4_sendto, + .fn_pending = ip_pending, + .fn_close = ip_close, + + .fn_set_option = ipv4_set_option, + + .fn_get_peer_name = ipv4_get_peer_name, + .fn_get_peer_addr = ipv4_get_peer_addr, + .fn_get_my_addr = ipv4_get_my_addr, + + .fn_get_fd = ip_get_fd +}; + +_PUBLIC_ const struct socket_ops *socket_ipv4_ops(enum socket_type type) +{ + return &ipv4_ops; +} + +#ifdef HAVE_IPV6 + +static struct in6_addr interpret_addr6(const char *name) +{ + char addr[INET6_ADDRSTRLEN]; + struct in6_addr dest6; + const char *sp = name; + char *p; + int ret; + + if (sp == NULL) return in6addr_any; + + p = strchr_m(sp, '%'); + + if (strcasecmp(sp, "localhost") == 0) { + sp = "::1"; + } + + /* + * Cope with link-local. + * This is IP:v6:addr%ifname. + */ + + if (p && (p > sp) && (if_nametoindex(p+1) != 0)) { + strlcpy(addr, sp, + MIN(PTR_DIFF(p,sp)+1, + sizeof(addr))); + sp = addr; + } + + ret = inet_pton(AF_INET6, sp, &dest6); + if (ret > 0) { + return dest6; + } + + return in6addr_any; +} + +static NTSTATUS ipv6_init(struct socket_context *sock) +{ + int type; + + switch (sock->type) { + case SOCKET_TYPE_STREAM: + type = SOCK_STREAM; + break; + case SOCKET_TYPE_DGRAM: + type = SOCK_DGRAM; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + sock->fd = socket(PF_INET6, type, 0); + if (sock->fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + smb_set_close_on_exec(sock->fd); + + sock->backend_name = "ipv6"; + sock->family = AF_INET6; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_tcp_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *srv_address, + uint32_t flags) +{ + int ret; + + if (my_address && my_address->sockaddr) { + ret = bind(sock->fd, my_address->sockaddr, my_address->sockaddrlen); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } else if (my_address) { + struct in6_addr my_ip; + my_ip = interpret_addr6(my_address->addr); + + if (memcmp(&my_ip, &in6addr_any, sizeof(my_ip)) || my_address->port != 0) { + struct sockaddr_in6 my_addr; + ZERO_STRUCT(my_addr); + my_addr.sin6_addr = my_ip; + my_addr.sin6_port = htons(my_address->port); + my_addr.sin6_family = PF_INET6; + + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + } + + if (srv_address->sockaddr) { + ret = connect(sock->fd, srv_address->sockaddr, srv_address->sockaddrlen); + } else { + struct in6_addr srv_ip; + struct sockaddr_in6 srv_addr; + srv_ip = interpret_addr6(srv_address->addr); + if (memcmp(&srv_ip, &in6addr_any, sizeof(srv_ip)) == 0) { + return NT_STATUS_BAD_NETWORK_NAME; + } + + ZERO_STRUCT(srv_addr); + srv_addr.sin6_addr = srv_ip; + srv_addr.sin6_port = htons(srv_address->port); + srv_addr.sin6_family = PF_INET6; + + ret = connect(sock->fd, (const struct sockaddr *)&srv_addr, sizeof(srv_addr)); + } + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + return ip_connect_complete(sock, flags); +} + +/* + fix the sin6_scope_id based on the address interface + */ +static void fix_scope_id(struct sockaddr_in6 *in6, + const char *address) +{ + const char *p = strchr(address, '%'); + if (p != NULL) { + in6->sin6_scope_id = if_nametoindex(p+1); + } +} + + +static NTSTATUS ipv6_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags) +{ + struct sockaddr_in6 my_addr; + struct in6_addr ip_addr; + int ret; + + socket_set_option(sock, "SO_REUSEADDR=1", NULL); + + if (my_address->sockaddr) { + ret = bind(sock->fd, my_address->sockaddr, my_address->sockaddrlen); + } else { + int one = 1; + ip_addr = interpret_addr6(my_address->addr); + + ZERO_STRUCT(my_addr); + my_addr.sin6_addr = ip_addr; + my_addr.sin6_port = htons(my_address->port); + my_addr.sin6_family = PF_INET6; + fix_scope_id(&my_addr, my_address->addr); + + /* when binding on ipv6 we always want to only bind on v6 */ + ret = setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, + (const void *)&one, sizeof(one)); + if (ret != -1) { + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + } + } + + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + if (sock->type == SOCKET_TYPE_STREAM) { + ret = listen(sock->fd, queue_size); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + sock->state= SOCKET_STATE_SERVER_LISTEN; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_tcp_accept(struct socket_context *sock, struct socket_context **new_sock) +{ + struct sockaddr_in6 cli_addr; + socklen_t cli_addr_len = sizeof(cli_addr); + int new_fd, ret; + + if (sock->type != SOCKET_TYPE_STREAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + new_fd = accept(sock->fd, (struct sockaddr *)&cli_addr, &cli_addr_len); + if (new_fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + ret = set_blocking(new_fd, false); + if (ret == -1) { + close(new_fd); + return map_nt_error_from_unix_common(errno); + } + smb_set_close_on_exec(new_fd); + + /* TODO: we could add a 'accept_check' hook here + * which get the black/white lists via socket_set_accept_filter() + * or something like that + * --metze + */ + + (*new_sock) = talloc(NULL, struct socket_context); + if (!(*new_sock)) { + close(new_fd); + return NT_STATUS_NO_MEMORY; + } + + /* copy the socket_context */ + (*new_sock)->type = sock->type; + (*new_sock)->state = SOCKET_STATE_SERVER_CONNECTED; + (*new_sock)->flags = sock->flags; + + (*new_sock)->fd = new_fd; + + (*new_sock)->private_data = NULL; + (*new_sock)->ops = sock->ops; + (*new_sock)->backend_name = sock->backend_name; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_recvfrom(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread, + TALLOC_CTX *addr_ctx, struct socket_address **_src) +{ + ssize_t gotlen; + struct sockaddr_in6 *from_addr; + socklen_t from_len = sizeof(*from_addr); + struct socket_address *src; + char addrstring[INET6_ADDRSTRLEN]; + + src = talloc(addr_ctx, struct socket_address); + if (!src) { + return NT_STATUS_NO_MEMORY; + } + + src->family = sock->backend_name; + + from_addr = talloc(src, struct sockaddr_in6); + if (!from_addr) { + talloc_free(src); + return NT_STATUS_NO_MEMORY; + } + + src->sockaddr = (struct sockaddr *)from_addr; + + *nread = 0; + + gotlen = recvfrom(sock->fd, buf, wantlen, 0, + src->sockaddr, &from_len); + if (gotlen == 0) { + talloc_free(src); + return NT_STATUS_END_OF_FILE; + } else if (gotlen == -1) { + talloc_free(src); + return map_nt_error_from_unix_common(errno); + } + + src->sockaddrlen = from_len; + + if (inet_ntop(AF_INET6, &from_addr->sin6_addr, addrstring, sizeof(addrstring)) == NULL) { + DEBUG(0, ("Unable to convert address to string: %s\n", strerror(errno))); + talloc_free(src); + return NT_STATUS_INTERNAL_ERROR; + } + + src->addr = talloc_strdup(src, addrstring); + if (src->addr == NULL) { + talloc_free(src); + return NT_STATUS_NO_MEMORY; + } + src->port = ntohs(from_addr->sin6_port); + + *nread = gotlen; + *_src = src; + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr) +{ + ssize_t len; + + if (dest_addr->sockaddr) { + len = sendto(sock->fd, blob->data, blob->length, 0, + dest_addr->sockaddr, dest_addr->sockaddrlen); + } else { + struct sockaddr_in6 srv_addr; + struct in6_addr addr; + + ZERO_STRUCT(srv_addr); + addr = interpret_addr6(dest_addr->addr); + if (memcmp(&addr.s6_addr, &in6addr_any, + sizeof(addr.s6_addr)) == 0) { + return NT_STATUS_HOST_UNREACHABLE; + } + srv_addr.sin6_addr = addr; + srv_addr.sin6_port = htons(dest_addr->port); + srv_addr.sin6_family = PF_INET6; + + *sendlen = 0; + + len = sendto(sock->fd, blob->data, blob->length, 0, + (struct sockaddr *)&srv_addr, sizeof(srv_addr)); + } + if (len == -1) { + return map_nt_error_from_unix_common(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_set_option(struct socket_context *sock, const char *option, const char *val) +{ + set_socket_options(sock->fd, option); + return NT_STATUS_OK; +} + +static char *ipv6_tcp_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in6 peer_addr; + socklen_t len = sizeof(peer_addr); + struct hostent *he; + int ret; + + ret = getpeername(sock->fd, (struct sockaddr *)&peer_addr, &len); + if (ret == -1) { + return NULL; + } + + he = gethostbyaddr((char *)&peer_addr.sin6_addr, sizeof(peer_addr.sin6_addr), AF_INET6); + if (he == NULL) { + return NULL; + } + + return talloc_strdup(mem_ctx, he->h_name); +} + +static struct socket_address *ipv6_tcp_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in6 *peer_addr; + socklen_t len = sizeof(*peer_addr); + struct socket_address *peer; + int ret; + char addr[128]; + const char *addr_ret; + + peer = talloc(mem_ctx, struct socket_address); + if (!peer) { + return NULL; + } + + peer->family = sock->backend_name; + peer_addr = talloc(peer, struct sockaddr_in6); + if (!peer_addr) { + talloc_free(peer); + return NULL; + } + + peer->sockaddr = (struct sockaddr *)peer_addr; + + ret = getpeername(sock->fd, peer->sockaddr, &len); + if (ret == -1) { + talloc_free(peer); + return NULL; + } + + peer->sockaddrlen = len; + + addr_ret = inet_ntop(AF_INET6, &peer_addr->sin6_addr, addr, sizeof(addr)); + if (addr_ret == NULL) { + talloc_free(peer); + return NULL; + } + + peer->addr = talloc_strdup(peer, addr_ret); + if (peer->addr == NULL) { + talloc_free(peer); + return NULL; + } + + peer->port = ntohs(peer_addr->sin6_port); + + return peer; +} + +static struct socket_address *ipv6_tcp_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in6 *local_addr; + socklen_t len = sizeof(*local_addr); + struct socket_address *local; + int ret; + char addrstring[INET6_ADDRSTRLEN]; + + local = talloc(mem_ctx, struct socket_address); + if (!local) { + return NULL; + } + + local->family = sock->backend_name; + local_addr = talloc(local, struct sockaddr_in6); + if (!local_addr) { + talloc_free(local); + return NULL; + } + + local->sockaddr = (struct sockaddr *)local_addr; + + ret = getsockname(sock->fd, local->sockaddr, &len); + if (ret == -1) { + talloc_free(local); + return NULL; + } + + local->sockaddrlen = len; + + if (inet_ntop(AF_INET6, &local_addr->sin6_addr, addrstring, + sizeof(addrstring)) == NULL) { + DEBUG(0, ("Unable to convert address to string: %s\n", + strerror(errno))); + talloc_free(local); + return NULL; + } + + local->addr = talloc_strdup(local, addrstring); + if (!local->addr) { + talloc_free(local); + return NULL; + } + local->port = ntohs(local_addr->sin6_port); + + return local; +} + +static const struct socket_ops ipv6_tcp_ops = { + .name = "ipv6", + .fn_init = ipv6_init, + .fn_connect = ipv6_tcp_connect, + .fn_connect_complete = ip_connect_complete, + .fn_listen = ipv6_listen, + .fn_accept = ipv6_tcp_accept, + .fn_recv = ip_recv, + .fn_recvfrom = ipv6_recvfrom, + .fn_send = ip_send, + .fn_sendto = ipv6_sendto, + .fn_pending = ip_pending, + .fn_close = ip_close, + + .fn_set_option = ipv6_set_option, + + .fn_get_peer_name = ipv6_tcp_get_peer_name, + .fn_get_peer_addr = ipv6_tcp_get_peer_addr, + .fn_get_my_addr = ipv6_tcp_get_my_addr, + + .fn_get_fd = ip_get_fd +}; + +_PUBLIC_ const struct socket_ops *socket_ipv6_ops(enum socket_type type) +{ + return &ipv6_tcp_ops; +} + +#endif diff --git a/source4/lib/socket/socket_unix.c b/source4/lib/socket/socket_unix.c new file mode 100644 index 0000000..d4946d8 --- /dev/null +++ b/source4/lib/socket/socket_unix.c @@ -0,0 +1,436 @@ +/* + Unix SMB/CIFS implementation. + + unix domain socket functions + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Tridgell 2004-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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "system/network.h" +#include "system/filesys.h" + +_PUBLIC_ const struct socket_ops *socket_unixdom_ops(enum socket_type type); + + +/* + approximate errno mapping +*/ +static NTSTATUS unixdom_error(int ernum) +{ + return map_nt_error_from_unix_common(ernum); +} + +static NTSTATUS unixdom_init(struct socket_context *sock) +{ + int type; + + switch (sock->type) { + case SOCKET_TYPE_STREAM: + type = SOCK_STREAM; + break; + case SOCKET_TYPE_DGRAM: + type = SOCK_DGRAM; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + sock->fd = socket(PF_UNIX, type, 0); + if (sock->fd == -1) { + return map_nt_error_from_unix_common(errno); + } + sock->private_data = NULL; + + sock->backend_name = "unix"; + + smb_set_close_on_exec(sock->fd); + + return NT_STATUS_OK; +} + +static void unixdom_close(struct socket_context *sock) +{ + close(sock->fd); +} + +static NTSTATUS unixdom_connect_complete(struct socket_context *sock, uint32_t flags) +{ + int error=0, ret; + socklen_t len = sizeof(error); + + /* check for any errors that may have occurred - this is needed + for non-blocking connect */ + ret = getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &error, &len); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + if (error != 0) { + return map_nt_error_from_unix_common(error); + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + sock->state = SOCKET_STATE_CLIENT_CONNECTED; + + return NT_STATUS_OK; +} + +static NTSTATUS unixdom_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *srv_address, + uint32_t flags) +{ + int ret; + + if (srv_address->sockaddr) { + ret = connect(sock->fd, srv_address->sockaddr, srv_address->sockaddrlen); + } else { + struct sockaddr_un srv_addr; + if (strlen(srv_address->addr)+1 > sizeof(srv_addr.sun_path)) { + return NT_STATUS_OBJECT_PATH_INVALID; + } + + ZERO_STRUCT(srv_addr); + srv_addr.sun_family = AF_UNIX; + snprintf(srv_addr.sun_path, sizeof(srv_addr.sun_path), "%s", srv_address->addr); + + ret = connect(sock->fd, (const struct sockaddr *)&srv_addr, sizeof(srv_addr)); + } + if (ret == -1) { + return unixdom_error(errno); + } + + return unixdom_connect_complete(sock, flags); +} + +static NTSTATUS unixdom_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags) +{ + struct sockaddr_un my_addr; + int ret; + + /* delete if it already exists */ + if (my_address->addr) { + unlink(my_address->addr); + } + + if (my_address->sockaddr) { + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + } else if (my_address->addr == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } else { + if (strlen(my_address->addr)+1 > sizeof(my_addr.sun_path)) { + return NT_STATUS_OBJECT_PATH_INVALID; + } + + + ZERO_STRUCT(my_addr); + my_addr.sun_family = AF_UNIX; + snprintf(my_addr.sun_path, sizeof(my_addr.sun_path), "%s", my_address->addr); + + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + } + if (ret == -1) { + return unixdom_error(errno); + } + + if (sock->type == SOCKET_TYPE_STREAM) { + ret = listen(sock->fd, queue_size); + if (ret == -1) { + return unixdom_error(errno); + } + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return unixdom_error(errno); + } + + sock->state = SOCKET_STATE_SERVER_LISTEN; + sock->private_data = (void *)talloc_strdup(sock, my_address->addr); + + return NT_STATUS_OK; +} + +static NTSTATUS unixdom_accept(struct socket_context *sock, + struct socket_context **new_sock) +{ + struct sockaddr_un cli_addr; + socklen_t cli_addr_len = sizeof(cli_addr); + int new_fd, ret; + + if (sock->type != SOCKET_TYPE_STREAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + new_fd = accept(sock->fd, (struct sockaddr *)&cli_addr, &cli_addr_len); + if (new_fd == -1) { + return unixdom_error(errno); + } + + ret = set_blocking(new_fd, false); + if (ret == -1) { + close(new_fd); + return map_nt_error_from_unix_common(errno); + } + + smb_set_close_on_exec(new_fd); + + (*new_sock) = talloc(NULL, struct socket_context); + if (!(*new_sock)) { + close(new_fd); + return NT_STATUS_NO_MEMORY; + } + + /* copy the socket_context */ + (*new_sock)->type = sock->type; + (*new_sock)->state = SOCKET_STATE_SERVER_CONNECTED; + (*new_sock)->flags = sock->flags; + + (*new_sock)->fd = new_fd; + + (*new_sock)->private_data = NULL; + (*new_sock)->ops = sock->ops; + (*new_sock)->backend_name = sock->backend_name; + + return NT_STATUS_OK; +} + +static NTSTATUS unixdom_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread) +{ + ssize_t gotlen; + + *nread = 0; + + gotlen = recv(sock->fd, buf, wantlen, 0); + if (gotlen == 0) { + return NT_STATUS_END_OF_FILE; + } else if (gotlen == -1) { + return unixdom_error(errno); + } + + *nread = gotlen; + + return NT_STATUS_OK; +} + +static NTSTATUS unixdom_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen) +{ + ssize_t len; + + *sendlen = 0; + + len = send(sock->fd, blob->data, blob->length, 0); + if (len == -1) { + return unixdom_error(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + + +static NTSTATUS unixdom_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest) +{ + struct sockaddr_un srv_addr; + const struct sockaddr *sa; + socklen_t sa_len; + ssize_t len; + + *sendlen = 0; + + if (dest->sockaddr) { + sa = dest->sockaddr; + sa_len = dest->sockaddrlen; + } else { + if (strlen(dest->addr)+1 > sizeof(srv_addr.sun_path)) { + return NT_STATUS_OBJECT_PATH_INVALID; + } + + ZERO_STRUCT(srv_addr); + srv_addr.sun_family = AF_UNIX; + snprintf(srv_addr.sun_path, sizeof(srv_addr.sun_path), "%s", + dest->addr); + sa = (struct sockaddr *) &srv_addr; + sa_len = sizeof(srv_addr); + } + + len = sendto(sock->fd, blob->data, blob->length, 0, sa, sa_len); + + /* retry once */ + if (len == -1 && errno == EMSGSIZE) { + /* round up in 1K increments */ + int bufsize = ((blob->length + 1023) & (~1023)); + if (setsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF, &bufsize, + sizeof(bufsize)) == -1) + { + return map_nt_error_from_unix_common(EMSGSIZE); + } + len = sendto(sock->fd, blob->data, blob->length, 0, sa, sa_len); + } + + if (len == -1) { + return map_nt_error_from_unix_common(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + + +static NTSTATUS unixdom_set_option(struct socket_context *sock, + const char *option, const char *val) +{ + return NT_STATUS_OK; +} + +static char *unixdom_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + return talloc_strdup(mem_ctx, "LOCAL/unixdom"); +} + +static struct socket_address *unixdom_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_un *peer_addr; + socklen_t len = sizeof(*peer_addr); + struct socket_address *peer; + int ret; + + peer = talloc(mem_ctx, struct socket_address); + if (!peer) { + return NULL; + } + + peer->family = sock->backend_name; + peer_addr = talloc(peer, struct sockaddr_un); + if (!peer_addr) { + talloc_free(peer); + return NULL; + } + + peer->sockaddr = (struct sockaddr *)peer_addr; + + ret = getpeername(sock->fd, peer->sockaddr, &len); + if (ret == -1) { + talloc_free(peer); + return NULL; + } + + peer->sockaddrlen = len; + + peer->port = 0; + peer->addr = talloc_strdup(peer, "LOCAL/unixdom"); + if (!peer->addr) { + talloc_free(peer); + return NULL; + } + + return peer; +} + +static struct socket_address *unixdom_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_un *local_addr; + socklen_t len = sizeof(*local_addr); + struct socket_address *local; + int ret; + + local = talloc(mem_ctx, struct socket_address); + if (!local) { + return NULL; + } + + local->family = sock->backend_name; + local_addr = talloc(local, struct sockaddr_un); + if (!local_addr) { + talloc_free(local); + return NULL; + } + + local->sockaddr = (struct sockaddr *)local_addr; + + ret = getsockname(sock->fd, local->sockaddr, &len); + if (ret == -1) { + talloc_free(local); + return NULL; + } + + local->sockaddrlen = len; + + local->port = 0; + local->addr = talloc_strdup(local, "LOCAL/unixdom"); + if (!local->addr) { + talloc_free(local); + return NULL; + } + + return local; +} + +static int unixdom_get_fd(struct socket_context *sock) +{ + return sock->fd; +} + +static NTSTATUS unixdom_pending(struct socket_context *sock, size_t *npending) +{ + int value = 0; + if (ioctl(sock->fd, FIONREAD, &value) == 0) { + *npending = value; + return NT_STATUS_OK; + } + return map_nt_error_from_unix_common(errno); +} + +static const struct socket_ops unixdom_ops = { + .name = "unix", + .fn_init = unixdom_init, + .fn_connect = unixdom_connect, + .fn_connect_complete = unixdom_connect_complete, + .fn_listen = unixdom_listen, + .fn_accept = unixdom_accept, + .fn_recv = unixdom_recv, + .fn_send = unixdom_send, + .fn_sendto = unixdom_sendto, + .fn_close = unixdom_close, + .fn_pending = unixdom_pending, + + .fn_set_option = unixdom_set_option, + + .fn_get_peer_name = unixdom_get_peer_name, + .fn_get_peer_addr = unixdom_get_peer_addr, + .fn_get_my_addr = unixdom_get_my_addr, + + .fn_get_fd = unixdom_get_fd +}; + +_PUBLIC_ const struct socket_ops *socket_unixdom_ops(enum socket_type type) +{ + return &unixdom_ops; +} diff --git a/source4/lib/socket/testsuite.c b/source4/lib/socket/testsuite.c new file mode 100644 index 0000000..1df96e3 --- /dev/null +++ b/source4/lib/socket/testsuite.c @@ -0,0 +1,194 @@ +/* + Unix SMB/CIFS implementation. + + local testing of socket routines. + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "lib/events/events.h" +#include "system/network.h" +#include "lib/socket/netif.h" +#include "torture/torture.h" +#include "torture/local/proto.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" + +/** + basic testing of udp routines +*/ +static bool test_udp(struct torture_context *tctx) +{ + struct socket_context *sock1, *sock2; + NTSTATUS status; + struct socket_address *srv_addr, *from_addr, *localhost; + size_t size = 100 + (random() % 100); + DATA_BLOB blob, blob2; + size_t sent, nread; + TALLOC_CTX *mem_ctx = tctx; + struct interface *ifaces; + + load_interface_list(tctx, tctx->lp_ctx, &ifaces); + + status = socket_create(mem_ctx, "ip", SOCKET_TYPE_DGRAM, &sock1, 0); + torture_assert_ntstatus_ok(tctx, status, "creating DGRAM IP socket 1"); + + status = socket_create(mem_ctx, "ip", SOCKET_TYPE_DGRAM, &sock2, 0); + torture_assert_ntstatus_ok(tctx, status, "creating DGRAM IP socket 1"); + + localhost = socket_address_from_strings(sock1, sock1->backend_name, + iface_list_best_ip(ifaces, "127.0.0.1"), 0); + + torture_assert(tctx, localhost, "Localhost not found"); + + status = socket_listen(sock1, localhost, 0, 0); + torture_assert_ntstatus_ok(tctx, status, "listen on socket 1"); + + srv_addr = socket_get_my_addr(sock1, mem_ctx); + torture_assert(tctx, srv_addr != NULL && + strcmp(srv_addr->addr, iface_list_best_ip(ifaces, "127.0.0.1")) == 0, + talloc_asprintf(tctx, + "Expected server address of %s but got %s", + iface_list_best_ip(ifaces, "127.0.0.1"), srv_addr ? srv_addr->addr : NULL)); + + torture_comment(tctx, "server port is %d\n", srv_addr->port); + + blob = data_blob_talloc(mem_ctx, NULL, size); + blob2 = data_blob_talloc(mem_ctx, NULL, size); + generate_random_buffer(blob.data, blob.length); + + sent = size; + status = socket_sendto(sock2, &blob, &sent, srv_addr); + torture_assert_ntstatus_ok(tctx, status, "sendto() on socket 2"); + + status = socket_recvfrom(sock1, blob2.data, size, &nread, + sock1, &from_addr); + torture_assert_ntstatus_ok(tctx, status, "recvfrom() on socket 1"); + + torture_assert_str_equal(tctx, from_addr->addr, srv_addr->addr, + "different address"); + + torture_assert_int_equal(tctx, nread, size, "Unexpected recvfrom size"); + + torture_assert_mem_equal(tctx, blob2.data, blob.data, size, + "Bad data in recvfrom"); + + generate_random_buffer(blob.data, blob.length); + status = socket_sendto(sock1, &blob, &sent, from_addr); + torture_assert_ntstatus_ok(tctx, status, "sendto() on socket 1"); + + status = socket_recvfrom(sock2, blob2.data, size, &nread, + sock2, &from_addr); + torture_assert_ntstatus_ok(tctx, status, "recvfrom() on socket 2"); + torture_assert_str_equal(tctx, from_addr->addr, srv_addr->addr, + "Unexpected recvfrom addr"); + + torture_assert_int_equal(tctx, nread, size, "Unexpected recvfrom size"); + + torture_assert_int_equal(tctx, from_addr->port, srv_addr->port, + "Unexpected recvfrom port"); + + torture_assert_mem_equal(tctx, blob2.data, blob.data, size, + "Bad data in recvfrom"); + + talloc_free(sock1); + talloc_free(sock2); + return true; +} + +/* + basic testing of tcp routines +*/ +static bool test_tcp(struct torture_context *tctx) +{ + struct socket_context *sock1, *sock2, *sock3; + NTSTATUS status; + struct socket_address *srv_addr, *from_addr, *localhost; + size_t size = 100 + (random() % 100); + DATA_BLOB blob, blob2; + size_t sent, nread; + TALLOC_CTX *mem_ctx = tctx; + struct tevent_context *ev = tctx->ev; + struct interface *ifaces; + + status = socket_create(mem_ctx, "ip", SOCKET_TYPE_STREAM, &sock1, 0); + torture_assert_ntstatus_ok(tctx, status, "creating IP stream socket 1"); + + status = socket_create(mem_ctx, "ip", SOCKET_TYPE_STREAM, &sock2, 0); + torture_assert_ntstatus_ok(tctx, status, "creating IP stream socket 1"); + + load_interface_list(tctx, tctx->lp_ctx, &ifaces); + localhost = socket_address_from_strings(sock1, sock1->backend_name, + iface_list_best_ip(ifaces, "127.0.0.1"), 0); + torture_assert(tctx, localhost, "Localhost not found"); + + status = socket_listen(sock1, localhost, 0, 0); + torture_assert_ntstatus_ok(tctx, status, "listen on socket 1"); + + srv_addr = socket_get_my_addr(sock1, mem_ctx); + torture_assert(tctx, srv_addr && srv_addr->addr, + "Unexpected socket_get_my_addr NULL\n"); + + torture_assert_str_equal(tctx, srv_addr->addr, iface_list_best_ip(ifaces, "127.0.0.1"), + "Unexpected server address"); + + torture_comment(tctx, "server port is %d\n", srv_addr->port); + + status = socket_connect_ev(sock2, NULL, srv_addr, 0, ev); + torture_assert_ntstatus_ok(tctx, status, "connect() on socket 2"); + + status = socket_accept(sock1, &sock3); + torture_assert_ntstatus_ok(tctx, status, "accept() on socket 1"); + talloc_steal(mem_ctx, sock3); + talloc_free(sock1); + + blob = data_blob_talloc(mem_ctx, NULL, size); + blob2 = data_blob_talloc(mem_ctx, NULL, size); + generate_random_buffer(blob.data, blob.length); + + sent = size; + status = socket_send(sock2, &blob, &sent); + torture_assert_ntstatus_ok(tctx, status, "send() on socket 2"); + + status = socket_recv(sock3, blob2.data, size, &nread); + torture_assert_ntstatus_ok(tctx, status, "recv() on socket 3"); + + from_addr = socket_get_peer_addr(sock3, mem_ctx); + + torture_assert(tctx, from_addr && from_addr->addr, + "Unexpected recvfrom addr NULL"); + + torture_assert_str_equal(tctx, from_addr->addr, srv_addr->addr, + "Unexpected recvfrom addr"); + + torture_assert_int_equal(tctx, nread, size, "Unexpected recvfrom size"); + + torture_assert_mem_equal(tctx, blob2.data, blob.data, size, + "Bad data in recv"); + return true; +} + +struct torture_suite *torture_local_socket(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "socket"); + + torture_suite_add_simple_test(suite, "udp", test_udp); + torture_suite_add_simple_test(suite, "tcp", test_tcp); + + return suite; +} diff --git a/source4/lib/socket/wscript_build b/source4/lib/socket/wscript_build new file mode 100644 index 0000000..e243824 --- /dev/null +++ b/source4/lib/socket/wscript_build @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +bld.SAMBA_LIBRARY('netif', + source='interface.c', + deps='samba-util interfaces samba-hostconfig', + private_library=True, + autoproto='netif_proto.h' + ) + +bld.SAMBA_MODULE('socket_ip', + source='socket_ip.c', + subsystem='samba_socket', + deps='samba-errors', + internal_module=True + ) + +bld.SAMBA_MODULE('socket_unix', + source='socket_unix.c', + subsystem='samba_socket', + deps='talloc', + internal_module=True + ) + +bld.SAMBA_SUBSYSTEM('samba_socket', + source='socket.c access.c connect_multi.c connect.c', + public_deps='talloc LIBTSOCKET', + deps='cli_composite LIBCLI_RESOLVE socket_ip socket_unix access' + ) + diff --git a/source4/lib/stream/packet.c b/source4/lib/stream/packet.c new file mode 100644 index 0000000..6f4bfaa --- /dev/null +++ b/source4/lib/stream/packet.c @@ -0,0 +1,614 @@ +/* + Unix SMB/CIFS Implementation. + + helper layer for breaking up streams into discrete requests + + 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 3 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, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "../lib/util/dlinklist.h" +#include "lib/events/events.h" +#include "lib/socket/socket.h" +#include "lib/stream/packet.h" +#include "libcli/raw/smb.h" + +struct packet_context { + packet_callback_fn_t callback; + packet_full_request_fn_t full_request; + packet_error_handler_fn_t error_handler; + DATA_BLOB partial; + uint32_t num_read; + uint32_t initial_read; + struct socket_context *sock; + struct tevent_context *ev; + size_t packet_size; + void *private_data; + struct tevent_fd *fde; + bool serialise; + int processing; + bool recv_disable; + bool recv_need_enable; + bool nofree; + + bool busy; + bool destructor_called; + + bool unreliable_select; + + struct send_element { + struct send_element *next, *prev; + DATA_BLOB blob; + size_t nsent; + packet_send_callback_fn_t send_callback; + void *send_callback_private; + } *send_queue; +}; + +/* + a destructor used when we are processing packets to prevent freeing of this + context while it is being used +*/ +static int packet_destructor(struct packet_context *pc) +{ + if (pc->busy) { + pc->destructor_called = true; + /* now we refuse the talloc_free() request. The free will + happen again in the packet_recv() code */ + return -1; + } + + return 0; +} + + +/* + initialise a packet receiver +*/ +_PUBLIC_ struct packet_context *packet_init(TALLOC_CTX *mem_ctx) +{ + struct packet_context *pc = talloc_zero(mem_ctx, struct packet_context); + if (pc != NULL) { + talloc_set_destructor(pc, packet_destructor); + } + return pc; +} + + +/* + set the request callback, called when a full request is ready +*/ +_PUBLIC_ void packet_set_callback(struct packet_context *pc, packet_callback_fn_t callback) +{ + pc->callback = callback; +} + +/* + set the error handler +*/ +_PUBLIC_ void packet_set_error_handler(struct packet_context *pc, packet_error_handler_fn_t handler) +{ + pc->error_handler = handler; +} + +/* + set the private pointer passed to the callback functions +*/ +_PUBLIC_ void packet_set_private(struct packet_context *pc, void *private_data) +{ + pc->private_data = private_data; +} + +/* + set the full request callback. Should return as follows: + NT_STATUS_OK == blob is a full request. + STATUS_MORE_ENTRIES == blob is not complete yet + any error == blob is not a valid +*/ +_PUBLIC_ void packet_set_full_request(struct packet_context *pc, packet_full_request_fn_t callback) +{ + pc->full_request = callback; +} + +/* + set a socket context to use. You must set a socket_context +*/ +_PUBLIC_ void packet_set_socket(struct packet_context *pc, struct socket_context *sock) +{ + pc->sock = sock; +} + +/* + set an event context. If this is set then the code will ensure that + packets arrive with separate events, by creating a immediate event + for any secondary packets when more than one packet is read at one + time on a socket. This can matter for code that relies on not + getting more than one packet per event +*/ +_PUBLIC_ void packet_set_event_context(struct packet_context *pc, struct tevent_context *ev) +{ + pc->ev = ev; +} + +/* + tell the packet layer the fde for the socket +*/ +_PUBLIC_ void packet_set_fde(struct packet_context *pc, struct tevent_fd *fde) +{ + pc->fde = fde; +} + +/* + tell the packet layer to serialise requests, so we don't process two + requests at once on one connection. You must have set the + event_context and fde +*/ +_PUBLIC_ void packet_set_serialise(struct packet_context *pc) +{ + pc->serialise = true; +} + +/* + tell the packet layer how much to read when starting a new packet + this ensures it doesn't overread +*/ +_PUBLIC_ void packet_set_initial_read(struct packet_context *pc, uint32_t initial_read) +{ + pc->initial_read = initial_read; +} + +/* + tell the packet system not to steal/free blobs given to packet_send() +*/ +_PUBLIC_ void packet_set_nofree(struct packet_context *pc) +{ + pc->nofree = true; +} + +/* + tell the packet system that select/poll/epoll on the underlying + socket may not be a reliable way to determine if data is available + for receive. This happens with underlying socket systems such as the + one implemented on top of GNUTLS, where there may be data in + encryption/compression buffers that could be received by + socket_recv(), while there is no data waiting at the real socket + level as seen by select/poll/epoll. The GNUTLS library is supposed + to cope with this by always leaving some data sitting in the socket + buffer, but it does not seem to be reliable. + */ +_PUBLIC_ void packet_set_unreliable_select(struct packet_context *pc) +{ + pc->unreliable_select = true; +} + +/* + tell the caller we have an error +*/ +static void packet_error(struct packet_context *pc, NTSTATUS status) +{ + pc->sock = NULL; + if (pc->error_handler) { + pc->error_handler(pc->private_data, status); + return; + } + /* default error handler is to free the callers private pointer */ + if (!NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) { + DEBUG(0,("packet_error on %s - %s\n", + talloc_get_name(pc->private_data), nt_errstr(status))); + } + talloc_free(pc->private_data); + return; +} + + +/* + tell the caller we have EOF +*/ +static void packet_eof(struct packet_context *pc) +{ + packet_error(pc, NT_STATUS_END_OF_FILE); +} + + +/* + used to put packets on event boundaries +*/ +static void packet_next_event(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *private_data) +{ + struct packet_context *pc = talloc_get_type(private_data, struct packet_context); + if (pc->num_read != 0 && pc->packet_size != 0 && + pc->packet_size <= pc->num_read) { + packet_recv(pc); + } +} + + +/* + call this when the socket becomes readable to kick off the whole + stream parsing process +*/ +_PUBLIC_ void packet_recv(struct packet_context *pc) +{ + size_t npending; + NTSTATUS status; + size_t nread = 0; + DATA_BLOB blob; + bool recv_retry = false; + + if (pc->processing) { + TEVENT_FD_NOT_READABLE(pc->fde); + pc->processing++; + return; + } + + if (pc->recv_disable) { + pc->recv_need_enable = true; + TEVENT_FD_NOT_READABLE(pc->fde); + return; + } + + if (pc->packet_size != 0 && pc->num_read >= pc->packet_size) { + goto next_partial; + } + + if (pc->packet_size != 0) { + /* we've already worked out how long this next packet is, so skip the + socket_pending() call */ + npending = pc->packet_size - pc->num_read; + } else if (pc->initial_read != 0) { + npending = pc->initial_read - pc->num_read; + } else { + if (pc->sock) { + status = socket_pending(pc->sock, &npending); + } else { + status = NT_STATUS_CONNECTION_DISCONNECTED; + } + if (!NT_STATUS_IS_OK(status)) { + packet_error(pc, status); + return; + } + } + + if (npending == 0) { + packet_eof(pc); + return; + } + +again: + + if (npending + pc->num_read < npending) { + packet_error(pc, NT_STATUS_INVALID_PARAMETER); + return; + } + + if (npending + pc->num_read < pc->num_read) { + packet_error(pc, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* possibly expand the partial packet buffer */ + if (npending + pc->num_read > pc->partial.length) { + if (!data_blob_realloc(pc, &pc->partial, npending+pc->num_read)) { + packet_error(pc, NT_STATUS_NO_MEMORY); + return; + } + } + + if (pc->partial.length < pc->num_read + npending) { + packet_error(pc, NT_STATUS_INVALID_PARAMETER); + return; + } + + if ((uint8_t *)pc->partial.data + pc->num_read < (uint8_t *)pc->partial.data) { + packet_error(pc, NT_STATUS_INVALID_PARAMETER); + return; + } + if ((uint8_t *)pc->partial.data + pc->num_read + npending < (uint8_t *)pc->partial.data) { + packet_error(pc, NT_STATUS_INVALID_PARAMETER); + return; + } + + status = socket_recv(pc->sock, pc->partial.data + pc->num_read, + npending, &nread); + + if (NT_STATUS_IS_ERR(status)) { + packet_error(pc, status); + return; + } + if (recv_retry && NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { + nread = 0; + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + return; + } + + if (nread == 0 && !recv_retry) { + packet_eof(pc); + return; + } + + pc->num_read += nread; + + if (pc->unreliable_select && nread != 0) { + recv_retry = true; + status = socket_pending(pc->sock, &npending); + if (!NT_STATUS_IS_OK(status)) { + packet_error(pc, status); + return; + } + if (npending != 0) { + goto again; + } + } + +next_partial: + if (pc->partial.length != pc->num_read) { + if (!data_blob_realloc(pc, &pc->partial, pc->num_read)) { + packet_error(pc, NT_STATUS_NO_MEMORY); + return; + } + } + + /* see if its a full request */ + blob = pc->partial; + blob.length = pc->num_read; + status = pc->full_request(pc->private_data, blob, &pc->packet_size); + if (NT_STATUS_IS_ERR(status)) { + packet_error(pc, status); + return; + } + if (!NT_STATUS_IS_OK(status)) { + return; + } + + if (pc->packet_size > pc->num_read) { + /* the caller made an error */ + DEBUG(0,("Invalid packet_size %lu greater than num_read %lu\n", + (long)pc->packet_size, (long)pc->num_read)); + packet_error(pc, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* it is a full request - give it to the caller */ + blob = pc->partial; + blob.length = pc->num_read; + + if (pc->packet_size < pc->num_read) { + pc->partial = data_blob_talloc(pc, blob.data + pc->packet_size, + pc->num_read - pc->packet_size); + if (pc->partial.data == NULL) { + packet_error(pc, NT_STATUS_NO_MEMORY); + return; + } + /* Trunate the blob sent to the caller to only the packet length */ + if (!data_blob_realloc(pc, &blob, pc->packet_size)) { + packet_error(pc, NT_STATUS_NO_MEMORY); + return; + } + } else { + pc->partial = data_blob(NULL, 0); + } + pc->num_read -= pc->packet_size; + pc->packet_size = 0; + + if (pc->serialise) { + pc->processing = 1; + } + + pc->busy = true; + + status = pc->callback(pc->private_data, blob); + + pc->busy = false; + + if (pc->destructor_called) { + talloc_free(pc); + return; + } + + if (pc->processing) { + if (pc->processing > 1) { + TEVENT_FD_READABLE(pc->fde); + } + pc->processing = 0; + } + + if (!NT_STATUS_IS_OK(status)) { + packet_error(pc, status); + return; + } + + /* Have we consumed the whole buffer yet? */ + if (pc->partial.length == 0) { + return; + } + + /* we got multiple packets in one tcp read */ + if (pc->ev == NULL) { + goto next_partial; + } + + blob = pc->partial; + blob.length = pc->num_read; + + status = pc->full_request(pc->private_data, blob, &pc->packet_size); + if (NT_STATUS_IS_ERR(status)) { + packet_error(pc, status); + return; + } + + if (!NT_STATUS_IS_OK(status)) { + return; + } + + tevent_add_timer(pc->ev, pc, timeval_zero(), packet_next_event, pc); +} + + +/* + temporarily disable receiving +*/ +_PUBLIC_ void packet_recv_disable(struct packet_context *pc) +{ + pc->recv_disable = true; +} + +/* + re-enable receiving +*/ +_PUBLIC_ void packet_recv_enable(struct packet_context *pc) +{ + if (pc->recv_need_enable) { + pc->recv_need_enable = false; + TEVENT_FD_READABLE(pc->fde); + } + pc->recv_disable = false; + if (pc->num_read != 0 && pc->packet_size >= pc->num_read) { + tevent_add_timer(pc->ev, pc, timeval_zero(), packet_next_event, pc); + } +} + +/* + trigger a run of the send queue +*/ +_PUBLIC_ void packet_queue_run(struct packet_context *pc) +{ + while (pc->send_queue) { + struct send_element *el = pc->send_queue; + NTSTATUS status; + size_t nwritten; + DATA_BLOB blob = data_blob_const(el->blob.data + el->nsent, + el->blob.length - el->nsent); + + status = socket_send(pc->sock, &blob, &nwritten); + + if (NT_STATUS_IS_ERR(status)) { + packet_error(pc, status); + return; + } + if (!NT_STATUS_IS_OK(status)) { + return; + } + el->nsent += nwritten; + if (el->nsent == el->blob.length) { + DLIST_REMOVE(pc->send_queue, el); + if (el->send_callback) { + pc->busy = true; + el->send_callback(el->send_callback_private); + pc->busy = false; + if (pc->destructor_called) { + talloc_free(pc); + return; + } + } + talloc_free(el); + } + } + + /* we're out of requests to send, so don't wait for write + events any more */ + TEVENT_FD_NOT_WRITEABLE(pc->fde); +} + +/* + put a packet in the send queue. When the packet is actually sent, + call send_callback. + + Useful for operations that must occur after sending a message, such + as the switch to SASL encryption after as successful LDAP bind reply. +*/ +_PUBLIC_ NTSTATUS packet_send_callback(struct packet_context *pc, DATA_BLOB blob, + packet_send_callback_fn_t send_callback, + void *private_data) +{ + struct send_element *el; + el = talloc(pc, struct send_element); + NT_STATUS_HAVE_NO_MEMORY(el); + + DLIST_ADD_END(pc->send_queue, el); + el->blob = blob; + el->nsent = 0; + el->send_callback = send_callback; + el->send_callback_private = private_data; + + /* if we aren't going to free the packet then we must reference it + to ensure it doesn't disappear before going out */ + if (pc->nofree) { + if (!talloc_reference(el, blob.data)) { + return NT_STATUS_NO_MEMORY; + } + } else { + talloc_steal(el, blob.data); + } + + if (private_data && !talloc_reference(el, private_data)) { + return NT_STATUS_NO_MEMORY; + } + + TEVENT_FD_WRITEABLE(pc->fde); + + return NT_STATUS_OK; +} + +/* + put a packet in the send queue +*/ +_PUBLIC_ NTSTATUS packet_send(struct packet_context *pc, DATA_BLOB blob) +{ + return packet_send_callback(pc, blob, NULL, NULL); +} + + +/* + a full request checker for NBT formatted packets (first 3 bytes are length) +*/ +_PUBLIC_ NTSTATUS packet_full_request_nbt(void *private_data, DATA_BLOB blob, size_t *size) +{ + if (blob.length < 4) { + return STATUS_MORE_ENTRIES; + } + /* + * Note: that we use smb_len_tcp() instead + * of smb_len_nbt() as this function is not + * used for nbt and the source4 copy + * of smb_len() was smb_len_tcp() + */ + *size = 4 + smb_len_tcp(blob.data); + if (*size > blob.length) { + return STATUS_MORE_ENTRIES; + } + return NT_STATUS_OK; +} + + +/* + work out if a packet is complete for protocols that use a 32 bit network byte + order length +*/ +_PUBLIC_ NTSTATUS packet_full_request_u32(void *private_data, DATA_BLOB blob, size_t *size) +{ + if (blob.length < 4) { + return STATUS_MORE_ENTRIES; + } + *size = 4 + RIVAL(blob.data, 0); + if (*size > blob.length) { + return STATUS_MORE_ENTRIES; + } + return NT_STATUS_OK; +} diff --git a/source4/lib/stream/packet.h b/source4/lib/stream/packet.h new file mode 100644 index 0000000..afd0591 --- /dev/null +++ b/source4/lib/stream/packet.h @@ -0,0 +1,65 @@ +/* + Unix SMB/CIFS Implementation. + + helper layer for breaking up streams into discrete requests + + 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 3 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, see <http://www.gnu.org/licenses/>. + +*/ + +struct packet_context; +struct tevent_context; +struct tevent_fd; +struct socket_context; + +typedef NTSTATUS (*packet_full_request_fn_t)(void *private_data, + DATA_BLOB blob, size_t *packet_size); +typedef NTSTATUS (*packet_callback_fn_t)(void *private_data, DATA_BLOB blob); + +/* Used to notify that a packet has been sent, and is on the wire */ +typedef void (*packet_send_callback_fn_t)(void *private_data); +typedef void (*packet_error_handler_fn_t)(void *private_data, NTSTATUS status); + + + +struct packet_context *packet_init(TALLOC_CTX *mem_ctx); +void packet_set_callback(struct packet_context *pc, packet_callback_fn_t callback); +void packet_set_error_handler(struct packet_context *pc, packet_error_handler_fn_t handler); +void packet_set_private(struct packet_context *pc, void *private_data); +void packet_set_full_request(struct packet_context *pc, packet_full_request_fn_t callback); +void packet_set_socket(struct packet_context *pc, struct socket_context *sock); +void packet_set_event_context(struct packet_context *pc, struct tevent_context *ev); +void packet_set_fde(struct packet_context *pc, struct tevent_fd *fde); +void packet_set_serialise(struct packet_context *pc); +void packet_set_initial_read(struct packet_context *pc, uint32_t initial_read); +void packet_set_nofree(struct packet_context *pc); +void packet_recv(struct packet_context *pc); +void packet_recv_disable(struct packet_context *pc); +void packet_recv_enable(struct packet_context *pc); +void packet_set_unreliable_select(struct packet_context *pc); +NTSTATUS packet_send(struct packet_context *pc, DATA_BLOB blob); +NTSTATUS packet_send_callback(struct packet_context *pc, DATA_BLOB blob, + packet_send_callback_fn_t send_callback, + void *private_data); +void packet_queue_run(struct packet_context *pc); + +/* + pre-canned handlers +*/ +NTSTATUS packet_full_request_nbt(void *private_data, DATA_BLOB blob, size_t *size); +NTSTATUS packet_full_request_u32(void *private_data, DATA_BLOB blob, size_t *size); + + diff --git a/source4/lib/stream/wscript_build b/source4/lib/stream/wscript_build new file mode 100644 index 0000000..8427c17 --- /dev/null +++ b/source4/lib/stream/wscript_build @@ -0,0 +1,8 @@ +#!/usr/bin/env python + + +bld.SAMBA_SUBSYSTEM('LIBPACKET', + source='packet.c', + deps='LIBTLS' + ) + diff --git a/source4/lib/tls/tls.h b/source4/lib/tls/tls.h new file mode 100644 index 0000000..d9b18ff --- /dev/null +++ b/source4/lib/tls/tls.h @@ -0,0 +1,105 @@ +/* + Unix SMB/CIFS implementation. + + transport layer security handling code + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _TLS_H_ +#define _TLS_H_ + +#include "lib/socket/socket.h" + +struct loadparm_context; + +void tls_cert_generate(TALLOC_CTX *mem_ctx, + const char *hostname, + const char *keyfile, const char *certfile, + const char *cafile); + +struct tstream_context; +struct tstream_tls_params; + +enum tls_verify_peer_state { + TLS_VERIFY_PEER_NO_CHECK = 0, +#define TLS_VERIFY_PEER_NO_CHECK_STRING "no_check" + + TLS_VERIFY_PEER_CA_ONLY = 10, +#define TLS_VERIFY_PEER_CA_ONLY_STRING "ca_only" + + TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE = 20, +#define TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE_STRING \ + "ca_and_name_if_available" + + TLS_VERIFY_PEER_CA_AND_NAME = 30, +#define TLS_VERIFY_PEER_CA_AND_NAME_STRING "ca_and_name" + + TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE = 9999, +#define TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE_STRING \ + "as_strict_as_possible" +}; + +const char *tls_verify_peer_string(enum tls_verify_peer_state verify_peer); + +NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx, + const char *ca_file, + const char *crl_file, + const char *tls_priority, + enum tls_verify_peer_state verify_peer, + const char *peer_name, + struct tstream_tls_params **_tlsp); + +NTSTATUS tstream_tls_params_server(TALLOC_CTX *mem_ctx, + const char *dns_host_name, + bool enabled, + const char *key_file, + const char *cert_file, + const char *ca_file, + const char *crl_file, + const char *dhp_file, + const char *tls_priority, + struct tstream_tls_params **_params); + +bool tstream_tls_params_enabled(struct tstream_tls_params *params); + +struct tevent_req *_tstream_tls_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *tls_params, + const char *location); +#define tstream_tls_connect_send(mem_ctx, ev, plain_stream, tls_params) \ + _tstream_tls_connect_send(mem_ctx, ev, plain_stream, tls_params, __location__) + +int tstream_tls_connect_recv(struct tevent_req *req, + int *perrno, + TALLOC_CTX *mem_ctx, + struct tstream_context **tls_stream); + +struct tevent_req *_tstream_tls_accept_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *tls_params, + const char *location); +#define tstream_tls_accept_send(mem_ctx, ev, plain_stream, tls_params) \ + _tstream_tls_accept_send(mem_ctx, ev, plain_stream, tls_params, __location__) + +int tstream_tls_accept_recv(struct tevent_req *req, + int *perrno, + TALLOC_CTX *mem_ctx, + struct tstream_context **tls_stream); + +#endif /* _TLS_H_ */ diff --git a/source4/lib/tls/tls_tstream.c b/source4/lib/tls/tls_tstream.c new file mode 100644 index 0000000..5e0c56b --- /dev/null +++ b/source4/lib/tls/tls_tstream.c @@ -0,0 +1,1550 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2010 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "system/filesys.h" +#include "system/time.h" +#include "../util/tevent_unix.h" +#include "../lib/tsocket/tsocket.h" +#include "../lib/tsocket/tsocket_internal.h" +#include "../lib/util/util_net.h" +#include "lib/tls/tls.h" + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#define DH_BITS 2048 + +const char *tls_verify_peer_string(enum tls_verify_peer_state verify_peer) +{ + switch (verify_peer) { + case TLS_VERIFY_PEER_NO_CHECK: + return TLS_VERIFY_PEER_NO_CHECK_STRING; + + case TLS_VERIFY_PEER_CA_ONLY: + return TLS_VERIFY_PEER_CA_ONLY_STRING; + + case TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE: + return TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE_STRING; + + case TLS_VERIFY_PEER_CA_AND_NAME: + return TLS_VERIFY_PEER_CA_AND_NAME_STRING; + + case TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE: + return TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE_STRING; + } + + return "unknown tls_verify_peer_state"; +} + +static const struct tstream_context_ops tstream_tls_ops; + +struct tstream_tls { + struct tstream_context *plain_stream; + int error; + + gnutls_session_t tls_session; + + enum tls_verify_peer_state verify_peer; + const char *peer_name; + + struct tevent_context *current_ev; + + struct tevent_immediate *retry_im; + + struct { + uint8_t *buf; + off_t ofs; + struct iovec iov; + struct tevent_req *subreq; + struct tevent_immediate *im; + } push; + + struct { + uint8_t *buf; + struct iovec iov; + struct tevent_req *subreq; + } pull; + + struct { + struct tevent_req *req; + } handshake; + + struct { + off_t ofs; + size_t left; + uint8_t buffer[1024]; + struct tevent_req *req; + } write; + + struct { + off_t ofs; + size_t left; + uint8_t buffer[1024]; + struct tevent_req *req; + } read; + + struct { + struct tevent_req *req; + } disconnect; +}; + +static void tstream_tls_retry_handshake(struct tstream_context *stream); +static void tstream_tls_retry_read(struct tstream_context *stream); +static void tstream_tls_retry_write(struct tstream_context *stream); +static void tstream_tls_retry_disconnect(struct tstream_context *stream); +static void tstream_tls_retry_trigger(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data); + +static void tstream_tls_retry(struct tstream_context *stream, bool deferred) +{ + + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + + if (tlss->disconnect.req) { + tstream_tls_retry_disconnect(stream); + return; + } + + if (tlss->handshake.req) { + tstream_tls_retry_handshake(stream); + return; + } + + if (tlss->write.req && tlss->read.req && !deferred) { + tevent_schedule_immediate(tlss->retry_im, tlss->current_ev, + tstream_tls_retry_trigger, + stream); + } + + if (tlss->write.req) { + tstream_tls_retry_write(stream); + return; + } + + if (tlss->read.req) { + tstream_tls_retry_read(stream); + return; + } +} + +static void tstream_tls_retry_trigger(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data) +{ + struct tstream_context *stream = + talloc_get_type_abort(private_data, + struct tstream_context); + + tstream_tls_retry(stream, true); +} + +static void tstream_tls_push_trigger_write(struct tevent_context *ev, + struct tevent_immediate *im, + void *private_data); + +static ssize_t tstream_tls_push_function(gnutls_transport_ptr_t ptr, + const void *buf, size_t size) +{ + struct tstream_context *stream = + talloc_get_type_abort(ptr, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + uint8_t *nbuf; + size_t len; + + if (tlss->error != 0) { + errno = tlss->error; + return -1; + } + + if (tlss->push.subreq) { + errno = EAGAIN; + return -1; + } + + len = MIN(size, UINT16_MAX - tlss->push.ofs); + + if (len == 0) { + errno = EAGAIN; + return -1; + } + + nbuf = talloc_realloc(tlss, tlss->push.buf, + uint8_t, tlss->push.ofs + len); + if (nbuf == NULL) { + if (tlss->push.buf) { + errno = EAGAIN; + return -1; + } + + return -1; + } + tlss->push.buf = nbuf; + + memcpy(tlss->push.buf + tlss->push.ofs, buf, len); + + if (tlss->push.im == NULL) { + tlss->push.im = tevent_create_immediate(tlss); + if (tlss->push.im == NULL) { + errno = ENOMEM; + return -1; + } + } + + if (tlss->push.ofs == 0) { + /* + * We'll do start the tstream_writev + * in the next event cycle. + * + * This way we can batch all push requests, + * if they fit into a UINT16_MAX buffer. + * + * This is important as gnutls_handshake() + * had a bug in some versions e.g. 2.4.1 + * and others (See bug #7218) and it doesn't + * handle EAGAIN. + */ + tevent_schedule_immediate(tlss->push.im, + tlss->current_ev, + tstream_tls_push_trigger_write, + stream); + } + + tlss->push.ofs += len; + return len; +} + +static void tstream_tls_push_done(struct tevent_req *subreq); + +static void tstream_tls_push_trigger_write(struct tevent_context *ev, + struct tevent_immediate *im, + void *private_data) +{ + struct tstream_context *stream = + talloc_get_type_abort(private_data, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *subreq; + + if (tlss->push.subreq) { + /* nothing todo */ + return; + } + + tlss->push.iov.iov_base = (char *)tlss->push.buf; + tlss->push.iov.iov_len = tlss->push.ofs; + + subreq = tstream_writev_send(tlss, + tlss->current_ev, + tlss->plain_stream, + &tlss->push.iov, 1); + if (subreq == NULL) { + tlss->error = ENOMEM; + tstream_tls_retry(stream, false); + return; + } + tevent_req_set_callback(subreq, tstream_tls_push_done, stream); + + tlss->push.subreq = subreq; +} + +static void tstream_tls_push_done(struct tevent_req *subreq) +{ + struct tstream_context *stream = + tevent_req_callback_data(subreq, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + int ret; + int sys_errno; + + tlss->push.subreq = NULL; + ZERO_STRUCT(tlss->push.iov); + TALLOC_FREE(tlss->push.buf); + tlss->push.ofs = 0; + + ret = tstream_writev_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tlss->error = sys_errno; + tstream_tls_retry(stream, false); + return; + } + + tstream_tls_retry(stream, false); +} + +static void tstream_tls_pull_done(struct tevent_req *subreq); + +static ssize_t tstream_tls_pull_function(gnutls_transport_ptr_t ptr, + void *buf, size_t size) +{ + struct tstream_context *stream = + talloc_get_type_abort(ptr, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *subreq; + size_t len; + + if (tlss->error != 0) { + errno = tlss->error; + return -1; + } + + if (tlss->pull.subreq) { + errno = EAGAIN; + return -1; + } + + if (tlss->pull.iov.iov_base) { + uint8_t *b; + size_t n; + + b = (uint8_t *)tlss->pull.iov.iov_base; + + n = MIN(tlss->pull.iov.iov_len, size); + memcpy(buf, b, n); + + tlss->pull.iov.iov_len -= n; + b += n; + tlss->pull.iov.iov_base = (char *)b; + if (tlss->pull.iov.iov_len == 0) { + tlss->pull.iov.iov_base = NULL; + TALLOC_FREE(tlss->pull.buf); + } + + return n; + } + + if (size == 0) { + return 0; + } + + len = MIN(size, UINT16_MAX); + + tlss->pull.buf = talloc_array(tlss, uint8_t, len); + if (tlss->pull.buf == NULL) { + return -1; + } + + tlss->pull.iov.iov_base = (char *)tlss->pull.buf; + tlss->pull.iov.iov_len = len; + + subreq = tstream_readv_send(tlss, + tlss->current_ev, + tlss->plain_stream, + &tlss->pull.iov, 1); + if (subreq == NULL) { + errno = ENOMEM; + return -1; + } + tevent_req_set_callback(subreq, tstream_tls_pull_done, stream); + + tlss->pull.subreq = subreq; + errno = EAGAIN; + return -1; +} + +static void tstream_tls_pull_done(struct tevent_req *subreq) +{ + struct tstream_context *stream = + tevent_req_callback_data(subreq, + struct tstream_context); + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + int ret; + int sys_errno; + + tlss->pull.subreq = NULL; + + ret = tstream_readv_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tlss->error = sys_errno; + tstream_tls_retry(stream, false); + return; + } + + tstream_tls_retry(stream, false); +} + +static int tstream_tls_destructor(struct tstream_tls *tlss) +{ + if (tlss->tls_session) { + gnutls_deinit(tlss->tls_session); + tlss->tls_session = NULL; + } + + return 0; +} + +static ssize_t tstream_tls_pending_bytes(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + size_t ret; + + if (tlss->error != 0) { + errno = tlss->error; + return -1; + } + + ret = gnutls_record_check_pending(tlss->tls_session); + ret += tlss->read.left; + + return ret; +} + +struct tstream_tls_readv_state { + struct tstream_context *stream; + + struct iovec *vector; + int count; + + int ret; +}; + +static void tstream_tls_readv_crypt_next(struct tevent_req *req); + +static struct tevent_req *tstream_tls_readv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + struct iovec *vector, + size_t count) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req; + struct tstream_tls_readv_state *state; + + tlss->read.req = NULL; + tlss->current_ev = ev; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_readv_state); + if (req == NULL) { + return NULL; + } + + state->stream = stream; + state->ret = 0; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return tevent_req_post(req, ev); + } + + /* + * we make a copy of the vector so we can change the structure + */ + state->vector = talloc_array(state, struct iovec, count); + if (tevent_req_nomem(state->vector, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->vector, vector, sizeof(struct iovec) * count); + state->count = count; + + tstream_tls_readv_crypt_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_tls_readv_crypt_next(struct tevent_req *req) +{ + struct tstream_tls_readv_state *state = + tevent_req_data(req, + struct tstream_tls_readv_state); + struct tstream_tls *tlss = + tstream_context_data(state->stream, + struct tstream_tls); + + /* + * copy the pending buffer first + */ + while (tlss->read.left > 0 && state->count > 0) { + uint8_t *base = (uint8_t *)state->vector[0].iov_base; + size_t len = MIN(tlss->read.left, state->vector[0].iov_len); + + memcpy(base, tlss->read.buffer + tlss->read.ofs, len); + + base += len; + state->vector[0].iov_base = (char *) base; + state->vector[0].iov_len -= len; + + tlss->read.ofs += len; + tlss->read.left -= len; + + if (state->vector[0].iov_len == 0) { + state->vector += 1; + state->count -= 1; + } + + state->ret += len; + } + + if (state->count == 0) { + tevent_req_done(req); + return; + } + + tlss->read.req = req; + tstream_tls_retry_read(state->stream); +} + +static void tstream_tls_retry_read(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req = tlss->read.req; + int ret; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return; + } + + tlss->read.left = 0; + tlss->read.ofs = 0; + + ret = gnutls_record_recv(tlss->tls_session, + tlss->read.buffer, + sizeof(tlss->read.buffer)); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return; + } + + tlss->read.req = NULL; + + if (gnutls_error_is_fatal(ret) != 0) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (ret == 0) { + tlss->error = EPIPE; + tevent_req_error(req, tlss->error); + return; + } + + tlss->read.left = ret; + tstream_tls_readv_crypt_next(req); +} + +static int tstream_tls_readv_recv(struct tevent_req *req, + int *perrno) +{ + struct tstream_tls_readv_state *state = + tevent_req_data(req, + struct tstream_tls_readv_state); + struct tstream_tls *tlss = + tstream_context_data(state->stream, + struct tstream_tls); + int ret; + + tlss->read.req = NULL; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_tls_writev_state { + struct tstream_context *stream; + + struct iovec *vector; + int count; + + int ret; +}; + +static void tstream_tls_writev_crypt_next(struct tevent_req *req); + +static struct tevent_req *tstream_tls_writev_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + const struct iovec *vector, + size_t count) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req; + struct tstream_tls_writev_state *state; + + tlss->write.req = NULL; + tlss->current_ev = ev; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_writev_state); + if (req == NULL) { + return NULL; + } + + state->stream = stream; + state->ret = 0; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return tevent_req_post(req, ev); + } + + /* + * we make a copy of the vector so we can change the structure + */ + state->vector = talloc_array(state, struct iovec, count); + if (tevent_req_nomem(state->vector, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->vector, vector, sizeof(struct iovec) * count); + state->count = count; + + tstream_tls_writev_crypt_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_tls_writev_crypt_next(struct tevent_req *req) +{ + struct tstream_tls_writev_state *state = + tevent_req_data(req, + struct tstream_tls_writev_state); + struct tstream_tls *tlss = + tstream_context_data(state->stream, + struct tstream_tls); + + tlss->write.left = sizeof(tlss->write.buffer); + tlss->write.ofs = 0; + + /* + * first fill our buffer + */ + while (tlss->write.left > 0 && state->count > 0) { + uint8_t *base = (uint8_t *)state->vector[0].iov_base; + size_t len = MIN(tlss->write.left, state->vector[0].iov_len); + + memcpy(tlss->write.buffer + tlss->write.ofs, base, len); + + base += len; + state->vector[0].iov_base = (char *) base; + state->vector[0].iov_len -= len; + + tlss->write.ofs += len; + tlss->write.left -= len; + + if (state->vector[0].iov_len == 0) { + state->vector += 1; + state->count -= 1; + } + + state->ret += len; + } + + if (tlss->write.ofs == 0) { + tevent_req_done(req); + return; + } + + tlss->write.left = tlss->write.ofs; + tlss->write.ofs = 0; + + tlss->write.req = req; + tstream_tls_retry_write(state->stream); +} + +static void tstream_tls_retry_write(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req = tlss->write.req; + int ret; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return; + } + + ret = gnutls_record_send(tlss->tls_session, + tlss->write.buffer + tlss->write.ofs, + tlss->write.left); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return; + } + + tlss->write.req = NULL; + + if (gnutls_error_is_fatal(ret) != 0) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (ret == 0) { + tlss->error = EPIPE; + tevent_req_error(req, tlss->error); + return; + } + + tlss->write.ofs += ret; + tlss->write.left -= ret; + + if (tlss->write.left > 0) { + tlss->write.req = req; + tstream_tls_retry_write(stream); + return; + } + + tstream_tls_writev_crypt_next(req); +} + +static int tstream_tls_writev_recv(struct tevent_req *req, + int *perrno) +{ + struct tstream_tls_writev_state *state = + tevent_req_data(req, + struct tstream_tls_writev_state); + struct tstream_tls *tlss = + tstream_context_data(state->stream, + struct tstream_tls); + int ret; + + tlss->write.req = NULL; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_tls_disconnect_state { + uint8_t _dummy; +}; + +static struct tevent_req *tstream_tls_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req; + struct tstream_tls_disconnect_state *state; + + tlss->disconnect.req = NULL; + tlss->current_ev = ev; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_disconnect_state); + if (req == NULL) { + return NULL; + } + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return tevent_req_post(req, ev); + } + + tlss->disconnect.req = req; + tstream_tls_retry_disconnect(stream); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_tls_retry_disconnect(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req = tlss->disconnect.req; + int ret; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return; + } + + ret = gnutls_bye(tlss->tls_session, GNUTLS_SHUT_WR); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return; + } + + tlss->disconnect.req = NULL; + + if (gnutls_error_is_fatal(ret) != 0) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + tevent_req_done(req); +} + +static int tstream_tls_disconnect_recv(struct tevent_req *req, + int *perrno) +{ + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + + tevent_req_received(req); + return ret; +} + +static const struct tstream_context_ops tstream_tls_ops = { + .name = "tls", + + .pending_bytes = tstream_tls_pending_bytes, + + .readv_send = tstream_tls_readv_send, + .readv_recv = tstream_tls_readv_recv, + + .writev_send = tstream_tls_writev_send, + .writev_recv = tstream_tls_writev_recv, + + .disconnect_send = tstream_tls_disconnect_send, + .disconnect_recv = tstream_tls_disconnect_recv, +}; + +struct tstream_tls_params_internal { + gnutls_certificate_credentials_t x509_cred; + gnutls_dh_params_t dh_params; + const char *tls_priority; + bool tls_enabled; + enum tls_verify_peer_state verify_peer; + const char *peer_name; +}; + +struct tstream_tls_params { + struct tstream_tls_params_internal *internal; +}; + +static int tstream_tls_params_internal_destructor(struct tstream_tls_params_internal *tlsp) +{ + if (tlsp->x509_cred) { + gnutls_certificate_free_credentials(tlsp->x509_cred); + tlsp->x509_cred = NULL; + } + if (tlsp->dh_params) { + gnutls_dh_params_deinit(tlsp->dh_params); + tlsp->dh_params = NULL; + } + + return 0; +} + +bool tstream_tls_params_enabled(struct tstream_tls_params *tls_params) +{ + struct tstream_tls_params_internal *tlsp = tls_params->internal; + + return tlsp->tls_enabled; +} + +NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx, + const char *ca_file, + const char *crl_file, + const char *tls_priority, + enum tls_verify_peer_state verify_peer, + const char *peer_name, + struct tstream_tls_params **_tlsp) +{ + struct tstream_tls_params *__tlsp = NULL; + struct tstream_tls_params_internal *tlsp = NULL; + int ret; + + __tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); + if (__tlsp == NULL) { + return NT_STATUS_NO_MEMORY; + } + + tlsp = talloc_zero(__tlsp, struct tstream_tls_params_internal); + if (tlsp == NULL) { + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(tlsp, tstream_tls_params_internal_destructor); + __tlsp->internal = tlsp; + + tlsp->verify_peer = verify_peer; + if (peer_name != NULL) { + tlsp->peer_name = talloc_strdup(tlsp, peer_name); + if (tlsp->peer_name == NULL) { + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_CA_AND_NAME) { + DEBUG(0,("TLS failed to missing peer_name - " + "with 'tls verify peer = %s'\n", + tls_verify_peer_string(tlsp->verify_peer))); + TALLOC_FREE(__tlsp); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + ret = gnutls_certificate_allocate_credentials(&tlsp->x509_cred); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + + if (ca_file && *ca_file && file_exist(ca_file)) { + ret = gnutls_certificate_set_x509_trust_file(tlsp->x509_cred, + ca_file, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + DEBUG(0,("TLS failed to initialise cafile %s - %s\n", + ca_file, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_CA_ONLY) { + DEBUG(0,("TLS failed to missing cafile %s - " + "with 'tls verify peer = %s'\n", + ca_file, + tls_verify_peer_string(tlsp->verify_peer))); + TALLOC_FREE(__tlsp); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (crl_file && *crl_file && file_exist(crl_file)) { + ret = gnutls_certificate_set_x509_crl_file(tlsp->x509_cred, + crl_file, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + DEBUG(0,("TLS failed to initialise crlfile %s - %s\n", + crl_file, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE) { + DEBUG(0,("TLS failed to missing crlfile %s - " + "with 'tls verify peer = %s'\n", + crl_file, + tls_verify_peer_string(tlsp->verify_peer))); + TALLOC_FREE(__tlsp); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + tlsp->tls_priority = talloc_strdup(tlsp, tls_priority); + if (tlsp->tls_priority == NULL) { + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + + tlsp->tls_enabled = true; + + *_tlsp = __tlsp; + return NT_STATUS_OK; +} + +struct tstream_tls_connect_state { + struct tstream_context *tls_stream; +}; + +struct tevent_req *_tstream_tls_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *_tls_params, + const char *location) +{ + struct tevent_req *req; + struct tstream_tls_connect_state *state; + const char *error_pos; + struct tstream_tls *tlss; + struct tstream_tls_params_internal *tls_params = NULL; + int ret; + unsigned int flags = GNUTLS_CLIENT; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_connect_state); + if (req == NULL) { + return NULL; + } + + state->tls_stream = tstream_context_create(state, + &tstream_tls_ops, + &tlss, + struct tstream_tls, + location); + if (tevent_req_nomem(state->tls_stream, req)) { + return tevent_req_post(req, ev); + } + ZERO_STRUCTP(tlss); + talloc_set_destructor(tlss, tstream_tls_destructor); + + /* + * Note we need to make sure x509_cred and dh_params + * from tstream_tls_params_internal stay alive for + * the whole lifetime of this session! + * + * See 'man gnutls_credentials_set' and + * 'man gnutls_certificate_set_dh_params'. + * + * Note: here we use talloc_reference() in a way + * that does not expose it to the caller. + * + */ + tls_params = talloc_reference(tlss, _tls_params->internal); + if (tevent_req_nomem(tls_params, req)) { + return tevent_req_post(req, ev); + } + + tlss->plain_stream = plain_stream; + tlss->verify_peer = tls_params->verify_peer; + if (tls_params->peer_name != NULL) { + tlss->peer_name = talloc_strdup(tlss, tls_params->peer_name); + if (tevent_req_nomem(tlss->peer_name, req)) { + return tevent_req_post(req, ev); + } + } + + tlss->current_ev = ev; + tlss->retry_im = tevent_create_immediate(tlss); + if (tevent_req_nomem(tlss->retry_im, req)) { + return tevent_req_post(req, ev); + } + +#ifdef GNUTLS_NO_TICKETS + /* + * tls_tstream can't properly handle 'New Session Ticket' messages + * sent 'after' the client sends the 'Finished' message. + * GNUTLS_NO_TICKETS was introduced in GnuTLS 3.5.6. This flag is to + * indicate the session Flag session should not use resumption with + * session tickets. + */ + flags |= GNUTLS_NO_TICKETS; +#endif + + ret = gnutls_init(&tlss->tls_session, flags); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + ret = gnutls_set_default_priority(tlss->tls_session); + if (ret != GNUTLS_E_SUCCESS) { + DBG_ERR("TLS %s - %s. Failed to set default priorities\n", + __location__, gnutls_strerror(ret)); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + if (strlen(tls_params->tls_priority) > 0) { + ret = gnutls_priority_set_direct(tlss->tls_session, + tls_params->tls_priority, + &error_pos); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s. Check 'tls priority' option at '%s'\n", + __location__, gnutls_strerror(ret), error_pos)); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + } + + ret = gnutls_credentials_set(tlss->tls_session, + GNUTLS_CRD_CERTIFICATE, + tls_params->x509_cred); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + gnutls_transport_set_ptr(tlss->tls_session, + (gnutls_transport_ptr_t)state->tls_stream); + gnutls_transport_set_pull_function(tlss->tls_session, + (gnutls_pull_func)tstream_tls_pull_function); + gnutls_transport_set_push_function(tlss->tls_session, + (gnutls_push_func)tstream_tls_push_function); + + tlss->handshake.req = req; + tstream_tls_retry_handshake(state->tls_stream); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +int tstream_tls_connect_recv(struct tevent_req *req, + int *perrno, + TALLOC_CTX *mem_ctx, + struct tstream_context **tls_stream) +{ + struct tstream_tls_connect_state *state = + tevent_req_data(req, + struct tstream_tls_connect_state); + + if (tevent_req_is_unix_error(req, perrno)) { + tevent_req_received(req); + return -1; + } + + *tls_stream = talloc_move(mem_ctx, &state->tls_stream); + tevent_req_received(req); + return 0; +} + +/* + initialise global tls state +*/ +NTSTATUS tstream_tls_params_server(TALLOC_CTX *mem_ctx, + const char *dns_host_name, + bool enabled, + const char *key_file, + const char *cert_file, + const char *ca_file, + const char *crl_file, + const char *dhp_file, + const char *tls_priority, + struct tstream_tls_params **_tlsp) +{ + struct tstream_tls_params *__tlsp = NULL; + struct tstream_tls_params_internal *tlsp = NULL; + int ret; + struct stat st; + + if (!enabled || key_file == NULL || *key_file == 0) { + __tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); + if (__tlsp == NULL) { + return NT_STATUS_NO_MEMORY; + } + + tlsp = talloc_zero(__tlsp, struct tstream_tls_params_internal); + if (tlsp == NULL) { + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + + talloc_set_destructor(tlsp, tstream_tls_params_internal_destructor); + __tlsp->internal = tlsp; + tlsp->tls_enabled = false; + + *_tlsp = __tlsp; + return NT_STATUS_OK; + } + + __tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); + if (__tlsp == NULL) { + return NT_STATUS_NO_MEMORY; + } + + tlsp = talloc_zero(__tlsp, struct tstream_tls_params_internal); + if (tlsp == NULL) { + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + + talloc_set_destructor(tlsp, tstream_tls_params_internal_destructor); + __tlsp->internal = tlsp; + + if (!file_exist(ca_file)) { + tls_cert_generate(tlsp, dns_host_name, + key_file, cert_file, ca_file); + } + + if (file_exist(key_file) && + !file_check_permissions(key_file, geteuid(), 0600, &st)) + { + DEBUG(0, ("Invalid permissions on TLS private key file '%s':\n" + "owner uid %u should be %u, mode 0%o should be 0%o\n" + "This is known as CVE-2013-4476.\n" + "Removing all tls .pem files will cause an " + "auto-regeneration with the correct permissions.\n", + key_file, + (unsigned int)st.st_uid, geteuid(), + (unsigned int)(st.st_mode & 0777), 0600)); + TALLOC_FREE(__tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + ret = gnutls_certificate_allocate_credentials(&tlsp->x509_cred); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + + if (ca_file && *ca_file) { + ret = gnutls_certificate_set_x509_trust_file(tlsp->x509_cred, + ca_file, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + DEBUG(0,("TLS failed to initialise cafile %s - %s\n", + ca_file, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } + + if (crl_file && *crl_file) { + ret = gnutls_certificate_set_x509_crl_file(tlsp->x509_cred, + crl_file, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + DEBUG(0,("TLS failed to initialise crlfile %s - %s\n", + crl_file, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } + + ret = gnutls_certificate_set_x509_key_file(tlsp->x509_cred, + cert_file, key_file, + GNUTLS_X509_FMT_PEM); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS failed to initialise certfile %s and keyfile %s - %s\n", + cert_file, key_file, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + ret = gnutls_dh_params_init(&tlsp->dh_params); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + + if (dhp_file && *dhp_file) { + gnutls_datum_t dhparms; + size_t size; + + dhparms.data = (uint8_t *)file_load(dhp_file, &size, 0, tlsp); + + if (!dhparms.data) { + DEBUG(0,("TLS failed to read DH Parms from %s - %d:%s\n", + dhp_file, errno, strerror(errno))); + TALLOC_FREE(__tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + dhparms.size = size; + + ret = gnutls_dh_params_import_pkcs3(tlsp->dh_params, + &dhparms, + GNUTLS_X509_FMT_PEM); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS failed to import pkcs3 %s - %s\n", + dhp_file, gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } else { + ret = gnutls_dh_params_generate2(tlsp->dh_params, DH_BITS); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS failed to generate dh_params - %s\n", + gnutls_strerror(ret))); + TALLOC_FREE(__tlsp); + return NT_STATUS_INTERNAL_ERROR; + } + } + + gnutls_certificate_set_dh_params(tlsp->x509_cred, tlsp->dh_params); + + tlsp->tls_priority = talloc_strdup(tlsp, tls_priority); + if (tlsp->tls_priority == NULL) { + TALLOC_FREE(__tlsp); + return NT_STATUS_NO_MEMORY; + } + + tlsp->tls_enabled = true; + + *_tlsp = __tlsp; + return NT_STATUS_OK; +} + +struct tstream_tls_accept_state { + struct tstream_context *tls_stream; +}; + +struct tevent_req *_tstream_tls_accept_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *plain_stream, + struct tstream_tls_params *_tlsp, + const char *location) +{ + struct tevent_req *req; + struct tstream_tls_accept_state *state; + struct tstream_tls *tlss; + const char *error_pos; + struct tstream_tls_params_internal *tlsp = NULL; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_tls_accept_state); + if (req == NULL) { + return NULL; + } + + state->tls_stream = tstream_context_create(state, + &tstream_tls_ops, + &tlss, + struct tstream_tls, + location); + if (tevent_req_nomem(state->tls_stream, req)) { + return tevent_req_post(req, ev); + } + ZERO_STRUCTP(tlss); + talloc_set_destructor(tlss, tstream_tls_destructor); + + /* + * Note we need to make sure x509_cred and dh_params + * from tstream_tls_params_internal stay alive for + * the whole lifetime of this session! + * + * See 'man gnutls_credentials_set' and + * 'man gnutls_certificate_set_dh_params'. + * + * Note: here we use talloc_reference() in a way + * that does not expose it to the caller. + */ + tlsp = talloc_reference(tlss, _tlsp->internal); + if (tevent_req_nomem(tlsp, req)) { + return tevent_req_post(req, ev); + } + + tlss->plain_stream = plain_stream; + + tlss->current_ev = ev; + tlss->retry_im = tevent_create_immediate(tlss); + if (tevent_req_nomem(tlss->retry_im, req)) { + return tevent_req_post(req, ev); + } + + ret = gnutls_init(&tlss->tls_session, GNUTLS_SERVER); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + ret = gnutls_set_default_priority(tlss->tls_session); + if (ret != GNUTLS_E_SUCCESS) { + DBG_ERR("TLS %s - %s. Failed to set default priorities\n", + __location__, gnutls_strerror(ret)); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + if (strlen(tlsp->tls_priority) > 0) { + ret = gnutls_priority_set_direct(tlss->tls_session, + tlsp->tls_priority, + &error_pos); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s. Check 'tls priority' option at '%s'\n", + __location__, gnutls_strerror(ret), error_pos)); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + } + + ret = gnutls_credentials_set(tlss->tls_session, GNUTLS_CRD_CERTIFICATE, + tlsp->x509_cred); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + gnutls_certificate_server_set_request(tlss->tls_session, + GNUTLS_CERT_REQUEST); + gnutls_dh_set_prime_bits(tlss->tls_session, DH_BITS); + + gnutls_transport_set_ptr(tlss->tls_session, + (gnutls_transport_ptr_t)state->tls_stream); + gnutls_transport_set_pull_function(tlss->tls_session, + (gnutls_pull_func)tstream_tls_pull_function); + gnutls_transport_set_push_function(tlss->tls_session, + (gnutls_push_func)tstream_tls_push_function); + + tlss->handshake.req = req; + tstream_tls_retry_handshake(state->tls_stream); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_tls_retry_handshake(struct tstream_context *stream) +{ + struct tstream_tls *tlss = + tstream_context_data(stream, + struct tstream_tls); + struct tevent_req *req = tlss->handshake.req; + int ret; + + if (tlss->error != 0) { + tevent_req_error(req, tlss->error); + return; + } + + ret = gnutls_handshake(tlss->tls_session); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return; + } + + tlss->handshake.req = NULL; + + if (gnutls_error_is_fatal(ret) != 0) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (tlss->verify_peer >= TLS_VERIFY_PEER_CA_ONLY) { + unsigned int status = UINT32_MAX; + bool ip = true; + const char *hostname = NULL; + + if (tlss->peer_name != NULL) { + ip = is_ipaddress(tlss->peer_name); + } + + if (!ip) { + hostname = tlss->peer_name; + } + + if (tlss->verify_peer == TLS_VERIFY_PEER_CA_ONLY) { + hostname = NULL; + } + + if (tlss->verify_peer >= TLS_VERIFY_PEER_CA_AND_NAME) { + if (hostname == NULL) { + DEBUG(1,("TLS %s - no hostname available for " + "verify_peer[%s] and peer_name[%s]\n", + __location__, + tls_verify_peer_string(tlss->verify_peer), + tlss->peer_name)); + tlss->error = EINVAL; + tevent_req_error(req, tlss->error); + return; + } + } + + ret = gnutls_certificate_verify_peers3(tlss->tls_session, + hostname, + &status); + if (ret != GNUTLS_E_SUCCESS) { + DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); + tlss->error = EIO; + tevent_req_error(req, tlss->error); + return; + } + + if (status != 0) { + DEBUG(1,("TLS %s - check failed for " + "verify_peer[%s] and peer_name[%s] " + "status 0x%x (%s%s%s%s%s%s%s%s)\n", + __location__, + tls_verify_peer_string(tlss->verify_peer), + tlss->peer_name, + status, + status & GNUTLS_CERT_INVALID ? "invalid " : "", + status & GNUTLS_CERT_REVOKED ? "revoked " : "", + status & GNUTLS_CERT_SIGNER_NOT_FOUND ? + "signer_not_found " : "", + status & GNUTLS_CERT_SIGNER_NOT_CA ? + "signer_not_ca " : "", + status & GNUTLS_CERT_INSECURE_ALGORITHM ? + "insecure_algorithm " : "", + status & GNUTLS_CERT_NOT_ACTIVATED ? + "not_activated " : "", + status & GNUTLS_CERT_EXPIRED ? + "expired " : "", + status & GNUTLS_CERT_UNEXPECTED_OWNER ? + "unexpected_owner " : "")); + tlss->error = EINVAL; + tevent_req_error(req, tlss->error); + return; + } + } + + tevent_req_done(req); +} + +int tstream_tls_accept_recv(struct tevent_req *req, + int *perrno, + TALLOC_CTX *mem_ctx, + struct tstream_context **tls_stream) +{ + struct tstream_tls_accept_state *state = + tevent_req_data(req, + struct tstream_tls_accept_state); + + if (tevent_req_is_unix_error(req, perrno)) { + tevent_req_received(req); + return -1; + } + + *tls_stream = talloc_move(mem_ctx, &state->tls_stream); + tevent_req_received(req); + return 0; +} diff --git a/source4/lib/tls/tlscert.c b/source4/lib/tls/tlscert.c new file mode 100644 index 0000000..36482e3 --- /dev/null +++ b/source4/lib/tls/tlscert.c @@ -0,0 +1,159 @@ +/* + Unix SMB/CIFS implementation. + + auto-generate self signed TLS certificates + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/tls/tls.h" + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#define ORGANISATION_NAME "Samba Administration" +#define CA_NAME "Samba - temporary autogenerated CA certificate" +#define UNIT_NAME "Samba - temporary autogenerated HOST certificate" +#define LIFETIME 700*24*60*60 + +/* FIPS140-2 only allows 2048 or 3072 prime sizes. */ +#define RSA_BITS gnutls_fips140_mode_enabled() ? 3072 : 4096 + +/* + auto-generate a set of self signed certificates +*/ +void tls_cert_generate(TALLOC_CTX *mem_ctx, + const char *hostname, + const char *keyfile, const char *certfile, + const char *cafile) +{ + gnutls_x509_crt_t cacrt, crt; + gnutls_x509_privkey_t key, cakey; + uint32_t serial = (uint32_t)time(NULL); + unsigned char keyid[100]; + char buf[4096]; + size_t bufsize; + size_t keyidsize = sizeof(keyid); + time_t activation = time(NULL), expiry = activation + LIFETIME; + int ret; + + if (file_exist(keyfile) || file_exist(certfile) || file_exist(cafile)) { + DEBUG(0,("TLS autogeneration skipped - some TLS files already exist\n")); + return; + } + +#define TLSCHECK(call) do { \ + ret = call; \ + if (ret < 0) { \ + DEBUG(0,("TLS %s - %s\n", #call, gnutls_strerror(ret))); \ + goto failed; \ + } \ +} while (0) + + DEBUG(0,("Attempting to autogenerate TLS self-signed keys for https for hostname '%s'\n", + hostname)); + + DEBUG(3,("Generating private key\n")); + TLSCHECK(gnutls_x509_privkey_init(&key)); + TLSCHECK(gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, RSA_BITS, 0)); + + DEBUG(3,("Generating CA private key\n")); + TLSCHECK(gnutls_x509_privkey_init(&cakey)); + TLSCHECK(gnutls_x509_privkey_generate(cakey, GNUTLS_PK_RSA, RSA_BITS, 0)); + + DEBUG(3,("Generating CA certificate\n")); + TLSCHECK(gnutls_x509_crt_init(&cacrt)); + TLSCHECK(gnutls_x509_crt_set_dn_by_oid(cacrt, + GNUTLS_OID_X520_ORGANIZATION_NAME, 0, + ORGANISATION_NAME, strlen(ORGANISATION_NAME))); + TLSCHECK(gnutls_x509_crt_set_dn_by_oid(cacrt, + GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, + CA_NAME, strlen(CA_NAME))); + TLSCHECK(gnutls_x509_crt_set_dn_by_oid(cacrt, + GNUTLS_OID_X520_COMMON_NAME, 0, + hostname, strlen(hostname))); + TLSCHECK(gnutls_x509_crt_set_key(cacrt, cakey)); + TLSCHECK(gnutls_x509_crt_set_serial(cacrt, &serial, sizeof(serial))); + TLSCHECK(gnutls_x509_crt_set_activation_time(cacrt, activation)); + TLSCHECK(gnutls_x509_crt_set_expiration_time(cacrt, expiry)); + TLSCHECK(gnutls_x509_crt_set_ca_status(cacrt, 1)); + TLSCHECK(gnutls_x509_crt_set_key_usage(cacrt, GNUTLS_KEY_KEY_CERT_SIGN | GNUTLS_KEY_CRL_SIGN)); + TLSCHECK(gnutls_x509_crt_set_version(cacrt, 3)); + TLSCHECK(gnutls_x509_crt_get_key_id(cacrt, 0, keyid, &keyidsize)); + TLSCHECK(gnutls_x509_crt_set_subject_key_id(cacrt, keyid, keyidsize)); + TLSCHECK(gnutls_x509_crt_sign2(cacrt, cacrt, cakey, + GNUTLS_DIG_SHA256, 0)); + + DEBUG(3,("Generating TLS certificate\n")); + TLSCHECK(gnutls_x509_crt_init(&crt)); + TLSCHECK(gnutls_x509_crt_set_dn_by_oid(crt, + GNUTLS_OID_X520_ORGANIZATION_NAME, 0, + ORGANISATION_NAME, strlen(ORGANISATION_NAME))); + TLSCHECK(gnutls_x509_crt_set_dn_by_oid(crt, + GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, + UNIT_NAME, strlen(UNIT_NAME))); + TLSCHECK(gnutls_x509_crt_set_dn_by_oid(crt, + GNUTLS_OID_X520_COMMON_NAME, 0, + hostname, strlen(hostname))); + TLSCHECK(gnutls_x509_crt_set_key(crt, key)); + TLSCHECK(gnutls_x509_crt_set_serial(crt, &serial, sizeof(serial))); + TLSCHECK(gnutls_x509_crt_set_activation_time(crt, activation)); + TLSCHECK(gnutls_x509_crt_set_expiration_time(crt, expiry)); + TLSCHECK(gnutls_x509_crt_set_ca_status(crt, 0)); + TLSCHECK(gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_TLS_WWW_SERVER, 0)); + TLSCHECK(gnutls_x509_crt_set_version(crt, 3)); + TLSCHECK(gnutls_x509_crt_get_key_id(crt, 0, keyid, &keyidsize)); + TLSCHECK(gnutls_x509_crt_set_subject_key_id(crt, keyid, keyidsize)); + TLSCHECK(gnutls_x509_crt_sign2(crt, crt, key, + GNUTLS_DIG_SHA256, 0)); + TLSCHECK(gnutls_x509_crt_sign2(crt, cacrt, cakey, + GNUTLS_DIG_SHA256, 0)); + + DEBUG(3,("Exporting TLS keys\n")); + + bufsize = sizeof(buf); + TLSCHECK(gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, buf, &bufsize)); + if (!file_save(certfile, buf, bufsize)) { + DEBUG(0,("Unable to save certificate in %s parent dir exists ?\n", certfile)); + goto failed; + } + + bufsize = sizeof(buf); + TLSCHECK(gnutls_x509_crt_export(cacrt, GNUTLS_X509_FMT_PEM, buf, &bufsize)); + if (!file_save(cafile, buf, bufsize)) { + DEBUG(0,("Unable to save ca cert in %s parent dir exists ?\n", cafile)); + goto failed; + } + + bufsize = sizeof(buf); + TLSCHECK(gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buf, &bufsize)); + if (!file_save_mode(keyfile, buf, bufsize, 0600)) { + DEBUG(0,("Unable to save privatekey in %s parent dir exists ?\n", keyfile)); + goto failed; + } + + gnutls_x509_privkey_deinit(key); + gnutls_x509_privkey_deinit(cakey); + gnutls_x509_crt_deinit(cacrt); + gnutls_x509_crt_deinit(crt); + + DEBUG(0,("TLS self-signed keys generated OK\n")); + return; + +failed: + DEBUG(0,("TLS certificate generation failed\n")); +} diff --git a/source4/lib/tls/wscript_build b/source4/lib/tls/wscript_build new file mode 100644 index 0000000..7980a63 --- /dev/null +++ b/source4/lib/tls/wscript_build @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM('LIBTLS', + source=''' + tlscert.c + tls_tstream.c + ''', + public_deps=''' + talloc + gnutls + samba-hostconfig + LIBTSOCKET + tevent + tevent-util + ''') |