Skip to content

Instantly share code, notes, and snippets.

@chris-se
Created January 8, 2016 13:22
Show Gist options
  • Save chris-se/9c0def7dca60d023d188 to your computer and use it in GitHub Desktop.
Save chris-se/9c0def7dca60d023d188 to your computer and use it in GitHub Desktop.
systemd generator for the use with keyscript=
/*
* Parts of this code is based on systemd's cryptsetup-generator.c.
* (under LGPLv2.1+, see https://github.com/systemd/systemd/)
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "sd-functions.h"
#ifndef KEYSCRIPT_CRYPTSETUP_PATH
#define KEYSCRIPT_CRYPTSETUP_PATH "/lib/cryptsetup/systemd-keyscript-cryptsetup"
#endif
#ifndef CRYPTTAB_FILE
#define CRYPTTAB_FILE "/etc/crypttab"
#endif
static char *to_device_node(const char *name)
{
const char *tag = NULL;
char *buf;
size_t len;
if (strncmp(name, "LABEL=", 6) == 0) {
tag = "label";
name += 6;
} else if (strncmp(name, "UUID=", 5) == 0) {
tag = "uuid";
name += 5;
} else if (strncmp(name, "PARTLABEL=", 10) == 0) {
tag = "partlabel";
name += 10;
} else if (strncmp(name, "PARTUUID=", 9) == 0) {
tag = "partuuid";
name += 9;
} else {
return strdup(name);
}
len = strlen("/dev/disk/by-") + strlen(tag) + 1 + strlen(name) + 1;
buf = calloc(1, len);
snprintf(buf, len, "/dev/disk/by-%s/%s", tag, name);
return buf;
}
static void write_keyscript_helper(const char *basepath, char *name, char *device_name, char *password, char *options, char *keyscript)
{
FILE *f;
char *escaped_name;
char *dropin_path_name;
char *ptr;
char *filtered_options;
char *token, *saveptr;
const char *unit_format = "%s/systemd-cryptsetup@%s.service.d/keyscript.conf";
size_t len;
int r;
filtered_options = malloc(strlen(options) + 1);
if (!filtered_options) {
/* FIXME: OOM */
return;
}
/* systemd's generator filters this, so so must we */
ptr = filtered_options;
for (token = strtok_r(options ? options : "", ",", &saveptr); token != NULL; token = strtok_r(NULL, ",", &saveptr)) {
if (strncmp(token, "x-systemd.device-timeout=", 25) == 0 || strncmp(token, "comment=systemd.device-timeout=", 31) == 0)
continue;
if (ptr != filtered_options)
*ptr++ = ',';
strcpy(ptr, token);
ptr += strlen(token);
}
*ptr = '\0';
escaped_name = unit_name_escape(name);
if (!escaped_name) {
/* FIXME: OOM */
return;
}
len = strlen(unit_format) - 4 + strlen(basepath) + strlen(escaped_name) + 1;
dropin_path_name = calloc(1, len);
if (!dropin_path_name) {
/* FIXME: OOM */
return;
}
snprintf(dropin_path_name, len, unit_format, basepath, escaped_name);
free(escaped_name);
escaped_name = NULL;
/* Create directory */
ptr = strrchr(dropin_path_name, '/');
*ptr = '\0';
r = mkdir(dropin_path_name, 0755);
if (r < 0 && errno != EEXIST) {
/* FIXME: error handling */
return;
}
*ptr = '/';
/* Create file, but we want to make sure it's not world-readable */
umask(0077);
f = fopen(dropin_path_name, "w");
if (!f) {
/* FIXME: error handling */
umask(0022);
return;
}
fprintf(f, "[Service]\n"
"ExecStart=\n"
"ExecStart=" KEYSCRIPT_CRYPTSETUP_PATH " '%s' '%s' '%s' '%s' '%s'\n",
name, device_name, password ? password : "", filtered_options, keyscript);
fclose(f);
free(dropin_path_name);
umask(0022);
}
int main(int argc, char **argv)
{
FILE *f;
const char *basepath = "/tmp";
umask(0022);
if (argc > 1 && argc != 4) {
fprintf(stderr, "This program takes three or no arguments.\n");
return 1;
}
if (argc > 1)
basepath = argv[1];
f = fopen(CRYPTTAB_FILE, "r");
if (!f)
return 0;
for (;;) {
char line[2048];
char *token, *saveptr, *l, *keyscript;
int k;
char *name = NULL, *device = NULL, *password = NULL, *options = NULL, *device_name, *p_options;
if (!fgets(line, sizeof(line), f))
break;
l = strstrip(line);
if (*l == '#' || *l == '\0')
continue;
k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
if (k < 2 || k > 4) {
free(name);
free(device);
free(password);
free(options);
continue;
}
device_name = to_device_node(device);
if (!device_name) {
/* FIXME: out of memory */
free(name);
free(device);
free(password);
free(options);
continue;
}
/* don't need that anymore */
free(device);
device = NULL;
/* FIXME: cryptsetup-generator.c also parsers /proc/cmdline and looks for
* UUID-specific options - currently that isn't supported */
keyscript = NULL;
p_options = strdup(options);
if (!p_options) {
/* FIXME: out of memory */
free(name);
free(device_name);
free(password);
free(options);
continue;
}
for (token = strtok_r(p_options, ",", &saveptr); token != NULL; token = strtok_r(NULL, ",", &saveptr)) {
if (strncmp(token, "keyscript=", 10) == 0) {
keyscript = token + 10;
}
}
if (keyscript)
write_keyscript_helper(basepath, name, device_name, password, options, keyscript);
free(name);
free(device_name);
free(password);
free(options);
free(p_options);
}
fclose(f);
return 0;
}
CC=gcc
CFLAGS=-Wall -Wextra -O2 -ggdb
#CPPFLAGS=-DCRYPTTAB_FILE=\"/tmp/crypttab\"
all: keyscript-generator systemd-keyscript-cryptsetup
keyscript-generator: keyscript-generator.o sd-functions.o
systemd-keyscript-cryptsetup: systemd-keyscript-cryptsetup.o
clean:
rm -f keyscript-generator systemd-keyscript-cryptsetup *.o *~
/*
* This code is from systemd source, slightly modified.
*
* Look at src/shared/unit-name.c and src/shared/util.c for the
* original code.
*
* Reference: https://github.com/systemd/systemd/
*
* License: LGPLv2.1+
*/
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define DIGITS "0123456789"
#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz"
#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define LETTERS LOWERCASE_LETTERS UPPERCASE_LETTERS
#define WHITESPACE " \t\r\n"
#define VALID_CHARS \
DIGITS LETTERS \
":-_.\\"
char hexchar(int x) {
static const char table[16] = "0123456789abcdef";
return table[x & 15];
}
static char *do_escape_char(char c, char *t) {
assert(t);
*(t++) = '\\';
*(t++) = 'x';
*(t++) = hexchar(c >> 4);
*(t++) = hexchar(c);
return t;
}
static char *do_escape(const char *f, char *t) {
assert(f);
assert(t);
/* do not create units with a leading '.', like for "/.dotdir" mount points */
if (*f == '.') {
t = do_escape_char(*f, t);
f++;
}
for (; *f; f++) {
if (*f == '/')
*(t++) = '-';
else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
t = do_escape_char(*f, t);
else
*(t++) = *f;
}
return t;
}
char *unit_name_escape(const char *f) {
char *r, *t;
assert(f);
r = calloc(1, strlen(f)*4+1);
if (!r)
return NULL;
t = do_escape(f, r);
*t = 0;
return r;
}
char *strstrip(char *s) {
char *e;
/* Drops trailing whitespace. Modifies the string in
* place. Returns pointer to first non-space character */
s += strspn(s, WHITESPACE);
for (e = strchr(s, 0); e > s; e --)
if (!strchr(WHITESPACE, e[-1]))
break;
*e = 0;
return s;
}
#ifndef SD_FUNCTIONS_H
#define SD_FUNCTIONS_H
char *strstrip(char *s);
char *unit_name_escape(const char *f);
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#ifndef SYSTEMD_CRYPTSETUP_PATH
#define SYSTEMD_CRYPTSETUP_PATH "/lib/systemd/systemd-cryptsetup"
#endif
extern char **environ;
static void freev(char **p)
{
char **ptr;
if (!p)
return;
for (ptr = p; *ptr; ptr++)
free(*ptr);
free(p);
}
static char *concat(const char *a, const char *b)
{
char *p = malloc(strlen(a) + strlen(b) + 1);
if (!p)
return NULL;
snprintf(p, strlen(a) + strlen(b) + 1, "%s%s", a, b);
return p;
}
static char *concat_paths(const char *a, const char *b)
{
char *p = malloc(strlen(a) + strlen(b) + 2);
if (!p)
return NULL;
snprintf(p, strlen(a) + strlen(b) + 2, "%s%s%s", a, a[strlen(a) - 1] == '/' ? "" : "/", b);
return p;
}
static char **get_script_environment(const char *name, const char *device, const char *password, const char *options)
{
char **ptr;
char **result;
char *p_options;
char *token, *saveptr;
char *val;
size_t i;
p_options = strdup(options);
if (!p_options)
return NULL;
/* Count cryptsetup options + other environment variables */
for (i = 0, ptr = environ; *ptr; ptr++, i++);
if (strlen(options))
i++;
for (val = strchr(options, ','); val; val = strchr(val + 1, ','), i++);
i += 5;
result = calloc(sizeof(char *), i + 1);
if (!result) {
free(p_options);
return NULL;
}
i = 0;
for (ptr = environ; *ptr; ptr++) {
char *p = strdup(*ptr);
if (!p) {
freev(result);
free(p_options);
return NULL;
}
result[i++] = p;
}
for (token = strtok_r(p_options, ",", &saveptr); token != NULL; token = strtok_r(NULL, ",", &saveptr)) {
val = malloc(strlen("CRYPTTAB_OPTION_") + strlen(token) + 4 + 1);
if (!val) {
freev(result);
free(p_options);
return NULL;
}
snprintf(val, strlen("CRYPTTAB_OPTION_") + strlen(token) + 4 + 1, "CRYPTTAB_OPTION_%s%s", token, strchr(token, '=') ? "" : "=yes");
result[i++] = val;
}
free(p_options);
p_options = NULL;
val = concat("CRYPTTAB_OPTIONS=", options);
if (!val) {
freev(result);
return NULL;
}
result[i++] = val;
val = concat("CRYPTTAB_NAME=", name);
if (!val) {
freev(result);
return NULL;
}
result[i++] = val;
val = concat("CRYPTTAB_SOURCE=", device);
if (!val) {
freev(result);
return NULL;
}
result[i++] = val;
val = concat("CRYPTTAB_KEY=", password);
if (!val) {
freev(result);
return NULL;
}
result[i++] = val;
val = strdup("CRYPTTAB_TRIED=0");
if (!val) {
freev(result);
return NULL;
}
result[i++] = val;
return result;
}
static char *get_system_path(void)
{
char *buf = getenv("PATH");
size_t len;
if (buf)
return strdup(buf);
len = confstr(_CS_PATH, NULL, (size_t) 0);
buf = malloc(len);
if (!buf)
return NULL;
(void) confstr(_CS_PATH, buf, len);
return buf;
}
static char *find_in_path(const char *additional_path, const char *executable)
{
char *system_path = get_system_path();
char *buf = NULL;
char *token, *saveptr = NULL;
buf = concat_paths(additional_path, executable);
if (access(buf, X_OK) == 0)
return buf;
free(buf);
buf = NULL;
for (token = strtok_r(system_path, ":", &saveptr); token != NULL; token = strtok_r(NULL, ":", &saveptr)) {
buf = concat_paths(token, executable);
if (access(buf, X_OK) == 0)
return buf;
free(buf);
}
return NULL;
}
int main(int argc, char **argv)
{
pid_t pid;
int pipefd[2];
int r;
const char *name;
const char *device;
const char *keyfile;
const char *options;
const char *keyscript;
const char *real_keyscript;
char **script_environment;
if (argc != 6) {
fprintf(stderr, "%s: may only be called with 5 arguments\n", argv[0]);
return 1;
}
name = argv[1];
device = argv[2];
keyfile = argv[3];
options = argv[4];
keyscript = argv[5];
if (access(keyscript, X_OK) == 0) {
real_keyscript = keyscript;
} else {
/* not a direct path */
real_keyscript = find_in_path("/lib/cryptsetup/scripts", keyscript);
if (!real_keyscript) {
fprintf(stderr, "%s: keyscript %s does not exist\n", argv[0], keyscript);
return 1;
}
}
script_environment = get_script_environment(name, device, keyfile, options);
if (!script_environment) {
fprintf(stderr, "%s: memory allocation error\n", argv[0]);
return 1;
}
r = pipe(pipefd);
if (r < 0) {
fprintf(stderr, "%s: error setting up pipe: %m\n", argv[0]);
return 1;
}
pid = fork();
if (pid < 0) {
fprintf(stderr, "%s: fork() failed: %m\n", argv[0]);
return 1;
}
if (pid == 0) {
char *script_argv[] = {
(char *)keyscript,
(char *)keyfile,
NULL
};
/* child process */
close(pipefd[0]);
r = dup2(pipefd[1], 1);
if (r < 0) {
fprintf(stderr," %s: dup2() failed: %m\n", argv[0]);
return 1;
}
close(pipefd[1]);
execve(real_keyscript, script_argv, script_environment);
fprintf(stderr,"%s: couldn't execute %s: %m\n", argv[0], real_keyscript);
} else {
char *cryptsetup_argv[] = {
(char *)SYSTEMD_CRYPTSETUP_PATH,
(char *)"attach",
(char *)name,
(char *)device,
(char *)"/proc/self/fd/0",
(char *)options,
NULL
};
/* parent process */
close(pipefd[1]);
r = dup2(pipefd[0], 0);
if (r < 0) {
fprintf(stderr," %s: dup2() failed: %m\n", argv[0]);
return 1;
}
close(pipefd[0]);
execv(SYSTEMD_CRYPTSETUP_PATH, cryptsetup_argv);
fprintf(stderr,"%s: couldn't execute " SYSTEMD_CRYPTSETUP_PATH ": %m\n", argv[0]);
}
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment