| /* 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@misiek.eu.org> |
| - 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 "nls.h" |
| |
| #ifdef __linux__ |
| # include <sys/sysmacros.h> |
| # include <linux/major.h> |
| #endif |
| |
| #include "pathnames.h" |
| |
| /* functions in login.c */ |
| void badlogin(const char *s); |
| void sleepexit(int); |
| extern struct hostent hostaddress; |
| extern char *hostname; |
| |
| #ifdef TESTING |
| struct hostent hostaddress; |
| char *hostname; |
| |
| void |
| badlogin(const char *s) |
| { |
| printf(_("badlogin: %s\n"), s); |
| } |
| |
| void |
| sleepexit(int x) |
| { |
| printf(_("sleepexit %d\n"), x); |
| exit(1); |
| } |
| #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; |
| strncpy(tc->classname, class, CLASSNAMELEN); |
| tc->classname[CLASSNAMELEN-1] = 0; |
| 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; |
| strncpy(ge->name, tty, NAMELEN); |
| ge->name[NAMELEN-1] = 0; |
| tc->first = ge; |
| } |
| |
| |
| /* return true if tty is a pty. Very linux dependent */ |
| static int |
| isapty(const char *tty) |
| { |
| 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 defined(__linux__) |
| 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 |
| return 0; |
| } |
| |
| /* match the hostname hn against the pattern pat */ |
| static int |
| hnmatch(const char *hn, const char *pat) |
| { |
| int x1, x2, x3, x4, y1, y2, y3, y4; |
| unsigned long p, mask, a; |
| unsigned char *ha; |
| int n, m; |
| |
| if ((hn == NULL) && (strcmp(pat, "localhost") == 0)) return 1; |
| if ((hn == NULL) || hn[0] == 0) return 0; |
| |
| if (pat[0] >= '0' && pat[0] <= '9') { |
| /* pattern is an IP QUAD address and a mask x.x.x.x/y.y.y.y */ |
| sscanf(pat, "%d.%d.%d.%d/%d.%d.%d.%d", &x1, &x2, &x3, &x4, |
| &y1, &y2, &y3, &y4); |
| 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.h_addr_list || !hostaddress.h_addr_list[0]) |
| return 0; |
| |
| ha = (unsigned char *)hostaddress.h_addr_list[0]; |
| 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)); |
| } else { |
| /* pattern is a suffix of a FQDN */ |
| n = strlen(pat); |
| m = strlen(hn); |
| if (n > m) return 0; |
| return (strcasecmp(pat, hn + m - n) == 0); |
| } |
| } |
| |
| 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; |
| strncpy(timespec, class+1, sizeof(timespec)); |
| timespec[sizeof(timespec)-1] = 0; |
| *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; |
| strncpy(timespec, n+1, sizeof(timespec)); |
| timespec[sizeof(timespec)-1] = 0; |
| *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 */ |
| void |
| free_group(struct grplist *ge) |
| { |
| if (ge) { |
| memset(ge->name, 0, NAMELEN); |
| free_group(ge->next); |
| free(ge->next); |
| ge->next = NULL; |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| 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 */ |
| #ifdef TESTING |
| if (!(f = fopen("usertty", "r"))) return; |
| #else |
| if (!(f = fopen(_PATH_USERTTY, "r"))) return; |
| #endif |
| |
| 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] == '*') { |
| strncpy(defaultbuf, buf, 256); |
| defaultbuf[255] = 0; |
| 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 */ |
| } |
| |
| #ifdef TESTING |
| main(int argc, char *argv[]) |
| { |
| struct passwd *pw; |
| |
| pw = getpwnam(argv[1]); |
| checktty(argv[1], argv[2], pw); |
| } |
| #endif |