pam_login_access vs. pam_access

Thorsten Kukuk kukuk at suse.de
Mon Jan 30 22:10:30 UTC 2006


On Fri, Jan 27, Thorsten Kukuk wrote:

> On Thu, Jan 05, Mike Becher wrote:
> 
> > Hi again,
> > 
> > because I don't know whether my patch for pam_access module (please
> > have a look at forwarded message but without patch) will be accepted
> > by list moderator or not (message was too large, larger than 40kB
> > because patch size is 100735 bytes) I post it again but now in 5
> > pieces in messages with subject: "pam_access patch part X of 5"
> > 
> > I hope this code finds the way into official distribution of
> > Linux-PAM.
> 
> I looked at it and the code is terrible. My first step will be to
> merge only the basic stuff like netmasks and IPv6, not the external
> helper and compatibility hacks.

Attached is my patch against current CVS. Using IP addresses in
access.conf works now, even if PAM_RHOSTS is set to a name. It
also looks at all IP addresses, not only the first one.

This patch is topic for discussion, at least the access.conf.5 manual
page needs some rework.

  Thorsten

-- 
Thorsten Kukuk         http://www.suse.de/~kukuk/      kukuk at suse.de
SUSE LINUX Products GmbH       Maxfeldstr. 5       D-90409 Nuernberg
--------------------------------------------------------------------    
Key fingerprint = A368 676B 5E1B 3E46 CFCE  2D97 F8FD 4E23 56C6 FB4B
-------------- next part --------------
--- configure.in	27 Jan 2006 11:44:38 -0000	1.71
+++ configure.in	30 Jan 2006 21:59:19 -0000
@@ -337,6 +337,9 @@
 AM_CONDITIONAL([HAVE_LIBDB], [test ! -z "$LIBDB"])
 
 AC_CHECK_LIB([nsl],[yp_get_default_domain], LIBNSL="-lnsl", LIBNSL="")
+LIBS="$LIBS $LIBNSL"
+AC_CHECK_FUNCS(yp_get_default_domain)
+LIBS=$BACKUP_LIBS
 AC_SUBST(LIBNSL)
 
 AC_CHECK_LIB([selinux],[getfilecon], LIBSELINUX="-lselinux", LIBSELINUX="")
@@ -383,6 +386,7 @@
 AC_CHECK_FUNCS(strcspn strdup strspn strstr strtol uname)
 AC_CHECK_FUNCS(getpwnam_r getpwuid_r getgrnam_r getgrgid_r getspnam_r)
 AC_CHECK_FUNCS(getgrouplist getline getdelim)
+AC_CHECK_FUNCS(inet_ntop inet_pton)
 
 dnl Checks for programs/utilities
 AC_CHECK_PROG(SGML2PS, sgml2ps, yes, no)
--- modules/pam_access/Makefile.am	21 Sep 2005 13:27:11 -0000	1.5
+++ modules/pam_access/Makefile.am	30 Jan 2006 21:59:19 -0000
@@ -1,10 +1,13 @@
 #
-# Copyright (c) 2005 Thorsten Kukuk <kukuk at suse.de>
+# Copyright (c) 2005, 2006 Thorsten Kukuk <kukuk at thkukuk.de>
 #
 
 CLEANFILES = *~
 
-EXTRA_DIST = README access.conf
+EXTRA_DIST = README access.conf $(MANS) $(XMLS)
+
+man_MANS = access.conf.5 login.access.5 pam_access.8
+man_XMLS = access.conf.5.xml pam_access.8.xml
 
 securelibdir = $(SECUREDIR)
 secureconfdir = $(SCONFIGDIR)
@@ -20,3 +23,7 @@
 securelib_LTLIBRARIES = pam_access.la
 
 secureconf_DATA = access.conf
+
+if ENABLE_REGENERATE_MAN
+-include $(top_srcdir)/Make.xml.rules
+endif
--- modules/pam_access/access.conf	26 Sep 2005 09:56:28 -0000	1.5
+++ modules/pam_access/access.conf	30 Jan 2006 21:59:19 -0000
@@ -1,5 +1,8 @@
 # Login access control table.
 # 
+# Comment line must start with "#", no space at front.
+# Order of lines is important.
+#
 # When someone logs in, the table is scanned for the first entry that
 # matches the (user, host) combination, or, in case of non-networked
 # logins, the first entry that matches the (user, tty) combination.  The
