(root)/
Linux-PAM-1.5.3/
modules/
pam_tty_audit/
pam_tty_audit.c
       1  /* Copyright © 2007, 2008 Red Hat, Inc. All rights reserved.
       2     Red Hat author: Miloslav Trmač <mitr@redhat.com>
       3  
       4     Redistribution and use in source and binary forms of Linux-PAM, with
       5     or without modification, are permitted provided that the following
       6     conditions are met:
       7  
       8     1. Redistributions of source code must retain any existing copyright
       9        notice, and this entire permission notice in its entirety,
      10        including the disclaimer of warranties.
      11  
      12     2. Redistributions in binary form must reproduce all prior and current
      13        copyright notices, this list of conditions, and the following
      14        disclaimer in the documentation and/or other materials provided
      15        with the distribution.
      16  
      17     3. The name of any author may not be used to endorse or promote
      18        products derived from this software without their specific prior
      19        written permission.
      20  
      21     ALTERNATIVELY, this product may be distributed under the terms of the
      22     GNU General Public License, in which case the provisions of the GNU
      23     GPL are required INSTEAD OF the above restrictions.  (This clause is
      24     necessary due to a potential conflict between the GNU GPL and the
      25     restrictions contained in a BSD-style copyright.)
      26  
      27     THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
      28     WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
      29     MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
      30     IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
      31     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
      32     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
      33     OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
      34     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
      35     TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
      36     USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
      37     DAMAGE. */
      38  
      39  #include "config.h"
      40  #include <errno.h>
      41  #include <fnmatch.h>
      42  #include <stdlib.h>
      43  #include <string.h>
      44  #include <syslog.h>
      45  #include <sys/socket.h>
      46  #include <unistd.h>
      47  
      48  #include <libaudit.h>
      49  #include <linux/netlink.h>
      50  
      51  #include <security/pam_ext.h>
      52  #include <security/pam_modules.h>
      53  #include <security/pam_modutil.h>
      54  
      55  #include "pam_cc_compat.h"
      56  #include "pam_inline.h"
      57  
      58  #define DATANAME "pam_tty_audit_last_state"
      59  
      60  /* Open an audit netlink socket */
      61  static int
      62  nl_open (void)
      63  {
      64    return socket (AF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
      65  }
      66  
      67  static int
      68  nl_send (int fd, unsigned type, unsigned flags, const void *data, size_t size)
      69  {
      70    struct sockaddr_nl addr;
      71    struct msghdr msg;
      72    struct nlmsghdr nlm;
      73    struct iovec iov[2];
      74    ssize_t res;
      75  
      76    nlm.nlmsg_len = NLMSG_LENGTH (size);
      77    nlm.nlmsg_type = type;
      78    nlm.nlmsg_flags = NLM_F_REQUEST | flags;
      79    nlm.nlmsg_seq = 0;
      80    nlm.nlmsg_pid = 0;
      81    iov[0].iov_base = &nlm;
      82    iov[0].iov_len = sizeof (nlm);
      83    DIAG_PUSH_IGNORE_CAST_QUAL;
      84    iov[1].iov_base = (void *)data;
      85    DIAG_POP_IGNORE_CAST_QUAL;
      86    iov[1].iov_len = size;
      87    addr.nl_family = AF_NETLINK;
      88    addr.nl_pid = 0;
      89    addr.nl_groups = 0;
      90    msg.msg_name = &addr;
      91    msg.msg_namelen = sizeof (addr);
      92    msg.msg_iov = iov;
      93    msg.msg_iovlen = 2;
      94    msg.msg_control = NULL;
      95    msg.msg_controllen = 0;
      96    msg.msg_flags = 0;
      97    res = sendmsg (fd, &msg, 0);
      98    if (res == -1)
      99      return -1;
     100    if ((size_t)res != nlm.nlmsg_len)
     101      {
     102        errno = EIO;
     103        return -1;
     104      }
     105    return 0;
     106  }
     107  
     108  static int
     109  nl_recv (int fd, unsigned type, void *buf, size_t size)
     110  {
     111    struct sockaddr_nl addr;
     112    struct msghdr msg;
     113    struct nlmsghdr nlm;
     114    struct iovec iov[2];
     115    ssize_t res, resdiff;
     116  
     117   again:
     118    iov[0].iov_base = &nlm;
     119    iov[0].iov_len = sizeof (nlm);
     120    msg.msg_name = &addr;
     121    msg.msg_namelen = sizeof (addr);
     122    msg.msg_iov = iov;
     123    msg.msg_iovlen = 1;
     124    msg.msg_control = NULL;
     125    msg.msg_controllen = 0;
     126    msg.msg_flags = 0;
     127    if (type != NLMSG_ERROR)
     128      {
     129        res = recvmsg (fd, &msg, MSG_PEEK);
     130        if (res == -1)
     131  	return -1;
     132        if (res != NLMSG_LENGTH (0))
     133  	{
     134  	  errno = EIO;
     135  	  return -1;
     136  	}
     137        if (nlm.nlmsg_type == NLMSG_ERROR)
     138  	{
     139  	  struct nlmsgerr err;
     140  
     141  	  iov[1].iov_base = &err;
     142  	  iov[1].iov_len = sizeof (err);
     143  	  msg.msg_iovlen = 2;
     144  	  res = recvmsg (fd, &msg, 0);
     145  	  if (res == -1)
     146  	    return -1;
     147  	  if ((size_t)res != NLMSG_LENGTH (sizeof (err))
     148  	      || nlm.nlmsg_type != NLMSG_ERROR)
     149  	    {
     150  	      errno = EIO;
     151  	      return -1;
     152  	    }
     153  	  if (err.error == 0)
     154  	    goto again;
     155  	  errno = -err.error;
     156  	  return -1;
     157  	}
     158      }
     159    if (size != 0)
     160      {
     161        iov[1].iov_base = buf;
     162        iov[1].iov_len = size;
     163        msg.msg_iovlen = 2;
     164      }
     165    res = recvmsg (fd, &msg, 0);
     166    if (res == -1)
     167      return -1;
     168    resdiff = NLMSG_LENGTH(size) - (size_t)res;
     169    if (resdiff < 0
     170        || nlm.nlmsg_type != type)
     171      {
     172        errno = EIO;
     173        return -1;
     174      }
     175    else if (resdiff > 0)
     176      {
     177        memset((char *)buf + size - resdiff, 0, resdiff);
     178      }
     179    return 0;
     180  }
     181  
     182  static int
     183  nl_recv_ack (int fd)
     184  {
     185    struct nlmsgerr err;
     186  
     187    if (nl_recv (fd, NLMSG_ERROR, &err, sizeof (err)) != 0)
     188      return -1;
     189    if (err.error != 0)
     190      {
     191        errno = -err.error;
     192        return -1;
     193      }
     194    return 0;
     195  }
     196  
     197  static void
     198  cleanup_old_status (pam_handle_t *pamh, void *data, int error_status)
     199  {
     200    (void)pamh;
     201    (void)error_status;
     202    free (data);
     203  }
     204  
     205  enum uid_range { UID_RANGE_NONE, UID_RANGE_MM, UID_RANGE_MIN,
     206      UID_RANGE_ONE, UID_RANGE_ERR };
     207  
     208  static enum uid_range
     209  parse_uid_range(pam_handle_t *pamh, const char *s,
     210                  uid_t *min_uid, uid_t *max_uid)
     211  {
     212      const char *range = s;
     213      const char *pmax;
     214      char *endptr;
     215      enum uid_range rv = UID_RANGE_MM;
     216  
     217      if ((pmax=strchr(range, ':')) == NULL)
     218          return UID_RANGE_NONE;
     219      ++pmax;
     220  
     221      if (range[0] == ':')
     222          rv = UID_RANGE_ONE;
     223      else {
     224              errno = 0;
     225              *min_uid = strtoul (range, &endptr, 10);
     226              if (errno != 0 || (range == endptr) || *endptr != ':') {
     227                  pam_syslog(pamh, LOG_DEBUG,
     228                             "wrong min_uid value in '%s'", s);
     229                  return UID_RANGE_ERR;
     230              }
     231      }
     232  
     233      if (*pmax == '\0') {
     234          if (rv == UID_RANGE_ONE)
     235              return UID_RANGE_ERR;
     236  
     237          return UID_RANGE_MIN;
     238      }
     239  
     240      errno = 0;
     241      *max_uid = strtoul (pmax, &endptr, 10);
     242      if (errno != 0 || (pmax == endptr) || *endptr != '\0') {
     243          pam_syslog(pamh, LOG_DEBUG,
     244                     "wrong max_uid value in '%s'", s);
     245          return UID_RANGE_ERR;
     246      }
     247  
     248      if (rv == UID_RANGE_ONE)
     249          *min_uid = *max_uid;
     250      return rv;
     251  }
     252  
     253  int
     254  pam_sm_open_session (pam_handle_t *pamh, int flags, int argc, const char **argv)
     255  {
     256    enum command { CMD_NONE, CMD_ENABLE, CMD_DISABLE };
     257  
     258    enum command command;
     259    struct audit_tty_status *old_status, new_status;
     260    const char *user;
     261    int i, fd, open_only;
     262    struct passwd *pwd;
     263  #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
     264    int log_passwd;
     265  #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
     266  
     267    (void)flags;
     268  
     269    if (pam_get_user (pamh, &user, NULL) != PAM_SUCCESS)
     270      {
     271        pam_syslog(pamh, LOG_NOTICE, "cannot determine user name");
     272        return PAM_SESSION_ERR;
     273      }
     274  
     275    pwd = pam_modutil_getpwnam(pamh, user);
     276    if (pwd == NULL)
     277      {
     278        pam_syslog(pamh, LOG_NOTICE,
     279                   "open_session unknown user '%s'", user);
     280        return PAM_SESSION_ERR;
     281      }
     282  
     283    command = CMD_NONE;
     284    open_only = 0;
     285  #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
     286    log_passwd = 0;
     287  #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
     288    for (i = 0; i < argc; i++)
     289      {
     290        const char *str;
     291  
     292        if ((str = pam_str_skip_prefix(argv[i], "enable=")) != NULL
     293  	  || (str = pam_str_skip_prefix(argv[i], "disable=")) != NULL)
     294  	{
     295  	  enum command this_command;
     296  	  char *copy, *tok_data, *tok;
     297  
     298  	  this_command = *argv[i] == 'e' ? CMD_ENABLE : CMD_DISABLE;
     299  	  copy = strdup (str);
     300  	  if (copy == NULL)
     301  	    return PAM_SESSION_ERR;
     302  	  for (tok = strtok_r (copy, ",", &tok_data);
     303  	       tok != NULL && command != this_command;
     304  	       tok = strtok_r (NULL, ",", &tok_data))
     305  	    {
     306  	      uid_t min_uid = 0, max_uid = 0;
     307  	      switch (parse_uid_range(pamh, tok, &min_uid, &max_uid))
     308  		{
     309  		case UID_RANGE_NONE:
     310  		    if (fnmatch (tok, user, 0) == 0)
     311  			command = this_command;
     312  		    break;
     313  		case UID_RANGE_MM:
     314  		    if (pwd->pw_uid >= min_uid && pwd->pw_uid <= max_uid)
     315  			command = this_command;
     316  		    break;
     317  		case UID_RANGE_MIN:
     318  		    if (pwd->pw_uid >= min_uid)
     319  			command = this_command;
     320  		    break;
     321  		case UID_RANGE_ONE:
     322  		    if (pwd->pw_uid == max_uid)
     323  			command = this_command;
     324  		    break;
     325  		case UID_RANGE_ERR:
     326  		    break;
     327  		}
     328  	    }
     329  	  free (copy);
     330  	}
     331        else if (strcmp (argv[i], "open_only") == 0)
     332  	open_only = 1;
     333        else if (strcmp (argv[i], "log_passwd") == 0)
     334  #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
     335          log_passwd = 1;
     336  #else /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
     337          pam_syslog (pamh, LOG_WARNING,
     338                      "The log_passwd option was not available at compile time.");
     339  #warning "pam_tty_audit: The log_passwd option is not available.  Please upgrade your headers/kernel."
     340  #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
     341        else
     342  	{
     343  	  pam_syslog (pamh, LOG_ERR, "unknown option `%s'", argv[i]);
     344  	}
     345      }
     346    if (command == CMD_NONE)
     347      return PAM_SUCCESS;
     348  
     349    old_status = malloc (sizeof (*old_status));
     350    if (old_status == NULL)
     351      return PAM_SESSION_ERR;
     352  
     353    fd = nl_open ();
     354    if (fd == -1
     355        && errno == EPROTONOSUPPORT)
     356      {
     357        pam_syslog (pamh, LOG_WARNING, "unable to open audit socket, audit not "
     358                    "supported; tty_audit skipped");
     359        free (old_status);
     360        return PAM_IGNORE;
     361      }
     362    else if (fd == -1
     363        || nl_send (fd, AUDIT_TTY_GET, 0, NULL, 0) != 0
     364        || nl_recv (fd, AUDIT_TTY_GET, old_status, sizeof (*old_status)) != 0)
     365      {
     366        pam_syslog (pamh, LOG_ERR, "error reading current audit status: %m");
     367        if (fd != -1)
     368  	close (fd);
     369        free (old_status);
     370        return PAM_SESSION_ERR;
     371      }
     372  
     373    memcpy(&new_status, old_status, sizeof(new_status));
     374  
     375    new_status.enabled = (command == CMD_ENABLE ? 1 : 0);
     376  #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
     377    new_status.log_passwd = log_passwd;
     378  #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
     379    if (old_status->enabled == new_status.enabled
     380  #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
     381        && old_status->log_passwd == new_status.log_passwd
     382  #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
     383       )
     384      {
     385        open_only = 1; /* to clean up old_status */
     386        goto ok_fd;
     387      }
     388  
     389    if (open_only == 0
     390        && pam_set_data (pamh, DATANAME, old_status, cleanup_old_status)
     391        != PAM_SUCCESS)
     392      {
     393        pam_syslog (pamh, LOG_ERR, "error saving old audit status");
     394        close (fd);
     395        free (old_status);
     396        return PAM_SESSION_ERR;
     397      }
     398  
     399    if (nl_send (fd, AUDIT_TTY_SET, NLM_F_ACK, &new_status,
     400  	       sizeof (new_status)) != 0
     401        || nl_recv_ack (fd) != 0)
     402      {
     403        pam_syslog (pamh, LOG_ERR, "error setting current audit status: %m");
     404        close (fd);
     405        if (open_only != 0)
     406  	free (old_status);
     407        return PAM_SESSION_ERR;
     408      }
     409    /* Fall through */
     410   ok_fd:
     411    close (fd);
     412    pam_syslog (pamh, LOG_DEBUG, "changed status from %d to %d",
     413  	      old_status->enabled, new_status.enabled);
     414    if (open_only != 0)
     415      free (old_status);
     416    return PAM_SUCCESS;
     417  }
     418  
     419  int
     420  pam_sm_close_session (pam_handle_t *pamh, int flags, int argc,
     421  		      const char **argv)
     422  {
     423    const void *status_;
     424  
     425    (void)flags;
     426    (void)argc;
     427    (void)argv;
     428    if (pam_get_data (pamh, DATANAME, &status_) == PAM_SUCCESS)
     429      {
     430        const struct audit_tty_status *status;
     431        int fd;
     432  
     433        status = status_;
     434  
     435        fd = nl_open ();
     436        if (fd == -1
     437  	  || nl_send (fd, AUDIT_TTY_SET, NLM_F_ACK, status,
     438  		      sizeof (*status)) != 0
     439  	  || nl_recv_ack (fd) != 0)
     440  	{
     441  	  pam_syslog (pamh, LOG_ERR, "error restoring audit status: %m");
     442  	  if (fd != -1)
     443  	    close (fd);
     444  	  return PAM_SESSION_ERR;
     445  	}
     446        close (fd);
     447        pam_syslog (pamh, LOG_DEBUG, "restored status to %d", status->enabled);
     448      }
     449    return PAM_SUCCESS;
     450  }