summaryrefslogtreecommitdiff
path: root/pam_cap/pam_cap.c
diff options
context:
space:
mode:
Diffstat (limited to 'pam_cap/pam_cap.c')
-rw-r--r--pam_cap/pam_cap.c310
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 );
+}