chiark / gitweb /
manual/Makefile.am: Install the HTML version of the manual correctly.
[become] / src / netg.c
1 /* -*-c-*-
2  *
3  * $Id: netg.c,v 1.7 2004/04/08 01:36:20 mdw Exp $
4  *
5  * A local database of netgroups
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 /*----- Header files ------------------------------------------------------*/
30
31 /* --- ANSI headers --- */
32
33 #include <ctype.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 /* --- Unix headers --- */
39
40 #include "config.h"
41
42 #include <sys/types.h>
43
44 #include <netinet/in.h>
45
46 #include <arpa/inet.h>
47
48 #include <pwd.h>
49 #include <netdb.h>
50 #include <unistd.h>
51
52 /* --- mLib headers --- */
53
54 #include <mLib/alloc.h>
55 #include <mLib/report.h>
56 #include <mLib/sym.h>
57 #include <mLib/trace.h>
58
59 /* --- Local headers --- */
60
61 #include "become.h"
62 #include "config.h"
63 #include "netg.h"
64 #include "userdb.h"
65 #include "ypstuff.h"
66
67 /*----- Type definitions --------------------------------------------------*/
68
69 /* --- Quick discussion --- *
70  *
71  * I've just noticed: netgroups are horrible.  They form a directed graph
72  * which is really horrible; I'll have to try and turn it into something
73  * more sensible (which will essentially involve cutting cycles).
74  *
75  * The structure looks a little bit like a good ol' List (see Knuth 1 or
76  * any decent Lisp manual), but with more information in the cons cells.
77  */
78
79 /* --- @netg__cons@ --- */
80
81 typedef struct netg__cons {
82   unsigned f;
83   union {
84     struct netg__cons *cons;
85     struct netg__atom *atom;
86   } car;
87   struct netg__cons *cdr;
88 } netg__cons;
89
90 enum {
91   f_cons = 1,                           /* The @car@ is a cons cell */
92   f_visit = 2,                          /* Currently threaded on this cell */
93   f_uncycled = 4                        /* Cycles removed from here on in */
94 };
95
96 /* --- @netg__atom@ --- */
97
98 typedef struct netg__atom {
99   char *n;                              /* Unresolved netgroup reference */
100   char *h;                              /* Matched hostname */
101   char *u;                              /* Matched user name */
102   char *d;                              /* Matched domain name */
103 } netg__atom;
104
105 /* --- @netg__sym@ --- */
106
107 typedef struct netg__sym {
108   sym_base _base;
109   netg__cons *cons;
110 } netg__sym;
111
112 /* --- Token types for the netgroup parser --- */
113
114 enum {
115   tok_string = 256,
116   tok_eof
117 };
118
119 /*----- Static variables --------------------------------------------------*/
120
121 static sym_table netg__table;           /* Netgroup table */
122 static sym_iter netg__iter;             /* Iterator object for users */
123
124 /*----- Main code ---------------------------------------------------------*/
125
126 /* --- @netg__lex@ --- *
127  *
128  * Arguments:   @const char **p@ = pointer to next unscanned character
129  *              @char *q@ = pointer to output buffer
130  *
131  * Returns:     Token type (either character code or a magic number).
132  *
133  * Use:         Lexes a netgroups line into tokens.
134  */
135
136 static int netg__lex(char **p, char *q)
137 {
138   /* --- Skip any leading whitespace --- */
139
140   while (isspace((unsigned char)**p))
141     (*p)++;
142
143   /* --- Now work out what we've got --- */
144
145   if (**p == 0)
146     return (tok_eof);
147   if (**p == '(' || **p == ')' || **p == ',')
148     return (*(*p)++);
149   do
150     *q++ = *(*p)++;
151   while (**p != 0 && **p != '(' && **p != ')' &&
152          **p != ',' && !isspace((unsigned char)**p));
153   *q++ = 0;
154   return (tok_string);
155 }
156
157 /* --- @netg__foreach@ --- *
158  *
159  * Arguments:   @int st@ = YP protocol-level status code
160  *              @char *k@ = pointer to string containing the key
161  *              @int ksz@ = length of the key string
162  *              @char *v@ = pointer to string containing the value
163  *              @int vsz@ = length of the value string
164  *              @char *data@ = pointer to my data information
165  *
166  * Returns:     Zero to continue, nonzero for no more entries.
167  *
168  * Use:         Handles each incoming netgroup, attaching it to the table.
169  */
170
171 static int netg__foreach(int st, char *k, int ksz,
172                          char *v, int vsz, char *data)
173 {
174   char *kc, *vc;
175   unsigned f;
176   netg__sym *sng;
177   netg__cons *c, **link;
178   char *p;
179   int t;
180
181   /* --- If something is amiss, then quit now --- */
182
183   if (st != YP_TRUE)
184     return (-1);
185
186   /* --- Ignore empty lines from the original file --- */
187
188   if (!ksz || !vsz)
189     return (0);
190
191   /* --- Build my own trashable copies of the key and value --- *
192    *
193    * Note the oddness when I copy the value string.  The extra byte at the
194    * beginning allows me to use the same area of memory as an output buffer
195    * for the lexer.  It must be big enough; the lexer doesn't back up; and
196    * that extra byte gives me somewhere to put a terminating null byte.
197    */
198
199   kc = xmalloc(ksz + 1);
200   memcpy(kc, k, ksz);
201   kc[ksz] = 0;
202
203   vc = xmalloc(vsz + 2);
204   memcpy(vc + 1, v, vsz);
205   vc[vsz + 1] = 0;
206
207   T( trace(TRACE_DEBUG, "debug: netgroup `%s': `%s'", kc, vc + 1); )
208
209   /* --- Allocate a symbol in my table --- */
210
211   sng = sym_find(&netg__table, kc, -1, sizeof(*sng), &f);
212   if (!f)
213     sng->cons = 0;
214
215   /* --- Run to the end of the list --- */
216
217   for (link = &sng->cons; *link; link = &((*link)->cdr))
218     ;
219
220   /* --- Now start the tricky bit --- *
221    *
222    * I have to start parsing the netgroup value string.  Oh, well, it
223    * could be worse.
224    *
225    * The parser is written so as to avoid saying things more often than
226    * necessary.  This tends to involve @goto@s.  You've been warned.
227    */
228
229   p = vc + 1;
230   t = netg__lex(&p, vc);
231
232   for (;;) {
233
234     /* --- Start with a fresh cons cell, with an empty atom attached --- */
235
236     c = xmalloc(sizeof(*c));
237     c->car.atom = xmalloc(sizeof(*c->car.atom));
238
239     /* --- Restart here after an error --- *
240      *
241      * If I restart here, I can avoid freeing the cons cell reallocating
242      * it, which is a little silly.
243      */
244
245   duff_restart:
246     c->car.atom->n = c->car.atom->h = c->car.atom->u = c->car.atom->d = 0;
247     c->f = 0;
248     c->cdr = 0;
249
250     /* --- Handle end-of-line --- */
251
252     if (t == tok_eof)
253       break;
254
255     /* --- Handle a netgroup reference --- */
256
257     if (t == tok_string) {
258       T( trace(TRACE_DEBUG, "debug: add reference to `%s'", vc); )
259       c->car.atom->n = xstrdup(vc);
260       *link = c;
261       link = &c->cdr;
262       t = netg__lex(&p, vc);
263       continue;
264     }
265
266     /* --- Parse our merry way through the host--user--domain triple --- */
267
268     if (t != '(')
269       goto duff;
270     t = netg__lex(&p, vc);
271
272     if (t == tok_string) {
273       T( trace(TRACE_DEBUG, "debug: add host `%s'", vc); )
274       c->car.atom->h = xstrdup(vc);
275       t = netg__lex(&p, vc);
276     }
277
278     if (t != ',')
279       goto duff_paren;
280     t = netg__lex(&p, vc);
281
282     if (t == tok_string) {
283       T( trace(TRACE_DEBUG, "debug: add user `%s'", vc); )
284       c->car.atom->u = xstrdup(vc);
285       t = netg__lex(&p, vc);
286     }
287
288     if (t != ',')
289       goto duff_paren;
290     t = netg__lex(&p, vc);
291
292     if (t == tok_string) {
293       T( trace(TRACE_DEBUG, "debug: add domain `%s'", vc); )
294       c->car.atom->d = xstrdup(vc);
295       t = netg__lex(&p, vc);
296     }
297
298     if (t != ')')
299       goto duff_paren;
300     t = netg__lex(&p, vc);
301
302     /* --- Finished that, so insert this cons cell into the list --- */
303
304     *link = c;
305     link = &c->cdr;
306     continue;
307
308     /* --- Tidy up during scanning of a triple --- *
309      *
310      * I'll search for the closing paren, and hope that I won't miss out too
311      * much.
312      */
313
314   duff_paren:
315     while (t != tok_eof && t != ')')
316       t = netg__lex(&p, vc);
317
318     /* --- Other syntax oddnesses come out here --- *
319      *
320      * Snarf the token which caused the error.
321      */
322
323   duff:
324     moan("syntax error in netgroups line for `%s'", kc);
325     if (c->car.atom->n) free(c->car.atom->n);
326     if (c->car.atom->h) free(c->car.atom->h);
327     if (c->car.atom->u) free(c->car.atom->u);
328     if (c->car.atom->d) free(c->car.atom->d);
329     t = netg__lex(&p, vc);
330     goto duff_restart;
331   }
332
333   free(c->car.atom);
334   free(c);
335   free(kc);
336   free(vc);
337   return (0);
338 }
339
340 /* --- @netg__dumpGroup@ ---  *
341  *
342  * Arguments:   @netg__cons *c@ = pointer to a list head
343  *              @int lev@ = indentation level
344  *
345  * Returns:     ---
346  *
347  * Use:         Dumps the netgroup given.
348  */
349
350 #ifndef NTRACE
351
352 static void netg__dumpGroup(netg__cons *c, int lev)
353 {
354   netg__cons *cc;
355
356   if (!c)
357     return;
358
359   /* --- Check for a cycle --- */
360
361   if (c->f & f_visit) {
362     trace(TRACE_DEBUG, "debug: %*scycle!", lev * 2, "");
363     return;
364   }
365
366   /* --- Dump the netgroup --- */
367
368   c->f |= f_visit;
369
370   for (cc = c; cc; cc = cc->cdr) {
371     if (cc->f & f_cons) {
372       trace(TRACE_DEBUG, "debug: %*ssubnetgroup...", lev * 2, "");
373       netg__dumpGroup(cc->car.cons, lev + 1);
374     } else if (cc->car.atom->n) {
375       trace(TRACE_DEBUG, "debug: %*sunresolved subgroup `%s'",
376             lev * 2, "", cc->car.atom->n);
377     } else {
378       trace(TRACE_DEBUG, "debug: %*s(%s, %s, %s)", lev * 2, "",
379             cc->car.atom->h ? cc->car.atom->h : "<all-hosts>",
380             cc->car.atom->u ? cc->car.atom->u : "<all-users>",
381             cc->car.atom->d ? cc->car.atom->d : "<all-domains>");
382     }
383   }
384
385   c->f &= ~f_visit;
386 }
387
388 #endif
389
390 /* --- @netg__dump@ --- *
391  *
392  * Arguments:   ---
393  *
394  * Returns:     ---
395  *
396  * Use:         Dumps the netgroups table.
397  */
398
399 #ifndef NTRACE
400
401 static void netg__dump(void)
402 {
403   sym_iter i;
404   netg__sym *sng;
405
406   trace(TRACE_DEBUG, "debug: dumping netgroups file");
407   for (sym_mkiter(&i, &netg__table); (sng = sym_next(&i)) != 0; ) {
408     trace(TRACE_DEBUG, "debug: netgroup `%s'...", sng->_base.name);
409     sng->cons->f &= ~f_visit;
410     netg__dumpGroup(sng->cons, 1);
411   }
412 }
413
414 #endif
415
416 /* --- @netg_iterate@, @netg_iterate_r@ --- *
417  *
418  * Arguments:   @netg_iter *i@ = pointer to a netgroup iterator object
419  *
420  * Returns:     ---
421  *
422  * Use:         Starts iterating over the netgroups.
423  */
424
425 void netg_iterate(void) { netg_iterate_r(&netg__iter); }
426 void netg_iterate_r(netg_iter *i) { sym_mkiter(i, &netg__table); }
427
428 /* --- @netg_next@, @netg_next_r@ --- *
429  *
430  * Arguments:   @netg_iter *i@ = pointer to a netgroup iterator object
431  *
432  * Returns:     An opaque pointer to the next item, or null.
433  *
434  * Use:         Returns the next netgroup.
435  */
436
437 netg *netg_next(void) { return (netg_next_r(&netg__iter)); }
438 netg *netg_next_r(netg_iter *i) { return (sym_next(i)); }
439
440 /* --- @netg_name@ --- *
441  *
442  * Arguments:   @netg *n@ = netgroup handle returned by @netg_next@.
443  *
444  * Returns:     A pointer to the name; you may not modify this string.
445  *
446  * Use:         Returns the name of a netgroup.
447  */
448
449 const char *netg_name(netg *n) { return (n->_base.name); }
450
451 /* --- @netg_scan@ --- *
452  *
453  * Arguments:   @netg *n@ = a netgroup handle returned by @netg_next@
454  *              @int (*proc)(netg *n, const char *host, const char *user,@
455  *                      @const char *domain, void *ctx)@ = function to call
456  *                              for each member.
457  *              @void *ctx@ = context pointer to pass to @proc@.
458  *
459  * Returns:     Zero if all went well, or the nonzero return value from
460  *              @proc@.
461  *
462  * Use:         Passes all the members of the netgroup to a given function.
463  *              The function is given the names, directly from the NIS
464  *              netgroup map, except that any empty entries are passed as
465  *              null pointers rather than empty strings.  You may not modify
466  *              any of the strings.  The enumeration function, @proc@, may
467  *              return nonzero to stop itself from being called any more;
468  *              if this happens, the value it returns becomes the result of
469  *              this function.  If all the items are enumerated OK, this
470  *              function returns zero.
471  */
472
473 static int netg__doScan(netg__cons *c,
474                         netg *n,
475                         int (*proc)(netg */*n*/, const char */*host*/,
476                                     const char */*user*/,
477                                     const char */*domain*/, void */*ctx*/),
478                         void *ctx)
479 {
480   int e;
481
482   while (c) {
483     if (c->f & f_cons)
484       e = netg__doScan(c->car.cons, n, proc, ctx);
485     else
486       e = proc(n, c->car.atom->h, c->car.atom->u, c->car.atom->d, ctx);
487     if (e)
488       return (e);
489     c = c->cdr;
490   }
491   return (0);
492 }
493
494 int netg_scan(netg *n,
495               int (*proc)(netg */*n*/, const char */*host*/,
496                           const char */*user*/, const char */*domain*/,
497                           void */*ctx*/),
498               void *ctx)
499 {
500   return (netg__doScan(n->cons, n, proc, ctx));
501 }
502
503 /* --- @netg__breakCycle@ --- *
504  *
505  * Arguments:   @netg__cons *c@ = pointer to a list
506  *
507  * Returns:     ---
508  *
509  * Use:         Scans the given list (recursively) and breaks any cycles it
510  *              finds.
511  */
512
513 static void netg__breakCycle(netg__cons *c)
514 {
515   netg__cons *cc;
516
517   if (!c || c->f & f_uncycled)
518     return;
519
520   c->f |= f_visit;
521   for (cc = c; cc; cc = cc->cdr) {
522     if (~cc->f & f_cons)
523       continue;
524     if (cc->car.cons->f & f_visit) {
525       T( trace(TRACE_DEBUG, "debug: cycle in netgroups"); )
526       cc->car.cons = 0;
527     } else
528       netg__breakCycle(cc->car.cons);
529   }
530   c->f &= ~f_visit;
531   c->f |= f_uncycled;
532 }
533
534 /* --- @netg_init@ --- *
535  *
536  * Arguments:   ---
537  *
538  * Returns:     ---
539  *
540  * Use:         Reads the netgroup database and turns it into something nice.
541  */
542
543 void netg_init(void)
544 {
545   /* --- Initialise my symbol table --- */
546
547   sym_create(&netg__table);
548
549   /* --- Bind myself unto a YP server --- */
550
551   ypstuff_bind();
552   if (!yp_domain)
553     return;
554   
555   /* --- Now try to read all the netgroup entries --- */
556
557   {
558     static struct ypall_callback ncb = { netg__foreach, 0 };
559     yp_all(yp_domain, "netgroup", &ncb);
560   }
561
562   /* --- Dump the table --- */
563
564   IF_TRACING(TRACE_DEBUG, netg__dump(); )
565
566   /* --- Now resolve all the remaining references --- */
567
568   {
569     sym_iter i;
570     netg__sym *sng, *ng;
571     netg__cons *c;
572     netg__atom *a;
573
574     for (sym_mkiter(&i, &netg__table); (sng = sym_next(&i)) != 0; ) {
575       for (c = sng->cons; c; c = c->cdr) {
576         if ((c->f & f_cons) == 0 && c->car.atom->n) {
577           a = c->car.atom;
578           ng = sym_find(&netg__table, a->n, -1, 0, 0);
579           if (!ng) {
580             moan("undefined netgroup `%s' (ignored)", a->n);
581             c->car.atom = 0;
582           } else {
583             c->car.cons = ng->cons;
584             c->f |= f_cons;
585           }
586           free(a->n);
587           free(a);
588         }
589       }
590     }
591   }
592
593   /* --- Remove cycles in the netgroups table --- */
594
595   {
596     sym_iter i;
597     netg__sym *sng;
598
599     for (sym_mkiter(&i, &netg__table); (sng = sym_next(&i)) != 0; )
600       sng->cons->f &= ~f_uncycled;
601     for (sym_mkiter(&i, &netg__table); (sng = sym_next(&i)) != 0; )
602       netg__breakCycle(sng->cons);
603   }    
604
605   /* --- Dump the table again --- */
606
607   IF_TRACING(TRACE_DEBUG, netg__dump(); )
608 }
609
610 /* --- @netg_end@ --- *
611  *
612  * Arguments:   ---
613  *
614  * Returns:     ---
615  *
616  * Use:         Empties the netgroups database.
617  */
618
619 void netg_end(void)
620 {
621   sym_iter i;
622   netg__sym *sng;
623   netg__cons *c, *cc;
624
625   /* --- Remove all the old netgroups rubbish --- */
626
627   for (sym_mkiter(&i, &netg__table); (sng = sym_next(&i)) != 0; ) {
628     c = sng->cons;
629     while (c) {
630       cc = c->cdr;
631       if (~c->f & f_cons) {
632         if (c->car.atom->n) free(c->car.atom->n);
633         if (c->car.atom->h) free(c->car.atom->h);
634         if (c->car.atom->u) free(c->car.atom->u);
635         if (c->car.atom->d) free(c->car.atom->d);
636         free(c->car.atom);
637       }
638       free(c);
639       c = cc;
640     }
641     sym_remove(&netg__table, sng);
642   }
643
644   sym_destroy(&netg__table);
645 }
646
647 /*----- Test driver -------------------------------------------------------*/
648
649 #ifdef TEST_RIG
650
651 int scanner(netg *n, const char *h, const char *u, const char *d, void *c)
652 {
653   fprintf(stderr, "  %s, %s, %s\n",
654           h ? h : "<any>", u ? u : "<any>", d ? d : "<any>");
655   return (0);
656 }
657
658 int main(void)
659 {
660   netg *n;
661   ego("netg-test");
662   trace_on(stderr, TRACE_ALL);
663   netg_init();
664   for (netg_iterate(); (n = netg_next()) != 0; ) {
665     fprintf(stderr, "netgroup %s\n", netg_name(n));
666     netg_scan(n, scanner, 0);
667   }
668   return (0);
669 }
670
671 #endif
672
673 /*----- That's all, folks -------------------------------------------------*/