diff options
Diffstat (limited to 'pam_cap/pam_cap.c')
-rw-r--r-- | pam_cap/pam_cap.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c new file mode 100644 index 0000000..e6ebbe9 --- /dev/null +++ b/pam_cap/pam_cap.c @@ -0,0 +1,310 @@ +/* + * Copyright (c) 1999,2007 Andrew G. Morgan <morgan@kernel.org> + * + * 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 ); +} |