Sorry, any TABs are replaced by MUA.
I'll send the patch again.

> The attached patch provides several improvement for pam_cap module.
> 1. It enables pam_cap to drop capabilities from process'es capability
>    bounding set.
> 2. It enables to specify allowing inheritable capability set or dropping
>    bounding capability set for groups, not only users.
> 3. It provide pam_sm_session() method, not only pam_sm_authenticate()
>    and pam_sm_setcred(). A system administrator can select more
>    appropriate mode for his purpose.
> 4. In the auth/cred mode, it enables to cache the configuration file,
>    to avoid read and analyze it twice.
> (Therefore, most of the part in the original one got replaced....)
> 
> The default configuration file is "/etc/security/capability.conf".
> You can describe as follows:
> --------
> # kaigai get cap_net_raw and cap_kill, tak get cap_sys_pacct pI.
> # We can omit "i:" in the head of each line.
> i:cap_net_raw,cap_kill         kaigai
> cap_sys_pacct                  tak
> 
> # ymj and tak lost cap_sys_chroot from cap_bset
> b:cap_sys_chroot               ymj  tak
> 
> # Any user within webadm group get cap_net_bind_service pI.
> i:cap_net_bind_service         @webadm
> 
> # Any user within users group lost cap_sys_module from cap_bset
> b:cap_sys_module               @users
> --------
> 
> When a user or groups he belongs is on several lines, all configurations
> are simplly compounded.
> 
> In the above example, if tak belongs to webadm and users group,
> he will get cap_sys_pacct and cap_net_bind_service pI, and lost
> cap_sys_chroot and cap_sys_module from his cap_bset.
> 
> Thanks,

Signed-off-by: KaiGai Kohei <[EMAIL PROTECTED]>
--
 pam_cap/capability.conf |    6 +
 pam_cap/pam_cap.c       |  495 ++++++++++++++++++++++++++++-------------------
 2 files changed, 305 insertions(+), 196 deletions(-)

diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
index b543142..707cdc3 100644
--- a/pam_cap/capability.conf
+++ b/pam_cap/capability.conf
@@ -24,6 +24,12 @@ cap_setfcap          morgan
 ## 'everyone else' gets no inheritable capabilities
 none  *

+# user 'kaigai' lost CAP_NET_RAW capability from bounding set
+b:cap_net_raw                   kaigai
+
+# group 'acctadm' get CAP_SYS_PACCT inheritable capability
+i:cap_sys_pacct                 @acctadm
+
 ## if there is no '*' entry, all users not explicitly mentioned will
 ## get all available capabilities. This is a permissive default, and
 ## probably not what you want...
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 94c5ebc..a917d5c 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 1999,2007 Andrew G. Morgan <[EMAIL PROTECTED]>
+ * Copyright (c) 2007      KaiGai Kohei <[EMAIL PROTECTED]>
  *
  * The purpose of this module is to enforce inheritable capability sets
  * for a specified user.
@@ -13,298 +14,400 @@
 #include <stdarg.h>
 #include <stdlib.h>
 #include <syslog.h>
+#include <pwd.h>
+#include <grp.h>

 #include <sys/capability.h>
+#include <sys/prctl.h>

 #include <security/pam_modules.h>
 #include <security/_pam_macros.h>

+#define MODULE_NAME            "pam_cap"
 #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"
+
+#ifndef PR_CAPBSET_DROP
+#define PR_CAPBSET_DROP                24
+#endif
+
+extern char const *_cap_names[];

 struct pam_cap_s {
     int debug;
     const char *user;
     const char *conf_filename;
+    /* set in read_capabilities_for_user() */
+    cap_t result;
+    int do_set_inh : 1;
+    int do_set_bset : 1;
 };

