/* * heartbeat++.c -- flash a Lock LED (keyboard) in an hearthbeat fashion, usually without options. * * * The flash code is from the book LNUX DEVICE DRIVER (O'Reilly). * Works even with USB-Keyboards and also with PS/2-Keyboards (even with both at the same time, but * keyboard detection is made only for PS/2). * With an PS/2-USB adapter an USB-Keyboard can be used as an PS/2 keyboard. * * 2005: Changed to 50 % dute-cute and 1/2 Hz at a relative loadaverage (load / cpus (cores)) of 0, 1 Hz at 1, etc.. * * 2007: Added Keylogger detection via controll port reading, because ioctls, udevmonitor --env * and reading in /proc are not sufficient. * * 2008: Added higher time resolution of ns because with a dozen of Cherry, Logitech, Microsoft and noname keyboards * and dozens of thousends of hours usage i have seen no false alarm but with a Steelseries 7G i get about two false * alarms per day, when the load average is about 10. The steelseries keyboard is sleeping about half a second. * It seems the 7G is a USB keyboard with an internal USB to PS/2 adapter which adds extra latency. * I also added a loop counter. * * 2009: Added better integer rounding (mc_POS_DIV), addedd a minimal blink/scan frequency. * Added blinking with the num lock led and opposite phase after the first keyboard disconnect. * New frequency scaling: 1/2 Hz at load 0, 2 Hz at a rel. load (load / cpus (cores)) of 1, 3,5 Hz at 2, 5 Hz at 3, etc.. * * 2010: Changed the scanning for the core count to the shorter string "processor ", because the old format failed under Debian 5.0.4. * * TODO: Signal-Handler, Extension for USB keyboards (maybe udevmonitor --env, input-tools (http://dl.bytesex.org/cvs-snapshots/) or input-layers (http://linuxconsole.sourceforge.net/)), hwinfo ... * * * Dr. Rolf Freitag * Version 2010-08-25 Example usage with two lines in /etc/inittab, after compiling, e. g. wia gcc -Wall -O3 -o heartbeat++ heartbeat++.c and copying via cp -i heartbeat++ /sbin/ : # keyboard led heartbeat++ 13:S12345:respawn:/usr/bin/nice -n+19 /sbin/heartbeat++ --- Citation from the flash code source license: This source code can be redistributed in source or binary form so long as an acknowledgment appears in derived source files. The citation should list that the code comes from "Linux Device Drivers" by Alessandro Rubini, published by O'Reilly & Associates. This code is under copyright and cannot be included in any other book, publication, or educational product without permission from O'Reilly & Associates. No warranty is attached; we cannot take responsibility for errors or fitness for use. Enjoy /alessandro */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* or */ #include // version 2013-06-23 // count down start value: if this number of timeouts is reached in a row the alarm starts. Unit: Delays (default 200 ms). #define COUNT_DOWN_START 3 // debugging (or not) //#define DEBUG // scale the heartbeat by the cpu (core) number, but ususally that does not make sense //#define WITH_CPU_SCALING // Dividing of (unsigned) integer numbers with good rounding (with minimized quantisation error). // Integer division (of positive numbers) ignores everything behind the point while good rounding // rounds up >= .5 and rounds down < .5. # define mc_POS_DIV(a, b) ( (a)/(b) + ( ( (a) % (b) >= (b)/2 ) ? 1 : 0 ) ) int main (int argc, char **argv) { unsigned char led; unsigned char chosenled = 1; // default: scroll lock unsigned char hearth[] = { 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; // heartbeat pattern unsigned int udelay = 200000; // 1/5 s default delay int udelay_tmp = 0; int load = 0, i_cpus = 0, line_counter = 0, i_ac = 0; char *prgname = argv[0]; char string[128]; FILE *f = NULL; char ca_in_line[123 + 1] = { 0 }; uint8_t b_status = 0, b_old_status = 0; // Keyboard status: Start with present keyboard (no timeout flag set). uint64_t u64_lc = 0; // loop counter uint64_t u64_dc = 0; // disconnect counter int down_counter = COUNT_DOWN_START; // counter for count down int old_down_counter = down_counter; if (argc > 1 && isdigit (argv[1][0])) { /* the time delay */ udelay_tmp = 1000 * strtol(argv[1], (char **)NULL, 0); if (udelay < 1000) { (void)fprintf (stderr, "%s: delay too short; using default (200 ms)\n", prgname); } else { udelay = udelay_tmp; argv++; argc--; } } (void)nice (19); /* in case it succeeds... */ (void)iopl (3); /* allows access to all I/O-Ports, ioperm doesen't work above the 0x3ff-Limit e. g. at PCI-Cards */ // estimate number of cpus/cores f = fopen ("/proc/cpuinfo", "r"); for (line_counter = 0; (line_counter < 123456) and (NULL != fgets (ca_in_line, 123, f)); line_counter++) { i_ac = sscanf (ca_in_line, "processor %s", string); if (1 == i_ac) i_cpus++; } if (i_cpus < 1) i_cpus = 1; (void)fclose (f); udelay *= 100; /* prepare for a later division, for percent calculation */ /* if (argc > 1 && strnlen (argv[1], 123) == 1) { argv++, argc--; if (tolower (argv[0][0]) == 's') chosenled = 1; // scroll lock else if (tolower (argv[0][0]) == 'n') chosenled = 2; // num lock else if (tolower (argv[0][0]) == 'c') chosenled = 4; // caps lock else { fprintf (stderr, "%s: unknown led '%s'\n", prgname, argv[1]); argc++; } } */ if (argc > 1) { (void)fprintf (stderr, "%s: usage \"%s [delay ms]\"\n", prgname, prgname); exit (1); } // ok, now do your loop for (u64_lc=0;;u64_lc++) { int consolefd = open ("/dev/tty0", O_RDONLY); int i = 0; f = fopen ("/proc/loadavg", "r"); if (f) { (void)fscanf (f, "%d.%d", &load, &i); (void)fclose (f); } else { load = i = 0; } // scale load by 100 to get 100 at a load of 1 load = load * 100 + i; #ifdef DEBUG (void)printf ("load (before div and scaling): %d, i_cpus: %d\n", load, i_cpus); #endif #ifdef WITH_CPU_SCALING // scale load by number of cpus/cores for the average load of one core load = mc_POS_DIV(load, i_cpus); #endif // add offset 100 to start at 1/2 Hz (one second on, one off) and avoid division by zero load += 100; #ifdef DEBUG (void)printf ("waiting intervall %d microseconds, udelay: %d, load with scaling, offset etc: %d\n", mc_POS_DIV(udelay,load), udelay, load); #endif for (i = 0; i < sizeof (hearth) / sizeof (hearth[0]); i++) { if (u64_dc) // maybe compromised system { if (ioctl (consolefd, KDGETLED, &led) || ioctl (consolefd, KDSETLED, (led & chosenled) | (hearth[i] ? 2 : 4))) // blink with Num and Caps { (void)fprintf (stderr, "%s: ioctl(): %s\n", prgname, strerror (errno)); exit (-2); } } else { if (ioctl (consolefd, KDGETLED, &led) || ioctl (consolefd, KDSETLED, (led & ~chosenled) | (chosenled * hearth[i]))) // blink with Scroll { (void)fprintf (stderr, "%s: ioctl(): %s\n", prgname, strerror (errno)); exit (-1); } } #ifdef DEBUG (void)printf ("led: %i, hearth[i]: %i, (led & ~chosenled) | (chosenled * hearth[i]): %i, (led & chosenled) | (hearth[i] ? 2 : 4): %i\n", (int)led, (int)hearth[i], (int)((led & ~chosenled) | (chosenled * hearth[i])), (int)((led & chosenled) | (hearth[i] ? 2 : 4))); #endif usleep (mc_POS_DIV(udelay,load)); // check if the keyboard is present: Look for the timeout flag 0x40 at port 0x64 b_status = inb (0x64); // filter the timeout flag 0x40 (BIT6) b_status and_eq 0x40; if (b_status) // keyboard timeout down_counter -= (down_counter > 0) ? 1 : 0; // saturated substraction, count down else down_counter = COUNT_DOWN_START; // not timeout: reset the counter if (0 >= down_counter) // count down reached zero, alarm { // usually \a does not work; it's only a one-line try (void)printf ("\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a"); // beep via the speaker (install beep first) (void)system ("beep -e /dev/input/by-path/platform-pcspkr-event-spkr -l 432"); // beep via the (first) soundcard (void)system ("dd if=/dev/urandom of=/dev/dsp bs=1 count=4321"); } if (b_status and (0 >= down_counter) and old_down_counter ) // keyboard hangup (vanished and count down reached zero, alarm) { u64_dc++; // increase disconnect counter (void)system ("date --rfc-3339=ns | mail -s \"The Keyboard has been removed!\" root@localhost"); (void)system ("echo -n \"-\" >> /root/heartbeat++.log; date --rfc-3339=ns >> /root/heartbeat++.log"); (void)printf ("Keyboard status change: The (PS/2) Keyboard has been removed! loop counter %llu\n", (long long unsigned int)u64_lc); // usually \a does not work; it's only a one-line try (void)printf ("\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a"); // beep via the speaker (install beep first) (void)system ("beep -e /dev/input/by-path/platform-pcspkr-event-spkr -l 432"); // beep via the (first) soundcard (void)system ("dd if=/dev/urandom of=/dev/dsp bs=1 count=4321"); } if (b_status != b_old_status) // status change { if ( (not b_status) and (0 >= old_down_counter) ) // keyboard has gone online { (void)system ("date --rfc-3339=ns | mail -s \"The Keyboard removal has been ended!\" root@localhost"); (void)system ("echo -n \"+\" >> /root/heartbeat++.log; date --rfc-3339=ns >> /root/heartbeat++.log"); (void)printf ("Keyboard status change: The (PS/2) Keyboard removal has been ended! loop counter %llu\n", (long long unsigned int)u64_lc); } } b_old_status = b_status; // store the status of this round old_down_counter = down_counter; // store the down counter of this round } // for (i i = close (consolefd); } // endless loop iopl (0); /* release region */ exit (0); /* never happen */ } // main