1 /* checktty.c - linked into login, checks user against /etc/usertty
2 Created 25-Aug-95 by Peter Orbaek <poe@daimi.aau.dk>
3 Fixed by JDS June 1996 to clear lists and close files
5 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
6 - added Native Language Support
10 #include <sys/types.h>
11 #include <sys/param.h>
23 #include <sys/syslog.h>
27 #include <sys/sysmacros.h>
28 #ifdef HAVE_LINUX_MAJOR_H
29 #include <linux/major.h>
32 #include "pathnames.h"
40 static gid_t mygroups[NGROUPS];
41 static int num_groups;
45 /* linked list of names */
51 enum State { StateUsers, StateGroups, StateClasses };
53 #define CLASSNAMELEN 32
56 struct grplist *first;
57 struct ttyclass *next;
58 char classname[CLASSNAMELEN];
61 struct ttyclass *ttyclasses = NULL;
64 am_in_group(char *group)
71 for (ge = mygroups; ge < mygroups + num_groups; ge++) {
72 if (g->gr_gid== *ge) return 1;
79 find_groups(gid_t defgrp, const char *user)
81 num_groups = getgroups(NGROUPS, mygroups);
84 static struct ttyclass *
85 new_class(char *class)
89 tc = (struct ttyclass *)malloc(sizeof(struct ttyclass));
91 printf(_("login: memory low, login may fail\n"));
92 syslog(LOG_WARNING, _("can't malloc for ttyclass"));
96 tc->next = ttyclasses;
98 xstrncpy(tc->classname, class, CLASSNAMELEN);
104 add_to_class(struct ttyclass *tc, char *tty)
108 if (tc == NULL) return;
110 ge = (struct grplist *)malloc(sizeof(struct grplist));
112 printf(_("login: memory low, login may fail\n"));
113 syslog(LOG_WARNING, _("can't malloc for grplist"));
117 ge->next = tc->first;
118 xstrncpy(ge->name, tty, NAMELEN);
123 /* return true if tty is a pty. Very linux dependent */
125 isapty(const char *tty)
131 /* avoid snprintf - old systems do not have it */
132 if (strlen(tty) + 6 > sizeof(devname))
134 sprintf(devname, "/dev/%s", tty);
136 if((stat(devname, &stb) >= 0) && S_ISCHR(stb.st_mode)) {
137 int majordev = major(stb.st_rdev);
139 /* this is for linux versions before 1.3: use major 4 */
140 if(majordev == TTY_MAJOR && minor(stb.st_rdev) >= 192)
143 #if defined(PTY_SLAVE_MAJOR)
144 /* this is for linux 1.3 and newer: use major 3 */
145 if(majordev == PTY_SLAVE_MAJOR)
149 #if defined(UNIX98_PTY_SLAVE_MAJOR) && defined(UNIX98_PTY_MAJOR_COUNT)
150 /* this is for linux 2.1.116 and newer: use majors 136-143 */
151 if(majordev >= UNIX98_PTY_SLAVE_MAJOR &&
152 majordev < UNIX98_PTY_SLAVE_MAJOR + UNIX98_PTY_MAJOR_COUNT)
157 #endif /* __linux__ */
162 /* IPv4 -- pattern is x.x.x.x/y.y.y.y (net/mask)*/
164 hnmatch_ip4(const char *pat)
166 int x1, x2, x3, x4, y1, y2, y3, y4;
167 unsigned long p, mask, a;
170 /* pattern is an IP QUAD address and a mask x.x.x.x/y.y.y.y */
171 if (sscanf(pat, "%d.%d.%d.%d/%d.%d.%d.%d",
172 &x1, &x2, &x3, &x4, &y1, &y2, &y3, &y4) < 8)
175 p = (((unsigned long)x1<<24)+((unsigned long)x2<<16)
176 +((unsigned long)x3<<8)+((unsigned long)x4));
177 mask = (((unsigned long)y1<<24)+((unsigned long)y2<<16)
178 +((unsigned long)y3<<8)+((unsigned long)y4));
180 if (hostaddress[0] == 0)
183 ha = (unsigned char *)hostaddress;
184 a = (((unsigned long)ha[0]<<24)+((unsigned long)ha[1]<<16)
185 +((unsigned long)ha[2]<<8)+((unsigned long)ha[3]));
186 return ((p & mask) == (a & mask));
189 /* IPv6 -- pattern is [hex:hex:....]/number ([net]/mask) */
191 hnmatch_ip6(const char *pat)
195 struct in6_addr addr;
196 struct addrinfo hints, *res;
197 struct sockaddr_in6 net;
201 if (pat == NULL || *pat != '[')
204 memcpy(&addr, hostaddress, sizeof(addr));
206 memset(&hints, 0, sizeof(hints));
207 hints.ai_family = AF_INET6;
208 hints.ai_socktype = SOCK_STREAM;
209 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
211 patnet = strdup(pat);
213 /* match IPv6 address against [netnumber]/prefixlen */
214 if (!(p = strchr(patnet, ']')))
219 if (! (*p == '/' && isdigit((unsigned char) *(p + 1))))
224 /* prepare net address */
225 if (getaddrinfo(patnet + 1, NULL, &hints, &res) != 0)
228 memcpy(&net, res->ai_addr, sizeof(net));
231 /* convert mask to number */
232 if ((mask_len = atoi(patmask)) < 0 || mask_len > 128)
236 while (mask_len > 0) {
238 u_int32_t mask = htonl(~(0xffffffff >> mask_len));
240 if ((*(u_int32_t *)&addr.s6_addr[i] & mask) !=
241 (*(u_int32_t *)&net.sin6_addr.s6_addr[i] & mask))
245 if (*(u_int32_t *)&addr.s6_addr[i] !=
246 *(u_int32_t *)&net.sin6_addr.s6_addr[i])
260 /* match the hostname hn against the pattern pat */
262 hnmatch(const char *hn, const char *pat)
265 if ((hn == NULL) && (strcmp(pat, "localhost") == 0))
267 if ((hn == NULL) || *hn == '\0')
270 if (*pat >= '0' && *pat <= '9')
271 return hostfamily == AF_INET ? hnmatch_ip4(pat) : 0;
272 else if (*pat == '[')
273 return hostfamily == AF_INET6 ? hnmatch_ip6(pat) : 0;
275 /* pattern is a suffix of a FQDN */
281 return (strcasecmp(pat, hn + m - n) == 0);
285 #ifdef MAIN_TEST_CHECKTTY
287 char hostaddress[16];
288 sa_family_t hostfamily;
290 void sleepexit(int eval) {} /* dummy for this test */
291 void badlogin(const char *s) {} /* dummy for this test */
294 main(int argc, char **argv)
296 struct addrinfo hints, *info = NULL;
301 { "130.225.16.0/255.255.254.0", "130.225.16.1" },
302 { "130.225.16.0/255.255.254.0", "10.20.30.1" },
303 { "130.225.0.0/255.254.0.0", "130.225.16.1" },
304 { "130.225.0.0/255.254.0.0", "130.225.17.1" },
305 { "130.225.0.0/255.254.0.0", "150.160.170.180" },
306 { "[3ffe:505:2:1::]/64", "3ffe:505:2:1::" },
307 { "[3ffe:505:2:1::]/64", "3ffe:505:2:2::" },
308 { "[3ffe:505:2:1::]/64", "3ffe:505:2:1:ffff:ffff::" },
312 memset(&hints, 0, sizeof(hints));
313 hints.ai_family = AF_UNSPEC;
314 hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE | AI_ADDRCONFIG;
315 hints.ai_socktype = SOCK_STREAM;
317 for (item = alist; item->range; item++) {
319 printf("hnmatch() on %-30s <-- %-15s: ", item->range, item->ip);
321 if (getaddrinfo(item->ip, NULL, &hints, &info)==0 && info) {
322 if (info->ai_family == AF_INET) {
323 struct sockaddr_in *sa =
324 (struct sockaddr_in *) info->ai_addr;
325 memcpy(hostaddress, &(sa->sin_addr),
326 sizeof(sa->sin_addr));
328 else if (info->ai_family == AF_INET6) {
329 struct sockaddr_in6 *sa =
330 (struct sockaddr_in6 *) info->ai_addr;
331 memcpy(hostaddress, &(sa->sin6_addr),
332 sizeof(sa->sin6_addr));
334 hostfamily = info->ai_family;
336 printf("%s\n", hnmatch("dummy", item->range) ?
337 "match" : "mismatch");
340 printf("getaddrinfo() failed\n");
345 #endif /* MAIN_TEST_CHECKTTY */
347 static char *wdays[] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
349 /* example timespecs:
353 meaning monday, tuesday or wednesday between 8:00 and 17:59
357 meaning fridays from 4:00 to 5:59 and from 13:00 to 13:59
360 timeok(struct tm *t, char *spec)
369 while ((p = strsep(&sp, ":"))) {
370 if (*p >= '0' && *p <= '9') {
372 if (h == t->tm_hour) hourok = 1;
373 if ((q = strchr(p, '-')) && (q[1] >= '0' && q[1] <= '9')) {
375 if (h <= t->tm_hour && t->tm_hour <= h2) hourok = 1;
377 } else if (strcasecmp(wdays[t->tm_wday], p) == 0) {
382 return (dayok && hourok);
385 /* return true if tty equals class or is in the class defined by class.
386 Also return true if hostname matches the hostname pattern, class
387 or a pattern in the class named by class. */
389 in_class(const char *tty, char *class)
402 if (class[0] == '[') {
403 if ((p = strchr(class, ']'))) {
405 xstrncpy(timespec, class+1, sizeof(timespec));
407 if(!timeok(tm, timespec)) return 0;
410 /* really ought to warn about syntax error */
413 if (strcmp(tty, class) == 0) return 1;
415 if ((class[0] == '@') && isapty(tty)
416 && hnmatch(hostname, class+1)) return 1;
418 for (tc = ttyclasses; tc; tc = tc->next) {
419 if (strcmp(tc->classname, class) == 0) {
420 for (ge = tc->first; ge; ge = ge->next) {
424 if ((p = strchr(n, ']'))) {
426 xstrncpy(timespec, n+1, sizeof(timespec));
428 if(!timeok(tm, timespec)) continue;
431 /* really ought to warn about syntax error */
434 if (strcmp(n, tty) == 0) return 1;
436 if ((n[0] == '@') && isapty(tty)
437 && hnmatch(hostname, n+1)) return 1;
445 /* start JDS - SBA */
447 free_group(struct grplist *ge)
450 memset(ge->name, 0, NAMELEN);
451 free_group(ge->next);
458 free_class(struct ttyclass *tc)
461 memset(tc->classname, 0, CLASSNAMELEN);
462 free_group(tc->first);
464 free_class(tc->next);
473 free_class(ttyclasses);
479 checktty(const char *user, const char *tty, struct passwd *pwd)
482 char buf[256], defaultbuf[256];
484 enum State state = StateUsers;
487 /* no /etc/usertty, default to allow access */
488 if (!(f = fopen(_PATH_USERTTY, "r"))) return;
492 return; /* misspelled username handled elsewhere */
495 find_groups(pwd->pw_gid, user);
498 while(fgets(buf, 255, f)) {
501 for(ptr = buf; ptr < buf + 256; ptr++)
502 if(*ptr == '#') *ptr = 0;
505 xstrncpy(defaultbuf, buf, 256);
509 if (strncmp("GROUPS", buf, 6) == 0) {
512 } else if (strncmp("USERS", buf, 5) == 0) {
515 } else if (strncmp("CLASSES", buf, 7) == 0) {
516 state = StateClasses;
521 if((state == StateUsers && (strncmp(user, buf, 8) == 0))
522 || (state == StateGroups && am_in_group(buf))) {
523 found_match = 1; /* we found a line matching the user */
524 while((ptr = strtok(NULL, "\t\n "))) {
525 if (in_class(tty, ptr)) {
527 free_all(); /* JDS */
531 } else if (state == StateClasses) {
532 /* define a new tty/host class */
533 struct ttyclass *tc = new_class(buf);
535 while ((ptr = strtok(NULL, "\t\n "))) {
536 add_to_class(tc, ptr);
542 /* user is not explicitly mentioned in /etc/usertty, if there was
543 a default rule, use that */
545 strtok(defaultbuf, " \t");
546 while((ptr = strtok(NULL, "\t\n "))) {
547 if (in_class(tty, ptr)) {
548 free_all(); /* JDS */
553 /* there was a default rule, but user didn't match, reject! */
554 printf(_("Login on %s from %s denied by default.\n"), tty, hostname);
560 /* if we get here, /etc/usertty exists, there's a line
561 matching our username, but it doesn't contain the
562 name of the tty where the user is trying to log in.
565 printf(_("Login on %s from %s denied.\n"), tty, hostname);
570 /* users not matched in /etc/usertty are by default allowed access
572 free_all(); /* JDS */