summaryrefslogtreecommitdiffstats
path: root/lib/libaccess/nsamgmt.cpp
blob: f2bc93e76c35fa98556227f28be3df953ce21fd2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 * END COPYRIGHT BLOCK **/

/*
 * Description (nsamgmt.c)
 *
 *	This module contains routines for managing information in a
 *	Netscape authentication database.  An authentication database
 *	consists of a user database and a group database.  This module
 *	implements an authentication database based on Netscape user and
 *	group databases defined in nsuser.h and nsgroup.h, which in turn
 *	are based on the Netscape (server) database implementation
 *	defined in nsdb.h.  The interface for retrieving information
 *	from an authentication database is described separately in
 *	nsadb.h.
 */

#include "base/systems.h"
#include "netsite.h"
#include "base/file.h"
#define __PRIVATE_NSADB
#include "libaccess/nsamgmt.h"
#include "libaccess/nsumgmt.h"
#include "libaccess/nsgmgmt.h"

/*
 * Description (nsadbEnumUsersHelp)
 *
 *	This is a local function that is called by NSDB during user
 *	database enumeration.  It decodes user records into user
 *	objects, and presents them to the caller of nsadbEnumerateUsers(),
 *	via the specified call-back function.  The call-back function
 *	return value may be a negative error code, which will cause
 *	enumeration to stop, and the error code will be returned from
 *	nsadbEnumerateUsers().  If the return value of the call-back
 *	function is not negative, it can contain one or more of the
 *	following flags:
 *
 *		ADBF_KEEPOBJ	- do not free the UserObj_t structure
 *				  that was passed to the call-back function
 *		ADBF_STOPENUM	- stop the enumeration without an error
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	parg			- pointer to UserEnumArgs_t structure
 *	namelen			- user record key length including null
 *				  terminator
 *	name			- user record key (user account name)
 *	reclen			- length of user record
 *	recptr			- pointer to user record contents
 *
 * Returns:
 *
 *	If the call-back returns a negative result, that value is
 *	returned.  If the call-back returns ADBF_STOPENUM, then
 *	-1 is returned, causing the enumeration to stop.  Otherwise
 *	the return value is zero.
 */

typedef struct EnumUserArgs_s EnumUserArgs_t;
struct EnumUserArgs_s {
    void * authdb;
    int (*func)(NSErr_t * ferrp,
		void * authdb, void * argp, UserObj_t * uoptr);
    void * user;
    int rv;
};

static int nsadbEnumUsersHelp(NSErr_t * errp, void * parg,
			      int namelen, char * name,
			      int reclen, char * recptr)
{
    EnumUserArgs_t * ue = (EnumUserArgs_t *)parg;
    UserObj_t * uoptr;			/* user object pointer */
    int rv;

    uoptr = userDecode((NTS_t)name, reclen, (ATR_t)recptr);
    if (uoptr != 0) {
	rv = (*ue->func)(errp, ue->authdb, ue->user, uoptr);
	if (rv >= 0) {

	    /* Count the number of users seen */
	    ue->rv += 1;

	    /* Free the user object unless the call-back says not to */
	    if (!(rv & ADBF_KEEPOBJ)) {
		userFree(uoptr);
	    }
	    /* Return either 0 or -1, depending on ADBF_STOPENUM */
	    rv = (rv & ADBF_STOPENUM) ? -1 : 0;
	}
	else {
	    /* Free the user object in the event of an error */
	    userFree(uoptr);

	    /* Also return the error code */
	    ue->rv = rv;
	}
    }

    return rv;
}

/*
 * Description (nsadbEnumGroupsHelp)
 *
 *	This is a local function that is called by NSDB during group
 *	database enumeration.  It decodes group records into group
 *	objects, and presents them to the caller of nsadbEnumerateGroups(),
 *	via the specified call-back function.  The call-back function
 *	return value may be a negative error code, which will cause
 *	enumeration to stop, and the error code will be returned from
 *	nsadbEnumerateGroups().  If the return value of the call-back
 *	function is not negative, it can contain one or more of the
 *	following flags:
 *
 *		ADBF_KEEPOBJ	- do not free the GroupObj_t structure
 *				  that was passed to the call-back function
 *		ADBF_STOPENUM	- stop the enumeration without an error
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	parg			- pointer to GroupEnumArgs_t structure
 *	namelen			- group record key length including null
 *				  terminator
 *	name			- group record key (group name)
 *	reclen			- length of group record
 *	recptr			- pointer to group record contents
 *
 * Returns:
 *
 *	If the call-back returns a negative result, that value is
 *	returned.  If the call-back returns ADBF_STOPENUM, then
 *	-1 is returned, causing the enumeration to stop.  Otherwise
 *	the return value is zero.
 */

typedef struct EnumGroupArgs_s EnumGroupArgs_t;
struct EnumGroupArgs_s {
    void * authdb;
    int (*func)(NSErr_t * ferrp,
		void * authdb, void * argp, GroupObj_t * goptr);
    void * user;
    int rv;
};

static int nsadbEnumGroupsHelp(NSErr_t * errp, void * parg,
			       int namelen, char * name,
			       int reclen, char * recptr)
{
    EnumGroupArgs_t * eg = (EnumGroupArgs_t *)parg;
    GroupObj_t * goptr;			/* group object pointer */
    int rv;

    goptr = groupDecode((NTS_t)name, reclen, (ATR_t)recptr);
    if (goptr != 0) {
	rv = (*eg->func)(errp, eg->authdb, eg->user, goptr);
	if (rv >= 0) {

	    /* Count the number of groups seen */
	    eg->rv += 1;

	    /* Free the group object unless the call-back says not to */
	    if (!(rv & ADBF_KEEPOBJ)) {
		groupFree(goptr);
	    }
	    /* Return either 0 or -1, depending on ADBF_STOPENUM */
	    rv = (rv & ADBF_STOPENUM) ? -1 : 0;
	}
	else {
	    /* Free the group object in the event of an error */
	    groupFree(goptr);

	    /* Also return the error code */
	    eg->rv = rv;
	}
    }

    return rv;
}

