mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-15 10:17:20 +00:00
015136259e
using `internet' keyword into /etc/skey.access. Not Objected to by: -audit and -net
589 lines
15 KiB
C
589 lines
15 KiB
C
/*
|
|
* Figure out if UNIX passwords are permitted for any combination of user
|
|
* name, group member, terminal port, host_name or network:
|
|
*
|
|
* Programmatic interface: skeyaccess(user, port, host, addr)
|
|
*
|
|
* All arguments are null-terminated strings. Specify a null character pointer
|
|
* where information is not available.
|
|
*
|
|
* When no address information is given this code performs the host (internet)
|
|
* address lookup itself. It rejects addresses that appear to belong to
|
|
* someone else.
|
|
*
|
|
* 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_or_ip_addr]
|
|
*
|
|
* Errors are reported via syslogd.
|
|
*
|
|
* Author: Wietse Venema, Eindhoven University of Technology.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <string.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#include <stdio.h>
|
|
#include <grp.h>
|
|
#include <ctype.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <sys/param.h>
|
|
|
|
#include "pathnames.h"
|
|
|
|
/*
|
|
* Token input with one-deep pushback.
|
|
*/
|
|
static char *prev_token = 0; /* push-back buffer */
|
|
static char *line_pointer = NULL;
|
|
static char *first_token __P((char *, int, FILE *));
|
|
static int line_number;
|
|
static void unget_token __P((char *));
|
|
static char *get_token __P((void));
|
|
static char *need_token __P((void));
|
|
static char *need_internet_addr __P((void));
|
|
|
|
/*
|
|
* 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)
|
|
struct login_info;
|
|
static int match_internet_addr __P((struct login_info *));
|
|
static int match_group __P((struct login_info *));
|
|
static int match_token __P((char *));
|
|
static int is_internet_addr __P((char *));
|
|
static struct addrinfo *convert_internet_addr __P((char *));
|
|
static struct addrinfo *lookup_internet_addr __P((char *));
|
|
|
|
#define MAX_ADDR 32
|
|
#define PERMIT 1
|
|
#define DENY 0
|
|
|
|
#ifndef CONSOLE
|
|
#define CONSOLE "console"
|
|
#endif
|
|
#ifndef VTY_PREFIX
|
|
#define VTY_PREFIX "ttyv"
|
|
#endif
|
|
|
|
struct login_info {
|
|
char *host_name; /* host name */
|
|
struct addrinfo *internet_addr; /* addrinfo chain */
|
|
char *user; /* user name */
|
|
char *port; /* login port */
|
|
};
|
|
|
|
static int _skeyaccess __P((FILE *, struct login_info *));
|
|
int skeyaccess __P((char *, char *, char *, char *));
|
|
|
|
/* skeyaccess - find out if UNIX passwords are permitted */
|
|
|
|
int skeyaccess(user, port, host, addr)
|
|
char *user;
|
|
char *port;
|
|
char *host;
|
|
char *addr;
|
|
{
|
|
FILE *fp;
|
|
struct login_info login_info;
|
|
int result;
|
|
|
|
/*
|
|
* 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) {
|
|
#ifdef TEST
|
|
fprintf(stderr, "No file %s, thus no access control\n", _PATH_SKEYACCESS);
|
|
#endif
|
|
return (PERMIT);
|
|
}
|
|
|
|
/*
|
|
* Bundle up the arguments in a structure so we won't have to drag around
|
|
* boring long argument lists.
|
|
*
|
|
* Look up the host address when only the name is given. We try to reject
|
|
* addresses that belong to someone else.
|
|
*/
|
|
login_info.user = user;
|
|
login_info.port = port;
|
|
|
|
if (host != NULL && !is_internet_addr(host)) {
|
|
login_info.host_name = host;
|
|
} else {
|
|
login_info.host_name = NULL;
|
|
}
|
|
|
|
if (addr != NULL && is_internet_addr(addr)) {
|
|
login_info.internet_addr = convert_internet_addr(addr);
|
|
} else if (host != NULL) {
|
|
if (is_internet_addr(host)) {
|
|
login_info.internet_addr = convert_internet_addr(host);
|
|
} else {
|
|
login_info.internet_addr = lookup_internet_addr(host);
|
|
}
|
|
} else {
|
|
login_info.internet_addr = NULL;
|
|
}
|
|
|
|
/*
|
|
* 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 == NULL) {
|
|
printf("none\n");
|
|
} else {
|
|
struct addrinfo *res;
|
|
char haddr[NI_MAXHOST];
|
|
|
|
for (res = login_info.internet_addr; res; res = res->ai_next) {
|
|
getnameinfo(res->ai_addr, res->ai_addrlen, haddr, sizeof(haddr),
|
|
NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
|
|
printf("%s%s", haddr, res->ai_next ? " " : "\n");
|
|
}
|
|
}
|
|
#endif
|
|
result = _skeyaccess(fp, &login_info);
|
|
fclose(fp);
|
|
if (login_info.internet_addr)
|
|
freeaddrinfo(login_info.internet_addr);
|
|
return (result);
|
|
}
|
|
|
|
/* _skeyaccess - find out if UNIX passwords are permitted */
|
|
|
|
static int _skeyaccess(fp, login_info)
|
|
FILE *fp;
|
|
struct login_info *login_info;
|
|
{
|
|
char buf[BUFSIZ];
|
|
char *tok;
|
|
int match;
|
|
int permission=DENY;
|
|
|
|
#ifdef PERMIT_CONSOLE
|
|
if (login_info->port != 0 &&
|
|
(strcmp(login_info->port, CONSOLE) == 0 ||
|
|
strncmp(login_info->port, VTY_PREFIX, sizeof(VTY_PREFIX) - 1) == 0
|
|
)
|
|
)
|
|
return (1);
|
|
#endif
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
return (match ? permission : DENY);
|
|
}
|
|
|
|
/* translate IPv4 mapped IPv6 address to IPv4 address */
|
|
|
|
static void
|
|
ai_unmapped(struct addrinfo *ai)
|
|
{
|
|
struct sockaddr_in6 *sin6;
|
|
struct sockaddr_in *sin4;
|
|
u_int32_t addr;
|
|
int port;
|
|
|
|
if (ai->ai_family != AF_INET6)
|
|
return;
|
|
sin6 = (struct sockaddr_in6 *)ai->ai_addr;
|
|
if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
|
|
return;
|
|
sin4 = (struct sockaddr_in *)ai->ai_addr;
|
|
addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
|
|
port = sin6->sin6_port;
|
|
memset(sin4, 0, sizeof(struct sockaddr_in));
|
|
sin4->sin_addr.s_addr = addr;
|
|
sin4->sin_port = port;
|
|
sin4->sin_family = AF_INET;
|
|
sin4->sin_len = sizeof(struct sockaddr_in);
|
|
ai->ai_family = AF_INET;
|
|
ai->ai_addrlen = sizeof(struct sockaddr_in);
|
|
}
|
|
|
|
/* match_internet_addr - match internet network address */
|
|
|
|
static int match_internet_addr(login_info)
|
|
struct login_info *login_info;
|
|
{
|
|
char *tok;
|
|
struct addrinfo *res;
|
|
struct sockaddr_storage pattern, mask;
|
|
struct sockaddr_in *addr4, *pattern4, *mask4;
|
|
struct sockaddr_in6 *addr6, *pattern6, *mask6;
|
|
int i, match;
|
|
|
|
if (login_info->internet_addr == NULL)
|
|
return (0);
|
|
if ((tok = need_internet_addr()) == 0)
|
|
return (0);
|
|
if ((res = convert_internet_addr(tok)) == NULL)
|
|
return (0);
|
|
memcpy(&pattern, res->ai_addr, res->ai_addrlen);
|
|
freeaddrinfo(res);
|
|
if ((tok = need_internet_addr()) == 0)
|
|
return (0);
|
|
if ((res = convert_internet_addr(tok)) == NULL)
|
|
return (0);
|
|
memcpy(&mask, res->ai_addr, res->ai_addrlen);
|
|
freeaddrinfo(res);
|
|
if (pattern.ss_family != mask.ss_family)
|
|
return (0);
|
|
mask4 = (struct sockaddr_in *)&mask;
|
|
pattern4 = (struct sockaddr_in *)&pattern;
|
|
mask6 = (struct sockaddr_in6 *)&mask;
|
|
pattern6 = (struct sockaddr_in6 *)&pattern;
|
|
|
|
/*
|
|
* See if any of the addresses matches a pattern in the control file. We
|
|
* have already tried to drop addresses that belong to someone else.
|
|
*/
|
|
|
|
for (res = login_info->internet_addr; res; res = res->ai_next) {
|
|
ai_unmapped(res);
|
|
if (res->ai_family != pattern.ss_family)
|
|
continue;
|
|
switch (res->ai_family) {
|
|
case AF_INET:
|
|
addr4 = (struct sockaddr_in *)res->ai_addr;
|
|
if (addr4->sin_addr.s_addr != INADDR_NONE &&
|
|
(addr4->sin_addr.s_addr & mask4->sin_addr.s_addr) == pattern4->sin_addr.s_addr)
|
|
return (1);
|
|
break;
|
|
case AF_INET6:
|
|
addr6 = (struct sockaddr_in6 *)res->ai_addr;
|
|
if (pattern6->sin6_scope_id != 0 &&
|
|
addr6->sin6_scope_id != pattern6->sin6_scope_id)
|
|
break;
|
|
match = 1;
|
|
for (i = 0; i < 16; ++i) {
|
|
if ((addr6->sin6_addr.s6_addr[i] & mask6->sin6_addr.s6_addr[i]) != pattern6->sin6_addr.s6_addr[i]) {
|
|
match = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (match)
|
|
return (1);
|
|
break;
|
|
}
|
|
}
|
|
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
|
|
line_pointer = buf;
|
|
while ((cp = strsep(&line_pointer, " \t")) != NULL && *cp == '\0')
|
|
;
|
|
if (cp != NULL)
|
|
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 {
|
|
while ((cp = strsep(&line_pointer, " \t")) != NULL && *cp == '\0')
|
|
;
|
|
}
|
|
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;
|
|
{
|
|
struct addrinfo *res;
|
|
|
|
if ((res = convert_internet_addr(str)) != NULL)
|
|
freeaddrinfo(res);
|
|
return (res != NULL);
|
|
}
|
|
|
|
/*
|
|
* Nuke addrinfo entry from list.
|
|
* XXX: Depending on the implementation of KAME's getaddrinfo(3).
|
|
*/
|
|
static void nuke_ai(aip)
|
|
struct addrinfo **aip;
|
|
{
|
|
struct addrinfo *ai;
|
|
|
|
ai = *aip;
|
|
*aip = ai->ai_next;
|
|
if (ai->ai_canonname) {
|
|
if (ai->ai_next && !ai->ai_next->ai_canonname)
|
|
ai->ai_next->ai_canonname = ai->ai_canonname;
|
|
else
|
|
free(ai->ai_canonname);
|
|
}
|
|
free(ai);
|
|
}
|
|
|
|
/* lookup_internet_addr - look up internet addresses with extreme prejudice */
|
|
|
|
static struct addrinfo *lookup_internet_addr(host)
|
|
char *host;
|
|
{
|
|
struct addrinfo hints, *res0, *res, **resp;
|
|
char hname[NI_MAXHOST], haddr[NI_MAXHOST];
|
|
int error;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_PASSIVE | AI_CANONNAME;
|
|
if (getaddrinfo(host, NULL, &hints, &res0) != 0)
|
|
return (NULL);
|
|
if (res0->ai_canonname == NULL) {
|
|
freeaddrinfo(res0);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Wipe addresses that appear to belong to someone else. We will get
|
|
* false alarms when when the hostname comes from DNS, while its
|
|
* addresses are listed under different names in local databases.
|
|
*/
|
|
#define NEQ(x,y) (strcasecmp((x),(y)) != 0)
|
|
#define NEQ3(x,y,n) (strncasecmp((x),(y), (n)) != 0)
|
|
|
|
resp = &res0;
|
|
while ((res = *resp) != NULL) {
|
|
if (res->ai_family != AF_INET && res->ai_family != AF_INET6) {
|
|
nuke_ai(resp);
|
|
continue;
|
|
}
|
|
error = getnameinfo(res->ai_addr, res->ai_addrlen,
|
|
hname, sizeof(hname),
|
|
NULL, 0, NI_NAMEREQD | NI_WITHSCOPEID);
|
|
if (error) {
|
|
getnameinfo(res->ai_addr, res->ai_addrlen, haddr, sizeof(haddr),
|
|
NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
|
|
syslog(LOG_ERR, "address %s not registered for host %s",
|
|
haddr, res0->ai_canonname);
|
|
nuke_ai(resp);
|
|
continue;
|
|
}
|
|
if (NEQ(res0->ai_canonname, hname) &&
|
|
NEQ3(res0->ai_canonname, "localhost.", 10)) {
|
|
getnameinfo(res->ai_addr, res->ai_addrlen, haddr, sizeof(haddr),
|
|
NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
|
|
syslog(LOG_ERR, "address %s registered for host %s and %s",
|
|
haddr, hname, res0->ai_canonname);
|
|
nuke_ai(resp);
|
|
continue;
|
|
}
|
|
resp = &res->ai_next;
|
|
}
|
|
return (res0);
|
|
}
|
|
|
|
/* convert_internet_addr - convert string to internet address */
|
|
|
|
static struct addrinfo *convert_internet_addr(string)
|
|
char *string;
|
|
{
|
|
struct addrinfo hints, *res;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
|
|
if (getaddrinfo(string, NULL, &hints, &res) != 0)
|
|
return (NULL);
|
|
return (res);
|
|
}
|
|
|
|
#ifdef TEST
|
|
|
|
main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
struct addrinfo hints, *res;
|
|
char host[MAXHOSTNAMELEN + 1];
|
|
int verdict;
|
|
char *user;
|
|
char *port;
|
|
|
|
if (argc != 3 && argc != 4) {
|
|
fprintf(stderr, "usage: %s user port [host_or_ip_address]\n", argv[0]);
|
|
exit(0);
|
|
}
|
|
if (_PATH_SKEYACCESS[0] != '/')
|
|
printf("Warning: this program uses control file: %s\n", _PATH_SKEYACCESS);
|
|
openlog("login", LOG_PID, LOG_AUTH);
|
|
|
|
user = argv[1];
|
|
port = argv[2];
|
|
if (argv[3]) {
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_PASSIVE | AI_CANONNAME;
|
|
if (getaddrinfo(argv[3], NULL, &hints, &res) == 0) {
|
|
if (res->ai_canonname == NULL)
|
|
strncpy(host, argv[3], MAXHOSTNAMELEN);
|
|
else
|
|
strncpy(host, res->ai_canonname, MAXHOSTNAMELEN);
|
|
freeaddrinfo(res);
|
|
} else
|
|
strncpy(host, argv[3], MAXHOSTNAMELEN);
|
|
host[MAXHOSTNAMELEN] = 0;
|
|
}
|
|
verdict = skeyaccess(user, port, argv[3] ? host : (char *) 0, (char *) 0);
|
|
printf("UNIX passwords %spermitted\n", verdict ? "" : "NOT ");
|
|
return (0);
|
|
}
|
|
|
|
#endif
|