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