NSPR_BEGIN_EXTERN_C

/*
 * Description (nsadbAddGroupToGroup)
 *
 *	This function adds a child group, C, to the definition of a
 *	parent group P.  This involves updating the group entries of
 *	C and P in the group database.  It also involves updating
 *	the group lists of any user descendants of C, to reflect the
 *	fact that these users are now members of P and P's ancestors.
 *	A check is made for an attempt to create a cycle in the group
 *	hierarchy, and this is rejected as an error.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	pgoptr			- pointer to parent group object
 *	cgoptr			- pointer to child group object
 *
 * Returns:
 *
 *	The return value is zero if group C was not already a direct
 *	member of group P, and was added successfully.  A return value
 *	of +1 indicates that group C was already a direct member of
 *	group P.  A negative return value indicates an error.
 */

NSAPI_PUBLIC int nsadbAddGroupToGroup(NSErr_t * errp, void * authdb,
			 GroupObj_t * pgoptr, GroupObj_t * cgoptr)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    USIList_t gsuper;		/* list of ancestors of group P */
    USIList_t dglist;		/* descendant groups of C */
    GroupObj_t * dgoptr;	/* descendant group object pointer */
    UserObj_t * uoptr;		/* user object pointer */
    USI_t id;			/* current descendant group id */
    int usercount;		/* count of users for descendant */
    USI_t * userlist;		/* pointer to array of descendant user ids */
    USI_t * idlist;		/* pointer to array of descendant group ids */
    int pass;			/* loop pass number */
    int i;			/* loop index */
    int rv;			/* result value */

    /* Is C a direct member of P already? */
    if (usiPresent(&pgoptr->go_groups, cgoptr->go_gid)) {
	/* Yes, indicate that */
	return 0;
    }

    dgoptr = 0;
    uoptr = 0;

    /* Initialize a list of the group descendants of group C */
    UILINIT(&dglist);

    /* Initialize a list of P and its ancestors */
    UILINIT(&gsuper);

    /* Add P to the ancestor list */
    rv = usiInsert(&gsuper, pgoptr->go_gid);
    if (rv < 0) goto punt;

    /* Open user database since the group lists of users may be modified */
    rv = nsadbOpenUsers(errp, authdb, ADBF_UWRITE);
    if (rv < 0) goto punt;

    /* Open group database since group entries will be modified */
    rv = nsadbOpenGroups(errp, authdb, ADBF_GWRITE);
    if (rv < 0) goto punt;

    /* Merge all the ancestors of group P into the list */
    rv = nsadbSuperGroups(errp, authdb, pgoptr, &gsuper);
    if (rv < 0) goto punt;

    /*
     * Each pass through the following loop visits C and all of C's
     * descendant groups.
     *
     * The first pass checks to see if making group C a member of
     * group P would create a cycle in the group structure.  It does
     * this by examining C and all of its dependents to see if any
     * appear in the list containing P and P's ancestors.
     *
     * The second pass updates the group lists of all users contained
     * in group C to include P and P's ancestors.
     */

    for (pass = 1; pass < 3; ++pass) {

	/* Use the group C as the first descendant */
	id = cgoptr->go_gid;
	dgoptr = cgoptr;

	for (;;) {

	    if (pass == 1) {
		/*
		 * Check for attempt to create a cycle in the group
		 * hierarchy.  See if this descendant is a member of
		 * the list of P and P's ancestors (gsuper).
		 */
		if (usiPresent(&gsuper, id)) {
		    /*
		     * Error - operation would create a cycle
		     * in the group structure.
		     */
		    return -1;
		}
	    }
	    else {

		/*
		 * Merge the list of ancestors of P (gsuper) with the
		 * group lists of any direct user members of the current
		 * descendant group, referenced by dgoptr.
		 */

		/* Get direct user member list size and pointer */
		usercount = UILCOUNT(&dgoptr->go_users);
		userlist = UILLIST(&dgoptr->go_users);

		/* For each direct user member of this descendant ... */
		for (i = 0; i < usercount; ++i) {

		    /* Get a user object for the user */
		    uoptr = userFindByUid(errp,
					  adb->adb_userdb, userlist[i]);
		    if (uoptr == 0) {
			/*
			 * Error - user not found,
			 * databases are inconsistent.
			 */
			rv = -1;
			goto punt;
		    }

		    /* Merge gsuper into the user's group list */
		    rv = uilMerge(&uoptr->uo_groups, &gsuper);
		    if (rv < 0) goto punt;

		    /* Write out the user object */
		    uoptr->uo_flags |= UOF_MODIFIED;
		    rv = userStore(errp, adb->adb_userdb, 0, uoptr);
		    if (rv) goto punt;

		    /* Free the user object */
		    userFree(uoptr);
		    uoptr = 0;
		}
	    }

	    /*
	     * Merge the direct member groups of the current descendant
	     * group into the list of descendants to be processed.
	     */
	    rv = uilMerge(&dglist, &dgoptr->go_groups);
	    if (rv < 0) goto punt;

	    /* Free the group object for the current descendant */
	    if (dgoptr != cgoptr) {
		groupFree(dgoptr);
		dgoptr = 0;
	    }

	    /* Exit the loop if the descendant list is empty */
	    if (UILCOUNT(&dglist) <= 0) break;

	    /* Otherwise remove the next descendant from the list */
	    idlist = UILLIST(&dglist);
	    id = idlist[0];
	    rv = usiRemove(&dglist, id);
	    if (rv < 0) goto punt;

	    /* Now get a group object for this descendant group */
	    dgoptr = groupFindByGid(errp, adb->adb_groupdb, id);
	    if (dgoptr == 0) {
		/* Error - group not found, databases are inconsistent */
		rv = -1;
		goto punt;
	    }
	}
    }

    /* Now add C to P's list of member groups */
    rv = usiInsert(&pgoptr->go_groups, cgoptr->go_gid);
    if (rv < 0) goto punt;

    /* Add P to C's list of parent groups */
    rv = usiInsert(&cgoptr->go_pgroups, pgoptr->go_gid);
    if (rv < 0) goto punt;

    /* Update the database entry for group C */
    cgoptr->go_flags |= GOF_MODIFIED;
    rv = groupStore(errp, adb->adb_groupdb, 0, cgoptr);
    if (rv) goto punt;

    /* Update the database entry for group P */
    pgoptr->go_flags |= GOF_MODIFIED;
    rv = groupStore(errp, adb->adb_groupdb, 0, pgoptr);

    return rv;

  punt:
    /* Handle errors */
    UILFREE(&gsuper);
    UILFREE(&dglist);
    if (dgoptr) {
	groupFree(dgoptr);
    }
    if (uoptr) {
	userFree(uoptr);
    }
    return rv;
}

