3
* Copyright © W. Michael Petullo <mike@flyn.org>, 2004
4
* Copyright © Jan Engelhardt, 2005-2011
5
* Copyright © Bastian Kleineidam <calvin [at] debian org>, 2005
7
* This file is part of pam_mount; you can redistribute it and/or
8
* modify it under the terms of the GNU Lesser General Public License
9
* as published by the Free Software Foundation; either version 2.1
10
* of the License, or (at your option) any later version.
13
pmvarrun.c -- Updates /run/pam_mount/<user>.
14
A seperate program is needed so that /run/pam_mount/<user> may be
15
created with a pam_mount-specific security context (otherwise SELinux
16
policy will conflict with whatever called pam_mount.so).
20
#include <sys/types.h>
31
#include <libHX/defs.h>
33
#include <libHX/string.h>
35
#include "libcryptmount.h"
36
#include "pam_mount.h"
39
#define ASCIIZ_LLX sizeof("0xFFFFFFFF""FFFFFFFF")
40
#define VAR_RUN_PMT RUNDIR "/pam_mount"
48
static int create_var_run(void);
49
static int modify_pm_count(const char *, long);
50
static int open_and_lock(const char *, long);
51
static void parse_args(const int, const char **, struct settings *);
52
static long read_current_count(int, const char *);
53
static void set_defaults(struct settings *);
54
static void usage(int, const char *);
55
static int write_count(int, long, const char *);
57
static unsigned int pmvr_debug;
60
* usage - display help
61
* @exitcode: numeric value we will be exiting with
62
* @error: descriptive error string
64
* Displays the help string and an optional extra error message.
66
static void usage(const int exitcode, const char *error)
68
fprintf(stderr, "Usage: pmvarrun -u USER [-o NUMBER] [-d]\n");
70
fprintf(stderr, "%s\n\n", error);
76
* @settings: pointer to settings structure
78
static void set_defaults(struct settings *settings)
82
settings->user = NULL;
83
settings->operation = 1;
85
if ((s = getenv("_PMT_DEBUG_LEVEL")) != NULL &&
86
strtoul(s, NULL, 0) != 0) {
88
ehd_logctl(EHD_LOGFT_DEBUG, EHD_LOG_SET);
94
* from git://dev.medozas.de/vitalnix /src/libvxutil/util.c
96
static bool valid_username(const char *n)
102
* Note to future editors: Some systems disallow leading digits for
103
* usernames. Possibly because it is concerned about badly-written
104
* programs detecting numeric UIDs by merely doing
105
* strtoul(s, &e, 0) && s != e, which falls victim to usernames
106
* with leading digits.
107
* So pam_mount, in its original form at least (distros can patch
108
* their copy up as they see fit), should at best reject
109
* leading digits too.
112
* Cannot use isalpha/isdigit here since that may include
115
if (!((*n >= 'A' && *n <= 'Z') || (*n >= 'a' && *n <= 'z') ||
122
if (*n == '$' && *(n+1) == '\0') /* Samba accounts */
125
valid = (*n >= 'A' && *n <= 'Z') || (*n >= 'a' && *n <= 'z') ||
126
(*n >= '0' && *n <= '9') || *n == '_' || *n == '.' ||
127
*n == '-' || *n == ' ' || *n == '\\';
138
* @n: string to analyze
140
* Calls @strtol on @n using base 10 and makes sure there were no invalid
141
* characters in @n. Returns the value, or %LONG_MAX in case of an
143
* NOTE: This function is only referenced from pmvarrun.c.
145
long str_to_long(const char *n)
150
l0g("count string is NULL\n");
153
val = strtol(n, &endptr, 10);
154
if (*endptr != '\0') {
155
l0g("count string is not valid\n");
163
* @argc: number of elements in @argv
164
* @argv: NULL-terminated argument vector
165
* @settings: pointer to settings structure
167
* Parse options from @argv and put it into @settings.
169
static void parse_args(int argc, const char **argv, struct settings *settings)
173
while ((c = getopt(argc, const_cast2(char * const *, argv),
177
usage(EXIT_SUCCESS, NULL);
181
ehd_logctl(EHD_LOGFT_DEBUG, EHD_LOG_SET);
185
settings->operation = str_to_long(optarg);
186
if (settings->operation == LONG_MAX ||
187
settings->operation == LONG_MIN)
188
usage(EXIT_FAILURE, "count string is not valid");
191
if (!valid_username(optarg)) {
192
fprintf(stderr, "Invalid user name\n");
195
if ((settings->user = HX_strdup(optarg)) == NULL)
199
usage(EXIT_FAILURE, NULL);
207
* @user: user to poke on
208
* @amount: increment (usually -1, 0 or +1)
210
* Adjusts /var/run/pam_mount/@user by @amount, or deletes the file if the
211
* resulting value (current + @amount) is <= 0. Returns >= 0 on success to
212
* indicate the new login count, or negative to indicate errno. -ESTALE and
213
* -EOVERFLOW are passed up from subfunctions and must be handled in the
216
static int modify_pm_count(const char *user, long amount)
218
char filename[PATH_MAX + 1];
224
assert(user != NULL);
226
if ((pent = getpwnam(user)) == NULL) {
228
l0g("could not resolve user %s\n", user);
232
if (stat(VAR_RUN_PMT, &sb) < 0) {
233
if (errno != ENOENT) {
235
l0g("unable to stat " VAR_RUN_PMT ": %s\n",
239
if ((ret = create_var_run()) < 0)
243
snprintf(filename, sizeof(filename), VAR_RUN_PMT "/%s", user);
244
while ((ret = fd = open_and_lock(filename, pent->pw_uid)) == -EAGAIN)
249
if ((val = read_current_count(fd, filename)) < 0) {
254
w4rn("parsed count value %ld\n", val);
255
/* amount == 0 implies query */
258
ret = write_count(fd, val + amount, filename);
261
return (ret < 0) ? ret : val + amount;
264
int main(int argc, const char **argv)
266
struct settings settings;
269
ehd_logctl(EHD_LOGFT_NOSYSLOG, EHD_LOG_SET);
270
set_defaults(&settings);
271
parse_args(argc, argv, &settings);
273
if (settings.user == NULL)
274
usage(EXIT_FAILURE, NULL);
276
ret = modify_pm_count(settings.user, settings.operation);
277
if (ret == -ESTALE) {
280
} else if (ret < 0) {
284
/* print current count so pam_mount module may read it */
289
//-----------------------------------------------------------------------------
293
* Creates the /run/pam_mount directory required by pmvarrun and sets
294
* proper permissions on it.
296
* Returns >0 for success or <=0 to indicate errno.
298
static int create_var_run(void)
300
static const unsigned int mode = S_IRUGO | S_IXUGO | S_IWUSR;
303
w4rn("creating " VAR_RUN_PMT);
304
if (HX_mkdir(VAR_RUN_PMT, mode) < 0) {
306
l0g("unable to create " VAR_RUN_PMT ": %s\n", strerror(errno));
309
if (chown(VAR_RUN_PMT, 0, 0) < 0) {
311
l0g("unable to chown " VAR_RUN_PMT ": %s\n", strerror(errno));
316
* 0755: `su` creates file group owned by user and then releases root
317
* permissions. User needs to be able to access file on logout.
319
if (chmod(VAR_RUN_PMT, mode) < 0) {
321
l0g("unable to chmod " VAR_RUN_PMT ": %s\n", strerror(errno));
330
* @filename: file to open
332
* Creates if necessary, opens and chown()s @filename, and locks it.
333
* Returns the fd if all of that succeeded, -EAGAIN if the file was unlinked
334
* during operation (see below), -ESTALE if the lock could not be obtained,
335
* and <0 otherwise to indicate errno.
337
static int open_and_lock(const char *filename, long uid) {
338
struct flock lockinfo = {
340
.l_whence = SEEK_SET,
347
if ((fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
349
l0g("unable to open %s: %s\n", filename, strerror(errno));
352
if (fchown(fd, uid, 0) < 0) {
354
l0g("unable to chown %s: %s\n", filename, strerror(errno));
359
* Note: Waiting too long might interfere with LOGIN_TIMEOUT from
360
* /etc/login.defs, and /bin/login itself may prematurely kill the
364
ret = fcntl(fd, F_SETLKW, &lockinfo);
368
* [Flyn] If someone has locked the file and not written to it
369
* in at least 20 seconds, we assume they either forgot to
370
* unlock it or are catatonic -- chances are slim that they are
371
* in the middle of a read-write cycle and I do not want to
372
* make us lock users out. Perhaps I should just return
373
* %PAM_SUCCESS instead and log the event? Kill the process
374
* holding the lock? Options abound... For now, we ignore it.
376
* [CCJ] pmvarrun is the only one ever writing to that file,
377
* and we keep the lock as short as possible. So if there is no
378
* response within the time limit, something is fouled up (e.g.
379
* NFS server not responding -- though /var/run should at best
380
* not be on an NFS mount). Continue, let user log in, do not
383
w4rn("stale lock on file %s - continuing without increasing"
384
"pam_mount reference count\n", filename);
390
* It is possible at this point that the file has been removed by a
391
* previous login; if this happens, we need to start over.
393
if (stat(filename, &sb) < 0) {
405
* read_current_count -
406
* @fd: file descriptor to read from
408
* Reads the current user's reference count from @fd and returns the value
409
* on success. Otherwise, returns -EOVERFLOW in case we suspect a problem or
410
* <0 to indicate errno.
412
static long read_current_count(int fd, const char *filename) {
413
char buf[ASCIIZ_LLX] = {};
416
if ((ret = read(fd, buf, sizeof(buf))) < 0) {
418
l0g("read error on %s: %s\n", filename, strerror(errno));
421
} else if (ret == 0) {
422
/* File is empty, ret is already 0 -- we are set. */
423
} else if (ret < sizeof(buf)) {
425
if ((ret = strtol(buf, &p, 0)) >= LONG_MAX || p == buf) {
426
l0g("parse problem / session count corrupt "
427
"(overflow), check your refcount file\n");
430
} else if (ret >= sizeof(buf)) {
431
l0g("session count corrupt (overflow)\n");
440
* @fd: file descriptor to write to
441
* @nv: new value to write
442
* @filename: filename, only used for l0g()
444
* Writes @nv as a number in hexadecimal to the start of the file @fd and
445
* truncates the file to the written length.
447
static int write_count(int fd, long nv, const char *filename) {
448
char buf[ASCIIZ_LLX];
452
if (unlink(filename) >= 0)
455
l0g("could not unlink %s: %s\n", filename, strerror(errno));
457
* Fallback to just blanking the file. This can happen when
458
* pmvarrun is called as unprivileged user.
460
if (ftruncate(fd, 0) < 0)
461
w4rn("truncate failed: %s\n", strerror(errno));
465
if ((ret = lseek(fd, 0, SEEK_SET)) != 0) {
467
l0g("failed to seek in %s: %s\n", filename, strerror(errno));
471
len = snprintf(buf, sizeof(buf), "0x%lX", nv);
472
if ((wrt = write(fd, buf, len)) != len) {
474
l0g("wrote %d of %d bytes; write error on %s: %s\n",
475
(wrt < 0) ? 0 : wrt, len, filename, strerror(errno));
479
if (ftruncate(fd, len) < 0) {
481
l0g("truncate failed: %s\n", strerror(errno));