Home | History | Annotate | Download | only in pam_cap
      1 /*
      2  * Copyright (c) 1999,2007 Andrew G. Morgan <morgan (at) kernel.org>
      3  *
      4  * The purpose of this module is to enforce inheritable capability sets
      5  * for a specified user.
      6  */
      7 
      8 /* #define DEBUG */
      9 
     10 #include <stdio.h>
     11 #include <string.h>
     12 #include <errno.h>
     13 #include <stdarg.h>
     14 #include <stdlib.h>
     15 #include <syslog.h>
     16 
     17 #include <sys/capability.h>
     18 
     19 #include <security/pam_modules.h>
     20 #include <security/_pam_macros.h>
     21 
     22 #define USER_CAP_FILE           "/etc/security/capability.conf"
     23 #define CAP_FILE_BUFFER_SIZE    4096
     24 #define CAP_FILE_DELIMITERS     " \t\n"
     25 #define CAP_COMBINED_FORMAT     "%s all-i %s+i"
     26 #define CAP_DROP_ALL            "%s all-i"
     27 
     28 struct pam_cap_s {
     29     int debug;
     30     const char *user;
     31     const char *conf_filename;
     32 };
     33 
     34 /* obtain the inheritable capabilities for the current user */
     35 
     36 static char *read_capabilities_for_user(const char *user, const char *source)
     37 {
     38     char *cap_string = NULL;
     39     char buffer[CAP_FILE_BUFFER_SIZE], *line;
     40     FILE *cap_file;
     41 
     42     cap_file = fopen(source, "r");
     43     if (cap_file == NULL) {
     44 	D(("failed to open capability file"));
     45 	return NULL;
     46     }
     47 
     48     while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
     49 	int found_one = 0;
     50 	const char *cap_text;
     51 
     52 	cap_text = strtok(line, CAP_FILE_DELIMITERS);
     53 
     54 	if (cap_text == NULL) {
     55 	    D(("empty line"));
     56 	    continue;
     57 	}
     58 	if (*cap_text == '#') {
     59 	    D(("comment line"));
     60 	    continue;
     61 	}
     62 
     63 	while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) {
     64 
     65 	    if (strcmp("*", line) == 0) {
     66 		D(("wildcard matched"));
     67 		found_one = 1;
     68 		cap_string = strdup(cap_text);
     69 		break;
     70 	    }
     71 
     72 	    if (strcmp(user, line) == 0) {
     73 		D(("exact match for user"));
     74 		found_one = 1;
     75 		cap_string = strdup(cap_text);
     76 		break;
     77 	    }
     78 
     79 	    D(("user is not [%s] - skipping", line));
     80 	}
     81 
     82 	cap_text = NULL;
     83 	line = NULL;
     84 
     85 	if (found_one) {
     86 	    D(("user [%s] matched - caps are [%s]", user, cap_string));
     87 	    break;
     88 	}
     89     }
     90 
     91     fclose(cap_file);
     92 
     93     memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
     94 
     95     return cap_string;
     96 }
     97 
     98 /*
     99  * Set capabilities for current process to match the current
    100  * permitted+executable sets combined with the configured inheritable
    101  * set.
    102  */
    103 
    104 static int set_capabilities(struct pam_cap_s *cs)
    105 {
    106     cap_t cap_s;
    107     ssize_t length = 0;
    108     char *conf_icaps;
    109     char *proc_epcaps;
    110     char *combined_caps;
    111     int ok = 0;
    112 
    113     cap_s = cap_get_proc();
    114     if (cap_s == NULL) {
    115 	D(("your kernel is capability challenged - upgrade: %s",
    116 	   strerror(errno)));
    117 	return 0;
    118     }
    119 
    120     conf_icaps =
    121 	read_capabilities_for_user(cs->user,
    122 				   cs->conf_filename
    123 				   ? cs->conf_filename:USER_CAP_FILE );
    124     if (conf_icaps == NULL) {
    125 	D(("no capabilities found for user [%s]", cs->user));
    126 	goto cleanup_cap_s;
    127     }
    128 
    129     proc_epcaps = cap_to_text(cap_s, &length);
    130     if (proc_epcaps == NULL) {
    131 	D(("unable to convert process capabilities to text"));
    132 	goto cleanup_icaps;
    133     }
    134 
    135     /*
    136      * This is a pretty inefficient way to combine
    137      * capabilities. However, it seems to be the most straightforward
    138      * one, given the limitations of the POSIX.1e draft spec. The spec
    139      * is optimized for applications that know the capabilities they
    140      * want to manipulate at compile time.
    141      */
    142 
    143     combined_caps = malloc(1+strlen(CAP_COMBINED_FORMAT)
    144 			   +strlen(proc_epcaps)+strlen(conf_icaps));
    145     if (combined_caps == NULL) {
    146 	D(("unable to combine capabilities into one string - no memory"));
    147 	goto cleanup_epcaps;
    148     }
    149 
    150     if (!strcmp(conf_icaps, "none")) {
    151 	sprintf(combined_caps, CAP_DROP_ALL, proc_epcaps);
    152     } else if (!strcmp(conf_icaps, "all")) {
    153 	/* no change */
    154 	sprintf(combined_caps, "%s", proc_epcaps);
    155     } else {
    156 	sprintf(combined_caps, CAP_COMBINED_FORMAT, proc_epcaps, conf_icaps);
    157     }
    158     D(("combined_caps=[%s]", combined_caps));
    159 
    160     cap_free(cap_s);
    161     cap_s = cap_from_text(combined_caps);
    162     _pam_overwrite(combined_caps);
    163     _pam_drop(combined_caps);
    164 
    165 #ifdef DEBUG
    166     {
    167         char *temp = cap_to_text(cap_s, NULL);
    168 	D(("abbreviated caps for process will be [%s]", temp));
    169 	cap_free(temp);
    170     }
    171 #endif /* DEBUG */
    172 
    173     if (cap_s == NULL) {
    174 	D(("no capabilies to set"));
    175     } else if (cap_set_proc(cap_s) == 0) {
    176 	D(("capabilities were set correctly"));
    177 	ok = 1;
    178     } else {
    179 	D(("failed to set specified capabilities: %s", strerror(errno)));
    180     }
    181 
    182 cleanup_epcaps:
    183     cap_free(proc_epcaps);
    184 
    185 cleanup_icaps:
    186     _pam_overwrite(conf_icaps);
    187     _pam_drop(conf_icaps);
    188 
    189 cleanup_cap_s:
    190     if (cap_s) {
    191 	cap_free(cap_s);
    192 	cap_s = NULL;
    193     }
    194 
    195     return ok;
    196 }
    197 
    198 /* log errors */
    199 
    200 static void _pam_log(int err, const char *format, ...)
    201 {
    202     va_list args;
    203 
    204     va_start(args, format);
    205     openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH);
    206     vsyslog(err, format, args);
    207     va_end(args);
    208     closelog();
    209 }
    210 
    211 static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
    212 {
    213     int ctrl=0;
    214 
    215     /* step through arguments */
    216     for (ctrl=0; argc-- > 0; ++argv) {
    217 
    218 	if (!strcmp(*argv, "debug")) {
    219 	    pcs->debug = 1;
    220 	} else if (!memcmp(*argv, "config=", 7)) {
    221 	    pcs->conf_filename = 7 + *argv;
    222 	} else {
    223 	    _pam_log(LOG_ERR, "unknown option; %s", *argv);
    224 	}
    225 
    226     }
    227 }
    228 
    229 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
    230 			int argc, const char **argv)
    231 {
    232     int retval;
    233     struct pam_cap_s pcs;
    234     char *conf_icaps;
    235 
    236     memset(&pcs, 0, sizeof(pcs));
    237 
    238     parse_args(argc, argv, &pcs);
    239 
    240     retval = pam_get_user(pamh, &pcs.user, NULL);
    241 
    242     if (retval == PAM_CONV_AGAIN) {
    243 	D(("user conversation is not available yet"));
    244 	memset(&pcs, 0, sizeof(pcs));
    245 	return PAM_INCOMPLETE;
    246     }
    247 
    248     if (retval != PAM_SUCCESS) {
    249 	D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
    250 	memset(&pcs, 0, sizeof(pcs));
    251 	return PAM_AUTH_ERR;
    252     }
    253 
    254     conf_icaps =
    255 	read_capabilities_for_user(pcs.user,
    256 				   pcs.conf_filename
    257 				   ? pcs.conf_filename:USER_CAP_FILE );
    258 
    259     memset(&pcs, 0, sizeof(pcs));
    260 
    261     if (conf_icaps) {
    262 	D(("it appears that there are capabilities for this user [%s]",
    263 	   conf_icaps));
    264 
    265 	/* We could also store this as a pam_[gs]et_data item for use
    266 	   by the setcred call to follow. As it is, there is a small
    267 	   race associated with a redundant read. Oh well, if you
    268 	   care, send me a patch.. */
    269 
    270 	_pam_overwrite(conf_icaps);
    271 	_pam_drop(conf_icaps);
    272 
    273 	return PAM_SUCCESS;
    274 
    275     } else {
    276 
    277 	D(("there are no capabilities restrctions on this user"));
    278 	return PAM_IGNORE;
    279 
    280     }
    281 }
    282 
    283 int pam_sm_setcred(pam_handle_t *pamh, int flags,
    284 		   int argc, const char **argv)
    285 {
    286     int retval;
    287     struct pam_cap_s pcs;
    288 
    289     if (!(flags & PAM_ESTABLISH_CRED)) {
    290 	D(("we don't handle much in the way of credentials"));
    291 	return PAM_IGNORE;
    292     }
    293 
    294     memset(&pcs, 0, sizeof(pcs));
    295 
    296     parse_args(argc, argv, &pcs);
    297 
    298     retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
    299     if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
    300 
    301 	D(("user's name is not set"));
    302 	return PAM_AUTH_ERR;
    303     }
    304 
    305     retval = set_capabilities(&pcs);
    306 
    307     memset(&pcs, 0, sizeof(pcs));
    308 
    309     return (retval ? PAM_SUCCESS:PAM_IGNORE );
    310 }
    311