1 2/* 3 * XML password backend for samba 4 * Copyright (C) Jelmer Vernooij 2002 5 * Some parts based on the libxml gjobread example by Daniel Veillard 6 * 7 * This program is free software; you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License as published by the Free 9 * Software Foundation; either version 2 of the License, or (at your option) 10 * any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 * more details. 16 * 17 * You should have received a copy of the GNU General Public License along with 18 * this program; if not, write to the Free Software Foundation, Inc., 675 19 * Mass Ave, Cambridge, MA 02139, USA. 20 */ 21 22/* FIXME: 23 * - Support stdin input by using '-' 24 * - Be faster. Don't rewrite the whole file when adding a user, but store it in the memory and save it when exiting. Requires changes to samba source. 25 * - Gives the ability to read/write to standard input/output 26 * - Do locking! 27 * - Better names! 28 */ 29 30 31#define XML_URL "http://samba.org/samba/DTD/passdb" 32 33#include "includes.h" 34 35#include <libxml/xmlmemory.h> 36#include <libxml/parser.h> 37 38static int xmlsam_debug_level = DBGC_ALL; 39 40#undef DBGC_CLASS 41#define DBGC_CLASS xmlsam_debug_level 42 43 44/* Helper utilities for charset conversion */ 45static xmlNodePtr smbXmlNewChild(xmlNodePtr prnt, xmlNsPtr ns, const xmlChar *name, const char *content) 46{ 47 char *string_utf8; 48 xmlNodePtr ret; 49 50 if(!content) return xmlNewChild(prnt, ns, name, NULL); 51 52 53 if(push_utf8_allocate(&string_utf8,content) == (size_t)-1) 54 return NULL; 55 56 ret = xmlNewTextChild(prnt, ns, name, string_utf8); 57 58 SAFE_FREE(string_utf8); 59 60 return ret; 61} 62 63 64static char * iota(int a) { 65 static char tmp[10]; 66 67 snprintf(tmp, 9, "%d", a); 68 return tmp; 69} 70 71static BOOL parsePass(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur, SAM_ACCOUNT * u) 72{ 73 pstring temp; 74 75 cur = cur->xmlChildrenNode; 76 while (cur != NULL) { 77 if (strcmp(cur->name, "crypt")) 78 DEBUG(0, ("Unknown element %s\n", cur->name)); 79 else { 80 if (!strcmp(xmlGetProp(cur, "type"), "nt") 81 && 82 pdb_gethexpwd(xmlNodeListGetString 83 (doc, cur->xmlChildrenNode, 1), temp)) 84 pdb_set_nt_passwd(u, temp, PDB_SET); 85 else if (!strcmp(xmlGetProp(cur, "type"), "lanman") 86 && 87 pdb_gethexpwd(xmlNodeListGetString 88 (doc, cur->xmlChildrenNode, 1), temp)) 89 pdb_set_lanman_passwd(u, temp, PDB_SET); 90 else 91 DEBUG(0, 92 ("Unknown crypt type: %s\n", 93 xmlGetProp(cur, "type"))); 94 } 95 cur = cur->next; 96 } 97 return True; 98} 99 100static BOOL parseUser(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur, SAM_ACCOUNT * u) 101{ 102 char *tmp; 103 DOM_SID sid; 104 105 tmp = xmlGetProp(cur, "sid"); 106 if (tmp){ 107 string_to_sid(&sid, tmp); 108 pdb_set_user_sid(u, &sid, PDB_SET); 109 } 110 pdb_set_username(u, xmlGetProp(cur, "name"), PDB_SET); 111 /* We don't care what the top level element name is */ 112 cur = cur->xmlChildrenNode; 113 while (cur != NULL) { 114 if ((!strcmp(cur->name, "group")) && (cur->ns == ns)) { 115 tmp = xmlGetProp(cur, "sid"); 116 if (tmp){ 117 string_to_sid(&sid, tmp); 118 pdb_set_group_sid(u, &sid, PDB_SET); 119 } 120 } 121 122 else if ((!strcmp(cur->name, "domain")) && (cur->ns == ns)) 123 pdb_set_domain(u, 124 xmlNodeListGetString(doc, cur->xmlChildrenNode, 125 1), PDB_SET); 126 127 else if (!strcmp(cur->name, "fullname") && cur->ns == ns) 128 pdb_set_fullname(u, 129 xmlNodeListGetString(doc, 130 cur->xmlChildrenNode, 131 1), PDB_SET); 132 133 else if (!strcmp(cur->name, "nt_username") && cur->ns == ns) 134 pdb_set_nt_username(u, 135 xmlNodeListGetString(doc, 136 cur->xmlChildrenNode, 137 1), PDB_SET); 138 139 else if (!strcmp(cur->name, "logon_script") && cur->ns == ns) 140 pdb_set_logon_script(u, 141 xmlNodeListGetString(doc, 142 cur->xmlChildrenNode, 143 1), PDB_SET); 144 145 else if (!strcmp(cur->name, "profile_path") && cur->ns == ns) 146 pdb_set_profile_path(u, 147 xmlNodeListGetString(doc, 148 cur->xmlChildrenNode, 149 1), PDB_SET); 150 151 else if (!strcmp(cur->name, "logon_time") && cur->ns == ns) 152 pdb_set_logon_time(u, 153 atol(xmlNodeListGetString 154 (doc, cur->xmlChildrenNode, 1)), PDB_SET); 155 156 else if (!strcmp(cur->name, "logoff_time") && cur->ns == ns) 157 pdb_set_logoff_time(u, 158 atol(xmlNodeListGetString 159 (doc, cur->xmlChildrenNode, 1)), 160 PDB_SET); 161 162 else if (!strcmp(cur->name, "kickoff_time") && cur->ns == ns) 163 pdb_set_kickoff_time(u, 164 atol(xmlNodeListGetString 165 (doc, cur->xmlChildrenNode, 1)), 166 PDB_SET); 167 168 else if (!strcmp(cur->name, "logon_divs") && cur->ns == ns) 169 pdb_set_logon_divs(u, 170 atol(xmlNodeListGetString 171 (doc, cur->xmlChildrenNode, 1)), PDB_SET); 172 173 else if (!strcmp(cur->name, "hours_len") && cur->ns == ns) 174 pdb_set_hours_len(u, 175 atol(xmlNodeListGetString 176 (doc, cur->xmlChildrenNode, 1)), PDB_SET); 177 178 else if (!strcmp(cur->name, "bad_password_count") && cur->ns == ns) 179 pdb_set_bad_password_count(u, 180 atol(xmlNodeListGetString 181 (doc, cur->xmlChildrenNode, 1)), PDB_SET); 182 183 else if (!strcmp(cur->name, "logon_count") && cur->ns == ns) 184 pdb_set_logon_count(u, 185 atol(xmlNodeListGetString 186 (doc, cur->xmlChildrenNode, 1)), PDB_SET); 187 188 else if (!strcmp(cur->name, "unknown_6") && cur->ns == ns) 189 pdb_set_unknown_6(u, 190 atol(xmlNodeListGetString 191 (doc, cur->xmlChildrenNode, 1)), PDB_SET); 192 193 else if (!strcmp(cur->name, "homedir") && cur->ns == ns) 194 pdb_set_homedir(u, 195 xmlNodeListGetString(doc, cur->xmlChildrenNode, 196 1), PDB_SET); 197 198 else if (!strcmp(cur->name, "unknown_str") && cur->ns == ns) 199 pdb_set_unknown_str(u, 200 xmlNodeListGetString(doc, 201 cur->xmlChildrenNode, 202 1), PDB_SET); 203 204 else if (!strcmp(cur->name, "dir_drive") && cur->ns == ns) 205 pdb_set_dir_drive(u, 206 xmlNodeListGetString(doc, 207 cur->xmlChildrenNode, 208 1), PDB_SET); 209 210 else if (!strcmp(cur->name, "munged_dial") && cur->ns == ns) 211 pdb_set_munged_dial(u, 212 xmlNodeListGetString(doc, 213 cur->xmlChildrenNode, 214 1), PDB_SET); 215 216 else if (!strcmp(cur->name, "acct_desc") && cur->ns == ns) 217 pdb_set_acct_desc(u, 218 xmlNodeListGetString(doc, 219 cur->xmlChildrenNode, 220 1), PDB_SET); 221 222 else if (!strcmp(cur->name, "acct_ctrl") && cur->ns == ns) 223 pdb_set_acct_ctrl(u, 224 atol(xmlNodeListGetString 225 (doc, cur->xmlChildrenNode, 1)), PDB_SET); 226 227 else if (!strcmp(cur->name, "workstations") && cur->ns == ns) 228 pdb_set_workstations(u, 229 xmlNodeListGetString(doc, 230 cur->xmlChildrenNode, 231 1), PDB_SET); 232 233 else if ((!strcmp(cur->name, "password")) && (cur->ns == ns)) { 234 tmp = xmlGetProp(cur, "last_set"); 235 if (tmp) 236 pdb_set_pass_last_set_time(u, atol(tmp), PDB_SET); 237 tmp = xmlGetProp(cur, "must_change"); 238 if (tmp) 239 pdb_set_pass_must_change_time(u, atol(tmp), PDB_SET); 240 tmp = xmlGetProp(cur, "can_change"); 241 if (tmp) 242 pdb_set_pass_can_change_time(u, atol(tmp), PDB_SET); 243 parsePass(doc, ns, cur, u); 244 } 245 246 else 247 DEBUG(0, ("Unknown element %s\n", cur->name)); 248 cur = cur->next; 249 } 250 251 return True; 252} 253 254typedef struct pdb_xml { 255 char *location; 256 char written; 257 xmlDocPtr doc; 258 xmlNodePtr users; 259 xmlNodePtr pwent; 260 xmlNsPtr ns; 261} pdb_xml; 262 263static xmlNodePtr parseSambaXMLFile(struct pdb_xml *data) 264{ 265 xmlNodePtr cur; 266 267 data->doc = xmlParseFile(data->location); 268 if (data->doc == NULL) 269 return NULL; 270 271 cur = xmlDocGetRootElement(data->doc); 272 if (!cur) { 273 DEBUG(0, ("empty document\n")); 274 xmlFreeDoc(data->doc); 275 return NULL; 276 } 277 data->ns = xmlSearchNsByHref(data->doc, cur, XML_URL); 278 if (!data->ns) { 279 DEBUG(0, 280 ("document of the wrong type, samba user namespace not found\n")); 281 xmlFreeDoc(data->doc); 282 return NULL; 283 } 284 if (strcmp(cur->name, "samba")) { 285 DEBUG(0, ("document of the wrong type, root node != samba")); 286 xmlFreeDoc(data->doc); 287 return NULL; 288 } 289 290 cur = cur->xmlChildrenNode; 291 while (cur && xmlIsBlankNode(cur)) { 292 cur = cur->next; 293 } 294 if (!cur) 295 return NULL; 296 if ((strcmp(cur->name, "users")) || (cur->ns != data->ns)) { 297 DEBUG(0, ("document of the wrong type, was '%s', users expected", 298 cur->name)); 299 DEBUG(0, ("xmlDocDump follows\n")); 300 xmlDocDump(stderr, data->doc); 301 DEBUG(0, ("xmlDocDump finished\n")); 302 xmlFreeDoc(data->doc); 303 return NULL; 304 } 305 data->users = cur; 306 cur = cur->xmlChildrenNode; 307 return cur; 308} 309 310static NTSTATUS xmlsam_setsampwent(struct pdb_methods *methods, BOOL update, uint16 acb_mask) 311{ 312 pdb_xml *data; 313 314 if (!methods) { 315 DEBUG(0, ("Invalid methods\n")); 316 return NT_STATUS_INVALID_PARAMETER; 317 } 318 data = (pdb_xml *) methods->private_data; 319 if (!data) { 320 DEBUG(0, ("Invalid pdb_xml_data\n")); 321 return NT_STATUS_INVALID_PARAMETER; 322 } 323 data->pwent = parseSambaXMLFile(data); 324 if (!data->pwent) 325 return NT_STATUS_UNSUCCESSFUL; 326 327 return NT_STATUS_OK; 328} 329 330/*************************************************************** 331 End enumeration of the passwd list. 332 ****************************************************************/ 333 334static void xmlsam_endsampwent(struct pdb_methods *methods) 335{ 336 pdb_xml *data; 337 338 if (!methods) { 339 DEBUG(0, ("Invalid methods\n")); 340 return; 341 } 342 343 data = (pdb_xml *) methods->private_data; 344 345 if (!data) { 346 DEBUG(0, ("Invalid pdb_xml_data\n")); 347 return; 348 } 349 350 xmlFreeDoc(data->doc); 351 data->doc = NULL; 352 data->pwent = NULL; 353} 354 355/***************************************************************** 356 Get one SAM_ACCOUNT from the list (next in line) 357 *****************************************************************/ 358 359static NTSTATUS xmlsam_getsampwent(struct pdb_methods *methods, SAM_ACCOUNT * user) 360{ 361 pdb_xml *data; 362 363 if (!methods) { 364 DEBUG(0, ("Invalid methods\n")); 365 return NT_STATUS_INVALID_PARAMETER; 366 } 367 data = (pdb_xml *) methods->private_data; 368 369 if (!data) { 370 DEBUG(0, ("Invalid pdb_xml_data\n")); 371 return NT_STATUS_INVALID_PARAMETER; 372 } 373 374 while (data->pwent) { 375 if ((!strcmp(data->pwent->name, "user")) && 376 (data->pwent->ns == data->ns)) { 377 378 parseUser(data->doc, data->ns, data->pwent, user); 379 data->pwent = data->pwent->next; 380 return NT_STATUS_OK; 381 } 382 data->pwent = data->pwent->next; 383 } 384 return NT_STATUS_UNSUCCESSFUL; 385} 386 387/*************************************************************************** 388 Adds an existing SAM_ACCOUNT 389 ****************************************************************************/ 390 391static NTSTATUS xmlsam_add_sam_account(struct pdb_methods *methods, SAM_ACCOUNT * u) 392{ 393 pstring temp; 394 fstring sid_str; 395 xmlNodePtr cur, user, pass, root; 396 pdb_xml *data; 397 398 DEBUG(10, ("xmlsam_add_sam_account called!\n")); 399 400 if (!methods) { 401 DEBUG(0, ("Invalid methods\n")); 402 return NT_STATUS_INVALID_PARAMETER; 403 } 404 405 data = (pdb_xml *) methods->private_data; 406 if (!data) { 407 DEBUG(0, ("Invalid pdb_xml_data\n")); 408 return NT_STATUS_INVALID_PARAMETER; 409 } 410 411 /* Create a new document if we can't open the current one */ 412 if (!parseSambaXMLFile(data)) { 413 DEBUG(0, ("Can't load current XML file, creating a new one\n")); 414 data->doc = xmlNewDoc(XML_DEFAULT_VERSION); 415 root = xmlNewDocNode(data->doc, NULL, "samba", NULL); 416 cur = xmlDocSetRootElement(data->doc, root); 417 data->ns = xmlNewNs(root, XML_URL, "samba"); 418 data->users = smbXmlNewChild(root, data->ns, "users", NULL); 419 } 420 421 user = smbXmlNewChild(data->users, data->ns, "user", NULL); 422 xmlNewProp(user, "sid", 423 sid_to_string(sid_str, pdb_get_user_sid(u))); 424 425 if (pdb_get_username(u) && strcmp(pdb_get_username(u), "")) 426 xmlNewProp(user, "name", pdb_get_username(u)); 427 428 cur = smbXmlNewChild(user, data->ns, "group", NULL); 429 430 xmlNewProp(cur, "sid", 431 sid_to_string(sid_str, pdb_get_group_sid(u))); 432 433 if (pdb_get_init_flags(u, PDB_LOGONTIME) != PDB_DEFAULT) 434 smbXmlNewChild(user, data->ns, "logon_time", 435 iota(pdb_get_logon_time(u))); 436 437 if (pdb_get_init_flags(u, PDB_LOGOFFTIME) != PDB_DEFAULT) 438 smbXmlNewChild(user, data->ns, "logoff_time", 439 iota(pdb_get_logoff_time(u))); 440 441 if (pdb_get_init_flags(u, PDB_KICKOFFTIME) != PDB_DEFAULT) 442 smbXmlNewChild(user, data->ns, "kickoff_time", 443 iota(pdb_get_kickoff_time(u))); 444 445 if (pdb_get_domain(u) && strcmp(pdb_get_domain(u), "")) 446 smbXmlNewChild(user, data->ns, "domain", pdb_get_domain(u)); 447 448 if (pdb_get_nt_username(u) && strcmp(pdb_get_nt_username(u), "")) 449 smbXmlNewChild(user, data->ns, "nt_username", pdb_get_nt_username(u)); 450 451 if (pdb_get_fullname(u) && strcmp(pdb_get_fullname(u), "")) 452 smbXmlNewChild(user, data->ns, "fullname", pdb_get_fullname(u)); 453 454 if (pdb_get_homedir(u) && strcmp(pdb_get_homedir(u), "")) 455 smbXmlNewChild(user, data->ns, "homedir", pdb_get_homedir(u)); 456 457 if (pdb_get_dir_drive(u) && strcmp(pdb_get_dir_drive(u), "")) 458 smbXmlNewChild(user, data->ns, "dir_drive", pdb_get_dir_drive(u)); 459 460 if (pdb_get_logon_script(u) && strcmp(pdb_get_logon_script(u), "")) 461 smbXmlNewChild(user, data->ns, "logon_script", 462 pdb_get_logon_script(u)); 463 464 if (pdb_get_profile_path(u) && strcmp(pdb_get_profile_path(u), "")) 465 smbXmlNewChild(user, data->ns, "profile_path", 466 pdb_get_profile_path(u)); 467 468 if (pdb_get_acct_desc(u) && strcmp(pdb_get_acct_desc(u), "")) 469 smbXmlNewChild(user, data->ns, "acct_desc", pdb_get_acct_desc(u)); 470 471 if (pdb_get_workstations(u) && strcmp(pdb_get_workstations(u), "")) 472 smbXmlNewChild(user, data->ns, "workstations", 473 pdb_get_workstations(u)); 474 475 if (pdb_get_unknown_str(u) && strcmp(pdb_get_unknown_str(u), "")) 476 smbXmlNewChild(user, data->ns, "unknown_str", pdb_get_unknown_str(u)); 477 478 if (pdb_get_munged_dial(u) && strcmp(pdb_get_munged_dial(u), "")) 479 smbXmlNewChild(user, data->ns, "munged_dial", pdb_get_munged_dial(u)); 480 481 482 /* Password stuff */ 483 pass = smbXmlNewChild(user, data->ns, "password", NULL); 484 if (pdb_get_pass_last_set_time(u)) 485 xmlNewProp(pass, "last_set", iota(pdb_get_pass_last_set_time(u))); 486 if (pdb_get_init_flags(u, PDB_CANCHANGETIME) != PDB_DEFAULT) 487 xmlNewProp(pass, "can_change", 488 iota(pdb_get_pass_can_change_time(u))); 489 490 if (pdb_get_init_flags(u, PDB_MUSTCHANGETIME) != PDB_DEFAULT) 491 xmlNewProp(pass, "must_change", 492 iota(pdb_get_pass_must_change_time(u))); 493 494 495 if (pdb_get_lanman_passwd(u)) { 496 pdb_sethexpwd(temp, pdb_get_lanman_passwd(u), 497 pdb_get_acct_ctrl(u)); 498 cur = smbXmlNewChild(pass, data->ns, "crypt", temp); 499 xmlNewProp(cur, "type", "lanman"); 500 } 501 502 if (pdb_get_nt_passwd(u)) { 503 pdb_sethexpwd(temp, pdb_get_nt_passwd(u), pdb_get_acct_ctrl(u)); 504 cur = smbXmlNewChild(pass, data->ns, "crypt", temp); 505 xmlNewProp(cur, "type", "nt"); 506 } 507 508 smbXmlNewChild(user, data->ns, "acct_ctrl", iota(pdb_get_acct_ctrl(u))); 509 510 if (pdb_get_logon_divs(u)) 511 smbXmlNewChild(user, data->ns, "logon_divs", 512 iota(pdb_get_logon_divs(u))); 513 514 if (pdb_get_hours_len(u)) 515 smbXmlNewChild(user, data->ns, "hours_len", 516 iota(pdb_get_hours_len(u))); 517 518 smbXmlNewChild(user, data->ns, "bad_password_count", iota(pdb_get_bad_password_count(u))); 519 smbXmlNewChild(user, data->ns, "logon_count", iota(pdb_get_logon_count(u))); 520 smbXmlNewChild(user, data->ns, "unknown_6", iota(pdb_get_unknown_6(u))); 521 xmlSaveFile(data->location, data->doc); 522 523 return NT_STATUS_OK; 524} 525 526static NTSTATUS xmlsam_init(PDB_CONTEXT * pdb_context, PDB_METHODS ** pdb_method, 527 const char *location) 528{ 529 NTSTATUS nt_status; 530 pdb_xml *data; 531 532 xmlsam_debug_level = debug_add_class("xmlsam"); 533 if (xmlsam_debug_level == -1) { 534 xmlsam_debug_level = DBGC_ALL; 535 DEBUG(0, ("xmlsam: Couldn't register custom debugging class!\n")); 536 } 537 538 if (!pdb_context) { 539 DEBUG(0, ("invalid pdb_methods specified\n")); 540 return NT_STATUS_UNSUCCESSFUL; 541 } 542 543 if (!NT_STATUS_IS_OK 544 (nt_status = make_pdb_methods(pdb_context->mem_ctx, pdb_method))) { 545 return nt_status; 546 } 547 548 (*pdb_method)->name = "xmlsam"; 549 550 (*pdb_method)->setsampwent = xmlsam_setsampwent; 551 (*pdb_method)->endsampwent = xmlsam_endsampwent; 552 (*pdb_method)->getsampwent = xmlsam_getsampwent; 553 (*pdb_method)->add_sam_account = xmlsam_add_sam_account; 554 (*pdb_method)->getsampwnam = NULL; 555 (*pdb_method)->getsampwsid = NULL; 556 (*pdb_method)->update_sam_account = NULL; 557 (*pdb_method)->delete_sam_account = NULL; 558 (*pdb_method)->getgrsid = NULL; 559 (*pdb_method)->getgrgid = NULL; 560 (*pdb_method)->getgrnam = NULL; 561 (*pdb_method)->add_group_mapping_entry = NULL; 562 (*pdb_method)->update_group_mapping_entry = NULL; 563 (*pdb_method)->delete_group_mapping_entry = NULL; 564 (*pdb_method)->enum_group_mapping = NULL; 565 566 data = talloc(pdb_context->mem_ctx, sizeof(pdb_xml)); 567 data->location = talloc_strdup(pdb_context->mem_ctx, (location ? location : "passdb.xml")); 568 data->pwent = NULL; 569 data->written = 0; 570 (*pdb_method)->private_data = data; 571 572 LIBXML_TEST_VERSION xmlKeepBlanksDefault(0); 573 574 return NT_STATUS_OK; 575} 576 577NTSTATUS pdb_xml_init(void) 578{ 579 return smb_register_passdb(PASSDB_INTERFACE_VERSION, "xml", xmlsam_init); 580} 581