pam_setquota.c 13 KB

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