1 /* request-key.c: hand a key request off to the appropriate process
3 * Copyright (C) 2005 Red Hat, Inc. All Rights Reserved.
4 * Written by David Howells (dhowells@redhat.com)
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
11 * /sbin/request-key <op> <key> <uid> <gid> <threadring> <processring> <sessionring> [<info>]
13 * Searches the specified session ring for a key indicating the command to run:
15 * desc: "request-key:<op>"
16 * data: command name, eg: "/home/dhowells/request-key-create.sh"
30 #include <sys/select.h>
40 static char *xthread_keyring;
41 static char *xprocess_keyring;
42 static char *xsession_keyring;
46 static void lookup_action(char *op,
51 __attribute__((noreturn));
53 static void execute_program(char *op,
59 __attribute__((noreturn));
61 static void pipe_to_program(char *op,
68 __attribute__((noreturn));
70 static int match(const char *pattern, int plen, const char *datum, int dlen);
72 static void debug(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
73 static void debug(const char *fmt, ...)
79 vfprintf(stderr, fmt, va);
83 openlog("request-key", 0, LOG_AUTHPRIV);
86 vsyslog(LOG_DEBUG, fmt, va);
94 static void error(const char *fmt, ...) __attribute__((noreturn, format(printf, 1, 2)));
95 static void error(const char *fmt, ...)
101 vfprintf(stderr, fmt, va);
106 openlog("request-key", 0, LOG_AUTHPRIV);
109 vsyslog(LOG_ERR, fmt, va);
118 static void oops(int x)
120 error("Died on signal %d", x);
123 /*****************************************************************************/
127 int main(int argc, char *argv[])
130 char *ktype, *kdesc, *buf, *callout_info;
131 int ret, ntype, dpos, n, fd;
133 signal(SIGSEGV, oops);
134 signal(SIGBUS, oops);
135 signal(SIGPIPE, SIG_IGN);
138 if (argc > 1 && strcmp(argv[1], "-d") == 0) {
143 else if (argc > 1 && strcmp(argv[1], "-n") == 0) {
152 if (argc != 8 && argc != 9)
153 error("Unexpected argument count: %d\n", argc);
155 fd = open("/dev/null", O_RDWR);
164 error("dup failed: %m\n");
166 if (ret < 2 && dup(fd) < 0)
167 error("dup failed: %m\n");
173 xthread_keyring = argv[5];
174 xprocess_keyring = argv[6];
175 xsession_keyring = argv[7];
179 /* assume authority over the key
180 * - older kernel doesn't support this function
182 ret = keyctl_assume_authority(key);
183 if (ret < 0 && !(argc == 9 || errno == EOPNOTSUPP))
184 error("Failed to assume authority over key %d (%m)\n", key);
186 /* ask the kernel to describe the key to us */
188 ret = keyctl_describe_alloc(key, &buf);
193 buf = strdup("user;0;0;1f0000;debug:1234");
196 /* extract the type and description from the key */
197 debug("Key descriptor: \"%s\"\n", buf);
201 n = sscanf(buf, "%*[^;]%n;%*d;%*d;%x;%n", &ntype, &n, &dpos);
203 error("Failed to parse key description\n");
209 debug("Key type: %s\n", ktype);
210 debug("Key desc: %s\n", kdesc);
212 /* get hold of the callout info */
213 callout_info = argv[8];
218 if (keyctl_read_alloc(KEY_SPEC_REQKEY_AUTH_KEY, &tmp) < 0)
219 error("Failed to retrieve callout info (%m)\n");
224 debug("CALLOUT: '%s'\n", callout_info);
226 /* determine the action to perform */
227 lookup_action(argv[1], /* op */
228 key, /* ID of key under construction */
229 ktype, /* key type */
230 kdesc, /* key description */
231 callout_info /* call out information */
235 error("Key %d is inaccessible (%m)\n", key);
239 /*****************************************************************************/
241 * determine the action to perform
243 static void lookup_action(char *op,
249 char buf[4096 + 2], *p, *q;
251 int len, oplen, ktlen, kdlen, cilen;
254 ktlen = strlen(ktype);
255 kdlen = strlen(kdesc);
256 cilen = strlen(callout_info);
258 /* search the config file for a command to run */
259 conf = fopen(xdebug < 2 ? "/etc/request-key.conf" : "request-key.conf", "r");
261 error("Cannot open /etc/request-key.conf: %m\n");
263 for (confline = 1;; confline++) {
264 /* read the file line-by-line */
265 if (!fgets(buf, sizeof(buf), conf)) {
267 error("Cannot find command to construct key %d\n", key);
268 error("Error reading /etc/request-key.conf\n");
272 if (len >= sizeof(buf) - 2)
273 error("/etc/request-key.conf:%d: Line too long\n", confline);
275 /* ignore blank lines and comments */
276 if (len == 1 || buf[0] == '#' || isspace(buf[0]))
282 /* attempt to match the op */
284 while (*p && !isspace(*p)) p++;
289 if (!match(q, p - q, op, oplen))
294 /* attempt to match the type */
295 while (isspace(*p)) p++;
300 while (*p && !isspace(*p)) p++;
305 if (!match(q, p - q, ktype, ktlen))
310 /* attempt to match the description */
311 while (isspace(*p)) p++;
316 while (*p && !isspace(*p)) p++;
321 if (!match(q, p - q, kdesc, kdlen))
326 /* attempt to match the callout info */
327 while (isspace(*p)) p++;
332 while (*p && !isspace(*p)) p++;
337 if (!match(q, p - q, callout_info, cilen))
342 debug("Line %d matches\n", confline);
344 /* we've got an action */
345 while (isspace(*p)) p++;
351 execute_program(op, key, ktype, kdesc, callout_info, p);
354 error("/etc/request-key.conf: No matching action\n");
357 error("/etc/request-key.conf:%d: Syntax error\n", confline);
359 } /* end lookup_action() */
361 /*****************************************************************************/
363 * attempt to match a datum to a pattern
364 * - one asterisk is allowed anywhere in the pattern to indicate a wildcard
365 * - returns true if matched, false if not
367 static int match(const char *pattern, int plen, const char *datum, int dlen)
369 const char *asterisk;
372 debug("match(%*.*s,%*.*s)\n", plen, plen, pattern, dlen, dlen, datum);
374 asterisk = memchr(pattern, '*', plen);
376 /* exact match only if no wildcard */
377 if (plen == dlen && memcmp(pattern, datum, dlen) == 0)
382 /* the datum mustn't be shorter than the pattern without the asterisk */
386 n = asterisk - pattern;
388 /* wildcard at beginning of pattern */
391 goto yes; /* "*" matches everything */
393 /* match the end of the datum */
395 if (memcmp(pattern, datum + (dlen - plen), plen) == 0)
400 /* need to match beginning of datum for "abc*" and "abc*def" */
401 if (memcmp(pattern, datum, n) != 0)
405 goto yes; /* "abc*" matches */
407 /* match the end of the datum */
410 if (memcmp(pattern, datum + (dlen - n), n) == 0)
423 /*****************************************************************************/
425 * execute a program to deal with a key
427 static void execute_program(char *op,
438 debug("execute_program('%s','%s')\n", callout_info, cmdline);
440 /* if the commandline begins with a bar, then we pipe the callout data into it and read
441 * back the payload data
445 if (cmdline[0] == '|') {
450 /* extract the path to the program to run */
452 while (*p && !isspace(*p)) p++;
454 // error("/etc/request-key.conf:%d: No command path\n", confline);
459 argv[0] = strrchr(prog, '/') + 1;
461 /* extract the arguments */
462 for (argc = 1; p; argc++) {
463 while (isspace(*p)) p++;
468 error("/etc/request-key.conf:%d: Too many arguments\n", confline);
471 while (*p && !isspace(*p)) p++;
478 debug("argv[%d]: '%s'\n", argc, argv[argc]);
486 error("/etc/request-key.conf:%d: Missing macro name\n", confline);
489 /* it's actually an anti-macro escape "%%..." -> "%..." */
494 /* single character macros */
497 case 'o': argv[argc] = op; continue;
498 case 'k': argv[argc] = xkey; continue;
499 case 't': argv[argc] = ktype; continue;
500 case 'd': argv[argc] = kdesc; continue;
501 case 'c': argv[argc] = callout_info; continue;
502 case 'u': argv[argc] = xuid; continue;
503 case 'g': argv[argc] = xgid; continue;
504 case 'T': argv[argc] = xthread_keyring; continue;
505 case 'P': argv[argc] = xprocess_keyring; continue;
506 case 'S': argv[argc] = xsession_keyring; continue;
508 error("/etc/request-key.conf:%d: Unsupported macro\n", confline);
516 char *ksdesc, *end, *subdata;
519 /* extract type and description */
521 ksdesc = strchr(q, ':');
523 error("/etc/request-key.conf:%d: Keysub macro lacks ':'\n",
526 end = strchr(ksdesc, '}');
528 error("/etc/request-key.conf:%d: Unterminated keysub macro\n",
533 error("/etc/request-key.conf:%d:"
534 " Keysub macro has trailing rubbish\n",
537 debug("Keysub: %s key \"%s\"\n", q, ksdesc);
540 error("/etc/request-key.conf:%d: Keysub type empty\n", confline);
543 error("/etc/request-key.conf:%d: Keysub description empty\n",
546 /* look up the key in the requestor's keyrings, but fail immediately if the
547 * key is not found rather than invoking /sbin/request-key again
549 keysub = request_key(q, ksdesc, NULL, 0);
551 error("/etc/request-key.conf:%d:"
552 " Keysub key not found: %m\n",
555 ret = keyctl_read_alloc(keysub, &tmp);
557 error("/etc/request-key.conf:%d:"
558 " Can't read keysub %d data: %m\n",
562 for (loop = 0; loop < ret; loop++)
563 if (!isprint(subdata[loop]))
564 error("/etc/request-key.conf:%d:"
565 " keysub %d data not printable ('%02hhx')\n",
566 confline, keysub, subdata[loop]);
568 argv[argc] = subdata;
574 error("/etc/request-key.conf:%d: No arguments\n", confline);
581 debug("%s %s\n", pipeit ? "PipeThru" : "Run", prog);
582 for (ap = argv; *ap; ap++)
583 debug("- argv[%td] = \"%s\"\n", ap - argv, *ap);
586 /* become the same UID/GID as the key requesting process */
587 //setgid(atoi(xuid));
588 //setuid(atoi(xgid));
590 /* if the last argument is a single bar, we spawn off the program dangling on the end of
591 * three pipes and read the key material from the program, otherwise we just exec
594 pipe_to_program(op, key, ktype, kdesc, callout_info, prog, argv);
596 /* attempt to execute the command */
599 error("/etc/request-key.conf:%d: Failed to execute '%s': %m\n", confline, prog);
601 } /* end execute_program() */
603 /*****************************************************************************/
605 * pipe the callout information to the specified program and retrieve the payload data over another
608 static void pipe_to_program(char *op,
616 char errbuf[512], payload[32768 + 1], *pp, *pc, *pe;
617 int ipi[2], opi[2], epi[2], childpid;
618 int ifl, ofl, efl, npay, ninfo, espace, tmp;
620 debug("pipe_to_program(%s -> %s)", callout_info, prog);
622 if (pipe(ipi) < 0 || pipe(opi) < 0 || pipe(epi) < 0)
623 error("pipe failed: %m");
627 error("fork failed: %m");
631 if (dup2(ipi[0], 0) < 0 ||
632 dup2(opi[1], 1) < 0 ||
634 error("dup2 failed: %m");
643 error("/etc/request-key.conf:%d: Failed to execute '%s': %m\n", confline, prog);
651 #define TOSTDIN ipi[1]
652 #define FROMSTDOUT opi[0]
653 #define FROMSTDERR epi[0]
655 ifl = fcntl(TOSTDIN, F_GETFL);
656 ofl = fcntl(FROMSTDOUT, F_GETFL);
657 efl = fcntl(FROMSTDERR, F_GETFL);
658 if (ifl < 0 || ofl < 0 || efl < 0)
659 error("fcntl/F_GETFL failed: %m");
665 if (fcntl(TOSTDIN, F_SETFL, ifl) < 0 ||
666 fcntl(FROMSTDOUT, F_SETFL, ofl) < 0 ||
667 fcntl(FROMSTDERR, F_SETFL, efl) < 0)
668 error("fcntl/F_SETFL failed: %m");
670 ninfo = strlen(callout_info);
673 npay = sizeof(payload);
676 espace = sizeof(errbuf);
687 FD_SET(TOSTDIN, &wfds);
696 if (FROMSTDOUT != -1)
697 FD_SET(FROMSTDOUT, &rfds);
699 if (FROMSTDERR != -1)
700 FD_SET(FROMSTDERR, &rfds);
702 tmp = TOSTDIN > FROMSTDOUT ? TOSTDIN : FROMSTDOUT;
703 tmp = tmp > FROMSTDERR ? tmp : FROMSTDERR;
706 debug("select r=%d,%d w=%d m=%d\n", FROMSTDOUT, FROMSTDERR, TOSTDIN, tmp);
708 tmp = select(tmp, &rfds, &wfds, NULL, NULL);
710 error("select failed: %m\n");
712 if (TOSTDIN != -1 && FD_ISSET(TOSTDIN, &wfds)) {
713 tmp = write(TOSTDIN, pc, ninfo);
716 error("write failed: %m\n");
722 debug("wrote %d\n", tmp);
729 if (FROMSTDOUT != -1 && FD_ISSET(FROMSTDOUT, &rfds)) {
730 tmp = read(FROMSTDOUT, pp, npay);
732 error("read failed: %m\n");
734 debug("read %d\n", tmp);
745 error("Too much data read from query program\n");
749 if (FROMSTDERR != -1 && FD_ISSET(FROMSTDERR, &rfds)) {
752 tmp = read(FROMSTDERR, pe, espace);
754 error("read failed: %m\n");
756 debug("read err %d\n", tmp);
767 while ((nl = memchr(errbuf, '\n', pe - errbuf))) {
774 fprintf(stderr, "Child: %*.*s", n, n, errbuf);
777 openlog("request-key", 0, LOG_AUTHPRIV);
778 syslog(LOG_ERR, "Child: %*.*s", n, n, errbuf);
784 memmove(errbuf, nl, rest);
790 espace = sizeof(errbuf);
795 int n = sizeof(errbuf);
798 fprintf(stderr, "Child: %*.*s", n, n, errbuf);
801 openlog("request-key", 0, LOG_AUTHPRIV);
802 syslog(LOG_ERR, "Child: %*.*s", n, n, errbuf);
807 espace = sizeof(errbuf);
811 } while (TOSTDIN != -1 || FROMSTDOUT != -1 || FROMSTDERR != -1);
813 /* wait for the program to exit */
814 if (waitpid(childpid, &tmp, 0) != childpid)
815 error("wait for child failed: %m\n");
817 /* if the process exited non-zero or died on a signal, then we call back in to ourself to
819 * - this is not exactly beautiful but the quickest way of having configurable negation
822 if (WIFEXITED(tmp) && WEXITSTATUS(tmp) != 0) {
824 error("child exited %d\n", WEXITSTATUS(tmp));
827 debug("child exited %d\n", WEXITSTATUS(tmp));
828 lookup_action("negate", key, ktype, kdesc, callout_info);
831 if (WIFSIGNALED(tmp)) {
833 error("child died on signal %d\n", WTERMSIG(tmp));
836 debug("child died on signal %d\n", WTERMSIG(tmp));
837 lookup_action("negate", key, ktype, kdesc, callout_info);
840 /* attempt to instantiate the key */
841 debug("instantiate with %td bytes\n", pp - payload);
843 if (keyctl_instantiate(key, payload, pp - payload, 0) < 0)
844 error("instantiate key failed: %m\n");
846 debug("instantiation successful\n");
849 } /* end pipe_to_program() */