| /* Software-based Trusted Platform Module (TPM) Emulator |
| * Copyright (C) 2004-2010 Mario Strasser <mast@gmx.net> |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * $Id: tpmd.c 405 2010-02-18 23:11:31Z mast $ |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <signal.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <syslog.h> |
| #include <stdarg.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <pwd.h> |
| #include <grp.h> |
| #include "config.h" |
| #include "tpm/tpm_emulator.h" |
| |
| #define TPM_COMMAND_TIMEOUT 30 |
| #define TPM_RANDOM_DEVICE "/dev/urandom" |
| |
| static volatile int stopflag = 0; |
| static int is_daemon = 0; |
| static int opt_debug = 0; |
| static int opt_foreground = 0; |
| static const char *opt_socket_name = TPM_SOCKET_NAME; |
| static const char *opt_storage_file = TPM_STORAGE_NAME; |
| static uid_t opt_uid = 0; |
| static gid_t opt_gid = 0; |
| static int tpm_startup = 2; |
| static uint32_t tpm_config = 0; |
| static int rand_fh; |
| |
| void *tpm_malloc(size_t size) |
| { |
| return malloc(size); |
| } |
| |
| void tpm_free(/*const*/ void *ptr) |
| { |
| if (ptr != NULL) free((void*)ptr); |
| } |
| |
| void tpm_log(int priority, const char *fmt, ...) |
| { |
| va_list ap, bp; |
| va_start(ap, fmt); |
| va_copy(bp, ap); |
| switch (priority) { |
| case TPM_LOG_DEBUG: |
| vsyslog(LOG_DEBUG, fmt, ap); |
| break; |
| case TPM_LOG_ERROR: |
| vsyslog(LOG_ERR, fmt, ap); |
| break; |
| case TPM_LOG_INFO: |
| default: |
| vsyslog(LOG_INFO, fmt, ap); |
| break; |
| } |
| va_end(ap); |
| if (!is_daemon && (priority != TPM_LOG_DEBUG || opt_debug)) { |
| vprintf(fmt, bp); |
| } |
| va_end(bp); |
| } |
| |
| void tpm_get_extern_random_bytes(void *buf, size_t nbytes) |
| { |
| uint8_t *p = (uint8_t*)buf; |
| ssize_t res; |
| while (nbytes > 0) { |
| res = read(rand_fh, p, nbytes); |
| if (res > 0) { |
| nbytes -= res; p += res; |
| } |
| } |
| } |
| |
| uint64_t tpm_get_ticks(void) |
| { |
| static uint64_t old_t = 0; |
| uint64_t new_t, res_t; |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| new_t = (uint64_t)tv.tv_sec * 1000000 + (uint64_t)tv.tv_usec; |
| res_t = (old_t > 0) ? new_t - old_t : 0; |
| old_t = new_t; |
| return res_t; |
| } |
| |
| int tpm_write_to_storage(uint8_t *data, size_t data_length) |
| { |
| int fh; |
| ssize_t res; |
| fh = open(opt_storage_file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); |
| if (fh < 0) return -1; |
| while (data_length > 0) { |
| res = write(fh, data, data_length); |
| if (res < 0) { |
| close(fh); |
| return -1; |
| } |
| data_length -= res; |
| data += res; |
| } |
| close(fh); |
| return 0; |
| } |
| |
| int tpm_read_from_storage(uint8_t **data, size_t *data_length) |
| { |
| int fh; |
| ssize_t res; |
| size_t total_length; |
| fh = open(opt_storage_file, O_RDONLY); |
| if (fh < 0) return -1; |
| total_length = lseek(fh, 0, SEEK_END); |
| lseek(fh, 0, SEEK_SET); |
| *data = tpm_malloc(total_length); |
| if (*data == NULL) { |
| close(fh); |
| return -1; |
| } |
| *data_length = 0; |
| while (total_length > 0) { |
| res = read(fh, &(*data)[*data_length], total_length); |
| if (res < 0) { |
| close(fh); |
| tpm_free(*data); |
| return -1; |
| } |
| if (res == 0) break; |
| *data_length += res; |
| total_length -= res; |
| } |
| close(fh); |
| return 0; |
| } |
| |
| static void print_usage(char *name) |
| { |
| printf("usage: %s [-d] [-f] [-s storage file] [-u unix socket name] " |
| "[-o user name] [-g group name] [-h] [startup mode]\n", name); |
| printf(" d : enable debug mode\n"); |
| printf(" f : forces the application to run in the foreground\n"); |
| printf(" s : storage file to use (default: %s)\n", opt_storage_file); |
| printf(" u : unix socket name to use (default: %s)\n", opt_socket_name); |
| printf(" o : effective user the application should run as\n"); |
| printf(" g : effective group the application should run as\n"); |
| printf(" h : print this help message\n"); |
| printf(" startup mode : must be 'clear', " |
| "'save' (default) or 'deactivated\n"); |
| } |
| |
| static void parse_options(int argc, char **argv) |
| { |
| int c; |
| struct passwd *pwd; |
| struct group *grp; |
| opt_uid = getuid(); |
| opt_gid = getgid(); |
| info("parsing options"); |
| while ((c = getopt (argc, argv, "dfs:u:o:g:c:h")) != -1) { |
| debug("handling option '-%c'", c); |
| switch (c) { |
| case 'd': |
| opt_debug = 1; |
| setlogmask(setlogmask(0) | LOG_MASK(LOG_DEBUG)); |
| debug("debug mode enabled"); |
| break; |
| case 'f': |
| debug("application is forced to run in foreground"); |
| opt_foreground = 1; |
| break; |
| case 's': |
| opt_storage_file = optarg; |
| debug("using storage file '%s'", opt_storage_file); |
| break; |
| case 'u': |
| opt_socket_name = optarg; |
| debug("using unix socket '%s'", opt_socket_name); |
| break; |
| case 'o': |
| pwd = getpwnam(optarg); |
| if (pwd == NULL) { |
| error("invalid user name '%s'\n", optarg); |
| exit(EXIT_FAILURE); |
| } |
| opt_uid = pwd->pw_uid; |
| break; |
| case 'g': |
| grp = getgrnam(optarg); |
| if (grp == NULL) { |
| error("invalid group name '%s'\n", optarg); |
| exit(EXIT_FAILURE); |
| } |
| opt_gid = grp->gr_gid; |
| break; |
| case 'c': |
| tpm_config = strtol(optarg, NULL, 0); |
| break; |
| case '?': |
| error("unknown option '-%c'", optopt); |
| print_usage(argv[0]); |
| exit(EXIT_FAILURE); |
| case 'h': |
| default: |
| print_usage(argv[0]); |
| exit(EXIT_SUCCESS); |
| } |
| } |
| if (optind < argc) { |
| debug("startup mode = '%s'", argv[optind]); |
| if (!strcmp(argv[optind], "clear")) { |
| tpm_startup = 1; |
| } else if (!strcmp(argv[optind], "save")) { |
| tpm_startup = 2; |
| } else if (!strcmp(argv[optind], "deactivated")) { |
| tpm_startup = 3; |
| } else { |
| error("invalid startup mode '%s'; must be 'clear', " |
| "'save' (default) or 'deactivated", argv[optind]); |
| print_usage(argv[0]); |
| exit(EXIT_SUCCESS); |
| } |
| } else { |
| /* if no startup mode is given assume save if a configuration |
| file is available, clear otherwise */ |
| int fh = open(opt_storage_file, O_RDONLY); |
| if (fh < 0) { |
| tpm_startup = 1; |
| info("no startup mode was specified; asuming 'clear'"); |
| } else { |
| tpm_startup = 2; |
| close(fh); |
| } |
| } |
| } |
| |
| static void switch_uid_gid(void) |
| { |
| if (opt_gid != getgid()) { |
| info("switching effective group ID to %d", opt_gid); |
| if (setgid(opt_gid) == -1) { |
| error("switching effective group ID to %d failed: %s", opt_gid, strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| } |
| if (opt_uid != getuid()) { |
| info("switching effective user ID to %d", opt_uid); |
| if (setuid(opt_uid) == -1) { |
| error("switching effective user ID to %d failed: %s", opt_uid, strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| } |
| } |
| |
| static void init_random(void) |
| { |
| info("openening random device %s", TPM_RANDOM_DEVICE); |
| rand_fh = open(TPM_RANDOM_DEVICE, O_RDONLY); |
| if (rand_fh < 0) { |
| error("open(%s) failed: %s", TPM_RANDOM_DEVICE, strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| static void signal_handler(int sig) |
| { |
| info("signal received: %d", sig); |
| if (sig == SIGTERM || sig == SIGQUIT || sig == SIGINT) stopflag = 1; |
| } |
| |
| static void init_signal_handler(void) |
| { |
| info("installing signal handlers"); |
| if (signal(SIGTERM, signal_handler) == SIG_ERR) { |
| error("signal(SIGTERM) failed: %s", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| if (signal(SIGQUIT, signal_handler) == SIG_ERR) { |
| error("signal(SIGQUIT) failed: %s", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| if (signal(SIGINT, signal_handler) == SIG_ERR) { |
| error("signal(SIGINT) failed: %s", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| if (signal(SIGPIPE, signal_handler) == SIG_ERR) { |
| error("signal(SIGPIPE) failed: %s", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| static void daemonize(void) |
| { |
| pid_t sid, pid; |
| info("daemonizing process"); |
| pid = fork(); |
| if (pid < 0) { |
| error("fork() failed: %s", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| if (pid > 0) exit(EXIT_SUCCESS); |
| pid = getpid(); |
| sid = setsid(); |
| if (sid < 0) { |
| error("setsid() failed: %s", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| if (chdir("/") < 0) { |
| error("chdir() failed: %s", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| close(STDIN_FILENO); |
| close(STDOUT_FILENO); |
| close(STDERR_FILENO); |
| is_daemon = 1; |
| info("process was successfully daemonized: pid=%d sid=%d", pid, sid); |
| } |
| |
| static int mkdirs(const char *path) |
| { |
| char *copy = strdup(path); |
| char *p = strchr(copy + 1, '/'); |
| while (p != NULL) { |
| *p = '\0'; |
| if ((mkdir(copy, 0755) == -1) && (errno != EEXIST)) { |
| free(copy); |
| return errno; |
| } |
| *p = '/'; |
| p = strchr(p + 1, '/'); |
| } |
| free(copy); |
| return 0; |
| } |
| |
| static int init_socket(const char *name) |
| { |
| int sock; |
| struct sockaddr_un addr; |
| info("initializing socket %s", name); |
| sock = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (sock < 0) { |
| error("socket(AF_UNIX) failed: %s", strerror(errno)); |
| return -1; |
| } |
| mkdirs(name); |
| addr.sun_family = AF_UNIX; |
| strncpy(addr.sun_path, name, sizeof(addr.sun_path)); |
| umask(0177); |
| if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { |
| error("bind(%s) failed: %s", addr.sun_path, strerror(errno)); |
| close(sock); |
| return -1; |
| } |
| listen(sock, 1); |
| return sock; |
| } |
| |
| static void main_loop(void) |
| { |
| int sock, fh, res; |
| int32_t in_len; |
| uint32_t out_len; |
| uint8_t in[TPM_CMD_BUF_SIZE], *out; |
| struct sockaddr_un addr; |
| socklen_t addr_len; |
| fd_set rfds; |
| struct timeval tv; |
| |
| info("staring main loop"); |
| /* open UNIX socket */ |
| sock = init_socket(opt_socket_name); |
| if (sock < 0) exit(EXIT_FAILURE); |
| /* init tpm emulator */ |
| mkdirs(opt_storage_file); |
| debug("initializing TPM emulator"); |
| tpm_emulator_init(tpm_startup, tpm_config); |
| /* start command processing */ |
| while (!stopflag) { |
| /* wait for incomming connections */ |
| debug("waiting for connections..."); |
| FD_ZERO(&rfds); |
| FD_SET(sock, &rfds); |
| tv.tv_sec = 10; |
| tv.tv_usec = 0; |
| res = select(sock + 1, &rfds, NULL, NULL, &tv); |
| if (res < 0) { |
| error("select(sock) failed: %s", strerror(errno)); |
| break; |
| } else if (res == 0) { |
| continue; |
| } |
| addr_len = sizeof(addr); |
| fh = accept(sock, (struct sockaddr*)&addr, &addr_len); |
| if (fh < 0) { |
| error("accept() failed: %s", strerror(errno)); |
| continue; |
| } |
| /* receive and handle commands */ |
| in_len = 0; |
| do { |
| debug("waiting for commands..."); |
| FD_ZERO(&rfds); |
| FD_SET(fh, &rfds); |
| tv.tv_sec = TPM_COMMAND_TIMEOUT; |
| tv.tv_usec = 0; |
| res = select(fh + 1, &rfds, NULL, NULL, &tv); |
| if (res < 0) { |
| error("select(fh) failed: %s", strerror(errno)); |
| close(fh); |
| break; |
| } else if (res == 0) { |
| #ifdef TPMD_DISCONNECT_IDLE_CLIENTS |
| info("connection closed due to inactivity"); |
| close(fh); |
| break; |
| #else |
| continue; |
| #endif |
| } |
| in_len = read(fh, in, sizeof(in)); |
| if (in_len > 0) { |
| debug("received %d bytes", in_len); |
| out = NULL; |
| res = tpm_handle_command(in, in_len, &out, &out_len); |
| if (res < 0) { |
| error("tpm_handle_command() failed"); |
| } else { |
| debug("sending %d bytes", out_len); |
| uint32_t len = 0; |
| while (len < out_len) { |
| res = write(fh, &out[len], out_len - len); |
| if (res < 0) { |
| error("write(%d) failed: %s", |
| out_len - len, strerror(errno)); |
| break; |
| } |
| len += res; |
| } |
| tpm_free(out); |
| } |
| } |
| } while (in_len > 0); |
| close(fh); |
| } |
| /* shutdown tpm emulator */ |
| tpm_emulator_shutdown(); |
| /* close socket */ |
| close(sock); |
| unlink(opt_socket_name); |
| info("main loop stopped"); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| openlog(argv[0], 0, LOG_DAEMON); |
| setlogmask(~LOG_MASK(LOG_DEBUG)); |
| syslog(LOG_INFO, "--- separator ---\n"); |
| info("starting TPM Emulator daemon (1.2.%d.%d-%d)", |
| VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD); |
| parse_options(argc, argv); |
| /* switch uid/gid if required */ |
| switch_uid_gid(); |
| /* init signal handlers */ |
| init_signal_handler(); |
| /* unless requested otherwiese, fork and daemonize process */ |
| if (!opt_foreground) daemonize(); |
| /* open random device */ |
| init_random(); |
| /* start main processing loop */ |
| main_loop(); |
| info("stopping TPM Emulator daemon"); |
| close(rand_fh); |
| closelog(); |
| return EXIT_SUCCESS; |
| } |