-/* obtain the inheritable capabilities for the current user */
-
-static char *read_capabilities_for_user(const char *user, const char *source)
+/* obtain the inheritable/bounding capabilities for the current user */
+static int read_capabilities_for_user(struct pam_cap_s *pcs)
 {
-    char *cap_string = NULL;
-    char buffer[CAP_FILE_BUFFER_SIZE], *line;
+    char buffer[CAP_FILE_BUFFER_SIZE];
     FILE *cap_file;
+    struct passwd *pwd;
+    int line_num = 0;
+    int rc = -1;       /* PAM_(AUTH|CRED|SESSION)_ERR */
+
+    pwd = getpwnam(pcs->user);
+    if (!pwd) {
+       syslog(LOG_ERR, "user %s not in passwd entries", pcs->user);
+       return PAM_AUTH_ERR;
+    }

-    cap_file = fopen(source, "r");
-    if (cap_file == NULL) {
-       D(("failed to open capability file"));
-       return NULL;
+    cap_file = fopen(pcs->conf_filename, "r");
+    if (!cap_file) {
+       if (errno == ENOENT) {
+           syslog(LOG_NOTICE, "%s is not found",
+                  pcs->conf_filename);
+           return PAM_IGNORE;
+       } else {
+           syslog(LOG_ERR, "unable to open '%s' (%s)",
+                  pcs->conf_filename, strerror(errno));
+           return rc;
+       }
     }

-    while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
-       int found_one = 0;
-       const char *cap_text;
+    pcs->result = NULL;
+    while (fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file) != NULL) {
+       char *pos, *cap_text;
+       int matched = 0;
+       int line_ops = CAP_INHERITABLE;

-       cap_text = strtok(line, CAP_FILE_DELIMITERS);
+       line_num++;

-       if (cap_text == NULL) {
-           D(("empty line"));
-           continue;
-       }
-       if (*cap_text == '#') {
-           D(("comment line"));
+       /* remove comment */
+       pos = strchr(buffer, '#');
+       if (pos)
+           *pos = '\0';
+
+       cap_text = strtok(buffer, CAP_FILE_DELIMITERS);
+       /* empty line */
+       if (!cap_text)
            continue;
+
+       if (!strncmp(cap_text, "b:", 2)) {
+           /* permitted field is used to store bounding set */
+           line_ops = CAP_PERMITTED;
+           cap_text += 2;
+       } else if (!strncmp(cap_text, "i:", 2)) {
+           cap_text += 2;
        }

-       while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) {
+       /* check members */
+       while ((pos = strtok(NULL, CAP_FILE_DELIMITERS)) != NULL) {
+           /* wildcard */
+           if (!strcmp("*", pos)) {
+               matched = 1;
+               break;
+           }

-           if (strcmp("*", line) == 0) {
-               D(("wildcard matched"));
-               found_one = 1;
-               cap_string = strdup(cap_text);
+           /* It it group name? */
+           if (*pos == '@') {
+               struct group *grp;
+               int i;
+
+               pos++;
+               grp = getgrnam(pos);
+               if (!grp) {
+                   if (pcs->debug)
+                       syslog(LOG_DEBUG, "group '%s' not found at line:%d",
+                              pos, line_num);
+                   continue;
+               }
+
+               if (pwd->pw_gid == grp->gr_gid) {
+                   if (pcs->debug)
+                       syslog(LOG_DEBUG, "user %s matched with group %s at 
line:%d",
+                              pcs->user, pos, line_num);
+                   matched = 1;
+                   break;
+               }
+
+               for (i=0; grp->gr_mem[i]; i++) {
+                   if (!strcmp(pcs->user, grp->gr_mem[i])) {
+                       if (pcs->debug)
+                           syslog(LOG_DEBUG, "user %s matched with group %s at 
line:%d",
+                                  pcs->user, pos, line_num);
+                       matched = 1;
+                       break;
+                   }
+               }
+               syslog(LOG_ERR, "no matching %s", pos);
+           } else if (!strcmp(pcs->user, pos)) {
+               if (pcs->debug)
+                   syslog(LOG_DEBUG, "user '%s' matched at line:%d",
+                          pos, line_num);
+               matched = 1;
                break;
            }
+       }
+
+       if (matched) {
+           char tmpbuf[CAP_FILE_BUFFER_SIZE];
+           cap_t tmp;
+           cap_value_t value;
+           cap_flag_value_t code;
+
+           if (!pcs->result) {
+               pcs->result = cap_init();
+               if (!pcs->result) {
+                   syslog(LOG_ERR, "unable to allocate cap_t object (%s)",
+                          strerror(errno));
+                   goto out;
+               }
+           }

-           if (strcmp(user, line) == 0) {
-               D(("exact match for user"));
-               found_one = 1;
-               cap_string = strdup(cap_text);
+           switch (line_ops) {
+           case CAP_INHERITABLE:
+               pcs->do_set_inh = 1;
+               break;
+           case CAP_PERMITTED:
+               pcs->do_set_bset = 1;
                break;
            }

-           D(("user is not [%s] - skipping", line));
+           if (!strcmp(cap_text, "none"))
+               continue;
+
+           snprintf(tmpbuf, sizeof(tmpbuf), "%s=p", cap_text);
+           tmp = cap_from_text(tmpbuf);
+           if (!tmp) {
+               syslog(LOG_ERR, "unable to convert '%s' (%s)",
+                      tmpbuf, strerror(errno));
+               cap_free(pcs->result);
+               pcs->result = NULL;
+               goto out;
+           }
+
+           for (value=0; ;value++) {
+               if (cap_get_flag(tmp, value, CAP_PERMITTED, &code) < 0)
+                   break;      /* If value == __CAP_BITS, we get EINVAL */
+               if (code == CAP_SET)
+                   cap_set_flag(pcs->result, line_ops, 1, &value, CAP_SET);
+           }
+           cap_free(tmp);
        }
+    }

-       cap_text = NULL;
-       line = NULL;
+    if (pcs->debug) {
+       char *tmp = cap_to_text(pcs->result, NULL);

-       if (found_one) {
-           D(("user [%s] matched - caps are [%s]", user, cap_string));
-           break;
-       }
+       syslog(LOG_DEBUG, "configuration for user %s is %s",
+              pcs->user, tmp);
+       cap_free(tmp);
     }
+    rc = PAM_SUCCESS;

+  out:
     fclose(cap_file);

-    memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
-
-    return cap_string;
+    return rc;
 }

 /*
  * Set capabilities for current process to match the current
  * permitted+executable sets combined with the configured inheritable
- * set.
+ * and bounding set.
  */

-static int set_capabilities(struct pam_cap_s *cs)
+static int set_capabilities(struct pam_cap_s *pcs)
 {
-    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);
+    cap_value_t value;
+    cap_flag_value_t code;
+    int rc = -1;       /* PAM_(AUTH|CRED|SESSION)_ERR */
+
+    /* set inheritable capability set */
+    if (pcs->do_set_inh) {
+       cap_t cap_s = cap_get_proc();
+       if (!cap_s) {
+           syslog(LOG_ERR, "your kernel is capability challenged - upgrade: 
%s",
+                  strerror(errno));
+           goto out;
+       }
+       for (value=0; ;value++) {
+           if (cap_get_flag(pcs->result, value, CAP_INHERITABLE, &code))
+               break;
+           cap_set_flag(cap_s, CAP_INHERITABLE, 1, &value, code);
+       }
+       if (cap_set_proc(cap_s) < 0) {
+           if (errno == EPERM)
+               rc = PAM_PERM_DENIED;
+           syslog(LOG_ERR, "unable to set inheritable capabilities (%s)",
+                  strerror(errno));
+           cap_free(cap_s);
+           goto out;
+       }
+       if (pcs->debug) {
+           char *tmp = cap_to_text(cap_s, NULL);

-cleanup_cap_s:
-    if (cap_s) {
+           syslog(LOG_DEBUG, "user %s new capabilities: %s",
+                  pcs->user, tmp);
+           cap_free(tmp);
+       }
        cap_free(cap_s);
-       cap_s = NULL;
     }
+    /* drop capability bounding set */
+    if (pcs->do_set_bset) {
+       for (value=0; ;value++) {
+           if (cap_get_flag(pcs->result, value, CAP_PERMITTED, &code))
+               break;
+           if (code == CAP_SET) {
+               if (prctl(PR_CAPBSET_DROP, value) < 0) {
+                   syslog(LOG_ERR, "unable to drop capability b-set %u (%s)",
+                          value, strerror(errno));
+                   goto out;
+               }
+               if (pcs->debug)
+                   syslog(LOG_DEBUG, "%s drops capability %s from bounding 
set",
+                          pcs->user, _cap_names[value]);
+           }
+       }
+    }
+    rc = PAM_SUCCESS;

-    return ok;
-}
-
-/* log errors */
-
-static void _pam_log(int err, const char *format, ...)
-{
-    va_list args;
+  out:
+    cap_free(pcs->result);

-    va_start(args, format);
-    openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH);
-    vsyslog(err, format, args);
-    va_end(args);
-    closelog();
+    return rc;
 }

-static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
+static int init_pam_cap(pam_handle_t *pamh, int argc, const char **argv,
+                       struct pam_cap_s *pcs)
 {
-    int ctrl=0;
+    int ctrl, rc;
+
+    /* Initialization */
+    memset(pcs, 0, sizeof(struct pam_cap_s));
+    pcs->conf_filename = USER_CAP_FILE;
+    rc = pam_get_user(pamh, &pcs->user, NULL);
+    if (rc == PAM_CONV_AGAIN) {
+       syslog(LOG_INFO, "user conversation is not available yet");
+       return PAM_INCOMPLETE;
+    }
+    if (rc != PAM_SUCCESS) {
+       syslog(LOG_INFO, "pam_get_user failed: %s", pam_strerror(pamh, rc));
+       return -1;
+    }

     /* step through arguments */
     for (ctrl=0; argc-- > 0; ++argv) {
-
        if (!strcmp(*argv, "debug")) {
            pcs->debug = 1;
        } else if (!strcmp(*argv, "config=")) {
            pcs->conf_filename = strlen("config=") + *argv;
        } else {
-           _pam_log(LOG_ERR, "unknown option; %s", *argv);
+           syslog(LOG_ERR, "unknown option: %s", *argv);
+           return -1;
        }
+    }
+    return PAM_SUCCESS;
+}

+static void cleanup_pam_cap(pam_handle_t *pamh, void *data, int error_status)
+{
+    struct pam_cap_s *pcs = (struct pam_cap_s *) data;
+
+    if (pcs) {
+       if (pcs->result)
+           cap_free(pcs->result);
+       free(pcs);
     }
 }

 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;
+    struct pam_cap_s *pcs = NULL;
+    int rc = PAM_BUF_ERR;

-    memset(&pcs, 0, sizeof(pcs));
+    openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

-    parse_args(argc, argv, &pcs);
+    pcs = malloc(sizeof(struct pam_cap_s));
+    if (!pcs)
+       goto error;

-    retval = pam_get_user(pamh, &pcs.user, NULL);
+    rc = init_pam_cap(pamh, argc, argv, pcs);
+    if (rc != PAM_SUCCESS)
+       goto error;

-    if (retval == PAM_CONV_AGAIN) {
-       D(("user conversation is not available yet"));
-       memset(&pcs, 0, sizeof(pcs));
-       return PAM_INCOMPLETE;
-    }
+    rc = read_capabilities_for_user(pcs);
+    if (rc != PAM_SUCCESS)
+       goto error;

-    if (retval != PAM_SUCCESS) {
-       D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
-       memset(&pcs, 0, sizeof(pcs));
-       return PAM_AUTH_ERR;
+    rc = pam_set_data(pamh, MODULE_NAME, pcs, cleanup_pam_cap);
+    if (rc == PAM_SUCCESS) {
+       /* OK, pam_sm_setcred() will be called next */
+       closelog();
+       return rc;
     }

-    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));
+  error:
+    cleanup_pam_cap(pamh, pcs, rc);
+    closelog();
+    return rc < 0 ? PAM_AUTH_ERR : rc;
+}

