1*3089Swyllys /* 2*3089Swyllys * CDDL HEADER START 3*3089Swyllys * 4*3089Swyllys * The contents of this file are subject to the terms of the 5*3089Swyllys * Common Development and Distribution License (the "License"). 6*3089Swyllys * You may not use this file except in compliance with the License. 7*3089Swyllys * 8*3089Swyllys * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9*3089Swyllys * or http://www.opensolaris.org/os/licensing. 10*3089Swyllys * See the License for the specific language governing permissions 11*3089Swyllys * and limitations under the License. 12*3089Swyllys * 13*3089Swyllys * When distributing Covered Code, include this CDDL HEADER in each 14*3089Swyllys * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15*3089Swyllys * If applicable, add the following below this CDDL HEADER, with the 16*3089Swyllys * fields enclosed by brackets "[]" replaced with your own identifying 17*3089Swyllys * information: Portions Copyright [yyyy] [name of copyright owner] 18*3089Swyllys * 19*3089Swyllys * CDDL HEADER END 20*3089Swyllys * 21*3089Swyllys * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 22*3089Swyllys * Use is subject to license terms. 23*3089Swyllys */ 24*3089Swyllys 25*3089Swyllys #pragma ident "%Z%%M% %I% %E% SMI" 26*3089Swyllys 27*3089Swyllys #include <stdio.h> 28*3089Swyllys #include <strings.h> 29*3089Swyllys #include <ctype.h> 30*3089Swyllys #include <libgen.h> 31*3089Swyllys #include <libintl.h> 32*3089Swyllys #include <errno.h> 33*3089Swyllys #include <kmfapiP.h> 34*3089Swyllys #include <cryptoutil.h> 35*3089Swyllys #include "util.h" 36*3089Swyllys 37*3089Swyllys int 38*3089Swyllys kc_create(int argc, char *argv[]) 39*3089Swyllys { 40*3089Swyllys KMF_RETURN ret; 41*3089Swyllys int rv = KC_OK; 42*3089Swyllys int opt; 43*3089Swyllys extern int optind_av; 44*3089Swyllys extern char *optarg_av; 45*3089Swyllys char *filename = NULL; 46*3089Swyllys int ocsp_set_attr = 0; 47*3089Swyllys boolean_t crl_set_attr = 0; 48*3089Swyllys KMF_POLICY_RECORD plc; 49*3089Swyllys 50*3089Swyllys (void) memset(&plc, 0, sizeof (KMF_POLICY_RECORD)); 51*3089Swyllys 52*3089Swyllys while ((opt = getopt_av(argc, argv, 53*3089Swyllys "i:(dbfile)" 54*3089Swyllys "p:(policy)" 55*3089Swyllys "d:(ignore-date)" 56*3089Swyllys "e:(ignore-unknown-eku)" 57*3089Swyllys "a:(ignore-trust-anchor)" 58*3089Swyllys "v:(validity-adjusttime)" 59*3089Swyllys "t:(ta-name)" 60*3089Swyllys "s:(ta-serial)" 61*3089Swyllys "o:(ocsp-responder)" 62*3089Swyllys "P:(ocsp-proxy)" 63*3089Swyllys "r:(ocsp-use-cert-responder)" 64*3089Swyllys "T:(ocsp-response-lifetime)" 65*3089Swyllys "R:(ocsp-ignore-response-sign)" 66*3089Swyllys "n:(ocsp-responder-cert-name)" 67*3089Swyllys "A:(ocsp-responder-cert-serial)" 68*3089Swyllys "c:(crl-basefilename)" 69*3089Swyllys "I:(crl-directory)" 70*3089Swyllys "g:(crl-get-crl-uri)" 71*3089Swyllys "X:(crl-proxy)" 72*3089Swyllys "S:(crl-ignore-crl-sign)" 73*3089Swyllys "D:(crl-ignore-crl-date)" 74*3089Swyllys "u:(keyusage)" 75*3089Swyllys "E:(ekunames)" 76*3089Swyllys "O:(ekuoids)")) != EOF) { 77*3089Swyllys switch (opt) { 78*3089Swyllys case 'i': 79*3089Swyllys filename = get_string(optarg_av, &rv); 80*3089Swyllys if (filename == NULL) { 81*3089Swyllys (void) fprintf(stderr, 82*3089Swyllys gettext("Error dbfile input.\n")); 83*3089Swyllys } 84*3089Swyllys break; 85*3089Swyllys case 'p': 86*3089Swyllys plc.name = get_string(optarg_av, &rv); 87*3089Swyllys if (plc.name == NULL) { 88*3089Swyllys (void) fprintf(stderr, 89*3089Swyllys gettext("Error policy name.\n")); 90*3089Swyllys } 91*3089Swyllys break; 92*3089Swyllys case 'd': 93*3089Swyllys plc.ignore_date = get_boolean(optarg_av); 94*3089Swyllys if (plc.ignore_date == -1) { 95*3089Swyllys (void) fprintf(stderr, 96*3089Swyllys gettext("Error boolean input.\n")); 97*3089Swyllys rv = KC_ERR_USAGE; 98*3089Swyllys } 99*3089Swyllys break; 100*3089Swyllys case 'e': 101*3089Swyllys plc.ignore_unknown_ekus = 102*3089Swyllys get_boolean(optarg_av); 103*3089Swyllys if (plc.ignore_unknown_ekus == -1) { 104*3089Swyllys (void) fprintf(stderr, 105*3089Swyllys gettext("Error boolean input.\n")); 106*3089Swyllys rv = KC_ERR_USAGE; 107*3089Swyllys } 108*3089Swyllys break; 109*3089Swyllys case 'a': 110*3089Swyllys plc.ignore_trust_anchor = 111*3089Swyllys get_boolean(optarg_av); 112*3089Swyllys if (plc.ignore_trust_anchor == -1) { 113*3089Swyllys (void) fprintf(stderr, 114*3089Swyllys gettext("Error boolean input.\n")); 115*3089Swyllys rv = KC_ERR_USAGE; 116*3089Swyllys } 117*3089Swyllys break; 118*3089Swyllys case 'v': 119*3089Swyllys plc.validity_adjusttime = 120*3089Swyllys get_string(optarg_av, &rv); 121*3089Swyllys if (plc.validity_adjusttime == NULL) { 122*3089Swyllys (void) fprintf(stderr, 123*3089Swyllys gettext("Error time input.\n")); 124*3089Swyllys } else { 125*3089Swyllys uint32_t adj; 126*3089Swyllys /* for syntax checking */ 127*3089Swyllys if (str2lifetime( 128*3089Swyllys plc.validity_adjusttime, 129*3089Swyllys &adj) < 0) { 130*3089Swyllys (void) fprintf(stderr, 131*3089Swyllys gettext("Error time " 132*3089Swyllys "input.\n")); 133*3089Swyllys rv = KC_ERR_USAGE; 134*3089Swyllys } 135*3089Swyllys } 136*3089Swyllys break; 137*3089Swyllys case 't': 138*3089Swyllys plc.ta_name = get_string(optarg_av, &rv); 139*3089Swyllys if (plc.ta_name == NULL) { 140*3089Swyllys (void) fprintf(stderr, 141*3089Swyllys gettext("Error name input.\n")); 142*3089Swyllys } else { 143*3089Swyllys KMF_X509_NAME taDN; 144*3089Swyllys /* for syntax checking */ 145*3089Swyllys if (KMF_DNParser(plc.ta_name, 146*3089Swyllys &taDN) != KMF_OK) { 147*3089Swyllys (void) fprintf(stderr, 148*3089Swyllys gettext("Error name " 149*3089Swyllys "input.\n")); 150*3089Swyllys rv = KC_ERR_USAGE; 151*3089Swyllys } else { 152*3089Swyllys KMF_FreeDN(&taDN); 153*3089Swyllys } 154*3089Swyllys } 155*3089Swyllys break; 156*3089Swyllys case 's': 157*3089Swyllys plc.ta_serial = get_string(optarg_av, &rv); 158*3089Swyllys if (plc.ta_serial == NULL) { 159*3089Swyllys (void) fprintf(stderr, 160*3089Swyllys gettext("Error serial input.\n")); 161*3089Swyllys } else { 162*3089Swyllys uchar_t *bytes = NULL; 163*3089Swyllys size_t bytelen; 164*3089Swyllys 165*3089Swyllys ret = KMF_HexString2Bytes( 166*3089Swyllys (uchar_t *)plc.ta_serial, 167*3089Swyllys &bytes, &bytelen); 168*3089Swyllys if (ret != KMF_OK || bytes == NULL) { 169*3089Swyllys (void) fprintf(stderr, 170*3089Swyllys gettext("serial number " 171*3089Swyllys "must be specified as a " 172*3089Swyllys "hex number " 173*3089Swyllys "(ex: 0x0102030405" 174*3089Swyllys "ffeeddee)\n")); 175*3089Swyllys rv = KC_ERR_USAGE; 176*3089Swyllys } 177*3089Swyllys if (bytes != NULL) 178*3089Swyllys free(bytes); 179*3089Swyllys } 180*3089Swyllys break; 181*3089Swyllys case 'o': 182*3089Swyllys plc.VAL_OCSP_RESPONDER_URI = 183*3089Swyllys get_string(optarg_av, &rv); 184*3089Swyllys if (plc.VAL_OCSP_RESPONDER_URI == NULL) { 185*3089Swyllys (void) fprintf(stderr, gettext( 186*3089Swyllys "Error responder input.\n")); 187*3089Swyllys } else { 188*3089Swyllys ocsp_set_attr++; 189*3089Swyllys } 190*3089Swyllys break; 191*3089Swyllys case 'P': 192*3089Swyllys plc.VAL_OCSP_PROXY = 193*3089Swyllys get_string(optarg_av, &rv); 194*3089Swyllys if (plc.VAL_OCSP_PROXY == NULL) { 195*3089Swyllys (void) fprintf(stderr, 196*3089Swyllys gettext("Error proxy input.\n")); 197*3089Swyllys } else { 198*3089Swyllys ocsp_set_attr++; 199*3089Swyllys } 200*3089Swyllys break; 201*3089Swyllys case 'r': 202*3089Swyllys plc.VAL_OCSP_URI_FROM_CERT = 203*3089Swyllys get_boolean(optarg_av); 204*3089Swyllys if (plc.VAL_OCSP_URI_FROM_CERT == -1) { 205*3089Swyllys (void) fprintf(stderr, 206*3089Swyllys gettext("Error boolean input.\n")); 207*3089Swyllys rv = KC_ERR_USAGE; 208*3089Swyllys } else { 209*3089Swyllys ocsp_set_attr++; 210*3089Swyllys } 211*3089Swyllys break; 212*3089Swyllys case 'T': 213*3089Swyllys plc.VAL_OCSP_RESP_LIFETIME = 214*3089Swyllys get_string(optarg_av, &rv); 215*3089Swyllys if (plc.VAL_OCSP_RESP_LIFETIME == NULL) { 216*3089Swyllys (void) fprintf(stderr, 217*3089Swyllys gettext("Error time input.\n")); 218*3089Swyllys } else { 219*3089Swyllys uint32_t adj; 220*3089Swyllys /* for syntax checking */ 221*3089Swyllys if (str2lifetime( 222*3089Swyllys plc.VAL_OCSP_RESP_LIFETIME, 223*3089Swyllys &adj) < 0) { 224*3089Swyllys (void) fprintf(stderr, 225*3089Swyllys gettext("Error time " 226*3089Swyllys "input.\n")); 227*3089Swyllys rv = KC_ERR_USAGE; 228*3089Swyllys } else { 229*3089Swyllys ocsp_set_attr++; 230*3089Swyllys } 231*3089Swyllys } 232*3089Swyllys break; 233*3089Swyllys case 'R': 234*3089Swyllys plc.VAL_OCSP_IGNORE_RESP_SIGN = 235*3089Swyllys get_boolean(optarg_av); 236*3089Swyllys if (plc.VAL_OCSP_IGNORE_RESP_SIGN == -1) { 237*3089Swyllys (void) fprintf(stderr, 238*3089Swyllys gettext("Error boolean input.\n")); 239*3089Swyllys rv = KC_ERR_USAGE; 240*3089Swyllys } else { 241*3089Swyllys ocsp_set_attr++; 242*3089Swyllys } 243*3089Swyllys break; 244*3089Swyllys case 'n': 245*3089Swyllys plc.VAL_OCSP_RESP_CERT_NAME = 246*3089Swyllys get_string(optarg_av, &rv); 247*3089Swyllys if (plc.VAL_OCSP_RESP_CERT_NAME == NULL) { 248*3089Swyllys (void) fprintf(stderr, 249*3089Swyllys gettext("Error name input.\n")); 250*3089Swyllys } else { 251*3089Swyllys KMF_X509_NAME respDN; 252*3089Swyllys /* for syntax checking */ 253*3089Swyllys if (KMF_DNParser( 254*3089Swyllys plc.VAL_OCSP_RESP_CERT_NAME, 255*3089Swyllys &respDN) != KMF_OK) { 256*3089Swyllys (void) fprintf(stderr, 257*3089Swyllys gettext("Error name " 258*3089Swyllys "input.\n")); 259*3089Swyllys rv = KC_ERR_USAGE; 260*3089Swyllys } else { 261*3089Swyllys KMF_FreeDN(&respDN); 262*3089Swyllys ocsp_set_attr++; 263*3089Swyllys } 264*3089Swyllys } 265*3089Swyllys break; 266*3089Swyllys case 'A': 267*3089Swyllys plc.VAL_OCSP_RESP_CERT_SERIAL = 268*3089Swyllys get_string(optarg_av, &rv); 269*3089Swyllys if (plc.VAL_OCSP_RESP_CERT_SERIAL == NULL) { 270*3089Swyllys (void) fprintf(stderr, 271*3089Swyllys gettext("Error serial input.\n")); 272*3089Swyllys } else { 273*3089Swyllys uchar_t *bytes = NULL; 274*3089Swyllys size_t bytelen; 275*3089Swyllys 276*3089Swyllys ret = KMF_HexString2Bytes((uchar_t *) 277*3089Swyllys plc.VAL_OCSP_RESP_CERT_SERIAL, 278*3089Swyllys &bytes, &bytelen); 279*3089Swyllys if (ret != KMF_OK || bytes == NULL) { 280*3089Swyllys (void) fprintf(stderr, 281*3089Swyllys gettext("serial number " 282*3089Swyllys "must be specified as a " 283*3089Swyllys "hex number " 284*3089Swyllys "(ex: 0x0102030405" 285*3089Swyllys "ffeeddee)\n")); 286*3089Swyllys rv = KC_ERR_USAGE; 287*3089Swyllys break; 288*3089Swyllys } 289*3089Swyllys if (bytes != NULL) 290*3089Swyllys free(bytes); 291*3089Swyllys ocsp_set_attr++; 292*3089Swyllys } 293*3089Swyllys break; 294*3089Swyllys case 'c': 295*3089Swyllys plc.VAL_CRL_BASEFILENAME = 296*3089Swyllys get_string(optarg_av, &rv); 297*3089Swyllys if (plc.VAL_CRL_BASEFILENAME == NULL) { 298*3089Swyllys (void) fprintf(stderr, 299*3089Swyllys gettext("Error boolean input.\n")); 300*3089Swyllys } else { 301*3089Swyllys crl_set_attr++; 302*3089Swyllys } 303*3089Swyllys break; 304*3089Swyllys case 'I': 305*3089Swyllys plc.VAL_CRL_DIRECTORY = 306*3089Swyllys get_string(optarg_av, &rv); 307*3089Swyllys if (plc.VAL_CRL_DIRECTORY == NULL) { 308*3089Swyllys (void) fprintf(stderr, 309*3089Swyllys gettext("Error boolean input.\n")); 310*3089Swyllys } else { 311*3089Swyllys crl_set_attr++; 312*3089Swyllys } 313*3089Swyllys break; 314*3089Swyllys case 'g': 315*3089Swyllys plc.VAL_CRL_GET_URI = get_boolean(optarg_av); 316*3089Swyllys if (plc.VAL_CRL_GET_URI == -1) { 317*3089Swyllys (void) fprintf(stderr, 318*3089Swyllys gettext("Error boolean input.\n")); 319*3089Swyllys rv = KC_ERR_USAGE; 320*3089Swyllys } else { 321*3089Swyllys crl_set_attr++; 322*3089Swyllys } 323*3089Swyllys break; 324*3089Swyllys case 'X': 325*3089Swyllys plc.VAL_CRL_PROXY = get_string(optarg_av, &rv); 326*3089Swyllys if (plc.VAL_CRL_PROXY == NULL) { 327*3089Swyllys (void) fprintf(stderr, 328*3089Swyllys gettext("Error proxy input.\n")); 329*3089Swyllys } else { 330*3089Swyllys crl_set_attr++; 331*3089Swyllys } 332*3089Swyllys break; 333*3089Swyllys case 'S': 334*3089Swyllys plc.VAL_CRL_IGNORE_SIGN = 335*3089Swyllys get_boolean(optarg_av); 336*3089Swyllys if (plc.VAL_CRL_IGNORE_SIGN == -1) { 337*3089Swyllys (void) fprintf(stderr, 338*3089Swyllys gettext("Error boolean input.\n")); 339*3089Swyllys rv = KC_ERR_USAGE; 340*3089Swyllys } else { 341*3089Swyllys crl_set_attr++; 342*3089Swyllys } 343*3089Swyllys break; 344*3089Swyllys case 'D': 345*3089Swyllys plc.VAL_CRL_IGNORE_DATE = 346*3089Swyllys get_boolean(optarg_av); 347*3089Swyllys if (plc.VAL_CRL_IGNORE_DATE == -1) { 348*3089Swyllys (void) fprintf(stderr, 349*3089Swyllys gettext("Error boolean input.\n")); 350*3089Swyllys rv = KC_ERR_USAGE; 351*3089Swyllys } else { 352*3089Swyllys crl_set_attr++; 353*3089Swyllys } 354*3089Swyllys break; 355*3089Swyllys case 'u': 356*3089Swyllys plc.ku_bits = parseKUlist(optarg_av); 357*3089Swyllys if (plc.ku_bits == 0) { 358*3089Swyllys (void) fprintf(stderr, gettext( 359*3089Swyllys "Error keyusage input.\n")); 360*3089Swyllys rv = KC_ERR_USAGE; 361*3089Swyllys } 362*3089Swyllys break; 363*3089Swyllys case 'E': 364*3089Swyllys if (parseEKUNames(optarg_av, &plc) != 0) { 365*3089Swyllys (void) fprintf(stderr, 366*3089Swyllys gettext("Error EKU input.\n")); 367*3089Swyllys rv = KC_ERR_USAGE; 368*3089Swyllys } 369*3089Swyllys break; 370*3089Swyllys case 'O': 371*3089Swyllys if (parseEKUOIDs(optarg_av, &plc) != 0) { 372*3089Swyllys (void) fprintf(stderr, 373*3089Swyllys gettext("Error EKU OID input.\n")); 374*3089Swyllys rv = KC_ERR_USAGE; 375*3089Swyllys } 376*3089Swyllys break; 377*3089Swyllys default: 378*3089Swyllys (void) fprintf(stderr, 379*3089Swyllys gettext("Error input option.\n")); 380*3089Swyllys rv = KC_ERR_USAGE; 381*3089Swyllys break; 382*3089Swyllys } 383*3089Swyllys 384*3089Swyllys if (rv != KC_OK) 385*3089Swyllys goto out; 386*3089Swyllys } 387*3089Swyllys 388*3089Swyllys /* No additional args allowed. */ 389*3089Swyllys argc -= optind_av; 390*3089Swyllys if (argc) { 391*3089Swyllys (void) fprintf(stderr, 392*3089Swyllys gettext("Error input option\n")); 393*3089Swyllys rv = KC_ERR_USAGE; 394*3089Swyllys goto out; 395*3089Swyllys } 396*3089Swyllys 397*3089Swyllys if (filename == NULL) { 398*3089Swyllys filename = strdup(KMF_DEFAULT_POLICY_FILE); 399*3089Swyllys if (filename == NULL) { 400*3089Swyllys rv = KC_ERR_MEMORY; 401*3089Swyllys goto out; 402*3089Swyllys } 403*3089Swyllys } 404*3089Swyllys 405*3089Swyllys /* 406*3089Swyllys * Must have a policy name. The policy name can not be default 407*3089Swyllys * if using the default policy file. 408*3089Swyllys */ 409*3089Swyllys if (plc.name == NULL) { 410*3089Swyllys (void) fprintf(stderr, 411*3089Swyllys gettext("You must specify a policy name\n")); 412*3089Swyllys rv = KC_ERR_USAGE; 413*3089Swyllys goto out; 414*3089Swyllys } else if (strcmp(filename, KMF_DEFAULT_POLICY_FILE) == 0 && 415*3089Swyllys strcmp(plc.name, KMF_DEFAULT_POLICY_NAME) == 0) { 416*3089Swyllys (void) fprintf(stderr, 417*3089Swyllys gettext("Can not create a default policy in the default " 418*3089Swyllys "policy file\n")); 419*3089Swyllys rv = KC_ERR_USAGE; 420*3089Swyllys goto out; 421*3089Swyllys } 422*3089Swyllys 423*3089Swyllys /* 424*3089Swyllys * If the policy file exists and the policy is in the policy file 425*3089Swyllys * already, we will not create it again. 426*3089Swyllys */ 427*3089Swyllys if (access(filename, R_OK) == 0) { 428*3089Swyllys POLICY_LIST *plclist = NULL, *pnode; 429*3089Swyllys int found = 0; 430*3089Swyllys 431*3089Swyllys rv = load_policies(filename, &plclist); 432*3089Swyllys if (rv != KMF_OK) 433*3089Swyllys goto out; 434*3089Swyllys 435*3089Swyllys pnode = plclist; 436*3089Swyllys while (pnode != NULL && !found) { 437*3089Swyllys if (strcmp(plc.name, pnode->plc.name) == 0) 438*3089Swyllys found++; 439*3089Swyllys pnode = pnode->next; 440*3089Swyllys } 441*3089Swyllys free_policy_list(plclist); 442*3089Swyllys 443*3089Swyllys if (found) { 444*3089Swyllys (void) fprintf(stderr, 445*3089Swyllys gettext("Could not create policy \"%s\" - exists " 446*3089Swyllys "already\n"), plc.name); 447*3089Swyllys rv = KC_ERR_USAGE; 448*3089Swyllys goto out; 449*3089Swyllys } 450*3089Swyllys } 451*3089Swyllys 452*3089Swyllys /* 453*3089Swyllys * If any OCSP attribute is set, turn on the OCSP checking flag. 454*3089Swyllys * Also set "has_resp_cert" to be true, if the responder cert 455*3089Swyllys * is provided. 456*3089Swyllys */ 457*3089Swyllys if (ocsp_set_attr > 0) 458*3089Swyllys plc.revocation |= KMF_REVOCATION_METHOD_OCSP; 459*3089Swyllys 460*3089Swyllys if (plc.VAL_OCSP_RESP_CERT.name != NULL && 461*3089Swyllys plc.VAL_OCSP_RESP_CERT.serial != NULL) { 462*3089Swyllys plc.VAL_OCSP.has_resp_cert = B_TRUE; 463*3089Swyllys } 464*3089Swyllys 465*3089Swyllys /* 466*3089Swyllys * If any CRL attribute is set, turn on the CRL checking flag. 467*3089Swyllys */ 468*3089Swyllys if (crl_set_attr > 0) 469*3089Swyllys plc.revocation |= KMF_REVOCATION_METHOD_CRL; 470*3089Swyllys 471*3089Swyllys /* 472*3089Swyllys * Does a sanity check on the new policy. 473*3089Swyllys */ 474*3089Swyllys ret = KMF_VerifyPolicy(&plc); 475*3089Swyllys if (ret != KMF_OK) { 476*3089Swyllys print_sanity_error(ret); 477*3089Swyllys rv = KC_ERR_ADD_POLICY; 478*3089Swyllys goto out; 479*3089Swyllys } 480*3089Swyllys 481*3089Swyllys /* 482*3089Swyllys * Add to the DB. 483*3089Swyllys */ 484*3089Swyllys ret = KMF_AddPolicyToDB(&plc, filename, B_FALSE); 485*3089Swyllys if (ret != KMF_OK) { 486*3089Swyllys (void) fprintf(stderr, 487*3089Swyllys gettext("Error adding policy to database: 0x%04x\n"), ret); 488*3089Swyllys rv = KC_ERR_ADD_POLICY; 489*3089Swyllys } 490*3089Swyllys 491*3089Swyllys out: 492*3089Swyllys if (filename != NULL) 493*3089Swyllys free(filename); 494*3089Swyllys 495*3089Swyllys KMF_FreePolicyRecord(&plc); 496*3089Swyllys 497*3089Swyllys return (rv); 498*3089Swyllys } 499