/*
 * Description (nsadbAddUserToGroup)
 *
 *	This function adds a user to a group definition.  This involves
 *	updating the group entry in the group database, and the user
 *	entry in the user database.  The caller provides a pointer to
 *	a user object for the user to be added, a pointer to a group
 *	object for the group being modified, and a handle for the
 *	authentication databases (from nsadbOpen()).
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	goptr			- pointer to group object
 *	uoptr			- pointer to user object
 *
 * Returns:
 *
 *	The return value is zero if the user was not already a direct
 *	member of the group, and was added successfully.  A return value
 *	of +1 indicates that the user was already a direct member of the
 *	group.  A negative return value indicates an error.
 */

NSAPI_PUBLIC int nsadbAddUserToGroup(NSErr_t * errp, void * authdb,
			GroupObj_t * goptr, UserObj_t * uoptr)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    USIList_t nglist;		/* new group list for specified user */
    USIList_t gsuper;		/* groups containing+ the specified group */
    GroupObj_t * aoptr;		/* group object for 'id' group */
    USI_t * idlist;		/* pointer to gsuper gid array */
    USI_t id;			/* current gid from gsuper */
    int rv;			/* result value */

    /* Is the user already a direct member of the group? */
    if (usiPresent(&goptr->go_users, uoptr->uo_uid)) {

	/* Yes, nothing to do */
	return 1;
    }

    /*
     * The user object contains a list of all of the groups that contain
     * the user, either directly or indirectly.  We need to add the
     * specified group and its ancestors to this list.  Each group contains
     * a list of the group's parents, which is used to locate all of the
     * group's ancestors.  As an optimization, we need not consider any
     * ancestors which are already on the user's current group list.
     */

    /*
     * The following loop will deal with two lists of group ids.  One
     * is the list that will become the new group list for the user,
     * which is initialized to the user's current group list.  The other
     * is a list of ancestors of the group to be considered for addition
     * to the user's group list.  This list is initialized to the specified
     * group.
     */

    /* Initialize both lists to be empty */
    UILINIT(&nglist);
    UILINIT(&gsuper);

    /* Make a copy of the user's current group list */
    rv = uilDuplicate(&nglist, &uoptr->uo_groups);
    if (rv < 0) goto punt;

    /* Start the other list with the specified group */
    rv = usiInsert(&gsuper, goptr->go_gid);
    if (rv < 0) goto punt;

    /* Open user database since the group lists of users may be modified */
    rv = nsadbOpenUsers(errp, authdb, ADBF_UWRITE);
    if (rv < 0) goto punt;

    /* Open group database since group entries will be modified */
    rv = nsadbOpenGroups(errp, authdb, ADBF_GWRITE);
    if (rv < 0) goto punt;

    /* While entries remain on the ancestor list */
    while (UILCOUNT(&gsuper) > 0) {

	/* Get pointer to array of ancestor group ids */
	idlist = UILLIST(&gsuper);

	/* Remove the first ancestor */
	id = idlist[0];
	usiRemove(&gsuper, id);

	/* Is the ancestor on the user's current group list? */
	if (!usiPresent(&uoptr->uo_groups, id)) {

	    /* No, add its parents to the ancestor list */

	    /* Look up the ancestor group (get a group object for it) */
	    aoptr = groupFindByGid(errp, adb->adb_groupdb, id);
	    if (aoptr == 0) {
		/* Error - group not found, database inconsistent */
		rv = -1;
		goto punt;
	    }

	    /* Merge the ancestors parents into the ancestor list */
	    rv = uilMerge(&gsuper, &aoptr->go_pgroups);

	    /* Lose the ancestor group object */
	    groupFree(aoptr);

	    /* See if the merge worked */
	    if (rv < 0) goto punt;
	}

	/* Add the ancestor to the new group list for the user */
	rv = usiInsert(&nglist, id);
	if (rv < 0) goto punt;
    }

    /* Add the user to the group's user member list */
    rv = usiInsert(&goptr->go_users, uoptr->uo_uid);
    if (rv < 0) goto punt;

    /* Replace the user's group list with the new one */
    UILREPLACE(&uoptr->uo_groups, &nglist);
    
    /* Write out the updated user object */
    uoptr->uo_flags |= UOF_MODIFIED;
    rv = userStore(errp, adb->adb_userdb, 0, uoptr);
    if (rv < 0) goto punt;

    /* Write out the updated group object */
    goptr->go_flags |= GOF_MODIFIED;
    rv = groupStore(errp, adb->adb_groupdb, 0, goptr);
    
    return rv;

  punt:
    /* Handle error */

    /* Free ancestor and new group lists */
    UILFREE(&nglist);
    UILFREE(&gsuper);

    return rv;
}

/*
 * Description (nsadbCreateGroup)
 *
 *	This function creates a new group in a specified authentication
 *	database.  The group is described by a group object.  A group
 *	object can be created by calling nsadbGroupNew().
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	goptr			- pointer to group object
 *
 * Returns:
 */

