pam_access: rework resolving of tokens as hostname

* modules/pam_access/pam_access.c: separate resolving of IP addresses
  from hostnames. Don't resolve TTYs or display variables as hostname
  (#834).
  Add "nodns" option to disallow resolving of tokens as hostname.
* modules/pam_access/pam_access.8.xml: document nodns option
* modules/pam_access/access.conf.5.xml: document that hostnames should
  be written as FQHN.
This commit is contained in:
Thorsten Kukuk 2024-11-14 10:27:28 +01:00
parent 37b416c152
commit 940747f88c
3 changed files with 105 additions and 17 deletions

View File

@ -233,6 +233,10 @@
An IPv6 link local host address must contain the interface An IPv6 link local host address must contain the interface
identifier. IPv6 link local network/netmask is not supported. identifier. IPv6 link local network/netmask is not supported.
</para> </para>
<para>
Hostnames should be written as Fully-Qualified Host Name (FQHN) to avoid
confusion with device names or PAM service names.
</para>
</refsect1> </refsect1>
<refsect1 xml:id="access.conf-see_also"> <refsect1 xml:id="access.conf-see_also">

View File

@ -22,11 +22,14 @@
<arg choice="opt" rep="norepeat"> <arg choice="opt" rep="norepeat">
debug debug
</arg> </arg>
<arg choice="opt" rep="norepeat">
noaudit
</arg>
<arg choice="opt" rep="norepeat"> <arg choice="opt" rep="norepeat">
nodefgroup nodefgroup
</arg> </arg>
<arg choice="opt" rep="norepeat"> <arg choice="opt" rep="norepeat">
noaudit nodns
</arg> </arg>
<arg choice="opt" rep="norepeat"> <arg choice="opt" rep="norepeat">
quiet_log quiet_log
@ -132,6 +135,33 @@
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>
nodefgroup
</term>
<listitem>
<para>
User tokens which are not enclosed in parentheses will not be
matched against the group database. The backwards compatible default is
to try the group database match even for tokens not enclosed
in parentheses.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
nodns
</term>
<listitem>
<para>
Do not try to resolve tokens as hostnames, only IPv4 and IPv6
addresses will be resolved. Which means to allow login from a
remote host, the IP addresses need to be specified in <filename>access.conf</filename>.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term> <term>
quiet_log quiet_log
@ -185,20 +215,6 @@
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>
nodefgroup
</term>
<listitem>
<para>
User tokens which are not enclosed in parentheses will not be
matched against the group database. The backwards compatible default is
to try the group database match even for tokens not enclosed
in parentheses.
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@ -100,6 +100,7 @@ struct login_info {
int only_new_group_syntax; /* Only allow group entries of the form "(xyz)" */ int only_new_group_syntax; /* Only allow group entries of the form "(xyz)" */
int noaudit; /* Do not audit denials */ int noaudit; /* Do not audit denials */
int quiet_log; /* Do not log denials */ int quiet_log; /* Do not log denials */
int nodns; /* Do not try to resolve tokens as hostnames */
const char *fs; /* field separator */ const char *fs; /* field separator */
const char *sep; /* list-element separator */ const char *sep; /* list-element separator */
int from_remote_host; /* If PAM_RHOST was used for from */ int from_remote_host; /* If PAM_RHOST was used for from */
@ -154,6 +155,8 @@ parse_args(pam_handle_t *pamh, struct login_info *loginfo,
loginfo->noaudit = YES; loginfo->noaudit = YES;
} else if (strcmp (argv[i], "quiet_log") == 0) { } else if (strcmp (argv[i], "quiet_log") == 0) {
loginfo->quiet_log = YES; loginfo->quiet_log = YES;
} else if (strcmp (argv[i], "nodns") == 0) {
loginfo->nodns = YES;
} else { } else {
pam_syslog(pamh, LOG_ERR, "unrecognized option [%s]", argv[i]); pam_syslog(pamh, LOG_ERR, "unrecognized option [%s]", argv[i]);
} }
@ -820,7 +823,7 @@ remote_match (pam_handle_t *pamh, char *tok, struct login_info *item)
if ((str_len = strlen(string)) > tok_len if ((str_len = strlen(string)) > tok_len
&& strcasecmp(tok, string + str_len - tok_len) == 0) && strcasecmp(tok, string + str_len - tok_len) == 0)
return YES; return YES;
} else if (tok[tok_len - 1] == '.') { /* internet network numbers (end with ".") */ } else if (tok[tok_len - 1] == '.') { /* internet network numbers/subnet (end with ".") */
struct addrinfo hint; struct addrinfo hint;
memset (&hint, '\0', sizeof (hint)); memset (&hint, '\0', sizeof (hint));
@ -895,6 +898,39 @@ string_match (pam_handle_t *pamh, const char *tok, const char *string,
} }
static int
is_device (pam_handle_t *pamh, const char *tok)
{
struct stat st;
const char *dev = "/dev/";
char *devname;
devname = malloc (strlen(dev) + strlen (tok) + 1);
if (devname == NULL) {
pam_syslog(pamh, LOG_ERR, "Cannot allocate memory for device name: %m");
/*
* We should return an error and abort, but pam_access has no good
* error handling.
*/
return NO;
}
char *cp = stpcpy (devname, dev);
strcpy (cp, tok);
if (lstat(devname, &st) != 0)
{
free (devname);
return NO;
}
free (devname);
if (S_ISCHR(st.st_mode))
return YES;
return NO;
}
/* network_netmask_match - match a string against one token /* network_netmask_match - match a string against one token
* where string is a hostname or ip (v4,v6) address and tok * where string is a hostname or ip (v4,v6) address and tok
* represents either a hostname, a single ip (v4,v6) address * represents either a hostname, a single ip (v4,v6) address
@ -956,10 +992,42 @@ network_netmask_match (pam_handle_t *pamh,
return NO; return NO;
} }
} }
else if (isipaddr(tok, NULL, NULL) == YES)
{
if (getaddrinfo (tok, NULL, NULL, &ai) != 0)
{
if (item->debug)
pam_syslog(pamh, LOG_DEBUG, "cannot resolve IP address \"%s\"", tok);
return NO;
}
netmask_ptr = NULL;
}
else if (item->nodns)
{
/* Only hostnames are left, which we would need to resolve via DNS */
return NO;
}
else else
{ {
/* Bail out on X11 Display entries and ttys. */
if (tok[0] == ':')
{
if (item->debug)
pam_syslog (pamh, LOG_DEBUG,
"network_netmask_match: tok=%s is X11 display", tok);
return NO;
}
if (is_device (pamh, tok))
{
if (item->debug)
pam_syslog (pamh, LOG_DEBUG,
"network_netmask_match: tok=%s is a TTY", tok);
return NO;
}
/* /*
* It is either an IP address or a hostname. * It is most likely a hostname.
* Let getaddrinfo sort everything out * Let getaddrinfo sort everything out
*/ */
if (getaddrinfo (tok, NULL, NULL, &ai) != 0) if (getaddrinfo (tok, NULL, NULL, &ai) != 0)