@@ -31,8 +34,8 @@
 # matches), NONE (matches no tty on non-networked logins) or
 # LOCAL (matches any string that does not contain a "." character).
 #
-# If you run NIS you can use @netgroupname in host or user patterns; this
-# even works for @usergroup@@hostgroup patterns. Weird.
+# You can use @netgroupname in host or user patterns; this even works
+# for @usergroup@@hostgroup patterns.
 #
 # The EXCEPT operator makes it possible to write very compact rules.
 #
@@ -63,3 +66,49 @@
 #
 # All other accounts are allowed to login from anywhere.
 #
+##############################################################################
+# All lines from here up to the end are building a more complex example.
+##############################################################################
+#
+# User "root" should be allowed to get access via cron .. tty5 tty6.
+#+ : root : cron crond :0 tty1 tty2 tty3 tty4 tty5 tty6
+#
+# User "root" should be allowed to get access from hosts with ip addresses.
+#+ : root : 192.168.200.1 192.168.200.4 192.168.200.9
+#+ : root : 127.0.0.1
+#
+# User "root" should get access from network 192.168.201.
+# This term will be evaluated by string matching.
+# comment: It might be better to use network/netmask instead.
+#          The same is 192.168.201.0/24 or 192.168.201.0/255.255.255.0
+#+ : root : 192.168.201.
+#
+# User "root" should be able to have access from domain.
+# Uses string matching also.
+#+ : root : .foo.bar.org
+#
+# User "root" should be denied to get access from all other sources. 
+#- : root : ALL
+#
+# User "foo" and members of netgroup "nis_group" should be
+# allowed to get access from all sources.
+# This will only work if netgroup service is available.
+#+ : @nis_group foo : ALL
+#
+# User "john" should get access from ipv4 net/mask
+#+ : john : 127.0.0.0/24
+#
+# User "john" should get access from ipv4 as ipv6 net/mask
+#+ : john : ::ffff:127.0.0.0/127
+#
+# User "john" should get access from ipv6 host address
+#+ : john : 2001:4ca0:0:101::1
+#
+# User "john" should get access from ipv6 host address (same as above)
+#+ : john : 2001:4ca0:0:101:0:0:0:1
+#
+# User "john" should get access from ipv6 net/mask
+#+ : john : 2001:4ca0:0:101::/64
+#
+# All other users should be denied to get access from all sources.
+#- : ALL : ALL 
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/pam_access/access.conf.5	30 Jan 2006 21:59:19 -0000
@@ -0,0 +1,228 @@
+.\" ** You probably do not want to edit this file directly **
+.\" It was generated using the DocBook XSL Stylesheets (version 1.69.1).
+.\" Instead of manually editing it, you probably should edit the DocBook XML
+.\" source for it and then use the DocBook XSL Stylesheets to regenerate it.
+.TH "ACCESS.CONF" "5" "12 December 2005" "Version 1.14" "Reference Manual"
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.SH "NAME"
+access.conf \- The login access control table file
+.SH "DESCRIPTION"
+.PP
+Original
+\fBlogin.access\fR(5)
+manual was provided by
+\fIGuido van Rooij\fR
+which was renamed to
+\fBaccess.conf\fR(5)
+to reflect relation to default config file. The
+\fIaccess.conf\fR
+file specifies (\fIuser\fR,
+\fIhost\fR), (\fIuser\fR,
+\fInetwork/netmask\fR) or (\fIuser\fR,
+\fItty\fR) combinations for which a login will be either accepted or refused.
+.PP
+When someone logs in, the file
+\fIaccess.conf\fR
+is scanned for the first entry that matches the (\fIuser\fR,
+\fIhost\fR) or (\fIuser\fR,
+\fInetwork/netmask\fR) combination, or, in case of non\-networked logins, the first entry that matches the (\fIuser\fR,
+\fItty\fR) combination. The permissions field of that table entry determines whether the login will be accepted or refused.
+.PP
+Each line of the login access control table has three fields separated by a
+\fI:\fR
+character (colon) and looks like:
+.PP
+\fIPERMISSION\fR
+:
+\fIUSERS\fR
+:
+\fIORIGINS\fR
+.PP
+The first field, the
+\fIPERMISSION\fR
+field, can be either a
+\fI+\fR
+character (plus) for access granted or a
+\fI\-\fR
+character (minus) for access denied.
+.PP
+The second field, the
+\fIUSERS\fR
+field, should be a list of one or more login names, group names, or
+\fIALL\fR
+(which always matches).
+.PP
+The third field, the
+\fIORIGINS\fR
+field, should be a list of one or more tty names (for non\-networked logins), host names, domain names (begin with "."), host addresses, internet network numbers (end with "."), internet network addresses with network mask (where network mask can be a decimal number or an internet address also),
+\fIALL\fR
+(which always matches) or
+\fILOCAL\fR
+(which matches any string that does not contain a "." character). If you run NIS you can use
+\fI@\fR\fInetgroupname\fR
+in host or user patterns.
+.PP
+The
+\fIEXCEPT\fR
+operator makes it possible to write very compact rules.
+.PP
+The group file is searched only when a name does not match that of the logged\-in user. Only groups are matched in which users are explicitly listed: the program does not look at a user's primary group id value.
+.PP
+The
+\fI#\fR
+character at start of line (no space at front) can be used to mark this line as a comment line.
+.PP
+\fIHINT:\fR
+.PP
+It is a good idea to specify a line like
+.PP
+\fI + : ALL : ALL \fR
+.PP
+or
+.PP
+\fI \- : ALL : ALL \fR
+.PP
+as last line in access control files. So it is clear that all users that aren't matched by lines before are getting access granted or denied. If you don't do this a user gets access to a service if access was not explicitly denied for him through a rule.
+.SH "EXAMPLES"
+.PP
+These are some example lines which might be specified in
+\fIaccess.conf\fR
+file.
+.PP
+User
+\fIroot\fR
+should be allowed to get access via
+\fIsu\fR,
+\fIcron\fR,
+\fIxdm\fR, X11 terminal
+\fI:0\fR, ...,
+\fItty5\fR\fItty6\fR.
+.PP
++ : root : su cron crond xdm :0 tty1 tty2 tty3 tty4 tty5 tty6
+.PP
+User
+\fIroot\fR
+should be allowed to get access from hosts with IPv4 addresses:
+.PP
++ : root : 192.168.200.1 192.168.200.4 192.168.200.9
+.PP
++ : root : 127.0.0.1
+.PP
+User
+\fIroot\fR
+should get access from network
+192.168.201.
+where the term will be evaluated by string matching. But it might be better to use network/netmask instead. The same meaning of
+192.168.201.
+is
+\fI192.168.201.0/24\fR
+or
+\fI192.168.201.0/255.255.255.0\fR
+.
+.PP
++ : root : 192.168.201.
+.PP
+User
+\fIroot\fR
+should be able to have access from hosts
+\fIfoo1.bar.org\fR
+and
+\fIfoo2.bar.org\fR
+(uses string matching also).
+.PP
++ : root : foo1.bar.org foo2.bar.org
+.PP
+User
+\fIroot\fR
+should be able to have access from domain
+\fIfoo.bar.org (uses string matching also).\fR
+.PP
++ : root : .foo.bar.org
+.PP
+User
+\fIroot\fR
+should be denied to get access from all other sources.
+.PP
+\- : root : ALL
+.PP
+User
+\fIfoo\fR
+and members of NIS group
+\fInis_group\fR
+should be allowed to get access from all sources. This will only work if NIS service is available.
+.PP
++ : @nis_group foo : ALL
+.PP
+User
+\fIxfs\fR
+and
+\fIfoo\fR
+should be allowed to get acccess via
+\fIsu .\fR
+.PP
++ : xfs foo : su
+.PP
+User
+\fIjohn\fR
+should get access from IPv4 net/mask.
+.PP
++ : john : 127.0.0.0/24
+.PP
+User
+\fIjohn\fR
+should get access from IPv4 as IPv6 net/mask.
+.PP
++ : john : ::ffff:127.0.0.0/127
+.PP
+User
+\fIjohn\fR
+should get access from IPv6 host address.
+.PP
++ : john : 2001:4ca0:0:101::1
+.PP
+User
+\fIjohn\fR
+should get access from IPv6 host address (same as above).
+.PP
++ : john : 2001:4ca0:0:101:0:0:0:1
+.PP
+User
+\fIjohn\fR
+should get access from IPv6 net/mask.
+.PP
++ : john : 2001:4ca0:0:101::/64
+.PP
+All other users should be denied to get access from all sources.
+.PP
+\- : ALL : ALL
+.SH "FILES"
+.PP
+Normally the
+\fIaccess.conf\fR
+file resides in
+\fI/etc/security\fR
+but this depends on configuration at compilation time. Thats why please run
+\fBcheck_login_access\fR(8)
+to find out which is the default config file for
+\fBpam_access\fR(8).
+.SH "SEE ALSO"
+.PP
+\fBcheck_login_access\fR(8)\fI,\fR\fBpam_access\fR(8)\fI,\fR\fBpam.d\fR(8)\fI,\fR
+and
+\fBpam\fR(8).
+.SH "AUTHORS"
+.PP
+Original
+\fBlogin.access\fR(5)
+manual was provided by
+\fIGuido van Rooij\fR
+which was renamed to
+\fBaccess.conf\fR(5)
+to reflect relation to default config file.
+.PP
+\fINetwork address / netmask\fR
+description and example text was introduced by
+\fIMike Becher <mike.becher at lrz\-muenchen.de>.\fR
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/pam_access/pam_access.8	30 Jan 2006 21:59:20 -0000
@@ -0,0 +1,79 @@
+.\" ** You probably do not want to edit this file directly **
+.\" It was generated using the DocBook XSL Stylesheets (version 1.69.1).
+.\" Instead of manually editing it, you probably should edit the DocBook XML
+.\" source for it and then use the DocBook XSL Stylesheets to regenerate it.
+.TH "PAM_ACCESS" "8" "01/30/2006" "Linux\-PAM Manual" "Linux\-PAM Manual"
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.SH "NAME"
+pam_access \- PAM module for logdaemon style login access control
+.SH "SYNOPSIS"
+.HP 14
+\fBpam_access.so\fR [debug] [accessfile=\fIfile\fR] [fieldsep=\fIsep\fR] [listsep=\fIsep\fR]
+.SH "DESCRIPTION"
+.PP
+The pam_access PAM module is mainly for access management. It provides logdaemon style login access control based on login names, host or domain names, internet addresses or network numbers, or on terminal line names in case of non\-networked logins.
+.PP
+By default rules for access management are taken from config file
+\fI/etc/security/access.conf\fR
+if you don't specify another file.
+.SH "OPTIONS"
+.TP
+\fBaccessfile=\fR\fB\fI/path/to/access.conf\fR\fR
+Indicate an alternative
+\fIaccess.conf\fR
+style configuration file to override the default. This can be useful when different services need different access lists.
+.TP
+\fBdebug\fR
+A lot of debug informations are printed with
+\fBsyslog\fR(3).
+.TP
+\fBfieldsep=\fR\fB\fIseparators\fR\fR
+This option modifies the field separator character that pam_access will recognize when parsing the access configuration file. For example:
+\fIfieldsep=|\fR
+will cause the default `:' character to be treated as part of a field value and `|' becomes the field separator. Doing this may be useful in conjuction with a system that wants to use pam_access with X based applications, since the
+\fIPAM_TTY\fR
+item is likely to be of the form "hostname:0" which includes a `:' character in its value. But you should not need this.
+.TP
+\fBlistsep=\fR\fB\fIseparators\fR\fR
+This option modifies the list separator character that pam_access will recognize when parsing the access configuration file. For example:
+\fIlistsep=,\fR
+will cause the default ` ' (space) and `\\t' (tab) characters to be treated as part of a list element value and `,' becomes the only list element separator. Doing this may be useful on a system with group information obtained from a Windows domain, where the default built\-in groups "Domain Users", "Domain Admins" contain a space.
+.SH "MODULE SERVICES PROVIDED"
+.PP
+The
+\fBauth\fR
+and
+\fBaccount\fR
+services are supported.
+.SH "RETURN VALUES"
+.TP
+PAM_SUCCESS
+Access was granted.
+.TP
+PAM_PERM_DENIED
+Access was not granted.
+.TP
+PAM_IGNORE
+\fBpam_setcred\fR
+was called which does nothing.
+.TP
+PAM_ABORT
+Not all relevant data or options could be gotten.
+.TP
+PAM_USER_UNKNOWN
+The user is not known to the system.
+.SH "FILES"
+.TP
+\fI/etc/security/access.conf\fR
+Default configuration file
+.SH "SEE ALSO"
+.PP
+\fBaccess.conf\fR(5),
+\fBpam.d\fR(8),
+\fBpam\fR(8).
+.SH "AUTHORS"
+.PP
+The logdaemon style login access control scheme was designed and implemented by Wietse Venema. The pam_access PAM module was developed by Alexei Nogin <alexei at nogin.dnttm.ru>. The IPv4(/) IPv6 support and the network(address) / netmask feature was developed and provided by Mike Becher <mike.becher at lrz\-muenchen.de>.
--- modules/pam_access/pam_access.c	9 Nov 2005 10:17:00 -0000	1.21
+++ modules/pam_access/pam_access.c	30 Jan 2006 21:59:20 -0000
@@ -42,11 +42,9 @@
 #include <ctype.h>
 #include <sys/utsname.h>
 #include <rpcsvc/ypclnt.h>
-
-#ifndef BROKEN_NETWORK_MATCH
-# include <netdb.h>
-# include <sys/socket.h>
-#endif
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/socket.h>
 
 /*
  * here, we make definitions for the externally accessible functions
@@ -55,6 +53,7 @@
  * modules include file to define their prototypes.
  */
 
+#define PAM_SM_AUTH
 #define PAM_SM_ACCOUNT
 
 #include <security/_pam_macros.h>
@@ -93,12 +92,14 @@
   * 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 --- */
+/* Print debugging messages.
+   Default is NO which means don't print debugging messages.  */
+static char pam_access_debug = NO;
 
 /* Parse module config arguments */
 
@@ -131,6 +132,8 @@
 		return 0;
 	    }
 
+	} else if (strcmp (argv[i], "debug") == 0) {
+	    pam_access_debug = YES;
 	} else {
 	    pam_syslog(pamh, LOG_ERR, "unrecognized option [%s]", argv[i]);
 	}
@@ -139,13 +142,167 @@
     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 *, 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 *);
+static int string_match (pam_handle_t *, const char *, const char *);
+static int isipaddr (const char* line, int *addr_type);
+static int network_netmask_match (pam_handle_t *, const char *, const char *);
+
+/* 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 comparation to
+ * relevant bits.
+ */
+static int
+are_addresses_equal(const char *ipaddr0, const char *ipaddr1,
+   const char *netmask)
+{
+  int itis = NO;
+  /* 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 addr0;
+  struct sockaddr_storage addr1;
+  int addr_type0 = 0;
+ int addr_type1 = 0;
+
+  /* normalize addr0 */
+  itis = NO;
+  memset(&addr0, 0, sizeof(struct sockaddr_storage));
+  /* first ipv4 */
+  if (inet_pton(AF_INET, ipaddr0, (void *)&addr0) > 0) {
+    addr_type0 = AF_INET;
+    itis = YES;
+  } else
+  /* then ipv6 */
+  if (inet_pton(AF_INET6, ipaddr0, (void *)&addr0) > 0) {
+    addr_type0 = AF_INET6;
+    itis = YES;
+  }
+
+  /* addr0 seems to be no ip addr */
+  if (itis == NO) {
+    return(NO);
+  }
+
+  /* normalize addr1 */
+  itis = NO;
+  itis = NO;
+  memset(&addr1, 0, sizeof(struct sockaddr_storage));
+  /* first ipv4 */
+  if (inet_pton(AF_INET, ipaddr1, (void *)&addr1) > 0) {
+    addr_type1 = AF_INET;
+    itis = YES;
+  } else
+  /* then ipv6 */
+  if (inet_pton(AF_INET6, ipaddr1, (void *)&addr1) > 0) {
+    addr_type1 = AF_INET6;
+    itis = YES;
+  }
+
+  /* addr1 seems to be no ip addr */
+  if (itis == 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 */
 
@@ -161,6 +318,12 @@
     int     end;
     int     lineno = 0;		/* for diagnostics */
 
+    if (pam_access_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.
@@ -186,10 +349,10 @@
 	    if (line[0] == 0)			/* skip blank lines */
 		continue;
 
-	    /* Allow trailing: in last field fo froms */
+	    /* Allow field seperator in last field fo froms */
 	    if (!(perm = strtok(line, fs))
 		|| !(users = strtok((char *) 0, fs))
-  	        || !(froms = strtok((char *) 0, fs))) {
+  	        || !(froms = strtok((char *) 0, "\n"))) {
 		pam_syslog(pamh, LOG_ERR, "%s: line %d: bad field count",
 			   item->config_file, lineno);
 		continue;
@@ -199,17 +362,32 @@
 			   item->config_file, lineno);
 		continue;
 	    }
-	    match = (list_match(pamh, froms, item, from_match)
-		     && list_match(pamh, users, item, user_match));
+	    if (pam_access_debug)
+	      pam_syslog (pamh, LOG_DEBUG,
+			  "line %d: %s : %s : %s", lineno, perm, users, froms);
+	    match = list_match(pamh, froms, item, from_match);
+	    if (pam_access_debug)
+	      pam_syslog (pamh, LOG_DEBUG,
+			  "from_match=%d, \"%s\"", match, item->from);
+	    match = match && list_match (pamh, users, item, user_match);
+	    if (pam_access_debug)
+	      pam_syslog (pamh, LOG_DEBUG, "user_match=%d, \"%s\"",
+			  match, item->user->pw_name);
 	}
 	(void) fclose(fp);
-    } else if (errno != ENOENT) {
-	pam_syslog(pamh, LOG_ERR, "cannot open %s: %m", item->config_file);
+    } else if (errno == ENOENT) {
+        /* This is no error.  */
+        if (pam_access_debug)
+	    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;
     }
-    return (match == 0 || (line[0] == '+'));
+    return (match == NO || (line[0] == '+'));
 }
 
+
 /* list_match - match an item against a list of tokens with exceptions */
 
 static int list_match(pam_handle_t *pamh,
@@ -257,23 +435,39 @@
 
 /* netgroup_match - match group against machine or user */
 
-static int netgroup_match(const char *group, const char *machine, const char *user)
+static int
+netgroup_match (pam_handle_t *pamh, const char *group,
+		const char *machine, const char *user)
 {
-    static char *mydomain = NULL;
+  char *mydomain = NULL;
+  int retval;
+
+  yp_get_default_domain(&mydomain);
+
+
+  retval = innetgr (group, machine, user, mydomain);
+  if (pam_access_debug == YES)
+    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", mydomain ? mydomain : "NULL");
+  return retval;
 
-    if (mydomain == 0)
-	yp_get_default_domain(&mydomain);
-    return (innetgr(group, machine, user, mydomain));
 }
 
 /* user_match - match a username against one token */
 
-static int user_match(pam_handle_t *pamh, char *tok, struct login_info *item)
+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;
 
+    if (pam_access_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
@@ -286,10 +480,11 @@
 	fake_item.from = myhostname();
 	if (fake_item.from == NULL)
 	  return NO;
-	return (user_match (pamh, tok, item) && from_match (pamh, at + 1, &fake_item));
+	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 (netgroup_match (pamh, tok + 1, (char *) 0, string));
+    else if (string_match (pamh, 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 */
@@ -307,6 +502,10 @@
     int        tok_len;
     int        str_len;
 
+    if (pam_access_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
@@ -316,12 +515,13 @@
      * if it matches the head of the string.
      */
 
-    if (string != NULL && tok[0] == '@') {			/* netgroup */
-	return (netgroup_match(tok + 1, string, (char *) 0));
-    } else if (string_match(tok, string)) {	/* ALL or exact match */
+    if (string == NULL) {
+	return NO;
+    } else if (tok[0] == '@') {			/* netgroup */
+        return (netgroup_match (pamh, tok + 1, string, (char *) 0));
+    } else if (string_match(pamh, 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)
@@ -329,47 +529,93 @@
     } 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 <saw at msu.ru>
-         */
-
-        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 */
+      struct addrinfo *res;
+      struct addrinfo hint;
+
+      memset (&hint, '\0', sizeof (hint));
+      hint.ai_flags = AI_ADDRCONFIG | AI_CANONNAME;
+      hint.ai_family = AF_INET;
+
+      if (getaddrinfo (string, NULL, &hint, &res) != 0)
+	return NO;
+      else
+	{
+	  struct addrinfo *runp = res;
+
+          while (runp != NULL)
+	    {
+	      char buf[INET_ADDRSTRLEN];
+
+	      if (runp->ai_family == AF_INET)
+		{
+		  inet_ntop (runp->ai_family,
+			     &((struct sockaddr_in *) runp->ai_addr)->sin_addr,
+			     buf, sizeof (buf));
+
+		  if (strncmp(tok, buf, tok_len) == 0)
+		    {
+		      freeaddrinfo (res);
+		      return YES;
+		    }
+		  runp = runp->ai_next;
+		}
+	      freeaddrinfo (res);
+	    }
+	}
+    } else  if (isipaddr(string, NULL) == YES) {
+      /* Assume network/netmask with a IP of a host.  */
+      if (network_netmask_match(pamh, tok, string))
+	return YES;
+    } else {
+      /* Assume network/netmask with a name of a host.  */
+      struct addrinfo *res;
+      struct addrinfo hint;
+
+      memset (&hint, '\0', sizeof (hint));
+      hint.ai_flags = AI_ADDRCONFIG | AI_CANONNAME;
+      hint.ai_family = AF_UNSPEC;
+
+      if (getaddrinfo (string, NULL, &hint, &res) != 0)
+	return NO;
+      else
+	{
+	  struct addrinfo *runp = res;
+
+          while (runp != NULL)
+	    {
+	      char buf[INET6_ADDRSTRLEN];
+
+	      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));
+
+	      if (network_netmask_match(pamh, tok, buf))
+		{
+		  freeaddrinfo (res);
+		  return YES;
+		}
+	      runp = runp->ai_next;
+	    }
+	  freeaddrinfo (res);
+	}
     }
-    return (NO);
+
+    return NO;
 }
 
 /* string_match - match a string against one token */
 
 static int
-string_match (const char *tok, const char *string)
+string_match (pam_handle_t *pamh, const char *tok, const char *string)
 {
 
+    if (pam_access_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.
@@ -388,11 +634,115 @@
     return (NO);
 }
 
-/* --- public account management functions --- */
+
+/* isipaddr - find out if string provided is an IP address or not */
+
+static int
+isipaddr (const char *string, int *addr_type)
+{
+  int itis = YES;
+
+  /* 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 if (inet_pton(AF_INET6, string, (void *)&addr) > 0)
+    { /* then ipv6 */
+      if (addr_type != NULL) {
+	*addr_type = AF_INET6;
+      }
+      itis = YES;
+    }
+  else
+    itis = NO;
+
+  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 (pam_handle_t *pamh,
+		       const char *tok, const char *string)
+{
+  if (pam_access_debug)
+    pam_syslog (pamh, LOG_DEBUG,
+		"network_netmask_match: tok=%s, item=%s", tok, string);
+
+  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);
+		}
+
+	      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 (NO);
+}
+
+
+/* --- public PAM management functions --- */
 
 PAM_EXTERN int
-pam_sm_acct_mgmt (pam_handle_t *pamh, int flags UNUSED,
-		  int argc, const char **argv)
+pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
+		     int argc, const char **argv)
 {
     struct login_info loginfo;
     const char *user=NULL;
@@ -426,16 +776,25 @@
             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;
-	        }
+	        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;
+	      pam_syslog (pamh, LOG_INFO,
+			  "cannot determine tty or remote hostname, using service %s",
+			  from);
 	    }
         }
 	else
 	  from = void_from;
 
-	if (from[0] == '/') {   /* full path */
+	if (from[0] == '/') {   /* full path, remove device path.  */
 	    const char *f;
 	    from++;
 	    if ((f = strchr(from, '/')) != NULL) {
@@ -444,7 +803,8 @@
 	}
     }
 
-    if ((user_pw=pam_modutil_getpwnam(pamh, user))==NULL) 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.
@@ -469,6 +829,20 @@
     }
 }
 
+PAM_EXTERN int
+pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED,
+		int argc UNUSED, const char **argv UNUSED)
+{
+  return PAM_IGNORE;
+}
+
+PAM_EXTERN int
+pam_sm_acct_mgmt (pam_handle_t *pamh, int flags,
+		  int argc, const char **argv)
+{
+  return pam_sm_authenticate (pamh, flags, argc, argv);
+}
+
 /* end of module definition */
 
 #ifdef PAM_STATIC
@@ -477,8 +851,8 @@
 
 struct pam_module _pam_access_modstruct = {
     "pam_access",
-    NULL,
-    NULL,
+    pam_sm_authenticate,
+    pam_sm_setcred,
     pam_sm_acct_mgmt,
     NULL,
     NULL,


More information about the Pam-list mailing list