/* samdb proxy module Copyright (C) Andrew Tridgell 2005 ** NOTE! The following LGPL license applies to the ldb ** library. This does NOT imply that all of Samba is released ** under the LGPL This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ /* ldb proxy module. At startup this looks for a record like this: dn=@PROXYINFO url=destination url olddn = basedn to proxy in upstream server newdn = basedn in local server username = username to connect to upstream password = password for upstream NOTE: this module is a complete hack at this stage. I am committing it just so others can know how I am investigating mmc support */ #include "includes.h" #include "ldb/include/ldb.h" #include "ldb/include/ldb_errors.h" #include "ldb/include/ldb_private.h" #include "auth/credentials/credentials.h" struct proxy_data { struct ldb_context *upstream; struct ldb_dn *olddn; struct ldb_dn *newdn; const char **oldstr; const char **newstr; }; struct proxy_ctx { struct ldb_module *module; struct ldb_request *req; #ifdef DEBUG_PROXY int count; #endif }; /* load the @PROXYINFO record */ static int load_proxy_info(struct ldb_module *module) { struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data); struct ldb_dn *dn; struct ldb_result *res = NULL; int ret; const char *olddn, *newdn, *url, *username, *password, *oldstr, *newstr; struct cli_credentials *creds; /* see if we have already loaded it */ if (proxy->upstream != NULL) { return LDB_SUCCESS; } dn = ldb_dn_new(proxy, module->ldb, "@PROXYINFO"); if (dn == NULL) { goto failed; } ret = ldb_search(module->ldb, proxy, &res, dn, LDB_SCOPE_BASE, NULL, NULL); talloc_free(dn); if (ret != LDB_SUCCESS || res->count != 1) { ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Can't find @PROXYINFO\n"); goto failed; } url = ldb_msg_find_attr_as_string(res->msgs[0], "url", NULL); olddn = ldb_msg_find_attr_as_string(res->msgs[0], "olddn", NULL); newdn = ldb_msg_find_attr_as_string(res->msgs[0], "newdn", NULL); username = ldb_msg_find_attr_as_string(res->msgs[0], "username", NULL); password = ldb_msg_find_attr_as_string(res->msgs[0], "password", NULL); oldstr = ldb_msg_find_attr_as_string(res->msgs[0], "oldstr", NULL); newstr = ldb_msg_find_attr_as_string(res->msgs[0], "newstr", NULL); if (url == NULL || olddn == NULL || newdn == NULL || username == NULL || password == NULL) { ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Need url, olddn, newdn, oldstr, newstr, username and password in @PROXYINFO\n"); goto failed; } proxy->olddn = ldb_dn_new(proxy, module->ldb, olddn); if (proxy->olddn == NULL) { ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to explode olddn '%s'\n", olddn); goto failed; } proxy->newdn = ldb_dn_new(proxy, module->ldb, newdn); if (proxy->newdn == NULL) { ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to explode newdn '%s'\n", newdn); goto failed; } proxy->upstream = ldb_init(proxy, ldb_get_event_context(ldb)); if (proxy->upstream == NULL) { ldb_oom(module->ldb); goto failed; } proxy->oldstr = str_list_make(proxy, oldstr, ", "); if (proxy->oldstr == NULL) { ldb_oom(module->ldb); goto failed; } proxy->newstr = str_list_make(proxy, newstr, ", "); if (proxy->newstr == NULL) { ldb_oom(module->ldb); goto failed; } /* setup credentials for connection */ creds = cli_credentials_init(proxy->upstream); if (creds == NULL) { ldb_oom(module->ldb); goto failed; } cli_credentials_guess(creds, ldb_get_opaque(module->ldb, "loadparm")); cli_credentials_set_username(creds, username, CRED_SPECIFIED); cli_credentials_set_password(creds, password, CRED_SPECIFIED); ldb_set_opaque(proxy->upstream, "credentials", creds); ret = ldb_connect(proxy->upstream, url, 0, NULL); if (ret != 0) { ldb_debug(module->ldb, LDB_DEBUG_FATAL, "proxy failed to connect to %s\n", url); goto failed; } ldb_debug(module->ldb, LDB_DEBUG_TRACE, "proxy connected to %s\n", url); talloc_free(res); return LDB_SUCCESS; failed: talloc_free(res); talloc_free(proxy->olddn); talloc_free(proxy->newdn); talloc_free(proxy->upstream); proxy->upstream = NULL; return LDB_ERR_OPERATIONS_ERROR; } /* convert a binary blob */ static void proxy_convert_blob(TALLOC_CTX *mem_ctx, struct ldb_val *v, const char *oldstr, const char *newstr) { int len1, len2, len3; uint8_t *olddata = v->data; char *p = strcasestr((char *)v->data, oldstr); len1 = (p - (char *)v->data); len2 = strlen(newstr); len3 = v->length - (p+strlen(oldstr) - (char *)v->data); v->length = len1+len2+len3; v->data = talloc_size(mem_ctx, v->length); memcpy(v->data, olddata, len1); memcpy(v->data+len1, newstr, len2); memcpy(v->data+len1+len2, olddata + len1 + strlen(oldstr), len3); } /* convert a returned value */ static void proxy_convert_value(struct proxy_data *proxy, struct ldb_message *msg, struct ldb_val *v) { int i; for (i=0;proxy->oldstr[i];i++) { char *p = strcasestr((char *)v->data, proxy->oldstr[i]); if (p == NULL) continue; proxy_convert_blob(msg, v, proxy->oldstr[i], proxy->newstr[i]); } } /* convert a returned value */ static struct ldb_parse_tree *proxy_convert_tree(TALLOC_CTX *mem_ctx, struct proxy_data *proxy, struct ldb_parse_tree *tree) { int i; char *expression = ldb_filter_from_tree(mem_ctx, tree); for (i=0;proxy->newstr[i];i++) { struct ldb_val v; char *p = strcasestr(expression, proxy->newstr[i]); if (p == NULL) continue; v.data = (uint8_t *)expression; v.length = strlen(expression)+1; proxy_convert_blob(mem_ctx, &v, proxy->newstr[i], proxy->oldstr[i]); return ldb_parse_tree(mem_ctx, (const char *)v.data); } return tree; } /* convert a returned record */ static void proxy_convert_record(struct ldb_context *ldb, struct proxy_data *proxy, struct ldb_message *msg) { int attr, v; /* fix the message DN */ if (ldb_dn_compare_base(proxy->olddn, msg->dn) == 0) { ldb_dn_remove_base_components(msg->dn, ldb_dn_get_comp_num(proxy->olddn)); ldb_dn_add_base(msg->dn, proxy->newdn); } /* fix any attributes */ for (attr=0;attrnum_elements;attr++) { for (v=0;velements[attr].num_values;v++) { proxy_convert_value(proxy, msg, &msg->elements[attr].values[v]); } } /* fix any DN components */ for (attr=0;attrnum_elements;attr++) { for (v=0;velements[attr].num_values;v++) { proxy_convert_value(proxy, msg, &msg->elements[attr].values[v]); } } } static int proxy_search_callback(struct ldb_request *req, struct ldb_reply *ares) { struct proxy_data *proxy; struct proxy_ctx *ac; int ret; ac = talloc_get_type(req->context, struct proxy_ctx); proxy = talloc_get_type(ac->module->private_data, struct proxy_data); if (!ares) { return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); } if (ares->error != LDB_SUCCESS) { return ldb_module_done(ac->req, ares->controls, ares->response, ares->error); } /* Only entries are interesting, and we only want the olddn */ switch (ares->type) { case LDB_REPLY_ENTRY: #ifdef DEBUG_PROXY ac->count++; #endif proxy_convert_record(ac->module->ldb, proxy, ares->message); ret = ldb_module_send_entry(ac->req, ares->message); break; case LDB_REPLY_REFERRAL: /* ignore remote referrals */ break; case LDB_REPLY_DONE: #ifdef DEBUG_PROXY printf("# record %d\n", ac->count+1); #endif return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS); } talloc_free(ares); return ret; } /* search */ static int proxy_search_bytree(struct ldb_module *module, struct ldb_request *req) { struct proxy_ctx *ac; struct ldb_parse_tree *newtree; struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data); struct ldb_request *newreq; struct ldb_dn *base; int ret, i; if (req->op.search.base == NULL || (req->op.search.base->comp_num == 1 && req->op.search.base->components[0].name[0] == '@')) { goto passthru; } if (load_proxy_info(module) != 0) { return LDB_ERR_OPERATIONS_ERROR; } /* see if the dn is within olddn */ if (ldb_dn_compare_base(proxy->newdn, req->op.search.base) != 0) { goto passthru; } ac = talloc(req, struct proxy_ctx); if (ac == NULL) { return LDB_ERR_OPERATIONS_ERROR; } ac->module = module; ac->req = req; #ifdef DEBUG_PROXY ac->count = 0; #endif newtree = proxy_convert_tree(ac, proxy, req->op.search.tree); /* convert the basedn of this search */ base = ldb_dn_copy(ac, req->op.search.base); if (base == NULL) { goto failed; } ldb_dn_remove_base_components(base, ldb_dn_get_comp_num(proxy->newdn)); ldb_dn_add_base(base, proxy->olddn); ldb_debug(module->ldb, LDB_DEBUG_FATAL, "proxying: '%s' with dn '%s' \n", ldb_filter_from_tree(ac, newreq->op.search.tree), ldb_dn_get_linearized(newreq->op.search.base)); for (i = 0; req->op.search.attrs && req->op.search.attrs[i]; i++) { ldb_debug(module->ldb, LDB_DEBUG_FATAL, "attr: '%s'\n", req->op.search.attrs[i]); } ret = ldb_build_search_req_ex(&newreq, module->ldb, ac, base, req->op.search.scope, newtree, req->op.search.attrs, req->controls, ac, proxy_search_callback, req); /* FIXME: warning, need a real event system hooked up for this to work properly, * for now this makes the module *not* ASYNC */ ret = ldb_request(proxy->upstream, newreq); if (ret != LDB_SUCCESS) { ldb_set_errstring(module->ldb, ldb_errstring(proxy->upstream)); } ret = ldb_wait(newreq->handle, LDB_WAIT_ALL); if (ret != LDB_SUCCESS) { ldb_set_errstring(module->ldb, ldb_errstring(proxy->upstream)); } return ret; failed: ldb_debug(module->ldb, LDB_DEBUG_TRACE, "proxy failed for %s\n", ldb_dn_get_linearized(req->op.search.base)); passthru: return ldb_next_request(module, req); } static int proxy_request(struct ldb_module *module, struct ldb_request *req) { switch (req->operation) { case LDB_REQ_SEARCH: return proxy_search_bytree(module, req); default: return ldb_next_request(module, req); } } _PUBLIC_ const struct ldb_module_ops ldb_proxy_module_ops = { .name = "proxy", .request = proxy_request };