NSAPI_PUBLIC int nsadbCreateGroup(NSErr_t * errp, void * authdb, GroupObj_t * goptr)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    int rv;

    /* Open the group database for write access */
    rv = nsadbOpenGroups(errp, authdb, ADBF_GWRITE);
    if (rv < 0) goto punt;

    /* Add this group to the database */
    rv = groupStore(errp, adb->adb_groupdb, 0, goptr);

  punt:
    return rv;
}

/*
 * Description (nsadbCreateUser)
 *
 *	This function creates a new user in a specified authentication
 *	database.  The user is described by a user object.  A user
 *	object can be created by calling nsadbUserNew().
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	uoptr			- pointer to user object
 *
 * Returns:
 */

NSAPI_PUBLIC int nsadbCreateUser(NSErr_t * errp, void * authdb, UserObj_t * uoptr)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    int rv;

    /* Open the user database for write access */
    rv = nsadbOpenUsers(errp, authdb, ADBF_UWRITE);
    if (rv < 0) goto punt;

    /* Add this user to the database */
    rv = userStore(errp, adb->adb_userdb, 0, uoptr);

  punt:
    return rv;
}

/*
 * Description (nsadbEnumerateUsers)
 *
 *	This function is called to enumerate all of the users in a
 *	given authentication database to a call-back function specified
 *	by the caller.  The call-back function is provided with a
 *	handle for the authentication database, an opaque value provided
 *	by the caller, and a pointer to a user object.  See the
 *	description of nsadbEnumUsersHelp above for the interpretation
 *	of the call-back function's return value.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	argp			- opaque value for call-back function
 *	func			- pointer to call-back function
 *
 * Returns:
 *
 *	If the call-back function returns a negative error code, this
 *	value is returned.  A negative value may also be returned if
 *	nsadb encounters an error.  Otherwise the result is the number
 *	of users enumerated.
 */

NSAPI_PUBLIC int nsadbEnumerateUsers(NSErr_t * errp, void * authdb, void * argp,
#ifdef UnixWare
	ArgFn_EnumUsers func) /* for ANSI C++ standard, see nsamgmt.h */
#else
	int (*func)(NSErr_t * ferrp, void * authdb, void * parg, UserObj_t * uoptr))
#endif
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    EnumUserArgs_t args;		/* arguments for enumeration helper */
    int rv;				/* result value */

    /* Open the users subdatabase for read access */
    rv = nsadbOpenUsers(errp, authdb, ADBF_UREAD);
    if (rv < 0) goto punt;

    args.authdb = authdb;
    args.func = func;
    args.user = argp;
    args.rv = 0;

    rv = ndbEnumerate(errp, adb->adb_userdb,
		      NDBF_ENUMNORM, (void *)&args, nsadbEnumUsersHelp);
    if (rv < 0) goto punt;

    rv = args.rv;

  punt:
    return rv;
}

/*
 * Description (nsadbEnumerateGroups)
 *
 *	This function is called to enumerate all of the groups in a
 *	given authentication database to a call-back function specified
 *	by the caller.  The call-back function is provided with a
 *	handle for the authentication database, an opaque value provided
 *	by the caller, and a pointer to a group object.  See the
 *	description of nsadbEnumGroupsHelp above for the interpretation
 *	of the call-back function's return value.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	argp			- opaque value for call-back function
 *	func			- pointer to call-back function
 *
 * Returns:
 *
 *	If the call-back function returns a negative error code, this
 *	value is returned.  A negative value may also be returned if
 *	nsadb encounters an error.  Otherwise the result is the number
 *	of groups enumerated.
 */

NSAPI_PUBLIC int nsadbEnumerateGroups(NSErr_t * errp, void * authdb, void * argp,
#ifdef UnixWare
	ArgFn_EnumGroups func) /* for ANSI C++ standard, see nsamgmt.h */
#else
	int (*func)(NSErr_t * ferrp, void * authdb, void * parg, GroupObj_t * goptr))
#endif
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    EnumGroupArgs_t args;
    int rv;				/* result value */

    /* Open group database for read access */
    rv = nsadbOpenGroups(errp, authdb, ADBF_GREAD);
    if (rv < 0) goto punt;

    args.authdb = authdb;
    args.func = func;
    args.user = argp;
    args.rv = 0;

    rv = ndbEnumerate(errp, adb->adb_groupdb,
		      NDBF_ENUMNORM, (void *)&args, nsadbEnumGroupsHelp);
    if (rv < 0) goto punt;

    rv = args.rv;

  punt:
    return rv;
}

/*
 * Description (nsadbIsUserInGroup)
 *
 *	This function tests whether a given user id is a member of the
 *	group associated with a specified group id.  The caller may
 *	provide a list of group ids for groups to which the user is
 *	already known to belong, and this may speed up the check.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	uid			- user id
 *	gid			- group id
 *	ngroups			- number of group ids in grplist
 *	grplist			- groups the user is known to belong to
 *
 * Returns:
 *
 *	The return value is +1 if the user is found to belong to the
 *	indicated group, or 0 if the user does not belong to the group.
 *	An error is indicated by a negative return value.
 */

