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
28 struct 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
read_capabilities_for_user(const char * user,const char * source)36 static 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
set_capabilities(struct pam_cap_s * cs)104 static 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
182 cleanup_epcaps:
183 cap_free(proc_epcaps);
184
185 cleanup_icaps:
186 _pam_overwrite(conf_icaps);
187 _pam_drop(conf_icaps);
188
189 cleanup_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
_pam_log(int err,const char * format,...)200 static 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
parse_args(int argc,const char ** argv,struct pam_cap_s * pcs)211 static 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
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)229 int 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
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)283 int 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