-       /* 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.. */
+int pam_sm_setcred(pam_handle_t *pamh, int flags,
+                  int argc, const char **argv)
+{
+    struct pam_cap_s *pcs = NULL;
+    int rc = PAM_IGNORE;

-       _pam_overwrite(conf_icaps);
-       _pam_drop(conf_icaps);
+    openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

-       return PAM_SUCCESS;
+    if (!(flags & PAM_ESTABLISH_CRED))
+       goto out;

-    } else {
+    rc = pam_get_data(pamh, MODULE_NAME, (void *)&pcs);
+    if (rc != PAM_SUCCESS)
+       return rc;

-       D(("there are no capabilities restrctions on this user"));
-       return PAM_IGNORE;
+    rc = set_capabilities(pcs);

-    }
+  out:
+    closelog();
+    return rc < 0 ? PAM_CRED_ERR : rc;
 }

-int pam_sm_setcred(pam_handle_t *pamh, int flags,
-                  int argc, const char **argv)
+int pam_sm_open_session(pam_handle_t *pamh, int flags,
+                       int argc, const char **argv)
 {
-    int retval;
     struct pam_cap_s pcs;
+    int rc;

-    if (!(flags & PAM_ESTABLISH_CRED)) {
-       D(("we don't handle much in the way of credentials"));
-       return PAM_IGNORE;
-    }
+    openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

-    memset(&pcs, 0, sizeof(pcs));
+    rc = init_pam_cap(pamh, argc, argv, &pcs);
+    if (rc != PAM_SUCCESS)
+       goto out;

-    parse_args(argc, argv, &pcs);
+    rc = read_capabilities_for_user(&pcs);
+    if (rc != PAM_SUCCESS)
+       goto out;

-    retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
-    if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
+    rc = set_capabilities(&pcs);

-       D(("user's name is not set"));
-       return PAM_AUTH_ERR;
+    if (rc == PAM_SUCCESS) {
+       rc = set_capabilities(&pcs);
+       if (pcs.result)
+           cap_free(pcs.result);
     }

-    retval = set_capabilities(&pcs);
+  out:
+    if (pcs.result)
+       cap_free(pcs.result);
+    closelog();

-    memset(&pcs, 0, sizeof(pcs));
+    return rc < 0 ? PAM_SESSION_ERR : rc;
+}

-    return (retval ? PAM_SUCCESS:PAM_IGNORE );
+int pam_sm_close_session(pam_handle_t *pamh, int flags,
+                        int argc, const char **argv)
+{
+    return PAM_SUCCESS; /* do nothing */
 }

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to