NSAPI_PUBLIC int nsadbIsUserInGroup(NSErr_t * errp, void * authdb,
		       USI_t uid, USI_t gid, int ngroups, USI_t * grplist)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    USIList_t dglist;			/* descendant group list */
    GroupObj_t * goptr = 0;		/* group object pointer */
    USI_t * idlist;			/* pointer to array of group ids */
    USI_t tgid;				/* test group id */
    int i;				/* loop index */
    int rv;				/* result value */

    UILINIT(&dglist);

    /* Open group database for read access */
    rv = nsadbOpenGroups(errp, authdb, ADBF_GREAD);
    if (rv < 0) goto punt;

    for (tgid = gid;;) {

	/* Get a group object for this group id */
	goptr = groupFindByGid(errp, adb->adb_groupdb, tgid);
	if (goptr == 0) {
	    /* Error - group id not found, databases are inconsistent */
	    rv = -1;
	    goto punt;
	}

	/* Is the user a direct member of this group? */
	if (usiPresent(&goptr->go_users, uid)) goto is_member;

	/*
	 * Is there any group to which the user is already known to
	 * belong that is a direct group member of this group?  If so,
	 * the user is also a member of this group.
	 */

	/* Scan list of groups to which the user is known to belong */
	for (i = 0; i < ngroups; ++i) {

	    if (usiPresent(&goptr->go_groups, grplist[i])) goto is_member;
	}

	/* Merge group member list of this group with descendants list */
	rv = uilMerge(&dglist, &goptr->go_groups);
	if (rv < 0) goto punt;

	/*
	 * If descendants list is empty, the user is not contained in
	 * the specified group.
	 */
	if (UILCOUNT(&dglist) <= 0) {
	    rv = 0;
	    goto punt;
	}

	/* Remove the next id from the descendants list */
	idlist = UILLIST(&dglist);
	tgid = idlist[0];

	rv = usiRemove(&dglist, tgid);
	if (rv < 0) goto punt;

	groupFree(goptr);
	goptr = 0;
    }

  is_member:
    rv = 1;

  punt:
    if (goptr) {
	groupFree(goptr);
    }
    UILFREE(&dglist);
    return rv;
}

/*
 * Description (nsadbModifyGroup)
 *
 *	This function is called to write modifications to a group to
 *	a specified authentication database.  The group is assumed to
 *	already exist in the database.  Information about the group
 *	is passed in a group object.  This function should not be used
 *	to alter the lists of group members or parents.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	goptr			- pointer to modified group object
 *
 * Returns:
 *
 *	The return value is zero if the group information is successfully
 *	updated.  An error is indicated by a negative return value, and
 *	an error frame is generated if an error frame list is provided.
 */

NSAPI_PUBLIC int nsadbModifyGroup(NSErr_t * errp, void * authdb, GroupObj_t * goptr)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    int rv;

    rv = nsadbOpenGroups(errp, authdb, ADBF_GWRITE);
    if (rv < 0) goto punt;

    rv = groupStore(errp, adb->adb_groupdb, 0, goptr);

  punt:
    return rv;
}

/*
 * Description (nsadbModifyUser)
 *
 *	This function is called to write modifications to a user to
 *	a specified authentication database.  The user is assumed to
 *	already exist in the database.  Information about the user
 *	is passed in a user object.  This function should not be used
 *	to modify the list of groups which contain the user.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	uoptr			- pointer to modified user object
 *
 * Returns:
 *
 *	The return value is zero if the user information is successfully
 *	updated.  An error is indicated by a negative return value, and
 *	an error frame is generated if an error frame list is provided.
 */

NSAPI_PUBLIC int nsadbModifyUser(NSErr_t * errp, void * authdb, UserObj_t * uoptr)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    int rv;

    rv = nsadbOpenUsers(errp, authdb, ADBF_UWRITE);
    if (rv < 0) goto punt;

    rv = userStore(errp, adb->adb_userdb, 0, uoptr);

  punt:
    return rv;
}

/*
 * Description (nsadbRemoveGroup)
 *
 *	This function is called to remove a given group name from
 *	a specified authentication database.  This can cause updates
 *	to both the user and group subdatabases.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	name			- pointer to name of group to remove
 *
 * Returns:
 *
 *	The return value is zero if the group information is successfully
 *	removed.  An error is indicated by a negative return value, and
 *	an error frame is generated if an error frame list is provided.
 */

NSAPI_PUBLIC int nsadbRemoveGroup(NSErr_t * errp, void * authdb, char * name)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    UserObj_t * uoptr = 0;		/* user object pointer */
    GroupObj_t * goptr = 0;		/* group object pointer */
    GroupObj_t * ogoptr = 0;		/* other group object pointer */
    char * ugname;			/* user or group name */
    USI_t * list;			/* pointer into user/group id list */
    int cnt;				/* count of user or group ids */
    int i;				/* loop index */
    int eid;				/* error id code */
    int rv;				/* result value */

    /* Open the groups subdatabase for write access */
    rv = nsadbOpenGroups(errp, authdb, ADBF_GWRITE);
    if (rv < 0) goto punt;

    /* Look up the group to be removed, and get a group object */
    rv = nsadbFindByName(errp, authdb, name, AIF_GROUP, (void **)&goptr);
    if (rv != AIF_GROUP) {
	if (rv < 0) goto punt;
	goto err_nogroup;
    }

    /* Mark the group for delete */
    goptr->go_flags |= GOF_DELPEND;

    /* Does the specified group belong to any groups? */
    cnt = UILCOUNT(&goptr->go_pgroups);
    if (cnt > 0) {

	/* Yes, for each parent group ... */
	for (i = 0; i < cnt; ++i) {

	    /* Note that nsadbRemGroupFromGroup() will shrink this list */
	    list = UILLIST(&goptr->go_pgroups);

	    /* Get group name associated with the group id */
	    rv = nsadbIdToName(errp, authdb, *list, AIF_GROUP, &ugname);
	    if (rv < 0) goto punt;

	    /* Look up the group by name and get a group object for it */
	    rv = nsadbFindByName(errp,
				 authdb, ugname, AIF_GROUP, (void **)&ogoptr);
	    if (rv < 0) goto punt;

	    /* Remove the specified group from the parent group */
	    rv = nsadbRemGroupFromGroup(errp, authdb, ogoptr, goptr);
	    if (rv < 0) goto punt;

	    /* Free the parent group object */
	    groupFree(ogoptr);
	    ogoptr = 0;
	}
    }

    /* Are there any group members of this group? */
    cnt = UILCOUNT(&goptr->go_groups);
    if (cnt > 0) {

	/* For each group member of the group ... */

	for (i = 0; i < cnt; ++i) {

	    /* Note that nsadbRemGroupFromGroup() will shrink this list */
	    list = UILLIST(&goptr->go_groups);

	    /* Get group name associated with the group id */
	    rv = nsadbIdToName(errp, authdb, *list, AIF_GROUP, &ugname);
	    if (rv < 0) goto punt;

	    /* Look up the group by name and get a group object for it */
	    rv = nsadbFindByName(errp,
				 authdb, ugname, AIF_GROUP, (void **)&ogoptr);
	    if (rv < 0) goto punt;

	    /* Remove member group from the specified group */
	    rv = nsadbRemGroupFromGroup(errp, authdb, goptr, ogoptr);
	    if (rv < 0) goto punt;

	    /* Free the member group object */
	    groupFree(ogoptr);
	    ogoptr = 0;
	}
    }

    /* Are there any direct user members of this group? */
    cnt = UILCOUNT(&goptr->go_users);
    if (cnt > 0) {

	/* Yes, open users subdatabase for write access */
	rv = nsadbOpenUsers(errp, authdb, ADBF_UWRITE);
	if (rv < 0) goto punt;

	/* For each user member of the group ... */
	for (i = 0; i < cnt; ++i) {

	    /* Note that nsadbRemUserFromGroup() will shrink this list */
	    list = UILLIST(&goptr->go_users);

	    /* Get user name associated with the user id */
	    rv = nsadbIdToName(errp, authdb, *list, AIF_USER, &ugname);
	    if (rv < 0) goto punt;

	    /* Look up the user by name and get a user object for it */
	    rv = nsadbFindByName(errp,
				 authdb, ugname, AIF_USER, (void **)&uoptr);
	    if (rv < 0) goto punt;

	    /* Remove user from the group */
	    rv = nsadbRemUserFromGroup(errp, authdb, goptr, uoptr);
	    if (rv < 0) goto punt;

	    /* Free the member user object */
	    userFree(uoptr);
	    uoptr = 0;
	}
    }

    /* Free the group object for the specified group */
    groupFree(goptr);
    goptr = 0;

    /* Now we can remove the group entry */
    rv = groupRemove(errp, adb->adb_groupdb, 0, (NTS_t)name);

    return rv;

  err_nogroup:
    eid = NSAUERR4100;
    rv = NSAERRNAME;
    nserrGenerate(errp, rv, eid, NSAuth_Program, 2, adb->adb_dbname, name);
    goto punt;

  punt:
    /* Free any user or group objects that we created */
    if (ogoptr != 0) {
	groupFree(ogoptr);
    }
    if (uoptr != 0) {
	userFree(uoptr);
    }
    if (goptr != 0) {
	groupFree(goptr);
    }
    return rv;
}

