update control to reflect move of primary repo to collab-maint
[debian/sudo] / plugins / sudoers / match.c
1 /*
2  * Copyright (c) 1996, 1998-2005, 2007-2012
3  *      Todd C. Miller <Todd.Miller@courtesan.com>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
17  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18  *
19  * Sponsored in part by the Defense Advanced Research Projects
20  * Agency (DARPA) and Air Force Research Laboratory, Air Force
21  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
22  */
23
24 #include <config.h>
25
26 #include <sys/types.h>
27 #include <sys/param.h>
28 #include <sys/stat.h>
29 #include <stdio.h>
30 #ifdef STDC_HEADERS
31 # include <stdlib.h>
32 # include <stddef.h>
33 #else
34 # ifdef HAVE_STDLIB_H
35 #  include <stdlib.h>
36 # endif
37 #endif /* STDC_HEADERS */
38 #ifdef HAVE_STRING_H
39 # include <string.h>
40 #endif /* HAVE_STRING_H */
41 #ifdef HAVE_STRINGS_H
42 # include <strings.h>
43 #endif /* HAVE_STRINGS_H */
44 #ifdef HAVE_UNISTD_H
45 # include <unistd.h>
46 #endif /* HAVE_UNISTD_H */
47 #ifdef HAVE_FNMATCH
48 # include <fnmatch.h>
49 #endif /* HAVE_FNMATCH */
50 #ifdef HAVE_GLOB
51 # include <glob.h>
52 #endif /* HAVE_GLOB */
53 #ifdef HAVE_NETGROUP_H
54 # include <netgroup.h>
55 #endif /* HAVE_NETGROUP_H */
56 #include <ctype.h>
57 #include <pwd.h>
58 #include <grp.h>
59 #include <netdb.h>
60 #ifdef HAVE_DIRENT_H
61 # include <dirent.h>
62 # define NAMLEN(dirent) strlen((dirent)->d_name)
63 #else
64 # define dirent direct
65 # define NAMLEN(dirent) (dirent)->d_namlen
66 # ifdef HAVE_SYS_NDIR_H
67 #  include <sys/ndir.h>
68 # endif
69 # ifdef HAVE_SYS_DIR_H
70 #  include <sys/dir.h>
71 # endif
72 # ifdef HAVE_NDIR_H
73 #  include <ndir.h>
74 # endif
75 #endif
76
77 #include "sudoers.h"
78 #include "parse.h"
79 #include <gram.h>
80
81 #ifndef HAVE_FNMATCH
82 # include "compat/fnmatch.h"
83 #endif /* HAVE_FNMATCH */
84 #ifndef HAVE_GLOB
85 # include "compat/glob.h"
86 #endif /* HAVE_GLOB */
87
88 static struct member_list empty;
89
90 static bool command_matches_dir(char *, size_t);
91 static bool command_matches_glob(char *, char *);
92 static bool command_matches_fnmatch(char *, char *);
93 static bool command_matches_normal(char *, char *);
94
95 /*
96  * Returns true if string 's' contains meta characters.
97  */
98 #define has_meta(s)     (strpbrk(s, "\\?*[]") != NULL)
99
100 /*
101  * Check for user described by pw in a list of members.
102  * Returns ALLOW, DENY or UNSPEC.
103  */
104 static int
105 _userlist_matches(struct passwd *pw, struct member_list *list)
106 {
107     struct member *m;
108     struct alias *a;
109     int rval, matched = UNSPEC;
110     debug_decl(_userlist_matches, SUDO_DEBUG_MATCH)
111
112     tq_foreach_rev(list, m) {
113         switch (m->type) {
114             case ALL:
115                 matched = !m->negated;
116                 break;
117             case NETGROUP:
118                 if (netgr_matches(m->name, NULL, NULL, pw->pw_name))
119                     matched = !m->negated;
120                 break;
121             case USERGROUP:
122                 if (usergr_matches(m->name, pw->pw_name, pw))
123                     matched = !m->negated;
124                 break;
125             case ALIAS:
126                 if ((a = alias_find(m->name, USERALIAS)) != NULL) {
127                     rval = _userlist_matches(pw, &a->members);
128                     if (rval != UNSPEC)
129                         matched = m->negated ? !rval : rval;
130                     break;
131                 }
132                 /* FALLTHROUGH */
133             case WORD:
134                 if (userpw_matches(m->name, pw->pw_name, pw))
135                     matched = !m->negated;
136                 break;
137         }
138         if (matched != UNSPEC)
139             break;
140     }
141     debug_return_bool(matched);
142 }
143
144 int
145 userlist_matches(struct passwd *pw, struct member_list *list)
146 {
147     alias_seqno++;
148     return _userlist_matches(pw, list);
149 }
150
151 /*
152  * Check for user described by pw in a list of members.
153  * If both lists are empty compare against def_runas_default.
154  * Returns ALLOW, DENY or UNSPEC.
155  */
156 static int
157 _runaslist_matches(struct member_list *user_list, struct member_list *group_list)
158 {
159     struct member *m;
160     struct alias *a;
161     int rval;
162     int user_matched = UNSPEC;
163     int group_matched = UNSPEC;
164     debug_decl(_runaslist_matches, SUDO_DEBUG_MATCH)
165
166     if (runas_pw != NULL) {
167         /* If no runas user or runas group listed in sudoers, use default. */
168         if (tq_empty(user_list) && tq_empty(group_list))
169             debug_return_int(userpw_matches(def_runas_default, runas_pw->pw_name, runas_pw));
170
171         tq_foreach_rev(user_list, m) {
172             switch (m->type) {
173                 case ALL:
174                     user_matched = !m->negated;
175                     break;
176                 case NETGROUP:
177                     if (netgr_matches(m->name, NULL, NULL, runas_pw->pw_name))
178                         user_matched = !m->negated;
179                     break;
180                 case USERGROUP:
181                     if (usergr_matches(m->name, runas_pw->pw_name, runas_pw))
182                         user_matched = !m->negated;
183                     break;
184                 case ALIAS:
185                     if ((a = alias_find(m->name, RUNASALIAS)) != NULL) {
186                         rval = _runaslist_matches(&a->members, &empty);
187                         if (rval != UNSPEC)
188                             user_matched = m->negated ? !rval : rval;
189                         break;
190                     }
191                     /* FALLTHROUGH */
192                 case WORD:
193                     if (userpw_matches(m->name, runas_pw->pw_name, runas_pw))
194                         user_matched = !m->negated;
195                     break;
196             }
197             if (user_matched != UNSPEC)
198                 break;
199         }
200     }
201
202     if (runas_gr != NULL) {
203         if (user_matched == UNSPEC) {
204             if (runas_pw == NULL || strcmp(runas_pw->pw_name, user_name) == 0)
205                 user_matched = ALLOW;   /* only changing group */
206         }
207         tq_foreach_rev(group_list, m) {
208             switch (m->type) {
209                 case ALL:
210                     group_matched = !m->negated;
211                     break;
212                 case ALIAS:
213                     if ((a = alias_find(m->name, RUNASALIAS)) != NULL) {
214                         rval = _runaslist_matches(&empty, &a->members);
215                         if (rval != UNSPEC)
216                             group_matched = m->negated ? !rval : rval;
217                         break;
218                     }
219                     /* FALLTHROUGH */
220                 case WORD:
221                     if (group_matches(m->name, runas_gr))
222                         group_matched = !m->negated;
223                     break;
224             }
225             if (group_matched != UNSPEC)
226                 break;
227         }
228         if (group_matched == UNSPEC) {
229             if (runas_pw != NULL && runas_pw->pw_gid == runas_gr->gr_gid)
230                 group_matched = ALLOW;  /* runas group matches passwd db */
231         }
232     }
233
234     if (user_matched == DENY || group_matched == DENY)
235         debug_return_int(DENY);
236     if (user_matched == group_matched || runas_gr == NULL)
237         debug_return_int(user_matched);
238     debug_return_int(UNSPEC);
239 }
240
241 int
242 runaslist_matches(struct member_list *user_list, struct member_list *group_list)
243 {
244     alias_seqno++;
245     return _runaslist_matches(user_list ? user_list : &empty,
246         group_list ? group_list : &empty);
247 }
248
249 /*
250  * Check for host and shost in a list of members.
251  * Returns ALLOW, DENY or UNSPEC.
252  */
253 static int
254 _hostlist_matches(struct member_list *list)
255 {
256     struct member *m;
257     struct alias *a;
258     int rval, matched = UNSPEC;
259     debug_decl(_hostlist_matches, SUDO_DEBUG_MATCH)
260
261     tq_foreach_rev(list, m) {
262         switch (m->type) {
263             case ALL:
264                 matched = !m->negated;
265                 break;
266             case NETGROUP:
267                 if (netgr_matches(m->name, user_host, user_shost, NULL))
268                     matched = !m->negated;
269                 break;
270             case NTWKADDR:
271                 if (addr_matches(m->name))
272                     matched = !m->negated;
273                 break;
274             case ALIAS:
275                 if ((a = alias_find(m->name, HOSTALIAS)) != NULL) {
276                     rval = _hostlist_matches(&a->members);
277                     if (rval != UNSPEC)
278                         matched = m->negated ? !rval : rval;
279                     break;
280                 }
281                 /* FALLTHROUGH */
282             case WORD:
283                 if (hostname_matches(user_shost, user_host, m->name))
284                     matched = !m->negated;
285                 break;
286         }
287         if (matched != UNSPEC)
288             break;
289     }
290     debug_return_bool(matched);
291 }
292
293 int
294 hostlist_matches(struct member_list *list)
295 {
296     alias_seqno++;
297     return _hostlist_matches(list);
298 }
299
300 /*
301  * Check for cmnd and args in a list of members.
302  * Returns ALLOW, DENY or UNSPEC.
303  */
304 static int
305 _cmndlist_matches(struct member_list *list)
306 {
307     struct member *m;
308     int matched = UNSPEC;
309     debug_decl(_cmndlist_matches, SUDO_DEBUG_MATCH)
310
311     tq_foreach_rev(list, m) {
312         matched = cmnd_matches(m);
313         if (matched != UNSPEC)
314             break;
315     }
316     debug_return_bool(matched);
317 }
318
319 int
320 cmndlist_matches(struct member_list *list)
321 {
322     alias_seqno++;
323     return _cmndlist_matches(list);
324 }
325
326 /*
327  * Check cmnd and args.
328  * Returns ALLOW, DENY or UNSPEC.
329  */
330 int
331 cmnd_matches(struct member *m)
332 {
333     struct alias *a;
334     struct sudo_command *c;
335     int rval, matched = UNSPEC;
336     debug_decl(cmnd_matches, SUDO_DEBUG_MATCH)
337
338     switch (m->type) {
339         case ALL:
340             matched = !m->negated;
341             break;
342         case ALIAS:
343             alias_seqno++;
344             if ((a = alias_find(m->name, CMNDALIAS)) != NULL) {
345                 rval = _cmndlist_matches(&a->members);
346                 if (rval != UNSPEC)
347                     matched = m->negated ? !rval : rval;
348             }
349             break;
350         case COMMAND:
351             c = (struct sudo_command *)m->name;
352             if (command_matches(c->cmnd, c->args))
353                 matched = !m->negated;
354             break;
355     }
356     debug_return_bool(matched);
357 }
358
359 static bool
360 command_args_match(sudoers_cmnd, sudoers_args)
361     char *sudoers_cmnd;
362     char *sudoers_args;
363 {
364     int flags = 0;
365     debug_decl(command_args_match, SUDO_DEBUG_MATCH)
366
367     /*
368      * If no args specified in sudoers, any user args are allowed.
369      * If the empty string is specified in sudoers, no user args are allowed.
370      */
371     if (!sudoers_args ||
372         (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)))
373         debug_return_bool(true);
374     /*
375      * If args are specified in sudoers, they must match the user args.
376      * If running as sudoedit, all args are assumed to be paths.
377      */
378     if (sudoers_args) {
379         /* For sudoedit, all args are assumed to be pathnames. */
380         if (strcmp(sudoers_cmnd, "sudoedit") == 0)
381             flags = FNM_PATHNAME;
382         if (fnmatch(sudoers_args, user_args ? user_args : "", flags) == 0)
383             debug_return_bool(true);
384     }
385     debug_return_bool(false);
386 }
387
388 /*
389  * If path doesn't end in /, return true iff cmnd & path name the same inode;
390  * otherwise, return true if user_cmnd names one of the inodes in path.
391  */
392 bool
393 command_matches(char *sudoers_cmnd, char *sudoers_args)
394 {
395     debug_decl(command_matches, SUDO_DEBUG_MATCH)
396
397     /* Check for pseudo-commands */
398     if (sudoers_cmnd[0] != '/') {
399         /*
400          * Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
401          *  a) there are no args in sudoers OR
402          *  b) there are no args on command line and none req by sudoers OR
403          *  c) there are args in sudoers and on command line and they match
404          */
405         if (strcmp(sudoers_cmnd, "sudoedit") != 0 ||
406             strcmp(user_cmnd, "sudoedit") != 0)
407             debug_return_bool(false);
408         if (command_args_match(sudoers_cmnd, sudoers_args)) {
409             efree(safe_cmnd);
410             safe_cmnd = estrdup(sudoers_cmnd);
411             debug_return_bool(true);
412         } else
413             debug_return_bool(false);
414     }
415
416     if (has_meta(sudoers_cmnd)) {
417         /*
418          * If sudoers_cmnd has meta characters in it, we need to
419          * use glob(3) and/or fnmatch(3) to do the matching.
420          */
421         if (def_fast_glob)
422             debug_return_bool(command_matches_fnmatch(sudoers_cmnd, sudoers_args));
423         debug_return_bool(command_matches_glob(sudoers_cmnd, sudoers_args));
424     }
425     debug_return_bool(command_matches_normal(sudoers_cmnd, sudoers_args));
426 }
427
428 static bool
429 command_matches_fnmatch(char *sudoers_cmnd, char *sudoers_args)
430 {
431     debug_decl(command_matches_fnmatch, SUDO_DEBUG_MATCH)
432
433     /*
434      * Return true if fnmatch(3) succeeds AND
435      *  a) there are no args in sudoers OR
436      *  b) there are no args on command line and none required by sudoers OR
437      *  c) there are args in sudoers and on command line and they match
438      * else return false.
439      */
440     if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
441         debug_return_bool(false);
442     if (command_args_match(sudoers_cmnd, sudoers_args)) {
443         if (safe_cmnd)
444             free(safe_cmnd);
445         safe_cmnd = estrdup(user_cmnd);
446         debug_return_bool(true);
447     }
448     debug_return_bool(false);
449 }
450
451 static bool
452 command_matches_glob(char *sudoers_cmnd, char *sudoers_args)
453 {
454     struct stat sudoers_stat;
455     size_t dlen;
456     char **ap, *base, *cp;
457     glob_t gl;
458     debug_decl(command_matches_glob, SUDO_DEBUG_MATCH)
459
460     /*
461      * First check to see if we can avoid the call to glob(3).
462      * Short circuit if there are no meta chars in the command itself
463      * and user_base and basename(sudoers_cmnd) don't match.
464      */
465     dlen = strlen(sudoers_cmnd);
466     if (sudoers_cmnd[dlen - 1] != '/') {
467         if ((base = strrchr(sudoers_cmnd, '/')) != NULL) {
468             base++;
469             if (!has_meta(base) && strcmp(user_base, base) != 0)
470                 debug_return_bool(false);
471         }
472     }
473     /*
474      * Return true if we find a match in the glob(3) results AND
475      *  a) there are no args in sudoers OR
476      *  b) there are no args on command line and none required by sudoers OR
477      *  c) there are args in sudoers and on command line and they match
478      * else return false.
479      */
480     if (glob(sudoers_cmnd, GLOB_NOSORT, NULL, &gl) != 0 || gl.gl_pathc == 0) {
481         globfree(&gl);
482         debug_return_bool(false);
483     }
484     /* For each glob match, compare basename, st_dev and st_ino. */
485     for (ap = gl.gl_pathv; (cp = *ap) != NULL; ap++) {
486         /* If it ends in '/' it is a directory spec. */
487         dlen = strlen(cp);
488         if (cp[dlen - 1] == '/') {
489             if (command_matches_dir(cp, dlen))
490                 debug_return_bool(true);
491             continue;
492         }
493
494         /* Only proceed if user_base and basename(cp) match */
495         if ((base = strrchr(cp, '/')) != NULL)
496             base++;
497         else
498             base = cp;
499         if (strcmp(user_base, base) != 0 ||
500             stat(cp, &sudoers_stat) == -1)
501             continue;
502         if (user_stat == NULL ||
503             (user_stat->st_dev == sudoers_stat.st_dev &&
504             user_stat->st_ino == sudoers_stat.st_ino)) {
505             efree(safe_cmnd);
506             safe_cmnd = estrdup(cp);
507             break;
508         }
509     }
510     globfree(&gl);
511     if (cp == NULL)
512         debug_return_bool(false);
513
514     if (command_args_match(sudoers_cmnd, sudoers_args)) {
515         efree(safe_cmnd);
516         safe_cmnd = estrdup(user_cmnd);
517         debug_return_bool(true);
518     }
519     debug_return_bool(false);
520 }
521
522 static bool
523 command_matches_normal(char *sudoers_cmnd, char *sudoers_args)
524 {
525     struct stat sudoers_stat;
526     char *base;
527     size_t dlen;
528     debug_decl(command_matches_normal, SUDO_DEBUG_MATCH)
529
530     /* If it ends in '/' it is a directory spec. */
531     dlen = strlen(sudoers_cmnd);
532     if (sudoers_cmnd[dlen - 1] == '/')
533         debug_return_bool(command_matches_dir(sudoers_cmnd, dlen));
534
535     /* Only proceed if user_base and basename(sudoers_cmnd) match */
536     if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
537         base = sudoers_cmnd;
538     else
539         base++;
540     if (strcmp(user_base, base) != 0 ||
541         stat(sudoers_cmnd, &sudoers_stat) == -1)
542         debug_return_bool(false);
543
544     /*
545      * Return true if inode/device matches AND
546      *  a) there are no args in sudoers OR
547      *  b) there are no args on command line and none req by sudoers OR
548      *  c) there are args in sudoers and on command line and they match
549      */
550     if (user_stat != NULL &&
551         (user_stat->st_dev != sudoers_stat.st_dev ||
552         user_stat->st_ino != sudoers_stat.st_ino))
553         debug_return_bool(false);
554     if (command_args_match(sudoers_cmnd, sudoers_args)) {
555         efree(safe_cmnd);
556         safe_cmnd = estrdup(sudoers_cmnd);
557         debug_return_bool(true);
558     }
559     debug_return_bool(false);
560 }
561
562 /*
563  * Return true if user_cmnd names one of the inodes in dir, else false.
564  */
565 static bool
566 command_matches_dir(char *sudoers_dir, size_t dlen)
567 {
568     struct stat sudoers_stat;
569     struct dirent *dent;
570     char buf[PATH_MAX];
571     DIR *dirp;
572     debug_decl(command_matches_dir, SUDO_DEBUG_MATCH)
573
574     /*
575      * Grot through directory entries, looking for user_base.
576      */
577     dirp = opendir(sudoers_dir);
578     if (dirp == NULL)
579         debug_return_bool(false);
580
581     if (strlcpy(buf, sudoers_dir, sizeof(buf)) >= sizeof(buf)) {
582         closedir(dirp);
583         debug_return_bool(false);
584     }
585     while ((dent = readdir(dirp)) != NULL) {
586         /* ignore paths > PATH_MAX (XXX - log) */
587         buf[dlen] = '\0';
588         if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
589             continue;
590
591         /* only stat if basenames are the same */
592         if (strcmp(user_base, dent->d_name) != 0 ||
593             stat(buf, &sudoers_stat) == -1)
594             continue;
595         if (user_stat == NULL ||
596             (user_stat->st_dev == sudoers_stat.st_dev &&
597             user_stat->st_ino == sudoers_stat.st_ino)) {
598             efree(safe_cmnd);
599             safe_cmnd = estrdup(buf);
600             break;
601         }
602     }
603
604     closedir(dirp);
605     debug_return_bool(dent != NULL);
606 }
607
608 /*
609  * Returns true if the hostname matches the pattern, else false
610  */
611 bool
612 hostname_matches(char *shost, char *lhost, char *pattern)
613 {
614     debug_decl(hostname_matches, SUDO_DEBUG_MATCH)
615
616     if (has_meta(pattern)) {
617         if (strchr(pattern, '.'))
618             debug_return_bool(!fnmatch(pattern, lhost, FNM_CASEFOLD));
619         else
620             debug_return_bool(!fnmatch(pattern, shost, FNM_CASEFOLD));
621     } else {
622         if (strchr(pattern, '.'))
623             debug_return_bool(!strcasecmp(lhost, pattern));
624         else
625             debug_return_bool(!strcasecmp(shost, pattern));
626     }
627 }
628
629 /*
630  *  Returns true if the user/uid from sudoers matches the specified user/uid,
631  *  else returns false.
632  */
633 bool
634 userpw_matches(char *sudoers_user, char *user, struct passwd *pw)
635 {
636     debug_decl(userpw_matches, SUDO_DEBUG_MATCH)
637
638     if (pw != NULL && *sudoers_user == '#') {
639         uid_t uid = (uid_t) atoi(sudoers_user + 1);
640         if (uid == pw->pw_uid)
641             debug_return_bool(true);
642     }
643     debug_return_bool(strcmp(sudoers_user, user) == 0);
644 }
645
646 /*
647  *  Returns true if the group/gid from sudoers matches the specified group/gid,
648  *  else returns false.
649  */
650 bool
651 group_matches(char *sudoers_group, struct group *gr)
652 {
653     debug_decl(group_matches, SUDO_DEBUG_MATCH)
654
655     if (*sudoers_group == '#') {
656         gid_t gid = (gid_t) atoi(sudoers_group + 1);
657         if (gid == gr->gr_gid)
658             debug_return_bool(true);
659     }
660     debug_return_bool(strcmp(gr->gr_name, sudoers_group) == 0);
661 }
662
663 /*
664  *  Returns true if the given user belongs to the named group,
665  *  else returns false.
666  */
667 bool
668 usergr_matches(char *group, char *user, struct passwd *pw)
669 {
670     int matched = false;
671     struct passwd *pw0 = NULL;
672     debug_decl(usergr_matches, SUDO_DEBUG_MATCH)
673
674     /* make sure we have a valid usergroup, sudo style */
675     if (*group++ != '%')
676         goto done;
677
678     if (*group == ':' && def_group_plugin) {
679         matched = group_plugin_query(user, group + 1, pw);
680         goto done;
681     }
682
683     /* look up user's primary gid in the passwd file */
684     if (pw == NULL) {
685         if ((pw0 = sudo_getpwnam(user)) == NULL)
686             goto done;
687         pw = pw0;
688     }
689
690     if (user_in_group(pw, group)) {
691         matched = true;
692         goto done;
693     }
694
695     /* not a Unix group, could be an external group */
696     if (def_group_plugin && group_plugin_query(user, group, pw)) {
697         matched = true;
698         goto done;
699     }
700
701 done:
702     if (pw0 != NULL)
703         pw_delref(pw0);
704
705     debug_return_bool(matched);
706 }
707
708 /*
709  * Returns true if "host" and "user" belong to the netgroup "netgr",
710  * else return false.  Either of "host", "shost" or "user" may be NULL
711  * in which case that argument is not checked...
712  *
713  * XXX - swap order of host & shost
714  */
715 bool
716 netgr_matches(char *netgr, char *lhost, char *shost, char *user)
717 {
718     static char *domain;
719 #ifdef HAVE_GETDOMAINNAME
720     static int initialized;
721 #endif
722     debug_decl(netgr_matches, SUDO_DEBUG_MATCH)
723
724     /* make sure we have a valid netgroup, sudo style */
725     if (*netgr++ != '+')
726         debug_return_bool(false);
727
728 #ifdef HAVE_GETDOMAINNAME
729     /* get the domain name (if any) */
730     if (!initialized) {
731         domain = (char *) emalloc(MAXHOSTNAMELEN + 1);
732         if (getdomainname(domain, MAXHOSTNAMELEN + 1) == -1 || *domain == '\0') {
733             efree(domain);
734             domain = NULL;
735         }
736         initialized = 1;
737     }
738 #endif /* HAVE_GETDOMAINNAME */
739
740 #ifdef HAVE_INNETGR
741     if (innetgr(netgr, lhost, user, domain))
742         debug_return_bool(true);
743     else if (lhost != shost && innetgr(netgr, shost, user, domain))
744         debug_return_bool(true);
745 #endif /* HAVE_INNETGR */
746
747     debug_return_bool(false);
748 }