| /* checktty.c - linked into login, checks user against /etc/usertty |
| Created 25-Aug-95 by Peter Orbaek <poe@daimi.aau.dk> |
| Fixed by JDS June 1996 to clear lists and close files |
| |
| 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> |
| - added Native Language Support |
| |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/param.h> |
| |
| #include <pwd.h> |
| #include <grp.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <time.h> |
| #include <sys/stat.h> |
| #include <malloc.h> |
| #include <netdb.h> |
| #include <sys/syslog.h> |
| #include <ctype.h> |
| #include "nls.h" |
| |
| #include <sys/sysmacros.h> |
| #ifdef HAVE_LINUX_MAJOR_H |
| #include <linux/major.h> |
| #endif |
| |
| #include "pathnames.h" |
| #include "login.h" |
| #include "xstrncpy.h" |
| |
| #ifndef TTY_MAJOR |
| #define TTY_MAJOR 4 |
| #endif |
| |
| static gid_t mygroups[NGROUPS]; |
| static int num_groups; |
| |
| #define NAMELEN 128 |
| |
| /* linked list of names */ |
| struct grplist { |
| struct grplist *next; |
| char name[NAMELEN]; |
| }; |
| |
| enum State { StateUsers, StateGroups, StateClasses }; |
| |
| #define CLASSNAMELEN 32 |
| |
| struct ttyclass { |
| struct grplist *first; |
| struct ttyclass *next; |
| char classname[CLASSNAMELEN]; |
| }; |
| |
| struct ttyclass *ttyclasses = NULL; |
| |
| static int |
| am_in_group(char *group) |
| { |
| struct group *g; |
| gid_t *ge; |
| |
| g = getgrnam(group); |
| if (g) { |
| for (ge = mygroups; ge < mygroups + num_groups; ge++) { |
| if (g->gr_gid== *ge) return 1; |
| } |
| } |
| return 0; |
| } |
| |
| static void |
| find_groups(gid_t defgrp, const char *user) |
| { |
| num_groups = getgroups(NGROUPS, mygroups); |
| } |
| |
| static struct ttyclass * |
| new_class(char *class) |
| { |
| struct ttyclass *tc; |
| |
| tc = (struct ttyclass *)malloc(sizeof(struct ttyclass)); |
| if (tc == NULL) { |
| printf(_("login: memory low, login may fail\n")); |
| syslog(LOG_WARNING, _("can't malloc for ttyclass")); |
| return NULL; |
| } |
| |
| tc->next = ttyclasses; |
| tc->first = NULL; |
| xstrncpy(tc->classname, class, CLASSNAMELEN); |
| ttyclasses = tc; |
| return tc; |
| } |
| |
| static void |
| add_to_class(struct ttyclass *tc, char *tty) |
| { |
| struct grplist *ge; |
| |
| if (tc == NULL) return; |
| |
| ge = (struct grplist *)malloc(sizeof(struct grplist)); |
| if (ge == NULL) { |
| printf(_("login: memory low, login may fail\n")); |
| syslog(LOG_WARNING, _("can't malloc for grplist")); |
| return; |
| } |
| |
| ge->next = tc->first; |
| xstrncpy(ge->name, tty, NAMELEN); |
| tc->first = ge; |
| } |
| |
| |
| /* return true if tty is a pty. Very linux dependent */ |
| static int |
| isapty(const char *tty) |
| { |
| #ifdef __linux__ |
| char devname[100]; |
| struct stat stb; |
| |
| /* avoid snprintf - old systems do not have it */ |
| if (strlen(tty) + 6 > sizeof(devname)) |
| return 0; |
| sprintf(devname, "/dev/%s", tty); |
| |
| if((stat(devname, &stb) >= 0) && S_ISCHR(stb.st_mode)) { |
| int majordev = major(stb.st_rdev); |
| |
| /* this is for linux versions before 1.3: use major 4 */ |
| if(majordev == TTY_MAJOR && minor(stb.st_rdev) >= 192) |
| return 1; |
| |
| #if defined(PTY_SLAVE_MAJOR) |
| /* this is for linux 1.3 and newer: use major 3 */ |
| if(majordev == PTY_SLAVE_MAJOR) |
| return 1; |
| #endif |
| |
| #if defined(UNIX98_PTY_SLAVE_MAJOR) && defined(UNIX98_PTY_MAJOR_COUNT) |
| /* this is for linux 2.1.116 and newer: use majors 136-143 */ |
| if(majordev >= UNIX98_PTY_SLAVE_MAJOR && |
| majordev < UNIX98_PTY_SLAVE_MAJOR + UNIX98_PTY_MAJOR_COUNT) |
| return 1; |
| #endif |
| |
| } |
| #endif /* __linux__ */ |
| return 0; |
| } |
| |
| |
| /* IPv4 -- pattern is x.x.x.x/y.y.y.y (net/mask)*/ |
| static int |
| hnmatch_ip4(const char *pat) |
| { |
| int x1, x2, x3, x4, y1, y2, y3, y4; |
| unsigned long p, mask, a; |
| unsigned char *ha; |
| |
| /* pattern is an IP QUAD address and a mask x.x.x.x/y.y.y.y */ |
| if (sscanf(pat, "%d.%d.%d.%d/%d.%d.%d.%d", |
| &x1, &x2, &x3, &x4, &y1, &y2, &y3, &y4) < 8) |
| return 0; |
| |
| p = (((unsigned long)x1<<24)+((unsigned long)x2<<16) |
| +((unsigned long)x3<<8)+((unsigned long)x4)); |
| mask = (((unsigned long)y1<<24)+((unsigned long)y2<<16) |
| +((unsigned long)y3<<8)+((unsigned long)y4)); |
| |
| if (hostaddress[0] == 0) |
| return 0; |
| |
| ha = (unsigned char *)hostaddress; |
| a = (((unsigned long)ha[0]<<24)+((unsigned long)ha[1]<<16) |
| +((unsigned long)ha[2]<<8)+((unsigned long)ha[3])); |
| return ((p & mask) == (a & mask)); |
| } |
| |
| /* IPv6 -- pattern is [hex:hex:....]/number ([net]/mask) */ |
| static int |
| hnmatch_ip6(const char *pat) |
| { |
| char *patnet; |
| char *patmask; |
| struct in6_addr addr; |
| struct addrinfo hints, *res; |
| struct sockaddr_in6 net; |
| int mask_len, i = 0; |
| char *p; |
| |
| if (pat == NULL || *pat != '[') |
| return 0; |
| |
| memcpy(&addr, hostaddress, sizeof(addr)); |
| |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_INET6; |
| hints.ai_socktype = SOCK_STREAM; |
| hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; |
| |
| patnet = strdup(pat); |
| |
| /* match IPv6 address against [netnumber]/prefixlen */ |
| if (!(p = strchr(patnet, ']'))) |
| goto mismatch; |
| |
| *p++ = '\0'; |
| |
| if (! (*p == '/' && isdigit((unsigned char) *(p + 1)))) |
| goto mismatch; |
| |
| patmask = p + 1; |
| |
| /* prepare net address */ |
| if (getaddrinfo(patnet + 1, NULL, &hints, &res) != 0) |
| goto mismatch; |
| |
| memcpy(&net, res->ai_addr, sizeof(net)); |
| freeaddrinfo(res); |
| |
| /* convert mask to number */ |
| if ((mask_len = atoi(patmask)) < 0 || mask_len > 128) |
| goto mismatch; |
| |
| /* compare */ |
| while (mask_len > 0) { |
| if (mask_len < 32) { |
| u_int32_t mask = htonl(~(0xffffffff >> mask_len)); |
| |
| if ((*(u_int32_t *)&addr.s6_addr[i] & mask) != |
| (*(u_int32_t *)&net.sin6_addr.s6_addr[i] & mask)) |
| goto mismatch; |
| break; |
| } |
| if (*(u_int32_t *)&addr.s6_addr[i] != |
| *(u_int32_t *)&net.sin6_addr.s6_addr[i]) |
| goto mismatch; |
| i += 4; |
| mask_len -= 32; |
| } |
| |
| free(patnet); |
| return 1; |
| |
| mismatch: |
| free(patnet); |
| return 0; |
| } |
| |
| /* match the hostname hn against the pattern pat */ |
| static int |
| hnmatch(const char *hn, const char *pat) |
| { |
| |
| if ((hn == NULL) && (strcmp(pat, "localhost") == 0)) |
| return 1; |
| if ((hn == NULL) || *hn == '\0') |
| return 0; |
| |
| if (*pat >= '0' && *pat <= '9') |
| return hostfamily == AF_INET ? hnmatch_ip4(pat) : 0; |
| else if (*pat == '[') |
| return hostfamily == AF_INET6 ? hnmatch_ip6(pat) : 0; |
| else { |
| /* pattern is a suffix of a FQDN */ |
| int n = strlen(pat), |
| m = strlen(hn); |
| |
| if (n > m) |
| return 0; |
| return (strcasecmp(pat, hn + m - n) == 0); |
| } |
| } |
| |
| #ifdef MAIN_TEST_CHECKTTY |
| |
| char hostaddress[16]; |
| sa_family_t hostfamily; |
| char *hostname; |
| void sleepexit(int eval) {} /* dummy for this test */ |
| void badlogin(const char *s) {} /* dummy for this test */ |
| |
| int |
| main(int argc, char **argv) |
| { |
| struct addrinfo hints, *info = NULL; |
| struct addrexp { |
| const char *range; |
| const char *ip; |
| } alist[] = { |
| { "130.225.16.0/255.255.254.0", "130.225.16.1" }, |
| { "130.225.16.0/255.255.254.0", "10.20.30.1" }, |
| { "130.225.0.0/255.254.0.0", "130.225.16.1" }, |
| { "130.225.0.0/255.254.0.0", "130.225.17.1" }, |
| { "130.225.0.0/255.254.0.0", "150.160.170.180" }, |
| { "[3ffe:505:2:1::]/64", "3ffe:505:2:1::" }, |
| { "[3ffe:505:2:1::]/64", "3ffe:505:2:2::" }, |
| { "[3ffe:505:2:1::]/64", "3ffe:505:2:1:ffff:ffff::" }, |
| { NULL, NULL } |
| }, *item; |
| |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE | AI_ADDRCONFIG; |
| hints.ai_socktype = SOCK_STREAM; |
| |
| for (item = alist; item->range; item++) { |
| |
| printf("hnmatch() on %-30s <-- %-15s: ", item->range, item->ip); |
| |
| if (getaddrinfo(item->ip, NULL, &hints, &info)==0 && info) { |
| if (info->ai_family == AF_INET) { |
| struct sockaddr_in *sa = |
| (struct sockaddr_in *) info->ai_addr; |
| memcpy(hostaddress, &(sa->sin_addr), |
| sizeof(sa->sin_addr)); |
| } |
| else if (info->ai_family == AF_INET6) { |
| struct sockaddr_in6 *sa = |
| (struct sockaddr_in6 *) info->ai_addr; |
| memcpy(hostaddress, &(sa->sin6_addr), |
| sizeof(sa->sin6_addr)); |
| } |
| hostfamily = info->ai_family; |
| freeaddrinfo(info); |
| printf("%s\n", hnmatch("dummy", item->range) ? |
| "match" : "mismatch"); |
| } |
| else |
| printf("getaddrinfo() failed\n"); |
| |
| } |
| return 0; |
| } |
| #endif /* MAIN_TEST_CHECKTTY */ |
| |
| static char *wdays[] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; |
| |
| /* example timespecs: |
| |
| mon:tue:wed:8-17 |
| |
| meaning monday, tuesday or wednesday between 8:00 and 17:59 |
| |
| 4:5:13:fri |
| |
| meaning fridays from 4:00 to 5:59 and from 13:00 to 13:59 |
| */ |
| static int |
| timeok(struct tm *t, char *spec) |
| { |
| char *p, *q; |
| int dayok = 0; |
| int hourok = 0; |
| int h, h2; |
| char *sp; |
| |
| sp = spec; |
| while ((p = strsep(&sp, ":"))) { |
| if (*p >= '0' && *p <= '9') { |
| h = atoi(p); |
| if (h == t->tm_hour) hourok = 1; |
| if ((q = strchr(p, '-')) && (q[1] >= '0' && q[1] <= '9')) { |
| h2 = atoi(q+1); |
| if (h <= t->tm_hour && t->tm_hour <= h2) hourok = 1; |
| } |
| } else if (strcasecmp(wdays[t->tm_wday], p) == 0) { |
| dayok = 1; |
| } |
| } |
| |
| return (dayok && hourok); |
| } |
| |
| /* return true if tty equals class or is in the class defined by class. |
| Also return true if hostname matches the hostname pattern, class |
| or a pattern in the class named by class. */ |
| static int |
| in_class(const char *tty, char *class) |
| { |
| struct ttyclass *tc; |
| struct grplist *ge; |
| time_t t; |
| char *p; |
| char timespec[256]; |
| struct tm *tm; |
| char *n; |
| |
| time(&t); |
| tm = localtime(&t); |
| |
| if (class[0] == '[') { |
| if ((p = strchr(class, ']'))) { |
| *p = 0; |
| xstrncpy(timespec, class+1, sizeof(timespec)); |
| *p = ']'; |
| if(!timeok(tm, timespec)) return 0; |
| class = p+1; |
| } |
| /* really ought to warn about syntax error */ |
| } |
| |
| if (strcmp(tty, class) == 0) return 1; |
| |
| if ((class[0] == '@') && isapty(tty) |
| && hnmatch(hostname, class+1)) return 1; |
| |
| for (tc = ttyclasses; tc; tc = tc->next) { |
| if (strcmp(tc->classname, class) == 0) { |
| for (ge = tc->first; ge; ge = ge->next) { |
| |
| n = ge->name; |
| if (n[0] == '[') { |
| if ((p = strchr(n, ']'))) { |
| *p = 0; |
| xstrncpy(timespec, n+1, sizeof(timespec)); |
| *p = ']'; |
| if(!timeok(tm, timespec)) continue; |
| n = p+1; |
| } |
| /* really ought to warn about syntax error */ |
| } |
| |
| if (strcmp(n, tty) == 0) return 1; |
| |
| if ((n[0] == '@') && isapty(tty) |
| && hnmatch(hostname, n+1)) return 1; |
| } |
| return 0; |
| } |
| } |
| return 0; |
| } |
| |
| /* start JDS - SBA */ |
| static void |
| free_group(struct grplist *ge) |
| { |
| if (ge) { |
| memset(ge->name, 0, NAMELEN); |
| free_group(ge->next); |
| free(ge->next); |
| ge->next = NULL; |
| } |
| } |
| |
| static void |
| free_class(struct ttyclass *tc) |
| { |
| if (tc) { |
| memset(tc->classname, 0, CLASSNAMELEN); |
| free_group(tc->first); |
| tc->first = NULL; |
| free_class(tc->next); |
| free(tc->next); |
| tc->next = NULL; |
| } |
| } |
| |
| static void |
| free_all(void) |
| { |
| free_class(ttyclasses); |
| ttyclasses = NULL; |
| } |
| /* end JDS - SBA */ |
| |
| void |
| checktty(const char *user, const char *tty, struct passwd *pwd) |
| { |
| FILE *f; |
| char buf[256], defaultbuf[256]; |
| char *ptr; |
| enum State state = StateUsers; |
| int found_match = 0; |
| |
| /* no /etc/usertty, default to allow access */ |
| if (!(f = fopen(_PATH_USERTTY, "r"))) return; |
| |
| if (pwd == NULL) { |
| fclose(f); |
| return; /* misspelled username handled elsewhere */ |
| } |
| |
| find_groups(pwd->pw_gid, user); |
| |
| defaultbuf[0] = 0; |
| while(fgets(buf, 255, f)) { |
| |
| /* strip comments */ |
| for(ptr = buf; ptr < buf + 256; ptr++) |
| if(*ptr == '#') *ptr = 0; |
| |
| if (buf[0] == '*') { |
| xstrncpy(defaultbuf, buf, 256); |
| continue; |
| } |
| |
| if (strncmp("GROUPS", buf, 6) == 0) { |
| state = StateGroups; |
| continue; |
| } else if (strncmp("USERS", buf, 5) == 0) { |
| state = StateUsers; |
| continue; |
| } else if (strncmp("CLASSES", buf, 7) == 0) { |
| state = StateClasses; |
| continue; |
| } |
| |
| strtok(buf, " \t"); |
| if((state == StateUsers && (strncmp(user, buf, 8) == 0)) |
| || (state == StateGroups && am_in_group(buf))) { |
| found_match = 1; /* we found a line matching the user */ |
| while((ptr = strtok(NULL, "\t\n "))) { |
| if (in_class(tty, ptr)) { |
| fclose(f); |
| free_all(); /* JDS */ |
| return; |
| } |
| } |
| } else if (state == StateClasses) { |
| /* define a new tty/host class */ |
| struct ttyclass *tc = new_class(buf); |
| |
| while ((ptr = strtok(NULL, "\t\n "))) { |
| add_to_class(tc, ptr); |
| } |
| } |
| } |
| fclose(f); |
| |
| /* user is not explicitly mentioned in /etc/usertty, if there was |
| a default rule, use that */ |
| if (defaultbuf[0]) { |
| strtok(defaultbuf, " \t"); |
| while((ptr = strtok(NULL, "\t\n "))) { |
| if (in_class(tty, ptr)) { |
| free_all(); /* JDS */ |
| return; |
| } |
| } |
| |
| /* there was a default rule, but user didn't match, reject! */ |
| printf(_("Login on %s from %s denied by default.\n"), tty, hostname); |
| badlogin(user); |
| sleepexit(1); |
| } |
| |
| if (found_match) { |
| /* if we get here, /etc/usertty exists, there's a line |
| matching our username, but it doesn't contain the |
| name of the tty where the user is trying to log in. |
| So deny access! */ |
| |
| printf(_("Login on %s from %s denied.\n"), tty, hostname); |
| badlogin(user); |
| sleepexit(1); |
| } |
| |
| /* users not matched in /etc/usertty are by default allowed access |
| on all tty's */ |
| free_all(); /* JDS */ |
| } |