-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I've pushed it to a pamcap-enhancements branch and I'll will try to
review it quickly.

Thanks

Andrew

KaiGai Kohei wrote:
> 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 */
>  }
> 
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHWOSqmwytjiwfWMwRAnSIAJ0ea1HisHTLBfeApmdoHx+aSRbQ9wCbBC9C
I8mLshEVleoPG9OkJVUHTo0=
=WZO6
-----END PGP SIGNATURE-----
--
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