/*
 * Description (nsadbRemoveUser)
 *
 *	This function is called to remove a given user name from
 *	a specified authentication database.  This can cause updates
 *	to both the user and user subdatabases.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	name			- pointer to name of user to remove
 *
 * Returns:
 *
 *	The return value is zero if the user information is successfully
 *	removed.  An error is indicated by a negative return value, and
 *	an error frame is generated if an error frame list is provided.
 */

NSAPI_PUBLIC int nsadbRemoveUser(NSErr_t * errp, void * authdb, char * name)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    UserObj_t * uoptr = 0;		/* user object pointer */
    GroupObj_t * goptr = 0;		/* group object pointer */
    char * gname;			/* group name */
    USI_t * list;			/* pointer into group id list */
    int gcnt;				/* number of groups containing user */
    int i;				/* loop index */
    int eid;				/* error id code */
    int rv;				/* result value */

    /* Open the users subdatabase for write access */
    rv = nsadbOpenUsers(errp, authdb, ADBF_UWRITE);
    if (rv < 0) goto punt;

    /* Look up the user to be removed, and get a user object */
    rv = nsadbFindByName(errp, authdb, name, AIF_USER, (void **)&uoptr);
    if (rv != AIF_USER) {
	if (rv < 0) goto punt;
	goto err_nouser;
    }

    /* Mark the user for delete */
    uoptr->uo_flags |= UOF_DELPEND;

    /* Does this user belong to any groups? */
    gcnt = UILCOUNT(&uoptr->uo_groups);
    if (gcnt > 0) {

	/* Yes, get pointer to list of group ids */
	list = UILLIST(&uoptr->uo_groups);

	/* Open groups subdatabase for write access */
	rv = nsadbOpenGroups(errp, authdb, ADBF_GWRITE);
	if (rv < 0) goto punt;

	/* For each group that the user belongs to ... */
	for (i = 0; i < gcnt; ++i) {

	    /* Get group name associated with the group id */
	    rv = nsadbIdToName(errp, authdb, *list, AIF_GROUP, &gname);
	    if (rv < 0) goto punt;

	    /* Look up the group by name and get a group object for it */
	    rv = nsadbFindByName(errp,
				 authdb, gname, AIF_GROUP, (void **)&goptr);
	    if (rv < 0) goto punt;

	    /* Remove user from group if it's a direct member */
	    rv = nsadbRemUserFromGroup(errp, authdb, goptr, uoptr);
	    if (rv < 0) goto punt;

	    /* Free the group object */
	    groupFree(goptr);
	    goptr = 0;

	    ++list;
	}
    }

#ifdef CLIENT_AUTH
    /* Remove certificate mapping for user, if any */
    rv = nsadbRemoveUserCert(errp, authdb, name);
#endif

    /* Free the user object */
    userFree(uoptr);

    /* Now we can remove the user entry */
    rv = userRemove(errp, adb->adb_userdb, 0, (NTS_t)name);

    return rv;

  err_nouser:
    eid = NSAUERR4000;
    rv = NSAERRNAME;
    nserrGenerate(errp, rv, eid, NSAuth_Program, 2, adb->adb_dbname, name);
    goto punt;

  punt:
    if (goptr != 0) {
	groupFree(goptr);
    }
    if (uoptr != 0) {
	userFree(uoptr);
    }
    return rv;
}

