diff -u -r -N Linux-PAM-0.99.2.1.orig/modules/pam_access/pam_access.c Linux-PAM-0.99.2.1/modules/pam_access/pam_access.c --- Linux-PAM-0.99.2.1.orig/modules/pam_access/pam_access.c 2005-11-10 10:07:00.000000000 +0100 +++ Linux-PAM-0.99.2.1/modules/pam_access/pam_access.c 2006-01-03 22:00:38.554955264 +0100 @@ -4,6 +4,9 @@ * Written by Alexei Nogin 1997/06/15 * (I took login_access from logdaemon-5.6 and converted it to PAM * using parts of pam_time code.) + * Additional changes were made by Thomas Mueller + * and Mike Becher which are documented in + * CHANGELOG file. * ************************************************************************ * Copyright message from logdaemon-5.6 (original file name DISCLAIMER) @@ -26,6 +29,7 @@ */ #include "config.h" +#include "pam_access_config.h" #include #include @@ -41,12 +45,25 @@ #include #include #include -#include +#ifdef NIS_SUPPORT +# include +#endif /* NIS_SUPPORT */ +#include +#ifdef WITH_SELINUX +#include +#define SELINUX_ENABLED (is_selinux_enabled() > 0) +#endif + #ifndef BROKEN_NETWORK_MATCH # include # include -#endif +#endif /* BROKEN_NETWORK_MATCH */ + +#ifdef NEW_INET_SUPPORT +# include +# include +#endif /* NEW_INET_SUPPORT */ /* * here, we make definitions for the externally accessible functions @@ -55,12 +72,53 @@ * modules include file to define their prototypes. */ +#if !defined(PAM_ACCESS_COMPILE_PROGRAM) +#define PAM_SM_AUTH #define PAM_SM_ACCOUNT +#define PAM_SM_SESSION +#define PAM_SM_PASSWORD +#endif /* ! PAM_ACCESS_COMPILE_PROGRAM */ #include #include + +#if !defined(COMPILE_AS_LOGIN_ACCESS) #include #include +#endif + +#if defined(COMPILE_AS_LOGIN_ACCESS) +#if !defined(UNUSED) +#define UNUSED +#endif +#define MY_PAM_SYSLOG(handle, ... ) syslog(__VA_ARGS__) +#else +#define MY_PAM_SYSLOG(handle, ... ) pam_syslog(handle, __VA_ARGS__) +#endif + +#ifndef PAM_ACCESS_CONFIG +#define PAM_ACCESS_CONFIG "/etc/security/access.conf" +#endif /* PAM_ACCESS_CONFIG */ + +#define SUCCESS 1 +#define FAIL 0 + +#if defined(HAVE_YP_GET_DEFAULT_DOMAIN) +#define NIS_SUPPORT 1 +#else +#undef NIS_SUPPORT +#endif + +#if defined(HAVE_INET_NTOP) && defined(HAVE_INET_PTON) +#define NEW_INET_SUPPORT 1 +#else +#undef NEW_INET_SUPPORT +#endif + +#if !defined(MAXHOSTNAMELEN) || (MAXHOSTNAMELEN < 64) +#undef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif /* login_access.c from logdaemon-5.6 with several changes by A.Nogin: */ @@ -68,405 +126,1495 @@ * This module implements a simple but effective form of login access * control based on login names and on host (or domain) names, internet * addresses (or network numbers), or on terminal line names in case of - * non-networked logins. Diagnostics are reported through syslog(3). + * non-networked logins. Diagnostics are reported through MY_PAM_SYSLOG(pamh, 3). * * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. */ -#if !defined(MAXHOSTNAMELEN) || (MAXHOSTNAMELEN < 64) -#undef MAXHOSTNAMELEN -#define MAXHOSTNAMELEN 256 -#endif - /* Delimiters for fields and for lists of users, ttys or hosts. */ -static const char *fs = ":"; /* field separator */ -static const char *sep = ", \t"; /* list-element separator */ +static const char *fs = ":"; /* field separator */ +static const char *sep = ", \t"; /* list-element separator */ /* Constants to be used in assignments only, not in comparisons... */ #define YES 1 #define NO 0 +/* This is the default config file which will be scanned by module or + * application. + */ +static const char pam_access_default_config_file[] = PAM_ACCESS_CONFIG; + +/* Global options to configure PAM module or application. */ + +/* Print messages to stdout. + * Default is NO which means use syslog. + */ +static char pam_access_opt_msg_to_stdout = NO; +/* Print debugging messages. + * Default is NO which means don't print debugging messages. + */ +static char pam_access_opt_msg_debug = NO; +/* If an internal error occurs module should return with FAIL. + */ +static char pam_access_opt_onerr = FAIL; +/* Use an alternative config file than PAM_ACCESS_CONFIG. + * Default is NULL which means use PAM_ACCESS_CONFIG. + */ +static const char *pam_access_opt_config_file = NULL; +/* If an hostname was provided convert it to IP address. + * Default is NO which means don't convert. + */ +static char pam_access_opt_convert_hostname = NO; +/* Should we ask an external helper program if a user should get + * access to this service? + * Default is NULL which means don't ask. + */ +static const char *pam_access_opt_helper_file = NULL; +/* Should we ask external helper program only and skip evaluation of + * access control table? + * Default is NO. + */ +static char pam_access_opt_ask_helper_only = NO; + + /* * A structure to bundle up all login-related information to keep the * functional interfaces as generic as possible. */ struct login_info { - struct passwd *user; + const struct passwd *user; const char *from; - const char *config_file; }; -/* --- static functions for checking whether the user should be let in --- */ - -/* Parse module config arguments */ +#ifdef NEW_INET_SUPPORT + /* Structure that is large enough to hold any socket address (with the + * historical exception of AF_UNIX). + * 128 bytes reserved. + * This is from: + * http://jungla.dit.upm.es/~ecastro/IPv6-web/ipv6.html + * provided by: + * Eva M. Castro + * found similar definition in glibc 2.3.3 include file: + * /usr/include/bits/socket.h + */ +#if defined(HAVE_STRUCT_SOCKADDR_STORAGE) && !HAVE_STRUCT_SOCKADDR_STORAGE +#if ULONG_MAX > 0xffffffff +# define __ss_aligntype __uint64_t +#else +# define __ss_aligntype __uint32_t +#endif +#define _SS_SIZE 128 +#define _SS_PADSIZE (_SS_SIZE - (2 * sizeof (__ss_aligntype))) -static int -parse_args(pam_handle_t *pamh, struct login_info *loginfo, - int argc, const char **argv) +struct sockaddr_storage { - int i; + sa_family_t ss_family; /* Address family */ + __ss_aligntype __ss_align; /* Force desired alignment. */ + char __ss_padding[_SS_PADSIZE]; +}; +#endif /* defined(HAVE_STRUCT_SOCKADDR_STORAGE) && !HAVE_STRUCT_SOCKADDR_STORAGE */ +#endif /* NEW_INET_SUPPORT */ - for (i=0; iconfig_file = 11 + argv[i]; - fclose(fp); - } else { - pam_syslog(pamh, LOG_ERR, - "failed to open accessfile=[%s]: %m", 11 + argv[i]); - return 0; - } + return(NO); +} - } else { - pam_syslog(pamh, LOG_ERR, "unrecognized option [%s]", argv[i]); - } +static char * +number_to_netmask(long netmask, int addr_type, + char *ipaddr_buf, size_t ipaddr_buf_len) +{ + /* We use struct sockaddr_storage addr because + * struct in_addr/in6_addr is an integral part + * of struct sockaddr and we doesn't want to + * use its value. + */ + struct sockaddr_storage nmask; + unsigned char *byte_nm; + const char *ipaddr_dst = NULL; + int i, ip_bytes; + + if (netmask == 0) { + /* mask 0 is the same like no mask */ + return(NULL); + } + + memset(&nmask, 0, sizeof(struct sockaddr_storage)); + if (addr_type == AF_INET6) { + /* ipv6 address mask */ + ip_bytes = 16; + } else { + /* default might be an ipv4 address mask */ + addr_type = AF_INET; + ip_bytes = 4; + } + + byte_nm = (unsigned char *)(&nmask); + /* translate number to mask */ + for (i=0; i= 8) { + byte_nm[i] = 0xff; + netmask -= 8; + } else + if (netmask > 0) { + byte_nm[i] = 0xff << (8 - netmask); + break; + } else + if (netmask <= 0) { + break; } + } + + /* now generate netmask address string */ + ipaddr_dst = inet_ntop(addr_type, &nmask, ipaddr_buf, ipaddr_buf_len); + if (ipaddr_dst == ipaddr_buf) { + return (ipaddr_buf); + } - return 1; /* OK */ + return (NULL); } -typedef int match_func (pam_handle_t *, char *, struct login_info *); +#endif /* NEW_INET_SUPPORT */ -static int list_match (pam_handle_t *, char *, struct login_info *, - match_func *); -static int user_match (pam_handle_t *, char *, struct login_info *); -static int from_match (pam_handle_t *, char *, struct login_info *); -static int string_match (const char *, const char *); +/* convert_hostname_r - convert a hostname to an IP address which + * based on private buffer. NEW_INET_SUPPORT should be reentrant but + * old may not. + */ +static const char* +convert_hostname_r(const char* hostname, char *addr_buf, size_t addr_buf_len) +{ + struct hostent* host; + const char* my_hostname = hostname; + const char* my_ipaddr; + + if ((my_hostname == NULL) + || (*my_hostname == '\0') + || (isipaddr(my_hostname, NULL) == YES) + || (addr_buf == NULL) + || (addr_buf_len <= 0)) { + return (my_hostname); + } + + host = gethostbyname(my_hostname); + if (host == NULL) { + return (my_hostname); + } + +#ifdef NEW_INET_SUPPORT + my_ipaddr = inet_ntop(host->h_addrtype, *host->h_addr_list, + addr_buf, addr_buf_len); +#else + my_ipaddr = inet_ntoa(*(struct in_addr *) * host->h_addr_list); + if (strlen(my_ipaddr) >= addr_buf_len) { + return (my_hostname); + } else { + strcpy(addr_buf, my_ipaddr); + my_ipaddr = addr_buf; + } +#endif /* NEW_INET_SUPPORT */ + if (my_ipaddr == NULL) { + return (my_hostname); + } + if (isipaddr(my_ipaddr, NULL) == YES) { + return (my_ipaddr); + } -/* login_access - match username/group and host/tty with access control file */ + return (my_hostname); +} + +/* convert_hostname - convert a hostname to an IP address */ +static const char* +convert_hostname(const char* hostname) +{ +#ifdef NEW_INET_SUPPORT + static char ipaddr_buf[MAXHOSTNAMELEN + 1] = ""; +#endif /* NEW_INET_SUPPORT */ + return(convert_hostname_r(hostname, ipaddr_buf, MAXHOSTNAMELEN)); +} + +/* list_match - match an item against a list of tokens with exceptions */ static int -login_access (pam_handle_t *pamh, struct login_info *item) +list_match(pam_handle_t *pamh, char *list, struct login_info *item, + match_func *match_fn) { - FILE *fp; - char line[BUFSIZ]; - char *perm; /* becomes permission field */ - char *users; /* becomes list of login names */ - char *froms; /* becomes list of terminals or hosts */ - int match = NO; - int end; - int lineno = 0; /* for diagnostics */ + char *tok; + int match = NO; - /* - * Process the table one line at a time and stop at the first match. - * Blank lines and lines that begin with a '#' character are ignored. - * Non-comment lines are broken at the ':' character. All fields are - * mandatory. The first field should be a "+" or "-" character. A - * non-existing table means no access control. - */ + /* + * Process tokens one at a time. We have exhausted all possible matches + * when we reach an "EXCEPT" token or the end of the list. If we do find + * a match, look for an "EXCEPT" list and recurse to determine whether + * the match is affected by any exceptions. + */ + + for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { + if (strcasecmp(tok, "EXCEPT") == 0) { /* EXCEPT: give up */ + break; + } + if ((match = (*match_fn)(pamh, tok, item))) { /* YES */ + break; + } + } - if ((fp = fopen(item->config_file, "r"))!=NULL) { - while (!match && fgets(line, sizeof(line), fp)) { - lineno++; - if (line[end = strlen(line) - 1] != '\n') { - pam_syslog(pamh, LOG_ERR, - "%s: line %d: missing newline or line too long", - item->config_file, lineno); - continue; - } - if (line[0] == '#') - continue; /* comment line */ - while (end > 0 && isspace(line[end - 1])) - end--; - line[end] = 0; /* strip trailing whitespace */ - if (line[0] == 0) /* skip blank lines */ - continue; - - /* Allow trailing: in last field fo froms */ - if (!(perm = strtok(line, fs)) - || !(users = strtok((char *) 0, fs)) - || !(froms = strtok((char *) 0, fs))) { - pam_syslog(pamh, LOG_ERR, "%s: line %d: bad field count", - item->config_file, lineno); - continue; - } - if (perm[0] != '+' && perm[0] != '-') { - pam_syslog(pamh, LOG_ERR, "%s: line %d: bad first field", - item->config_file, lineno); - continue; - } - match = (list_match(pamh, froms, item, from_match) - && list_match(pamh, users, item, user_match)); - } - (void) fclose(fp); - } else if (errno != ENOENT) { - pam_syslog(pamh, LOG_ERR, "cannot open %s: %m", item->config_file); - return NO; + /* Process exceptions to matches. */ + if (match != NO) { + while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT")) { + /* VOID */ ; + } + if (tok == 0 || list_match(pamh, (char *) 0, item, match_fn) == NO) { + return (match); } - return (match == 0 || (line[0] == '+')); + } + return (NO); } -/* list_match - match an item against a list of tokens with exceptions */ +/* myhostname - figure out local machine name */ +static char * +myhostname(void) +{ + static char name[MAXHOSTNAMELEN + 1]; + + if (gethostname(name, MAXHOSTNAMELEN) == 0) { + name[MAXHOSTNAMELEN] = 0; + return (name); + } + return (NULL); +} + +/* netgroup_match - match group against machine or user */ +static int +netgroup_match(pam_handle_t *pamh, const char *group, + const char *machine, const char *user) +{ +#ifdef NIS_SUPPORT + static char *my_domain = 0; + int retval; + + if (my_domain == 0) { + yp_get_default_domain(&my_domain); + } + + retval = innetgr(group, machine, user, my_domain); + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, + "netgroup_match: %d (group=%s, machine=%s, user=%s, domain=%s)", + retval, + group ? group : "NULL", + machine ? machine : "NULL", + user ? user : "NULL", + my_domain ? my_domain : "NULL"); + } else { + fprintf(stdout, + "netgroup_match: %d (group=%s, machine=%s, user= %s, domain=%s)\n", + retval, + group ? group : "NULL", + machine ? machine : "NULL", + user ? user : "NULL", + my_domain ? my_domain : "NULL"); + } + } + return retval; +#else + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "NIS netgroup support not configured"); + } else { + fprintf(stdout, "NIS netgroup support not configured\n"); + } + return (NO); +#endif +} -static int list_match(pam_handle_t *pamh, - char *list, struct login_info *item, match_func *match_fn) +/* user_match - match a username against one token */ +static int +user_match(pam_handle_t *pamh, char *tok, struct login_info *item) { - char *tok; - int match = NO; + char *string = item->user->pw_name; + struct login_info fake_item; + char *at; +#if defined(COMPILE_AS_LOGIN_ACCESS) + struct group *group; +#endif /* COMPILE_AS_LOGIN_ACCESS */ + + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "user_match: tok=%s, item=%s", tok, string); + } else { + fprintf(stdout, "user_match: tok=%s, item=%s\n", tok, string); + } + } + /* + * If a token has the magic value "ALL" the match always succeeds. + * Otherwise, return YES if the token fully matches the username, if the + * token is a group that contains the username, or if the token is the + * name of the user's primary group. + */ + /* split user@host pattern */ + if ((at = strchr(tok + 1, '@')) != 0) { + *at = 0; + fake_item.from = myhostname(); + if (fake_item.from == NULL) { + return (NO); + } + return (user_match(pamh, tok, item) && + from_match(pamh, at + 1, &fake_item)); + } else + /* netgroup */ + if (tok[0] == '@') { + return (netgroup_match(pamh, tok + 1, (char *) 0, string)); + } else + /* ALL or exact match */ + if (string_match(tok, string)) { + return (YES); + } else +#if defined(COMPILE_AS_LOGIN_ACCESS) + /* try group membership */ + if (group = getgrnam(tok)) { + int i; + if (item->user->pw_gid == group->gr_gid) { + return (YES); + } + for (i = 0; group->gr_mem[i]; i++) { + if (strcasecmp(string, group->gr_mem[i]) == 0) { + return (YES); + } + } + } +#else /* COMPILE_AS_LOGIN_ACCESS */ + /* try group membership */ + if (pam_modutil_user_in_group_nam_nam (pamh, item->user->pw_name, tok)) { + return (YES); + } +#endif /* COMPILE_AS_LOGIN_ACCESS */ + return (NO); +} + +/* from_match - match a host or tty against a list of tokens */ +static int +from_match(pam_handle_t *pamh, char *tok, struct login_info *item) +{ + const char *string = item->from; + int tok_len; + int str_len; + + /* + * If a token has the magic value "ALL" the match always succeeds. Return + * YES if the token fully matches the string. If the token is a domain + * name, return YES if it matches the last fields of the string. If the + * token has the magic value "LOCAL", return YES if the string does not + * contain a "." character. If the token is a network number, return YES + * if it matches the head of the string. + */ + + /* netgroup */ + if ((string != NULL) && (tok[0] == '@')) { + return (netgroup_match(pamh, tok + 1, string, (char *)0)); + } else + /* ALL or exact match */ + if (string_match(tok, string)) { + return (YES); + } else + /* domain: match last fields */ + if (tok[0] == '.') { + if ((str_len = strlen(string)) > (tok_len = strlen(tok)) + && strcasecmp(tok, string + str_len - tok_len) == 0) { + return (YES); + } + } else + /* local: no dots */ + if (strcasecmp(tok, "LOCAL") == 0) { + if (strchr(string, '.') == 0) { + return (YES); + } + } else +#ifdef BROKEN_NETWORK_MATCH + /* network */ + if (tok[(tok_len = strlen(tok)) - 1] == '.' + && strncmp(tok, string, tok_len) == 0) { + return (YES); + } else +#else /* BROKEN_NETWORK_MATCH */ + /* network */ + if (tok[(tok_len = strlen(tok)) - 1] == '.' ) { /* - * Process tokens one at a time. We have exhausted all possible matches - * when we reach an "EXCEPT" token or the end of the list. If we do find - * a match, look for an "EXCEPT" list and recurse to determine whether - * the match is affected by any exceptions. + * The code below does a more correct check if the address specified + * by "string" starts from "tok". + * 1998/01/27 Andrey V. Savochkin + * But this code has worked only with IPv4 addresses. Now it works + * also with IPv6 addresses. */ - - for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { - if (strcasecmp(tok, "EXCEPT") == 0) /* EXCEPT: give up */ - break; - if ((match = (*match_fn) (pamh, tok, item))) /* YES */ - break; - } - /* Process exceptions to matches. */ - - if (match != NO) { - while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT")) - /* VOID */ ; - if (tok == 0 || list_match(pamh, (char *) 0, item, match_fn) == NO) - return (match); + const char *ip_str; + char *ipaddr_buf = NULL; + + ipaddr_buf = (char *)malloc(sizeof(char) *(MAXHOSTNAMELEN + 1)); + if (ipaddr_buf == NULL) { + return (NO); } - return (NO); + + ip_str = convert_hostname_r(string, ipaddr_buf, MAXHOSTNAMELEN); + if ((ip_str == string) || (ip_str == NULL)) { + /* This was no valid hostname because we get it back + * or it has produced an error. + */ + free(ipaddr_buf); + return (NO); + } + if (strncmp(tok, ip_str, tok_len) == 0) { + free(ipaddr_buf); + return (YES); + } + free(ipaddr_buf); + } else +#endif /* BROKEN_NETWORK_MATCH */ + /* network/netmask */ + if (network_netmask_match(tok, string)) { + return (YES); + } + return (NO); } -/* myhostname - figure out local machine name */ +/* string_match - match a string against one token */ +static int +string_match(const char *tok, const char *string) +{ -static char * myhostname(void) + /* + * If the token has the magic value "ALL" the match always succeeds. + * Otherwise, return YES if the token fully matches the string. + * "NONE" token matches NULL string. + */ + + /* all: always matches */ + if (strcasecmp(tok, "ALL") == 0) { + return (YES); + } else + /* try exact match */ + if ((string != NULL) && (strcasecmp(tok, string) == 0)) { + return (YES); + } else + /* NONE token matches NULL string. */ + if (strcasecmp(tok, "NONE") == 0) { + return (YES); + } + + return (NO); +} + +/* isipaddr - find out if string provided is an IP address or not */ +static int +isipaddr(const char *string, int *addr_type) +{ + int itis = YES; +#ifdef NEW_INET_SUPPORT + /* We use struct sockaddr_storage addr because + * struct in_addr/in6_addr is an integral part + * of struct sockaddr and we doesn't want to + * use its value. + */ + struct sockaddr_storage addr; + + /* first ipv4 */ + if (inet_pton(AF_INET, string, (void *)&addr) > 0) { + if (addr_type != NULL) { + *addr_type = AF_INET; + } + itis = YES; + } else + /* then ipv6 */ + if (inet_pton(AF_INET6, string, (void *)&addr) > 0) { + if (addr_type != NULL) { + *addr_type = AF_INET6; + } + itis = YES; + } else { + itis = NO; + } +#else + unsigned int i; + + for (i = 0; itis && i < strlen(string); i++) { + itis = (string[i] == '.') || isdigit(string[i]); + } + if ((itis == YES) && (addr_type != NULL)) { + *addr_type = AF_INET; + } +#endif /* NEW_INET_SUPPORT */ + return itis; +} + +/* network_netmask_match - match a string against one token + * where string is an ip (v4,v6) address and tok represents + * whether a single ip (v4,v6) address or a network/netmask + */ +static int +network_netmask_match(const char *tok, const char *string) { - static char name[MAXHOSTNAMELEN + 1]; +#ifdef NEW_INET_SUPPORT + if (isipaddr(string, NULL) == YES) { + char *netmask_ptr = NULL; + static char netmask_string[MAXHOSTNAMELEN + 1] = ""; + int addr_type; + + /* OK, check if tok is of type addr/mask */ + if ((netmask_ptr = strchr(tok, '/')) != NULL) { + long netmask = 0; + + /* YES */ + *netmask_ptr = 0; + netmask_ptr++; + + if (isipaddr(tok, &addr_type) == NO) { + /* no netaddr */ + return(NO); + } + + /* check netmask */ + if (isipaddr(netmask_ptr, NULL) == NO) { + /* netmask as integre value */ + char *endptr = NULL; + netmask = strtol(netmask_ptr, &endptr, 0); + if ((endptr == NULL) || (*endptr != '\0')) { + /* invalid netmask value */ + return(NO); + } + if ((netmask < 0) || (netmask >= 128)) { + /* netmask value out of range */ + return(NO); + } - if (gethostname(name, MAXHOSTNAMELEN) == 0) { - name[MAXHOSTNAMELEN] = 0; - return (name); + netmask_ptr = number_to_netmask(netmask, addr_type, + netmask_string, MAXHOSTNAMELEN); + } + + /* Netmask is now an ipv4/ipv6 address. + * This works also if netmask_ptr is NULL. + */ + return(are_addresses_equal(string, tok, netmask_ptr)); + } else + /* NO, then check if it is only an addr */ + if (isipaddr(tok, NULL) == YES) { + /* check if they are the same, no netmask */ + return(are_addresses_equal(string, tok, NULL)); } - return NULL; + } +#endif /* NEW_INET_SUPPORT */ + return (NO); } -/* netgroup_match - match group against machine or user */ -static int netgroup_match(const char *group, const char *machine, const char *user) +/* execute_external_helper - this function calls our external helper + * program to decide if user gets or doesn't get access. + * If helper program does not exist access will be denied. + * If it exists program will be called and if it returns with 0 than + * access will be granted in other cases access will be denied. + * + * Code was adapted from pam_unix_acct.c. + */ +static int +execute_external_helper(pam_handle_t *pamh, const char *user, + const char *from) { - static char *mydomain = NULL; + int retval = 0; + int child = 0; + void (*sighandler)(int) = NULL; + + if ((user == NULL) || (from == NULL)) { + return(NO); + } + + /* + * This code arranges that the demise of the child does not cause + * the application to receive a signal it is not expecting - which + * may kill the application or worse. + */ + sighandler = signal(SIGCHLD, SIG_DFL); + + /* fork */ + child = fork(); + if (child == 0) { + size_t i=0; + struct rlimit rlim; + static char *envp[] = { NULL }; + char *args[] = { NULL, NULL, NULL, NULL }; + + /* XXX - should really tidy up PAM here too */ + + if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) { + for (i = 3; i < rlim.rlim_max; i++) { + close(i); + } + } + +#ifdef WITH_SELINUX + if (SELINUX_ENABLED && (geteuid() == 0)) { + /* must set the real uid to 0 so the helper will not error + out if pam is called from setuid binary (su, sudo...) */ + setuid(0); + } +#endif /* WITH_SELINUX */ + + /* exec binary helper */ + args[0] = x_strdup(pam_access_opt_helper_file); + args[1] = x_strdup(user); + args[2] = x_strdup(from); + + execve(pam_access_opt_helper_file, args, envp); + + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "helper binary execve failed: %m"); + } else { + fprintf(stderr, "helper binary execve failed: %m\n"); + } + + /* should not get here: exit with error */ + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, + "%s helper binary is not available or not an executable.", + pam_access_opt_helper_file); + } else { + fprintf(stdout, + "%s helper binary is not available or not an executable.\n", + pam_access_opt_helper_file); + } + } + exit(PAM_AUTHINFO_UNAVAIL); + } else { + /* The parent */ + if (child > 0) { + int rc = 0; + rc = waitpid(child, &retval, 0); /* wait for helper to complete */ + if (rc < 0) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "helper waitpid returned %d: %m", rc); + } else { + fprintf(stderr, "helper waitpid returned %d: %m\n", rc); + } + retval = NO; + } else { + retval = WEXITSTATUS(retval); + if (retval == 0) { + retval = YES; + } else + if (retval == 1) { + retval = NO; + } else { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, + "error: helper with PID %d returns with %d %m", + rc, retval); + } else { + fprintf(stderr, + "error: helper with PID %d returns with %d %m\n", + rc, retval); + } + retval = -2; + } + } + } else { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "Could not fork: %m"); + } else { + fprintf(stderr, "Could not fork: %m\n"); + } + retval = -3; + } + } + + if (sighandler != NULL) { + /* restore old signal handler */ + (void)signal(SIGCHLD, sighandler); + } - if (mydomain == 0) - yp_get_default_domain(&mydomain); - return (innetgr(group, machine, user, mydomain)); + return(retval); } -/* user_match - match a username against one token */ -static int user_match(pam_handle_t *pamh, char *tok, struct login_info *item) +/* getopt - parse module config or command line arguments. */ +static int +pam_access_getopt(pam_handle_t *pamh, int argc, const char **argv) { - char *string = item->user->pw_name; - struct login_info fake_item; - char *at; + int i; - /* - * If a token has the magic value "ALL" the match always succeeds. - * Otherwise, return YES if the token fully matches the username, if the - * token is a group that contains the username, or if the token is the - * name of the user's primary group. - */ + for (i=0; i" to pam_access */ + if (strncmp("accessfile=", argv[i], 11) == 0) { + pam_access_opt_config_file = argv[i] + 11; + } else { + /* option "ask_helper_only" */ + if (strcmp(argv[i], "ask_helper_only") == 0) { + pam_access_opt_ask_helper_only = YES; + } else + /* option "convert_hostname" */ + if (strcmp(argv[i], "convert_hostname") == 0) { + pam_access_opt_convert_hostname = YES; + } else + /* option "debug" */ + if (strcmp(argv[i], "debug") == 0) { + pam_access_opt_msg_debug = YES; + } else + /* option "fieldsep=" */ + if (strncmp("fieldsep=", argv[i], 9) == 0) { + /* the admin wants to override the default field separators */ + fs = argv[i] + 9; + } else + /* compatibility option "file=" */ + if (strncmp(argv[i], "file=", 5) == 0) { + pam_access_opt_config_file = argv[i] + 5; + } else + /* compatibility option "file " (old version). */ + if (strcmp(argv[i], "file") == 0) { + i++; + pam_access_opt_config_file = argv[i]; + } else + /* option "helperfile=" */ + if (strncmp(argv[i], "helperfile=", 11) == 0) { + pam_access_opt_helper_file = argv[i] + 11; + } else + /* option "listsep=" */ + if (strncmp("listsep=", argv[i], 8) == 0) { + /* the admin wants to override the default list separators */ + sep = argv[i] + 8; + } else + /* option "onerr=<[fail|success]>" */ + if (strncmp(argv[i], "onerr=", 6) == 0) { + const char *s = argv[i] + 6; + if (strcmp(s, "fail") == 0) { + pam_access_opt_onerr = FAIL; + } else if (strcmp(s, "success") == 0) { + pam_access_opt_onerr = SUCCESS; + } else { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, + "Unknown argument '%s' to option 'onerr', using '%s'", + argv[i], + (pam_access_opt_onerr == SUCCESS) ? "success" : "fail"); + } else { + fprintf(stdout, + "Unknown argument '%s' to option 'onerr', using '%s'\n", + argv[i], + (pam_access_opt_onerr == SUCCESS) ? "success" : "fail"); + } + } + } else + /* compatiblity option "onerr <[fail|success]>" */ + if (strcmp(argv[i], "onerr") == 0) { + i++; + if (strcmp(argv[i], "fail") == 0) { + pam_access_opt_onerr = FAIL; + } else if (strcmp(argv[i], "success") == 0) { + pam_access_opt_onerr = SUCCESS; + } else { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, + "Unknown argument '%s' to option 'onerr', using '%s'", + argv[i], + (pam_access_opt_onerr == SUCCESS) ? "success" : "fail"); + } else { + fprintf(stdout, + "Unknown argument '%s' to option 'onerr', using '%s'\n", + argv[i], + (pam_access_opt_onerr == SUCCESS) ? "success" : "fail"); + } + } + } else + /* unknown option! write to syslog! */ + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, + "Unknown option '%s'", argv[i]); + } else { + fprintf(stdout, + "Unknown option '%s'\n", argv[i]); + } + } + } - if ((at = strchr(tok + 1, '@')) != 0) { /* split user@host pattern */ - *at = 0; - fake_item.from = myhostname(); - if (fake_item.from == NULL) - return NO; - return (user_match (pamh, tok, item) && from_match (pamh, at + 1, &fake_item)); - } else if (tok[0] == '@') /* netgroup */ - return (netgroup_match(tok + 1, (char *) 0, string)); - else if (string_match (tok, string)) /* ALL or exact match */ - return YES; - else if (pam_modutil_user_in_group_nam_nam (pamh, item->user->pw_name, tok)) - /* try group membership */ - return YES; + /* correct use of external helper program if no one was configured. */ + if (pam_access_opt_helper_file == NULL) { + pam_access_opt_ask_helper_only = NO; + } - return NO; + /* was OK */ + return(0); } -/* from_match - match a host or tty against a list of tokens */ - +/* login_access - match username/group/host/tty and network/netmask + * with access control file + */ static int -from_match (pam_handle_t *pamh UNUSED, char *tok, struct login_info *item) +pam_access_login_access(pam_handle_t *pamh, const struct passwd *user, + const char *from, const char *file) { - const char *string = item->from; - int tok_len; - int str_len; + struct login_info item; + FILE *fp = NULL; + char line[BUFSIZ]; + char *perm; /* becomes permission field */ + char *users; /* becomes list of login names */ + char *froms; /* becomes list of terminals or hosts */ + int match = NO; + int end; + int lineno = 0; /* for diagnostics */ + int syntax_error = NO; + + /* If we should call external helper program only then there is no + * need to process our access control table. So we do it at first. + */ + if (pam_access_opt_ask_helper_only == YES) { + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, + "Asking external helper program \"%s\" only.", + pam_access_opt_helper_file); + } else { + fprintf(stdout, + "Asking external helper program \"%s\" only.\n", + pam_access_opt_helper_file); + } + } + return(execute_external_helper(pamh, user->pw_name, from)); + } - /* - * If a token has the magic value "ALL" the match always succeeds. Return - * YES if the token fully matches the string. If the token is a domain - * name, return YES if it matches the last fields of the string. If the - * token has the magic value "LOCAL", return YES if the string does not - * contain a "." character. If the token is a network number, return YES - * if it matches the head of the string. - */ + /* + * Bundle up the arguments to avoid unnecessary clumsiness lateron. + */ + item.user = user; + item.from = from; + + /* + * Process the table one line at a time and stop at the first match. + * Blank lines and lines that begin with a '#' character are ignored. + * Non-comment lines are broken at the ':' character. All fields are + * mandatory. The first field should be a "+" or "-" character. A + * non-existing table means no access control. + */ + + if ((fp = fopen(file, "r")) != NULL) { + while (!match && fgets(line, sizeof(line), fp)) { + lineno++; + if (line[end = strlen(line) - 1] != '\n') { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, + "%s: line %d: missing newline or line too long", + file, lineno); + } else { + fprintf(stdout, + "%s: line %d: missing newline or line too long\n", + file, lineno); + } + syntax_error = YES; + continue; + } + if (line[0] == '#') { + /* comment line starts with '#' */ + continue; + } + + /* strip trailing whitespace */ + while (end > 0 && isspace(line[end - 1])) { + end--; + } + line[end] = 0; + + /* skip blank lines */ + if (line[0] == 0) { + continue; + } + + if (!(perm = strtok(line, fs)) + || !(users = strtok((char *) 0, fs)) +#ifdef DONTACCEPTDISPLAYNAME + || !(froms = strtok((char *) 0, fs)) +#else + || !(froms = strtok((char *) 0, "\n")) +#endif + || strtok((char *) 0, fs)) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "%s: line %d: bad field count", file, lineno); + } else { + fprintf(stdout, "%s: line %d: bad field count\n", file, lineno); + } + syntax_error = YES; + continue; + } + + if (perm[0] != '+' && perm[0] != '-') { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "%s: line %d: bad first field", file, lineno); + } else { + fprintf(stdout, "%s: line %d: bad first field\n", file, lineno); + } + syntax_error = YES; + continue; + } + + match = list_match(pamh, froms, &item, from_match); + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, + "from_match: %d, \"%s\"", match, item.from); + } else { + fprintf(stdout, + "from_match: %d, \"%s\"\n", match, item.from); + } + } + match = match && list_match(pamh, users, &item, user_match); + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, + "user_match: %d, \"%s\"", match, item.user->pw_name); + } else { + fprintf(stdout, + "user_match: %d, \"%s\"\n", match, item.user->pw_name); + } + } + } + (void) fclose(fp); + } else + /* file not found */ + if (errno == ENOENT) { + /* This is no error. */ + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_WARNING, + "warning: cannot open \"%s\": %s", + file, strerror(errno)); + } else { + fprintf(stdout, + "warning: cannot open \"%s\": %s\n", + file, strerror(errno)); + } + } + } else + /* but all other errors (except file not found) are treated like + * syntax errors. + */ + if (errno != ENOENT) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_ERR, + "error: cannot open \"%s\": %s", + file, strerror(errno)); + } else { + fprintf(stdout, + "error: cannot open \"%s\": %s\n", + file, strerror(errno)); + } + syntax_error = YES; + } - if (string != NULL && tok[0] == '@') { /* netgroup */ - return (netgroup_match(tok + 1, string, (char *) 0)); - } else if (string_match(tok, string)) { /* ALL or exact match */ - return (YES); - } else if (string == NULL) { - return (NO); - } else if (tok[0] == '.') { /* domain: match last fields */ - if ((str_len = strlen(string)) > (tok_len = strlen(tok)) - && strcasecmp(tok, string + str_len - tok_len) == 0) - return (YES); - } else if (strcasecmp(tok, "LOCAL") == 0) { /* local: no dots */ - if (strchr(string, '.') == 0) - return (YES); -#ifdef BROKEN_NETWORK_MATCH - } else if (tok[(tok_len = strlen(tok)) - 1] == '.' /* network */ - && strncmp(tok, string, tok_len) == 0) { - return (YES); -#else /* BROKEN_NETWORK_MATCH */ - } else if (tok[(tok_len = strlen(tok)) - 1] == '.') { - /* - The code below does a more correct check if the address specified - by "string" starts from "tok". - 1998/01/27 Andrey V. Savochkin - */ - - struct hostent *h; - char hn[3+1+3+1+3+1+3+1+1]; - int r; - - h = gethostbyname(string); - if (h == NULL) - return (NO); - if (h->h_addrtype != AF_INET) - return (NO); - if (h->h_length != 4) - return (NO); /* only IPv4 addresses (SAW) */ - r = snprintf(hn, sizeof(hn), "%u.%u.%u.%u.", - (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], - (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); - if (r < 0 || r >= (int)sizeof(hn)) - return (NO); - if (!strncmp(tok, hn, tok_len)) - return (YES); -#endif /* BROKEN_NETWORK_MATCH */ + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "Syntax Error: %d", syntax_error); + } else { + fprintf(stdout, "Syntax Error: %d\n", syntax_error); } - return (NO); + } + + if (syntax_error == YES) { + /* Syntax errors are internal errors. */ + return(-1); + } + + if ((match == NO) || (line[0] == '+')) { + /* access granted */ + return(YES); + } + + /* If we should call external helper program then we do it after + * processing of our access control table and only if access isn't + * already granted. + */ + if (pam_access_opt_helper_file != NULL) { + if (pam_access_opt_msg_debug == YES) { + if (pam_access_opt_msg_to_stdout == NO) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, + "Asking external helper program \"%s\".", + pam_access_opt_helper_file); + } else { + fprintf(stdout, + "Asking external helper program \"%s\".\n", + pam_access_opt_helper_file); + } + } + + return(execute_external_helper(pamh, user->pw_name, from)); + } + + /* access denied */ + return(NO); } -/* string_match - match a string against one token */ +#if !defined(PAM_ACCESS_COMPILE_PROGRAM) +/* --- public functions --- */ -static int -string_match (const char *tok, const char *string) +/* begin of module definition */ + +/* --- authentication management functions --- */ +static char wherefrom[MAXHOSTNAMELEN + 128]; + +PAM_EXTERN int +pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) { + const char *filename = pam_access_default_config_file; + const void *const_void; + const char *username = NULL; + const char *my_rhost = NULL; + const char *my_hostname = NULL; + const char *my_ttyname = NULL; + struct passwd *pwd = NULL; + int retval; + + /* process options from pam call */ + pam_access_getopt(pamh, argc, argv); + + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_sm_authenticate"); + } + if (pam_access_opt_config_file != NULL) { + filename = pam_access_opt_config_file; + } - /* - * If the token has the magic value "ALL" the match always succeeds. - * Otherwise, return YES if the token fully matches the string. - * "NONE" token matches NULL string. - */ + if (access(filename, R_OK) == -1) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "Can't read %s: %s", filename, strerror(errno)); + + if (pam_access_opt_onerr == FAIL) { + return PAM_AUTH_ERR; + } else { + return PAM_SUCCESS; + } + } + /* get user name */ + retval = pam_get_user(pamh, (const char **) &username, NULL); + if (retval != PAM_SUCCESS) { + return(retval); + } + + pwd = getpwnam(username); + if (pwd == NULL || strcmp(pwd->pw_name,username)) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "Can't get passwd entry for %s", username); - if (strcasecmp(tok, "ALL") == 0) { /* all: always matches */ - return (YES); - } else if (string != NULL) { - if (strcasecmp(tok, string) == 0) { /* try exact match */ - return (YES); - } - } else if (strcasecmp(tok, "NONE") == 0) { - return (YES); + if (pam_access_opt_onerr == FAIL) { + return PAM_USER_UNKNOWN; + } else { + return PAM_SUCCESS; + } + } + /* get hostname or tty */ + const_void = NULL; + (void) pam_get_item(pamh, PAM_RHOST, &const_void); + my_rhost = const_void; + if (pam_access_opt_convert_hostname == YES) { + my_hostname = convert_hostname(my_rhost); + } else { + my_hostname = my_rhost; + } + const_void = NULL; + (void) pam_get_item(pamh, PAM_TTY, &const_void); + my_ttyname = const_void; + if (my_hostname == NULL && my_ttyname == NULL) { + const_void = NULL; + (void) pam_get_item(pamh, PAM_SERVICE, &const_void); + my_ttyname = const_void; + MY_PAM_SYSLOG(pamh, LOG_ERR, + "can not determine tty or remote hostname, using service %s", + my_ttyname); + } + /* check it out */ + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "trying %s from %s where file was %s", + pwd->pw_name, my_hostname ? my_hostname : my_ttyname, filename); + } + + retval = pam_access_login_access( + pamh, + pwd, + my_hostname && *my_hostname != '\0' ? my_hostname : my_ttyname, + filename); + + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_access_login_access returns: %d", retval); + } + + if ((retval < 0) && (pam_access_opt_onerr != SUCCESS)) { + retval = PAM_AUTH_ERR; + } else + if (retval == 0) { + retval = PAM_AUTH_ERR; + } else { + retval = PAM_SUCCESS; + } + + if (retval == PAM_AUTH_ERR) { + if (my_hostname && *my_hostname != '\0') { + sprintf(wherefrom, "from %s", my_hostname); + } else { + sprintf(wherefrom, "on %s", my_ttyname); } - return (NO); + MY_PAM_SYSLOG(pamh, LOG_ERR, "%s LOGIN REFUSED %s", username, wherefrom); + } + + return retval; } -/* --- public account management functions --- */ PAM_EXTERN int -pam_sm_acct_mgmt (pam_handle_t *pamh, int flags UNUSED, - int argc, const char **argv) +pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) { - struct login_info loginfo; - const char *user=NULL; - const void *void_from=NULL; - const char *from; - struct passwd *user_pw; + /* process options from pam call */ + pam_access_getopt(pamh, argc, argv); - /* set username */ + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_sm_setcred"); + } - if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL - || *user == '\0') { - pam_syslog(pamh, LOG_ERR, "cannot determine the user's name"); - return PAM_USER_UNKNOWN; - } + /* really nothing to do */ + return(PAM_SUCCESS); +} - /* remote host name */ - if (pam_get_item(pamh, PAM_RHOST, &void_from) - != PAM_SUCCESS) { - pam_syslog(pamh, LOG_ERR, "cannot find the remote host name"); - return PAM_ABORT; - } - from = void_from; +/* --- account management functions --- */ +PAM_EXTERN int +pam_sm_acct_mgmt(pam_handle_t *pamh UNUSED, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) +{ - if ((from==NULL) || (*from=='\0')) { + const char *filename = pam_access_default_config_file; + const void *const_void; + const char *username = NULL; + const char *my_rhost = NULL; + const char *my_hostname = NULL; + const char *my_ttyname = NULL; + struct passwd *pwd = NULL; + int retval; + + /* process options from pam call */ + pam_access_getopt(pamh, argc, argv); + + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_sm_acct_mgmt"); + } + if (pam_access_opt_config_file != NULL) { + filename = pam_access_opt_config_file; + } - /* local login, set tty name */ + if (access(filename, R_OK) == -1) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "Can't read %s: %s", filename, strerror(errno)); - if (pam_get_item(pamh, PAM_TTY, &void_from) != PAM_SUCCESS - || void_from == NULL) { - D(("PAM_TTY not set, probing stdin")); - from = ttyname(STDIN_FILENO); - if (from != NULL) { - if (pam_set_item(pamh, PAM_TTY, from) != PAM_SUCCESS) { - pam_syslog(pamh, LOG_ERR, "couldn't set tty name"); - return PAM_ABORT; - } - } - } - else - from = void_from; + if (pam_access_opt_onerr == FAIL) { + return PAM_PERM_DENIED; + } else { + return PAM_SUCCESS; + } + } + /* get user name */ + retval = pam_get_user(pamh, (const char **) &username, NULL); + if (retval != PAM_SUCCESS) { + + return(retval); + } + pwd = getpwnam(username); + if (pwd == NULL || strcmp(pwd->pw_name,username)) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "Can't get passwd entry for %s", username); + + if (pam_access_opt_onerr == FAIL) { + return PAM_USER_UNKNOWN; + } else { + return PAM_SUCCESS; + } + } - if (from[0] == '/') { /* full path */ - const char *f; - from++; - if ((f = strchr(from, '/')) != NULL) { - from = f + 1; - } - } + /* get hostname or tty */ + const_void = NULL; + (void) pam_get_item(pamh, PAM_RHOST, &const_void); + my_rhost = const_void; + if (pam_access_opt_convert_hostname == YES) { + my_hostname = convert_hostname(my_rhost); + } else { + my_hostname = my_rhost; + } + const_void = NULL; + (void) pam_get_item(pamh, PAM_TTY, &const_void); + my_ttyname = const_void; + if (my_hostname == NULL && my_ttyname == NULL) { + const_void = NULL; + (void) pam_get_item(pamh, PAM_SERVICE, &const_void); + my_ttyname = const_void; + MY_PAM_SYSLOG(pamh, LOG_ERR, + "can not determine tty or remote hostname, using service %s", + my_ttyname); + } + /* check it out */ + retval = pam_access_login_access( + pamh, + pwd, + my_hostname && *my_hostname != '\0' ? my_hostname : my_ttyname, + filename); + + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_access_login_access returns: %d", retval); + } + + if ((retval < 0) && (pam_access_opt_onerr != SUCCESS)) { + retval = PAM_PERM_DENIED; + } else + if (retval == 0) { + retval = PAM_PERM_DENIED; + } else { + retval = PAM_SUCCESS; + } + + if (retval == PAM_PERM_DENIED) { + if (my_hostname && *my_hostname != '\0') { + sprintf(wherefrom, "from %s", my_hostname); + } else { + sprintf(wherefrom, "on %s", my_ttyname); } + MY_PAM_SYSLOG(pamh, LOG_ERR, "%s LOGIN REFUSED %s", username, wherefrom); + } - if ((user_pw=pam_modutil_getpwnam(pamh, user))==NULL) return (PAM_USER_UNKNOWN); + return retval; +} - /* - * Bundle up the arguments to avoid unnecessary clumsiness later on. - */ - loginfo.user = user_pw; - loginfo.from = from; - loginfo.config_file = PAM_ACCESS_CONFIG; +/* --- password management --- */ +PAM_EXTERN int +pam_sm_chauthtok(pam_handle_t *pamh UNUSED, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) +{ + /* process options from pam call */ + pam_access_getopt(pamh, argc, argv); + + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_sm_chauthtok"); + } + + /* really nothing to do */ + return(PAM_AUTHTOK_ERR); +} + +/* --- session management --- */ +PAM_EXTERN int +pam_sm_open_session(pam_handle_t *pamh UNUSED, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) +{ + const char *filename = pam_access_default_config_file; + const void *const_void; + const char *username = NULL; + const char *my_rhost = NULL; + const char *my_hostname = NULL; + const char *my_ttyname = NULL; + struct passwd *pwd = NULL; + int retval; + + /* process options from pam call */ + pam_access_getopt(pamh, argc, argv); + + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_sm_open_session"); + } + if (pam_access_opt_config_file != NULL) { + filename = pam_access_opt_config_file; + } - /* parse the argument list */ + if (access(filename, R_OK) == -1) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "Can't read %s: %s", filename, strerror(errno)); - if (!parse_args(pamh, &loginfo, argc, argv)) { - pam_syslog(pamh, LOG_ERR, "failed to parse the module arguments"); - return PAM_ABORT; + if (pam_access_opt_onerr == FAIL) { + return PAM_SESSION_ERR; + } else { + return PAM_SUCCESS; } + } + + /* get user name */ + retval = pam_get_user(pamh, (const char **) &username, NULL); + if (retval != PAM_SUCCESS) { + return(retval); + } + pwd = getpwnam(username); + if (pwd == NULL || strcmp(pwd->pw_name,username)) { + MY_PAM_SYSLOG(pamh, LOG_ERR, "Can't get passwd entry for %s", username); - if (login_access(pamh, &loginfo)) { - return (PAM_SUCCESS); + if (pam_access_opt_onerr == FAIL) { + return PAM_USER_UNKNOWN; + } else { + return PAM_SUCCESS; + } + } + /* get hostname or tty */ + const_void = NULL; + (void) pam_get_item(pamh, PAM_RHOST, &const_void); + my_rhost = const_void; + if (pam_access_opt_convert_hostname == YES) { + my_hostname = convert_hostname(my_rhost); + } else { + my_hostname = my_rhost; + } + const_void = NULL; + (void) pam_get_item(pamh, PAM_TTY, &const_void); + my_ttyname = const_void; + if (my_hostname == NULL && my_ttyname == NULL) { + const_void = NULL; + (void) pam_get_item(pamh, PAM_SERVICE, &const_void); + my_ttyname = const_void; + MY_PAM_SYSLOG(pamh, LOG_ERR, + "can not determine tty or remote hostname, using service %s", + my_ttyname); + } + /* check it out */ + retval = pam_access_login_access( + pamh, + pwd, + my_hostname && *my_hostname != '\0' ? my_hostname : my_ttyname, + filename); + + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_access_login_access returns: %d", retval); + } + + if ((retval < 0) && (pam_access_opt_onerr != SUCCESS)) { + retval = PAM_SESSION_ERR; + } else + if (retval == 0) { + retval = PAM_SESSION_ERR; + } else { + retval = PAM_SUCCESS; + } + + if (retval == PAM_SESSION_ERR) { + if (my_hostname && *my_hostname != '\0') { + sprintf(wherefrom, "from %s", my_hostname); } else { - pam_syslog(pamh, LOG_ERR, - "access denied for user `%s' from `%s'",user,from); - return (PAM_PERM_DENIED); + sprintf(wherefrom, "on %s", my_ttyname); } + MY_PAM_SYSLOG(pamh, LOG_ERR, "%s LOGIN REFUSED %s", username, wherefrom); + } + + return retval; +} + +PAM_EXTERN int +pam_sm_close_session(pam_handle_t *pamh UNUSED, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) +{ + /* process options from pam call */ + pam_access_getopt(pamh, argc, argv); + + if (pam_access_opt_msg_debug == YES) { + MY_PAM_SYSLOG(pamh, LOG_DEBUG, "pam_sm_close_session"); + } + + /* really nothing to do */ + return(PAM_SUCCESS); } /* end of module definition */ @@ -477,11 +1625,14 @@ struct pam_module _pam_access_modstruct = { "pam_access", - NULL, - NULL, + pam_sm_authenticate, + pam_sm_setcred, pam_sm_acct_mgmt, - NULL, - NULL, - NULL + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok }; -#endif + +#endif /* PAM_STATIC */ + +#endif /* ! PAM_ACCESS_COMPILE_PROGRAM */