1/*
2 * Copyright (c) 2007-2009 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#define _PAM_EXTERN_FUNCTIONS
25#define PAM_SM_SESSION
26#include <security/pam_modules.h>
27#include <security/pam_appl.h>
28
29#include <stdio.h>
30#include <string.h>
31#include <unistd.h>
32#include <vproc.h>
33#include <vproc_priv.h>
34#include <servers/bootstrap.h>
35#include <bootstrap_priv.h>
36#include <pwd.h>
37#include <sys/syslimits.h>
38
39#define SESSION_TYPE_OPT "launchd_session_type"
40#define DEFAULT_SESSION_TYPE VPROCMGR_SESSION_BACKGROUND
41#define NULL_SESSION_TYPE "NullSession"
42
43/* An application may modify the default behavior as follows:
44 * (1) Choose a specific session type:
45 *     pam_putenv(pamh, "launchd_session_type=Aqua");
46 * (2) Choose to not start a new session:
47 *     pam_putenv(pamh, "launchd_session_type=NullSession");
48 * Otherwise, if launchd_session_type is not set, a new session of the
49 * default type will be created.
50 */
51
52extern vproc_err_t _vproc_post_fork_ping(void);
53
54static mach_port_t
55get_root_bootstrap_port(void)
56{
57	mach_port_t parent_port = MACH_PORT_NULL;
58	mach_port_t previous_port = MACH_PORT_NULL;
59	do {
60		if (previous_port) {
61			if (previous_port != bootstrap_port) {
62				mach_port_deallocate(mach_task_self(), previous_port);
63			}
64			previous_port = parent_port;
65		} else {
66			previous_port = bootstrap_port;
67		}
68		if (bootstrap_parent(previous_port, &parent_port) != 0) {
69			return MACH_PORT_NULL;
70		}
71	} while (parent_port != previous_port);
72
73	return parent_port;
74}
75
76PAM_EXTERN int
77pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
78{
79	char buffer[2*PATH_MAX];
80	const char* default_session_type = DEFAULT_SESSION_TYPE;
81	const char* session_type = pam_getenv(pamh, SESSION_TYPE_OPT);
82	const char* username;
83	struct passwd *pwd;
84	struct passwd pwdbuf;
85	uid_t uid, suid;
86
87	/* Deterine the launchd session type. */
88	if (NULL == (default_session_type = openpam_get_option(pamh, SESSION_TYPE_OPT))) {
89		openpam_log(PAM_LOG_DEBUG, "No session type specified.");
90		default_session_type = DEFAULT_SESSION_TYPE;
91	}
92	if (NULL == session_type) {
93		session_type = default_session_type;
94	} else if (0 == strcmp(session_type, NULL_SESSION_TYPE)) {
95		openpam_log(PAM_LOG_DEBUG, "Skipping due to NULL session type.");
96		return PAM_IGNORE;
97	}
98
99	/* Get the username (and UID). */
100	if (PAM_SUCCESS != pam_get_item(pamh, PAM_USER, (void *)&username) || NULL == username) {
101		openpam_log(PAM_LOG_DEBUG, "The username could not be obtained.");
102		return PAM_IGNORE;
103	}
104	if (0 != getpwnam_r(username, &pwdbuf, buffer, sizeof(buffer), &pwd) || NULL == pwd) {
105		openpam_log(PAM_LOG_DEBUG, "The pwd for %s could not be obtained.", username);
106		return PAM_IGNORE;
107	}
108	uid = pwd->pw_uid;
109	openpam_log(PAM_LOG_DEBUG, "Going to switch to (%s) %u's %s session", username, uid, session_type);
110
111	/* If we're running as root, set the root Mach bootstrap as our bootstrap port. If not, we fail. */
112	if (geteuid() == 0) {
113		mach_port_t rbs = get_root_bootstrap_port();
114		if (rbs) {
115			mach_port_mod_refs(mach_task_self(), bootstrap_port, MACH_PORT_RIGHT_SEND, -1);
116			task_set_bootstrap_port(mach_task_self(), rbs);
117			bootstrap_port = rbs;
118		}
119	} else {
120		openpam_log(PAM_LOG_DEBUG, "We are not running as root.");
121		return PAM_IGNORE;
122	}
123
124	/* We need to set the UID to appease launchd, then lookup the per-user bootstrap. */
125	suid = getuid();
126	setreuid(0, 0);
127	mach_port_t puc = MACH_PORT_NULL;
128	kern_return_t kr = bootstrap_look_up_per_user(bootstrap_port, NULL, uid, &puc);
129	setreuid(suid, 0);
130	if (BOOTSTRAP_SUCCESS != kr) {
131		openpam_log(PAM_LOG_ERROR, "Could not look up per-user bootstrap for UID %u.", uid);
132		return PAM_IGNORE;
133	} else if (BOOTSTRAP_NOT_PRIVILEGED == kr) {
134		openpam_log(PAM_LOG_ERROR, "Permission denied to look up per-user bootstrap for UID %u.", uid);
135		/* If this happens, bootstrap_port is probably already set appropriately anyway. */
136		return PAM_IGNORE;
137	}
138
139	/* Set our bootstrap port to be that of the Background session of the per-user launchd. */
140	mach_port_mod_refs(mach_task_self(), bootstrap_port, MACH_PORT_RIGHT_SEND, -1);
141	task_set_bootstrap_port(mach_task_self(), puc);
142	bootstrap_port = puc;
143
144	/* Now move ourselves into the appropriate session. */
145	if (strncmp(session_type, VPROCMGR_SESSION_BACKGROUND, sizeof(VPROCMGR_SESSION_BACKGROUND)) != 0) {
146		vproc_err_t verr = NULL;
147		if (NULL != (verr = _vprocmgr_switch_to_session(session_type, 0))) {
148			openpam_log(PAM_LOG_ERROR, "Unable to switch to %u's %s session (0x%p).", uid, session_type, verr);
149			return PAM_SESSION_ERR;
150		}
151	}
152	if (NULL != _vproc_post_fork_ping()) {
153		openpam_log(PAM_LOG_ERROR, "Calling _vproc_post_fork_ping failed.");
154		return PAM_SESSION_ERR;
155	}
156
157	return PAM_SUCCESS;
158}
159
160PAM_EXTERN int
161pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
162{
163	return PAM_SUCCESS;
164}
165