diff --git a/etc/skey.access b/etc/skey.access index 22cdb69fe107..ad0245f6edb9 100644 --- a/etc/skey.access +++ b/etc/skey.access @@ -1,8 +1,30 @@ -# First word says if UNIX passwords are to be permitted or denied. -# remainder of the rule is a networknumber and mask. A rule matches a -# host if any of its addresses satisfies: -# -# network = (address & mask) -# -#what network mask -permit 0.0.0.0 0.0.0.0 +# This file controls whether UNIX passwords are to be permitted. Rules +# are matched in order, and the search terminates when the first matching +# rule has been found. +# +# Each rule has the form: +# +# permit condition condition... +# deny condition condition... +# +# Where "permit" or "deny" may be followed by zero or more conditions. +# +# A rule is matched when all conditions are satisfied. A rule without +# conditions is always satisfied. +# +# Examples of conditions are: +# +# hostname wzv.win.tue.nl +# internet 131.155.210.0 255.255.255.0 +# port ttya +# user root +# group wheel +# +# The old S/Key form (permit/deny netnumber netmask) is still supported. +# +#permit user uugiga # uucp login via modem or internet +#permit port ttyb # local +#permit port console # local +#deny # anything else + +permit # permit plaintext passwords all the time diff --git a/lib/libskey/Makefile b/lib/libskey/Makefile index ecf483333ce8..e9ce8beb0f96 100644 --- a/lib/libskey/Makefile +++ b/lib/libskey/Makefile @@ -1,7 +1,10 @@ # @(#)Makefile 5.4 (Berkeley) 5/7/91 LIB= skey -SRCS= authfile.c md4.c put.c skey_crypt.c skeylogin.c skeysubr.c -CFLAGS+=-DMPU8086 +SRCS= skeyaccess.c md4.c put.c skey_crypt.c skeylogin.c skeysubr.c +MAN5= skey.access.5 + +CFLAGS+=-DMPU8086 -DPERMIT_CONSOLE + .include diff --git a/lib/libskey/skey.access.5 b/lib/libskey/skey.access.5 new file mode 100644 index 000000000000..e92b4a66c3b6 --- /dev/null +++ b/lib/libskey/skey.access.5 @@ -0,0 +1,86 @@ +.TH SKEY.ACCESS 5 +.SH NAME +skey.access \- S/Key password control table +.SH DESCRIPTION +The S/Key password control table (default +.IR /etc/skey.access ) +is used by \fIlogin\fR-like programs to determine when UNIX passwords +may be used to access the system. +.IP \(bu +When the table does not exist, there are no password restrictions. The +user may enter the UNIX password or the S/Key one. +.IP \(bu +When the table does exist, UNIX passwords are permitted only when +explicitly specified. +.IP \(bu +For the sake of sanity, UNIX passwords are always permitted on the +systems console. +.SH "TABLE FORMAT" +The format of the table is one rule per line. Rules are matched in +order. The search terminates when the first matching rule is found, or +when the end of the table is reached. +.PP +Rules have the form: +.sp +.in +5 +permit condition condition... +.br +deny condition condition... +.in +.PP +where +.I permit +and +.I deny +may be followed by zero or more conditions. Comments begin with a `#\' +character, and extend through the end of the line. Empty lines or +lines with only comments are ignored. +.PP +A rule is matched when all conditions are satisfied. A rule without +conditions is always satisfied. For example, the last entry could +be a line with just the word +.I deny +on it. +.SH CONDITIONS +.IP "hostname wzv.win.tue.nl" +True when the login comes from host wzv.win.tue.nl. +.IP "internet 131.155.210.0 255.255.255.0" +True when the remote host has an internet address in network +131.155.210. The general form of a net/mask rule is: +.sp +.ti +5 +internet net mask +.sp +The expression is true when the host has an internet address for which +the bitwise and of +.I address +and +.I mask +equals +.IR net. +.IP "port ttya" +True when the login terminal is equal to +.IR /dev/ttya . +Remember that UNIX passwords are always permitted with logins on the +system console. +.IP "user uucp" +True when the user attempts to log in as +.IR uucp . +.IP "group wheel" +True when the user attempts to log in as a member of the +.I wheel +group. +.SH COMPATIBILITY +For the sake of backwards compatibility, the +.I internet +keyword may be omitted from net/mask patterns. +.SH DIAGNOSTICS +Syntax errors are reported to the syslogd. When an error is found +the rule is skipped. +.SH FILES +/etc/skey.access, password control table +.SH AUTHOR +.nf +Wietse Venema +Eindhoven University of Technology +The Netherlands diff --git a/lib/libskey/skeyaccess.c b/lib/libskey/skeyaccess.c new file mode 100644 index 000000000000..67ed549dce36 --- /dev/null +++ b/lib/libskey/skeyaccess.c @@ -0,0 +1,383 @@ + /* + * Figure out if UNIX passwords are permitted for any combination of user + * name, group member, terminal port, host_name or network: + * + * Programmatic interface: skeyaccess(char *user, char *port, char *host) + * + * Specify a null character pointer where information is not available. + * + * When compiled with -DPERMIT_CONSOLE always permits UNIX passwords with + * console logins, no matter what the configuration file says. + * + * To build a stand-alone test version, compile with -DTEST and run it off an + * skey.access file in the current directory: + * + * Command-line interface: ./skeyaccess user port [host] + * + * Author: Wietse Venema, Eindhoven University of Technology. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pathnames.h" + + /* + * Token input with one-deep pushback. + */ +static char *prev_token = 0; /* push-back buffer */ +static char *first_token(); +static int line_number; +static void unget_token(); +static char *get_token(); +static char *need_token(); +static char *need_internet_addr(); + + /* + * Various forms of token matching. + */ +#define match_host_name(l) match_token((l)->host_name) +#define match_port(l) match_token((l)->port) +#define match_user(l) match_token((l)->user) +static int match_internet_addr(); +static int match_group(); +static int match_token(); +static int is_internet_addr(); + +#define MAX_ADDR 32 +#define PERMIT 1 +#define DENY 0 + +struct login_info { + char *host_name; /* host name */ + struct in_addr *internet_addr; /* null terminated list */ + char *user; /* user name */ + char *port; /* login port */ +}; + +/* skeyaccess - find out if UNIX passwords are permitted */ + +int skeyaccess(user, port, host) +char *user; +char *port; +char *host; +{ + struct hostent *hp; + struct login_info login_info; + struct in_addr internet_addr[MAX_ADDR + 1]; + char hostname_buf[MAXHOSTNAMELEN + 1]; + int i; + + login_info.user = user; + login_info.port = port; + login_info.host_name = 0; + login_info.internet_addr = 0; + + if (host) { + if (is_internet_addr(host)) { /* not DECnet */ + internet_addr[0].s_addr = inet_addr(host); + internet_addr[1].s_addr = 0; + login_info.internet_addr = internet_addr; + } else { + if ((hp = gethostbyname(host)) != 0 && hp->h_addrtype == AF_INET) { + for (i = 0; i < MAX_ADDR && hp->h_addr_list[i]; i++) + memcpy((char *) &internet_addr[i], + hp->h_addr_list[i], hp->h_length); + internet_addr[i].s_addr = 0; + login_info.internet_addr = internet_addr; + host = hp->h_name; + } + strncpy(hostname_buf, host, MAXHOSTNAMELEN); + hostname_buf[MAXHOSTNAMELEN] = 0; + login_info.host_name = hostname_buf; + } + } + + /* + * Print what we think the user wants us to do. + */ +#ifdef TEST + printf("port: %s\n", login_info.port); + printf("user: %s\n", login_info.user); + printf("host: %s\n", login_info.host_name ? login_info.host_name : "none"); + printf("addr: "); + if (login_info.internet_addr == 0) { + printf("none\n"); + } else { + for (i = 0; login_info.internet_addr[i].s_addr; i++) + printf("%s%s", inet_ntoa(login_info.internet_addr[i]), + login_info.internet_addr[i + 1].s_addr ? " " : "\n"); + } +#endif + return (_skeyaccess(&login_info)); +} + +/* _skeyaccess - find out if UNIX passwords are permitted */ + +int _skeyaccess(login_info) +struct login_info *login_info; +{ + FILE *fp; + char buf[BUFSIZ]; + char *tok; + int match; + int permission; + +#ifdef PERMIT_CONSOLE + if (login_info->port != 0 && strcasecmp(login_info->port, "console") == 0) + return (1); +#endif + + /* + * Assume no restriction on the use of UNIX passwords when the s/key + * acces table does not exist. + */ + if ((fp = fopen(_PATH_SKEYACCESS, "r")) == 0) + return (PERMIT); + + /* + * Scan the s/key access table until we find an entry that matches. If no + * match is found, assume that UNIX passwords are disallowed. + */ + match = 0; + while (match == 0 && (tok = first_token(buf, sizeof(buf), fp))) { + if (strncasecmp(tok, "permit", 4) == 0) { + permission = PERMIT; + } else if (strncasecmp(tok, "deny", 4) == 0) { + permission = DENY; + } else { + syslog(LOG_ERR, "%s: line %d: bad permission: %s", + _PATH_SKEYACCESS, line_number, tok); + continue; /* error */ + } + + /* + * Process all conditions in this entry until we find one that fails. + */ + match = 1; + while (match != 0 && (tok = get_token())) { + if (strcasecmp(tok, "hostname") == 0) { + match = match_host_name(login_info); + } else if (strcasecmp(tok, "port") == 0) { + match = match_port(login_info); + } else if (strcasecmp(tok, "user") == 0) { + match = match_user(login_info); + } else if (strcasecmp(tok, "group") == 0) { + match = match_group(login_info); + } else if (strcasecmp(tok, "internet") == 0) { + match = match_internet_addr(login_info); + } else if (is_internet_addr(tok)) { + unget_token(tok); + match = match_internet_addr(login_info); + } else { + syslog(LOG_ERR, "%s: line %d: bad condition: %s", + _PATH_SKEYACCESS, line_number, tok); + match = 0; + } + } + } + fclose(fp); + return (match ? permission : DENY); +} + +/* match_internet_addr - match internet network address */ + +static int match_internet_addr(login_info) +struct login_info *login_info; +{ + char *tok; + long pattern; + long mask; + struct in_addr *addrp; + struct hostent *hp; + + if (login_info->internet_addr == 0) + return (0); + if ((tok = need_internet_addr()) == 0) + return (0); + pattern = inet_addr(tok); + if ((tok = need_internet_addr()) == 0) + return (0); + mask = inet_addr(tok); + + /* + * See if any of the addresses matches a pattern in the control file. + * Report and skip the address if it does not belong to the remote host. + * Assume localhost == localhost.domain. + */ + +#define NEQ(x,y) (strcasecmp((x),(y)) != 0) + + for (addrp = login_info->internet_addr; addrp->s_addr; addrp++) { + if ((addrp->s_addr & mask) == pattern) { + if (login_info->host_name != 0 && + ((hp = gethostbyaddr((char *) addrp, sizeof(*addrp), AF_INET)) == 0 + || (NEQ(login_info->host_name, hp->h_name) + && NEQ(login_info->host_name, "localhost")))) { + syslog(LOG_ERR, "address %s not registered for host %s", + inet_ntoa(*addrp), login_info->host_name); + continue; + } + return (1); + } + } + return (0); +} + +/* match_group - match username against group */ + +static int match_group(login_info) +struct login_info *login_info; +{ + struct group *group; + char *tok; + char **memp; + + if ((tok = need_token()) && (group = getgrnam(tok))) { + for (memp = group->gr_mem; *memp; memp++) + if (strcmp(login_info->user, *memp) == 0) + return (1); + } + return (0); /* XXX endgrent() */ +} + +/* match_token - get and match token */ + +static int match_token(str) +char *str; +{ + char *tok; + + return (str && (tok = need_token()) && strcasecmp(str, tok) == 0); +} + +/* first_token - read line and return first token */ + +static char *first_token(buf, len, fp) +char *buf; +int len; +FILE *fp; +{ + char *cp; + + prev_token = 0; + for (;;) { + if (fgets(buf, len, fp) == 0) + return (0); + line_number++; + buf[strcspn(buf, "\r\n#")] = 0; +#ifdef TEST + if (buf[0]) + printf("rule: %s\n", buf); +#endif + if (cp = strtok(buf, " \t")) + return (cp); + } +} + +/* unget_token - push back last token */ + +static void unget_token(cp) +char *cp; +{ + prev_token = cp; +} + +/* get_token - retrieve next token from buffer */ + +static char *get_token() +{ + char *cp; + + if (cp = prev_token) { + prev_token = 0; + } else { + cp = strtok((char *) 0, " \t"); + } + return (cp); +} + +/* need_token - complain if next token is not available */ + +static char *need_token() +{ + char *cp; + + if ((cp = get_token()) == 0) + syslog(LOG_ERR, "%s: line %d: premature end of rule", + _PATH_SKEYACCESS, line_number); + return (cp); +} + +/* need_internet_addr - complain if next token is not an internet address */ + +static char *need_internet_addr() +{ + char *cp; + + if ((cp = get_token()) == 0) { + syslog(LOG_ERR, "%s: line %d: internet address expected", + _PATH_SKEYACCESS, line_number); + return (0); + } else if (!is_internet_addr(cp)) { + syslog(LOG_ERR, "%s: line %d: bad internet address: %s", + _PATH_SKEYACCESS, line_number, cp); + return (0); + } else { + return (cp); + } +} + +/* is_internet_addr - determine if string is a dotted quad decimal address */ + +static int is_internet_addr(str) +char *str; +{ + int in_run = 0; + int runs = 0; + + /* Count the number of runs of characters between the dots. */ + + while (*str) { + if (*str == '.') { + in_run = 0; + } else { + if (!isdigit(*str)) + return (0); + if (in_run == 0) { + in_run = 1; + runs++; + } + } + str++; + } + return (runs == 4); +} + +#ifdef TEST + +main(argc, argv) +int argc; +char **argv; +{ + if (argc != 3 && argc != 4) { + fprintf(stderr, "usage: %s user port [host_or_ip_address]\n", argv[0]); + exit(0); + } + openlog("login", LOG_PID, LOG_AUTH); + printf("%s\n", skeyaccess(argv[1], argv[2], argv[3]) ? "YES" : "NO"); + return (0); +} + +#endif