]> granicus.if.org Git - shadow/blob - src/login_nopam.c
* src/chpasswd.c: Add annotations to indicate that usage() does
[shadow] / src / login_nopam.c
1 /* Taken from logdaemon-5.0, only minimal changes.  --marekm */
2
3 /************************************************************************
4 * Copyright 1995 by Wietse Venema.  All rights reserved. Individual files
5 * may be covered by other copyrights (as noted in the file itself.)
6 *
7 * This material was originally written and compiled by Wietse Venema at
8 * Eindhoven University of Technology, The Netherlands, in 1990, 1991,
9 * 1992, 1993, 1994 and 1995.
10 *
11 * Redistribution and use in source and binary forms are permitted
12 * provided that this entire copyright notice is duplicated in all such
13 * copies.  
14 *
15 * This software is provided "as is" and without any expressed or implied
16 * warranties, including, without limitation, the implied warranties of
17 * merchantibility and fitness for any particular purpose.
18 ************************************************************************/
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #ifndef USE_PAM
25 #ident "$Id$"
26
27 #include "prototypes.h"
28     /*
29      * This module implements a simple but effective form of login access
30      * control based on login names and on host (or domain) names, internet
31      * addresses (or network numbers), or on terminal line names in case of
32      * non-networked logins. Diagnostics are reported through syslog(3).
33      * 
34      * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
35      */
36 #include <sys/types.h>
37 #include <stdio.h>
38 #include <syslog.h>
39 #include <ctype.h>
40 #ifdef HAVE_NETDB_H
41 #include <netdb.h>
42 #endif
43 #include <grp.h>
44 #ifdef PRIMARY_GROUP_MATCH
45 #include <pwd.h>
46 #endif
47 #include <errno.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <stdlib.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #include <arpa/inet.h>          /* for inet_ntoa() */
54
55 #if !defined(MAXHOSTNAMELEN) || (MAXHOSTNAMELEN < 64)
56 #undef MAXHOSTNAMELEN
57 #define MAXHOSTNAMELEN 256
58 #endif
59
60  /* Path name of the access control file. */
61 #ifndef TABLE
62 #define TABLE   "/etc/login.access"
63 #endif
64
65 /* Delimiters for fields and for lists of users, ttys or hosts. */
66 static char fs[] = ":";         /* field separator */
67 static char sep[] = ", \t";     /* list-element separator */
68
69 static bool list_match (char *list, const char *item, bool (*match_fn) (const char *, const char *));
70 static bool user_match (const char *tok, const char *string);
71 static bool from_match (const char *tok, const char *string);
72 static bool string_match (const char *tok, const char *string);
73 static const char *resolve_hostname (const char *string);
74
75 /* login_access - match username/group and host/tty with access control file */
76 int login_access (const char *user, const char *from)
77 {
78         FILE *fp;
79         char line[BUFSIZ];
80         char *perm;             /* becomes permission field */
81         char *users;            /* becomes list of login names */
82         char *froms;            /* becomes list of terminals or hosts */
83         bool match = false;
84
85         /*
86          * Process the table one line at a time and stop at the first match.
87          * Blank lines and lines that begin with a '#' character are ignored.
88          * Non-comment lines are broken at the ':' character. All fields are
89          * mandatory. The first field should be a "+" or "-" character. A
90          * non-existing table means no access control.
91          */
92         fp = fopen (TABLE, "r");
93         if (NULL != fp) {
94                 int lineno = 0; /* for diagnostics */
95                 while (   !match
96                        && (fgets (line, (int) sizeof (line), fp) == line)) {
97                         int end;
98                         lineno++;
99                         end = (int) strlen (line) - 1;
100                         if (line[end] != '\n') {
101                                 SYSLOG ((LOG_ERR,
102                                          "%s: line %d: missing newline or line too long",
103                                          TABLE, lineno));
104                                 continue;
105                         }
106                         if (line[0] == '#') {
107                                 continue;       /* comment line */
108                         }
109                         while (end > 0 && isspace (line[end - 1])) {
110                                 end--;
111                         }
112                         line[end] = '\0';       /* strip trailing whitespace */
113                         if (line[0] == '\0') {  /* skip blank lines */
114                                 continue;
115                         }
116                         if (   ((perm = strtok (line, fs)) == NULL)
117                             || ((users = strtok ((char *) 0, fs)) == NULL)
118                             || ((froms = strtok ((char *) 0, fs)) == NULL)
119                             || (strtok ((char *) 0, fs) != NULL)) {
120                                 SYSLOG ((LOG_ERR,
121                                          "%s: line %d: bad field count",
122                                          TABLE, lineno));
123                                 continue;
124                         }
125                         if (perm[0] != '+' && perm[0] != '-') {
126                                 SYSLOG ((LOG_ERR,
127                                          "%s: line %d: bad first field",
128                                          TABLE, lineno));
129                                 continue;
130                         }
131                         match = (   list_match (froms, from, from_match)
132                                  && list_match (users, user, user_match));
133                 }
134                 (void) fclose (fp);
135         } else if (errno != ENOENT) {
136                 int err = errno;
137                 SYSLOG ((LOG_ERR, "cannot open %s: %s", TABLE, strerror (err)));
138         }
139         return (!match || (line[0] == '+'))?1:0;
140 }
141
142 /* list_match - match an item against a list of tokens with exceptions */
143 static bool list_match (char *list, const char *item, bool (*match_fn) (const char *, const char*))
144 {
145         char *tok;
146         bool match = false;
147
148         /*
149          * Process tokens one at a time. We have exhausted all possible matches
150          * when we reach an "EXCEPT" token or the end of the list. If we do find
151          * a match, look for an "EXCEPT" list and recurse to determine whether
152          * the match is affected by any exceptions.
153          */
154         for (tok = strtok (list, sep); tok != NULL; tok = strtok ((char *) 0, sep)) {
155                 if (strcasecmp (tok, "EXCEPT") == 0) {  /* EXCEPT: give up */
156                         break;
157                 }
158                 match = (*match_fn) (tok, item);
159                 if (match) {
160                         break;
161                 }
162         }
163
164         /* Process exceptions to matches. */
165         if (match) {
166                 while (   ((tok = strtok ((char *) 0, sep)) != NULL)
167                        && (strcasecmp (tok, "EXCEPT") != 0))
168                         /* VOID */ ;
169                 if (tok == 0 || !list_match ((char *) 0, item, match_fn)) {
170                         return (match);
171                 }
172         }
173         return false;
174 }
175
176 /* myhostname - figure out local machine name */
177 static char *myhostname (void)
178 {
179         static char name[MAXHOSTNAMELEN + 1] = "";
180
181         if (name[0] == '\0') {
182                 gethostname (name, sizeof (name));
183                 name[MAXHOSTNAMELEN] = '\0';
184         }
185         return (name);
186 }
187
188 #if HAVE_INNETGR
189 /* netgroup_match - match group against machine or user */
190 static bool
191 netgroup_match (const char *group, const char *machine, const char *user)
192 {
193         static char *mydomain = (char *)0;
194
195         if (mydomain == (char *)0) {
196                 static char domain[MAXHOSTNAMELEN + 1];
197
198                 getdomainname (domain, MAXHOSTNAMELEN);
199                 mydomain = domain;
200         }
201
202         return (innetgr (group, machine, user, mydomain) != 0);
203 }
204 #endif
205
206 /* user_match - match a username against one token */
207 static bool user_match (const char *tok, const char *string)
208 {
209         struct group *group;
210
211 #ifdef PRIMARY_GROUP_MATCH
212         struct passwd *userinf;
213 #endif
214         char *at;
215
216         /*
217          * If a token has the magic value "ALL" the match always succeeds.
218          * Otherwise, return true if the token fully matches the username, or if
219          * the token is a group that contains the username.
220          */
221         at = strchr (tok + 1, '@');
222         if (NULL != at) {       /* split user@host pattern */
223                 *at = '\0';
224                 return (   user_match (tok, string)
225                         && from_match (at + 1, myhostname ()));
226 #if HAVE_INNETGR
227         } else if (tok[0] == '@') {     /* netgroup */
228                 return (netgroup_match (tok + 1, (char *) 0, string));
229 #endif
230         } else if (string_match (tok, string)) {        /* ALL or exact match */
231                 return true;
232         /* local, no need for xgetgrnam */
233         } else if ((group = getgrnam (tok)) != NULL) {  /* try group membership */
234                 int i;
235                 for (i = 0; NULL != group->gr_mem[i]; i++) {
236                         if (strcasecmp (string, group->gr_mem[i]) == 0) {
237                                 return true;
238                         }
239                 }
240 #ifdef PRIMARY_GROUP_MATCH
241                 /*
242                  * If the string is an user whose initial GID matches the token,
243                  * accept it. May avoid excessively long lines in /etc/group.
244                  * Radu-Adrian Feurdean <raf@licj.soroscj.ro>
245                  *
246                  * XXX - disabled by default for now.  Need to verify that
247                  * getpwnam() doesn't have some nasty side effects.  --marekm
248                  */
249                 /* local, no need for xgetpwnam */
250                 userinf = getpwnam (string);
251                 if (NULL != userinf) {
252                         if (userinf->pw_gid == group->gr_gid) {
253                                 return true;
254                         }
255                 }
256 #endif
257         }
258         return false;
259 }
260
261 static const char *resolve_hostname (const char *string)
262 {
263         /*
264          * Resolve hostname to numeric IP address, as suggested
265          * by Dave Hagewood <admin@arrowweb.com>.  --marekm
266          */
267         struct hostent *hp;
268
269         hp = gethostbyname (string);
270         if (NULL != hp) {
271                 return inet_ntoa (*((struct in_addr *) *(hp->h_addr_list)));
272         }
273
274         SYSLOG ((LOG_ERR, "%s - unknown host", string));
275         return string;
276 }
277
278 /* from_match - match a host or tty against a list of tokens */
279
280 static bool from_match (const char *tok, const char *string)
281 {
282         size_t tok_len;
283
284         /*
285          * If a token has the magic value "ALL" the match always succeeds. Return
286          * true if the token fully matches the string. If the token is a domain
287          * name, return true if it matches the last fields of the string. If the
288          * token has the magic value "LOCAL", return true if the string does not
289          * contain a "." character. If the token is a network number, return true
290          * if it matches the head of the string.
291          */
292 #if HAVE_INNETGR
293         if (tok[0] == '@') {    /* netgroup */
294                 return (netgroup_match (tok + 1, string, (char *) 0));
295         } else
296 #endif
297         if (string_match (tok, string)) {       /* ALL or exact match */
298                 return true;
299         } else if (tok[0] == '.') {     /* domain: match last fields */
300                 size_t str_len;
301                 str_len = strlen (string);
302                 tok_len = strlen (tok);
303                 if (   (str_len > tok_len)
304                     && (strcasecmp (tok, string + str_len - tok_len) == 0)) {
305                         return true;
306                 }
307         } else if (strcasecmp (tok, "LOCAL") == 0) {    /* local: no dots */
308                 if (strchr (string, '.') == NULL) {
309                         return true;
310                 }
311         } else if (   (tok[(tok_len = strlen (tok)) - 1] == '.') /* network */
312                    && (strncmp (tok, resolve_hostname (string), tok_len) == 0)) {
313                 return true;
314         }
315         return false;
316 }
317
318 /* string_match - match a string against one token */
319 static bool string_match (const char *tok, const char *string)
320 {
321
322         /*
323          * If the token has the magic value "ALL" the match always succeeds.
324          * Otherwise, return true if the token fully matches the string.
325          */
326         if (strcasecmp (tok, "ALL") == 0) {     /* all: always matches */
327                 return true;
328         } else if (strcasecmp (tok, string) == 0) {     /* try exact match */
329                 return true;
330         }
331         return false;
332 }
333
334 #else                           /* !USE_PAM */
335 extern int errno;               /* warning: ANSI C forbids an empty source file */
336 #endif                          /* !USE_PAM */