/*
 * Description (nsadbRemGroupFromGroup)
 *
 *	This function removes a given group C from a parent group P.
 *	The group C must be a direct member of the group P.  However,
 *	group C may also be a member of one or more of P's ancestor or
 *	descendant groups, and this function deals with that.  The
 *	group entries for C and P are updated in the group database.
 *	But the real work is updating the groups lists of all of the
 *	users contained in C.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	pgoptr			- pointer to parent group object
 *	cgoptr			- pointer to child group object
 *
 * Returns:
 *
 *	The return value is zero if group C was a direct member of
 *	group P, and was removed successfully.  A return value of +1
 *	indicates that group C was not a direct member of the group P.
 *	A negative return value indicates an error.
 */

NSAPI_PUBLIC int nsadbRemGroupFromGroup(NSErr_t * errp, void * authdb,
			   GroupObj_t * pgoptr, GroupObj_t * cgoptr)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    USIList_t dglist;		/* list of descendant groups of C */
    GroupObj_t * dgoptr;	/* descendant group object pointer */
    UserObj_t * uoptr;		/* user object pointer */
    USI_t * gidlist;		/* pointer to group id array */
    USI_t * userlist;		/* pointer to array of descendant user ids */
    USI_t dgid;			/* descendant group id */
    int iusr;			/* index on descendant user list */
    int usercnt;		/* count of descendant users */
    int igrp;			/* index of group in user group id list */
    int rv;			/* result value */

    dgoptr = 0;
    uoptr = 0;

    /* Initialize a list of descendant groups of C */
    UILINIT(&dglist);

    /* Is group C a direct member of group P? */
    if (!usiPresent(&pgoptr->go_groups, cgoptr->go_gid)) {

	/* No, nothing to do */
	return 1;
    }

    /* Remove group C from group P's group member list */
    rv = usiRemove(&pgoptr->go_groups, cgoptr->go_gid);
    if (rv < 0) goto punt;

    /* Remove group P from group C's parent group list */
    rv = usiRemove(&cgoptr->go_pgroups, pgoptr->go_gid);
    if (rv < 0) goto punt;

    /* Open user database since the group lists of users may be modified */
    rv = nsadbOpenUsers(errp, authdb, ADBF_UWRITE);
    if (rv < 0) goto punt;

    /* Open group database since group entries will be modified */
    rv = nsadbOpenGroups(errp, authdb, ADBF_GWRITE);
    if (rv < 0) goto punt;

    /* Write out the updated group C object */
    cgoptr->go_flags |= GOF_MODIFIED;
    rv = groupStore(errp, adb->adb_groupdb, 0, cgoptr);
    if (rv) goto punt;

    /* Write out the updated group P object */
    pgoptr->go_flags |= GOF_MODIFIED;
    rv = groupStore(errp, adb->adb_groupdb, 0, pgoptr);
    if (rv) goto punt;

    /* Now check the group lists of all users contained in group C */
    dgoptr = cgoptr;
    dgid = cgoptr->go_gid;

    for (;;) {

	/* Scan the direct user members of this descendant group */
	usercnt = UILCOUNT(&dgoptr->go_users);
	userlist = UILLIST(&dgoptr->go_users);

	for (iusr = 0; iusr < usercnt; ++iusr) {

	    /* Get a user object for this user member */
	    uoptr = userFindByUid(errp, adb->adb_userdb, userlist[iusr]);
	    if (uoptr == 0) {
		/* Error - user id not found, databases are inconsistent */
		rv = -1;
		goto punt;
	    }

	    /* Scan the group list for this user */
	    for (igrp = 0; igrp < UILCOUNT(&uoptr->uo_groups); ) {

		gidlist = UILLIST(&uoptr->uo_groups);

		/* Is the user a member of this group? */
		if (nsadbIsUserInGroup(errp, authdb,
				       uoptr->uo_uid, gidlist[igrp],
				       igrp, gidlist)) {

		    /* Yes, step to next group id */
		    ++igrp;
		}
		else {
		    /*
		     * No, remove it from the user's list of groups.  The
		     * next group id to consider will be shifted into the
		     * igrp position when the current id is removed.
		     */
		    rv = usiRemove(&uoptr->uo_groups, gidlist[igrp]);
		    if (rv < 0) goto punt;
		    uoptr->uo_flags |= UOF_MODIFIED;
		}
	    }

	    /* Write out the user object if it was changed */
	    if (uoptr->uo_flags & UOF_MODIFIED) {
		rv = userStore(errp, adb->adb_userdb, 0, uoptr);
		if (rv < 0) goto punt;
	    }

	    /* Free the user object */
	    userFree(uoptr);
	    uoptr = 0;
	}

	/*
	 * Merge the direct member groups of this group into the
	 * descendants list.
	 */
	rv = uilMerge(&dglist, &dgoptr->go_groups);
	if (rv < 0) goto punt;

	/* Free this descendant group object */
	if (dgoptr != cgoptr) {
	    groupFree(dgoptr);
	    dgoptr = 0;
	}

	/* If the descendants list is empty, we're done */
	if (UILCOUNT(&dglist) <= 0) break;

	/* Remove the next group id from the descendants list */
	gidlist = UILLIST(&dglist);
	dgid = gidlist[0];
	rv = usiRemove(&dglist, dgid);
	if (rv < 0) goto punt;

	/* Get a group object for this descendant group */
	dgoptr = groupFindByGid(errp, adb->adb_groupdb, dgid);
	if (dgoptr == 0) {
	    /* Error - group id not found, databases are inconsistent */
	    rv = -1;
	    goto punt;
	}
    }

    UILFREE(&dglist);
    return 0;

  punt:
    if (dgoptr) {
	groupFree(dgoptr);
    }
    if (uoptr) {
	userFree(uoptr);
    }
    UILFREE(&dglist);
    return rv;
}

/*
 * Description (nsadbRemUserFromGroup)
 *
 *	This function removes a given user from a specified group G.
 *	The user must be a direct member of the group.  However, the
 *	user may also be a member of one or more of G's descendant
 *	groups, and this function deals with that.  The group entry
 *	for G is updated in the group database, with the user removed
 *	from its user member list.  The user entry is updated in the
 *	user database, with an updated list of all groups which now
 *	contain the user.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	goptr			- pointer to group object
 *	uoptr			- pointer to user object
 *
 * Returns:
 *
 *	The return value is zero if the user was a direct member of the
 *	group, and was removed successfully.  A return value of +1
 *	indicates that the user was not a direct member of the
 *	group.  A negative return value indicates an error.
 */

