commit c44292b744e852c31116290d9b920b17cf24a823 Author: Seán Healy Date: Thu Feb 19 17:45:10 2026 +0000 Initial commit. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3526bf7 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +CC=gcc +CFLAGS=-Wall -Wextra -O2 +LDFLAGS= +LDLIBS=-lusb-1.0 + +TARGETS=desk_reminder scan + +all: $(TARGETS) + +desk_reminder: desk_reminder.c + $(CC) $(CFLAGS) desk_reminder.c -o desk_reminder $(LDLIBS) + +scan: scan.c + $(CC) $(CFLAGS) scan.c -o scan -lpcsclite + +clean: + rm -f $(TARGETS) + +.PHONY: all clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6eda05 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# desk-reminder + +Simple C program that listens for events from the Alcor Micro AU9540 USB smartcard reader. + +- USB device: `058f:9540` (Alcor Micro Corp. AU9540 Smartcard Reader) +- Implementation: raw USB using `libusb-1.0` + +The program opens the reader by VID/PID, locates the first interrupt IN endpoint, and prints any data received (typically slot change / card insertion or removal notifications). It does **not** implement full CCID or smartcard protocols, but is useful for observing low-level traffic. + +## Build + +On Debian/Ubuntu and similar, install the development package: + +```sh +sudo apt-get update +sudo apt-get install libusb-1.0-0-dev +``` + +Then build the program: + +```sh +cd desk-reminder +make +``` + +This produces a `desk_reminder` binary. + +## Run + +You may need root or appropriate udev rules to access USB devices directly: + +```sh +cd desk-reminder +sudo ./desk_reminder +``` + +Expected behavior: + +- The program prints which bus/device it opened. +- It reports which interrupt IN endpoint it is listening on. +- When the AU9540 reports an event (for example, card insertion/removal), the program prints the raw bytes received. + +Press `Ctrl+C` to stop the program. + +## Notes + +For higher-level smartcard operations (APDU exchange, PIN verification, etc.), you would typically use PC/SC (`pcscd`, `libpcsclite`) instead of talking to the reader via raw USB. This example focuses on listening to the USB interrupt endpoint. diff --git a/desk_reminder.c b/desk_reminder.c new file mode 100644 index 0000000..3c1f546 --- /dev/null +++ b/desk_reminder.c @@ -0,0 +1,229 @@ +// Simple listener for Alcor AU9540 smartcard reader using libusb-1.0 +// Device: Bus 001 Device 002: ID 058f:9540 Alcor Micro Corp. AU9540 Smartcard Reader +// +// This program opens the USB device by VID/PID, finds the first +// interrupt IN endpoint, and listens for slot change / card events. +// It does not implement full CCID or smartcard protocols; it just +// prints raw bytes received from the interrupt endpoint. + +#include +#include +#include +#include + +#include + +// Target USB device: IC Reader (Bus 001 Device 011: ID ffff:0035) +#define VENDOR_ID 0xffff +#define PRODUCT_ID 0x0035 + +static volatile int keep_running = 1; + +static void handle_signal(int signum) +{ + (void)signum; + keep_running = 0; +} + +struct listener_context { + libusb_device_handle *handle; + unsigned char endpoint_addr; +}; + +static void LIBUSB_CALL transfer_callback(struct libusb_transfer *transfer) +{ + struct listener_context *ctx = (struct listener_context *)transfer->user_data; + + if (transfer->status == LIBUSB_TRANSFER_COMPLETED) { + printf("[event] %d bytes: ", transfer->actual_length); + for (int i = 0; i < transfer->actual_length; ++i) { + printf("%02x ", transfer->buffer[i]); + } + printf("\n"); + fflush(stdout); + + if (keep_running) { + int rc = libusb_submit_transfer(transfer); + if (rc != 0) { + fprintf(stderr, "Failed to resubmit transfer: %s\n", libusb_error_name(rc)); + libusb_free_transfer(transfer); + } + } else { + libusb_free_transfer(transfer); + } + } else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { + libusb_free_transfer(transfer); + } else { + fprintf(stderr, "Transfer error: status=%d (%s)\n", + transfer->status, libusb_error_name(transfer->status)); + libusb_free_transfer(transfer); + } + + (void)ctx; // currently unused, kept for future extensions +} + +static int find_interrupt_in_endpoint(libusb_device_handle *handle, + unsigned char *endpoint_addr_out) +{ + libusb_device *dev = libusb_get_device(handle); + struct libusb_config_descriptor *config = NULL; + int rc = libusb_get_active_config_descriptor(dev, &config); + if (rc != 0) { + fprintf(stderr, "Failed to get config descriptor: %s\n", libusb_error_name(rc)); + return -1; + } + + int found = 0; + for (int i = 0; i < config->bNumInterfaces && !found; ++i) { + const struct libusb_interface *interface = &config->interface[i]; + for (int j = 0; j < interface->num_altsetting && !found; ++j) { + const struct libusb_interface_descriptor *alt = &interface->altsetting[j]; + for (int k = 0; k < alt->bNumEndpoints && !found; ++k) { + const struct libusb_endpoint_descriptor *ep = &alt->endpoint[k]; + if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_INTERRUPT && + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { + *endpoint_addr_out = ep->bEndpointAddress; + found = 1; + } + } + } + } + + libusb_free_config_descriptor(config); + + if (!found) { + fprintf(stderr, "No interrupt IN endpoint found on device.\n"); + return -1; + } + + return 0; +} + +int main(void) +{ + libusb_context *ctx = NULL; + libusb_device_handle *handle = NULL; + int rc; + + signal(SIGINT, handle_signal); + signal(SIGTERM, handle_signal); + + rc = libusb_init(&ctx); + if (rc != 0) { + fprintf(stderr, "libusb_init failed: %s\n", libusb_error_name(rc)); + return EXIT_FAILURE; + } + + libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); + + handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (!handle) { + fprintf(stderr, "Unable to open device %04x:%04x. Are you running as root or with correct udev rules?\n", + VENDOR_ID, PRODUCT_ID); + libusb_exit(ctx); + return EXIT_FAILURE; + } + + libusb_device *dev = libusb_get_device(handle); + uint8_t bus = libusb_get_bus_number(dev); + uint8_t addr = libusb_get_device_address(dev); + printf("Opened device on bus %03u device %03u (VID:PID %04x:%04x)\n", + bus, addr, VENDOR_ID, PRODUCT_ID); + + // Try to claim interface 0 (most CCID readers use it). Detach kernel driver if needed. + int iface = 0; + if (libusb_kernel_driver_active(handle, iface) == 1) { + printf("Kernel driver active on interface %d, detaching...\n", iface); + rc = libusb_detach_kernel_driver(handle, iface); + if (rc != 0) { + fprintf(stderr, "Failed to detach kernel driver: %s\n", libusb_error_name(rc)); + libusb_close(handle); + libusb_exit(ctx); + return EXIT_FAILURE; + } + } + + rc = libusb_claim_interface(handle, iface); + if (rc != 0) { + fprintf(stderr, "Failed to claim interface %d: %s\n", iface, libusb_error_name(rc)); + libusb_close(handle); + libusb_exit(ctx); + return EXIT_FAILURE; + } + + unsigned char endpoint_addr = 0; + if (find_interrupt_in_endpoint(handle, &endpoint_addr) != 0) { + libusb_release_interface(handle, iface); + libusb_close(handle); + libusb_exit(ctx); + return EXIT_FAILURE; + } + + printf("Listening on interrupt IN endpoint 0x%02x. Press Ctrl+C to stop.\n", + endpoint_addr); + + const int buf_size = 64; // typical interrupt packet size + unsigned char *buffer = (unsigned char *)malloc(buf_size); + if (!buffer) { + fprintf(stderr, "Failed to allocate buffer.\n"); + libusb_release_interface(handle, iface); + libusb_close(handle); + libusb_exit(ctx); + return EXIT_FAILURE; + } + + struct libusb_transfer *transfer = libusb_alloc_transfer(0); + if (!transfer) { + fprintf(stderr, "Failed to allocate transfer.\n"); + free(buffer); + libusb_release_interface(handle, iface); + libusb_close(handle); + libusb_exit(ctx); + return EXIT_FAILURE; + } + + struct listener_context listener_ctx; + listener_ctx.handle = handle; + listener_ctx.endpoint_addr = endpoint_addr; + + libusb_fill_interrupt_transfer(transfer, + handle, + endpoint_addr, + buffer, + buf_size, + transfer_callback, + &listener_ctx, + 0); // timeout 0 = infinite + + rc = libusb_submit_transfer(transfer); + if (rc != 0) { + fprintf(stderr, "Failed to submit transfer: %s\n", libusb_error_name(rc)); + libusb_free_transfer(transfer); + free(buffer); + libusb_release_interface(handle, iface); + libusb_close(handle); + libusb_exit(ctx); + return EXIT_FAILURE; + } + + while (keep_running) { + rc = libusb_handle_events(ctx); + if (rc != 0) { + fprintf(stderr, "libusb_handle_events error: %s\n", libusb_error_name(rc)); + break; + } + } + + // Clean up: ask libusb to cancel the transfer; the callback is + // responsible for freeing the transfer and buffer when cancellation + // completes or if an error occurred. + libusb_cancel_transfer(transfer); + + libusb_release_interface(handle, iface); + libusb_close(handle); + libusb_exit(ctx); + + printf("Exiting.\n"); + return EXIT_SUCCESS; +} + diff --git a/scan.c b/scan.c new file mode 100644 index 0000000..591a77c --- /dev/null +++ b/scan.c @@ -0,0 +1,232 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Target USB device: ffff:0035 (IC Reader) +#define VENDOR_ID "ffff" +#define PRODUCT_ID "0035" + +static int read_file_trim(const char *path, char *buf, size_t buf_size) +{ + FILE *f = fopen(path, "r"); + if (!f) return -1; + + if (!fgets(buf, (int)buf_size, f)) { + fclose(f); + return -1; + } + fclose(f); + + // Trim trailing whitespace/newline + size_t len = strlen(buf); + while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r' || buf[len - 1] == ' ' || buf[len - 1] == '\t')) { + buf[--len] = '\0'; + } + return 0; +} + +// Find the sysfs name of the target USB device under /sys/bus/usb/devices +// (e.g. "1-2"), by matching idVendor/idProduct. +static int find_usb_device_name(char *name_out, size_t name_out_size) +{ + const char *base = "/sys/bus/usb/devices"; + DIR *dir = opendir(base); + if (!dir) { + perror("opendir /sys/bus/usb/devices"); + return -1; + } + + struct dirent *ent; + int found = 0; + char path[512]; + char vid[16]; + char pid[16]; + + while ((ent = readdir(dir)) != NULL) { + if (ent->d_name[0] == '.') continue; + + // Skip interface/function entries like "1-2:1.0" + if (strchr(ent->d_name, ':') != NULL) continue; + + snprintf(path, sizeof(path), "%s/%s/idVendor", base, ent->d_name); + if (read_file_trim(path, vid, sizeof(vid)) != 0) continue; + + snprintf(path, sizeof(path), "%s/%s/idProduct", base, ent->d_name); + if (read_file_trim(path, pid, sizeof(pid)) != 0) continue; + + if (strcmp(vid, VENDOR_ID) == 0 && strcmp(pid, PRODUCT_ID) == 0) { + strncpy(name_out, ent->d_name, name_out_size - 1); + name_out[name_out_size - 1] = '\0'; + found = 1; + break; + } + } + + closedir(dir); + if (!found) { + fprintf(stderr, "USB device %s:%s not found on this system.\n", VENDOR_ID, PRODUCT_ID); + return -1; + } + return 0; +} + +static int write_sysfs(const char *path, const char *value) +{ + FILE *f = fopen(path, "w"); + if (!f) { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + return -1; + } + if (fprintf(f, "%s", value) < 0) { + fprintf(stderr, "Failed to write to %s: %s\n", path, strerror(errno)); + fclose(f); + return -1; + } + fclose(f); + return 0; +} + +// Disable the reader by unbinding it from the generic USB driver. +static int disable_reader(const char *dev_name) +{ + char buf[64]; + snprintf(buf, sizeof(buf), "%s\n", dev_name); + printf("Disabling reader %s (USB %s:%s)\n", dev_name, VENDOR_ID, PRODUCT_ID); + return write_sysfs("/sys/bus/usb/drivers/usb/unbind", buf); +} + +// Enable the reader by binding it back to the generic USB driver. +static int enable_reader(const char *dev_name) +{ + char buf[64]; + snprintf(buf, sizeof(buf), "%s\n", dev_name); + printf("Enabling reader %s (USB %s:%s)\n", dev_name, VENDOR_ID, PRODUCT_ID); + return write_sysfs("/sys/bus/usb/drivers/usb/bind", buf); +} + +// Trigger a one-shot card scan via PC/SC. Assumes the reader is already +// bound/enabled (use the "on" subcommand first). +static int trigger_scan(void) +{ + SCARDCONTEXT ctx; + LONG rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &ctx); + if (rv != SCARD_S_SUCCESS) { + fprintf(stderr, "SCardEstablishContext failed: %s\n", pcsc_stringify_error(rv)); + return -1; + } + + char readers[2048]; + DWORD readersLen = sizeof(readers); + rv = SCardListReaders(ctx, NULL, readers, &readersLen); + if (rv != SCARD_S_SUCCESS) { + fprintf(stderr, "SCardListReaders failed: %s\n", pcsc_stringify_error(rv)); + SCardReleaseContext(ctx); + return -1; + } + + if (readersLen <= 1) { + fprintf(stderr, "No smartcard readers found via PC/SC.\n"); + SCardReleaseContext(ctx); + return -1; + } + + // readers is a multi-string: sequence of null-terminated strings + // terminated by an extra null. Prefer a reader whose name contains + // "IC Reader", otherwise use the first. + const char *chosen = NULL; + const char *p = readers; + while (*p != '\0') { + if (!chosen) { + chosen = p; + } + if (strstr(p, "IC Reader") != NULL) { + chosen = p; + break; + } + p += strlen(p) + 1; + } + + if (!chosen) { + fprintf(stderr, "Unable to choose a reader from PC/SC list.\n"); + SCardReleaseContext(ctx); + return -1; + } + + printf("Using reader: %s\n", chosen); + + SCARDHANDLE card; + DWORD activeProt; + rv = SCardConnect(ctx, chosen, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, + &card, &activeProt); + if (rv != SCARD_S_SUCCESS) { + fprintf(stderr, "SCardConnect failed: %s\n", pcsc_stringify_error(rv)); + SCardReleaseContext(ctx); + return -1; + } + + // At this point the reader has powered/reset the card and read the ATR. + BYTE atr[64]; + DWORD atrLen = sizeof(atr); + DWORD state, prot; + rv = SCardStatus(card, NULL, NULL, &state, &prot, atr, &atrLen); + if (rv == SCARD_S_SUCCESS) { + printf("ATR (%u bytes): ", atrLen); + for (DWORD i = 0; i < atrLen; i++) { + printf("%02X ", atr[i]); + } + printf("\n"); + } else { + fprintf(stderr, "SCardStatus failed: %s\n", pcsc_stringify_error(rv)); + } + + SCardDisconnect(card, SCARD_LEAVE_CARD); + SCardReleaseContext(ctx); + return 0; +} + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s on|off|scan\n", prog); + fprintf(stderr, " off : unbind the IC Reader so the system cannot use it.\n"); + fprintf(stderr, " on : bind the IC Reader back so it works again.\n"); + fprintf(stderr, " scan: perform a one-shot PC/SC card scan using the reader.\n"); + fprintf(stderr, "Note: on/off require root (write access to /sys/bus/usb/drivers/usb).\n"); +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + usage(argv[0]); + return EXIT_FAILURE; + } + + char dev_name[64]; + if (find_usb_device_name(dev_name, sizeof(dev_name)) != 0) { + return EXIT_FAILURE; + } + + int rc; + if (strcmp(argv[1], "off") == 0) { + rc = disable_reader(dev_name); + } else if (strcmp(argv[1], "on") == 0) { + rc = enable_reader(dev_name); + } else if (strcmp(argv[1], "scan") == 0) { + rc = trigger_scan(); + } else { + usage(argv[0]); + return EXIT_FAILURE; + } + + if (rc != 0) { + fprintf(stderr, "Operation failed. Are you running as root?\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file