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 (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
24 */
25
26 #include <nss_dbdefs.h>
27 #include <pwd.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <syslog.h>
31 #include <unistd.h>
32 #include <auth_attr.h>
33 #include <deflt.h>
34 #include <priv.h>
35 #include <secdb.h>
36 #include <user_attr.h>
37 #include <sys/task.h>
38 #include <libintl.h>
39 #include <project.h>
40 #include <errno.h>
41 #include <alloca.h>
42
43 #include <bsm/adt.h>
44 #include <bsm/adt_event.h> /* adt_get_auid() */
45
46 #include <security/pam_appl.h>
47 #include <security/pam_modules.h>
48 #include <security/pam_impl.h>
49
50 #define PROJECT "project="
51 #define PROJSZ (sizeof (PROJECT) - 1)
52
53 /*
54 * unix_cred - PAM auth modules must contain both pam_sm_authenticate
55 * and pam_sm_setcred. Some other auth module is responsible
56 * for authentication (e.g., pam_unix_auth.so), this module
57 * only implements pam_sm_setcred so that the authentication
58 * can be separated without knowledge of the Solaris Unix style
59 * credential setting.
60 * Solaris Unix style credential setting includes initializing
61 * the audit characteristics if not already initialized and
62 * setting the user's default and limit privileges.
63 */
64
65 /*
66 * unix_cred - pam_sm_authenticate
67 *
68 * Returns PAM_IGNORE.
69 */
70
71 /*ARGSUSED*/
72 int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)73 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
74 {
75 return (PAM_IGNORE);
76 }
77
78 /*
79 * Set the privilege set. The attributes are enumerated by _enum_attrs,
80 * including the attribues user_attr, prof_attr and policy.conf
81 */
82 static int
getset(char * str,priv_set_t ** res)83 getset(char *str, priv_set_t **res)
84 {
85 priv_set_t *tmp;
86 char *badp;
87 int len;
88
89 if (str == NULL)
90 return (0);
91
92 len = strlen(str) + 1;
93 badp = alloca(len);
94 (void) memset(badp, '\0', len);
95 do {
96 const char *q, *endp;
97 tmp = priv_str_to_set(str, ",", &endp);
98 if (tmp == NULL) {
99 if (endp == NULL)
100 break;
101
102 /* Now remove the bad privilege endp points to */
103 q = strchr(endp, ',');
104 if (q == NULL)
105 q = endp + strlen(endp);
106
107 if (*badp != '\0')
108 (void) strlcat(badp, ",", len);
109 /* Memset above guarantees NUL termination */
110 /* LINTED */
111 (void) strncat(badp, endp, q - endp);
112 /* excise bad privilege; strtok ignores 2x sep */
113 (void) memmove((void *)endp, q, strlen(q) + 1);
114 }
115 } while (tmp == NULL && *str != '\0');
116
117 if (tmp == NULL) {
118 syslog(LOG_AUTH|LOG_ERR,
119 "pam_setcred: can't parse privilege specification: %m\n");
120 return (-1);
121 } else if (*badp != '\0') {
122 syslog(LOG_AUTH|LOG_DEBUG,
123 "pam_setcred: unrecognized privilege(s): %s\n", badp);
124 }
125 *res = tmp;
126 return (0);
127 }
128
129 typedef struct deflim {
130 char *def;
131 char *lim;
132 } deflim_t;
133
134 /*ARGSUSED*/
135 static int
finddeflim(const char * name,kva_t * kva,void * ctxt,void * pres)136 finddeflim(const char *name, kva_t *kva, void *ctxt, void *pres)
137 {
138 deflim_t *pdef = pres;
139 char *val;
140
141 if (pdef->def == NULL) {
142 val = kva_match(kva, USERATTR_DFLTPRIV_KW);
143 if (val != NULL)
144 pdef->def = strdup(val);
145 }
146 if (pdef->lim == NULL) {
147 val = kva_match(kva, USERATTR_LIMPRIV_KW);
148 if (val != NULL)
149 pdef->lim = strdup(val);
150 }
151 return (pdef->lim != NULL && pdef->def != NULL);
152 }
153
154 /*
155 * unix_cred - pam_sm_setcred
156 *
157 * Entry flags = PAM_ESTABLISH_CRED, set up Solaris Unix cred.
158 * PAM_DELETE_CRED, NOP, return PAM_SUCCESS.
159 * PAM_REINITIALIZE_CRED, set up Solaris Unix cred,
160 * or merge the current context with the new
161 * user.
162 * PAM_REFRESH_CRED, set up Solaris Unix cred.
163 * PAM_SILENT, print no messages to user.
164 *
165 * Returns PAM_SUCCESS, if all successful.
166 * PAM_CRED_ERR, if unable to set credentials.
167 * PAM_USER_UNKNOWN, if PAM_USER not set, or unable to find
168 * user in databases.
169 * PAM_SYSTEM_ERR, if no valid flag, or unable to get/set
170 * user's audit state.
171 */
172
173 int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)174 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
175 {
176 int i;
177 int debug = 0;
178 uint_t nowarn = flags & PAM_SILENT;
179 int ret = PAM_SUCCESS;
180 const char *user;
181 const char *auser;
182 const char *rhost;
183 const char *tty;
184 au_id_t auid;
185 adt_session_data_t *ah;
186 adt_termid_t *termid = NULL;
187 priv_set_t *lim, *def, *tset;
188 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
189 char buf[PROJECT_BUFSZ];
190 struct project proj, *pproj;
191 int error;
192 char *projname;
193 const char *kvs;
194 char *nckvs = NULL;
195 struct passwd pwd;
196 char pwbuf[NSS_BUFLEN_PASSWD];
197 deflim_t deflim;
198
199 for (i = 0; i < argc; i++) {
200 if (strcmp(argv[i], "debug") == 0)
201 debug = 1;
202 else if (strcmp(argv[i], "nowarn") == 0)
203 nowarn |= 1;
204 }
205
206 if (debug)
207 syslog(LOG_AUTH | LOG_DEBUG,
208 "pam_unix_cred: pam_sm_setcred(flags = %x, argc= %d)",
209 flags, argc);
210
211 (void) pam_get_item(pamh, PAM_USER, (const void **)&user);
212
213 if (user == NULL || *user == '\0') {
214 syslog(LOG_AUTH | LOG_ERR,
215 "pam_unix_cred: USER NULL or empty!\n");
216 return (PAM_USER_UNKNOWN);
217 }
218 (void) pam_get_item(pamh, PAM_AUSER, (const void **)&auser);
219 (void) pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
220 (void) pam_get_item(pamh, PAM_TTY, (const void **)&tty);
221 if (debug)
222 syslog(LOG_AUTH | LOG_DEBUG,
223 "pam_unix_cred: user = %s, auser = %s, rhost = %s, "
224 "tty = %s", user,
225 (auser == NULL) ? "NULL" : (*auser == '\0') ? "ZERO" :
226 auser,
227 (rhost == NULL) ? "NULL" : (*rhost == '\0') ? "ZERO" :
228 rhost,
229 (tty == NULL) ? "NULL" : (*tty == '\0') ? "ZERO" :
230 tty);
231
232 /* validate flags */
233 switch (flags & (PAM_ESTABLISH_CRED | PAM_DELETE_CRED |
234 PAM_REINITIALIZE_CRED | PAM_REFRESH_CRED)) {
235 case 0:
236 /* set default flag */
237 flags |= PAM_ESTABLISH_CRED;
238 break;
239 case PAM_ESTABLISH_CRED:
240 case PAM_REINITIALIZE_CRED:
241 case PAM_REFRESH_CRED:
242 break;
243 case PAM_DELETE_CRED:
244 return (PAM_SUCCESS);
245 default:
246 syslog(LOG_AUTH | LOG_ERR,
247 "pam_unix_cred: invalid flags %x", flags);
248 return (PAM_SYSTEM_ERR);
249 }
250
251 /*
252 * if auditing on and process audit state not set,
253 * setup audit context for process.
254 */
255 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
256 syslog(LOG_AUTH | LOG_ERR,
257 "pam_unix_cred: cannot create start audit session %m");
258 return (PAM_SYSTEM_ERR);
259 }
260 adt_get_auid(ah, &auid);
261 if (debug) {
262 int auditstate;
263
264 if (auditon(A_GETCOND, (caddr_t)&auditstate,
265 sizeof (auditstate)) != 0) {
266 auditstate = AUC_DISABLED;
267 }
268 syslog(LOG_AUTH | LOG_DEBUG,
269 "pam_unix_cred: state = %d, auid = %d", auditstate,
270 auid);
271 }
272 if (getpwnam_r(user, &pwd, pwbuf, sizeof (pwbuf)) == NULL) {
273 syslog(LOG_AUTH | LOG_ERR,
274 "pam_unix_cred: cannot get passwd entry for user = %s",
275 user);
276 ret = PAM_USER_UNKNOWN;
277 goto adt_done;
278 }
279
280 if ((auid == AU_NOAUDITID) &&
281 (flags & PAM_ESTABLISH_CRED)) {
282 struct passwd apwd;
283 char apwbuf[NSS_BUFLEN_PASSWD];
284
285 errno = 0;
286 if ((rhost == NULL || *rhost == '\0')) {
287 if (adt_load_ttyname(tty, &termid) != 0) {
288 if (errno == ENETDOWN) {
289 /*
290 * tolerate not being able to
291 * translate local hostname
292 * to a termid -- it will be
293 * "loopback".
294 */
295 syslog(LOG_AUTH | LOG_ERR,
296 "pam_unix_cred: cannot load "
297 "ttyname: %m, continuing.");
298 goto adt_setuser;
299 } else if (errno != 0) {
300 syslog(LOG_AUTH | LOG_ERR,
301 "pam_unix_cred: cannot load "
302 "ttyname: %m.");
303 } else {
304 syslog(LOG_AUTH | LOG_ERR,
305 "pam_unix_cred: cannot load "
306 "ttyname.");
307 }
308 ret = PAM_SYSTEM_ERR;
309 goto adt_done;
310 }
311 } else {
312 if (adt_load_hostname(rhost, &termid) != 0) {
313 if (errno != 0) {
314 syslog(LOG_AUTH | LOG_ERR,
315 "pam_unix_cred: cannot load "
316 "hostname: %m.");
317 } else {
318 syslog(LOG_AUTH | LOG_ERR,
319 "pam_unix_cred: cannot load "
320 "hostname.");
321 }
322 ret = PAM_SYSTEM_ERR;
323 goto adt_done;
324 }
325 }
326 adt_setuser:
327 if ((auser != NULL) && (*auser != '\0') &&
328 (getpwnam_r(auser, &apwd, apwbuf,
329 sizeof (apwbuf)) != NULL)) {
330 /*
331 * set up the initial audit for user coming
332 * from another user
333 */
334 if (adt_set_user(ah, apwd.pw_uid, apwd.pw_gid,
335 apwd.pw_uid, apwd.pw_gid, termid, ADT_NEW) != 0) {
336 syslog(LOG_AUTH | LOG_ERR,
337 "pam_unix_cred: cannot set auser audit "
338 "%m");
339 ret = PAM_SYSTEM_ERR;
340 goto adt_done;
341 }
342 if (adt_set_user(ah, pwd.pw_uid, pwd.pw_gid,
343 pwd.pw_uid, pwd.pw_gid, NULL,
344 ADT_UPDATE) != 0) {
345 syslog(LOG_AUTH | LOG_ERR,
346 "pam_unix_cred: cannot merge user audit "
347 "%m");
348 ret = PAM_SYSTEM_ERR;
349 goto adt_done;
350 }
351 if (debug) {
352 syslog(LOG_AUTH | LOG_DEBUG,
353 "pam_unix_cred: new audit set for %d:%d",
354 apwd.pw_uid, pwd.pw_uid);
355 }
356 } else {
357 /*
358 * No authenticated user or authenticated user is
359 * not a local user, no remote attribution, set
360 * up the initial audit as for direct user login
361 */
362 if (adt_set_user(ah, pwd.pw_uid, pwd.pw_gid,
363 pwd.pw_uid, pwd.pw_gid, termid, ADT_NEW) != 0) {
364 syslog(LOG_AUTH | LOG_ERR,
365 "pam_unix_cred: cannot set user audit %m");
366 ret = PAM_SYSTEM_ERR;
367 goto adt_done;
368 }
369 }
370 if (adt_set_proc(ah) != 0) {
371 syslog(LOG_AUTH | LOG_ERR,
372 "pam_unix_cred: cannot set process audit %m");
373 ret = PAM_CRED_ERR;
374 goto adt_done;
375 }
376 if (debug) {
377 syslog(LOG_AUTH | LOG_DEBUG,
378 "pam_unix_cred: new audit set for %d",
379 pwd.pw_uid);
380 }
381 } else if ((auid != AU_NOAUDITID) &&
382 (flags & PAM_REINITIALIZE_CRED)) {
383 if (adt_set_user(ah, pwd.pw_uid, pwd.pw_gid, pwd.pw_uid,
384 pwd.pw_gid, NULL, ADT_UPDATE) != 0) {
385 syslog(LOG_AUTH | LOG_ERR,
386 "pam_unix_cred: cannot set user audit %m");
387 ret = PAM_SYSTEM_ERR;
388 goto adt_done;
389 }
390 if (adt_set_proc(ah) != 0) {
391 syslog(LOG_AUTH | LOG_ERR,
392 "pam_unix_cred: cannot set process audit %m");
393 ret = PAM_CRED_ERR;
394 goto adt_done;
395 }
396 if (debug) {
397 syslog(LOG_AUTH | LOG_DEBUG,
398 "pam_unix_cred: audit merged for %d:%d",
399 auid, pwd.pw_uid);
400 }
401 } else if (debug) {
402 syslog(LOG_AUTH | LOG_DEBUG,
403 "pam_unix_cred: audit already set for %d", auid);
404 }
405 adt_done:
406 if (termid != NULL)
407 free(termid);
408 if (adt_end_session(ah) != 0) {
409 syslog(LOG_AUTH | LOG_ERR,
410 "pam_unix_cred: unable to end audit session");
411 }
412
413 if (ret != PAM_SUCCESS)
414 return (ret);
415
416 /* Initialize the user's project */
417 (void) pam_get_item(pamh, PAM_RESOURCE, (const void **)&kvs);
418 if (kvs != NULL) {
419 char *tmp, *lasts, *tok;
420
421 nckvs = tmp = strdup(kvs);
422 if (nckvs == NULL)
423 return (PAM_BUF_ERR);
424
425 while ((tok = strtok_r(tmp, ";", &lasts)) != NULL) {
426 if (strncmp(tok, PROJECT, PROJSZ) == 0) {
427 projname = tok + PROJSZ;
428 break;
429 }
430 tmp = NULL;
431 }
432 } else {
433 projname = NULL;
434 }
435
436 if (projname == NULL || *projname == '\0') {
437 pproj = getdefaultproj(user, &proj, (void *)&buf,
438 PROJECT_BUFSZ);
439 } else {
440 pproj = getprojbyname(projname, &proj, (void *)&buf,
441 PROJECT_BUFSZ);
442 }
443 /* projname points into nckvs; this is the first opportunity to free */
444 free(nckvs);
445 if (pproj == NULL) {
446 syslog(LOG_AUTH | LOG_ERR,
447 "pam_unix_cred: no default project for user %s", user);
448 if (!nowarn) {
449 (void) snprintf(messages[0], sizeof (messages[0]),
450 dgettext(TEXT_DOMAIN, "No default project!"));
451 (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
452 1, messages, NULL);
453 }
454 return (PAM_SYSTEM_ERR);
455 }
456 if ((error = setproject(proj.pj_name, user, TASK_NORMAL)) != 0) {
457 kva_t *kv_array;
458
459 switch (error) {
460 case SETPROJ_ERR_TASK:
461 if (errno == EAGAIN) {
462 syslog(LOG_AUTH | LOG_ERR,
463 "pam_unix_cred: project \"%s\" resource "
464 "control limit has been reached",
465 proj.pj_name);
466 (void) snprintf(messages[0],
467 sizeof (messages[0]), dgettext(
468 TEXT_DOMAIN,
469 "Resource control limit has been "
470 "reached"));
471 } else {
472 syslog(LOG_AUTH | LOG_ERR,
473 "pam_unix_cred: user %s could not join "
474 "project \"%s\": %m", user, proj.pj_name);
475 (void) snprintf(messages[0],
476 sizeof (messages[0]), dgettext(
477 TEXT_DOMAIN,
478 "Could not join default project"));
479 }
480 if (!nowarn)
481 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1,
482 messages, NULL);
483 break;
484 case SETPROJ_ERR_POOL:
485 (void) snprintf(messages[0], sizeof (messages[0]),
486 dgettext(TEXT_DOMAIN,
487 "Could not bind to resource pool"));
488 switch (errno) {
489 case EACCES:
490 syslog(LOG_AUTH | LOG_ERR,
491 "pam_unix_cred: project \"%s\" could not "
492 "bind to resource pool: No resource pool "
493 "accepting default bindings exists",
494 proj.pj_name);
495 (void) snprintf(messages[1],
496 sizeof (messages[1]),
497 dgettext(TEXT_DOMAIN,
498 "No resource pool accepting "
499 "default bindings exists"));
500 break;
501 case ESRCH:
502 syslog(LOG_AUTH | LOG_ERR,
503 "pam_unix_cred: project \"%s\" could not "
504 "bind to resource pool: The resource pool "
505 "is unknown", proj.pj_name);
506 (void) snprintf(messages[1],
507 sizeof (messages[1]),
508 dgettext(TEXT_DOMAIN,
509 "The specified resource pool "
510 "is unknown"));
511 break;
512 default:
513 (void) snprintf(messages[1],
514 sizeof (messages[1]),
515 dgettext(TEXT_DOMAIN,
516 "Failure during pool binding"));
517 syslog(LOG_AUTH | LOG_ERR,
518 "pam_unix_cred: project \"%s\" could not "
519 "bind to resource pool: %m", proj.pj_name);
520 }
521 if (!nowarn)
522 (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
523 2, messages, NULL);
524 break;
525 default:
526 /*
527 * Resource control assignment failed. Unlike
528 * newtask(1), we treat this as an error.
529 */
530 if (error < 0) {
531 /*
532 * This isn't supposed to happen, but in
533 * case it does, this error message
534 * doesn't use error as an index, like
535 * the others might.
536 */
537 syslog(LOG_AUTH | LOG_ERR,
538 "pam_unix_cred: unkwown error joining "
539 "project \"%s\" (%d)", proj.pj_name, error);
540 (void) snprintf(messages[0],
541 sizeof (messages[0]),
542 dgettext(TEXT_DOMAIN,
543 "unkwown error joining project \"%s\""
544 " (%d)"), proj.pj_name, error);
545 } else if ((kv_array = _str2kva(proj.pj_attr, KV_ASSIGN,
546 KV_DELIMITER)) != NULL) {
547 syslog(LOG_AUTH | LOG_ERR,
548 "pam_unix_cred: %s resource control "
549 "assignment failed for project \"%s\"",
550 kv_array->data[error - 1].key,
551 proj.pj_name);
552 (void) snprintf(messages[0],
553 sizeof (messages[0]),
554 dgettext(TEXT_DOMAIN,
555 "%s resource control assignment failed for "
556 "project \"%s\""),
557 kv_array->data[error - 1].key,
558 proj.pj_name);
559 _kva_free(kv_array);
560 } else {
561 syslog(LOG_AUTH | LOG_ERR,
562 "pam_unix_cred: resource control "
563 "assignment failed for project \"%s\""
564 "attribute %d", proj.pj_name, error);
565 (void) snprintf(messages[0],
566 sizeof (messages[0]),
567 dgettext(TEXT_DOMAIN,
568 "resource control assignment failed for "
569 "project \"%s\" attribute %d"),
570 proj.pj_name, error);
571 }
572 if (!nowarn)
573 (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
574 1, messages, NULL);
575 }
576 return (PAM_SYSTEM_ERR);
577 }
578
579 tset = def = lim = NULL;
580 deflim.def = deflim.lim = NULL;
581
582 (void) _enum_attrs(user, finddeflim, NULL, &deflim);
583
584 if (getset(deflim.lim, &lim) != 0 || getset(deflim.def, &def) != 0) {
585 ret = PAM_SYSTEM_ERR;
586 goto out;
587 }
588
589 if (def == NULL) {
590 def = priv_allocset();
591 if (def == NULL) {
592 ret = PAM_SYSTEM_ERR;
593 goto out;
594 }
595 priv_basicset(def);
596 errno = 0;
597 if ((pathconf("/", _PC_CHOWN_RESTRICTED) == -1) && (errno == 0))
598 (void) priv_addset(def, PRIV_FILE_CHOWN_SELF);
599 }
600 /*
601 * Silently limit the privileges to those actually available
602 * in the current zone.
603 */
604 tset = priv_allocset();
605 if (tset == NULL) {
606 ret = PAM_SYSTEM_ERR;
607 goto out;
608 }
609 if (getppriv(PRIV_PERMITTED, tset) != 0) {
610 ret = PAM_SYSTEM_ERR;
611 goto out;
612 }
613 if (!priv_issubset(def, tset))
614 priv_intersect(tset, def);
615 /*
616 * We set privilege awareness here so that I gets copied to
617 * P & E when the final setuid(uid) happens.
618 */
619 (void) setpflags(PRIV_AWARE, 1);
620 if (setppriv(PRIV_SET, PRIV_INHERITABLE, def) != 0) {
621 syslog(LOG_AUTH | LOG_ERR,
622 "pam_setcred: setppriv(defaultpriv) failed: %m");
623 ret = PAM_CRED_ERR;
624 }
625
626 if (lim != NULL) {
627 /*
628 * Silently limit the privileges to the limit set available.
629 */
630 if (getppriv(PRIV_LIMIT, tset) != 0) {
631 ret = PAM_SYSTEM_ERR;
632 goto out;
633 }
634 if (!priv_issubset(lim, tset))
635 priv_intersect(tset, lim);
636 if (setppriv(PRIV_SET, PRIV_LIMIT, lim) != 0) {
637 syslog(LOG_AUTH | LOG_ERR,
638 "pam_setcred: setppriv(limitpriv) failed: %m");
639 ret = PAM_CRED_ERR;
640 goto out;
641 }
642 /*
643 * In order not to surprise certain applications, we
644 * need to get rid of privilege awareness and thus we must
645 * set this flag which will cause a reset on set*uid().
646 */
647 (void) setpflags(PRIV_AWARE_RESET, 1);
648 }
649 /*
650 * This may fail but we do not care as this will be reset later
651 * when the uids are set to their final values.
652 */
653 (void) setpflags(PRIV_AWARE, 0);
654 /*
655 * Remove PRIV_PFEXEC; stop running as if we are under a profile
656 * shell. A user with a profile shell will set PRIV_PFEXEC.
657 */
658 (void) setpflags(PRIV_PFEXEC, 0);
659
660 out:
661 free(deflim.lim);
662 free(deflim.def);
663
664 if (lim != NULL)
665 priv_freeset(lim);
666 if (def != NULL)
667 priv_freeset(def);
668 if (tset != NULL)
669 priv_freeset(tset);
670
671 return (ret);
672 }
673