1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * This file comprises the main driver for this tool. 30 * Upon parsing the command verbs from user input, it 31 * branches to the appropriate modules to perform the 32 * requested task. 33 */ 34 35 #include <stdio.h> 36 #include <string.h> 37 #include <ctype.h> 38 #include <malloc.h> 39 #include <libgen.h> 40 #include <errno.h> 41 #include <cryptoutil.h> 42 #include <security/cryptoki.h> 43 #include "common.h" 44 45 /* 46 * The verbcmd construct allows genericizing information about a verb so 47 * that it is easier to manipulate. Makes parsing code easier to read, 48 * fix, and extend with new verbs. 49 */ 50 typedef struct verbcmd_s { 51 char *verb; 52 int (*action)(int, char *[]); 53 int mode; 54 char *summary; 55 char *synopsis; 56 } verbcmd; 57 58 /* External declarations for supported verb actions. */ 59 extern int pk_setpin(int argc, char *argv[]); 60 extern int pk_list(int argc, char *argv[]); 61 extern int pk_delete(int argc, char *argv[]); 62 extern int pk_import(int argc, char *argv[]); 63 extern int pk_export(int argc, char *argv[]); 64 extern int pk_tokens(int argc, char *argv[]); 65 extern int pk_gencert(int argc, char *argv[]); 66 extern int pk_gencsr(int argc, char *argv[]); 67 extern int pk_download(int argc, char *argv[]); 68 extern int pk_genkey(int argc, char *argv[]); 69 70 /* Forward declarations for "built-in" verb actions. */ 71 static int pk_help(int argc, char *argv[]); 72 73 /* Command structure for verbs and their actions. Do NOT i18n/l10n. */ 74 static verbcmd cmds[] = { 75 { "tokens", pk_tokens, 0, 76 "lists all visible PKCS#11 tokens", "tokens" }, 77 { "setpin", pk_setpin, 0, 78 "changes user authentication passphrase for keystore access", 79 "setpin [ keystore=pkcs11 ]\n\t\t" 80 "[ token=token[:manuf[:serial]]]\n\t" 81 82 "setpin keystore=nss\n\t\t" 83 "[ token=token ]\n\t\t" 84 "[ dir=directory-path ]\n\t\t" 85 "[ prefix=DBprefix ]\n\t" 86 }, 87 { "list", pk_list, 0, 88 "lists a summary of objects in the keystore", 89 "list [ token=token[:manuf[:serial]]]\n\t\t" 90 "[ objtype=private|public|both ]\n\t\t" 91 "[ label=label ]\n\t" 92 93 "list objtype=cert[:[public | private | both ]]\n\t\t" 94 "[ subject=subject-DN ]\n\t\t" 95 "[ keystore=pkcs11 ]\n\t\t" 96 "[ issuer=issuer-DN ]\n\t\t" 97 "[ serial=serial number]\n\t\t" 98 "[ label=cert-label ]\n\t\t" 99 "[ token=token[:manuf[:serial]]]\n\t\t" 100 "[ criteria=valid|expired|both ]\n\t" 101 102 "list objtype=key[:[public | private | both ]]\n\t\t" 103 "[ keystore=pkcs11 ]\n\t\t" 104 "[ subject=subject-DN ]\n\t\t" 105 "[ label=key-label ]\n\t\t" 106 "[ token=token[:manuf[:serial]]]\n\t" 107 108 "list keystore=pkcs11 objtype=crl\n\t\t" 109 "infile=crl-fn\n\t\t" 110 "[ dir=directory-path ]\n\t" 111 112 "list keystore=nss objtype=cert\n\t\t" 113 "[ subject=subject-DN ]\n\t\t" 114 "[ issuer=issuer-DN ]\n\t\t" 115 "[ serial=serial number]\n\t\t" 116 "[ nickname=cert-nickname ]\n\t\t" 117 "[ token=token[:manuf[:serial]]]\n\t\t" 118 "[ dir=directory-path ]\n\t\t" 119 "[ prefix=DBprefix ]\n\t\t" 120 "[ criteria=valid|expired|both ]\n\t" 121 122 "list keystore=nss objtype=key\n\t\t" 123 "[ token=token[:manuf[:serial]]]\n\t\t" 124 "[ dir=directory-path ]\n\t\t" 125 "[ prefix=DBprefix ]\n\t\t" 126 "[ nickname=key-nickname ]\n\t" 127 128 "list keystore=file objtype=cert\n\t\t" 129 "[ subject=subject-DN ]\n\t\t" 130 "[ issuer=issuer-DN ]\n\t\t" 131 "[ serial=serial number]\n\t\t" 132 "[ infile=cert-fn ]\n\t\t" 133 "[ dir=directory-path ]\n\t\t" 134 "[ criteria=valid|expired|both ]\n\t" 135 136 "list keystore=file objtype=key\n\t\t" 137 "[ infile=key-fn ]\n\t\t" 138 "[ dir=directory-path ]\n\t" 139 140 "list keystore=file objtype=crl\n\t\t" 141 "infile=crl-fn\n\t\t" 142 "[ dir=directory-path ]\n\t" 143 }, 144 145 { "delete", pk_delete, 0, 146 "deletes objects in the keystore", 147 148 "delete [ token=token[:manuf[:serial]]]\n\t\t" 149 "[ objtype=private|public|both ]\n\t\t" 150 "[ label=object-label ]\n\t" 151 152 "delete keystore=nss objtype=cert\n\t\t" 153 "[ subject=subject-DN ]\n\t\t" 154 "[ issuer=issuer-DN ]\n\t\t" 155 "[ serial=serial number]\n\t\t" 156 "[ nickname=cert-nickname ]\n\t\t" 157 "[ token=token[:manuf[:serial]]]\n\t\t" 158 "[ dir=directory-path ]\n\t\t" 159 "[ prefix=DBprefix ]\n\t\t" 160 "[ criteria=valid|expired|both ]\n\t" 161 162 "delete keystore=nss objtype=key\n\t\t" 163 "[ token=token[:manuf[:serial]]]\n\t\t" 164 "[ dir=directory-path ]\n\t\t" 165 "[ prefix=DBprefix ]\n\t\t" 166 "[ nickname=key-nickname ]\n\t\t" 167 168 "delete keystore=nss objtype=crl\n\t\t" 169 "[ nickname=issuer-nickname ]\n\t\t" 170 "[ subject=subject-DN ]\n\t\t" 171 "[ token=token[:manuf[:serial]]]\n\t\t" 172 "[ dir=directory-path ]\n\t\t" 173 "[ prefix=DBprefix ]\n\t" 174 175 "delete keystore=pkcs11 objtype=cert[:[public | private | both]]\n\t\t" 176 "[ subject=subject-DN ]\n\t\t" 177 "[ issuer=issuer-DN ]\n\t\t" 178 "[ serial=serial number]\n\t\t" 179 "[ label=cert-label ]\n\t\t" 180 "[ token=token[:manuf[:serial]]]\n\t\t" 181 "[ criteria=valid|expired|both ]\n\t" 182 183 "delete keystore=pkcs11 objtype=key[:[public | private | both]]\n\t\t" 184 "[ subject=subject-DN ]\n\t\t" 185 "[ label=key-label ]\n\t\t" 186 "[ token=token[:manuf[:serial]]]\n\t" 187 188 "delete keystore=pkcs11 objtype=crl\n\t\t" 189 "infile=crl-fn\n\t\t" 190 "[ dir=directory-path ]\n\t" 191 192 "delete keystore=file objtype=cert\n\t\t" 193 "[ subject=subject-DN ]\n\t\t" 194 "[ issuer=issuer-DN ]\n\t\t" 195 "[ serial=serial number]\n\t\t" 196 "[ infile=cert-fn ]\n\t\t" 197 "[ dir=directory-path ]\n\t\t" 198 "[ criteria=valid|expired|both ]\n\t" 199 200 "delete keystore=file objtype=key\n\t\t" 201 "[ infile=key-fn ]\n\t\t" 202 "[ dir=directory-path ]\n\t" 203 204 "delete keystore=file objtype=crl\n\t\t" 205 "infile=crl-fn\n\t\t" 206 "[ dir=directory-path ]\n\t" 207 }, 208 { "import", pk_import, 0, 209 "imports objects from an external source", 210 211 "import [token=token[:manuf[:serial]]]\n\t\t" 212 "infile=input-fn\n\t" 213 214 "import keystore=nss objtype=cert\n\t\t" 215 "infile=input-fn\n\t\t" 216 "nickname=cert-nickname\n\t\t" 217 "[ trust=trust-value ]\n\t\t" 218 "[ token=token[:manuf[:serial]]]\n\t\t" 219 "[ dir=directory-path ]\n\t\t" 220 "[ prefix=DBprefix ]\n\t" 221 222 "import keystore=nss objtype=crl\n\t\t" 223 "infile=input-fn\n\t\t" 224 "[ verifycrl=y|n ]\n\t\t" 225 "[ token=token[:manuf[:serial]]]\n\t\t" 226 "[ dir=directory-path ]\n\t\t" 227 "[ prefix=DBprefix ]\n\t" 228 229 "import keystore=pkcs11\n\t\t" 230 "infile=input-fn\n\t\t" 231 "label=cert-label\n\t\t" 232 "[ token=token[:manuf[:serial]]]\n\t" 233 234 "import keystore=pkcs11 objtype=crl\n\t\t" 235 "infile=input-crl-fn\n\t\t" 236 "outcrl=output-crl-fn\n\t\t" 237 "outformat=pem|der\n\t\t" 238 "[ dir=output-crl-directory-path ]\n\t" 239 240 "import keystore=file\n\t\t" 241 "infile=input-fn\n\t\t" 242 "outkey=output-key-fn\n\t\t" 243 "outcert=output-cert-fn\n\t\t" 244 "[ dir=output-cert-dir-path ]\n\t\t" 245 "[ keydir=output-key-dir-path ]\n\t\t" 246 "[ outformat=pem|der|pkcs12 ]\n\t" 247 248 "import keystore=file objtype=crl\n\t\t" 249 "infile=input-crl-fn\n\t\t" 250 "outcrl=output-crl-fn\n\t\t" 251 "outformat=pem|der\n\t\t" 252 "[ dir=output-crl-directory-path ]\n\t" 253 }, 254 255 { "export", pk_export, 0, 256 "exports objects from the keystore to a file", 257 258 "export [token=token[:manuf[:serial]]]\n\t\t" 259 "outfile=output-fn\n\t" 260 261 "export keystore=nss\n\t\t" 262 "outfile=output-fn\n\t\t" 263 "[ objtype=cert|key ]\n\t\t" 264 "[ subject=subject-DN ]\n\t\t" 265 "[ issuer=issuer-DN ]\n\t\t" 266 "[ serial=serial number]\n\t\t" 267 "[ nickname=cert-nickname]\n\t\t" 268 "[ token=token[:manuf[:serial]]]\n\t\t" 269 "[ dir=directory-path ]\n\t\t" 270 "[ prefix=DBPrefix ]\n\t\t" 271 "[ outformat=pem|der|pkcs12 ]\n\t" 272 273 "export keystore=pkcs11\n\t\t" 274 "outfile=output-fn\n\t\t" 275 "[ label=cert-label]\n\t\t" 276 "[ subject=subject-DN ]\n\t\t" 277 "[ issuer=issuer-DN ]\n\t\t" 278 "[ serial=serial number]\n\t\t" 279 "[ outformat=pem|der|pkcs12]\n\t\t" 280 "[ token=token[:manuf[:serial]]]\n\t" 281 282 "export keystore=file\n\t\t" 283 "certfile=cert-input-fn\n\t\t" 284 "keyfile=key-input-fn\n\t\t" 285 "outfile=output-pkcs12-fn\n\t\t" 286 "[ dir=directory-path ]\n\t" 287 }, 288 289 { "gencert", pk_gencert, 0, 290 "creates a self-signed X.509v3 certificate", 291 292 "gencert [-i] keystore=nss\n\t\t" 293 "label=cert-nickname\n\t\t" 294 "serial=serial number hex string]\n\t\t" 295 "subject=subject-DN\n\t\t" 296 "[ altname=[critical:]SubjectAltName ]\n\t\t" 297 "[ keyusage=[critical:]usage,usage,...]\n\t\t" 298 "[ token=token[:manuf[:serial]]]\n\t\t" 299 "[ dir=directory-path ]\n\t\t" 300 "[ prefix=DBprefix ]\n\t\t" 301 "[ keytype=rsa|dsa ]\n\t\t" 302 "[ keylen=key-size ]\n\t\t" 303 "[ trust=trust-value ]\n\t\t" 304 "[ lifetime=number-hour|number-day|number-year ]\n\t" 305 306 "gencert [-i] [ keystore=pkcs11 ]\n\t\t" 307 "label=key/cert-label\n\t\t" 308 "subject=subject-DN\n\t\t" 309 "serial=serial number hex string\n\t\t" 310 "[ altname=[critical:]SubjectAltName ]\n\t\t" 311 "[ keyusage=[critical:]usage,usage,...]\n\t\t" 312 "[ token=token[:manuf[:serial]]]\n\t\t" 313 "[ keytype=rsa|dsa ]\n\t\t" 314 "[ keylen=key-size ]\n\t\t" 315 "[ lifetime=number-hour|number-day|number-year ]\n\t" 316 317 "gencert [-i] keystore=file\n\t\t" 318 "outcert=cert_filename\n\t\t" 319 "outkey=key_filename\n\t\t" 320 "subject=subject-DN\n\t\t" 321 "serial=serial number hex string\n\t\t" 322 "[ altname=[critical:]SubjectAltName ]\n\t\t" 323 "[ keyusage=[critical:]usage,usage,...]\n\t\t" 324 "[ format=der|pem ]\n\t\t" 325 "[ dir=directory-path ]\n\t\t" 326 "[ prefix=DBprefix ]\n\t\t" 327 "[ keytype=rsa|dsa ]\n\t\t" 328 "[ keylen=key-size ]\n\t\t" 329 "[ lifetime=number-hour|number-day|number-year ]\n\t" 330 }, 331 { "gencsr", pk_gencsr, 0, 332 "creates a PKCS#10 certificate signing request file", 333 "gencsr [-i] keystore=nss \n\t\t" 334 "nickname=cert-nickname\n\t\t" 335 "outcsr=csr-fn\n\t\t" 336 "subject=subject-DN\n\t\t" 337 "[ altname=[critical:]SubjectAltName ]\n\t\t" 338 "[ keyusage=[critical:]usage,usage,...]\n\t\t" 339 "[ token=token[:manuf[:serial]]]\n\t\t" 340 "[ dir=directory-path ]\n\t\t" 341 "[ prefix=DBprefix ]\n\t\t" 342 "[ keytype=rsa|dsa ]\n\t\t" 343 "[ keylen=key-size ]\n\t\t" 344 "[ format=pem|der]\n\t" 345 "gencsr [-i] [ keystore=pkcs11 ]\n\t\t" 346 "label=key-label\n\t\t" 347 "outcsr=csr-fn\n\t\t" 348 "subject=subject-DN\n\t\t" 349 "[ altname=[critical:]SubjectAltName ]\n\t\t" 350 "[ keyusage=[critical:]usage,usage,...]\n\t\t" 351 "[ token=token[:manuf[:serial]]]\n\t\t" 352 "[ keytype=rsa|dsa ]\n\t\t" 353 "[ keylen=key-size ]\n\t\t" 354 "[ format=pem|der]\n\t" 355 "gencsr [-i] keystore=file\n\t\t" 356 "outcsr=csr-fn\n\t\t" 357 "outkey=key-fn\n\t\t" 358 "subject=subject-DN\n\t\t" 359 "[ altname=[critical:]SubjectAltName ]\n\t\t" 360 "[ keyusage=[critical:]usage,usage,...]\n\t\t" 361 "[ keytype=rsa|dsa ]\n\t\t" 362 "[ keylen=key-size ]\n\t\t" 363 "[ dir=directory-path ]\n\t\t" 364 "[ format=pem|der]\n\t" 365 }, 366 367 { "download", pk_download, 0, 368 "downloads a CRL or certificate file from an external source", 369 370 "download url=url_str\n\t\t" 371 "[ objtype=crl|cert ]\n\t\t" 372 "[ http_proxy=proxy_str ]\n\t\t" 373 "[ outfile = outfile ]\n\t\t" 374 }, 375 376 { "genkey", pk_genkey, 0, 377 "creates a symmetric key in the keystore", 378 379 "genkey [ keystore=pkcs11 ]\n\t\t" 380 "label=key-label\n\t\t" 381 "[ keytype=aes|arcfour|des|3des|generic ]\n\t\t" 382 "[ keylen=key-size (AES, ARCFOUR or GENERIC only)]\n\t\t" 383 "[ token=token[:manuf[:serial]]]\n\t\t" 384 "[ sensitive=y|n ]\n\t\t" 385 "[ extractable=y|n ]\n\t\t" 386 "[ print=y|n ]\n\t" 387 388 "genkey keystore=nss\n\t\t" 389 "label=key-label\n\t\t" 390 "[ keytype=aes|arcfour|des|3des|generic ]\n\t\t" 391 "[ keylen=key-size (AES, ARCFOUR or GENERIC only)]\n\t\t" 392 "[ token=token[:manuf[:serial]]]\n\t\t" 393 "[ dir=directory-path ]\n\t\t" 394 "[ prefix=DBprefix ]\n\t" 395 396 "genkey keystore=file\n\t\t" 397 "outkey=key-fn\n\t\t" 398 "[ keytype=aes|arcfour|des|3des|generic ]\n\t\t" 399 "[ keylen=key-size (AES, ARCFOUR or GENERIC only)]\n\t\t" 400 "[ dir=directory-path ]\n\t\t" 401 "[ print=y|n ]\n\t" 402 }, 403 404 { "help", pk_help, 0, 405 "displays help message", 406 "help\t(help and usage)" } 407 }; 408 409 static int num_cmds = sizeof (cmds) / sizeof (verbcmd); 410 411 static char *prog; 412 static void usage(int); 413 414 /* 415 * Usage information. This function must be updated when new verbs or 416 * options are added. 417 */ 418 static void 419 usage(int idx) 420 { 421 int i; 422 423 /* Display this block only in command-line mode. */ 424 (void) fprintf(stdout, gettext("Usage:\n")); 425 (void) fprintf(stdout, gettext(" %s -?\t(help and usage)\n"), 426 prog); 427 (void) fprintf(stdout, gettext(" %s -f option_file\n"), prog); 428 (void) fprintf(stdout, gettext(" %s subcommand [options...]\n"), 429 prog); 430 (void) fprintf(stdout, gettext("where subcommands may be:\n")); 431 432 /* Display only those verbs that match the current tool mode. */ 433 if (idx == -1) { 434 for (i = 0; i < num_cmds; i++) { 435 /* Do NOT i18n/l10n. */ 436 (void) fprintf(stdout, " %-8s - %s\n", 437 cmds[i].verb, cmds[i].summary); 438 } 439 (void) fprintf(stdout, gettext("\nFurther details on the " 440 "subcommands can be found by adding \'help\'.\n" 441 "Ex: pktool gencert help\n\n")); 442 } else { 443 (void) fprintf(stdout, "\t%s\n", cmds[idx].synopsis); 444 } 445 } 446 447 /* 448 * Provide help, in the form of displaying the usage. 449 */ 450 static int 451 pk_help(int argc, char *argv[]) 452 /* ARGSUSED */ 453 { 454 usage(-1); 455 return (0); 456 } 457 458 /* 459 * Process arguments from the argfile and create a new 460 * argv/argc list to be processed later. 461 */ 462 static int 463 process_arg_file(char *argfile, char ***argv, int *argc) 464 { 465 FILE *fp; 466 char argline[2 * BUFSIZ]; /* 2048 bytes should be plenty */ 467 char *p; 468 int nargs = 0; 469 470 if ((fp = fopen(argfile, "rF")) == NULL) { 471 (void) fprintf(stderr, 472 gettext("Cannot read argfile %s: %s\n"), 473 argfile, strerror(errno)); 474 return (errno); 475 } 476 477 while (fgets(argline, sizeof (argline), fp) != NULL) { 478 int j; 479 /* remove trailing whitespace */ 480 j = strlen(argline) - 1; 481 while (j >= 0 && isspace(argline[j])) { 482 argline[j] = 0; 483 j--; 484 } 485 /* If it was a blank line, get the next one. */ 486 if (!strlen(argline)) 487 continue; 488 489 (*argv) = realloc((*argv), 490 (nargs + 1) * sizeof (char *)); 491 if ((*argv) == NULL) { 492 perror("memory error"); 493 (void) fclose(fp); 494 return (errno); 495 } 496 p = (char *)strdup(argline); 497 if (p == NULL) { 498 perror("memory error"); 499 (void) fclose(fp); 500 return (errno); 501 } 502 (*argv)[nargs] = p; 503 nargs++; 504 } 505 *argc = nargs; 506 (void) fclose(fp); 507 return (0); 508 } 509 510 /* 511 * MAIN() -- where all the action is 512 */ 513 int 514 main(int argc, char *argv[], char *envp[]) 515 /* ARGSUSED2 */ 516 { 517 int i, found = -1; 518 int rv; 519 int pk_argc = 0; 520 char **pk_argv = NULL; 521 int save_errno = 0; 522 523 /* Set up for i18n/l10n. */ 524 (void) setlocale(LC_ALL, ""); 525 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D. */ 526 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it isn't. */ 527 #endif 528 (void) textdomain(TEXT_DOMAIN); 529 530 /* Get program base name and move pointer over 0th arg. */ 531 prog = basename(argv[0]); 532 argv++, argc--; 533 534 /* Set up for debug and error output. */ 535 if (argc == 0) { 536 usage(-1); 537 return (1); 538 } 539 540 /* Check for help options. For CLIP-compliance. */ 541 if (strcmp(argv[0], "-?") == 0) { 542 return (pk_help(argc, argv)); 543 } else if (strcmp(argv[0], "-f") == 0 && argc == 2) { 544 rv = process_arg_file(argv[1], &pk_argv, &pk_argc); 545 if (rv) 546 return (rv); 547 } else if (argc >= 1 && argv[0][0] == '-') { 548 usage(-1); 549 return (1); 550 } 551 552 /* Always turns off Metaslot so that we can see softtoken. */ 553 if (setenv("METASLOT_ENABLED", "false", 1) < 0) { 554 save_errno = errno; 555 cryptoerror(LOG_STDERR, 556 gettext("Disabling Metaslot failed (%s)."), 557 strerror(save_errno)); 558 return (1); 559 } 560 561 /* Begin parsing command line. */ 562 if (pk_argc == 0 && pk_argv == NULL) { 563 pk_argc = argc; 564 pk_argv = argv; 565 } 566 567 /* Check for valid verb (or an abbreviation of it). */ 568 found = -1; 569 for (i = 0; i < num_cmds; i++) { 570 if (strcmp(cmds[i].verb, pk_argv[0]) == 0) { 571 if (found < 0) { 572 found = i; 573 break; 574 } 575 } 576 } 577 /* Stop here if no valid verb found. */ 578 if (found < 0) { 579 cryptoerror(LOG_STDERR, gettext("Invalid verb: %s"), 580 pk_argv[0]); 581 return (1); 582 } 583 584 /* Get to work! */ 585 rv = (*cmds[found].action)(pk_argc, pk_argv); 586 switch (rv) { 587 case PK_ERR_NONE: 588 break; /* Command succeeded, do nothing. */ 589 case PK_ERR_USAGE: 590 usage(found); 591 break; 592 case PK_ERR_QUIT: 593 exit(0); 594 /* NOTREACHED */ 595 case PK_ERR_PK11: 596 case PK_ERR_SYSTEM: 597 case PK_ERR_OPENSSL: 598 case PK_ERR_NSS: 599 default: 600 break; 601 } 602 return (rv); 603 } 604