1 /* 2 * Copyright (c) 1999,2007 Andrew G. Morgan <morgan (at) 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 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 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 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 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 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 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