| /* |
| * Copyright (c) 1999,2007 Andrew G. Morgan <[email protected]> |
| * |
| * The purpose of this module is to enforce inheritable capability sets |
| * for a specified user. |
| */ |
| |
| /* #define DEBUG */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <syslog.h> |
| |
| #include <sys/capability.h> |
| |
| #include <security/pam_modules.h> |
| #include <security/_pam_macros.h> |
| |
| #define USER_CAP_FILE "/etc/security/capability.conf" |
| #define CAP_FILE_BUFFER_SIZE 4096 |
| #define CAP_FILE_DELIMITERS " \t\n" |
| #define CAP_COMBINED_FORMAT "%s all-i %s+i" |
| #define CAP_DROP_ALL "%s all-i" |
| |
| struct pam_cap_s { |
| int debug; |
| const char *user; |
| const char *conf_filename; |
| }; |
| |
| /* obtain the inheritable capabilities for the current user */ |
| |
| static char *read_capabilities_for_user(const char *user, const char *source) |
| { |
| char *cap_string = NULL; |
| char buffer[CAP_FILE_BUFFER_SIZE], *line; |
| FILE *cap_file; |
| |
| cap_file = fopen(source, "r"); |
| if (cap_file == NULL) { |
| D(("failed to open capability file")); |
| return NULL; |
| } |
| |
| while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) { |
| int found_one = 0; |
| const char *cap_text; |
| |
| cap_text = strtok(line, CAP_FILE_DELIMITERS); |
| |
| if (cap_text == NULL) { |
| D(("empty line")); |
| continue; |
| } |
| if (*cap_text == '#') { |
| D(("comment line")); |
| continue; |
| } |
| |
| while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) { |
| |
| if (strcmp("*", line) == 0) { |
| D(("wildcard matched")); |
| found_one = 1; |
| cap_string = strdup(cap_text); |
| break; |
| } |
| |
| if (strcmp(user, line) == 0) { |
| D(("exact match for user")); |
| found_one = 1; |
| cap_string = strdup(cap_text); |
| break; |
| } |
| |
| D(("user is not [%s] - skipping", line)); |
| } |
| |
| cap_text = NULL; |
| line = NULL; |
| |
| if (found_one) { |
| D(("user [%s] matched - caps are [%s]", user, cap_string)); |
| break; |
| } |
| } |
| |
| fclose(cap_file); |
| |
| memset(buffer, 0, CAP_FILE_BUFFER_SIZE); |
| |
| return cap_string; |
| } |
| |
| /* |
| * Set capabilities for current process to match the current |
| * permitted+executable sets combined with the configured inheritable |
| * set. |
| */ |
| |
| static int set_capabilities(struct pam_cap_s *cs) |
| { |
| cap_t cap_s; |
| ssize_t length = 0; |
| char *conf_icaps; |
| char *proc_epcaps; |
| char *combined_caps; |
| int ok = 0; |
| |
| cap_s = cap_get_proc(); |
| if (cap_s == NULL) { |
| D(("your kernel is capability challenged - upgrade: %s", |
| strerror(errno))); |
| return 0; |
| } |
| |
| conf_icaps = |
| read_capabilities_for_user(cs->user, |
| cs->conf_filename |
| ? cs->conf_filename:USER_CAP_FILE ); |
| if (conf_icaps == NULL) { |
| D(("no capabilities found for user [%s]", cs->user)); |
| goto cleanup_cap_s; |
| } |
| |
| proc_epcaps = cap_to_text(cap_s, &length); |
| if (proc_epcaps == NULL) { |
| D(("unable to convert process capabilities to text")); |
| goto cleanup_icaps; |
| } |
| |
| /* |
| * This is a pretty inefficient way to combine |
| * capabilities. However, it seems to be the most straightforward |
| * one, given the limitations of the POSIX.1e draft spec. The spec |
| * is optimized for applications that know the capabilities they |
| * want to manipulate at compile time. |
| */ |
| |
| combined_caps = malloc(1+strlen(CAP_COMBINED_FORMAT) |
| +strlen(proc_epcaps)+strlen(conf_icaps)); |
| if (combined_caps == NULL) { |
| D(("unable to combine capabilities into one string - no memory")); |
| goto cleanup_epcaps; |
| } |
| |
| if (!strcmp(conf_icaps, "none")) { |
| sprintf(combined_caps, CAP_DROP_ALL, proc_epcaps); |
| } else if (!strcmp(conf_icaps, "all")) { |
| /* no change */ |
| sprintf(combined_caps, "%s", proc_epcaps); |
| } else { |
| sprintf(combined_caps, CAP_COMBINED_FORMAT, proc_epcaps, conf_icaps); |
| } |
| D(("combined_caps=[%s]", combined_caps)); |
| |
| cap_free(cap_s); |
| cap_s = cap_from_text(combined_caps); |
| _pam_overwrite(combined_caps); |
| _pam_drop(combined_caps); |
| |
| #ifdef DEBUG |
| { |
| char *temp = cap_to_text(cap_s, NULL); |
| D(("abbreviated caps for process will be [%s]", temp)); |
| cap_free(temp); |
| } |
| #endif /* DEBUG */ |
| |
| if (cap_s == NULL) { |
| D(("no capabilies to set")); |
| } else if (cap_set_proc(cap_s) == 0) { |
| D(("capabilities were set correctly")); |
| ok = 1; |
| } else { |
| D(("failed to set specified capabilities: %s", strerror(errno))); |
| } |
| |
| cleanup_epcaps: |
| cap_free(proc_epcaps); |
| |
| cleanup_icaps: |
| _pam_overwrite(conf_icaps); |
| _pam_drop(conf_icaps); |
| |
| cleanup_cap_s: |
| if (cap_s) { |
| cap_free(cap_s); |
| cap_s = NULL; |
| } |
| |
| return ok; |
| } |
| |
| /* log errors */ |
| |
| static void _pam_log(int err, const char *format, ...) |
| { |
| va_list args; |
| |
| va_start(args, format); |
| openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH); |
| vsyslog(err, format, args); |
| va_end(args); |
| closelog(); |
| } |
| |
| static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs) |
| { |
| int ctrl=0; |
| |
| /* step through arguments */ |
| for (ctrl=0; argc-- > 0; ++argv) { |
| |
| if (!strcmp(*argv, "debug")) { |
| pcs->debug = 1; |
| } else if (!memcmp(*argv, "config=", 7)) { |
| pcs->conf_filename = 7 + *argv; |
| } else { |
| _pam_log(LOG_ERR, "unknown option; %s", *argv); |
| } |
| |
| } |
| } |
| |
| int pam_sm_authenticate(pam_handle_t *pamh, int flags, |
| int argc, const char **argv) |
| { |
| int retval; |
| struct pam_cap_s pcs; |
| char *conf_icaps; |
| |
| memset(&pcs, 0, sizeof(pcs)); |
| |
| parse_args(argc, argv, &pcs); |
| |
| retval = pam_get_user(pamh, &pcs.user, NULL); |
| |
| if (retval == PAM_CONV_AGAIN) { |
| D(("user conversation is not available yet")); |
| memset(&pcs, 0, sizeof(pcs)); |
| return PAM_INCOMPLETE; |
| } |
| |
| if (retval != PAM_SUCCESS) { |
| D(("pam_get_user failed: %s", pam_strerror(pamh, retval))); |
| memset(&pcs, 0, sizeof(pcs)); |
| return PAM_AUTH_ERR; |
| } |
| |
| conf_icaps = |
| read_capabilities_for_user(pcs.user, |
| pcs.conf_filename |
| ? pcs.conf_filename:USER_CAP_FILE ); |
| |
| memset(&pcs, 0, sizeof(pcs)); |
| |
| if (conf_icaps) { |
| D(("it appears that there are capabilities for this user [%s]", |
| conf_icaps)); |
| |
| /* We could also store this as a pam_[gs]et_data item for use |
| by the setcred call to follow. As it is, there is a small |
| race associated with a redundant read. Oh well, if you |
| care, send me a patch.. */ |
| |
| _pam_overwrite(conf_icaps); |
| _pam_drop(conf_icaps); |
| |
| return PAM_SUCCESS; |
| |
| } else { |
| |
| D(("there are no capabilities restrctions on this user")); |
| return PAM_IGNORE; |
| |
| } |
| } |
| |
| int pam_sm_setcred(pam_handle_t *pamh, int flags, |
| int argc, const char **argv) |
| { |
| int retval; |
| struct pam_cap_s pcs; |
| |
| if (!(flags & PAM_ESTABLISH_CRED)) { |
| D(("we don't handle much in the way of credentials")); |
| return PAM_IGNORE; |
| } |
| |
| memset(&pcs, 0, sizeof(pcs)); |
| |
| parse_args(argc, argv, &pcs); |
| |
| retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user); |
| if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) { |
| |
| D(("user's name is not set")); |
| return PAM_AUTH_ERR; |
| } |
| |
| retval = set_capabilities(&pcs); |
| |
| memset(&pcs, 0, sizeof(pcs)); |
| |
| return (retval ? PAM_SUCCESS:PAM_IGNORE ); |
| } |