Fixed bug in password and group file reading: strtok doesn't handle
[become] / src / userdb.c
1 /* -*-c-*-
2 *
3 * $Id: userdb.c,v 1.8 1998/06/08 11:21:22 mdw Exp $
4 *
5 * User database management
6 *
7 * (c) 1998 EBI
8 */
9
10 /*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of `become'
13 *
14 * `Become' is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * `Become' is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with `become'; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29 /*----- Revision history --------------------------------------------------*
30 *
31 * $Log: userdb.c,v $
32 * Revision 1.8 1998/06/08 11:21:22 mdw
33 * Fixed bug in password and group file reading: strtok doesn't handle
34 * double colons nicely.
35 *
36 * Revision 1.7 1998/04/23 13:27:46 mdw
37 * Switch to using the ypstuff interface to YP server.
38 *
39 * Revision 1.6 1998/01/12 16:46:33 mdw
40 * Fix copyright date.
41 *
42 * Revision 1.5 1997/09/17 10:24:08 mdw
43 * Use `uid_t' instead of `int' for uids and gids. Not quite sure why I
44 * didn't do this before.
45 *
46 * Revision 1.4 1997/08/20 16:24:58 mdw
47 * Patch memory leak. Rename `userdb_reinit' to `userdb_end' for more
48 * sensible restart.
49 *
50 * Revision 1.3 1997/08/07 09:44:29 mdw
51 * Read NIS-based passwords from the YP server directly, rather than using
52 * `popen(ypcat)', which is probably both slower and less secure.
53 *
54 * Revision 1.2 1997/08/04 10:24:26 mdw
55 * Sources placed under CVS control.
56 *
57 * Revision 1.1 1997/07/21 13:47:43 mdw
58 * Initial revision
59 *
60 */
61
62 /*----- Header files ------------------------------------------------------*/
63
64 /* --- ANSI headers --- */
65
66 #include <ctype.h>
67 #include <errno.h>
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
71
72 /* --- Unix headers --- */
73
74 #include "config.h"
75
76 #include <sys/types.h>
77
78 #include <grp.h>
79 #include <pwd.h>
80 #include <unistd.h>
81
82 /* --- Local headers --- */
83
84 #include "become.h"
85 #include "sym.h"
86 #include "userdb.h"
87 #include "utils.h"
88 #include "ypstuff.h"
89
90 /*----- Type definitions --------------------------------------------------*/
91
92 /* --- A map link --- */
93
94 typedef struct userdb__node {
95 struct userdb__node *next;
96 void *rec;
97 } userdb__node;
98
99 /* --- A reference to a real record --- */
100
101 typedef struct userdb__sym {
102 sym_base _base;
103 void *rec;
104 } userdb__sym;
105
106 /* --- A name- and number-mapping --- */
107
108 typedef struct userdb__map {
109 sym_table nmap;
110 sym_table idmap;
111 userdb__node *list;
112 } userdb__map;
113
114 /*----- Static variables --------------------------------------------------*/
115
116 static userdb__map userdb__users; /* Map of user info blocks */
117 static sym_iter userdb__useri; /* Iterator for users */
118 static userdb__map userdb__groups; /* Map of group info blocks */
119 static sym_iter userdb__groupi; /* Iterator for groups */
120
121 /*----- Map management functions ------------------------------------------*/
122
123 /* --- @userdb__createMap@ --- *
124 *
125 * Arguments: @userdb__map *m@ = pointer to a map block
126 *
127 * Returns: ---
128 *
129 * Use: Initialises a map table.
130 */
131
132 static void userdb__createMap(userdb__map *m)
133 {
134 sym_createTable(&m->nmap);
135 sym_createTable(&m->idmap);
136 m->list = 0;
137 }
138
139 /* --- @userdb__addToMap@ --- *
140 *
141 * Arguments: @userdb__map *m@ = pointer to the map block
142 * @const char *name@ = pointer to the item's name
143 * @uid_t id@ = the item's id number
144 * @void *rec@ = pointer to the actual record
145 *
146 * Returns: ---
147 *
148 * Use: Adds an item to the given map.
149 */
150
151 static void userdb__addToMap(userdb__map *m,
152 const char *name,
153 uid_t id, void *rec)
154 {
155 unsigned f;
156 userdb__sym *s;
157 userdb__node *n;
158
159 s = sym_find(&m->nmap, name, -1, sizeof(*s), &f);
160 if (!f)
161 s->rec = rec;
162
163 s = sym_find(&m->idmap, (char *)&id, sizeof(id), sizeof(*s), &f);
164 if (!f)
165 s->rec = rec;
166
167 n = xmalloc(sizeof(*n));
168 n->rec = rec;
169 n->next = m->list;
170 m->list = n;
171 }
172
173 /* --- @userdb__byName@ --- *
174 *
175 * Arguments: @userdb__map *m@ = pointer to a map block
176 * @const char *name@ = name to look up
177 *
178 * Returns: A pointer to the appropriate block, or zero if not found.
179 *
180 * Use: Looks up a name in a mapping and returns the result.
181 */
182
183 static void *userdb__byName(userdb__map *m, const char *name)
184 {
185 userdb__sym *s = sym_find(&m->nmap, name, -1, 0, 0);
186 return (s ? s->rec : 0);
187 }
188
189 /* --- @userdb__byId@ --- *
190 *
191 * Arguments: @userdb__map *m@ = pointer to a map block
192 * @uid_t id@ = id number to find
193 *
194 * Returns: A pointer to the appropriate block, or zero if not found.
195 *
196 * Use: Looks up an ID in a mapping, and returns the result.
197 */
198
199 static void *userdb__byId(userdb__map *m, uid_t id)
200 {
201 userdb__sym *s = sym_find(&m->idmap, (char *)&id, sizeof(id), 0, 0);
202 return (s ? s->rec : 0);
203 }
204
205 /* --- @userdb__clearMap@ --- *
206 *
207 * Arguments: @userdb__map *m@ = pointer to a map block
208 * @void (*freerec)(void *rec)@ = pointer to a free-record proc
209 *
210 * Returns: ---
211 *
212 * Use: Clears a map, emptying it and releasing the memory it
213 * occupied.
214 */
215
216 static void userdb__clearMap(userdb__map *m, void (*freerec)(void *rec))
217 {
218 userdb__node *n, *t;
219
220 sym_destroyTable(&m->nmap);
221 sym_destroyTable(&m->idmap);
222
223 for (n = m->list; n; n = t) {
224 t = n->next;
225 freerec(n->rec);
226 free(n);
227 }
228 }
229
230 /*----- User and group block management -----------------------------------*/
231
232 /* --- @userdb__dumpUser@ --- *
233 *
234 * Arguments: @const struct passwd *pw@ = pointer to a user block
235 *
236 * Returns: ---
237 *
238 * Use: Writes a user's informationt to a stream.
239 */
240
241 #ifdef TRACING
242
243 static void userdb__dumpUser(const struct passwd *pw)
244 {
245 trace(TRACE_DEBUG,
246 "debug: name `%s' passwd `%s' uid %i gid %i",
247 pw->pw_name, pw->pw_passwd, (int)pw->pw_uid, (int)pw->pw_gid);
248 trace(TRACE_DEBUG,
249 "debug: ... gecos `%s' home `%s' shell `%s'",
250 pw->pw_gecos, pw->pw_dir, pw->pw_shell);
251 }
252
253 #endif
254
255 /* --- @userdb__split@ --- *
256 *
257 * Arguments: @char *p@ = pointer to string
258 * @char **v@ = pointer to vector to fill in
259 * @int sz@ = maximum number of fields to split
260 *
261 * Returns: Number of fields extracted.
262 *
263 * Use: Splits a string into fields at colon characters.
264 */
265
266 static int userdb__split(char *p, char **v, int sz)
267 {
268 int count = 0;
269
270 *v++ = p; sz--; count++;
271 if (!sz)
272 goto done;
273 while (*p) {
274 if (*p++ == ':') {
275 p[-1] = 0;
276 *v++ = p; sz--; count++;
277 if (!sz)
278 goto done;
279 }
280 }
281 while (sz--)
282 *v++ = 0;
283
284 done:
285 return (count);
286 }
287
288 /* --- @userdb_copyUser@ --- *
289 *
290 * Arguments: @struct passwd *pw@ = pointer to block to copy
291 *
292 * Returns: Pointer to the copy.
293 *
294 * Use: Copies a user block. The copy is `deep' so all the strings
295 * are copied too. Free the copy with @userdb_freeUser@ when
296 * you don't want it any more.
297 */
298
299 struct passwd *userdb_copyUser(struct passwd *pw)
300 {
301 struct passwd *npw;
302
303 if (!pw)
304 return (0);
305
306 npw = xmalloc(sizeof(*npw));
307
308 npw->pw_name = xstrdup(pw->pw_name);
309 npw->pw_passwd = xstrdup(pw->pw_passwd);
310 npw->pw_uid = pw->pw_uid;
311 npw->pw_gid = pw->pw_gid;
312 npw->pw_gecos = xstrdup(pw->pw_gecos);
313 npw->pw_dir = xstrdup(pw->pw_dir);
314 npw->pw_shell = xstrdup(pw->pw_shell);
315
316 return (npw);
317 }
318
319 /* --- @userdb__buildUser@ --- *
320 *
321 * Arguments: @char *s@ = pointer to user string
322 *
323 * Returns: Pointer to a user block.
324 *
325 * Use: Converts a line from a user file into a password entry.
326 * Note that the string is corrupted by @strtok@ while it gets
327 * parsed.
328 */
329
330 static struct passwd *userdb__buildUser(char *s)
331 {
332 struct passwd *pw = xmalloc(sizeof(*pw));
333 char *v[7];
334
335 if (userdb__split(s, v, 7) < 7) {
336 free(pw);
337 return (0);
338 }
339
340 pw->pw_name = xstrdup(v[0]);
341 pw->pw_passwd = xstrdup(v[1]);
342 pw->pw_uid = (uid_t)atol(v[2]);
343 pw->pw_gid = (gid_t)atol(v[3]);
344 pw->pw_gecos = xstrdup(v[4]);
345 pw->pw_dir = xstrdup(v[5]);
346 pw->pw_shell = xstrdup(v[6]);
347 return (pw);
348 }
349
350 /* --- @userdb_freeUser@ --- *
351 *
352 * Arguments: @void *rec@ = pointer to a user record
353 *
354 * Returns: ---
355 *
356 * Use: Frees a user record.
357 */
358
359 void userdb_freeUser(void *rec)
360 {
361 struct passwd *pw;
362
363 if (!rec)
364 return;
365
366 pw = rec;
367 free(pw->pw_name);
368 free(pw->pw_passwd);
369 free(pw->pw_gecos);
370 free(pw->pw_dir);
371 free(pw->pw_shell);
372 free(pw);
373 }
374
375 /* --- @userdb__dumpGroup@ --- *
376 *
377 * Arguments: @const struct group *gr@ = pointer to a group block
378 * @FILE *fp@ = pointer to stream to write on
379 *
380 * Returns: ---
381 *
382 * Use: Writes a group's information to a stream.
383 */
384
385 #ifdef TRACING
386
387 static void userdb__dumpGroup(const struct group *gr)
388 {
389 char *const *p;
390
391 trace(TRACE_DEBUG,
392 "debug: name `%s' passwd `%s' gid %i",
393 gr->gr_name, gr->gr_passwd, (int)gr->gr_gid);
394 for (p = gr->gr_mem; *p; p++)
395 trace(TRACE_DEBUG,"debug: ... `%s'", *p);
396 }
397
398 #endif
399
400 /* --- @userdb_copyGroup@ --- *
401 *
402 * Arguments: @struct group *gr@ = pointer to group block
403 *
404 * Returns: Pointer to copied block
405 *
406 * Use: Copies a group block. The copy is `deep' so all the strings
407 * are copied too. Free the copy with @userdb_freeGroup@ when
408 * you don't want it any more.
409 */
410
411 struct group *userdb_copyGroup(struct group *gr)
412 {
413 struct group *ngr;
414 int i, max;
415
416 if (!gr)
417 return (0);
418
419 ngr = xmalloc(sizeof(*ngr));
420
421 ngr->gr_name = xstrdup(gr->gr_name);
422 ngr->gr_passwd = xstrdup(gr->gr_passwd);
423 ngr->gr_gid = gr->gr_gid;
424
425 for (max = 0; gr->gr_mem[max]; max++)
426 ;
427 ngr->gr_mem = xmalloc((max + 1) * sizeof(char *));
428 for (i = 0; i < max; i++)
429 ngr->gr_mem[i] = xstrdup(gr->gr_mem[i]);
430 ngr->gr_mem[max] = 0;
431
432 return (ngr);
433 }
434
435 /* --- @userdb__buildGroup@ --- *
436 *
437 * Arguments: @char *s@ = pointer to group line string
438 *
439 * Returns: Pointer to a group block
440 *
441 * Use: Parses an entry in the groups file. The string is garbled
442 * by @strtok@ as we go.
443 */
444
445 static struct group *userdb__buildGroup(char *s)
446 {
447 struct group *gr = xmalloc(sizeof(*gr));
448 char *v[4];
449 int i;
450
451 /* --- Do the easy bits --- */
452
453 if (userdb__split(s, v, 4) < 3) {
454 free(gr);
455 return (0);
456 }
457 gr->gr_name = xstrdup(v[0]);
458 gr->gr_passwd = xstrdup(v[1]);
459 gr->gr_gid = (gid_t)atol(v[2]);
460
461 /* --- Count the number of members --- */
462
463 s = v[3];
464 i = 0;
465 if (s && s[0]) {
466 for (;;) {
467 i++;
468 if ((s = strpbrk(s, ",")) == 0)
469 break;
470 s++;
471 }
472 }
473
474 /* --- Allocate the block and fill it --- */
475
476 gr->gr_mem = xmalloc((i + 1) * sizeof(char *));
477 i = 0;
478 if (v[3]) {
479 s = strtok(v[3], ",");
480 while (s) {
481 gr->gr_mem[i++] = xstrdup(s);
482 s = strtok(0, ",");
483 }
484 }
485 gr->gr_mem[i] = 0;
486
487 return (gr);
488 }
489
490 /* --- @userdb_freeGroup@ --- *
491 *
492 * Arguments: @void *rec@ = pointer to a group record
493 *
494 * Returns: ---
495 *
496 * Use: Frees a group record.
497 */
498
499 void userdb_freeGroup(void *rec)
500 {
501 struct group *gr;
502 char **p;
503
504 if (!rec)
505 return;
506
507 gr = rec;
508 free(gr->gr_name);
509 free(gr->gr_passwd);
510 for (p = gr->gr_mem; *p; p++)
511 free(*p);
512 free(gr->gr_mem);
513 free(gr);
514 }
515
516 /*----- Answering queries -------------------------------------------------*/
517
518 /* --- @userdb_userByName@, @userdb_userById@ --- *
519 *
520 * Arguments: @const char *name@ = pointer to user's name
521 * @uid_t id@ = user id to find
522 *
523 * Returns: Pointer to user block, or zero if not found.
524 *
525 * Use: Looks up a user by name or id.
526 */
527
528 struct passwd *userdb_userByName(const char *name)
529 { return (userdb__byName(&userdb__users, name)); }
530
531 struct passwd *userdb_userById(uid_t id)
532 { return (userdb__byId(&userdb__users, id)); }
533
534 /* --- @userdb_iterateUsers@, @userdb_iterateUsers_r@ --- *
535 *
536 * Arguments: @userdb_iter *i@ = pointer to a symbol table iterator object
537 *
538 * Returns: ---
539 *
540 * Use: Initialises an iteration for the user database.
541 */
542
543 void userdb_iterateUsers(void)
544 { userdb_iterateUsers_r(&userdb__useri); }
545
546 void userdb_iterateUsers_r(userdb_iter *i)
547 { sym_createIter(i, &userdb__users.nmap); }
548
549 /* --- @userdb_nextUser@, @userdb_nextUser_r@ --- *
550 *
551 * Arguments: @userdb_iter *i@ = pointer to a symbol table iterator oject
552 *
553 * Returns: Pointer to the next user block, or null.
554 *
555 * Use: Returns another user block.
556 */
557
558 struct passwd *userdb_nextUser(void)
559 { return (userdb_nextUser_r(&userdb__useri)); }
560
561 struct passwd *userdb_nextUser_r(userdb_iter *i)
562 {
563 userdb__sym *s = sym_next(i);
564 return (s ? s->rec : 0);
565 }
566
567 /* --- @userdb_groupByName@, @userdb_groupById@ --- *
568 *
569 * Arguments: @const char *name@ = pointer to group's name
570 * @gid_t id@ = group id to find
571 *
572 * Returns: Pointer to group block, or zero if not found.
573 *
574 * Use: Looks up a group by name or id.
575 */
576
577 struct group *userdb_groupByName(const char *name)
578 { return (userdb__byName(&userdb__groups, name)); }
579
580 struct group *userdb_groupById(gid_t id)
581 { return (userdb__byId(&userdb__groups, id)); }
582
583 /* --- @userdb_iterateGroups@, @userdb_iterateGroups_r@ --- *
584 *
585 * Arguments: @userdb_iter *i@ = pointer to a symbol table iterator object
586 *
587 * Returns: ---
588 *
589 * Use: Initialises an iteration for the group database.
590 */
591
592 void userdb_iterateGroups(void)
593 { userdb_iterateGroups_r(&userdb__groupi); }
594
595 void userdb_iterateGroups_r(userdb_iter *i)
596 { sym_createIter(i, &userdb__groups.nmap); }
597
598 /* --- @userdb_nextGroup@, @userdb_nextGroup_r@ --- *
599 *
600 * Arguments: @userdb_iter *i@ = pointer to a symbol table iterator oject
601 *
602 * Returns: Pointer to the next group block, or null.
603 *
604 * Use: Returns another group block.
605 */
606
607 struct group *userdb_nextGroup(void)
608 { return (userdb_nextGroup_r(&userdb__groupi)); }
609
610 struct group *userdb_nextGroup_r(userdb_iter *i)
611 {
612 userdb__sym *s = sym_next(i);
613 return (s ? s->rec : 0);
614 }
615
616 /*----- Yellow pages support ----------------------------------------------*/
617
618 #ifdef HAVE_YP
619
620 /* --- @userdb__foreachUser@ --- *
621 *
622 * Arguments: @int st@ = YP protocol-level status code
623 * @char *k@ = address of the key for this record
624 * @int ksz@ = size of the key
625 * @char *v@ = address of the value for this record
626 * @int vsz@ = size of the value
627 * @char *data@ = pointer to some data passed to me
628 *
629 * Returns: Zero to be called again, nonzero to end the enumeration.
630 *
631 * Use: Handles an incoming user record.
632 */
633
634 static int userdb__foreachUser(int st, char *k, int ksz,
635 char *v, int vsz, char *data)
636 {
637 char *cv;
638 struct passwd *pw;
639
640 if (st != YP_TRUE)
641 return (-1);
642 cv = xmalloc(vsz + 1);
643 memcpy(cv, v, vsz);
644 cv[vsz] = 0;
645 T( trace(TRACE_DEBUG, "debug: nis string: `%s'", cv); )
646 pw = userdb__buildUser(cv);
647 if (pw && !userdb__byName(&userdb__users, pw->pw_name)) {
648 IF_TRACING(TRACE_DEBUG, userdb__dumpUser(pw); )
649 userdb__addToMap(&userdb__users, pw->pw_name, pw->pw_uid, pw);
650 } else
651 userdb_freeUser(pw);
652 free(cv);
653 return (0);
654 }
655
656 /* --- @userdb__foreachGroup@ --- *
657 *
658 * Arguments: @int st@ = YP protocol-level status code
659 * @char *k@ = address of the key for this record
660 * @int ksz@ = size of the key
661 * @char *v@ = address of the value for this record
662 * @int vsz@ = size of the value
663 * @char *data@ = pointer to some data passed to me
664 *
665 * Returns: Zero to be called again, nonzero to end the enumeration.
666 *
667 * Use: Handles an incoming user record.
668 */
669
670 static int userdb__foreachGroup(int st, char *k, int ksz,
671 char *v, int vsz, char *data)
672 {
673 char *cv;
674 struct group *gr;
675
676 if (st != YP_TRUE)
677 return (-1);
678 cv = xmalloc(vsz + 1);
679 memcpy(cv, v, vsz);
680 cv[vsz] = 0;
681 T( trace(TRACE_DEBUG, "debug: nis string: `%s'", cv); )
682 gr = userdb__buildGroup(cv);
683 if (gr && !userdb__byName(&userdb__groups, gr->gr_name)) {
684 IF_TRACING(TRACE_DEBUG, userdb__dumpGroup(gr); )
685 userdb__addToMap(&userdb__groups, gr->gr_name, gr->gr_gid, gr);
686 } else
687 userdb_freeGroup(gr);
688 free(cv);
689 return (0);
690 }
691
692 /* --- @userdb_yp@ --- *
693 *
694 * Arguments: ---
695 *
696 * Returns: ---
697 *
698 * Use: Fetches the YP database of users.
699 */
700
701 void userdb_yp(void)
702 {
703 /* --- Bind to a server --- */
704
705 ypstuff_bind();
706 if (!yp_domain)
707 return;
708
709 T( trace(TRACE_DEBUG, "debug: adding NIS users"); )
710
711 /* --- Fetch the users map --- */
712
713 {
714 static struct ypall_callback ucb = { userdb__foreachUser, 0 };
715 yp_all(yp_domain, "passwd.byuid", &ucb);
716 }
717
718 /* --- Fetch the groups map --- */
719
720 {
721 static struct ypall_callback gcb = { userdb__foreachGroup, 0 };
722 yp_all(yp_domain, "group.bygid", &gcb);
723 }
724 }
725
726 #else
727
728 void userdb_yp(void) { ; }
729
730 #endif
731
732 /*----- Building the databases --------------------------------------------*/
733
734 /* --- @userdb_local@ --- *
735 *
736 * Arguments: ---
737 *
738 * Returns: ---
739 *
740 * Use: Reads the local list of users into the maps.
741 */
742
743 void userdb_local(void)
744 {
745 T( trace(TRACE_DEBUG, "debug: adding local users"); )
746
747 /* --- Fetch users first --- */
748
749 {
750 struct passwd *pw;
751
752 setpwent();
753 while ((pw = getpwent()) != 0) {
754 IF_TRACING(TRACE_DEBUG, userdb__dumpUser(pw); )
755 if (!userdb__byName(&userdb__users, pw->pw_name))
756 userdb__addToMap(&userdb__users, pw->pw_name, pw->pw_uid,
757 userdb_copyUser(pw));
758 }
759 endpwent();
760 }
761
762 /* --- Then fetch groups --- */
763
764 {
765 struct group *gr;
766
767 setgrent();
768 while ((gr = getgrent()) != 0) {
769 IF_TRACING(TRACE_DEBUG, userdb__dumpGroup(gr); )
770 if (!userdb__byName(&userdb__groups, gr->gr_name))
771 userdb__addToMap(&userdb__groups, gr->gr_name, gr->gr_gid,
772 userdb_copyGroup(gr));
773 }
774 endgrent();
775 }
776 }
777
778 /* --- @userdb_init@ --- *
779 *
780 * Arguments: ---
781 *
782 * Returns: ---
783 *
784 * Use: Initialises the user database.
785 */
786
787 void userdb_init(void)
788 {
789 userdb__createMap(&userdb__users);
790 userdb__createMap(&userdb__groups);
791 }
792
793 /* --- @userdb_end@ --- *
794 *
795 * Arguments: ---
796 *
797 * Returns: ---
798 *
799 * Use: Closes down the user database.
800 */
801
802 void userdb_end(void)
803 {
804 userdb__clearMap(&userdb__users, userdb_freeUser);
805 userdb__clearMap(&userdb__groups, userdb_freeGroup);
806 }
807
808 /*----- Test rig ----------------------------------------------------------*/
809
810 #ifdef TEST_RIG
811
812 void dumpit(const char *msg)
813 {
814 trace(TRACE_DEBUG, "debug: %s", msg);
815
816 {
817 struct passwd *pw;
818 for (userdb_iterateUsers(); (pw = userdb_nextUser()) != 0; )
819 userdb__dumpUser(pw);
820 }
821
822 {
823 struct group *gr;
824 for (userdb_iterateGroups(); (gr = userdb_nextGroup()) != 0; )
825 userdb__dumpGroup(gr);
826 }
827 }
828
829 int main(void)
830 {
831 ego("userdb-test");
832 traceon(stdout, TRACE_ALL);
833 userdb_init();
834 userdb_local();
835 userdb_yp();
836 dumpit("spong");
837 /* printf("loaded (%lu)\n", track_memused()); */
838 getchar();
839 for (;;) {
840 userdb_end();
841 /* printf("cleared (%lu)\n", track_memused()); */
842 /* track_memlist(); */
843 userdb_init();
844 userdb_local();
845 userdb_yp();
846 /* printf("reloaded (%lu)\n", track_memused()); */
847 getchar();
848 }
849 return (0);
850 }
851
852 #endif
853
854 /*----- That's all, folks -------------------------------------------------*/