| /* request-key.c: hand a key request off to the appropriate process |
| * |
| * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. |
| * Written by David Howells (dhowells@redhat.com) |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version |
| * 2 of the License, or (at your option) any later version. |
| * |
| * /sbin/request-key <op> <key> <uid> <gid> <threadring> <processring> <sessionring> <info> |
| * |
| * Searches the specified session ring for a key indicating the command to run: |
| * type: "user" |
| * desc: "request-key:<op>" |
| * data: command name, eg: "/home/dhowells/request-key-create.sh" |
| */ |
| |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <ctype.h> |
| #include "keyutil.h" |
| |
| |
| static int xdebug; |
| static int xnolog; |
| static char *xkey; |
| static char *xuid; |
| static char *xgid; |
| static char *xthread_keyring; |
| static char *xprocess_keyring; |
| static char *xsession_keyring; |
| static int confline; |
| |
| static void lookup_action(char *op, |
| key_serial_t key, |
| char *ktype, |
| char *kdesc, |
| char *callout_info) |
| __attribute__((noreturn)); |
| |
| static void execute_program(char *op, |
| char *ktype, |
| char *kdesc, |
| char *callout_info, |
| char *cmdline) |
| __attribute__((noreturn)); |
| |
| static int match(const char *pattern, int plen, const char *datum, int dlen); |
| |
| static void debug(const char *fmt, ...) __attribute__((format(printf, 1, 2))); |
| static void debug(const char *fmt, ...) |
| { |
| va_list va; |
| |
| if (xdebug) { |
| va_start(va, fmt); |
| vfprintf(stderr, fmt, va); |
| va_end(va); |
| |
| if (!xnolog) { |
| openlog("request-key", 0, LOG_AUTHPRIV); |
| |
| va_start(va, fmt); |
| vsyslog(LOG_DEBUG, fmt, va); |
| va_end(va); |
| |
| closelog(); |
| } |
| } |
| } |
| |
| static void error(const char *fmt, ...) __attribute__((noreturn, format(printf, 1, 2))); |
| static void error(const char *fmt, ...) |
| { |
| va_list va; |
| |
| if (xdebug) { |
| va_start(va, fmt); |
| vfprintf(stderr, fmt, va); |
| va_end(va); |
| } |
| |
| if (!xnolog) { |
| openlog("request-key", 0, LOG_AUTHPRIV); |
| |
| va_start(va, fmt); |
| vsyslog(LOG_ERR, fmt, va); |
| va_end(va); |
| |
| closelog(); |
| } |
| |
| exit(1); |
| } |
| |
| /*****************************************************************************/ |
| /* |
| * |
| */ |
| int main(int argc, char *argv[]) |
| { |
| key_serial_t key; |
| char *ktype, *kdesc, *buf; |
| int ret, ntype, dpos, dlen; |
| |
| for (;;) { |
| if (argc > 1 && strcmp(argv[1], "-d") == 0) { |
| xdebug++; |
| argv++; |
| argc--; |
| } |
| else if (argc > 1 && strcmp(argv[1], "-n") == 0) { |
| xnolog = 1; |
| argv++; |
| argc--; |
| } |
| else |
| break; |
| } |
| |
| if (argc != 9) |
| error("Unexpected argument count: %d\n", argc); |
| |
| xkey = argv[2]; |
| xuid = argv[3]; |
| xgid = argv[4]; |
| xthread_keyring = argv[5]; |
| xprocess_keyring = argv[6]; |
| xsession_keyring = argv[7]; |
| |
| key = atoi(xkey); |
| |
| /* ask the kernel to describe the key to us */ |
| if (xdebug <= 0) { |
| ret = keyctl_describe_alloc(key, &buf); |
| if (ret < 0) |
| goto inaccessible; |
| } |
| else { |
| buf = strdup("user;0;0;1f0000;debug:1234"); |
| } |
| |
| /* extract the type and description from the key */ |
| debug("Key descriptor: \"%s\"\n", buf); |
| ntype = -1; |
| dpos = -1; |
| dlen = -1; |
| |
| sscanf(buf, "%*[^;]%n;%*d;%*d;%*x;%n%*[^;]%n", &ntype, &dpos, &dlen); |
| if (dlen == -1) |
| error("Failed to parse key description\n"); |
| |
| ktype = buf; |
| ktype[ntype] = 0; |
| kdesc = buf + dpos; |
| |
| debug("Key type: %s\n", ktype); |
| debug("Key desc: %s\n", kdesc); |
| |
| /* determine the action to perform */ |
| lookup_action(argv[1], /* op */ |
| key, /* ID of key under construction */ |
| ktype, /* key type */ |
| kdesc, /* key description */ |
| argv[8] /* call out info */ |
| ); |
| |
| inaccessible: |
| error("Key %d is inaccessible (%m)\n", key); |
| |
| } /* end main() */ |
| |
| /*****************************************************************************/ |
| /* |
| * determine the action to perform |
| */ |
| static void lookup_action(char *op, |
| key_serial_t key, |
| char *ktype, |
| char *kdesc, |
| char *callout_info) |
| { |
| char buf[4096 + 2], *p, *q; |
| FILE *conf; |
| int len, oplen, ktlen, kdlen, cilen; |
| |
| oplen = strlen(op); |
| ktlen = strlen(ktype); |
| kdlen = strlen(kdesc); |
| cilen = strlen(callout_info); |
| |
| /* search the config file for a command to run */ |
| conf = fopen(xdebug < 2 ? "/etc/request-key.conf" : "request-key.conf", "r"); |
| if (!conf) |
| error("Cannot open /etc/request-key.conf: %m\n"); |
| |
| for (confline = 1;; confline++) { |
| /* read the file line-by-line */ |
| if (!fgets(buf, sizeof(buf), conf)) { |
| if (feof(conf)) |
| error("Cannot find command to construct key %d\n", key); |
| error("Error reading /etc/request-key.conf\n"); |
| } |
| |
| len = strlen(buf); |
| if (len >= sizeof(buf) - 2) |
| error("/etc/request-key.conf:%d: Line too long\n", confline); |
| |
| /* ignore blank lines and comments */ |
| if (len == 1 || buf[0] == '#' || isspace(buf[0])) |
| continue; |
| |
| buf[--len] = 0; |
| p = buf; |
| |
| /* attempt to match the op */ |
| q = p; |
| while (*p && !isspace(*p)) p++; |
| if (!*p) |
| goto syntax_error; |
| *p = 0; |
| |
| if (!match(q, p - q, op, oplen)) |
| continue; |
| |
| p++; |
| |
| /* attempt to match the type */ |
| while (isspace(*p)) p++; |
| if (!*p) |
| goto syntax_error; |
| |
| q = p; |
| while (*p && !isspace(*p)) p++; |
| if (!*p) |
| goto syntax_error; |
| *p = 0; |
| |
| if (!match(q, p - q, ktype, ktlen)) |
| continue; |
| |
| p++; |
| |
| /* attempt to match the description */ |
| while (isspace(*p)) p++; |
| if (!*p) |
| goto syntax_error; |
| |
| q = p; |
| while (*p && !isspace(*p)) p++; |
| if (!*p) |
| goto syntax_error; |
| *p = 0; |
| |
| if (!match(q, p - q, kdesc, kdlen)) |
| continue; |
| |
| p++; |
| |
| /* attempt to match the callout info */ |
| while (isspace(*p)) p++; |
| if (!*p) |
| goto syntax_error; |
| |
| q = p; |
| while (*p && !isspace(*p)) p++; |
| if (!*p) |
| goto syntax_error; |
| *p = 0; |
| |
| if (!match(q, p - q, callout_info, cilen)) |
| continue; |
| |
| p++; |
| |
| debug("Line %d matches\n", confline); |
| |
| /* we've got an action */ |
| while (isspace(*p)) p++; |
| if (!*p) |
| goto syntax_error; |
| |
| execute_program(op, ktype, kdesc, callout_info, p); |
| } |
| |
| error("/etc/request-key.conf: No matching action\n"); |
| |
| syntax_error: |
| error("/etc/request-key.conf:%d: Syntax error\n", confline); |
| |
| } /* end lookup_action() */ |
| |
| /*****************************************************************************/ |
| /* |
| * attempt to match a datum to a pattern |
| * - one asterisk is allowed anywhere in the pattern to indicate a wildcard |
| * - returns true if matched, false if not |
| */ |
| static int match(const char *pattern, int plen, const char *datum, int dlen) |
| { |
| const char *asterisk; |
| int n; |
| |
| debug("match(%*.*s,%*.*s)\n", plen, plen, pattern, dlen, dlen, datum); |
| |
| asterisk = memchr(pattern, '*', plen); |
| if (!asterisk) { |
| /* exact match only if no wildcard */ |
| if (plen == dlen && memcmp(pattern, datum, dlen) == 0) |
| goto yes; |
| goto no; |
| } |
| |
| /* the datum mustn't be shorter than the pattern without the asterisk */ |
| if (dlen < plen - 1) |
| goto no; |
| |
| n = asterisk - pattern; |
| if (n == 0) { |
| /* wildcard at beginning of pattern */ |
| pattern++; |
| if (!*pattern) |
| goto yes; /* "*" matches everything */ |
| |
| /* match the end of the datum */ |
| plen--; |
| if (memcmp(pattern, datum + (dlen - plen), plen) == 0) |
| goto yes; |
| goto no; |
| } |
| |
| /* need to match beginning of datum for "abc*" and "abc*def" */ |
| if (memcmp(pattern, datum, n) != 0) |
| goto no; |
| |
| if (!asterisk[1]) |
| goto yes; /* "abc*" matches */ |
| |
| /* match the end of the datum */ |
| asterisk++; |
| n = plen - n - 1; |
| if (memcmp(pattern, datum + (dlen - n), n) == 0) |
| goto yes; |
| |
| no: |
| debug(" = no\n"); |
| return 0; |
| |
| yes: |
| debug(" = yes\n"); |
| return 1; |
| |
| } /* end match() */ |
| |
| /*****************************************************************************/ |
| /* |
| * execute a program to deal with a key |
| */ |
| static void execute_program(char *op, |
| char *ktype, |
| char *kdesc, |
| char *callout_info, |
| char *cmdline) |
| { |
| char *argv[256]; |
| char *prog, *p, *q; |
| int argc; |
| |
| debug("execute_program('%s')\n", cmdline); |
| |
| /* extract the path to the program to run */ |
| prog = p = cmdline; |
| while (*p && !isspace(*p)) p++; |
| if (!*p) |
| error("/etc/request-key.conf:%d: No command path\n", confline); |
| *p++ = 0; |
| |
| argv[0] = strrchr(prog, '/') + 1; |
| |
| /* extract the arguments */ |
| for (argc = 1; p; argc++) { |
| while (isspace(*p)) p++; |
| if (!*p) |
| break; |
| |
| if (argc >= 254) |
| error("/etc/request-key.conf:%d: Too many arguments\n", confline); |
| argv[argc] = q = p; |
| |
| while (*p && !isspace(*p)) p++; |
| |
| if (*p) |
| *p++ = 0; |
| else |
| p = NULL; |
| |
| debug("argv[%d]: '%s'\n", argc, argv[argc]); |
| |
| if (*q != '%') |
| continue; |
| |
| /* it's a macro */ |
| q++; |
| if (!*q) |
| error("/etc/request-key.conf:%d: Missing macro name\n", confline); |
| |
| if (*q == '%') { |
| /* it's actually an anti-macro escape "%%..." -> "%..." */ |
| argv[argc]++; |
| continue; |
| } |
| |
| /* single character macros */ |
| if (!q[1]) { |
| switch (*q) { |
| case 'o': argv[argc] = op; continue; |
| case 'k': argv[argc] = xkey; continue; |
| case 't': argv[argc] = ktype; continue; |
| case 'd': argv[argc] = kdesc; continue; |
| case 'c': argv[argc] = callout_info; continue; |
| case 'u': argv[argc] = xuid; continue; |
| case 'g': argv[argc] = xgid; continue; |
| case 'T': argv[argc] = xthread_keyring; continue; |
| case 'P': argv[argc] = xprocess_keyring; continue; |
| case 'S': argv[argc] = xsession_keyring; continue; |
| default: |
| error("/etc/request-key.conf:%d: Unsupported macro\n", confline); |
| } |
| } |
| |
| /* keysub macro */ |
| if (*q == '{') { |
| key_serial_t rqsession, keysub; |
| void *tmp; |
| char *ksdesc, *end, *subdata; |
| int ret, loop; |
| |
| /* extract type and description */ |
| q++; |
| ksdesc = strchr(q, ':'); |
| if (!ksdesc) |
| error("/etc/request-key.conf:%d: Keysub macro lacks ':'\n", |
| confline); |
| *ksdesc++ = 0; |
| end = strchr(ksdesc, '}'); |
| if (!end) |
| error("/etc/request-key.conf:%d: Unterminated keysub macro\n", |
| confline); |
| |
| *end++ = 0; |
| if (*end) |
| error("/etc/request-key.conf:%d:" |
| " Keysub macro has trailing rubbish\n", |
| confline); |
| |
| debug("Keysub: %s key \"%s\"\n", q, ksdesc); |
| |
| if (!q[0]) |
| error("/etc/request-key.conf:%d: Keysub type empty\n", confline); |
| |
| if (!ksdesc[0]) |
| error("/etc/request-key.conf:%d: Keysub description empty\n", |
| confline); |
| |
| /* look up the key in the requestor's session keyring */ |
| rqsession = atoi(xsession_keyring); |
| |
| keysub = keyctl_search(rqsession, q, ksdesc, 0); |
| if (keysub < 0) |
| error("/etc/request-key.conf:%d:" |
| " Keysub key not found: %m\n", |
| confline); |
| |
| ret = keyctl_read_alloc(keysub, &tmp); |
| if (ret < 0) |
| error("/etc/request-key.conf:%d:" |
| " Can't read keysub %d data: %m\n", |
| confline, keysub); |
| subdata = tmp; |
| |
| for (loop = 0; loop < ret; loop++) |
| if (!isprint(subdata[loop])) |
| error("/etc/request-key.conf:%d:" |
| " keysub %d data not printable ('%02hhx')\n", |
| confline, keysub, subdata[loop]); |
| |
| argv[argc] = subdata; |
| continue; |
| } |
| } |
| |
| if (argc == 0) |
| error("/etc/request-key.conf:%d: No arguments\n", confline); |
| |
| argv[argc] = NULL; |
| |
| /* become the same UID/GID as the key requesting process */ |
| //setgid(atoi(xuid)); |
| //setuid(atoi(xgid)); |
| |
| /* attempt to execute the command */ |
| if (xdebug) { |
| char **ap; |
| |
| debug("Run %s\n", prog); |
| for (ap = argv; *ap; ap++) |
| debug("- argv[%zd] = \"%s\"\n", ap - argv, *ap); |
| } |
| |
| execv(prog, argv); |
| |
| error("/etc/request-key.conf:%d: Failed to execute '%s': %m\n", confline, prog); |
| |
| } /* end execute_program() */ |