diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:01:37 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:01:37 +0000 |
commit | de848d9e9146434817c65d74d1d0313e9d729462 (patch) | |
tree | dcbd0efb229b17f696f7195671f05b354b4f70fc /modules/pam_access/pam_access.c | |
parent | Initial commit. (diff) | |
download | pam-upstream.tar.xz pam-upstream.zip |
Adding upstream version 1.4.0.upstream/1.4.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | modules/pam_access/pam_access.c | 973 |
1 files changed, 973 insertions, 0 deletions
diff --git a/modules/pam_access/pam_access.c b/modules/pam_access/pam_access.c new file mode 100644 index 0000000..98848c5 --- /dev/null +++ b/modules/pam_access/pam_access.c @@ -0,0 +1,973 @@ +/* + * pam_access module + * + * Written by Alexei Nogin <alexei@nogin.dnttm.ru> 1997/06/15 + * (I took login_access from logdaemon-5.6 and converted it to PAM + * using parts of pam_time code.) + * + ************************************************************************ + * Copyright message from logdaemon-5.6 (original file name DISCLAIMER) + ************************************************************************ + * Copyright 1995 by Wietse Venema. All rights reserved. Individual files + * may be covered by other copyrights (as noted in the file itself.) + * + * This material was originally written and compiled by Wietse Venema at + * Eindhoven University of Technology, The Netherlands, in 1990, 1991, + * 1992, 1993, 1994 and 1995. + * + * Redistribution and use in source and binary forms are permitted + * provided that this entire copyright notice is duplicated in all such + * copies. + * + * This software is provided "as is" and without any expressed or implied + * warranties, including, without limitation, the implied warranties of + * merchantability and fitness for any particular purpose. + ************************************************************************* + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <stdarg.h> +#include <syslog.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <pwd.h> +#include <grp.h> +#include <errno.h> +#include <ctype.h> +#include <sys/utsname.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/socket.h> +#include <glob.h> +#ifdef HAVE_LIBAUDIT +#include <libaudit.h> +#endif + +#include <security/_pam_macros.h> +#include <security/pam_modules.h> +#include <security/pam_modutil.h> +#include <security/pam_ext.h> +#include "pam_cc_compat.h" +#include "pam_inline.h" + +/* login_access.c from logdaemon-5.6 with several changes by A.Nogin: */ + + /* + * 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). + * + * 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. */ + + +#define ALL 2 +#define YES 1 +#define NO 0 +#define NOMATCH -1 + + /* + * A structure to bundle up all login-related information to keep the + * functional interfaces as generic as possible. + */ +struct login_info { + const struct passwd *user; + const char *from; + const char *config_file; + const char *hostname; + int debug; /* Print debugging messages. */ + int only_new_group_syntax; /* Only allow group entries of the form "(xyz)" */ + int noaudit; /* Do not audit denials */ + const char *fs; /* field separator */ + const char *sep; /* list-element separator */ + int from_remote_host; /* If PAM_RHOST was used for from */ + struct addrinfo *res; /* Cached DNS resolution of from */ + int gai_rv; /* Cached retval of getaddrinfo */ +}; + +/* Parse module config arguments */ + +static int +parse_args(pam_handle_t *pamh, struct login_info *loginfo, + int argc, const char **argv) +{ + int i; + + loginfo->noaudit = NO; + loginfo->debug = NO; + loginfo->only_new_group_syntax = NO; + loginfo->fs = ":"; + loginfo->sep = ", \t"; + for (i=0; i<argc; ++i) { + const char *str; + + if ((str = pam_str_skip_prefix(argv[i], "fieldsep=")) != NULL) { + + /* the admin wants to override the default field separators */ + loginfo->fs = str; + + } else if ((str = pam_str_skip_prefix(argv[i], "listsep=")) != NULL) { + + /* the admin wants to override the default list separators */ + loginfo->sep = str; + + } else if ((str = pam_str_skip_prefix(argv[i], "accessfile=")) != NULL) { + FILE *fp = fopen(str, "r"); + + if (fp) { + loginfo->config_file = str; + fclose(fp); + } else { + pam_syslog(pamh, LOG_ERR, + "failed to open accessfile=[%s]: %m", str); + return 0; + } + + } else if (strcmp (argv[i], "debug") == 0) { + loginfo->debug = YES; + } else if (strcmp (argv[i], "nodefgroup") == 0) { + loginfo->only_new_group_syntax = YES; + } else if (strcmp (argv[i], "noaudit") == 0) { + loginfo->noaudit = YES; + } else { + pam_syslog(pamh, LOG_ERR, "unrecognized option [%s]", argv[i]); + } + } + + return 1; /* OK */ +} + +/* --- static functions for checking whether the user should be let in --- */ + +typedef int match_func (pam_handle_t *, char *, struct login_info *); + +static int list_match (pam_handle_t *, char *, char *, struct login_info *, + match_func *); +static int user_match (pam_handle_t *, char *, struct login_info *); +static int group_match (pam_handle_t *, const char *, const char *, int); +static int from_match (pam_handle_t *, char *, struct login_info *); +static int string_match (pam_handle_t *, const char *, const char *, int); +static int network_netmask_match (pam_handle_t *, const char *, const char *, struct login_info *); + + +/* isipaddr - find out if string provided is an IP address or not */ + +static int +isipaddr (const char *string, int *addr_type, + struct sockaddr_storage *addr) +{ + struct sockaddr_storage local_addr; + int is_ip; + + /* 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. + */ + + if (addr == NULL) + addr = &local_addr; + + memset(addr, 0, sizeof(struct sockaddr_storage)); + + /* first ipv4 */ + if (inet_pton(AF_INET, string, addr) > 0) + { + if (addr_type != NULL) + *addr_type = AF_INET; + + is_ip = YES; + } + else if (inet_pton(AF_INET6, string, addr) > 0) + { /* then ipv6 */ + if (addr_type != NULL) { + *addr_type = AF_INET6; + } + is_ip = YES; + } + else + is_ip = NO; + + return is_ip; +} + + +/* are_addresses_equal - translate IP address strings to real IP + * addresses and compare them to find out if they are equal. + * If netmask was provided it will be used to focus comparison to + * relevant bits. + */ +static int +are_addresses_equal (const char *ipaddr0, const char *ipaddr1, + const char *netmask) +{ + struct sockaddr_storage addr0; + struct sockaddr_storage addr1; + int addr_type0 = 0; + int addr_type1 = 0; + + if (isipaddr (ipaddr0, &addr_type0, &addr0) == NO) + return NO; + + if (isipaddr (ipaddr1, &addr_type1, &addr1) == NO) + return NO; + + if (addr_type0 != addr_type1) + /* different address types */ + return NO; + + if (netmask != NULL) { + /* Got a netmask, so normalize addresses? */ + struct sockaddr_storage nmask; + unsigned char *byte_a, *byte_nm; + + memset(&nmask, 0, sizeof(struct sockaddr_storage)); + if (inet_pton(addr_type0, netmask, (void *)&nmask) > 0) { + unsigned int i; + byte_a = (unsigned char *)(&addr0); + byte_nm = (unsigned char *)(&nmask); + for (i=0; i<sizeof(struct sockaddr_storage); i++) { + byte_a[i] = byte_a[i] & byte_nm[i]; + } + + byte_a = (unsigned char *)(&addr1); + byte_nm = (unsigned char *)(&nmask); + for (i=0; i<sizeof(struct sockaddr_storage); i++) { + byte_a[i] = byte_a[i] & byte_nm[i]; + } + } + } + + + /* Are the two addresses equal? */ + if (memcmp((void *)&addr0, (void *)&addr1, + sizeof(struct sockaddr_storage)) == 0) { + return(YES); + } + + return(NO); +} + +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<ip_bytes; i++) { + if (netmask >= 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 (NULL); +} + +/* login_access - match username/group and host/tty with access control file */ + +static int +login_access (pam_handle_t *pamh, struct login_info *item) +{ + 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; +#ifdef HAVE_LIBAUDIT + int nonall_match = NO; +#endif + int end; + int lineno = 0; /* for diagnostics */ + char *sptr; + + if (item->debug) + pam_syslog (pamh, LOG_DEBUG, + "login_access: user=%s, from=%s, file=%s", + item->user->pw_name, + item->from, item->config_file); + + /* + * 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(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 field separator in last field of froms */ + if (!(perm = strtok_r(line, item->fs, &sptr)) + || !(users = strtok_r(NULL, item->fs, &sptr)) + || !(froms = strtok_r(NULL, "\n", &sptr))) { + 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; + } + if (item->debug) + pam_syslog (pamh, LOG_DEBUG, + "line %d: %s : %s : %s", lineno, perm, users, froms); + match = list_match(pamh, users, NULL, item, user_match); + if (item->debug) + pam_syslog (pamh, LOG_DEBUG, "user_match=%d, \"%s\"", + match, item->user->pw_name); + if (match) { + match = list_match(pamh, froms, NULL, item, from_match); +#ifdef HAVE_LIBAUDIT + if (!match && perm[0] == '+') { + nonall_match = YES; + } +#endif + if (item->debug) + pam_syslog (pamh, LOG_DEBUG, + "from_match=%d, \"%s\"", match, item->from); + } + } + (void) fclose(fp); + } else if (errno == ENOENT) { + /* This is no error. */ + pam_syslog(pamh, LOG_WARNING, "warning: cannot open %s: %m", + item->config_file); + } else { + pam_syslog(pamh, LOG_ERR, "cannot open %s: %m", item->config_file); + return NO; + } +#ifdef HAVE_LIBAUDIT + if (!item->noaudit && (match == YES || (match == ALL && + nonall_match == YES)) && line[0] == '-') { + pam_modutil_audit_write(pamh, AUDIT_ANOM_LOGIN_LOCATION, + "pam_access", 0); + } +#endif + if (match == NO) + return NOMATCH; + if (line[0] == '+') + return YES; + return NO; +} + + +/* list_match - match an item against a list of tokens with exceptions */ + +static int +list_match(pam_handle_t *pamh, char *list, char *sptr, + struct login_info *item, match_func *match_fn) +{ + char *tok; + int match = NO; + + if (item->debug && list != NULL) + pam_syslog (pamh, LOG_DEBUG, + "list_match: list=%s, item=%s", list, item->user->pw_name); + + /* + * 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_r(list, item->sep, &sptr); tok != 0; + tok = strtok_r(NULL, item->sep, &sptr)) { + 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_r(NULL, item->sep, &sptr)) && strcasecmp(tok, "EXCEPT")) + /* VOID */ ; + if (tok == 0) + return match; + if (list_match(pamh, NULL, sptr, item, match_fn) == NO) + return YES; /* drop special meaning of ALL */ + } + return (NO); +} + +/* netgroup_match - match group against machine or user */ + +static int +netgroup_match (pam_handle_t *pamh, const char *netgroup, + const char *machine, const char *user, int debug) +{ + int retval; + char *mydomain = NULL; + +#ifdef HAVE_GETDOMAINNAME + char domainname_res[256]; + + if (getdomainname (domainname_res, sizeof (domainname_res)) == 0) + { + if (domainname_res[0] != '\0' && strcmp (domainname_res, "(none)") != 0) + { + mydomain = domainname_res; + } + } +#endif + +#ifdef HAVE_INNETGR + retval = innetgr (netgroup, machine, user, mydomain); +#else + retval = 0; + pam_syslog (pamh, LOG_ERR, "pam_access does not have netgroup support"); +#endif + if (debug == YES) + pam_syslog (pamh, LOG_DEBUG, + "netgroup_match: %d (netgroup=%s, machine=%s, user=%s, domain=%s)", + retval, netgroup ? netgroup : "NULL", + machine ? machine : "NULL", + user ? user : "NULL", mydomain ? mydomain : "NULL"); + return retval; +} + +/* user_match - match a username against one token */ + +static int +user_match (pam_handle_t *pamh, char *tok, struct login_info *item) +{ + char *string = item->user->pw_name; + struct login_info fake_item; + char *at; + int rv; + + if (item->debug) + pam_syslog (pamh, LOG_DEBUG, + "user_match: tok=%s, item=%s", 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. + */ + + /* Try to split on a pattern (@*[^@]+)(@+.*) */ + for (at = tok; *at == '@'; ++at); + + if (tok[0] == '(' && tok[strlen(tok) - 1] == ')') { + return (group_match (pamh, tok, string, item->debug)); + } else if ((at = strchr(at, '@')) != NULL) { + /* split user@host pattern */ + if (item->hostname == NULL) + return NO; + memcpy (&fake_item, item, sizeof(fake_item)); + fake_item.from = item->hostname; + fake_item.gai_rv = 0; + fake_item.res = NULL; + fake_item.from_remote_host = 1; /* hostname should be resolvable */ + *at = 0; + if (!user_match (pamh, tok, item)) + return NO; + rv = from_match (pamh, at + 1, &fake_item); + if (fake_item.gai_rv == 0 && fake_item.res) + freeaddrinfo(fake_item.res); + return rv; + } else if (tok[0] == '@') { /* netgroup */ + const char *hostname = NULL; + if (tok[1] == '@') { /* add hostname to netgroup match */ + if (item->hostname == NULL) + return NO; + ++tok; + hostname = item->hostname; + } + return (netgroup_match (pamh, tok + 1, hostname, string, item->debug)); + } else if ((rv=string_match (pamh, tok, string, item->debug)) != NO) /* ALL or exact match */ + return rv; + else if (item->only_new_group_syntax == NO && + pam_modutil_user_in_group_nam_nam (pamh, + item->user->pw_name, tok)) + /* try group membership */ + return YES; + + return NO; +} + + +/* group_match - match a username against token named group */ + +static int +group_match (pam_handle_t *pamh, const char *tok, const char* usr, + int debug) +{ + char grptok[BUFSIZ]; + + if (debug) + pam_syslog (pamh, LOG_DEBUG, + "group_match: grp=%s, user=%s", tok, usr); + + if (strlen(tok) < 3) + return NO; + + /* token is received under the format '(...)' */ + memset(grptok, 0, BUFSIZ); + strncpy(grptok, tok + 1, strlen(tok) - 2); + + if (pam_modutil_user_in_group_nam_nam(pamh, usr, grptok)) + return YES; + + return NO; +} + + +/* from_match - match a host or tty against a list of tokens */ + +static int +from_match (pam_handle_t *pamh UNUSED, char *tok, struct login_info *item) +{ + const char *string = item->from; + int tok_len; + int str_len; + int rv; + + if (item->debug) + pam_syslog (pamh, LOG_DEBUG, + "from_match: tok=%s, item=%s", tok, string); + + /* + * 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 from field was + * not taken by PAM_RHOST. If the token is a network number, return YES + * if it matches the head of the string. + */ + + if (string == NULL) { + return NO; + } else if (tok[0] == '@') { /* netgroup */ + return (netgroup_match (pamh, tok + 1, string, (char *) 0, item->debug)); + } else if ((rv = string_match(pamh, tok, string, item->debug)) != NO) { + /* ALL or exact match */ + return rv; + } 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 (item->from_remote_host == 0) { /* local: no PAM_RHOSTS */ + if (strcasecmp(tok, "LOCAL") == 0) + return (YES); + } else if (tok[(tok_len = strlen(tok)) - 1] == '.') { + struct addrinfo hint; + + memset (&hint, '\0', sizeof (hint)); + hint.ai_flags = AI_CANONNAME; + hint.ai_family = AF_INET; + + if (item->gai_rv != 0) + return NO; + else if (!item->res && + (item->gai_rv = getaddrinfo (string, NULL, &hint, &item->res)) != 0) + return NO; + else + { + struct addrinfo *runp = item->res; + + while (runp != NULL) + { + char buf[INET_ADDRSTRLEN+2]; + + if (runp->ai_family == AF_INET) + { + DIAG_PUSH_IGNORE_CAST_ALIGN; + inet_ntop (runp->ai_family, + &((struct sockaddr_in *) runp->ai_addr)->sin_addr, + buf, sizeof (buf)); + DIAG_POP_IGNORE_CAST_ALIGN; + + strcat (buf, "."); + + if (strncmp(tok, buf, tok_len) == 0) + { + return YES; + } + } + runp = runp->ai_next; + } + } + } else { + /* Assume network/netmask with a IP of a host. */ + if (network_netmask_match(pamh, tok, string, item)) + return YES; + } + + return NO; +} + +/* string_match - match a string against one token */ + +static int +string_match (pam_handle_t *pamh, const char *tok, const char *string, + int debug) +{ + + if (debug) + pam_syslog (pamh, LOG_DEBUG, + "string_match: tok=%s, item=%s", tok, string); + + /* + * 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 (strcasecmp(tok, "ALL") == 0) { /* all: always matches */ + return (ALL); + } else if (string != NULL) { + if (strcasecmp(tok, string) == 0) { /* try exact match */ + return (YES); + } + } else if (strcasecmp(tok, "NONE") == 0) { + return (YES); + } + return (NO); +} + + +/* network_netmask_match - match a string against one token + * where string is a hostname or ip (v4,v6) address and tok + * represents either a single ip (v4,v6) address or a network/netmask + */ +static int +network_netmask_match (pam_handle_t *pamh, + const char *tok, const char *string, struct login_info *item) +{ + char *netmask_ptr; + char netmask_string[MAXHOSTNAMELEN + 1]; + int addr_type; + + if (item->debug) + pam_syslog (pamh, LOG_DEBUG, + "network_netmask_match: tok=%s, item=%s", tok, string); + /* 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, NULL) == NO) + { /* no netaddr */ + return NO; + } + + /* check netmask */ + if (isipaddr(netmask_ptr, NULL, NULL) == NO) + { /* netmask as integre value */ + char *endptr = NULL; + netmask = strtol(netmask_ptr, &endptr, 0); + if ((endptr == netmask_ptr) || (*endptr != '\0')) + { /* invalid netmask value */ + return NO; + } + if ((netmask < 0) + || (addr_type == AF_INET && netmask > 32) + || (addr_type == AF_INET6 && netmask > 128)) + { /* netmask value out of range */ + return NO; + } + + netmask_ptr = number_to_netmask(netmask, addr_type, + netmask_string, MAXHOSTNAMELEN); + } + } + else + /* NO, then check if it is only an addr */ + if (isipaddr(tok, NULL, NULL) != YES) + { + return NO; + } + + if (isipaddr(string, NULL, NULL) != YES) + { + /* Assume network/netmask with a name of a host. */ + struct addrinfo hint; + + memset (&hint, '\0', sizeof (hint)); + hint.ai_flags = AI_CANONNAME; + hint.ai_family = AF_UNSPEC; + + if (item->gai_rv != 0) + return NO; + else if (!item->res && + (item->gai_rv = getaddrinfo (string, NULL, &hint, &item->res)) != 0) + return NO; + else + { + struct addrinfo *runp = item->res; + + while (runp != NULL) + { + char buf[INET6_ADDRSTRLEN]; + + DIAG_PUSH_IGNORE_CAST_ALIGN; + inet_ntop (runp->ai_family, + runp->ai_family == AF_INET + ? (void *) &((struct sockaddr_in *) runp->ai_addr)->sin_addr + : (void *) &((struct sockaddr_in6 *) runp->ai_addr)->sin6_addr, + buf, sizeof (buf)); + DIAG_POP_IGNORE_CAST_ALIGN; + + if (are_addresses_equal(buf, tok, netmask_ptr)) + { + return YES; + } + runp = runp->ai_next; + } + } + } + else + return (are_addresses_equal(string, tok, netmask_ptr)); + + return NO; +} + + +/* --- public PAM management functions --- */ + +int +pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED, + int argc, const char **argv) +{ + struct login_info loginfo; + const char *user=NULL; + const void *void_from=NULL; + const char *from; + const char *default_config = PAM_ACCESS_CONFIG; + struct passwd *user_pw; + char hostname[MAXHOSTNAMELEN + 1]; + int rv; + + + /* set username */ + + if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) { + pam_syslog(pamh, LOG_NOTICE, "cannot determine user name"); + return PAM_USER_UNKNOWN; + } + + if ((user_pw=pam_modutil_getpwnam(pamh, user))==NULL) + return (PAM_USER_UNKNOWN); + + /* + * Bundle up the arguments to avoid unnecessary clumsiness later on. + */ + memset(&loginfo, '\0', sizeof(loginfo)); + loginfo.user = user_pw; + loginfo.config_file = default_config; + + /* parse the argument list */ + + if (!parse_args(pamh, &loginfo, argc, argv)) { + pam_syslog(pamh, LOG_ERR, "failed to parse the module arguments"); + return PAM_ABORT; + } + + /* 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; + + if ((from==NULL) || (*from=='\0')) { + + /* local login, set tty name */ + + loginfo.from_remote_host = 0; + + 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_WARNING, "couldn't set tty name"); + } else { + if (pam_get_item(pamh, PAM_SERVICE, &void_from) != PAM_SUCCESS + || void_from == NULL) { + pam_syslog (pamh, LOG_ERR, + "cannot determine remote host, tty or service name"); + return PAM_ABORT; + } + from = void_from; + if (loginfo.debug) + pam_syslog (pamh, LOG_DEBUG, + "cannot determine tty or remote hostname, using service %s", + from); + } + } + else + from = void_from; + + if (from[0] == '/') { /* full path, remove device path. */ + const char *f; + from++; + if ((f = strchr(from, '/')) != NULL) { + from = f + 1; + } + } + } + else + loginfo.from_remote_host = 1; + + loginfo.from = from; + + hostname[sizeof(hostname)-1] = '\0'; + if (gethostname(hostname, sizeof(hostname)-1) == 0) + loginfo.hostname = hostname; + else { + pam_syslog (pamh, LOG_ERR, "gethostname failed: %m"); + loginfo.hostname = NULL; + } + + rv = login_access(pamh, &loginfo); + + if (rv == NOMATCH && loginfo.config_file == default_config) { + glob_t globbuf; + int i, glob_rv; + + /* We do not manipulate locale as setlocale() is not + * thread safe. We could use uselocale() in future. + */ + glob_rv = glob(ACCESS_CONF_GLOB, GLOB_ERR, NULL, &globbuf); + if (!glob_rv) { + /* Parse the *.conf files. */ + for (i = 0; globbuf.gl_pathv[i] != NULL; i++) { + loginfo.config_file = globbuf.gl_pathv[i]; + rv = login_access(pamh, &loginfo); + if (rv != NOMATCH) + break; + } + globfree(&globbuf); + } + } + + if (loginfo.gai_rv == 0 && loginfo.res) + freeaddrinfo(loginfo.res); + + if (rv) { + return (PAM_SUCCESS); + } else { + pam_syslog(pamh, LOG_ERR, + "access denied for user `%s' from `%s'",user,from); + return (PAM_PERM_DENIED); + } +} + +int +pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) +{ + return PAM_IGNORE; +} + +int +pam_sm_acct_mgmt (pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + return pam_sm_authenticate (pamh, flags, argc, argv); +} + +int +pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + return pam_sm_authenticate(pamh, flags, argc, argv); +} + +int +pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + return pam_sm_authenticate(pamh, flags, argc, argv); +} + +int +pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + return pam_sm_authenticate(pamh, flags, argc, argv); +} + +/* end of module definition */ |