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