3
* Copyright (C) Elvis Pfützenreuter <epx@conectiva.com>, 2000
4
* Copyright © Jan Engelhardt, 2005 - 2010
5
* Copyright © Bastian Kleineidam, 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.
12
#define PAM_SM_ACCOUNT 1
14
#define PAM_SM_SESSION 1
15
#define PAM_SM_PASSWORD 1
17
#include <security/pam_appl.h>
18
#include <security/pam_modules.h>
20
#include <sys/types.h>
31
#include <libHX/defs.h>
32
#include <libHX/proc.h>
34
#include "pam_mount.h"
40
#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
41
# define CONFIGFILE "/etc/pam_mount.conf.xml"
43
# define CONFIGFILE "/etc/security/pam_mount.conf.xml"
47
bool get_pw_from_pam, get_pw_interactive, propagate_pw;
51
static void clean_config(pam_handle_t *, void *, int);
52
static int converse(pam_handle_t *, int, const struct pam_message **,
53
struct pam_response **);
54
static int modify_pm_count(struct config *, char *, char *);
55
static void parse_pam_args(int, const char **);
56
static int read_password(pam_handle_t *, const char *, char **);
59
static const char *envpath_saved;
63
//-----------------------------------------------------------------------------
66
* @argv: NULL-terminated argument vector
67
* @argc: number of elements in @argc
69
* Global @Args is initialized, based on @argv.
71
static void parse_pam_args(int argc, const char **argv)
76
for (i = 0; i < argc; i++)
77
assert(argv[i] != NULL);
79
/* first, set default values */
80
Args.get_pw_from_pam = true;
81
Args.get_pw_interactive = true;
82
Args.propagate_pw = true;
84
for (i = 0; i < argc; ++i) {
85
if (strcasecmp("enable_pam_password", argv[i]) == 0)
86
Args.get_pw_from_pam = true;
87
else if (strcasecmp("disable_pam_password", argv[i]) == 0)
88
Args.get_pw_from_pam = false;
89
else if (strcasecmp("enable_interactive", argv[i]) == 0)
90
Args.get_pw_interactive = true;
91
else if (strcasecmp("disable_interactive", argv[i]) == 0)
92
Args.get_pw_interactive = false;
93
else if (strcasecmp("enable_propagate_password", argv[i]) == 0)
94
Args.propagate_pw = true;
95
else if (strcasecmp("disable_propagate_password", argv[i]) == 0)
96
Args.propagate_pw = false;
97
else if (strcasecmp("debug", argv[i]) == 0)
100
w4rn("unknown pam_mount option \"%s\"\n", argv[i]);
107
* @data: custom data pointer
110
* Free data from a struct config variable.
111
* Note: This is registered as a PAM callback function and is called directly.
113
static void clean_config(pam_handle_t *pamh, void *data, int err)
115
w4rn("Clean global config (%d)\n", err);
120
* clean_system_authtok -
122
* @data: custom data pointer
125
* Zero and free @data if it is not %NULL.
126
* Note: This is registered as a PAM callback function and is called directly.
128
* FIXME: Not binary-password safe.
130
static void clean_system_authtok(pam_handle_t *pamh, void *data, int errcode)
132
w4rn("clean system authtok=%p (%d)\n", data, errcode);
135
unsigned int len = strlen(data) + 1;
136
memset(data, 0, len);
145
* @nargs: number of messages
146
* @message: PAM message array
147
* @resp: user response array
149
* Note: Adapted from pam_unix/support.c.
151
static int converse(pam_handle_t *pamh, int nargs,
152
const struct pam_message **message, struct pam_response **resp)
155
struct pam_conv *conv;
157
assert(pamh != NULL);
159
assert(resp != NULL);
162
retval = pam_get_item(pamh, PAM_CONV, static_cast(const void **,
163
static_cast(void *, &conv)));
165
if (retval != PAM_SUCCESS) {
166
l0g("pam_get_item: %s\n", pam_strerror(pamh, retval));
167
} else if (conv == NULL || conv->conv == NULL) {
168
w4rn("No converse function available\n");
170
retval = conv->conv(nargs, message, resp, conv->appdata_ptr);
171
if (retval != PAM_SUCCESS)
172
l0g("conv->conv(...): %s\n", pam_strerror(pamh, retval));
175
if (resp == NULL || *resp == NULL || (*resp)->resp == NULL)
176
retval = PAM_AUTH_ERR;
178
assert(retval != PAM_SUCCESS || (resp != NULL && *resp != NULL &&
179
(*resp)->resp != NULL));
180
return retval; /* propagate error status */
186
* @prompt: a prompt message
187
* @pass: space for entered password
189
* Returns PAM error code or %PAM_SUCCESS.
190
* Note: Adapted from pam_unix/support.c:_unix_read_password().
192
static int read_password(pam_handle_t *pamh, const char *prompt, char **pass)
195
struct pam_message msg;
196
const struct pam_message *pmsg = &msg;
197
struct pam_response *resp = NULL;
199
assert(pamh != NULL);
200
assert(pass != NULL);
203
msg.msg_style = PAM_PROMPT_ECHO_OFF;
204
msg.msg = (prompt == NULL) ? "Password: " : prompt;
205
retval = converse(pamh, 1, &pmsg, &resp);
206
if (retval == PAM_SUCCESS)
207
*pass = xstrdup(resp->resp);
209
assert(retval != PAM_SUCCESS || (pass != NULL && *pass != NULL));
213
static void pmt_sigpipe_setup(bool block_it)
215
static pthread_mutex_t sp_lock = PTHREAD_MUTEX_INITIALIZER;
216
static int sp_blocked = 0;
217
static bool sp_previous;
218
sigset_t set, oldset;
220
pthread_mutex_lock(&sp_lock);
222
if (++sp_blocked == 1) {
224
sigaddset(&set, SIGPIPE);
225
sigprocmask(SIG_BLOCK, &set, &oldset);
226
sp_previous = sigismember(&oldset, SIGPIPE);
229
if (--sp_blocked == 0 && sp_previous) {
231
sigaddset(&set, SIGPIPE);
232
sigtimedwait(&set, NULL, &(struct timespec){0, 0});
233
sigprocmask(SIG_UNBLOCK, &set, NULL);
237
pthread_mutex_unlock(&sp_lock);
240
static int common_init(pam_handle_t *pamh, int argc, const char **argv)
242
const char *pam_user;
246
pmtlog_prefix = "pam_mount";
247
pmtlog_path[PMTLOG_ERR][PMTLOG_SYSLOG] = true;
248
pmtlog_path[PMTLOG_ERR][PMTLOG_STDERR] = true;
249
pmtlog_path[PMTLOG_DBG][PMTLOG_SYSLOG] = Debug;
250
pmtlog_path[PMTLOG_DBG][PMTLOG_STDERR] = Debug;
254
l0g("libHX init failed: %s\n", strerror(errno));
257
parse_pam_args(argc, argv);
259
* call pam_get_user again because ssh calls PAM fns from seperate
262
ret = pam_get_user(pamh, &pam_user, NULL);
263
if (ret != PAM_SUCCESS) {
264
l0g("could not get user");
266
* do NOT return %PAM_SERVICE_ERR or root will not be able to
268
* Also, if we could not get the user's info, an earlier auth
269
* module (like pam_unix2) likely blocked login already.
274
* FIXME: free me! the dup is requried because result of pam_get_user()
275
* disappears (valgrind)
277
Config.user = relookup_user(pam_user);
278
if (!readconfig(CONFIGFILE, true, &Config))
279
return PAM_SERVICE_ERR;
281
/* reinitialize after @Debug may have changed */
282
pmtlog_path[PMTLOG_DBG][PMTLOG_STDERR] = Debug;
283
pmtlog_path[PMTLOG_DBG][PMTLOG_SYSLOG] = Debug;
285
snprintf(buf, sizeof(buf), "%u", Debug);
286
setenv("_PMT_DEBUG_LEVEL", buf, true);
288
pmt_sigpipe_setup(true);
292
static void common_exit(void)
294
pmt_sigpipe_setup(false);
298
static void auth_grab_authtok(pam_handle_t *pamh, struct config *config)
300
char *authtok = NULL;
303
if (Args.get_pw_from_pam) {
306
ret = pam_get_item(pamh, PAM_AUTHTOK, static_cast(const void **,
307
static_cast(void *, &ptr)));
308
if (ret == PAM_SUCCESS && ptr != NULL)
309
authtok = xstrdup(ptr);
311
if (authtok == NULL && Args.get_pw_interactive) {
312
ret = read_password(pamh, config->msg_authpw, &authtok);
313
if (ret == PAM_SUCCESS && Args.propagate_pw) {
315
* pam_set_item() copies to PAM-internal memory.
317
* Using pam_set_item(PAM_AUTHTOK) here to make the
318
* password that was just entered available to further
321
ret = pam_set_item(pamh, PAM_AUTHTOK, authtok);
322
if (ret != PAM_SUCCESS)
323
l0g("warning: failure to export password (%s)\n",
324
pam_strerror(pamh, ret));
329
* Save auth token for pam_mount itself, since PAM_AUTHTOK
330
* will be gone when the auth stage exits.
332
if (authtok != NULL) {
333
ret = pam_set_data(pamh, "pam_mount_system_authtok", authtok,
334
clean_system_authtok);
335
if (ret == PAM_SUCCESS) {
336
if (mlock(authtok, strlen(authtok) + 1) < 0)
337
w4rn("mlock authtok: %s\n", strerror(errno));
339
l0g("error trying to save authtok for session code\n");
345
* pam_sm_authenticate -
348
* @argc: number of elements in @argv
349
* @argv: NULL-terminated argument vector
351
* Called by the PAM layer. The user's system password is added to PAM's
352
* global module data. This is because pam_sm_open_session() does not allow
353
* access to the user's password. Returns the PAM error code or %PAM_SUCCESS.
355
PAM_EXTERN EXPORT_SYMBOL int pam_sm_authenticate(pam_handle_t *pamh, int flags,
356
int argc, const char **argv)
358
int ret = PAM_SUCCESS;
360
assert(pamh != NULL);
362
if ((ret = common_init(pamh, argc, argv)) != -1)
364
w4rn(PACKAGE_STRING ": entering auth stage\n");
365
auth_grab_authtok(pamh, &Config);
368
* pam_mount is not really meant to be an auth module. So we should not
369
* hinder the login process.
375
* On login, $PATH is correctly set to ENV_ROOTPATH (from /etc/login.defs),
376
* while on logout, it happens to be ENV_PATH only. This is problematic,
377
* since some programs are in /sbin and /usr/sbin which is
378
* often not contained in ENV_PATH.
380
* In short: Another workaround for coreutils.
382
static void envpath_init(const char *new_path)
384
envpath_saved = getenv("PATH");
385
setenv("PATH", new_path, true);
388
static void envpath_restore(void)
390
if (envpath_saved == NULL)
393
setenv("PATH", envpath_saved, true);
400
* @operation: string specifying numerical increment
402
* Calls out to the `pmvarrun` helper utility to adjust the mount reference
403
* count in /var/run/pam_mount/@user for the specified user.
404
* Returns the new reference count value on success, or -1 on error.
406
* Note: Modified version of pam_console.c:use_count()
408
static int modify_pm_count(struct config *config, char *user,
412
struct HXformat_map *vinfo;
413
struct HXdeque *argv;
415
int ret = -1, use_count;
417
assert(user != NULL);
418
assert(operation != NULL);
420
if ((vinfo = HXformat_init()) == NULL)
422
format_add(vinfo, "USER", user);
423
format_add(vinfo, "OPERATION", operation);
424
misc_add_ntdom(vinfo, user);
426
argv = arglist_build(config->command[CMD_PMVARRUN], vinfo);
427
memset(&proc, 0, sizeof(proc));
428
proc.p_flags = HXPROC_VERBOSE | HXPROC_STDOUT;
429
proc.p_ops = &pmt_dropprivs_ops;
430
if ((ret = pmt_spawn_dq(argv, &proc)) <= 0) {
431
l0g("error executing pmvarrun: %s\n", strerror(-ret));
435
if ((fp = fdopen(proc.p_stdout, "r")) == NULL)
437
if (fscanf(fp, "%d", &use_count) != 1)
438
w4rn("error reading login count from pmvarrun\n");
440
w4rn("pmvarrun says login count is %d\n", use_count);
445
close(proc.p_stdout);
446
if (HXproc_wait(&proc) >= 0 && proc.p_exited && proc.p_status == 0)
450
HXformat_free(vinfo);
455
* ses_grab_authtok - get the password from PAM
457
* Session stage: reretrieve password that the auth stage stored.
458
* If that does not work, use interactive prompting if enabled.
460
static char *ses_grab_authtok(pam_handle_t *pamh)
462
char *authtok = NULL;
465
ret = pam_get_data(pamh, "pam_mount_system_authtok",
466
static_cast(const void **, static_cast(void *, &authtok)));
467
if (ret == PAM_SUCCESS)
470
/* No stored password, get one, if allowed to. */
471
if (Args.get_pw_interactive) {
472
ret = read_password(pamh, Config.msg_sessionpw, &authtok);
473
if (ret != PAM_SUCCESS)
474
l0g("warning: could not obtain password "
475
"interactively either\n");
477
if (authtok != NULL) {
478
ret = pam_set_data(pamh, "pam_mount_system_authtok",
479
authtok, clean_system_authtok);
480
if (ret == PAM_SUCCESS) {
481
if (mlock(authtok, strlen(authtok) + 1) < 0)
482
w4rn("mlock authtok: %s\n", strerror(errno));
484
l0g("error trying to save authtok for session code\n");
488
* Always proceed, even if there is no password. Some volumes may not
489
* need one, e.g. bind mounts and networked/unencrypted volumes.
494
static int process_volumes(struct config *config, const char *authtok)
496
int ret = PAM_SUCCESS;
499
HXlist_for_each_entry(vol, &config->volume_list, list) {
501
* Remember what we processed already - the function can
502
* be called multiple times.
504
if (vol->mnt_processed)
506
vol->mnt_processed = true;
508
* luserconf_volume_record_sane() is called here so that a user
509
* can nest loopback images. otherwise ownership tests will
510
* fail if parent loopback image not yet mounted.
511
* volume_record_sane() is here to be consistent.
513
if (!volume_record_sane(config, vol))
515
if (!vol->globalconf &&
516
!luserconf_volume_record_sane(config, vol))
519
if (!mount_op(do_mount, config, vol, authtok)) {
520
l0g("mount of %s failed\n", znul(vol->volume));
521
ret = PAM_SERVICE_ERR;
528
* pam_sm_open_session -
531
* @argc: number of elements in @argv
532
* @argv: NULL-terminated argument vector
534
* Entrypoint from the PAM layer. Starts the wheels and eventually mounts the
535
* user's directories according to pam_mount.conf.xml. Returns the PAM error
536
* code or %PAM_SUCCESS.
538
PAM_EXTERN EXPORT_SYMBOL int pam_sm_open_session(pam_handle_t *pamh, int flags,
539
int argc, const char **argv)
543
char *system_authtok = NULL;
547
assert(pamh != NULL);
549
if ((ret = common_init(pamh, argc, argv)) != -1)
552
w4rn(PACKAGE_STRING ": entering session stage\n");
555
* Environment variables set with setenv() only last while PAM is
556
* active, i.e. disappear when the shell is started. On the other hand,
557
* variabled fed to pam_putenv() are only visible once the shell
561
* Get the Kerberos CCNAME so we can make it available to the
562
* mount command later on.
564
krb5 = pam_getenv(pamh, "KRB5CCNAME");
565
if (krb5 != NULL && setenv("KRB5CCNAME", krb5, true) < 0)
566
l0g("KRB5CCNAME setenv failed\n");
568
/* Store initialized config as PAM data */
569
getval = pam_get_data(pamh, "pam_mount_config", &tmp);
570
if (getval == PAM_NO_MODULE_DATA) {
571
ret = pam_set_data(pamh, "pam_mount_config",
572
&Config, clean_config);
573
if (ret != PAM_SUCCESS) {
574
l0g("error trying to save config structure\n");
579
if (!expandconfig(&Config)) {
580
l0g("error expanding configuration\n");
581
ret = PAM_SERVICE_ERR;
584
if (Config.volume_list.items > 0)
585
/* There are some volumes, so grab a password. */
586
system_authtok = ses_grab_authtok(pamh);
588
misc_dump_id("Session open");
589
envpath_init(Config.path);
590
ret = process_volumes(&Config, system_authtok);
593
* Read luserconf after mounting of initial volumes. This makes it
594
* possible to store luserconfs on net volumes themselves.
596
if (Config.luserconf != NULL && *Config.luserconf != '\0' &&
597
pmt_fileop_exists(Config.luserconf)) {
598
w4rn("going to readconfig %s\n", Config.luserconf);
599
if (!pmt_fileop_owns(Config.user, Config.luserconf)) {
600
w4rn("%s does not exist or is not owned by user\n",
602
} else if (!readconfig(Config.luserconf, false, &Config)) {
603
ret = PAM_SERVICE_ERR;
604
} else if (!expandconfig(&Config)) {
605
ret = PAM_SERVICE_ERR;
606
l0g("error expanding configuration\n");
610
if (Config.volume_list.items == 0) {
611
w4rn("no volumes to mount\n");
615
* If there are no global volumes, but luserconf volumes,
616
* and we still have no password, ask for one now.
618
if (system_authtok == NULL)
619
system_authtok = ses_grab_authtok(pamh);
620
ret = process_volumes(&Config, system_authtok);
623
modify_pm_count(&Config, Config.user, "1");
626
/* Make sure root can always log in. */
627
/* NB: I don't even wanna think of SELINUX's ambiguous UIDs... */
631
* If mounting something failed, e.g. ret = %PAM_SERVICE_ERR, we have
632
* to unravel everything and umount all volumes. But *only* if
633
* pam_mount was configured as a "required" module. How can this info
635
* For now, always assume "optional", so that the volumes are
636
* definitely unmounted when the user logs out again.
641
unsetenv("KRB5CCNAME");
642
w4rn("done opening session (ret=%d)\n", ret);
651
* @argc: number of elements in @argv
652
* @argv: NULL-terminated argument vector
654
* This is a placeholder function so PAM does not get mad.
656
PAM_EXTERN EXPORT_SYMBOL int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
657
int argc, const char **argv)
659
return pam_sm_authenticate(pamh, flags, argc, argv);
663
* pam_sm_close_session -
666
* @argc: number of elements in @argv
667
* @argv: NULL-terminated argument vector
669
* Entrypoint from the PAM layer. Stops all wheels and eventually unmounts the
670
* user's directories. Returns the PAM error code or %PAM_SUCCESS.
672
* FIXME: This function currently always returns %PAM_SUCCESS. Should it
673
* return soemthing else when errors occur and all unmounts have been
676
PAM_EXTERN EXPORT_SYMBOL int pam_sm_close_session(pam_handle_t *pamh,
677
int flags, int argc, const char **argv)
679
const char *pam_user = NULL;
682
assert(pamh != NULL);
686
l0g("libHX init failed: %s\n", strerror(errno));
687
/* Up the refcount once again due to cleanconfig's delayed nature. */
690
w4rn("received order to close things\n");
691
if (Config.volume_list.items == 0) {
692
w4rn("No volumes to umount\n");
696
misc_dump_id("Session close");
698
* call pam_get_user() again because ssh calls PAM fns from seperate
701
ret = pam_get_user(pamh, &pam_user, NULL);
702
if (ret != PAM_SUCCESS) {
703
l0g("could not get user\n");
707
* FIXME: free me! the dup is requried because result of pam_get_user
708
* disappears (valgrind)
710
Config.user = relookup_user(pam_user);
711
/* if our CWD is in the home directory, it might not get umounted */
713
l0g("could not chdir\n");
716
envpath_init(Config.path);
717
if (modify_pm_count(&Config, Config.user, "-1") > 0)
718
w4rn("%s seems to have other remaining open sessions\n",
721
umount_final(&Config);
725
* Note that PMConfig is automatically freed later in clean_config()
727
w4rn("pam_mount execution complete\n");
736
* @argc: number of elements in @argv
737
* @argv: NULL-terminated argument vector
739
* This is a placeholder function so PAM does not get mad.
741
PAM_EXTERN EXPORT_SYMBOL int pam_sm_setcred(pam_handle_t *pamh, int flags,
742
int argc, const char **argv)
751
* @argc: number of elements in @argv
752
* @argv: NULL-terminated argument vector
754
* This is a placeholder function so PAM does not get mad.
756
PAM_EXTERN EXPORT_SYMBOL int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
757
int argc, const char **argv)
763
/* static module data */
765
EXPORT_SYMBOL struct pam_module _pam_mount_modstruct = {
767
.pam_sm_authenticate = pam_sm_authenticate,
768
.pam_sm_setcred = pam_sm_setcred,
769
.pam_sm_acct_mgmt = pam_sm_acct_mgmt,
770
.pam_sm_open_sesion = pam_sm_open_session,
771
.pam_sm_close_session = pam_sm_close_session,
772
.pam_sm_chauthtok = pam_sm_chauthtok,