NSAPI_PUBLIC int nsadbRemUserFromGroup(NSErr_t * errp, void * authdb,
			  GroupObj_t * goptr, UserObj_t * uoptr)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    USI_t * idlist;		/* pointer to user group id array */
    USI_t tgid;			/* test group id */
    int igrp;			/* position in user group list */
    int rv;			/* result value */

    /* Is the user a direct member of the group? */
    if (!usiPresent(&goptr->go_users, uoptr->uo_uid)) {

	/* No, nothing to do */
	return 1;
    }

    /* Remove the user from the group's user member list */
    rv = usiRemove(&goptr->go_users, uoptr->uo_uid);
    if (rv < 0) goto punt;

    /* If the user object is pending deletion, no need to open databases */
    if (!(uoptr->uo_flags & UOF_DELPEND)) {

	/*
	 * Open user database since the group list of the user
	 * will be modified.
	 */
	rv = nsadbOpenUsers(errp, authdb, ADBF_UWRITE);
	if (rv < 0) goto punt;

	/* Open group database since group entries will be modified */
	rv = nsadbOpenGroups(errp, authdb, ADBF_GWRITE);
	if (rv < 0) goto punt;
    }

    /*
     * Write out the updated group object.  This must be done here
     * because nsadbIsUserInGroup() in the loop below will read the
     * entry for this group, and it needs to reflect the user's
     * removal from being a direct member of the group.  This does
     * not preclude the possibility that the user will still be an
     * indirect member of this group.
     */
    goptr->go_flags |= GOF_MODIFIED;
    rv = groupStore(errp, adb->adb_groupdb, 0, goptr);
    if (rv) goto punt;

    /* If a delete is pending on the user, we're done */
    if (uoptr->uo_flags & UOF_DELPEND) goto punt;

    /*
     * Begin loop to check whether user is still a member of each
     * of the groups in its group list.  Note that the group list
     * may shrink during an iteration of the loop.
     */

    for (igrp = 0; igrp < UILCOUNT(&uoptr->uo_groups); ) {

	/* Get pointer to the user's array of group ids */
	idlist = UILLIST(&uoptr->uo_groups);

	/* Get the group id of the next group to consider */
	tgid = idlist[igrp];

	/* Is the user a member of this group? */
	if (nsadbIsUserInGroup(errp, authdb,
			       uoptr->uo_uid, tgid, igrp, idlist)) {

	    /* Yes, step to next group id */
	    ++igrp;
	}
	else {

	    /*
	     * No, remove it from the user's list of groups.  The
	     * next group id to consider will be shifted into the
	     * igrp position when the current id is removed.
	     */
	    rv = usiRemove(&uoptr->uo_groups, tgid);
	    if (rv < 0) goto punt;
	}
    }

    /* Write out the updated user object */
    uoptr->uo_flags |= UOF_MODIFIED;
    rv = userStore(errp, adb->adb_userdb, 0, uoptr);

  punt:
    return rv;
}

/*
 * Description (nsadbSuperGroups)
 *
 *	This function builds a list of the group ids for all groups
 *	which contain, directly or indirectly, a specified group as
 *	a subgroup.  We call these the supergroups of the specified
 *	group.
 *
 * Arguments:
 *
 *	errp			- error frame list pointer (may be null)
 *	authdb			- handle for authentication databases
 *	goptr			- pointer to group object
 *	gsuper			- pointer to list to contain supergroups
 *				  (caller must initialize)
 *
 * Returns:
 *
 *	Returns the number of elements in gsuper if successful.  An
 *	error is indicated by a negative return value.
 */

NSAPI_PUBLIC int nsadbSuperGroups(NSErr_t * errp, void * authdb,
		     GroupObj_t * goptr, USIList_t * gsuper)
{
    AuthDB_t * adb = (AuthDB_t *)authdb;
    USIList_t aglist;			/* ancestor group id list */
    GroupObj_t * aoptr;			/* ancestor group object pointer */
    USI_t * idlist;			/* pointer to array of group ids */
    USI_t id;				/* current group id */
    int rv;				/* result value */

    /* Initialize an empty ancestor group list */
    UILINIT(&aglist);

    /* Enter loop with specified group as first ancestor */
    id = goptr->go_gid;
    aoptr = goptr;

    /* Open group database for read access */
    rv = nsadbOpenGroups(errp, authdb, ADBF_GREAD);
    if (rv < 0) goto punt;

    /* Loop until the ancestor list is empty */
    for (;;) {

	/* Merge parent groups of current ancestor into ancestor list */
	rv = uilMerge(&aglist, &aoptr->go_pgroups);
	if (rv < 0) goto punt;

	/* Also merge parent groups into the result list */
	rv = uilMerge(gsuper, &aoptr->go_pgroups);
	if (rv < 0) goto punt;

	/* Free the ancestor group object (but not the original) */
	if (aoptr != goptr) {
	    groupFree(aoptr);
	    aoptr = 0;
	}

	/* Exit the loop if the ancestor list is empty */
	if (UILCOUNT(&aglist) <= 0) break;

	/* Get pointer to array of ancestor group ids */
	idlist = UILLIST(&aglist);

	/* Remove the first ancestor */
	id = idlist[0];
	rv = usiRemove(&aglist, id);

	/* Get a group object for the ancestor */
	aoptr = groupFindByGid(errp, adb->adb_groupdb, id);
	if (aoptr == 0) {
	    /* Error - group not found, database inconsistent */
	    rv = -1;
	    goto punt;
	}
    }

    return UILCOUNT(gsuper);

  punt:
    /* Handle error */

    /* Free ancestor list */
    UILFREE(&aglist);

    return rv;
}

NSPR_END_EXTERN_C