1/*
2 * Copyright (c) 1999,2007 Andrew G. Morgan <morgan@kernel.org>
3 *
4 * The purpose of this module is to enforce inheritable capability sets
5 * for a specified user.
6 */
7
8/* #define DEBUG */
9
10#include <stdio.h>
11#include <string.h>
12#include <errno.h>
13#include <stdarg.h>
14#include <stdlib.h>
15#include <syslog.h>
16
17#include <sys/capability.h>
18
19#include <security/pam_modules.h>
20#include <security/_pam_macros.h>
21
22#define USER_CAP_FILE           "/etc/security/capability.conf"
23#define CAP_FILE_BUFFER_SIZE    4096
24#define CAP_FILE_DELIMITERS     " \t\n"
25#define CAP_COMBINED_FORMAT     "%s all-i %s+i"
26#define CAP_DROP_ALL            "%s all-i"
27
28struct pam_cap_s {
29    int debug;
30    const char *user;
31    const char *conf_filename;
32};
33
34/* obtain the inheritable capabilities for the current user */
35
36static char *read_capabilities_for_user(const char *user, const char *source)
37{
38    char *cap_string = NULL;
39    char buffer[CAP_FILE_BUFFER_SIZE], *line;
40    FILE *cap_file;
41
42    cap_file = fopen(source, "r");
43    if (cap_file == NULL) {
44	D(("failed to open capability file"));
45	return NULL;
46    }
47
48    while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
49	int found_one = 0;
50	const char *cap_text;
51
52	cap_text = strtok(line, CAP_FILE_DELIMITERS);
53
54	if (cap_text == NULL) {
55	    D(("empty line"));
56	    continue;
57	}
58	if (*cap_text == '#') {
59	    D(("comment line"));
60	    continue;
61	}
62
63	while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) {
64
65	    if (strcmp("*", line) == 0) {
66		D(("wildcard matched"));
67		found_one = 1;
68		cap_string = strdup(cap_text);
69		break;
70	    }
71
72	    if (strcmp(user, line) == 0) {
73		D(("exact match for user"));
74		found_one = 1;
75		cap_string = strdup(cap_text);
76		break;
77	    }
78
79	    D(("user is not [%s] - skipping", line));
80	}
81
82	cap_text = NULL;
83	line = NULL;
84
85	if (found_one) {
86	    D(("user [%s] matched - caps are [%s]", user, cap_string));
87	    break;
88	}
89    }
90
91    fclose(cap_file);
92
93    memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
94
95    return cap_string;
96}
97
98/*
99 * Set capabilities for current process to match the current
100 * permitted+executable sets combined with the configured inheritable
101 * set.
102 */
103
104static int set_capabilities(struct pam_cap_s *cs)
105{
106    cap_t cap_s;
107    ssize_t length = 0;
108    char *conf_icaps;
109    char *proc_epcaps;
110    char *combined_caps;
111    int ok = 0;
112
113    cap_s = cap_get_proc();
114    if (cap_s == NULL) {
115	D(("your kernel is capability challenged - upgrade: %s",
116	   strerror(errno)));
117	return 0;
118    }
119
120    conf_icaps =
121	read_capabilities_for_user(cs->user,
122				   cs->conf_filename
123				   ? cs->conf_filename:USER_CAP_FILE );
124    if (conf_icaps == NULL) {
125	D(("no capabilities found for user [%s]", cs->user));
126	goto cleanup_cap_s;
127    }
128
129    proc_epcaps = cap_to_text(cap_s, &length);
130    if (proc_epcaps == NULL) {
131	D(("unable to convert process capabilities to text"));
132	goto cleanup_icaps;
133    }
134
135    /*
136     * This is a pretty inefficient way to combine
137     * capabilities. However, it seems to be the most straightforward
138     * one, given the limitations of the POSIX.1e draft spec. The spec
139     * is optimized for applications that know the capabilities they
140     * want to manipulate at compile time.
141     */
142
143    combined_caps = malloc(1+strlen(CAP_COMBINED_FORMAT)
144			   +strlen(proc_epcaps)+strlen(conf_icaps));
145    if (combined_caps == NULL) {
146	D(("unable to combine capabilities into one string - no memory"));
147	goto cleanup_epcaps;
148    }
149
150    if (!strcmp(conf_icaps, "none")) {
151	sprintf(combined_caps, CAP_DROP_ALL, proc_epcaps);
152    } else if (!strcmp(conf_icaps, "all")) {
153	/* no change */
154	sprintf(combined_caps, "%s", proc_epcaps);
155    } else {
156	sprintf(combined_caps, CAP_COMBINED_FORMAT, proc_epcaps, conf_icaps);
157    }
158    D(("combined_caps=[%s]", combined_caps));
159
160    cap_free(cap_s);
161    cap_s = cap_from_text(combined_caps);
162    _pam_overwrite(combined_caps);
163    _pam_drop(combined_caps);
164
165#ifdef DEBUG
166    {
167        char *temp = cap_to_text(cap_s, NULL);
168	D(("abbreviated caps for process will be [%s]", temp));
169	cap_free(temp);
170    }
171#endif /* DEBUG */
172
173    if (cap_s == NULL) {
174	D(("no capabilies to set"));
175    } else if (cap_set_proc(cap_s) == 0) {
176	D(("capabilities were set correctly"));
177	ok = 1;
178    } else {
179	D(("failed to set specified capabilities: %s", strerror(errno)));
180    }
181
182cleanup_epcaps:
183    cap_free(proc_epcaps);
184
185cleanup_icaps:
186    _pam_overwrite(conf_icaps);
187    _pam_drop(conf_icaps);
188
189cleanup_cap_s:
190    if (cap_s) {
191	cap_free(cap_s);
192	cap_s = NULL;
193    }
194
195    return ok;
196}
197
198/* log errors */
199
200static void _pam_log(int err, const char *format, ...)
201{
202    va_list args;
203
204    va_start(args, format);
205    openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH);
206    vsyslog(err, format, args);
207    va_end(args);
208    closelog();
209}
210
211static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
212{
213    int ctrl=0;
214
215    /* step through arguments */
216    for (ctrl=0; argc-- > 0; ++argv) {
217
218	if (!strcmp(*argv, "debug")) {
219	    pcs->debug = 1;
220	} else if (!memcmp(*argv, "config=", 7)) {
221	    pcs->conf_filename = 7 + *argv;
222	} else {
223	    _pam_log(LOG_ERR, "unknown option; %s", *argv);
224	}
225
226    }
227}
228
229int pam_sm_authenticate(pam_handle_t *pamh, int flags,
230			int argc, const char **argv)
231{
232    int retval;
233    struct pam_cap_s pcs;
234    char *conf_icaps;
235
236    memset(&pcs, 0, sizeof(pcs));
237
238    parse_args(argc, argv, &pcs);
239
240    retval = pam_get_user(pamh, &pcs.user, NULL);
241
242    if (retval == PAM_CONV_AGAIN) {
243	D(("user conversation is not available yet"));
244	memset(&pcs, 0, sizeof(pcs));
245	return PAM_INCOMPLETE;
246    }
247
248    if (retval != PAM_SUCCESS) {
249	D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
250	memset(&pcs, 0, sizeof(pcs));
251	return PAM_AUTH_ERR;
252    }
253
254    conf_icaps =
255	read_capabilities_for_user(pcs.user,
256				   pcs.conf_filename
257				   ? pcs.conf_filename:USER_CAP_FILE );
258
259    memset(&pcs, 0, sizeof(pcs));
260
261    if (conf_icaps) {
262	D(("it appears that there are capabilities for this user [%s]",
263	   conf_icaps));
264
265	/* We could also store this as a pam_[gs]et_data item for use
266	   by the setcred call to follow. As it is, there is a small
267	   race associated with a redundant read. Oh well, if you
268	   care, send me a patch.. */
269
270	_pam_overwrite(conf_icaps);
271	_pam_drop(conf_icaps);
272
273	return PAM_SUCCESS;
274
275    } else {
276
277	D(("there are no capabilities restrctions on this user"));
278	return PAM_IGNORE;
279
280    }
281}
282
283int pam_sm_setcred(pam_handle_t *pamh, int flags,
284		   int argc, const char **argv)
285{
286    int retval;
287    struct pam_cap_s pcs;
288
289    if (!(flags & PAM_ESTABLISH_CRED)) {
290	D(("we don't handle much in the way of credentials"));
291	return PAM_IGNORE;
292    }
293
294    memset(&pcs, 0, sizeof(pcs));
295
296    parse_args(argc, argv, &pcs);
297
298    retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
299    if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
300
301	D(("user's name is not set"));
302	return PAM_AUTH_ERR;
303    }
304
305    retval = set_capabilities(&pcs);
306
307    memset(&pcs, 0, sizeof(pcs));
308
309    return (retval ? PAM_SUCCESS:PAM_IGNORE );
310}
311