1 /* 2 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 7 /* 8 * kadmin/ldap_util/kdb5_ldap_util.c 9 * 10 * (C) Copyright 1990,1991, 1996 by the Massachusetts Institute of Technology. 11 * All Rights Reserved. 12 * 13 * Export of this software from the United States of America may 14 * require a specific license from the United States Government. 15 * It is the responsibility of any person or organization contemplating 16 * export to obtain such a license before exporting. 17 * 18 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 19 * distribute this software and its documentation for any purpose and 20 * without fee is hereby granted, provided that the above copyright 21 * notice appear in all copies and that both that copyright notice and 22 * this permission notice appear in supporting documentation, and that 23 * the name of M.I.T. not be used in advertising or publicity pertaining 24 * to distribution of the software without specific, written prior 25 * permission. Furthermore if you modify this software you must label 26 * your software as modified software and not distribute it in such a 27 * fashion that it might be confused with the original M.I.T. software. 28 * M.I.T. makes no representations about the suitability of 29 * this software for any purpose. It is provided "as is" without express 30 * or implied warranty. 31 * 32 * 33 * Edit a KDC database. 34 */ 35 36 /* 37 * Copyright (C) 1998 by the FundsXpress, INC. 38 * 39 * All rights reserved. 40 * 41 * Export of this software from the United States of America may require 42 * a specific license from the United States Government. It is the 43 * responsibility of any person or organization contemplating export to 44 * obtain such a license before exporting. 45 * 46 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 47 * distribute this software and its documentation for any purpose and 48 * without fee is hereby granted, provided that the above copyright 49 * notice appear in all copies and that both that copyright notice and 50 * this permission notice appear in supporting documentation, and that 51 * the name of FundsXpress. not be used in advertising or publicity pertaining 52 * to distribution of the software without specific, written prior 53 * permission. FundsXpress makes no representations about the suitability of 54 * this software for any purpose. It is provided "as is" without express 55 * or implied warranty. 56 * 57 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 58 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 59 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 60 */ 61 62 /* Copyright (c) 2004-2005, Novell, Inc. 63 * All rights reserved. 64 * 65 * Redistribution and use in source and binary forms, with or without 66 * modification, are permitted provided that the following conditions are met: 67 * 68 * * Redistributions of source code must retain the above copyright notice, 69 * this list of conditions and the following disclaimer. 70 * * Redistributions in binary form must reproduce the above copyright 71 * notice, this list of conditions and the following disclaimer in the 72 * documentation and/or other materials provided with the distribution. 73 * * The copyright holder's name is not used to endorse or promote products 74 * derived from this software without specific prior written permission. 75 * 76 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 77 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 78 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 79 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 80 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 81 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 82 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 83 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 84 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 85 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 86 * POSSIBILITY OF SUCH DAMAGE. 87 */ 88 89 #include <stdio.h> 90 #include <time.h> 91 92 #include <k5-int.h> 93 #include <kadm5/admin.h> 94 #include <adm_proto.h> 95 #include <libintl.h> 96 #include <locale.h> 97 #include "kdb5_ldap_util.h" 98 99 typedef void (*cmd_func)(int, char **); 100 int cmd_index(char *name); 101 102 char *mkey_password = 0; 103 int exit_status = 0; 104 krb5_context util_context; 105 kadm5_config_params global_params; 106 krb5_boolean db_inited = FALSE; 107 108 char *progname; 109 krb5_boolean manual_mkey = FALSE; 110 111 /* 112 * This function prints the usage of kdb5_ldap_util, which is 113 * the LDAP configuration utility. 114 */ 115 void usage() 116 { 117 fprintf(stderr, "%s: " 118 "kdb5_ldap_util [-D user_dn [-w passwd]] [-H ldapuri]\n" 119 "\tcmd [cmd_options]\n" 120 121 /* Create realm */ 122 "create [-subtrees subtree_dn_list] [-sscope search_scope] [-containerref container_reference_dn]\n" 123 #ifdef HAVE_EDIRECTORY 124 "\t\t[-kdcdn kdc_service_list] [-admindn admin_service_list]\n" 125 "\t\t[-pwddn passwd_service_list]\n" 126 #endif 127 "\t\t[-m|-P password|-sf stashfilename] [-k mkeytype] [-s]\n" 128 "\t\t[-maxtktlife max_ticket_life] [-maxrenewlife max_renewable_ticket_life]\n" 129 "\t\t[ticket_flags] [-r realm]\n" 130 131 /* modify realm */ 132 "modify [-subtrees subtree_dn_list] [-sscope search_scope] [-containerref container_reference_dn]\n" 133 #ifdef HAVE_EDIRECTORY 134 "\t\t[-kdcdn kdc_service_list |\n" 135 "\t\t[-clearkdcdn kdc_service_list] [-addkdcdn kdc_service_list]]\n" 136 "\t\t[-admindn admin_service_list | [-clearadmindn admin_service_list]\n" 137 "\t\t[-addadmindn admin_service_list]] [-pwddn passwd_service_list |\n" 138 "\t\t[-clearpwddn passwd_service_list] [-addpwddn passwd_service_list]]\n" 139 #endif 140 "\t\t[-maxtktlife max_ticket_life] [-maxrenewlife max_renewable_ticket_life]\n" 141 "\t\t[ticket_flags] [-r realm]\n" 142 /* View realm */ 143 "view [-r realm]\n" 144 145 /* Destroy realm */ 146 "destroy [-f] [-r realm]\n" 147 148 /* List realms */ 149 "list\n" 150 151 #ifdef HAVE_EDIRECTORY 152 /* Create Service */ 153 "create_service {-kdc|-admin|-pwd} [-servicehost service_host_list]\n" 154 "\t\t[-realm realm_list] \n" 155 "\t\t[-randpw|-fileonly] [-f filename] service_dn\n" 156 157 /* Modify service */ 158 "modify_service [-servicehost service_host_list |\n" 159 "\t\t[-clearservicehost service_host_list]\n" 160 "\t\t[-addservicehost service_host_list]]\n" 161 "\t\t[-realm realm_list | [-clearrealm realm_list]\n" 162 "\t\t[-addrealm realm_list]] service_dn\n" 163 164 /* View Service */ 165 "view_service service_dn\n" 166 167 /* Destroy Service */ 168 "destroy_service [-force] [-f stashfilename] service_dn\n" 169 170 /* List services */ 171 "list_service [-basedn base_dn]\n" 172 173 /* Set Service password */ 174 "setsrvpw [-randpw|-fileonly] [-f filename] service_dn\n" 175 176 #else 177 178 /* Stash the service password */ 179 "stashsrvpw [-f filename] service_dn\n" 180 181 #endif 182 183 /* Create policy */ 184 "create_policy [-r realm] [-maxtktlife max_ticket_life]\n" 185 "\t\t[-maxrenewlife max_renewable_ticket_life] [ticket_flags] policy\n" 186 187 /* Modify policy */ 188 "modify_policy [-r realm] [-maxtktlife max_ticket_life]\n" 189 "\t\t[-maxrenewlife max_renewable_ticket_life] [ticket_flags] policy\n" 190 191 /* View policy */ 192 "view_policy [-r realm] policy\n" 193 194 /* Destroy policy */ 195 "destroy_policy [-r realm] [-force] policy\n" 196 197 /* List policies */ 198 "list_policy [-r realm]\n", 199 gettext("Usage")); 200 } 201 202 void db_usage (int type) { 203 /* 204 * This should print usage of 'type' command. For now, we will print usage 205 * of all commands. 206 */ 207 usage (); 208 } 209 210 /* The help messages for all sub-commands should be in the 211 * same order as listed in this table. 212 */ 213 static struct _cmd_table { 214 char *name; 215 cmd_func func; 216 int opendb; 217 } cmd_table[] = { 218 {"create", kdb5_ldap_create, 1}, 219 {"modify", kdb5_ldap_modify, 1}, 220 {"view", kdb5_ldap_view, 1}, 221 {"destroy", kdb5_ldap_destroy, 1}, 222 {"list", kdb5_ldap_list, 1}, 223 #ifdef HAVE_EDIRECTORY 224 {"create_service", kdb5_ldap_create_service, 1}, 225 {"modify_service", kdb5_ldap_modify_service, 1}, 226 {"view_service", kdb5_ldap_view_service, 1}, 227 {"destroy_service", kdb5_ldap_destroy_service, 1}, 228 {"list_service",kdb5_ldap_list_services,1}, 229 {"setsrvpw", kdb5_ldap_set_service_password, 0}, 230 #else 231 {"stashsrvpw", kdb5_ldap_stash_service_password, 0}, 232 #endif 233 {"create_policy", kdb5_ldap_create_policy, 1}, 234 {"modify_policy", kdb5_ldap_modify_policy, 1}, 235 {"view_policy", kdb5_ldap_view_policy, 1}, 236 {"destroy_policy", kdb5_ldap_destroy_policy, 1}, 237 {"list_policy", kdb5_ldap_list_policies, 1}, 238 {NULL, NULL, 0}, 239 }; 240 241 242 /* 243 * The function cmd_lookup returns the structure matching the 244 * command name and returns NULL if nothing matches. 245 */ 246 static struct _cmd_table *cmd_lookup(name) 247 char *name; 248 { 249 int i; 250 251 for (i = 0; cmd_table[i].name != NULL; i++) 252 if (strcmp(cmd_table[i].name, name) == 0) 253 return &cmd_table[i]; 254 255 return NULL; 256 } 257 258 259 /* 260 * The function cmd_index provides the offset of the command 261 * in the command table, which can be used to get the corresponding 262 * help from the help message table. 263 */ 264 int cmd_index(name) 265 char *name; 266 { 267 int i; 268 269 if (name == NULL) 270 return -1; 271 272 for (i = 0; cmd_table[i].name != NULL; i++) 273 if (strcmp(cmd_table[i].name, name) == 0) 274 return i; 275 276 return -1; 277 } 278 279 static void extended_com_err_fn (const char *myprog, errcode_t code, 280 const char *fmt, va_list args) 281 { 282 const char *emsg; 283 /* Solaris Kerberos: code should be like that in kdb5_util.c */ 284 if (code) { 285 emsg = krb5_get_error_message (util_context, code); 286 fprintf (stderr, "%s: %s ", myprog, emsg); 287 krb5_free_error_message (util_context, emsg); 288 } else { 289 fprintf (stderr, "%s: ", myprog); 290 } 291 vfprintf (stderr, fmt, args); 292 fprintf (stderr, "\n"); 293 } 294 295 int main(argc, argv) 296 int argc; 297 char *argv[]; 298 { 299 struct _cmd_table *cmd = NULL; 300 char *koptarg = NULL, **cmd_argv = NULL; 301 int cmd_argc = 0; 302 krb5_error_code retval; 303 int usage_print = 0; 304 int gp_is_static = 1; 305 krb5_error_code db_retval = 1; 306 char *bind_dn = NULL; 307 char *passwd = NULL; 308 char *ldap_server = NULL; 309 unsigned int ldapmask = 0; 310 unsigned int passwd_len = 0; 311 char *prompt = NULL; 312 kdb5_dal_handle *dal_handle = NULL; 313 krb5_ldap_context *ldap_context=NULL; 314 char *value = NULL, *conf_section = NULL; 315 krb5_boolean realm_name_required = TRUE; 316 krb5_boolean print_help_message = FALSE; 317 318 /* 319 * Solaris Kerberos: 320 * Ensure that "progname" is set before calling com_err. 321 */ 322 progname = (strrchr(argv[0], '/') ? strrchr(argv[0], '/')+1 : argv[0]); 323 324 retval = krb5_init_context(&util_context); 325 set_com_err_hook(extended_com_err_fn); 326 if (retval) { 327 com_err (progname, retval, gettext("while initializing Kerberos code")); 328 exit_status++; 329 goto cleanup; 330 } 331 332 cmd_argv = (char **) malloc(sizeof(char *)*argc); 333 if (cmd_argv == NULL) { 334 com_err(progname, ENOMEM, gettext("while creating sub-command arguments")); 335 exit_status++; 336 goto cleanup; 337 } 338 memset(cmd_argv, 0, sizeof(char *)*argc); 339 cmd_argc = 1; 340 341 memset(&global_params, 0, sizeof(kadm5_config_params)); 342 343 argv++; argc--; 344 while (*argv) { 345 if (strcmp(*argv, "--help") == 0) { 346 print_help_message = TRUE; 347 } 348 if (strcmp(*argv, "-P") == 0 && ARG_VAL) { 349 mkey_password = koptarg; 350 manual_mkey = TRUE; 351 } else if (strcmp(*argv, "-r") == 0 && ARG_VAL) { 352 global_params.realm = koptarg; 353 global_params.mask |= KADM5_CONFIG_REALM; 354 /* not sure this is really necessary */ 355 if ((retval = krb5_set_default_realm(util_context, 356 global_params.realm))) { 357 com_err(progname, retval, gettext("while setting default realm name")); 358 exit_status++; 359 goto cleanup; 360 } 361 } else if (strcmp(*argv, "-k") == 0 && ARG_VAL) { 362 if (krb5_string_to_enctype(koptarg, &global_params.enctype)) 363 com_err(argv[0], 0, gettext("%s is an invalid enctype"), koptarg); 364 else 365 global_params.mask |= KADM5_CONFIG_ENCTYPE; 366 } else if (strcmp(*argv, "-M") == 0 && ARG_VAL) { 367 global_params.mkey_name = koptarg; 368 global_params.mask |= KADM5_CONFIG_MKEY_NAME; 369 } else if (strcmp(*argv, "-sf") == 0 && ARG_VAL) { 370 global_params.stash_file = koptarg; 371 global_params.mask |= KADM5_CONFIG_STASH_FILE; 372 } else if (strcmp(*argv, "-m") == 0) { 373 manual_mkey = TRUE; 374 global_params.mkey_from_kbd = 1; 375 global_params.mask |= KADM5_CONFIG_MKEY_FROM_KBD; 376 } else if (strcmp(*argv, "-D") == 0 && ARG_VAL) { 377 bind_dn = koptarg; 378 if (bind_dn == NULL) { 379 com_err(progname, ENOMEM, gettext("while reading ldap parameters")); 380 exit_status++; 381 goto cleanup; 382 } 383 ldapmask |= CMD_LDAP_D; 384 } else if (strcmp(*argv, "-w") == 0 && ARG_VAL) { 385 passwd = strdup(koptarg); 386 if (passwd == NULL) { 387 com_err(progname, ENOMEM, gettext("while reading ldap parameters")); 388 exit_status++; 389 goto cleanup; 390 } 391 ldapmask |= CMD_LDAP_W; 392 } else if (strcmp(*argv, "-H") == 0 && ARG_VAL) { 393 ldap_server = koptarg; 394 if (ldap_server == NULL) { 395 com_err(progname, ENOMEM, gettext("while reading ldap parameters")); 396 exit_status++; 397 goto cleanup; 398 } 399 ldapmask |= CMD_LDAP_H; 400 } else if (cmd_lookup(*argv) != NULL) { 401 if (cmd_argv[0] == NULL) 402 cmd_argv[0] = *argv; 403 else { 404 free(cmd_argv); 405 cmd_argv = NULL; 406 usage(); 407 goto cleanup; 408 } 409 } else { 410 cmd_argv[cmd_argc++] = *argv; 411 } 412 argv++; argc--; 413 } 414 415 if (cmd_argv[0] == NULL) { 416 free(cmd_argv); 417 cmd_argv = NULL; 418 usage(); 419 goto cleanup; 420 } 421 422 /* if we need to print the help message (because of --help option) 423 * we will print the help corresponding to the sub-command. 424 */ 425 if (print_help_message) { 426 char *cmd_name = cmd_argv[0]; 427 free(cmd_argv); 428 cmd_argv = NULL; 429 usage(); 430 goto cleanup; 431 } 432 433 /* We need to check for the presence of default realm name only in 434 * the case of realm related operations like create, destroy etc. 435 */ 436 if ((strcmp(cmd_argv[0], "list") == 0) || 437 (strcmp(cmd_argv[0], "stashsrvpw") == 0)) { 438 realm_name_required = FALSE; 439 } 440 441 if (!util_context->default_realm) { 442 char *temp = NULL; 443 retval = krb5_get_default_realm(util_context, &temp); 444 if (retval) { 445 if (realm_name_required) { 446 com_err (progname, retval, gettext("while getting default realm")); 447 exit_status++; 448 goto cleanup; 449 } 450 } else 451 util_context->default_realm = temp; 452 } 453 /* If we have the realm name, we can safely say that 454 * realm_name is required so that we don't neglect any information. 455 */ 456 else 457 realm_name_required = TRUE; 458 459 retval = profile_get_string(util_context->profile, KDB_REALM_SECTION, 460 util_context->default_realm, KDB_MODULE_POINTER, 461 NULL, 462 &value); 463 464 if (!(value)) { 465 retval = profile_get_string(util_context->profile, KDB_MODULE_DEF_SECTION, 466 KDB_MODULE_POINTER, NULL, 467 NULL, 468 &value); 469 if (!(value)) { 470 if (util_context->default_realm) 471 conf_section = strdup(util_context->default_realm); 472 } else { 473 conf_section = strdup(value); 474 free(value); 475 } 476 } else { 477 conf_section = strdup(value); 478 free(value); 479 } 480 481 if (realm_name_required) { 482 retval = kadm5_get_config_params(util_context, 1, 483 &global_params, &global_params); 484 if (retval) { 485 com_err(argv[0], retval, gettext("while retreiving configuration parameters")); 486 exit_status++; 487 goto cleanup; 488 } 489 gp_is_static = 0; 490 } 491 492 if ((retval = krb5_ldap_lib_init()) != 0) { 493 com_err(argv[0], retval, gettext("while initializing error handling")); 494 exit_status++; 495 goto cleanup; 496 } 497 498 /* Initialize the ldap context */ 499 ldap_context = calloc(sizeof(krb5_ldap_context), 1); 500 if (ldap_context == NULL) { 501 com_err(argv[0], ENOMEM, gettext("while initializing ldap handle")); 502 exit_status++; 503 goto cleanup; 504 } 505 506 ldap_context->kcontext = util_context; 507 508 /* If LDAP parameters are specified, replace them with the values from config */ 509 if (ldapmask & CMD_LDAP_D) { 510 /* If password is not specified, prompt for it */ 511 if (passwd == NULL) { 512 passwd = (char *)malloc(MAX_PASSWD_LEN); 513 if (passwd == NULL) { 514 com_err(argv[0], ENOMEM, gettext("while retrieving ldap configuration")); 515 exit_status++; 516 goto cleanup; 517 } 518 prompt = (char *)malloc(MAX_PASSWD_PROMPT_LEN); 519 if (prompt == NULL) { 520 free(passwd); 521 passwd = NULL; 522 com_err(argv[0], ENOMEM, gettext("while retrieving ldap configuration")); 523 exit_status++; 524 goto cleanup; 525 } 526 memset(passwd, 0, sizeof(passwd)); 527 passwd_len = MAX_PASSWD_LEN - 1; 528 snprintf(prompt, MAX_PASSWD_PROMPT_LEN, gettext("Password for \"%s\""), bind_dn); 529 530 db_retval = krb5_read_password(util_context, prompt, NULL, passwd, &passwd_len); 531 532 if ((db_retval) || (passwd_len == 0)) { 533 com_err(argv[0], ENOMEM, gettext("while retrieving ldap configuration")); 534 free(passwd); 535 passwd = NULL; 536 exit_status++; 537 goto cleanup; 538 } 539 } 540 541 ldap_context->bind_pwd = passwd; 542 } 543 544 /* If ldaphost is specified, release entry filled by configuration & use this */ 545 if (ldapmask & CMD_LDAP_H) { 546 547 ldap_context->server_info_list = (krb5_ldap_server_info **) calloc (2, sizeof (krb5_ldap_server_info *)) ; 548 if (ldap_context->server_info_list == NULL) { 549 com_err(argv[0], ENOMEM, gettext("while initializing server list")); 550 exit_status++; 551 goto cleanup; 552 } 553 554 ldap_context->server_info_list[0] = (krb5_ldap_server_info *) calloc (1, sizeof (krb5_ldap_server_info)); 555 if (ldap_context->server_info_list[0] == NULL) { 556 com_err(argv[0], ENOMEM, gettext("while initializing server list")); 557 exit_status++; 558 goto cleanup; 559 } 560 561 ldap_context->server_info_list[0]->server_status = NOTSET; 562 563 ldap_context->server_info_list[0]->server_name = strdup(ldap_server); 564 if (ldap_context->server_info_list[0]->server_name == NULL) { 565 com_err(argv[0], ENOMEM, gettext("while initializing server list")); 566 exit_status++; 567 goto cleanup; 568 } 569 } 570 if (bind_dn) { 571 ldap_context->bind_dn = strdup(bind_dn); 572 if (ldap_context->bind_dn == NULL) { 573 com_err(argv[0], ENOMEM, gettext("while retrieving ldap configuration")); 574 exit_status++; 575 goto cleanup; 576 } 577 } else 578 ldap_context->bind_dn = NULL; 579 580 ldap_context->service_type = SERVICE_DN_TYPE_CLIENT; 581 582 if (realm_name_required) { 583 if ((global_params.enctype != ENCTYPE_UNKNOWN) && 584 (!krb5_c_valid_enctype(global_params.enctype))) { 585 com_err(argv[0], KRB5_PROG_KEYTYPE_NOSUPP, 586 gettext("while setting up enctype %d"), global_params.enctype); 587 } 588 } 589 590 cmd = cmd_lookup(cmd_argv[0]); 591 592 /* Setup DAL handle to access the database */ 593 dal_handle = calloc((size_t)1, sizeof(kdb5_dal_handle)); 594 if (dal_handle == NULL) { 595 goto cleanup; 596 } 597 dal_handle->db_context = ldap_context; 598 util_context->db_context = (void *) dal_handle; 599 600 db_retval = krb5_ldap_read_server_params(util_context, conf_section, KRB5_KDB_SRV_TYPE_OTHER); 601 if (db_retval) { 602 com_err(argv[0], db_retval, gettext("while reading ldap configuration")); 603 exit_status++; 604 goto cleanup; 605 } 606 607 if (cmd->opendb) { 608 db_retval = krb5_ldap_db_init(util_context, ldap_context); 609 if (db_retval) { 610 com_err(progname, db_retval, gettext("while initializing database")); 611 exit_status++; 612 goto cleanup; 613 } 614 db_inited = TRUE; 615 } 616 (*cmd->func)(cmd_argc, cmd_argv); 617 618 goto cleanup; 619 620 cleanup: 621 if (passwd) 622 memset(passwd, 0, sizeof(passwd)); 623 if (ldap_context && ldap_context->bind_pwd) 624 memset(ldap_context->bind_pwd, 0, sizeof(ldap_context->bind_pwd)); 625 626 if (util_context) { 627 if (gp_is_static == 0) 628 kadm5_free_config_params(util_context, &global_params); 629 krb5_ldap_close(util_context); 630 krb5_free_context(util_context); 631 } 632 633 if (cmd_argv) 634 free(cmd_argv); 635 if (prompt) 636 free(prompt); 637 if (conf_section) 638 free(conf_section); 639 if (dal_handle) 640 free(dal_handle); 641 642 if (usage_print) { 643 usage(); 644 } 645 646 return exit_status; 647 } 648