• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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