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 */