3
* Copyright © W. Michael Petullo <mike@flyn.org>, 2004
4
* Copyright © Jan Engelhardt, 2005 - 2008
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 /var/run/pam_mount/<user>.
14
A seperate program is needed so that /var/run/pam_mount/<user> may be
15
created with a pam_mount-specific security context (otherwise SELinux
16
policy will conflict with gdm, which also creates files in /var/run).
20
#include <sys/types.h>
31
#include <libHX/defs.h>
32
#include <libHX/string.h>
34
#include "pam_mount.h"
37
#define ASCIIZ_LLX sizeof("0xFFFFFFFF""FFFFFFFF")
38
#define VAR_RUN "/var/run"
39
#define VAR_RUN_PMT VAR_RUN "/pam_mount"
47
static int create_var_run(void);
48
static int modify_pm_count(const char *, long);
49
static int open_and_lock(const char *, long);
50
static void parse_args(const int, const char **, struct settings *);
51
static long read_current_count(int, const char *);
52
static void set_defaults(struct settings *);
53
static void usage(int, const char *);
54
static int write_count(int, long, const char *);
57
* usage - display help
58
* @exitcode: numeric value we will be exiting with
59
* @error: descriptive error string
61
* Displays the help string and an optional extra error message.
63
static void usage(const int exitcode, const char *error)
65
fprintf(stderr, "Usage: pmvarrun -u USER [-o NUMBER] [-d]\n");
67
fprintf(stderr, "%s\n\n", error);
73
* @settings: pointer to settings structure
75
static void set_defaults(struct settings *settings)
79
settings->user = NULL;
80
settings->operation = 1;
82
if ((s = getenv("_PMT_DEBUG_LEVEL")) != NULL) {
83
Debug = strtoul(s, NULL, 0);
84
pmtlog_path[PMTLOG_ERR][PMTLOG_STDERR] = Debug;
85
pmtlog_path[PMTLOG_DBG][PMTLOG_STDERR] = Debug;
90
* from git://dev.medozas.de/vitalnix /src/libvxutil/util.c
92
static bool valid_username(const char *n)
98
* Note to future editors: Some systems disallow leading digits for
99
* usernames. Possibly because it is concerned about badly-written
100
* programs detecting numeric UIDs by merely doing
101
* strtoul(s, &e, 0) && s != e, which falls victim to usernames
102
* with leading digits.
103
* So pam_mount, in its original form at least (distros can patch
104
* their copy up as they see fit), should at best reject
105
* leading digits too.
108
* Cannot use isalpha/isdigit here since that may include
111
if (!((*n >= 'A' && *n <= 'Z') || (*n >= 'a' && *n <= 'z') ||
118
if (*n == '$' && *(n+1) == '\0') /* Samba accounts */
121
valid = (*n >= 'A' && *n <= 'Z') || (*n >= 'a' && *n <= 'z') ||
122
(*n >= '0' && *n <= '9') || *n == '_' || *n == '.' ||
123
*n == '-' || *n == ' ' || *n == '\\';
134
* @n: string to analyze
136
* Calls @strtol on @n using base 10 and makes sure there were no invalid
137
* characters in @n. Returns the value, or %LONG_MAX in case of an
139
* NOTE: This function is only referenced from pmvarrun.c.
141
long str_to_long(const char *n)
146
l0g("count string is NULL\n");
149
val = strtol(n, &endptr, 10);
150
if (*endptr != '\0') {
151
l0g("count string is not valid\n");
159
* @argc: number of elements in @argv
160
* @argv: NULL-terminated argument vector
161
* @settings: pointer to settings structure
163
* Parse options from @argv and put it into @settings.
165
static void parse_args(int argc, const char **argv, struct settings *settings)
169
while ((c = getopt(argc, const_cast2(char * const *, argv),
173
usage(EXIT_SUCCESS, NULL);
179
settings->operation = str_to_long(optarg);
180
if (settings->operation == LONG_MAX ||
181
settings->operation == LONG_MIN)
182
usage(EXIT_FAILURE, "count string is not valid");
185
if (!valid_username(optarg)) {
186
fprintf(stderr, "Invalid user name\n");
189
if ((settings->user = HX_strdup(optarg)) == NULL)
193
usage(EXIT_FAILURE, NULL);
201
* @user: user to poke on
202
* @amount: increment (usually -1, 0 or +1)
204
* Adjusts /var/run/pam_mount/@user by @amount, or deletes the file if the
205
* resulting value (current + @amount) is <= 0. Returns >= 0 on success to
206
* indicate the new login count, or negative to indicate errno. -ESTALE and
207
* -EOVERFLOW are passed up from subfunctions and must be handled in the
210
static int modify_pm_count(const char *user, long amount)
212
char filename[PATH_MAX + 1];
218
assert(user != NULL);
220
if ((pent = getpwnam(user)) == NULL) {
222
l0g("could not resolve user %s\n", user);
226
if (stat(VAR_RUN_PMT, &sb) < 0) {
227
if (errno != ENOENT) {
229
l0g("unable to stat " VAR_RUN_PMT ": %s\n",
233
if ((ret = create_var_run()) < 0)
237
snprintf(filename, sizeof(filename), VAR_RUN_PMT "/%s", user);
238
while ((ret = fd = open_and_lock(filename, pent->pw_uid)) == -EAGAIN)
243
if ((val = read_current_count(fd, filename)) < 0) {
248
w4rn("parsed count value %ld\n", val);
249
/* amount == 0 implies query */
252
ret = write_count(fd, val + amount, filename);
255
return (ret < 0) ? ret : val + amount;
258
int main(int argc, const char **argv)
260
struct settings settings;
264
/* pam_mount.so will pick stderr up */
265
pmtlog_path[PMTLOG_ERR][PMTLOG_STDERR] = true;
266
pmtlog_path[PMTLOG_DBG][PMTLOG_STDERR] = Debug;
267
pmtlog_prefix = "pmvarrun";
269
set_defaults(&settings);
270
parse_args(argc, argv, &settings);
272
if (settings.user == NULL)
273
usage(EXIT_FAILURE, NULL);
275
ret = modify_pm_count(settings.user, settings.operation);
276
if (ret == -ESTALE) {
279
} else if (ret < 0) {
283
/* print current count so pam_mount module may read it */
288
//-----------------------------------------------------------------------------
292
* Creates the /var/run/pam_mount directory required by pmvarrun and sets
293
* proper permissions on it.
295
* Returns >0 for success or <=0 to indicate errno.
297
static int create_var_run(void)
301
w4rn("creating " VAR_RUN_PMT);
302
if (mkdir(VAR_RUN_PMT, 0000) < 0) {
304
l0g("unable to create " VAR_RUN_PMT ": %s\n", strerror(errno));
307
if (chown(VAR_RUN_PMT, 0, 0) < 0) {
309
l0g("unable to chown " VAR_RUN_PMT ": %s\n", strerror(errno));
314
* 0755: `su` creates file group owned by user and then releases root
315
* permissions. User needs to be able to access file on logout.
317
if (chmod(VAR_RUN_PMT, S_IRWXU | S_IRXG | S_IRXO) < 0) {
319
l0g("unable to chmod " VAR_RUN_PMT ": %s\n", strerror(errno));
328
* @filename: file to open
330
* Creates if necessary, opens and chown()s @filename, and locks it.
331
* Returns the fd if all of that succeeded, -EAGAIN if the file was unlinked
332
* during operation (see below), -ESTALE if the lock could not be obtained,
333
* and <0 otherwise to indicate errno.
335
static int open_and_lock(const char *filename, long uid) {
336
struct flock lockinfo = {
338
.l_whence = SEEK_SET,
345
if ((fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
347
l0g("unable to open %s: %s\n", filename, strerror(errno));
350
if (fchown(fd, uid, 0) < 0) {
352
l0g("unable to chown %s: %s\n", filename, strerror(errno));
357
* Note: Waiting too long might interfere with LOGIN_TIMEOUT from
358
* /etc/login.defs, and /bin/login itself may prematurely kill the
362
ret = fcntl(fd, F_SETLKW, &lockinfo);
366
* [Flyn] If someone has locked the file and not written to it
367
* in at least 20 seconds, we assume they either forgot to
368
* unlock it or are catatonic -- chances are slim that they are
369
* in the middle of a read-write cycle and I do not want to
370
* make us lock users out. Perhaps I should just return
371
* %PAM_SUCCESS instead and log the event? Kill the process
372
* holding the lock? Options abound... For now, we ignore it.
374
* [CCJ] pmvarrun is the only one ever writing to that file,
375
* and we keep the lock as short as possible. So if there is no
376
* response within the time limit, something is fouled up (e.g.
377
* NFS server not responding -- though /var/run should at best
378
* not be on an NFS mount). Continue, let user log in, do not
381
w4rn("stale lock on file %s - continuing without increasing"
382
"pam_mount reference count\n", filename);
388
* It is possible at this point that the file has been removed by a
389
* previous login; if this happens, we need to start over.
391
if (stat(filename, &sb) < 0) {
403
* read_current_count -
404
* @fd: file descriptor to read from
406
* Reads the current user's reference count from @fd and returns the value
407
* on success. Otherwise, returns -EOVERFLOW in case we suspect a problem or
408
* <0 to indicate errno.
410
static long read_current_count(int fd, const char *filename) {
411
char buf[ASCIIZ_LLX] = {};
414
if ((ret = read(fd, buf, sizeof(buf))) < 0) {
416
l0g("read error on %s: %s\n", filename, strerror(errno));
419
} else if (ret == 0) {
420
/* File is empty, ret is already 0 -- we are set. */
421
} else if (ret < sizeof(buf)) {
423
if ((ret = strtol(buf, &p, 0)) >= LONG_MAX || p == buf) {
424
l0g("parse problem / session count corrupt "
425
"(overflow), check your refcount file\n");
428
} else if (ret >= sizeof(buf)) {
429
l0g("session count corrupt (overflow)\n");
438
* @fd: file descriptor to write to
439
* @nv: new value to write
440
* @filename: filename, only used for l0g()
442
* Writes @nv as a number in hexadecimal to the start of the file @fd and
443
* truncates the file to the written length.
445
static int write_count(int fd, long nv, const char *filename) {
446
char buf[ASCIIZ_LLX];
450
if (unlink(filename) >= 0)
453
l0g("could not unlink %s: %s\n", filename, strerror(errno));
455
* Fallback to just blanking the file. This can happen when
456
* pmvarrun is called as unprivileged user.
458
if (ftruncate(fd, 0) < 0)
459
w4rn("truncate failed: %s\n", strerror(errno));
463
if ((ret = lseek(fd, 0, SEEK_SET)) != 0) {
465
l0g("failed to seek in %s: %s\n", filename, strerror(errno));
469
len = snprintf(buf, sizeof(buf), "0x%lX", nv);
470
if ((wrt = write(fd, buf, len)) != len) {
472
l0g("wrote %d of %d bytes; write error on %s: %s\n",
473
(wrt < 0) ? 0 : wrt, len, filename, strerror(errno));
477
if (ftruncate(fd, len) < 0) {
479
l0g("truncate failed: %s\n", strerror(errno));