(root)/
Linux-PAM-1.5.3/
modules/
pam_setquota/
pam_setquota.c
       1  /* PAM setquota module
       2  
       3     This PAM module sets disk quota when a session begins.
       4  
       5     Copyright © 2006 Ruslan Savchenko <savrus@mexmat.net>
       6     Copyright © 2010 Shane Tzen <shane@ict.usc.edu>
       7     Copyright © 2012-2020 Sven Hartge <sven@svenhartge.de>
       8     Copyright © 2016 Keller Fuchs <kellerfuchs@hashbang.sh>
       9  */
      10  
      11  #include <sys/types.h>
      12  #include <sys/quota.h>
      13  #include <linux/quota.h>
      14  #include <pwd.h>
      15  #include <syslog.h>
      16  #include <errno.h>
      17  #include <mntent.h>
      18  #include <stdio.h>
      19  #include <stdbool.h>
      20  
      21  #include <security/pam_modules.h>
      22  #include <security/_pam_macros.h>
      23  #include <security/pam_ext.h>
      24  #include <security/pam_modutil.h>
      25  #include "pam_inline.h"
      26  
      27  #ifndef PATH_LOGIN_DEFS
      28  # define PATH_LOGIN_DEFS "/etc/login.defs"
      29  #endif
      30  
      31  #define MAX_UID_VALUE 0xFFFFFFFFUL
      32  
      33  struct pam_params {
      34    uid_t start_uid;
      35    uid_t end_uid;
      36    const char *fs;
      37    size_t fs_len;
      38    int overwrite;
      39    int debug;
      40  };
      41  
      42  static inline void
      43  debug(pam_handle_t *pamh, const struct if_dqblk *p,
      44        const char *device, const char *dbgprefix) {
      45    pam_syslog(pamh, LOG_DEBUG, "%s device=%s bsoftlimit=%llu bhardlimit=%llu "
      46                                "isoftlimit=%llu ihardlimit=%llu btime=%llu itime=%llu",
      47  	     dbgprefix, device,
      48  	     (unsigned long long) p->dqb_bsoftlimit,
      49  	     (unsigned long long) p->dqb_bhardlimit,
      50  	     (unsigned long long) p->dqb_isoftlimit,
      51  	     (unsigned long long) p->dqb_ihardlimit,
      52  	     (unsigned long long) p->dqb_btime,
      53  	     (unsigned long long) p->dqb_itime);
      54  }
      55  
      56  static unsigned long long
      57  str_to_dqb_num(pam_handle_t *pamh, const char *str, const char *param) {
      58    char *ep = NULL;
      59  
      60    errno = 0;
      61    long long temp = strtoll(str, &ep, 10);
      62    if (temp < 0 || str == ep || *ep != '\0' || errno !=0) {
      63      pam_syslog(pamh, LOG_ERR, "Parameter \"%s=%s\" invalid, setting to 0", param, str);
      64      return 0;
      65    }
      66    else {
      67      return temp;
      68    }
      69  }
      70  
      71  static bool
      72  parse_dqblk(pam_handle_t *pamh, int argc, const char **argv, struct if_dqblk *p) {
      73    bool bhard = false, bsoft = false, ihard = false, isoft = false;
      74  
      75    /* step through arguments */
      76    for (; argc-- > 0; ++argv) {
      77      const char *str;
      78      if ((str = pam_str_skip_prefix(*argv, "bhardlimit=")) != NULL) {
      79        p->dqb_bhardlimit = str_to_dqb_num(pamh, str, "bhardlimit");
      80        p->dqb_valid |= QIF_BLIMITS;
      81        bhard = true;
      82      } else if ((str = pam_str_skip_prefix(*argv, "bsoftlimit=")) != NULL) {
      83        p->dqb_bsoftlimit = str_to_dqb_num(pamh, str, "bsoftlimit");
      84        p->dqb_valid |= QIF_BLIMITS;
      85        bsoft = true;
      86      } else if ((str = pam_str_skip_prefix(*argv, "ihardlimit=")) != NULL) {
      87        p->dqb_ihardlimit = str_to_dqb_num(pamh, str, "ihardlimit");
      88        p->dqb_valid |= QIF_ILIMITS;
      89        ihard = true;
      90      } else if ((str = pam_str_skip_prefix(*argv, "isoftlimit=")) != NULL) {
      91        p->dqb_isoftlimit = str_to_dqb_num(pamh, str, "isoftlimit");
      92        p->dqb_valid |= QIF_ILIMITS;
      93        isoft = true;
      94      } else if ((str = pam_str_skip_prefix(*argv, "btime=")) != NULL) {
      95        p->dqb_btime = str_to_dqb_num(pamh, str, "btime");
      96        p->dqb_valid |= QIF_BTIME;
      97      } else if ((str = pam_str_skip_prefix(*argv, "itime=")) != NULL) {
      98        p->dqb_itime = str_to_dqb_num(pamh, str, "itime");
      99        p->dqb_valid |= QIF_ITIME;
     100      }
     101    }
     102  
     103    /* return FALSE if a softlimit or hardlimit has been set
     104     * independently of its counterpart.
     105     */
     106    return !(bhard ^ bsoft) && !(ihard ^ isoft);
     107  }
     108  
     109  /* inspired by pam_usertype_get_id */
     110  static uid_t
     111  str_to_uid(pam_handle_t *pamh, const char *value, uid_t default_value, const char *param) {
     112      unsigned long ul;
     113      char *ep;
     114      uid_t uid;
     115  
     116      errno = 0;
     117      ul = strtoul(value, &ep, 10);
     118      if (!(ul >= MAX_UID_VALUE
     119          || (uid_t)ul >= MAX_UID_VALUE
     120          || (errno != 0 && ul == 0)
     121          || value == ep
     122          || *ep != '\0')) {
     123          uid = (uid_t)ul;
     124      } else {
     125          pam_syslog(pamh, LOG_ERR, "Parameter \"%s=%s\" invalid, "
     126                     "setting to %u", param, value, default_value);
     127          uid = default_value;
     128      }
     129  
     130      return uid;
     131  }
     132  
     133  static void
     134  parse_params(pam_handle_t *pamh, int argc, const char **argv, struct pam_params *p) {
     135    /* step through arguments */
     136    for (; argc-- > 0; ++argv) {
     137      const char *str;
     138      char *ep = NULL;
     139      if ((str = pam_str_skip_prefix(*argv, "startuid=")) != NULL) {
     140        p->start_uid = str_to_uid(pamh, str, p->start_uid, "startuid");
     141      } else if ((str = pam_str_skip_prefix(*argv, "enduid=")) != NULL) {
     142        p->end_uid = str_to_uid(pamh, str, p->end_uid, "enduid");
     143      } else if ((str = pam_str_skip_prefix(*argv, "fs=")) != NULL) {
     144        p->fs = str;
     145        p->fs_len = strlen(str);
     146        /* Mask the unnecessary '/' from the end of fs parameter */
     147        if (p->fs_len > 1 && p->fs[p->fs_len - 1] == '/')
     148          --p->fs_len;
     149      } else if ((str = pam_str_skip_prefix(*argv, "overwrite=")) != NULL) {
     150        errno = 0;
     151        p->overwrite = strtol(str, &ep, 10);
     152        if (*ep != '\0' || str == ep || errno !=0 || (p->overwrite < 0)) {
     153          pam_syslog(pamh, LOG_ERR, "Parameter \"overwrite=%s\" invalid, "
     154                          "setting to 0", str);
     155          p->overwrite = 0;
     156        }
     157      } else if ((str = pam_str_skip_prefix(*argv, "debug=")) != NULL) {
     158        errno = 0;
     159        p->debug = strtol(str, &ep, 10);
     160        if (*ep != '\0' || str == ep || errno != 0 || (p->debug < 0)) {
     161          pam_syslog(pamh, LOG_ERR, "Parameter \"debug=%s\" invalid, "
     162                          "setting to 0", str);
     163          p->debug = 0;
     164        }
     165      }
     166    }
     167  }
     168  
     169  int
     170  pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
     171  		    int argc, const char **argv)
     172  {
     173    int retval;
     174    char *val, *mntdevice = NULL;
     175    const void *user;
     176    const struct passwd *pwd;
     177    struct pam_params param = {
     178            .start_uid = PAM_USERTYPE_UIDMIN,
     179            .end_uid = 0,
     180            .fs = NULL };
     181    struct if_dqblk ndqblk;
     182    FILE *fp;
     183    size_t mnt_len = 0, match_size = 0;
     184  #ifdef HAVE_GETMNTENT_R
     185    char buf[BUFSIZ];
     186    struct mntent ent;
     187  #endif
     188    const struct mntent *mnt;
     189    const char *service;
     190  
     191    if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service) != PAM_SUCCESS)
     192      service = "";
     193  
     194    /* Get UID_MIN for default start_uid from login.defs */
     195    val = pam_modutil_search_key(pamh, PATH_LOGIN_DEFS, "UID_MIN");
     196  
     197    /* Should UID_MIN be undefined, use current value of param.start_uid
     198     * pre-defined as PAM_USERTYPE_UIDMIN set by configure as safe
     199     * starting UID to avoid setting a quota for root and system
     200     * users if startuid= parameter is absent.
     201     */
     202    if (val) {
     203      param.start_uid = str_to_uid(pamh, val, param.start_uid, PATH_LOGIN_DEFS":UID_MIN");
     204    }
     205  
     206    /* Parse parameter values
     207     * Must come after pam_modutil_search_key so that the current system
     208     * default for UID_MIN is already in p.start_uid to serve as default
     209     * for str_to_uid in case of a parse error.
     210     * */
     211    parse_params(pamh, argc, argv, &param);
     212  
     213    if (param.debug >= 1)
     214      pam_syslog(pamh, LOG_DEBUG, "Config: startuid=%u enduid=%u fs=%s "
     215                      "debug=%d overwrite=%d",
     216                      param.start_uid, param.end_uid,
     217                      param.fs ? param.fs : "(none)",
     218                      param.debug, param.overwrite);
     219  
     220    /* Determine the user name so we can get the home directory */
     221    retval = pam_get_item(pamh, PAM_USER, &user);
     222    if (retval != PAM_SUCCESS || user == NULL || *(const char *)user == '\0') {
     223      pam_syslog(pamh, LOG_NOTICE, "user unknown");
     224      return PAM_USER_UNKNOWN;
     225    }
     226  
     227    /* Get the password entry */
     228    pwd = pam_modutil_getpwnam(pamh, user);
     229    if (pwd == NULL) {
     230      pam_syslog(pamh, LOG_NOTICE, "user unknown");
     231      return PAM_USER_UNKNOWN;
     232    }
     233  
     234    /* Check if we should not set quotas for user */
     235    if ((pwd->pw_uid < param.start_uid) ||
     236        ((param.end_uid >= param.start_uid) && (param.start_uid != 0) &&
     237         (pwd->pw_uid > param.end_uid)))
     238      return PAM_SUCCESS;
     239  
     240    /* Find out what device the filesystem is hosted on */
     241    if ((fp = setmntent("/proc/mounts", "r")) == NULL) {
     242      pam_syslog(pamh, LOG_ERR, "Unable to open /proc/mounts");
     243      return PAM_PERM_DENIED;
     244    }
     245  
     246    while (
     247  #ifdef HAVE_GETMNTENT_R
     248             (mnt = getmntent_r(fp, &ent, buf, sizeof(buf))) != NULL
     249  #else
     250             (mnt = getmntent(fp)) != NULL
     251  #endif
     252          ) {
     253      /* If param.fs is not specified use filesystem with users homedir
     254       * as default.
     255       */
     256      if (param.fs == NULL) {
     257        /* Mask trailing / from mnt->mnt_dir, to get a leading / on the
     258         * remaining suffix returned by pam_str_skip_prefix_len()
     259         */
     260        for (mnt_len = strlen(mnt->mnt_dir); mnt_len > 0; --mnt_len)
     261          if (mnt->mnt_dir[mnt_len - 1] != '/')
     262            break;
     263        const char *s;
     264        if (param.debug >= 2)
     265          pam_syslog(pamh, LOG_DEBUG, "Trying to match pw_dir=\"%s\" "
     266                          "with mnt_dir=\"%s\"", pwd->pw_dir, mnt->mnt_dir);
     267        /*
     268         * (mnt_len > match_size) Only try matching the mnt_dir if its length
     269         * is longer than the last matched length, trying to find the longest
     270         * mnt_dir for a given pwd_dir.
     271         *
     272         * (mnt_len == 0 && mnt->mnt_dir[0] == '/') special-cases the
     273         * root-dir /, which is the only mnt_dir with a trailing '/', which
     274         * got masked earlier.
     275         */
     276        if ((mnt_len > match_size || (mnt_len == 0 && mnt->mnt_dir[0] == '/')) &&
     277           (s = pam_str_skip_prefix_len(pwd->pw_dir, mnt->mnt_dir, mnt_len)) != NULL &&
     278           s[0] == '/') {
     279          free(mntdevice);
     280          if ((mntdevice = strdup(mnt->mnt_fsname)) == NULL) {
     281            pam_syslog(pamh, LOG_CRIT, "Memory allocation error");
     282            endmntent(fp);
     283            return PAM_PERM_DENIED;
     284          }
     285          match_size = mnt_len;
     286          if (param.debug >= 2)
     287            pam_syslog(pamh, LOG_DEBUG, "Found pw_dir=\"%s\" in mnt_dir=\"%s\" "
     288                       "with suffix=\"%s\" on device=\"%s\"", pwd->pw_dir,
     289                       mnt->mnt_dir, s, mntdevice);
     290        }
     291      /* param.fs has been specified, find exactly matching filesystem */
     292      } else if ((strncmp(param.fs, mnt->mnt_dir, param.fs_len) == 0
     293                  && mnt->mnt_dir[param.fs_len] == '\0') ||
     294                 (strncmp(param.fs, mnt->mnt_fsname, param.fs_len) == 0
     295                  && mnt->mnt_fsname[param.fs_len] == '\0' )) {
     296          free(mntdevice);
     297          if ((mntdevice = strdup(mnt->mnt_fsname)) == NULL) {
     298            pam_syslog(pamh, LOG_CRIT, "Memory allocation error");
     299            endmntent(fp);
     300            return PAM_PERM_DENIED;
     301          }
     302          if (param.debug >= 2)
     303            pam_syslog(pamh, LOG_DEBUG, "Found fs=\"%s\" in mnt_dir=\"%s\" "
     304                       "on device=\"%s\"", param.fs, mnt->mnt_dir, mntdevice);
     305      }
     306    }
     307  
     308    endmntent(fp);
     309  
     310    if (mntdevice == NULL) {
     311      pam_syslog(pamh, LOG_ERR, "Filesystem or device not found: %s", param.fs ? param.fs : pwd->pw_dir);
     312      return PAM_PERM_DENIED;
     313    }
     314  
     315    /* Get limits */
     316    if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), mntdevice, pwd->pw_uid,
     317                 (void *)&ndqblk) == -1) {
     318      pam_syslog(pamh, LOG_ERR, "fail to get limits for user %s : %m",
     319                 pwd->pw_name);
     320      free(mntdevice);
     321      return PAM_PERM_DENIED;
     322    }
     323  
     324    if (param.debug >= 1)
     325      debug(pamh, &ndqblk, mntdevice, "Quota read:");
     326  
     327    /* Only overwrite if quotas aren't already set or if overwrite is set */
     328    if ((ndqblk.dqb_bsoftlimit == 0 && ndqblk.dqb_bhardlimit == 0 &&
     329         ndqblk.dqb_isoftlimit == 0 && ndqblk.dqb_ihardlimit == 0) ||
     330        param.overwrite == 1) {
     331  
     332      /* Parse new limits
     333       * Exit with an error should only the hard- or softlimit be
     334       * configured but not both.
     335       * This avoids errors, inconsistencies and possible race conditions
     336       * during setquota.
     337       */
     338      ndqblk.dqb_valid = 0;
     339      if (!parse_dqblk(pamh, argc, argv, &ndqblk)) {
     340        pam_syslog(pamh, LOG_ERR,
     341                   "Both soft- and hardlimits for %s need to be configured "
     342                   "at the same time!", mntdevice);
     343        free(mntdevice);
     344        return PAM_PERM_DENIED;
     345      }
     346  
     347      /* Nothing changed? Are no limits defined at all in configuration? */
     348      if (ndqblk.dqb_valid == 0) {
     349        pam_syslog(pamh, LOG_AUTH | LOG_WARNING, "no limits defined in "
     350                   "configuration for user %s on %s", pwd->pw_name, mntdevice);
     351        free(mntdevice);
     352        return PAM_IGNORE;
     353      }
     354  
     355      /* Set limits */
     356      if (quotactl(QCMD(Q_SETQUOTA, USRQUOTA), mntdevice, pwd->pw_uid,
     357                   (void *)&ndqblk) == -1) {
     358        pam_syslog(pamh, LOG_ERR, "failed to set limits for user %s on %s: %m",
     359                   pwd->pw_name, mntdevice);
     360        free(mntdevice);
     361        return PAM_PERM_DENIED;
     362      }
     363      if (param.debug >= 1)
     364        debug(pamh, &ndqblk, mntdevice, "Quota set:");
     365  
     366      /* End module */
     367      free(mntdevice);
     368      return PAM_SUCCESS;
     369  
     370    } else {
     371      /* Quota exists and overwrite!=1 */
     372      if (param.debug >= 1) {
     373        pam_syslog(pamh, LOG_DEBUG, "Quota already exists for user %s "
     374                   "on %s, not overwriting it without \"overwrite=1\"",
     375                   pwd->pw_name, mntdevice);
     376      }
     377      /* End module */
     378      free(mntdevice);
     379      return PAM_IGNORE;
     380    }
     381  
     382  }
     383  
     384  int
     385  pam_sm_close_session(pam_handle_t *pamh UNUSED, int flags UNUSED,
     386  		     int argc UNUSED, const char **argv UNUSED)
     387  {
     388    return PAM_SUCCESS;
     389  }