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