From 058db8f704c82b5478b5e45aac9a6a6fc85c2436 Mon Sep 17 00:00:00 2001 From: JayPiKay Date: Thu, 12 Aug 2021 13:06:08 +0200 Subject: [PATCH] Inital Commit --- .gitignore | 10 +++ Makefile | 38 ++++++++++++ README.md | 65 +++++++++++++++++++- config.def.h | 7 +++ config.mk | 10 +++ main.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++ usbms.c | 126 +++++++++++++++++++++++++++++++++++++ usbms.h | 32 ++++++++++ xusb.c | 137 +++++++++++++++++++++++++++++++++++++++++ xusb.h | 14 +++++ 10 files changed, 608 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 config.def.h create mode 100644 config.mk create mode 100644 main.c create mode 100644 usbms.c create mode 100644 usbms.h create mode 100644 xusb.c create mode 100644 xusb.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4aef9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +attic/ +.ccls-cache/ +.cache/ + +compile_commands.json + +*.o + +actionpro +config.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4400f14 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# ACTIONPRO Makefile + +include config.mk + +PRG = actionpro +SRC = main.c xusb.c usbms.c +OBJ = ${SRC:.c=.o} +BIN = ${OBJ:.o=} + +all: options ${PRG} + +options: + @echo ${PRG} compile options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +${PRG}: ${OBJ} + ${CC} -o ${PRG} ${OBJ} ${LDFLAGS} + +clean: + @echo cleaning + @rm -f ${PRG} ${OBJ} + +purge: clean + @echo removing old config.h + @rm -f config.h + +.PHONY: all options clean purge diff --git a/README.md b/README.md index bfe16e4..428b368 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,63 @@ -# actionpro-cli -Command line interface for ACTIONPRO X7 configuration +# Actionpro-cli +A Command line interface for ACTIONPRO X7 to configure the WiFi settings from the command line. + +The ACTIONPRO X7 was produced by _CI IMAGEWEAR GmbH_ and is by now _end of life_ and now longer +supported. There was a Windows program called _Action Manager_, which allowed Windows users to +configure their action camera, or reset the credetials, if the credentials were forgotten. + +This project uses parts of the [xusb.c](https://github.com/libusb/libusb/blob/master/examples/xusb.c) +example program provided by the libusb project. + +This program is a result of my work on reverse engineering the _Action Manager_ + + +## Requirements + +**Libraries**: + + * libusb-1.0 + +To access usb devices, root access is often required. + + +## Compiling + +``` +$ make config.h +$ $EDITOR config.h +$ make +``` + + +### Config Options + +Adjust settings in `config.h` before running make to apply changes. + +The file `config.h` is created when running `make`, or by explicitly running `make config.h`. +If the file `config.h` does not exist while running make, the defaults are copied from `config.def.h`. + +**Options** + - `RETRY_MAX` (_Default 5_) - Number of retries for sending a mass storage command. + + +## Options + +``` +Usage: ./actionpro [OPTION] + -C, --config-file=FILE use this user configuration file + -h, --help give this help list + -p, --password=PASSWORD sets the access point authentication PASSWORD + -s, --ssid=SSID sets the access point SSID + -t, --time synchronize the camera time + -v, --version display version number +``` + + +## Executing + +To update the SSID to "newssid" and set the access point password of the cameras access +to "newpassword": + +``` +$ ./actionpro -s newssid -p newpassword +``` diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..2082449 --- /dev/null +++ b/config.def.h @@ -0,0 +1,7 @@ +#ifndef config_h +#define config_h + +/* defaults */ +#define RETRY_MAX 5 + +#endif /* config_h */ diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..4681630 --- /dev/null +++ b/config.mk @@ -0,0 +1,10 @@ +# includes and libs +INCS = -I. -I/usr/include +LIBS = -L/usr/lib -lusb-1.0 + +# flags +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} +LDFLAGS = -s ${LIBS} + +# compiler and linker +CC = cc diff --git a/main.c b/main.c new file mode 100644 index 0000000..c1d50ea --- /dev/null +++ b/main.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include +#include +#include + +#include "usbms.h" + +#include "config.h" + +#define VERSION "0.5" + +static int setpassword(const char *password) +{ + unsigned long slen = strlen(password); + if (slen < 2 || slen > 12) { + fprintf(stderr, "Password has to be at least 2 but no more than 12 characters long.\n"); + return 1; + } + + for (int i = 0; i < slen; i++) { + switch (password[i]) { + case '/': + case ':': + case '@': + case '[': + case '`': + case '{': + fprintf(stderr, "Use of invalid character in password. Do not use `%c'.\n", + password[i]); + fprintf(stderr, "Invalid character for passwords are \"/:@[`{\".\n"); + return 1; + } + } + printf("Updating password to `%s'... ", password); + if (send_command(ACTIONPRO_OPCODE_SETPASSWORD, password, slen) == ACTIONPRO_CMD_OK) + printf("OK\n"); + else + printf("ERROR\n"); + + return 0; +} + +static int setssid(const char *ssid) +{ + unsigned long slen = strlen(ssid); + if (slen < 2 || slen > 12) { + fprintf(stderr, "SSID has to be at least 2 but no more than 12 characters long.\n"); + return 1; + } + + for (int i = 0; i < slen; i++) { + switch (ssid[i]) { + case ' ': + case '~': + fprintf(stderr, "Use of invalid character in SSID. Do not use `%c'.\n", + ssid[i]); + fprintf(stderr, "Invalid character for SSID are \" ~\".\n"); + return 1; + } + } + printf("Updating SSID to `%s'... ", ssid); + if (send_command(ACTIONPRO_OPCODE_SETSSID, ssid, slen) == ACTIONPRO_CMD_OK) + printf("OK\n"); + else + printf("ERROR\n"); + + + return 0; +} + +static int settime() +{ + printf("Setting device time... "); + printf("NOT YET implemented\n"); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int c; + + bool show_help = false; + char *new_password = NULL; + char *new_ssid = NULL; + bool sync_time = false; + bool show_version = false; + + printf("ACTIONPRO configuration utility\n"); + + while (1) { + static struct option long_options[] = { + {"help" , no_argument , 0, 'p'}, + {"password", required_argument, 0, 'p'}, + {"ssid" , required_argument, 0, 's'}, + {"time" , no_argument , 0, 't'}, + {"version" , no_argument , 0, 'v'}, + {0, 0, 0, 0} + }; + + int option_index = 0; + + c = getopt_long(argc, argv, "hp:s:tv", long_options, &option_index); + + if (argc < 2) { + fprintf(stderr, "At least one option is required. See -h, --help for instructions.\n"); + exit(EXIT_FAILURE); + } + + if (c == -1) + break; + + switch (c) { + case 'h': + show_help = true; + break; + case 'p': + new_password = optarg; + break; + case 's': + new_ssid = optarg; + break; + case 't': + sync_time = true; + break; + case 'v': + show_version = true; + case '?': + fprintf(stderr, "Use option -h, --help for instructions.\n"); + exit(EXIT_FAILURE); + default: + abort(); + } + } + + if (show_help) { + printf("Usage: %s [OPTION]\n", argv[0]); + printf(" -C, --config-file=FILE use this user configuration file\n"); + printf(" -h, --help give this help list\n"); + printf(" -p, --password=PASSWORD sets the access point authentication PASSWORD\n"); + printf(" -s, --ssid=SSID sets the access point SSID\n"); + printf(" -t, --time synchronize the camera time\n"); + printf(" -v, --version display version number\n"); + printf("\nThis program requires write access to usb devices and might be run as root.\n"); + + exit(EXIT_SUCCESS); + } + + if (show_version) { + printf("Actionpro-cli version: %s\n", VERSION); + exit(EXIT_SUCCESS); + } + + if (open_device() == ACTIONPRO_OK) { + if (new_password) + setpassword(new_password); + + if (new_ssid) + setssid(new_ssid); + + open_device(); + + if (sync_time) + settime(); + } + close_device(); + + exit(EXIT_SUCCESS); +} diff --git a/usbms.c b/usbms.c new file mode 100644 index 0000000..5a8049c --- /dev/null +++ b/usbms.c @@ -0,0 +1,126 @@ +/* This file is part of actionpro-cli. + * + * Actionpro-cli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include +#include + +#include "xusb.h" +#include "usbms.h" + +#include "config.h" + +static struct libusb_device_handle *handle = NULL; + +int send_command(const uint8_t opcode, const char *buf, const uint8_t buflen) +{ + int rc; + uint32_t expected_tag = 0; + int size; + uint8_t cdb[CDB_MAX_LENGTH]; + uint8_t sense[REQUEST_SENSE_LENGTH]; + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x03; // Request Sense + cdb[4] = REQUEST_SENSE_LENGTH; + + send_mass_storage_command(handle, 0x01, 0, cdb, LIBUSB_ENDPOINT_IN, REQUEST_SENSE_LENGTH, &expected_tag); + rc = libusb_bulk_transfer(handle, 0x81, (unsigned char*)&sense, REQUEST_SENSE_LENGTH, &size, 1000); + if (rc < 0) { + printf("libusb_bulk_transfer failed: %s\n", libusb_error_name(rc)); + return ACTIONPRO_CMD_ERROR; + } + + rc = libusb_bulk_transfer(handle, 0x81, (unsigned char*)&sense, REQUEST_SENSE_LENGTH, &size, 1000); + if (rc < 0) { + printf("libusb_bulk_transfer failed: %s\n", libusb_error_name(rc)); + return ACTIONPRO_CMD_ERROR; + } + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = opcode; + memcpy(cdb+2, buf, buflen); + + send_mass_storage_command(handle, 0x01, 0, cdb, LIBUSB_ENDPOINT_IN, REQUEST_SENSE_LENGTH, &expected_tag); + rc = libusb_bulk_transfer(handle, 0x81, (unsigned char*)&sense, REQUEST_SENSE_LENGTH, &size, 1000); + if (rc < 0) { + printf("libusb_bulk_transfer failed: %s\n", libusb_error_name(rc)); + return ACTIONPRO_CMD_ERROR; + } + + return ACTIONPRO_CMD_OK; +} + +int open_device() +{ + int rc; + + if (handle) + return ACTIONPRO_OK; + + rc = libusb_init(NULL); + if (rc < 0) { + printf("\nFailed to initialize libusb\n"); + return ACTIONPRO_ERROR; + } + + handle = libusb_open_device_with_vid_pid(NULL, ACTION_VENDOR_ID, ACTION_PRODUCT_ID); + if (handle == NULL) { + fprintf(stderr, "Cannot open camera device %04X:%04X.\n", + ACTION_VENDOR_ID, ACTION_PRODUCT_ID); + return ACTIONPRO_NOT_FOUND; + } + + libusb_set_configuration(handle, ACTIONPRO_USB_CONFIGURATION); + if (libusb_kernel_driver_active(handle, ACTIONPRO_USB_INTERFACE) == 1) { + rc = libusb_detach_kernel_driver(handle, ACTIONPRO_USB_INTERFACE); + if (rc != 0) { + fprintf(stderr, "Failed to detach kernel driver.\n"); + return ACTIONPRO_ERROR; + } + } + + rc = libusb_claim_interface(handle, ACTIONPRO_USB_INTERFACE); + if (rc != 0) { + fprintf(stderr, "Failed to claim usb interface.\n"); + return ACTIONPRO_ERROR; + } + + return ACTIONPRO_OK; +} + +int close_device() +{ + if (handle) { + libusb_release_interface(handle, 0); + libusb_reset_device(handle); + libusb_close(handle); + handle = NULL; + } + + libusb_exit(NULL); + + return ACTIONPRO_OK; +} diff --git a/usbms.h b/usbms.h new file mode 100644 index 0000000..7e2abfa --- /dev/null +++ b/usbms.h @@ -0,0 +1,32 @@ +#ifndef usbms_h +#define usbms_h + +#include + +/* device information */ +#define ACTION_VENDOR_ID 0x4255 +#define ACTION_PRODUCT_ID 0x1000 + +/* usb configuration */ +#define ACTIONPRO_USB_CONFIGURATION 1 +#define ACTIONPRO_USB_INTERFACE 0 + +/* return codes */ +#define ACTIONPRO_OK 0 +#define ACTIONPRO_ERROR 1 +#define ACTIONPRO_NOT_FOUND 2 + +#define ACTIONPRO_CMD_OK 0 +#define ACTIONPRO_CMD_ERROR 1 + +/* vendor specific SPC-2 commands */ +#define ACTIONPRO_OPCODE_SETSSID 0xfd +#define ACTIONPRO_OPCODE_SETPASSWORD 0xfe +#define ACTIONPRO_OPCODE_SETTIME 0xff + +/* exported functions */ +extern int open_device(); +extern int close_device(); +extern int send_command(const uint8_t opcode, const char *buf, const uint8_t buflen); + +#endif /* usbms_h */ diff --git a/xusb.c b/xusb.c new file mode 100644 index 0000000..e41528e --- /dev/null +++ b/xusb.c @@ -0,0 +1,137 @@ +/* xusb: Generic USB test program + * Copyright © 2009-2012 Pete Batard + * Contributions to Mass Storage by Alan Stern. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +/* begin of code from xusb.c */ +#define ERR_EXIT(errcode) do { perr(" %s\n", libusb_strerror((enum libusb_error)errcode)); return -1; } while (0) +#define CALL_CHECK(fcall) do { int _r=fcall; if (_r < 0) ERR_EXIT(_r); } while (0) +#define CALL_CHECK_CLOSE(fcall, hdl) do { int _r=fcall; if (_r < 0) { libusb_close(hdl); ERR_EXIT(_r); } } while (0) + +// Section 5.1: Command Block Wrapper (CBW) +struct command_block_wrapper { + uint8_t dCBWSignature[4]; + uint32_t dCBWTag; + uint32_t dCBWDataTransferLength; + uint8_t bmCBWFlags; + uint8_t bCBWLUN; + uint8_t bCBWCBLength; + uint8_t CBWCB[16]; +}; + +// Section 5.2: Command Status Wrapper (CSW) +struct command_status_wrapper { + uint8_t dCSWSignature[4]; + uint32_t dCSWTag; + uint32_t dCSWDataResidue; + uint8_t bCSWStatus; +}; + +const uint8_t cdb_length[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 06,06,06,06,06,06,06,06,06,06,06,06,06,06,06,06, // 0 + 06,06,06,06,06,06,06,06,06,06,06,06,06,06,06,06, // 1 + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, // 2 + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, // 3 + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, // 4 + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, // 5 + 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, // 6 + 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, // 7 + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, // 8 + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, // 9 + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, // A + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, // B + 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, // C + 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, // D + 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, // E + /* Added length parameters for vendor commands. + * —— —— —— */ + 00,00,00,00,00,00,00,00,00,00,00,00,00,16,16,16, // F +}; + +static void perr(char const *format, ...) +{ + va_list args; + + va_start (args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +int send_mass_storage_command(libusb_device_handle *handle, uint8_t endpoint, uint8_t lun, + uint8_t *cdb, uint8_t direction, int data_length, uint32_t *ret_tag) +{ + static uint32_t tag = 1; + uint8_t cdb_len; + int i, r, size; + struct command_block_wrapper cbw; + + if (cdb == NULL) { + return -1; + } + + if (endpoint & LIBUSB_ENDPOINT_IN) { + perr("send_mass_storage_command: cannot send command on IN endpoint\n"); + return -1; + } + + cdb_len = cdb_length[cdb[0]]; + if ((cdb_len == 0) || (cdb_len > sizeof(cbw.CBWCB))) { + perr("send_mass_storage_command: don't know how to handle this command (%02X, length %d)\n", + cdb[0], cdb_len); + return -1; + } + + memset(&cbw, 0, sizeof(cbw)); + cbw.dCBWSignature[0] = 'U'; + cbw.dCBWSignature[1] = 'S'; + cbw.dCBWSignature[2] = 'B'; + cbw.dCBWSignature[3] = 'C'; + *ret_tag = tag; + cbw.dCBWTag = tag++; + cbw.dCBWDataTransferLength = data_length; + cbw.bmCBWFlags = direction; + cbw.bCBWLUN = lun; + // Subclass is 1 or 6 => cdb_len + cbw.bCBWCBLength = cdb_len; + memcpy(cbw.CBWCB, cdb, cdb_len); + + i = 0; + do { + // The transfer length must always be exactly 31 bytes. + r = libusb_bulk_transfer(handle, endpoint, (unsigned char*)&cbw, 31, &size, 1000); + if (r == LIBUSB_ERROR_PIPE) { + libusb_clear_halt(handle, endpoint); + } + i++; + } while ((r == LIBUSB_ERROR_PIPE) && (i < RETRY_MAX)); + if (r != LIBUSB_SUCCESS) { + perr("send_mass_storage_command: %s\n", libusb_strerror((enum libusb_error)r)); + return -1; + } + + return 0; +} diff --git a/xusb.h b/xusb.h new file mode 100644 index 0000000..5cccee0 --- /dev/null +++ b/xusb.h @@ -0,0 +1,14 @@ +#ifndef xusb_h +#define xusb_h + +#include +#include + +#define REQUEST_SENSE_LENGTH 0x12 +#define CDB_MAX_LENGTH 0x10 + +extern const uint8_t cdb_length[256]; +extern int send_mass_storage_command(libusb_device_handle *handle, uint8_t endpoint, uint8_t lun, + uint8_t *cdb, uint8_t direction, int data_length, uint32_t *ret_tag); + +#endif /* xusb_h */