diff --git a/README.md b/README.md index 6e3714b..beadac6 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,12 @@ Jelling is a simple daemon for Linux which receives OTP tokens from FreeOTP. # Test Results -| Device | OS | Adv. | Connect | Discovery | Pair | GATT | -| ---------- | ---------------- | ---- | ------- | --------- | ---- | ---- | -| iPhone 6+ | iOS 11.2 | ✔ | ✔ | ✔ | ✔ | ✔ | -| Nexus 5x | LineageOS 14.1 | ✔ | ✔ | ✘ | ✘ | ✘ | -| Pixel | Android 8.1 beta | ✔ | ✘ | ✘ | ✘ | ✘ | +| Device | OS | Adv. | Connect | Discovery | Pair | GATT | +| :----------: |:---------------- |:----:|:-------:|:---------:|:----:|:----:| +| iPhone 6+ | iOS 11.2 | ✔ | ✔ | ✔ | ✔ | ✔ | +| Redmi Note 8 | Arrow OS 13.1 | ✔ | ✔ | ✔ | ✔ | ✔ | +| Nexus 5x | LineageOS 14.1 | ✔ | ✔ | ✘ | ✘ | ✘ | +| Pixel | Android 8.1 beta | ✔ | ✘ | ✘ | ✘ | ✘ | # How to Test diff --git a/jelling.c b/jelling.c index 4a13ab2..7ca0471 100644 --- a/jelling.c +++ b/jelling.c @@ -21,6 +21,9 @@ #include #include #include +#include +#include + #include #include @@ -30,6 +33,7 @@ #include #include +#include #define MAN_PATH "/" #define ADV_PATH "/adv" @@ -56,8 +60,65 @@ #define SCOPED(type) \ __attribute__((cleanup(type ## _cleanup))) type +#if ENABLE_DEBUG +#define DEBUG(format, ...) LOG(DEBUG, format, ## __VA_ARGS__) +#else +#define DEBUG(format, ...) +#endif + +#define INFO(format, ...) LOG(INFO, format, ## __VA_ARGS__) +#define WARN(format, ...) LOG(WARN, format, ## __VA_ARGS__) +#define ERROR(format, ...) LOG(ERROR, format, ## __VA_ARGS__) +#define ABORT(ERRNUM, format, ...) do { \ + LOG(FATAL, format ": %s", ## __VA_ARGS__, strerror(ERRNUM));\ + exit(EXIT_FAILURE);\ +} while(0) + +typedef enum { + DEBUG = 0, + INFO = 5, + WARN = 10, + ERROR = 15, + FATAL = 20, +} LogLevel; + typedef int uinput; +static const char *strloglvl(LogLevel lvl) +{ + if (lvl >= FATAL) return "FATAL"; + if (lvl >= ERROR) return "ERROR"; + if (lvl >= WARN) return "WARN"; + if (lvl >= INFO) return "INFO"; + if (lvl >= DEBUG) return "DEBUG"; + return "UNKNOWN"; +} + +static void LOG(LogLevel level, const char *fmt, ...) +{ + va_list ap; + time_t now = time(NULL); + struct tm tm; + + localtime_r(&now, &tm); + + fprintf(stderr, "%04d-%02d-%02d %02d:%02d:%02d [%s] ", + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + strloglvl(level)); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, "\n"); + fflush(stderr); +} + static void uinput_cleanup(uinput *i) { @@ -68,6 +129,15 @@ uinput_cleanup(uinput *i) close(*i); } +static void +sd_event_cleanup(sd_event **loop) +{ + if (loop == NULL || *loop == NULL) + return; + + sd_event_unref(*loop); +} + static void sd_bus_message_cleanup(sd_bus_message **msg) { @@ -90,17 +160,28 @@ static inline uint16_t char2key(uint8_t c) { switch (c) { - case '0': return KEY_0; - case '1': return KEY_1; - case '2': return KEY_2; - case '3': return KEY_3; - case '4': return KEY_4; - case '5': return KEY_5; - case '6': return KEY_6; - case '7': return KEY_7; - case '8': return KEY_8; - case '9': return KEY_9; - default: return KEY_UNKNOWN; + case '0': + return KEY_0; + case '1': + return KEY_1; + case '2': + return KEY_2; + case '3': + return KEY_3; + case '4': + return KEY_4; + case '5': + return KEY_5; + case '6': + return KEY_6; + case '7': + return KEY_7; + case '8': + return KEY_8; + case '9': + return KEY_9; + default: + return KEY_UNKNOWN; } } @@ -159,8 +240,8 @@ static int chr_notsup(sd_bus_message *m, void *misc, sd_bus_error *err) { return sd_bus_error_set( - err, "org.bluez.Error.NotSupported", "Not supported" - ); + err, "org.bluez.Error.NotSupported", "Not supported" + ); } static int @@ -198,30 +279,35 @@ chr_writevalue(sd_bus_message *m, void *misc, sd_bus_error *err) return r; if (size == 0 || size > 32) { + WARN("Invalid write length: %zu", size); return sd_bus_reply_method_errorf( - m, "org.bluez.Error.InvalidValueLength", "Invalid value length" - ); + m, "org.bluez.Error.InvalidValueLength", "Invalid value length" + ); } /* Validate input. */ for (size_t i = 0; i < size; i++) { if (char2key(bytes[i]) == KEY_UNKNOWN) { + DEBUG("Unknown KeyCode: code %u", bytes[i]); return sd_bus_reply_method_errorf( - m, "org.bluez.Error.NotPermitted", "Invalid value" - ); + m, "org.bluez.Error.NotPermitted", "Invalid value" + ); } } - for (size_t i = 0; i < size && r >= 0; i++) + for (size_t i = 0; i < size && r >= 0; i++) { + DEBUG("Sending key: %c -> code %u", bytes[i], char2key(bytes[i])); r = event(*input, char2key(bytes[i]), true); + } if (r >= 0) r = event(*input, KEY_ENTER, true); if (r >= 0) r = event(*input, KEY_UNKNOWN, false); if (r < 0) { + DEBUG("Write failed"); return sd_bus_reply_method_errorf( - m, "org.bluez.Error.Failed", "Write failed" - ); + m, "org.bluez.Error.Failed", "Write failed" + ); } return sd_bus_reply_method_return(m, ""); @@ -266,8 +352,8 @@ static int on_reply(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { if (sd_bus_error_is_set(ret_error)) - fprintf(stderr, "Error registering: %s: %s\n", - ret_error->name, ret_error->message); + ERROR("Error registering: %s: %s\n", + ret_error->name, ret_error->message); return 0; } @@ -311,6 +397,7 @@ on_bt_iface(sd_bus_message *m, void *bus, sd_bus_error *ret_error) "oa{sv}", MAN_PATH, 0); if (r < 0) return r; + DEBUG("Registering object %s with BlueZ on %s", obj, iface); } if (strcmp(iface, "org.bluez.LEAdvertisingManager1") == 0) { @@ -319,6 +406,7 @@ on_bt_iface(sd_bus_message *m, void *bus, sd_bus_error *ret_error) "oa{sv}", ADV_PATH, 0); if (r < 0) return r; + DEBUG("Registering object %s with BlueZ on %s", obj, iface); } } if (r < 0) @@ -359,19 +447,20 @@ setup_uinput(uinput *input) if (fd < 0) { if (errno == ENOENT) continue; - error(EXIT_FAILURE, errno, "Error opening %s", devices[i]); + ABORT(errno, "Error opening %s", devices[i]); } } if (fd < 0) - error(EXIT_FAILURE, errno, "Error finding uevent"); + ABORT(errno, "Error finding uevent"); + DEBUG("uevent opened at fd %d", fd); r = ioctl(fd, UI_SET_EVBIT, EV_KEY); if (r < 0) - error(EXIT_FAILURE, errno, "Error setting uinput KEY type"); + ABORT(errno, "Error setting uinput KEY type"); r = ioctl(fd, UI_SET_EVBIT, EV_SYN); if (r < 0) - error(EXIT_FAILURE, errno, "Error setting uinput SYN type"); + ABORT(errno, "Error setting uinput SYN type"); for (uint8_t c = 0; c < UINT8_MAX; c++) { uint16_t k = char2key(c); @@ -383,16 +472,16 @@ setup_uinput(uinput *input) r = ioctl(fd, UI_SET_KEYBIT, k); if (r < 0) - error(EXIT_FAILURE, errno, "Error setting uinput keybit: %c", c); + ABORT(errno, "Error setting uinput keybit: %c", c); } r = write(fd, &dev, sizeof(dev)); if (r < 0) - error(EXIT_FAILURE, errno, "Error writing uinput device description"); + ABORT(errno, "Error writing uinput device description"); r = ioctl(fd, UI_DEV_CREATE); if (r < 0) - error(EXIT_FAILURE, errno, "Error creating uinput device"); + ABORT(errno, "Error creating uinput device"); *input = fd; fd = -1; @@ -405,25 +494,25 @@ setup_objects(sd_bus *bus, uinput *i) r = sd_bus_add_object_manager(bus, NULL, MAN_PATH); if (r < 0) - error(EXIT_FAILURE, -r, "Error adding object manager"); + ABORT(-r, "Error adding object manager"); r = sd_bus_add_object_vtable(bus, NULL, ADV_PATH, "org.bluez.LEAdvertisement1", adv_vtable, i); if (r < 0) - error(EXIT_FAILURE, -r, "Error creating advertisement"); + ABORT(-r, "Error creating advertisement"); r = sd_bus_add_object_vtable(bus, NULL, SVC_PATH, "org.bluez.GattService1", svc_vtable, i); if (r < 0) - error(EXIT_FAILURE, -r, "Error creating service"); + ABORT(-r, "Error creating service"); r = sd_bus_add_object_vtable(bus, NULL, CHR_PATH, "org.bluez.GattCharacteristic1", chr_vtable, i); if (r < 0) - error(EXIT_FAILURE, -r, "Error creating characteristic"); + ABORT(-r, "Error creating characteristic"); } static void @@ -434,71 +523,92 @@ setup_registration(sd_bus *bus) r = sd_bus_add_match(bus, NULL, MATCH, on_bt_iface, bus); if (r < 0) - error(EXIT_FAILURE, -r, "Error registering for bluetooth interfaces"); + ABORT(-r, "Error registering for bluetooth interfaces"); r = sd_bus_call_method(bus, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", NULL, &msg, ""); if (r < 0) - error(EXIT_FAILURE, -r, "Error calling bluez ObjectManager"); + ABORT(-r, "Error calling bluez ObjectManager"); r = sd_bus_message_enter_container(msg, 'a', "{oa{sa{sv}}}"); if (r < 0) - error(EXIT_FAILURE, -r, "Error parsing bluez results"); + ABORT(-r, "Error parsing bluez results"); while ((r = sd_bus_message_enter_container(msg, 'e', "oa{sa{sv}}")) > 0) { r = on_bt_iface(msg, bus, NULL); if (r < 0) - error(EXIT_FAILURE, -r, "Error parsing bluez results"); + ABORT(-r, "Error parsing bluez results"); r = sd_bus_message_exit_container(msg); if (r < 0) - error(EXIT_FAILURE, -r, "Error parsing bluez results"); + ABORT(-r, "Error parsing bluez results"); } if (r < 0) - error(EXIT_FAILURE, -r, "Error parsing bluez results"); + ABORT(-r, "Error parsing bluez results"); r = sd_bus_message_exit_container(msg); if (r < 0) - error(EXIT_FAILURE, -r, "Error parsing bluez results"); + ABORT(-r, "Error parsing bluez results"); } -static void -on_signal(int sig) +static int +on_signal(sd_event_source* src, const struct signalfd_siginfo* sig, void *loop) { + DEBUG("Signal %d caught", sig->ssi_signo); + return 0; +} + +static int +on_term(sd_event_source* src, const struct signalfd_siginfo* sig, void *loop) +{ + DEBUG("Termination signal %d received, exiting loop", sig->ssi_signo); + sd_event_exit((sd_event*)loop, 0); + return 0; } int main(int argc, char *argv[]) { + SCOPED(sd_event) *loop = NULL; SCOPED(sd_bus) *bus = NULL; SCOPED(uinput) i = -1; int r; - signal(SIGHUP, on_signal); - signal(SIGINT, on_signal); - signal(SIGPIPE, on_signal); - signal(SIGTERM, on_signal); - signal(SIGUSR1, on_signal); - signal(SIGUSR2, on_signal); + r = sd_event_default(&loop); + if (r < 0) + ABORT(-r, "Error creating event loop"); + + sd_event_add_signal(loop, NULL, SIGHUP | SD_EVENT_SIGNAL_PROCMASK, on_signal, loop); + sd_event_add_signal(loop, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_term, loop); + sd_event_add_signal(loop, NULL, SIGPIPE| SD_EVENT_SIGNAL_PROCMASK, on_signal, loop); + sd_event_add_signal(loop, NULL, SIGTERM| SD_EVENT_SIGNAL_PROCMASK, on_term, loop); + sd_event_add_signal(loop, NULL, SIGUSR1| SD_EVENT_SIGNAL_PROCMASK, on_signal, loop); + sd_event_add_signal(loop, NULL, SIGUSR2| SD_EVENT_SIGNAL_PROCMASK, on_signal, loop); r = sd_bus_default_system(&bus); if (r < 0) - error(EXIT_FAILURE, -r, "Error connecting to system bus"); + ABORT(-r, "Error connecting to system bus"); + +#if ENABLE_DEBUG + r = sd_bus_request_name(bus, "org.fedorahosted.freeotp.jelling", 0); + if (r < 0) + ABORT(-r, "Error claiming busname"); +#endif + + r = sd_bus_attach_event(bus, loop, 0); + if (r < 0) + ABORT(-r, "Error attaching bus to event loop"); setup_uinput(&i); setup_objects(bus, &i); setup_registration(bus); - while ((r = sd_bus_wait(bus, (uint64_t) -1)) >= 0) { - while ((r = sd_bus_process(bus, NULL)) > 0) - continue; - if (r < 0) - error(EXIT_FAILURE, -r, "Error processing bus"); - } - if (r < 0 && r != -EINTR) - error(EXIT_FAILURE, -r, "Error waiting on bus"); + DEBUG("Entering main event loop"); + r = sd_event_loop(loop); + if (r < 0) + ABORT(-r, "Error running event loop"); return EXIT_SUCCESS; } diff --git a/jelling.service.in b/jelling.service.in index e8b2b70..fa69be2 100644 --- a/jelling.service.in +++ b/jelling.service.in @@ -8,3 +8,4 @@ ExecStart=@libexecdir@/jelling [Install] WantedBy=multi-user.target +WantedBy=bluetooth.service diff --git a/meson.build b/meson.build index ed6860f..6d4529e 100644 --- a/meson.build +++ b/meson.build @@ -17,19 +17,7 @@ configure_file( configuration: config, install_dir: unitdir ) - -install_data( - sources: 'jelling.conf', - install_dir: modsdir -) - -executable( - 'jelling', - 'jelling.c', - install_dir : libexecdir, - dependencies: libsystemd, - install: true, - c_args: [ +c_args = [ '-Wall', '-Wextra', '-Werror', @@ -47,5 +35,19 @@ executable( '-Wunused-function', '-Wno-missing-field-initializers', '-Wno-unused-parameter', - ] +# '-DENABLE_DEBUG', +] + +install_data( + sources: 'jelling.conf', + install_dir: modsdir +) + +executable( + 'jelling', + 'jelling.c', + install_dir : libexecdir, + dependencies: libsystemd, + install: true, + c_args: c_args )