/* Copyright 2010 Adam Marchetti Copyright 2011-2013 Timothy Pearson <kb9vqf@pearsoncomputing.net> This file is part of tsak, the TDE Secure Attention Key daemon tsak 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 3 of the License, or (at your option) any later version. tsak 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. You should have received a copy of the GNU General Public License along with tsak. If not, see http://www.gnu.org/licenses/. */ #include <stdio.h> #include <stdlib.h> #include <exception> #include <string.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <dirent.h> #include <linux/input.h> #include <linux/uinput.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/select.h> #include <sys/time.h> #include <sys/wait.h> #include <termios.h> #include <signal.h> extern "C" { #include <libudev.h> } #include <libgen.h> using namespace std; #define FIFO_DIR "/tmp/tdesocket-global" #define FIFO_FILE_OUT "/tmp/tdesocket-global/tsak" #define FIFO_LOCKFILE_OUT "/tmp/tdesocket-global/tsak.lock" // WARNING // MAX_KEYBOARDS must be greater than or equal to MAX_INPUT_NODE #define MAX_KEYBOARDS 128 #define MAX_INPUT_NODE 128 #define TestBit(bit, array) (array[(bit) / 8] & (1 << ((bit) % 8))) typedef unsigned char byte; bool mPipeOpen_out = false; int mPipe_fd_out = -1; int mPipe_lockfd_out = -1; char filename[32]; char key_bitmask[(KEY_MAX + 7) / 8]; struct sigaction usr_action; sigset_t block_mask; int keyboard_fd_num; int keyboard_fds[MAX_KEYBOARDS]; int child_pids[MAX_KEYBOARDS]; int child_led_pids[MAX_KEYBOARDS]; int current_keyboard = -1; int devout[MAX_KEYBOARDS]; const char *keycode[256] = { "", "<esc>", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "−", "=", "<backspace>", "<tab>", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\n", "<control>", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "", "<shift>", "\\", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "<shift>", "", "<alt>", " ", "<capslock>", "<f1>", "<f2>", "<f3>", "<f4>", "<f5>", "<f6>", "<f7>", "<f8>", "<f9>", "<f10>", "<numlock>", "<scrolllock>", "", "", "", "", "", "", "", "", "", "", "\\", "f11", "f12", "", "", "", "", "", "", "", "", "<control>", "", "<sysrq>", "", "", "<control>", "", "", "<alt>", "", "", "", "", "", "", "", "", "", "", "<del>", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }; /* returns 1 if bit number i is set, otherwise returns 0 */ int bit_set(size_t i, const byte* a) { return a[i/CHAR_BIT] & (1 << i%CHAR_BIT); } /* exception handling */ struct exit_exception { int c; exit_exception(int c):c(c) { } }; /* signal handler */ void signal_callback_handler(int signum) { // Terminate program throw exit_exception(signum); } /* termination handler */ void tsak_friendly_termination() { int i; if ((current_keyboard >= 0) && (devout[current_keyboard] > 0)) { if (ioctl(devout[current_keyboard],UI_DEV_DESTROY)<0) { fprintf(stderr, "[tsak] Unable to destroy input device with UI_DEV_DESTROY\n"); } else { fprintf(stderr, "[tsak] Device destroyed\n"); } } // Close down all child processes for (i=0; i<MAX_KEYBOARDS; i++) { if (child_pids[i] != 0) { kill(child_pids[i], SIGTERM); } if (child_led_pids[i] != 0) { kill(child_led_pids[i], SIGTERM); } } // Wait for process termination sleep(1); fprintf(stderr, "[tsak] tsak terminated by external request\n"); exit(17); } // -------------------------------------------------------------------------------------- // Useful function from Stack Overflow // http://stackoverflow.com/questions/874134/find-if-string-endswith-another-string-in-c // -------------------------------------------------------------------------------------- /* returns 1 iff str ends with suffix */ int str_ends_with(const char * str, const char * suffix) { if( str == NULL || suffix == NULL ) return 0; size_t str_len = strlen(str); size_t suffix_len = strlen(suffix); if(suffix_len > str_len) return 0; return 0 == strncmp( str + str_len - suffix_len, suffix, suffix_len ); } // -------------------------------------------------------------------------------------- /* * Set a file descriptor to blocking or non-blocking mode. * * @param fd The file descriptor * @param blocking 0:non-blocking mode, 1:blocking mode * * @return 1:success, 0:failure. */ int fd_set_blocking(int fd, int blocking) { /* Save the current flags */ int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { return 0; } if (blocking) { flags &= ~O_NONBLOCK; } else { flags |= O_NONBLOCK; } return fcntl(fd, F_SETFL, flags) != -1; } /* Assign features (supported axes and keys) of the physical input device (devin) * to the virtual input device (devout) */ static void copy_features(int devin, int devout) { byte evtypes[EV_MAX/CHAR_BIT + 1] = {0}; byte codes[KEY_MAX/CHAR_BIT + 1]; unsigned i,code; int op; if (ioctl(devin, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) return; for(i=0;i<EV_MAX;++i) { if (bit_set(i, evtypes)) { switch(i) { case EV_KEY: op = UI_SET_KEYBIT; break; case EV_REL: op = UI_SET_RELBIT; break; case EV_ABS: op = UI_SET_ABSBIT; break; case EV_MSC: op = UI_SET_MSCBIT; break; case EV_LED: op = UI_SET_LEDBIT; break; case EV_SND: op = UI_SET_SNDBIT; break; case EV_SW: op = UI_SET_SWBIT; break; default: op = -1; } } if (op == -1) continue; ioctl(devout, UI_SET_EVBIT, i); memset(codes,0,sizeof(codes)); if (ioctl(devin, EVIOCGBIT(i, sizeof(codes)), codes) >= 0) { for(code=0;code<KEY_MAX;code++) { if (bit_set(code, codes)) ioctl(devout, op, code); } } } } int find_keyboards() { int i, j; int fd; char name[256] = "Unknown"; keyboard_fd_num = 0; for (i=0; i<MAX_KEYBOARDS; i++) { keyboard_fds[i] = 0; } for (i=0; i<MAX_INPUT_NODE; i++) { snprintf(filename, sizeof(filename), "/dev/input/event%d", i); fd = open(filename, O_RDWR|O_SYNC); if (fd >= 0) { ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask); // Ensure that we do not detect our own tsak faked keyboards ioctl (fd, EVIOCGNAME(sizeof(name)), name); if (str_ends_with(name, "+tsak") == 0) { // Do not attempt to use virtual keyboards per Bug 1275 struct input_id input_info; ioctl (fd, EVIOCGID, &input_info); if ((input_info.vendor != 0) && (input_info.product != 0)) { /* We assume that anything that has an alphabetic key in the QWERTYUIOP range in it is the main keyboard. */ for (j = KEY_Q; j <= KEY_P; j++) { if (TestBit(j, key_bitmask)) { keyboard_fds[keyboard_fd_num] = fd; } } } } if (keyboard_fds[keyboard_fd_num] == 0) { close(fd); } else { keyboard_fd_num++; } } } return 0; } void tearDownPipe() { if (mPipeOpen_out == true) { mPipeOpen_out = false; close(mPipe_fd_out); unlink(FIFO_FILE_OUT); } } void tearDownLockingPipe() { close(mPipe_lockfd_out); unlink(FIFO_LOCKFILE_OUT); } bool setFileLock(int fd, bool close_on_failure) { struct flock fl; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1; // Set the exclusive file lock if (fcntl(fd, F_SETLK, &fl) == -1) { close(fd); return false; } return true; } bool checkFileLock() { struct flock fl; fl.l_type = F_WRLCK; /* Test for any lock on any part of file. */ fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; int fd = open(FIFO_LOCKFILE_OUT, O_RDWR | O_NONBLOCK); fcntl(fd, F_GETLK, &fl); /* Overwrites lock structure with preventors. */ if (fd > -1) { if (fl.l_type == F_WRLCK) { return false; } return true; } return true; } bool setupPipe() { /* Create the FIFOs if they do not exist */ umask(0); mkdir(FIFO_DIR,0644); mknod(FIFO_FILE_OUT, S_IFIFO|0600, 0); chmod(FIFO_FILE_OUT, 0600); mPipe_fd_out = open(FIFO_FILE_OUT, O_RDWR | O_NONBLOCK); if (mPipe_fd_out > -1) { mPipeOpen_out = true; } // Set the exclusive file lock return setFileLock(mPipe_fd_out, true); } bool setupLockingPipe(bool writepid) { /* Create the FIFOs as they may not exist */ umask(0); mkdir(FIFO_DIR,0644); mknod(FIFO_LOCKFILE_OUT, 0600, 0); chmod(FIFO_LOCKFILE_OUT, 0600); mPipe_lockfd_out = open(FIFO_LOCKFILE_OUT, O_RDWR | O_NONBLOCK); if (mPipe_lockfd_out > -1) { if (writepid) { // Write my PID to the file pid_t tsakpid = getpid(); char pidstring[1024]; sprintf(pidstring, "%d", tsakpid); write(mPipe_lockfd_out, pidstring, strlen(pidstring)); } // Set the exclusive file lock return setFileLock(mPipe_lockfd_out, true); } return false; } void broadcast_sak() { // Let anyone listening to our interface know that an SAK keypress was received // I highly doubt there are more than 255 VTs active at once... int i; for (i=0;i<255;i++) { if (write(mPipe_fd_out, "SAK\n\r", 6) < 0) { fprintf(stderr, "[tsak] Unable to send SAK signal to clients\n"); } } } void restart_tsak() { int i; fprintf(stderr, "[tsak] Forcibly terminating...\n"); // Close down all child processes for (i=0; i<MAX_KEYBOARDS; i++) { if (child_pids[i] != 0) { kill(child_pids[i], SIGTERM); } if (child_led_pids[i] != 0) { kill(child_led_pids[i], SIGTERM); } } // Wait for child process termination for (i=0; i<MAX_KEYBOARDS; i++) { if (child_pids[i] != 0) { waitpid(child_pids[i], NULL, 0); child_pids[i] = 0; } if (child_led_pids[i] != 0) { waitpid(child_led_pids[i], NULL, 0); child_led_pids[i] = 0; } } // Unset the exclusive file lock if (mPipe_fd_out != -1) { struct flock fl; if (fcntl(mPipe_fd_out, F_UNLCK, &fl) == -1) { fprintf(stderr, "[tsak] Failed to release exclusive pipe lock\n"); } close(mPipe_fd_out); } #if 1 // Restart now // Note that the execl function never returns char me[2048]; int chars = readlink("/proc/self/exe", me, sizeof(me)); me[chars] = 0; me[2047] = 0; execl(me, basename(me), (char*)NULL); #else _exit(0); #endif } class PipeHandler { public: PipeHandler(); ~PipeHandler(); bool active; }; PipeHandler::PipeHandler() { active = false; } PipeHandler::~PipeHandler() { if (active) { tearDownPipe(); tearDownLockingPipe(); } } int main (int argc, char *argv[]) { struct input_event ev[64]; struct input_event event; struct input_event revev; struct uinput_user_dev devinfo={{0},{0}}; int rd; int i; int size = sizeof (struct input_event); char name[256] = "Unknown"; bool ctrl_down = false; bool alt_down = false; bool hide_event = false; bool established = false; bool testrun = false; bool depcheck = false; bool can_proceed; // Ignore SIGPIPE signal(SIGPIPE, SIG_IGN); // Register signal handlers // Register signal and signal handler signal(SIGINT, signal_callback_handler); signal(SIGTERM, signal_callback_handler); set_terminate(tsak_friendly_termination); try { for (i=0; i<MAX_KEYBOARDS; i++) { child_pids[i] = 0; child_led_pids[i] = 0; } if (argc == 2) { if (strcmp(argv[1], "checkactive") == 0) { testrun = true; } if (strcmp(argv[1], "checkdeps") == 0) { depcheck = true; } } if (depcheck == false) { // Check for existing file locks if (!checkFileLock()) { fprintf(stderr, "[tsak] Another instance of this program is already running [1]\n"); return 8; } if (!setupLockingPipe(true)) { fprintf(stderr, "[tsak] Another instance of this program is already running [2]\n"); return 8; } } // Create the output pipe PipeHandler controlpipe; if (depcheck == false) { if (!setupPipe()) { fprintf(stderr, "[tsak] Another instance of this program is already running\n"); return 8; } } if ((testrun == false) && (depcheck == false)) { // fork to background int i=fork(); if (i<0) { return 10; // fork failed } if (i>0) { // Terminate parent controlpipe.active = false; return 0; } } while (1) { if (depcheck == false) { controlpipe.active = true; } if ((getuid ()) != 0) { printf ("[tsak] You are not root! This WILL NOT WORK!\nDO NOT attempt to bypass security restrictions, e.g. by changing keyboard permissions or owner, if you want the SAK system to remain secure...\n"); return 5; } // Find keyboards find_keyboards(); if (keyboard_fd_num == 0) { printf ("[tsak] Could not find any usable keyboard(s)!\n"); if (depcheck == true) { return 50; } // Make sure everyone knows we physically can't detect a SAK // Before we do this we broadcast one so that active dialogs are updated appropriately // Also, we keep watching for a keyboard to be added via a forked child process... broadcast_sak(); if (established) sleep(1); else { int i=fork(); if (i<0) { return 12; // fork failed } if (i>0) { return 4; } sleep(1); restart_tsak(); } } else { fprintf(stderr, "[tsak] Found %d keyboard(s)\n", keyboard_fd_num); can_proceed = true; for (current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) { // Print Device Name ioctl (keyboard_fds[current_keyboard], EVIOCGNAME (sizeof (name)), name); fprintf(stderr, "[tsak] Reading from keyboard: (%s)\n", name); // Create filtered virtual output device devout[current_keyboard]=open("/dev/misc/uinput",O_RDWR|O_NONBLOCK); if (devout[current_keyboard]<0) { devout[current_keyboard]=open("/dev/uinput",O_RDWR|O_NONBLOCK); if (devout[current_keyboard]<0) { perror("open(\"/dev/misc/uinput\")"); } } if (devout[current_keyboard]<0) { can_proceed = false; fprintf(stderr, "[tsak] Unable to open /dev/uinput or /dev/misc/uinput (char device 10:223).\nPossible causes:\n 1) Device node does not exist\n 2) Kernel not compiled with evdev [INPUT_EVDEV] and uinput [INPUT_UINPUT] user level driver support\n 3) Permission denied.\n"); perror("open(\"/dev/uinput\")"); if (established) sleep(1); else return 3; } fd_set_blocking(devout[current_keyboard], true); } if (depcheck == true) { return 0; } if (can_proceed == true) { for (current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) { if(ioctl(keyboard_fds[current_keyboard], EVIOCGRAB, 2) < 0) { close(keyboard_fds[current_keyboard]); fprintf(stderr, "[tsak] Failed to grab exclusive input device lock\n"); if (established) { sleep(1); } else { return 1; } } else { ioctl(keyboard_fds[current_keyboard], EVIOCGNAME(UINPUT_MAX_NAME_SIZE), devinfo.name); strncat(devinfo.name, "+tsak", UINPUT_MAX_NAME_SIZE-1); fprintf(stderr, "[tsak] %s\n", devinfo.name); ioctl(keyboard_fds[current_keyboard], EVIOCGID, &devinfo.id); copy_features(keyboard_fds[current_keyboard], devout[current_keyboard]); if (write(devout[current_keyboard],&devinfo,sizeof(devinfo)) < 0) { fprintf(stderr, "[tsak] Unable to write to output device\n"); } if (ioctl(devout[current_keyboard],UI_DEV_CREATE)<0) { fprintf(stderr, "[tsak] Unable to create input device with UI_DEV_CREATE\n"); if (established) { sleep(1); } else { return 2; } } else { fprintf(stderr, "[tsak] Device created.\n"); if (established == false) { int i=fork(); if (i<0) return 9; // fork failed if (i>0) { child_pids[current_keyboard] = i; int i=fork(); if (i<0) return 9; // fork failed if (i>0) { child_led_pids[current_keyboard] = i; continue; } if (testrun == true) { return 0; } while (1) { // Replicate LED events from the virtual keyboard to the physical keyboard int rrd = read(devout[current_keyboard], &revev, size); if (rrd >= size) { if ((revev.type == EV_LED) || (revev.type == EV_MSC)) { if (write(keyboard_fds[current_keyboard], &revev, sizeof(revev)) < 0) { fprintf(stderr, "[tsak] Unable to replicate LED event\n"); } } } } return 0; } setupLockingPipe(false); } established = true; if (testrun == true) { return 0; } while (1) { if ((rd = read(keyboard_fds[current_keyboard], ev, size)) < size) { fprintf(stderr, "[tsak] Read failed.\n"); if (ioctl(devout[current_keyboard],UI_DEV_DESTROY)<0) { fprintf(stderr, "[tsak] Unable to destroy input device with UI_DEV_DESTROY\n"); return 13; } else { fprintf(stderr, "[tsak] Device destroyed.\n"); } return 14; } if (ev[0].value == 0 && ev[0].type == 1) { // Read the key release event if (keycode[(ev[0].code)]) { if (strcmp(keycode[(ev[0].code)], "<control>") == 0) ctrl_down = false; if (strcmp(keycode[(ev[0].code)], "<alt>") == 0) alt_down = false; } } if (ev[0].value == 1 && ev[0].type == 1) { // Read the key press event if (keycode[(ev[0].code)]) { if (strcmp(keycode[(ev[0].code)], "<control>") == 0) ctrl_down = true; if (strcmp(keycode[(ev[0].code)], "<alt>") == 0) alt_down = true; } } hide_event = false; if (ev[0].value == 1 && ev[0].type == 1) { // Read the key press event if (keycode[(ev[0].code)]) { if (alt_down && ctrl_down && (strcmp(keycode[(ev[0].code)], "<del>") == 0)) { hide_event = true; } } } if ((hide_event == false) && (ev[0].type != EV_LED) && (ev[0].type != EV_MSC)) { // Pass the event on... event = ev[0]; if (write(devout[current_keyboard], &event, sizeof(event)) < 0) { fprintf(stderr, "[tsak] Unable to replicate keyboard event!\n"); } } if (hide_event == true) { // Let anyone listening to our interface know that an SAK keypress was received broadcast_sak(); } } } } } // Close all keyboard file descriptors; we don't need them in this process and they can end up dangling/locked during forced restart for (int current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) { close(keyboard_fds[current_keyboard]); keyboard_fds[current_keyboard] = 0; } keyboard_fd_num = 0; if (testrun == true) { return 0; } // Prevent multiple process instances from starting setupLockingPipe(true); // Wait a little bit so that udev hotplug can stabilize before we start monitoring sleep(1); fprintf(stderr, "[tsak] Hotplug monitoring process started\n"); // Monitor for hotplugged keyboards int j; int hotplug_fd; bool is_new_keyboard; struct udev *udev; struct udev_device *dev; struct udev_monitor *mon; // Create the udev object udev = udev_new(); if (!udev) { fprintf(stderr, "[tsak] Cannot connect to udev interface\n"); return 11; } // Set up a udev monitor to monitor input devices mon = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL); udev_monitor_enable_receiving(mon); while (1) { // Watch for input from the monitoring process fd_set readfds; FD_ZERO(&readfds); FD_SET(udev_monitor_get_fd(mon), &readfds); int fdcount = select(udev_monitor_get_fd(mon)+1, &readfds, NULL, NULL, NULL); if (fdcount < 0) { if (errno == EINTR) { fprintf(stderr, "[tsak] Signal caught in hotplug monitoring process; ignoring\n"); } else { fprintf(stderr, "[tsak] Select failed on udev file descriptor in hotplug monitoring process\n"); } usleep(1000); continue; } dev = udev_monitor_receive_device(mon); if (dev) { // If a keyboard was removed we need to restart... if (strcmp(udev_device_get_action(dev), "remove") == 0) { udev_device_unref(dev); udev_unref(udev); restart_tsak(); } is_new_keyboard = false; snprintf(filename,sizeof(filename), "%s", udev_device_get_devnode(dev)); udev_device_unref(dev); // Print name of keyboard hotplug_fd = open(filename, O_RDWR|O_SYNC); ioctl(hotplug_fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask); /* We assume that anything that has an alphabetic key in the QWERTYUIOP range in it is the main keyboard. */ for (j = KEY_Q; j <= KEY_P; j++) { if (TestBit(j, key_bitmask)) { is_new_keyboard = true; } } ioctl (hotplug_fd, EVIOCGNAME (sizeof (name)), name); close(hotplug_fd); // Ensure that we do not detect our own tsak faked keyboards if (str_ends_with(name, "+tsak") == 1) { is_new_keyboard = false; } // If a keyboard was added we need to restart... if (is_new_keyboard == true) { fprintf(stderr, "[tsak] Hotplugged new keyboard: (%s)\n", name); udev_unref(udev); restart_tsak(); } } else { fprintf(stderr, "[tsak] No device from receive_device(). A udev error has occurred; terminating hotplug monitoring process.\n"); return 12; } } udev_unref(udev); fprintf(stderr, "[tsak] Hotplug monitoring process terminated\n"); } } } } catch(exit_exception& e) { tsak_friendly_termination(); } return 6; }