diff options
Diffstat (limited to 'source4/scripting')
-rw-r--r-- | source4/scripting/ejs/config.mk | 4 | ||||
-rw-r--r-- | source4/scripting/ejs/smbcalls.c | 1 | ||||
-rw-r--r-- | source4/scripting/libjs/provision.js | 1254 | ||||
-rw-r--r-- | source4/scripting/python/config.m4 | 5 | ||||
-rw-r--r-- | source4/scripting/python/config.mk | 16 | ||||
-rw-r--r-- | source4/scripting/python/modules.c | 3 | ||||
-rw-r--r-- | source4/scripting/python/samba/provision.py | 5 | ||||
-rw-r--r-- | source4/scripting/python/samba/tests/samdb.py | 8 | ||||
-rw-r--r-- | source4/scripting/python/uuidmodule.c | 2 |
9 files changed, 25 insertions, 1273 deletions
diff --git a/source4/scripting/ejs/config.mk b/source4/scripting/ejs/config.mk index cadd71673c..896b292e23 100644 --- a/source4/scripting/ejs/config.mk +++ b/source4/scripting/ejs/config.mk @@ -58,7 +58,9 @@ smbcalls_data_OBJ_FILES = scripting/ejs/smbcalls_data.o OUTPUT_TYPE = MERGED_OBJ SUBSYSTEM = smbcalls INIT_FUNCTION = smb_setup_ejs_auth -PRIVATE_DEPENDENCIES = auth +PRIVATE_DEPENDENCIES = service_auth + +smbcalls_auth_OBJ_FILES = scripting/ejs/smbcalls_auth.o smbcalls_auth_OBJ_FILES = scripting/ejs/smbcalls_auth.o diff --git a/source4/scripting/ejs/smbcalls.c b/source4/scripting/ejs/smbcalls.c index b1a2f6a37b..98d6be07bf 100644 --- a/source4/scripting/ejs/smbcalls.c +++ b/source4/scripting/ejs/smbcalls.c @@ -23,7 +23,6 @@ #include "includes.h" #include "param/param.h" #include "scripting/ejs/smbcalls.h" -#include "build.h" #include "version.h" /* diff --git a/source4/scripting/libjs/provision.js b/source4/scripting/libjs/provision.js deleted file mode 100644 index 51e2785762..0000000000 --- a/source4/scripting/libjs/provision.js +++ /dev/null @@ -1,1254 +0,0 @@ -/* - backend code for provisioning a Samba4 server - Copyright Andrew Tridgell 2005 - Released under the GNU GPL version 3 or later -*/ - -sys = sys_init(); - -/* - return true if the current install seems to be OK -*/ -function install_ok(session_info, credentials) -{ - var lp = loadparm_init(); - var ldb = ldb_init(); - ldb.session_info = session_info; - ldb.credentials = credentials; - if (lp.get("realm") == "") { - return false; - } - var ok = ldb.connect(lp.get("sam database")); - if (!ok) { - return false; - } - var res = ldb.search("(cn=Administrator)"); - if (res.error != 0 || res.msgs.length != 1) { - return false; - } - return true; -} - -/* - find a user or group from a list of possibilities -*/ -function findnss() -{ - var i; - assert(arguments.length >= 2); - var nssfn = arguments[0]; - for (i=1;i<arguments.length;i++) { - if (nssfn(arguments[i]) != undefined) { - return arguments[i]; - } - } - printf("Unable to find user/group for %s\n", arguments[1]); - assert(i<arguments.length); -} - -/* - add a foreign security principle - */ -function add_foreign(ldb, subobj, sid, desc) -{ - var add = sprintf(" -dn: CN=%s,CN=ForeignSecurityPrincipals,%s -objectClass: top -objectClass: foreignSecurityPrincipal -description: %s -", - sid, subobj.DOMAINDN, desc); - /* deliberately ignore errors from this, as the records may - already exist */ - ldb.add(add); -} - - -/* - setup a mapping between a sam name and a unix name - */ -function setup_name_mapping(info, ldb, sid, unixname) -{ - var attrs = new Array("dn"); - var res = ldb.search(sprintf("objectSid=%s", sid), - info.subobj.DOMAINDN, ldb.SCOPE_SUBTREE, attrs); - if (res.error != 0 || res.msgs.length != 1) { - info.message("Failed to find record for objectSid %s\n", sid); - return false; - } - var mod = sprintf(" -dn: %s -changetype: modify -replace: unixName -unixName: %s -", - res.msgs[0].dn, unixname); - var ok = ldb.modify(mod); - if (ok.error != 0) { - info.message("name mapping for %s failed - %s\n", - sid, ldb.errstring()); - return false; - } - return true; -} - -/* - return current time as a nt time string -*/ -function nttime() -{ - return "" + sys.nttime(); -} - -/* - return current time as a ldap time string -*/ -function ldaptime() -{ - return sys.ldaptime(sys.nttime()); -} - -/* - return a date string suitable for a dns zone serial number -*/ -function datestring() -{ - var t = sys.ntgmtime(sys.nttime()); - return sprintf("%04u%02u%02u%02u", - t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour); -} - -/* - return first host IP -*/ -function hostip() -{ - var list = sys.interfaces(); - return list[0]; -} - - -/* - return first part of hostname -*/ -function hostname() -{ - var s = split(".", sys.hostname()); - return s[0]; -} - -/* the ldb is in bad shape, possibly due to being built from an - incompatible previous version of the code, so delete it - completely */ -function ldb_delete(info, ldb) -{ - info.message("Deleting " + ldb.filename + "\n"); - var lp = loadparm_init(); - sys.unlink(sprintf("%s/%s", lp.get("private dir"), ldb.filename)); - ldb.transaction_cancel(); - ldb.close(); - var ok = ldb.connect(ldb.filename); - ldb.transaction_start(); - assert(ok); -} - -/* - erase an ldb, removing all records -*/ -function ldb_erase(info, ldb) -{ - var res; - - /* delete the specials */ - ldb.del("@INDEXLIST"); - ldb.del("@ATTRIBUTES"); - ldb.del("@OPTIONS"); - ldb.del("@MODULES"); - ldb.del("@PARTITION"); - ldb.del("@KLUDGEACL"); - - /* and the rest */ - attrs = new Array("dn"); - var basedn = ""; - var res = ldb.search("(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))", basedn, ldb.SCOPE_SUBTREE, attrs); - var i; - if (res.error != 0) { - ldb_delete(info, ldb); - return; - } - for (i=0;i<res.msgs.length;i++) { - ldb.del(res.msgs[i].dn); - } - - var res = ldb.search("(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))", basedn, ldb.SCOPE_SUBTREE, attrs); - if (res.error != 0 || res.msgs.length != 0) { - ldb_delete(info, ldb); - return; - } - assert(res.msgs.length == 0); -} - -/* - erase an ldb, removing all records -*/ -function ldb_erase_partitions(info, ldb, ldapbackend) -{ - var rootDSE_attrs = new Array("namingContexts"); - var lp = loadparm_init(); - var j; - - var res = ldb.search("(objectClass=*)", "", ldb.SCOPE_BASE, rootDSE_attrs); - if (res.error != 0) { - info.message("rootdse search failed: " + res.errstr + "\n"); - assert(res.error == 0); - } - assert(res.msgs.length == 1); - if (typeof(res.msgs[0].namingContexts) == "undefined") { - return; - } - for (j=0; j<res.msgs[0].namingContexts.length; j++) { - var anything = "(|(objectclass=*)(distinguishedName=*))"; - var attrs = new Array("distinguishedName"); - var basedn = res.msgs[0].namingContexts[j]; - var k; - var previous_remaining = 1; - var current_remaining = 0; - - if (ldapbackend && (basedn == info.subobj.DOMAINDN)) { - /* Only delete objects that were created by provision */ - anything = "(objectcategory=*)"; - } - - for (k=0; k < 10 && (previous_remaining != current_remaining); k++) { - /* and the rest */ - var res2 = ldb.search(anything, basedn, ldb.SCOPE_SUBTREE, attrs); - var i; - if (res2.error != 0) { - if (res2.error == 32) { - break; - } else { - info.message("ldb search(2) failed: " + res2.errstr + "\n"); - continue; - } - } - previous_remaining = current_remaining; - current_remaining = res2.msgs.length; - for (i=0;i<res2.msgs.length;i++) { - ldb.del(res2.msgs[i].dn); - } - - var res3 = ldb.search(anything, basedn, ldb.SCOPE_SUBTREE, attrs); - if (res3.error != 0) { - info.message("ldb search(3) failed: " + res3.errstr + "\n"); - continue; - } - if (res3.msgs.length != 0) { - info.message("Failed to delete all records under " + basedn + ", " + res3.msgs.length + " records remaining\n"); - } - } - } -} - -function open_ldb(info, dbname, erase) -{ - var ldb = ldb_init(); - ldb.session_info = info.session_info; - ldb.credentials = info.credentials; - ldb.filename = dbname; - - var connect_ok = ldb.connect(dbname); - if (!connect_ok) { - var lp = loadparm_init(); - sys.unlink(sprintf("%s/%s", lp.get("private dir"), dbname)); - connect_ok = ldb.connect(dbname); - assert(connect_ok); - } - - ldb.transaction_start(); - - if (erase) { - ldb_erase(info, ldb); - } - return ldb; -} - - -/* - setup a ldb in the private dir - */ -function setup_add_ldif(ldif, info, ldb, failok) -{ - var lp = loadparm_init(); - var src = lp.get("setup directory") + "/" + ldif; - - var data = sys.file_load(src); - data = substitute_var(data, info.subobj); - - var add_res = ldb.add(data); - if (add_res.error != 0) { - info.message("ldb load failed: " + add_res.errstr + "\n"); - if (!failok) { - assert(add_res.error == 0); - } - } - return (add_res.error == 0); -} - -function setup_modify_ldif(ldif, info, ldb, failok) -{ - var lp = loadparm_init(); - var src = lp.get("setup directory") + "/" + ldif; - - var data = sys.file_load(src); - data = substitute_var(data, info.subobj); - - var mod_res = ldb.modify(data); - if (mod_res.error != 0) { - info.message("ldb load failed: " + mod_res.errstr + "\n"); - if (!failok) { - assert(mod_res.error == 0); - } - } - return (mod_res.error == 0); -} - - -function setup_ldb(ldif, info, dbname) -{ - var erase = true; - var failok = false; - - if (arguments.length >= 4) { - erase = arguments[3]; - } - if (arguments.length == 5) { - failok = arguments[4]; - } - var ldb = open_ldb(info, dbname, erase); - if (setup_add_ldif(ldif, info, ldb, failok)) { - var commit_ok = ldb.transaction_commit(); - if (!commit_ok) { - info.message("ldb commit failed: " + ldb.errstring() + "\n"); - assert(commit_ok); - } - } -} - -/* - setup a ldb in the private dir - */ -function setup_ldb_modify(ldif, info, ldb) -{ - var lp = loadparm_init(); - - var src = lp.get("setup directory") + "/" + ldif; - - var data = sys.file_load(src); - data = substitute_var(data, info.subobj); - - var mod_res = ldb.modify(data); - if (mod_res.error != 0) { - info.message("ldb load failed: " + mod_res.errstr + "\n"); - return (mod_res.error == 0); - } - return (mod_res.error == 0); -} - -/* - setup a file in the private dir - */ -function setup_file(template, message, fname, subobj) -{ - var lp = loadparm_init(); - var f = fname; - var src = lp.get("setup directory") + "/" + template; - - if (! sys.stat(src)) { - message("Template file not found: %s\n",src); - assert(0); - } - - sys.unlink(f); - - var data = sys.file_load(src); - data = substitute_var(data, subobj); - - ok = sys.file_save(f, data); - if (!ok) { - message("failed to create file: " + f + "\n"); - assert(ok); - } -} - -function provision_default_paths(subobj) -{ - /* subobj.DNSDOMAIN isn't available at this point */ - var dnsdomain = strlower(subobj.REALM); - var lp = loadparm_init(); - var paths = new Object(); - paths.smbconf = lp.filename() - paths.shareconf = lp.get("private dir") + "/" + "share.ldb"; - paths.samdb = lp.get("sam database"); - paths.idmapdb = lp.get("idmap database"); - paths.secrets = lp.get("secrets database"); - paths.templates = lp.get("private dir") + "/" + "templates.ldb"; - paths.keytab = "secrets.keytab"; - paths.dns_keytab = "dns.keytab"; - paths.dns_keytab_abs = lp.get("private dir") + "/" + paths.dns_keytab; - paths.dns = lp.get("private dir") + "/" + dnsdomain + ".zone"; - paths.named_conf = lp.get("private dir") + "/named.conf"; - paths.winsdb = "wins.ldb"; - paths.ldapdir = lp.get("private dir") + "/ldap"; - - paths.s4_ldapi_socket = lp.get("private dir") + "/ldapi"; - paths.phpldapadminconfig = lp.get("private dir") + "/phpldapadmin-config.php"; - - paths.sysvol = lp.get("sysvol", "path"); - - if (paths.sysvol == undefined) { - paths.sysvol = lp.get("lock dir") + "/sysvol"; - } - - paths.netlogon = lp.get("netlogon", "path"); - - if (paths.netlogon == undefined) { - paths.netlogon = paths.sysvol + "/" + dnsdomain + "/scripts"; - } - - return paths; -} - - -/* - setup reasonable name mappings for sam names to unix names -*/ -function setup_name_mappings(info, ldb) -{ - var lp = loadparm_init(); - var attrs = new Array("objectSid"); - var subobj = info.subobj; - - res = ldb.search("objectSid=*", subobj.DOMAINDN, ldb.SCOPE_BASE, attrs); - assert(res.error == 0); - assert(res.msgs.length == 1 && res.msgs[0].objectSid != undefined); - var sid = res.msgs[0].objectSid; - - /* add some foreign sids if they are not present already */ - add_foreign(ldb, subobj, "S-1-5-7", "Anonymous"); - add_foreign(ldb, subobj, "S-1-1-0", "World"); - add_foreign(ldb, subobj, "S-1-5-2", "Network"); - add_foreign(ldb, subobj, "S-1-5-18", "System"); - add_foreign(ldb, subobj, "S-1-5-11", "Authenticated Users"); - - /* some well known sids */ - setup_name_mapping(info, ldb, "S-1-5-7", subobj.NOBODY); - setup_name_mapping(info, ldb, "S-1-1-0", subobj.NOGROUP); - setup_name_mapping(info, ldb, "S-1-5-2", subobj.NOGROUP); - setup_name_mapping(info, ldb, "S-1-5-18", subobj.ROOT); - setup_name_mapping(info, ldb, "S-1-5-11", subobj.USERS); - setup_name_mapping(info, ldb, "S-1-5-32-544", subobj.WHEEL); - setup_name_mapping(info, ldb, "S-1-5-32-545", subobj.USERS); - setup_name_mapping(info, ldb, "S-1-5-32-546", subobj.NOGROUP); - setup_name_mapping(info, ldb, "S-1-5-32-551", subobj.BACKUP); - - /* and some well known domain rids */ - setup_name_mapping(info, ldb, sid + "-500", subobj.ROOT); - setup_name_mapping(info, ldb, sid + "-518", subobj.WHEEL); - setup_name_mapping(info, ldb, sid + "-519", subobj.WHEEL); - setup_name_mapping(info, ldb, sid + "-512", subobj.WHEEL); - setup_name_mapping(info, ldb, sid + "-513", subobj.USERS); - setup_name_mapping(info, ldb, sid + "-520", subobj.WHEEL); - - return true; -} - -function provision_fix_subobj(subobj, paths) -{ - var ldb = ldb_init(); - - subobj.REALM = strupper(subobj.REALM); - subobj.HOSTNAME = strlower(subobj.HOSTNAME); - subobj.DOMAIN = strupper(subobj.DOMAIN); - subobj.NETBIOSNAME = strupper(subobj.HOSTNAME); - subobj.DNSDOMAIN = strlower(subobj.REALM); - subobj.DNSNAME = sprintf("%s.%s", - strlower(subobj.HOSTNAME), - subobj.DNSDOMAIN); - var rdn_list = split(".", subobj.DNSDOMAIN); - subobj.DOMAINDN = "DC=" + join(",DC=", rdn_list); - subobj.ROOTDN = subobj.DOMAINDN; - subobj.CONFIGDN = "CN=Configuration," + subobj.ROOTDN; - subobj.SCHEMADN = "CN=Schema," + subobj.CONFIGDN; - - subobj.MACHINEPASS_B64 = ldb.encode(subobj.MACHINEPASS); - subobj.KRBTGTPASS_B64 = ldb.encode(subobj.KRBTGTPASS); - subobj.ADMINPASS_B64 = ldb.encode(subobj.ADMINPASS); - subobj.DNSPASS_B64 = ldb.encode(subobj.DNSPASS); - - subobj.SAM_LDB = "tdb://" + paths.samdb; - subobj.SECRETS_KEYTAB = paths.keytab; - subobj.DNS_KEYTAB = paths.dns_keytab; - subobj.DNS_KEYTAB_ABS = paths.dns_keytab_abs; - - subobj.LDAPDIR = paths.ldapdir; - var ldap_path_list = split("/", paths.ldapdir); - subobj.LDAPI_URI = "ldapi://" + join("%2F", ldap_path_list) + "%2Fldapi"; - - var s4ldap_path_list = split("/", paths.s4_ldapi_socket); - subobj.S4_LDAPI_URI = "ldapi://" + join("%2F", s4ldap_path_list); - - subobj.LDAPMANAGERDN = "cn=Manager," + subobj.DOMAINDN; - - subobj.NETLOGONPATH = paths.netlogon; - subobj.SYSVOLPATH = paths.sysvol; - - if (subobj.DOMAIN_CONF == undefined) { - subobj.DOMAIN_CONF = subobj.DOMAIN; - } - if (subobj.REALM_CONF == undefined) { - subobj.REALM_CONF = subobj.REALM; - } - if (strlower(subobj.SERVERROLE) != strlower("domain controller")) { - subobj.REALM = subobj.HOSTNAME; - subobj.DOMAIN = subobj.HOSTNAME; - } - - return true; -} - -function provision_become_dc(subobj, message, erase, paths, session_info) -{ - var lp = loadparm_init(); - var sys = sys_init(); - var info = new Object(); - - var ok = provision_fix_subobj(subobj, paths); - assert(ok); - - if (subobj.BACKEND_MOD == undefined) { - subobj.BACKEND_MOD = "repl_meta_data"; - } - - info.subobj = subobj; - info.message = message; - info.session_info = session_info; - - message("Setting up templates into " + paths.templates + "\n"); - setup_ldb("provision_templates.ldif", info, paths.templates); - - /* Also wipes the database */ - message("Setting up " + paths.samdb + " partitions\n"); - setup_ldb("provision_partitions.ldif", info, paths.samdb); - - var samdb = open_ldb(info, paths.samdb, false); - - message("Setting up " + paths.samdb + " attributes\n"); - setup_add_ldif("provision_init.ldif", info, samdb, false); - - message("Setting up " + paths.samdb + " rootDSE\n"); - setup_add_ldif("provision_rootdse_add.ldif", info, samdb, false); - - if (erase) { - message("Erasing data from partitions\n"); - ldb_erase_partitions(info, samdb, undefined); - } - - message("Setting up " + paths.samdb + " indexes\n"); - setup_add_ldif("provision_index.ldif", info, samdb, false); - - ok = samdb.transaction_commit(); - assert(ok); - - message("Setting up " + paths.secrets + "\n"); - setup_ldb("secrets_init.ldif", info, paths.secrets); - - setup_ldb("secrets.ldif", info, paths.secrets, false); - - setup_ldb("secrets_dc.ldif", info, paths.secrets, false); - - return true; -} - -function load_schema(subobj, message, samdb) -{ - var lp = loadparm_init(); - var src = lp.get("setup directory") + "/" + "schema.ldif"; - - if (! sys.stat(src)) { - message("Template file not found: %s\n",src); - assert(0); - } - - var schema_data = sys.file_load(src); - - src = lp.get("setup directory") + "/" + "schema_samba4.ldif"; - - if (! sys.stat(src)) { - message("Template file not found: %s\n",src); - assert(0); - } - - schema_data = schema_data + sys.file_load(src); - - schema_data = substitute_var(schema_data, subobj); - - src = lp.get("setup directory") + "/" + "provision_schema_basedn_modify.ldif"; - - if (! sys.stat(src)) { - message("Template file not found: %s\n",src); - assert(0); - } - - var head_data = sys.file_load(src); - head_data = substitute_var(head_data, subobj); - - var ok = samdb.attach_dsdb_schema_from_ldif(head_data, schema_data); - return ok; -} - - -/* - provision samba4 - caution, this wipes all existing data! -*/ -function provision(subobj, message, blank, paths, session_info, credentials, ldapbackend) -{ - var lp = loadparm_init(); - var sys = sys_init(); - var info = new Object(); - random_init(local); - - var ok = provision_fix_subobj(subobj, paths); - assert(ok); - - if (strlower(subobj.SERVERROLE) == strlower("domain controller")) { - if (subobj.BACKEND_MOD == undefined) { - subobj.BACKEND_MOD = "repl_meta_data"; - } - } else { - if (subobj.BACKEND_MOD == undefined) { - subobj.BACKEND_MOD = "objectguid"; - } - } - - if (subobj.DOMAINGUID != undefined) { - subobj.DOMAINGUID_MOD = sprintf("replace: objectGUID\nobjectGUID: %s\n-", subobj.DOMAINGUID); - } else { - subobj.DOMAINGUID_MOD = ""; - } - - if (subobj.HOSTGUID != undefined) { - subobj.HOSTGUID_ADD = sprintf("objectGUID: %s", subobj.HOSTGUID); - } else { - subobj.HOSTGUID_ADD = ""; - } - - info.subobj = subobj; - info.message = message; - info.credentials = credentials; - info.session_info = session_info; - - /* only install a new smb.conf if there isn't one there already */ - var st = sys.stat(paths.smbconf); - if (st == undefined) { - var smbconfsuffix; - if (strlower(subobj.SERVERROLE) == strlower("domain controller")) { - smbconfsuffix = "dc"; - } else if (strlower(subobj.SERVERROLE) == strlower("member server")) { - smbconfsuffix = "member"; - } else { - smbconfsuffix = subobj.SERVERROLE; - } - message("Setting up " + paths.smbconf +"\n"); - setup_file("provision.smb.conf." + smbconfsuffix, info.message, paths.smbconf, subobj); - lp.reload(); - } - /* only install a new shares config db if there is none */ - st = sys.stat(paths.shareconf); - if (st == undefined) { - message("Setting up share.ldb\n"); - setup_ldb("share.ldif", info, paths.shareconf); - } - - message("Setting up " + paths.secrets + "\n"); - setup_ldb("secrets_init.ldif", info, paths.secrets); - setup_ldb("secrets.ldif", info, paths.secrets, false); - - message("Setting up the registry\n"); - var reg = reg_open(); - reg.apply_patchfile(lp.get("setup directory") + "/provision.reg") - - message("Setting up templates into " + paths.templates + "\n"); - setup_ldb("provision_templates.ldif", info, paths.templates); - - message("Setting up " + paths.idmapdb +"\n"); - setup_ldb("idmap_init.ldif", info, paths.idmapdb); - - message("Setting up sam.ldb partitions\n"); - /* Also wipes the database */ - setup_ldb("provision_partitions.ldif", info, paths.samdb); - - var samdb = open_ldb(info, paths.samdb, false); - - message("Setting up sam.ldb attributes\n"); - setup_add_ldif("provision_init.ldif", info, samdb, false); - - message("Setting up sam.ldb rootDSE\n"); - setup_add_ldif("provision_rootdse_add.ldif", info, samdb, false); - - message("Erasing data from partitions\n"); - ldb_erase_partitions(info, samdb, ldapbackend); - - // (hack) Reload, now we have the partitions and rootdse loaded. - var commit_ok = samdb.transaction_commit(); - if (!commit_ok) { - info.message("samdb commit failed: " + samdb.errstring() + "\n"); - assert(commit_ok); - } - samdb.close(); - - message("Pre-loading the Samba4 and AD schema\n"); - - samdb = open_ldb(info, paths.samdb, false); - - samdb.set_domain_sid(subobj.DOMAINSID); - - if (strlower(subobj.SERVERROLE) == strlower("domain controller")) { - if (subobj.INVOCATIONID == undefined) { - subobj.INVOCATIONID = randguid(); - } - samdb.set_ntds_invocationId(subobj.INVOCATIONID); - if (subobj.BACKEND_MOD == undefined) { - subobj.BACKEND_MOD = "repl_meta_data"; - } - } else { - if (subobj.BACKEND_MOD == undefined) { - subobj.BACKEND_MOD = "objectguid"; - } - } - - var load_schema_ok = load_schema(subobj, message, samdb); - assert(load_schema_ok.is_ok); - - message("Adding DomainDN: " + subobj.DOMAINDN + " (permitted to fail)\n"); - var add_ok = setup_add_ldif("provision_basedn.ldif", info, samdb, true); - message("Modifying DomainDN: " + subobj.DOMAINDN + "\n"); - var modify_basedn_ok = setup_ldb_modify("provision_basedn_modify.ldif", info, samdb); - if (!modify_basedn_ok) { - if (!add_ok) { - message("%s", "Failed to both add and modify " + subobj.DOMAINDN + " in target " + subobj.DOMAINDN_LDB + ": " + samdb.errstring() + "\n"); - message("Perhaps you need to run the provision script with the --ldap-base-dn option, and add this record to the backend manually\n"); - }; - assert(modify_basedn_ok); - }; - - message("Adding configuration container (permitted to fail)\n"); - var add_config_ok = setup_add_ldif("provision_configuration_basedn.ldif", info, samdb, true); - message("Modifying configuration container\n"); - var modify_config_ok = setup_ldb_modify("provision_configuration_basedn_modify.ldif", info, samdb); - if (!modify_config_ok) { - if (!add_config_ok) { - message("%s", "Failed to both add and modify " + subobj.CONFIGDN + " in target " + subobj.CONFIGDN_LDB + ": " + samdb.errstring() + "\n"); - message("Perhaps you need to run the provision script with the --ldap-base-dn option, and add this record to the backend manually\n"); - } - assert(modify_config_ok); - } - - message("Adding schema container (permitted to fail)\n"); - var add_schema_ok = setup_add_ldif("provision_schema_basedn.ldif", info, samdb, true); - message("Modifying schema container\n"); - var modify_schema_ok = setup_ldb_modify("provision_schema_basedn_modify.ldif", info, samdb); - if (!modify_schema_ok) { - if (!add_schema_ok) { - message("%s", "Failed to both add and modify " + subobj.SCHEMADN + " in target " + subobj.SCHEMADN_LDB + ": " + samdb.errstring() + "\n"); - message("Perhaps you need to run the provision script with the --ldap-base-dn option, and add this record to the backend manually\n"); - } - message("Failed to modify the schema container: " + samdb.errstring() + "\n"); - assert(modify_schema_ok); - } - - message("Setting up sam.ldb Samba4 schema\n"); - setup_add_ldif("schema_samba4.ldif", info, samdb, false); - message("Setting up sam.ldb AD schema\n"); - setup_add_ldif("schema.ldif", info, samdb, false); - - message("Setting up sam.ldb configuration data\n"); - setup_add_ldif("provision_configuration.ldif", info, samdb, false); - - message("Setting up display specifiers\n"); - setup_add_ldif("display_specifiers.ldif", info, samdb, false); - - message("Adding users container (permitted to fail)\n"); - var add_users_ok = setup_add_ldif("provision_users_add.ldif", info, samdb, true); - message("Modifying users container\n"); - var modify_users_ok = setup_ldb_modify("provision_users_modify.ldif", info, samdb); - if (!modify_users_ok) { - if (!add_users_ok) { - message("Failed to both add and modify the users container\n"); - } - assert(modify_users_ok); - } - message("Adding computers container (permitted to fail)\n"); - var add_computers_ok = setup_add_ldif("provision_computers_add.ldif", info, samdb, true); - message("Modifying computers container\n"); - var modify_computers_ok = setup_ldb_modify("provision_computers_modify.ldif", info, samdb); - if (!modify_computers_ok) { - if (!add_computers_ok) { - message("Failed to both add and modify the computers container\n"); - } - assert(modify_computers_ok); - } - - message("Setting up sam.ldb data\n"); - setup_add_ldif("provision.ldif", info, samdb, false); - - if (blank != false) { - message("Setting up sam.ldb index\n"); - setup_add_ldif("provision_index.ldif", info, samdb, false); - - message("Setting up sam.ldb rootDSE marking as syncronized\n"); - setup_modify_ldif("provision_rootdse_modify.ldif", info, samdb, false); - - var commit_ok = samdb.transaction_commit(); - if (!commit_ok) { - info.message("ldb commit failed: " + samdb.errstring() + "\n"); - assert(commit_ok); - } - return true; - } - -// message("Activate schema module"); -// setup_modify_ldif("schema_activation.ldif", info, samdb, false); -// -// // (hack) Reload, now we have the schema loaded. -// var commit_ok = samdb.transaction_commit(); -// if (!commit_ok) { -// info.message("samdb commit failed: " + samdb.errstring() + "\n"); -// assert(commit_ok); -// } -// samdb.close(); -// -// samdb = open_ldb(info, paths.samdb, false); -// - message("Setting up sam.ldb users and groups\n"); - setup_add_ldif("provision_users.ldif", info, samdb, false); - - if (strlower(subobj.SERVERROLE) == strlower("domain controller")) { - message("Setting up self join\n"); - setup_add_ldif("provision_self_join.ldif", info, samdb, false); - setup_add_ldif("provision_group_policy.ldif", info, samdb, false); - - sys.mkdir(paths.sysvol, 0755); - sys.mkdir(paths.sysvol + "/"+ subobj.DNSDOMAIN, 0755); - sys.mkdir(paths.sysvol + "/"+ subobj.DNSDOMAIN + "/Policies", 0755); - sys.mkdir(paths.sysvol + "/"+ subobj.DNSDOMAIN + "/Policies/{" + subobj.POLICYGUID + "}", 0755); - sys.mkdir(paths.sysvol + "/"+ subobj.DNSDOMAIN + "/Policies/{" + subobj.POLICYGUID + "}/Machine", 0755); - sys.mkdir(paths.sysvol + "/"+ subobj.DNSDOMAIN + "/Policies/{" + subobj.POLICYGUID + "}/User", 0755); - - sys.mkdir(paths.netlogon, 0755); - - setup_ldb("secrets_dc.ldif", info, paths.secrets, false); - - } - - if (setup_name_mappings(info, samdb) == false) { - return false; - } - - message("Setting up sam.ldb index\n"); - setup_add_ldif("provision_index.ldif", info, samdb, false); - - message("Setting up sam.ldb rootDSE marking as syncronized\n"); - setup_modify_ldif("provision_rootdse_modify.ldif", info, samdb, false); - - var commit_ok = samdb.transaction_commit(); - if (!commit_ok) { - info.message("samdb commit failed: " + samdb.errstring() + "\n"); - assert(commit_ok); - } - - message("Setting up phpLDAPadmin configuration\n"); - setup_file("phpldapadmin-config.php", info.message, paths.phpldapadminconfig, subobj); - message("Please install the phpLDAPadmin configuration located at " + paths.phpldapadminconfig + " into /etc/phpldapadmin/config.php\n"); - - return true; -} - -/* - provision just the schema into a temporary ldb, so we can run ad2oLschema on it -*/ -function provision_schema(subobj, message, tmp_schema_path, paths) -{ - var lp = loadparm_init(); - var sys = sys_init(); - var info = new Object(); - - var ok = provision_fix_subobj(subobj, paths); - assert(ok); - - info.subobj = subobj; - info.message = message; - - message("Setting up sam.ldb partitions\n"); - - /* This will erase anything in the tmp db */ - var samdb = open_ldb(info, tmp_schema_path, true); - - message("Setting up sam.ldb attributes\n"); - setup_add_ldif("provision_init.ldif", info, samdb, false); - - message("Setting up sam.ldb rootDSE\n"); - setup_add_ldif("provision_rootdse_add.ldif", info, samdb, false); - - message("Adding schema container (permitted to fail)\n"); - var add_ok = setup_add_ldif("provision_schema_basedn.ldif", info, samdb, true); - message("Modifying schema container\n"); - var modify_ok = setup_ldb_modify("provision_schema_basedn_modify.ldif", info, samdb); - if (!modify_ok) { - if (!add_ok) { - message("Failed to both add and modify schema dn: " + samdb.errstring() + "\n"); - message("Perhaps you need to run the provision script with the --ldap-base-dn option, and add this record to the backend manually\n"); - assert(modify_ok); - } - message("Failed to modify the schema container: " + samdb.errstring() + "\n"); - assert(modify_ok); - } - - message("Setting up sam.ldb Samba4 schema\n"); - setup_add_ldif("schema_samba4.ldif", info, samdb, false); - message("Setting up sam.ldb AD schema\n"); - setup_add_ldif("schema.ldif", info, samdb, false); - - var commit_ok = samdb.transaction_commit(); - if (!commit_ok) { - info.message("samdb commit failed: " + samdb.errstring() + "\n"); - assert(commit_ok); - } - samdb.close(); -} - -/* Write out a DNS zone file, from the info in the current database */ -function provision_dns(subobj, message, paths, session_info, credentials) -{ - var lp = loadparm_init(); - if (strlower(subobj.SERVERROLE) != strlower("domain controller")) { - message("No DNS zone required for role %s\n", subobj.SERVERROLE); - return; - } - message("Setting up DNS zone: " + subobj.DNSDOMAIN + " \n"); - var ldb = ldb_init(); - ldb.session_info = session_info; - ldb.credentials = credentials; - - /* connect to the sam */ - var ok = ldb.connect(paths.samdb); - assert(ok); - - /* These values may have changed, due to an incoming SamSync, - or may not have been specified, so fetch them from the database */ - - var attrs = new Array("objectGUID"); - res = ldb.search("objectGUID=*", subobj.DOMAINDN, ldb.SCOPE_BASE, attrs); - assert(res.error == 0); - assert(res.msgs.length == 1); - assert(res.msgs[0].objectGUID != undefined); - subobj.DOMAINGUID = res.msgs[0].objectGUID; - - subobj.HOSTGUID = searchone(ldb, subobj.DOMAINDN, "(&(objectClass=computer)(cn=" + subobj.NETBIOSNAME + "))", "objectGUID"); - assert(subobj.HOSTGUID != undefined); - - setup_file("provision.zone", - message, paths.dns, - subobj); - - setup_file("named.conf", - message, paths.named_conf, - subobj); - - message("Please install the zone located in " + paths.dns + " into your DNS server. A sample BIND configuration snippit is at " + paths.named_conf + "\n"); -} - - -/* - guess reasonably default options for provisioning -*/ -function provision_guess() -{ - var subobj = new Object(); - var nss = nss_init(); - var lp = loadparm_init(); - var rdn_list; - random_init(local); - - subobj.SERVERROLE = strlower(lp.get("server role")); - subobj.REALM = strupper(lp.get("realm")); - subobj.DOMAIN = lp.get("workgroup"); - subobj.HOSTNAME = hostname(); - - assert(subobj.REALM); - assert(subobj.DOMAIN); - assert(subobj.HOSTNAME); - - subobj.VERSION = version(); - subobj.HOSTIP = hostip(); - subobj.DOMAINSID = randsid(); - subobj.POLICYGUID = randguid(); - subobj.KRBTGTPASS = randpass(12); - subobj.MACHINEPASS = randpass(12); - subobj.DNSPASS = randpass(12); - subobj.ADMINPASS = randpass(12); - subobj.LDAPMANAGERPASS = randpass(12); - subobj.DEFAULTSITE = "Default-First-Site-Name"; - subobj.DATESTRING = datestring; - subobj.ROOT = findnss(nss.getpwnam, "root"); - subobj.NOBODY = findnss(nss.getpwnam, "nobody"); - subobj.NOGROUP = findnss(nss.getgrnam, "nogroup", "nobody"); - subobj.WHEEL = findnss(nss.getgrnam, "wheel", "root", "staff", "adm"); - subobj.BACKUP = findnss(nss.getgrnam, "backup", "wheel", "root", "staff"); - subobj.USERS = findnss(nss.getgrnam, "users", "guest", "other", "unknown", "usr"); - - //Add modules to the list to activate them by default - //beware often order is important - // - // Some Known ordering constraints: - // - rootdse must be first, as it makes redirects from "" -> cn=rootdse - // - objectclass must be before password_hash, because password_hash checks - // that the objectclass is of type person (filled in by the objectclass - // module when expanding the objectclass list) - // - partition must be last - // - each partition has its own module list then - var modules_list = new Array("rootdse", - "paged_results", - "ranged_results", - "anr", - "server_sort", - "extended_dn", - "asq", - "samldb", - "rdn_name", - "objectclass", - "kludge_acl", - "operational"); - var tdb_modules_list = new Array("subtree_rename", - "subtree_delete", - "linked_attributes"); - var modules_list2 = new Array("show_deleted", - "partition"); - subobj.MODULES_LIST = join(",", modules_list); - subobj.TDB_MODULES_LIST = "," + join(",", tdb_modules_list); - subobj.MODULES_LIST2 = join(",", modules_list2); - subobj.DOMAINDN_LDB = "users.ldb"; - subobj.CONFIGDN_LDB = "configuration.ldb"; - subobj.SCHEMADN_LDB = "schema.ldb"; - subobj.DOMAINDN_MOD = "pdc_fsmo,password_hash,instancetype"; - subobj.CONFIGDN_MOD = "naming_fsmo,instancetype"; - subobj.SCHEMADN_MOD = "schema_fsmo,instancetype"; - - subobj.ACI = "# no aci for local ldb"; - - return subobj; -} - -/* - search for one attribute as a string - */ -function searchone(ldb, basedn, expression, attribute) -{ - var attrs = new Array(attribute); - res = ldb.search(expression, basedn, ldb.SCOPE_SUBTREE, attrs); - if (res.error != 0 || - res.msgs.length != 1 || - res.msgs[0][attribute] == undefined) { - return undefined; - } - return res.msgs[0][attribute]; -} - -/* - modify an account to remove the -*/ -function enable_account(ldb, user_dn) -{ - var attrs = new Array("userAccountControl"); - var res = ldb.search(NULL, user_dn, ldb.SCOPE_ONELEVEL, attrs); - assert(res.error == 0); - assert(res.msgs.length == 1); - var userAccountControl = res.msgs[0].userAccountControl; - userAccountControl = userAccountControl - 2; /* remove disabled bit */ - var mod = sprintf(" -dn: %s -changetype: modify -replace: userAccountControl -userAccountControl: %u -", - user_dn, userAccountControl); - var ok = ldb.modify(mod); - return (ok.error == 0); -} - - -/* - add a new user record -*/ -function newuser(username, unixname, password, message, session_info, credentials) -{ - var lp = loadparm_init(); - var samdb = lp.get("sam database"); - var ldb = ldb_init(); - random_init(local); - ldb.session_info = session_info; - ldb.credentials = credentials; - - /* connect to the sam */ - var ok = ldb.connect(samdb); - assert(ok); - - ldb.transaction_start(); - - /* find the DNs for the domain and the domain users group */ - var attrs = new Array("defaultNamingContext"); - res = ldb.search("defaultNamingContext=*", "", ldb.SCOPE_BASE, attrs); - assert(res.error == 0); - assert(res.msgs.length == 1 && res.msgs[0].defaultNamingContext != undefined); - var domain_dn = res.msgs[0].defaultNamingContext; - assert(domain_dn != undefined); - var dom_users = searchone(ldb, domain_dn, "name=Domain Users", "dn"); - assert(dom_users != undefined); - - var user_dn = sprintf("CN=%s,CN=Users,%s", username, domain_dn); - - - /* - the new user record. note the reliance on the samdb module to fill - in a sid, guid etc - */ - var ldif = sprintf(" -dn: %s -sAMAccountName: %s -unixName: %s -sambaPassword: %s -objectClass: user -", - user_dn, username, - unixname, password); - /* - add the user to the users group as well - */ - var modgroup = sprintf(" -dn: %s -changetype: modify -add: member -member: %s -", - dom_users, user_dn); - - - /* - now the real work - */ - message("Adding user %s\n", user_dn); - ok = ldb.add(ldif); - if (ok.error != 0) { - message("Failed to add %s - %s\n", user_dn, ok.errstr); - return false; - } - - message("Modifying group %s\n", dom_users); - ok = ldb.modify(modgroup); - if (ok.error != 0) { - message("Failed to modify %s - %s\n", dom_users, ok.errstr); - return false; - } - - /* - modify the userAccountControl to remove the disabled bit - */ - ok = enable_account(ldb, user_dn); - if (ok) { - ldb.transaction_commit(); - } - return ok; -} - -// Check whether a name is valid as a NetBIOS name. -// FIXME: There are probably more constraints here. -// crh has a paragraph on this in his book (1.4.1.1) -function valid_netbios_name(name) -{ - if (strlen(name) > 15) return false; - return true; -} - -function provision_validate(subobj, message) -{ - var lp = loadparm_init(); - - if (!valid_netbios_name(subobj.DOMAIN)) { - message("Invalid NetBIOS name for domain\n"); - return false; - } - - if (!valid_netbios_name(subobj.NETBIOSNAME)) { - message("Invalid NetBIOS name for host\n"); - return false; - } - - - if (strupper(lp.get("workgroup")) != strupper(subobj.DOMAIN_CONF)) { - message("workgroup '%s' in smb.conf must match chosen domain '%s'\n", - lp.get("workgroup"), subobj.DOMAIN_CONF); - return false; - } - - if (strupper(lp.get("realm")) != strupper(subobj.REALM_CONF)) { - message("realm '%s' in smb.conf must match chosen realm '%s'\n", - lp.get("realm"), subobj.REALM_CONF); - return false; - } - - if (strlower(lp.get("server role")) != strlower(subobj.SERVERROLE)) { - message("server role '%s' in smb.conf must match chosen role '%s'\n", - lp.get("server role"), subobj.SERVERROLE); - return false; - } - - return true; -} - -function join_domain(domain, netbios_name, join_type, creds, message) -{ - var ctx = NetContext(creds); - var joindom = new Object(); - joindom.domain = domain; - joindom.join_type = join_type; - joindom.netbios_name = netbios_name; - if (!ctx.JoinDomain(joindom)) { - message("Domain Join failed: " + joindom.error_string); - return false; - } - return true; -} - -/* Vampire a remote domain. Session info and credentials are required for for - * access to our local database (might be remote ldap) - */ - -function vampire(domain, session_info, credentials, message) { - var ctx = NetContext(credentials); - var vampire_ctx = new Object(); - var machine_creds = credentials_init(); - machine_creds.set_domain(form.DOMAIN); - if (!machine_creds.set_machine_account()) { - message("Failed to access domain join information!"); - return false; - } - vampire_ctx.machine_creds = machine_creds; - vampire_ctx.session_info = session_info; - if (!ctx.SamSyncLdb(vampire_ctx)) { - message("Migration of remote domain to Samba failed: " + vampire_ctx.error_string); - return false; - } - - return true; -} - -return 0; diff --git a/source4/scripting/python/config.m4 b/source4/scripting/python/config.m4 index 3790071ba8..a61d541049 100644 --- a/source4/scripting/python/config.m4 +++ b/source4/scripting/python/config.m4 @@ -71,3 +71,8 @@ else AC_MSG_ERROR([Python not found. Please install Python 2.x and its development headers/libraries.]) fi +AC_MSG_CHECKING(python library directory) +pythondir=`$PYTHON -c "from distutils import sysconfig; print sysconfig.get_python_lib(1, 0, '\\${prefix}')"` +AC_MSG_RESULT($pythondir) + +AC_SUBST(pythondir) diff --git a/source4/scripting/python/config.mk b/source4/scripting/python/config.mk index 59f628fe18..e57ff1d1ac 100644 --- a/source4/scripting/python/config.mk +++ b/source4/scripting/python/config.mk @@ -5,9 +5,15 @@ smbpython_OBJ_FILES = scripting/python/smbpython.o [SUBSYSTEM::LIBPYTHON] PUBLIC_DEPENDENCIES = EXT_LIB_PYTHON +PRIVATE_DEPENDENCIES = PYTALLOC INIT_FUNCTION_SENTINEL = { NULL, NULL } -LIBPYTHON_OBJ_FILES = $(addprefix scripting/python/, modules.o pytalloc.o) +LIBPYTHON_OBJ_FILES = $(addprefix scripting/python/, modules.o) + +[SUBSYSTEM::PYTALLOC] +PUBLIC_DEPENDENCIES = EXT_LIB_PYTHON + +PYTALLOC_OBJ_FILES = $(addprefix scripting/python/, pytalloc.o) [PYTHON::python_uuid] PRIVATE_DEPENDENCIES = LIBNDR @@ -20,10 +26,6 @@ SWIG_FILE = misc.i python_misc_OBJ_FILES = scripting/python/misc_wrap.o -PYDOCTOR_MODULES=bin/python/ldb.py bin/python/auth.py bin/python/credentials.py bin/python/registry.py bin/python/tdb.py bin/python/security.py bin/python/events.py bin/python/net.py +_PY_FILES = $(shell find scripting/python -name "*.py") -installpython:: pythonmods - @$(SHELL) $(srcdir)/script/installpython.sh \ - $(INSTALLPERMS) \ - $(DESTDIR)$(PYTHONDIR) \ - scripting/python bin/python +$(foreach pyfile, $(_PY_FILES),$(eval $(call python_py_module_template,$(patsubst scripting/python/%,%,$(pyfile)),$(pyfile)))) diff --git a/source4/scripting/python/modules.c b/source4/scripting/python/modules.c index 6cd975c1a9..0fe15b2fda 100644 --- a/source4/scripting/python/modules.c +++ b/source4/scripting/python/modules.c @@ -19,7 +19,6 @@ #include "includes.h" #include <Python.h> -#include "build.h" extern void init_ldb(void); extern void init_security(void); @@ -40,12 +39,10 @@ extern void initdrsuapi(void); extern void initwinreg(void); extern void initepmapper(void); extern void initinitshutdown(void); -static void initdcerpc_misc(void) {} extern void initmgmt(void); extern void initnet(void); extern void initatsvc(void); extern void initsamr(void); -static void initdcerpc_security(void) {} extern void initlsa(void); extern void initsvcctl(void); extern void initwkssvc(void); diff --git a/source4/scripting/python/samba/provision.py b/source4/scripting/python/samba/provision.py index 0e8840646c..ad8eb8bffd 100644 --- a/source4/scripting/python/samba/provision.py +++ b/source4/scripting/python/samba/provision.py @@ -689,6 +689,7 @@ def setup_self_join(samdb, names, domainsid, invocationid, setup_path, policyguid): """Join a host to its own domain.""" + assert isinstance(invocationid, str) setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { "CONFIGDN": names.configdn, "SCHEMADN": names.schemadn, @@ -910,7 +911,7 @@ def provision(setup_dir, message, session_info, domainsid = security.Sid(domainsid) if policyguid is None: - policyguid = uuid.random() + policyguid = str(uuid.uuid4()) if adminpass is None: adminpass = misc.random_password(12) if krbtgtpass is None: @@ -960,7 +961,7 @@ def provision(setup_dir, message, session_info, assert serverrole in ("domain controller", "member server", "standalone") if invocationid is None and serverrole == "domain controller": - invocationid = uuid.random() + invocationid = str(uuid.uuid4()) if not os.path.exists(paths.private_dir): os.mkdir(paths.private_dir) diff --git a/source4/scripting/python/samba/tests/samdb.py b/source4/scripting/python/samba/tests/samdb.py index 0e175bf936..0d4f7bde0e 100644 --- a/source4/scripting/python/samba/tests/samdb.py +++ b/source4/scripting/python/samba/tests/samdb.py @@ -29,18 +29,18 @@ import uuid class SamDBTestCase(TestCaseInTempDir): def setUp(self): super(SamDBTestCase, self).setUp() - invocationid = uuid.random() + invocationid = str(uuid.uuid4()) domaindn = "DC=COM,DC=EXAMPLE" self.domaindn = domaindn configdn = "CN=Configuration," + domaindn schemadn = "CN=Schema," + configdn - domainguid = uuid.random() - policyguid = uuid.random() + domainguid = str(uuid.uuid4()) + policyguid = str(uuid.uuid4()) setup_path = lambda x: os.path.join("setup", x) creds = Credentials() creds.set_anonymous() domainsid = security.random_sid() - hostguid = uuid.random() + hostguid = str(uuid.uuid4()) path = os.path.join(self.tempdir, "samdb.ldb") self.samdb = setup_samdb(path, setup_path, system_session(), creds, cmdline_loadparm, schemadn, configdn, diff --git a/source4/scripting/python/uuidmodule.c b/source4/scripting/python/uuidmodule.c index 18cfb6ce32..98ef9adaa9 100644 --- a/source4/scripting/python/uuidmodule.c +++ b/source4/scripting/python/uuidmodule.c @@ -46,7 +46,7 @@ static PyObject *uuid_random(PyObject *self, PyObject *args) } static PyMethodDef methods[] = { - { "random", (PyCFunction)uuid_random, METH_VARARGS, NULL}, + { "uuid4", (PyCFunction)uuid_random, METH_VARARGS, NULL}, { NULL, NULL } }; |