/* * eyefitest.c * * Copyright (C) 2008 Dave Hansen * * This software may be redistributed and/or modified under the terms of * the GNU General Public License ("GPL") version 2 as published by the * Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #define O_DIRECT 00040000 /* direct disk access hint */ #define MOUNT "/media/EYE-FI/" #define PATH MOUNT "eyefi/" #define REQC PATH "REQC" #define REQM PATH "REQM" #define RSPC PATH "RSPC" #define RSPM PATH "RSPM" #define BUFSZ 16384 #define EYEFI_BUF_SIZE 16384 char unaligned_buf[BUFSZ*10]; void *buf; int debug_level = 1; #define debug_printf(level, args...) do { \ if ((level) <= debug_level) \ fprintf(stderr, ## args); \ } while(0) /* * Do some kernel-style types to make * definitions shorter. */ typedef unsigned long u32; typedef unsigned char u8; static inline u32 swap_bytes(u32 src) { unsigned int dest = 0; dest |= (src & 0xff000000) >> 24; dest |= (src & 0x00ff0000) >> 8; dest |= (src & 0x0000ff00) << 8; dest |= (src & 0x000000ff) << 24; return dest; } /* * Just a few functions so that I can't easily forget about * endinness. */ struct __be32 { u32 val; } __attribute__((packed)); typedef struct __be32 be32; /* * These two obviously need to get fixed for * big endian machines. */ u32 be32_to_u32(be32 src) { return swap_bytes(src.val); } be32 u32_to_be32(u32 src) { be32 ret; ret.val = swap_bytes(src); return ret; } void dumpbuf(const char *buffer, int bytesToWrite) { int i; static char linebuf[500]; for (i=0; i < bytesToWrite; i += 16) { char *tmpbuf = &linebuf[0]; unsigned long sum = 0; int j; #define lprintf(args...) do { \ tmpbuf += sprintf(tmpbuf, ## args);\ } while (0) lprintf("[%03d]: ", i); for (j=0; j < 16; j++) { u8 c = ((unsigned char *)buffer)[i+j]; lprintf("%02x ", (unsigned int)c); sum += c; } lprintf(" |"); for (j=0; j < 16; j++) { u8 c = ((unsigned char *)buffer)[i+j]; if (c >= 'a' && c <= 'z') lprintf("%c", c); else if (c >= 'A' && c <= 'Z') lprintf("%c", c); else if (c >= '0' && c <= '9') lprintf("%c", c); else if (c >= 0x20 && c <= 127) lprintf("%c", c); else lprintf("."); } lprintf("|\n"); if (sum == 0) continue; printf("%s", linebuf); //if (i > 200) // break; } } struct card_seq_num { u32 seq; } __attribute__((packed)); void read_from(char *file); void write_to(char *file, void *stuff, int len); struct card_seq_num read_seq_from(char *file) { struct card_seq_num *ret; read_from(file); ret = buf; return *ret; } /* * For O_DIRECT writes to files, we need * to be 512 byte aligned on Linux, I think. * So, just align this to something big * and be done with it. FIXME :) */ void align_buf(void) { unsigned long addr = (unsigned long)&unaligned_buf[BUFSZ]; addr &= ~(BUFSZ-1); buf = (void *)addr; debug_printf(4, "buf: %p\n", buf); debug_printf(4, "unaligned: %p\n", &unaligned_buf[0]); } struct card_seq_num seq; /* * The real manager does this so we might * as well, too. */ void zero_card_files(void) { write_to(REQM, buf, BUFSZ); write_to(REQC, buf, BUFSZ); write_to(RSPM, buf, BUFSZ); write_to(RSPC, buf, BUFSZ); read_from(REQM); read_from(REQC); read_from(RSPM); read_from(RSPC); } void init_card() { if (buf != NULL) return; align_buf(); zero_card_files(); seq = read_seq_from(RSPC); if (seq.seq == 0) seq.seq = 0x1234; } void open_error(char *file) { fprintf(stderr, "unable to open '%s'\n", file); fprintf(stderr, "Is the Eye-Fi card inserted and mounted at: %s ?\n", MOUNT); fprintf(stderr, "Do you have write permissions to it?\n"); if (debug_level > 1) perror("bad open"); exit(1); } void read_from(char *file) { u8 c; int i; int ret, retcntl; int fd; int zeros = 0; init_card(); fd = open(file, O_RDONLY); if (fd < 0) open_error(file); retcntl = fcntl(fd, F_SETFL, O_DIRECT); if (retcntl < 0) { perror("bad fcntl"); exit(1); } ret = read(fd, buf, BUFSZ); if (debug_level > 3) dumpbuf(buf, 128); if (ret < 0) { perror("bad read"); exit(1); } debug_printf(3, "read '%s': bytes: %d fcntl: %d\n", file, ret, retcntl); for (i=0; i < BUFSZ; i++) { c = ((char *)buf)[i]; if (c == '\0') { zeros++; continue; } } //if (zeros) // printf(" zeros: %d", zeros); //fsync(fd); close(fd); } void write_to(char *file, void *stuff, int len) { //printf("not writing to '%s'\n", file); //return; int ret; int fd; init_card(); if (len == -1) len = strlen(stuff); if (debug_level > 3) dumpbuf(stuff, len); memset(buf, 0, BUFSZ); memcpy(buf, stuff, len); fd = open(file, O_RDWR|O_DIRECT|O_CREAT, 0600); //ret = lseek(fd, 0, SEEK_SET); if (fd < 0) open_error(file); if (debug_level > 3) dumpbuf(buf, 128); ret = write(fd, buf, BUFSZ); //fsync(fd); close(fd); debug_printf(3, "wrote %d bytes to '%s' (string was %d bytes)\n", ret, file, len); if (ret < 0) exit(ret); } /* * Most of the eyefi strings are pascal-style with * a length byte preceeding content. (Did pascal * have just a byte for length or more??) */ struct pascal_string { u8 length; u8 value[32]; } __attribute__((packed)); void print_pascal_string(struct pascal_string *str) { int i; for (i = 0; i < str->length; i++) printf("%c", str->value[i]); } /* * The 'o' command has several sub-commands: */ enum card_info_subcommand { MAC_ADDRESS = 1, FIRMWARE_INFO = 2, CARD_KEY = 3, API_URL = 4, UNKNOWN1 = 5, // Chris says these are UNKNOWN2 = 6, // checksums LOG_LEN = 7, }; struct card_info_req { u8 o; u8 subcommand; } __attribute__((packed)); struct card_info_rsp_key { struct pascal_string key; }; #define MAC_BYTES 6 struct mac_address { u8 length; u8 mac[MAC_BYTES]; } __attribute__((packed)); struct card_info_api_url { struct pascal_string key; }; struct card_info_log_len { u8 len; be32 val; } __attribute__((packed)); #define write_struct(file, s) write_to((file), s, sizeof(*(s))) void print_mac(struct mac_address *mac) { int i; for (i=0; i < MAC_BYTES-1; i++) { printf("%02x:", mac->mac[i]); } printf("%02x\n", mac->mac[i]); } void inc_seq(void) { //u32 tmpseq = be32_to_u32(seq.seq); //seq.seq = u32_to_be32(tmpseq+1); seq.seq++; write_struct(REQC, &seq); } u32 current_seq(void) { return seq.seq; } void wait_for_response(void) { int i; debug_printf(3, "waiting for response...\n"); inc_seq(); for (i = 0; i < 50; i++) { struct card_seq_num cardseq = read_seq_from(RSPC); u32 rsp = cardseq.seq; debug_printf(3, "read rsp code: %lx, looking for: %lx raw: %lx\n", rsp, current_seq(), cardseq.seq); if (rsp == current_seq()) break; usleep(300000); } debug_printf(3, "got good seq, reading RSPM...\n"); read_from(RSPM); debug_printf(3, "done reading RSPM\n"); } struct byte_response { u8 response; }; enum net_type { UNSECURED, WEP, WPA, WPA2 }; #define ESSID_LEN 32 struct scanned_net { char essid[ESSID_LEN]; signed char strength; u8 type; } __attribute__((packed)); struct scanned_net_list { u8 nr; struct scanned_net nets[100]; } __attribute__((packed)); struct configured_net { char essid[ESSID_LEN]; } __attribute__((packed)); struct configured_net_list { u8 nr; struct configured_net nets[100]; } __attribute__((packed)); char *net_test_states[] = { "not scanning", "locating network", "verifying network key", "waiting for DHCP", "testing connection to Eye-Fi server", "success", }; #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) char *net_test_state_name(u8 state) { int size = ARRAY_SIZE(net_test_states); if (state >= size) return "unknown"; return net_test_states[state]; } char *net_types[] = { "No security", "WEP", "WPA", "unknown1", "WPA2", }; char *net_type_name(u8 type) { int size = ARRAY_SIZE(net_types); if (type >= size) return "unknown"; return net_types[type]; } #define WPA_KEY_BYTES 32 struct wpa_key { u8 key[WPA_KEY_BYTES]; } __attribute((packed)); #define WEP_KEY_BYTES 32 struct wep_key { u8 key[WEP_KEY_BYTES]; } __attribute((packed)); struct network_key { u8 len; union { struct wpa_key wpa; struct wep_key wep; }; } __attribute((packed)); #define KEY_LEN 32 struct net_request { char req; u8 essid_len; char essid[ESSID_LEN]; struct network_key key; } __attribute((packed)); char lower(char c) { if ((c >= 'A') && (c <= 'Z')) c += ('a' - 'A'); return c; } int atoh(char c) { char lc = lower(c); if ((c >= '0') && (c <= '9')) return c - '0'; else if ((c >= 'a') && (c <= 'z')) return (c - 'a') + 10; debug_printf(0, "non-hex character: '%c'/'%c'\n", c, lc); return 0; } /* * Take a string like "0ab1" and make it * a series of bytes: { 0x0a, 0xb1 } * * Destroys the original string. */ char *convert_ascii_to_hex(char *ascii, int len) { int i; if (len%2) { fprintf(stderr, "%s() must be even number of bytes: %d\n", __func__, len); exit(2); } for (i=0; i < len; i+=2) { u8 high = atoh(ascii[i]); u8 low = atoh(ascii[i+1]); u8 byte = (high<<4 | low); debug_printf(6, "high: %02x low: %02x, both: %02x\n", high, low, byte); ascii[i/2] = byte; } for (i=len/2; i < len; i++) ascii[i] = '\0'; return &ascii[0]; } #define PASSPHRASE_PROG "wpa_passphrase" struct wpa_key *make_wpa_key(char *essid, char *pass) { char program[] = PASSPHRASE_PROG; // 7 for 2 spaces, 4 quotes and a \0 char redirect[] = " 2> /dev/null"; char *cmdbuf = malloc(strlen(essid) + strlen(pass) + strlen(program) + 7 + strlen(redirect)); if (!cmdbuf) return NULL; sprintf(cmdbuf, "%s '%s' '%s' %s", program, essid, pass, redirect); FILE *pipe = popen(cmdbuf, "r"); if (!pipe) { perror("\nunable to execute " PASSPHRASE_PROG); return NULL; } int key_chars = WPA_KEY_BYTES*2; char hex_key_in_ascii[key_chars+1]; char line[1024]; int read = 0; while (fgets(&line[0], 1023, pipe)) { debug_printf(4, "read from %s: '%s'\n", PASSPHRASE_PROG, line); read = sscanf(&line[0], " psk=%64s", &hex_key_in_ascii[0]); if (read == 0) continue; break; } int exit_code = pclose(pipe); if (!read || exit_code) { fprintf(stderr, "\nunable to read wpa key from %s\n", PASSPHRASE_PROG); fprintf(stderr, "Is wpa_supplicant installed?\n"); exit(4); } debug_printf(4, "ascii key: '%s'\n", hex_key_in_ascii); char *hex_key = convert_ascii_to_hex(hex_key_in_ascii, key_chars); struct wpa_key *key = malloc(sizeof(*key)); memcpy(&key->key[0], hex_key, WPA_KEY_BYTES); free(cmdbuf); return key; } void card_info_cmd(enum card_info_subcommand cmd) { struct card_info_req cir; cir.o = 'o'; cir.subcommand = cmd; write_struct(REQM, &cir); wait_for_response(); } u32 fetch_log_length(void) { card_info_cmd(LOG_LEN); struct card_info_log_len *loglen = buf; return be32_to_u32(loglen->val); } void print_log_len(void) { u32 len = fetch_log_length(); printf("log len: %08lx\n", len); } void print_card_mac(void) { debug_printf(2, "%s()\n", __func__); card_info_cmd(MAC_ADDRESS); struct mac_address *mac = buf; assert(mac->length == MAC_BYTES); printf("card mac address: "); print_mac(mac); } void print_card_key(void) { debug_printf(2, "%s()\n", __func__); card_info_cmd(CARD_KEY); struct card_info_rsp_key *foo = buf; printf("card key (len: %d): '", foo->key.length); print_pascal_string(&foo->key); printf("'\n"); } struct noarg_request { u8 req; }; void issue_noarg_command(u8 cmd) { struct noarg_request req; req.req = cmd; write_struct(REQM, &req); wait_for_response(); } void scan_print_nets(void) { int i; debug_printf(2, "%s()\n", __func__); issue_noarg_command('g'); struct scanned_net_list *scanned = buf; if (scanned->nr == 0) { printf("unable to detect any wireless networks\n"); return; } printf("Scanned wireless networks:\n"); for (i=0; i < scanned->nr; i++) { struct scanned_net *net = &scanned->nets[i]; printf("'%s' type(%d): %s, strength: %d\n", net->essid, net->type, net_type_name(net->type), net->strength); } } void print_configured_nets(void) { int i; struct configured_net_list *configured; debug_printf(2, "%s()\n", __func__); issue_noarg_command('l'); configured = buf; if (configured->nr == 0) { printf("No wireless networks configured on card\n"); return; } printf("configured wireless networks:\n"); for (i=0; i < configured->nr; i++) { struct configured_net *net = &configured->nets[i]; printf("'%s'\n", net->essid); } } void reboot_card(void) { debug_printf(2, "%s()\n", __func__); issue_noarg_command('b'); } void copy_wep_key(struct wep_key *dst, struct wep_key *src) { memcpy(&dst->key, &src->key, sizeof(*dst)); } void copy_wpa_key(struct wpa_key *dst, struct wpa_key *src) { memcpy(&dst->key, &src->key, sizeof(*dst)); } void network_action(char cmd, char *essid, char *wpa_ascii) { struct net_request nr; memset(&nr, 0, sizeof(nr)); nr.req = cmd; strcpy(&nr.essid[0], essid); nr.essid_len = strlen(essid); struct wpa_key *wpakey; if (wpa_ascii) { wpakey = make_wpa_key(essid, wpa_ascii); nr.key.len = sizeof(*wpakey); copy_wpa_key(&nr.key.wpa, wpakey); } write_struct(REQM, &nr); wait_for_response(); } void add_network(char *essid, char *wpa_ascii) { debug_printf(2, "%s()\n", __func__); network_action('a', essid, wpa_ascii); } void remove_network(char *essid) { debug_printf(2, "%s()\n", __func__); network_action('d', essid, NULL); } int try_connection_to(char *essid, char *wpa_ascii) { int i; int ret = -1; char *type = net_type_name(WPA); if (!wpa_ascii) type = net_type_name(UNSECURED); printf("trying to connect to %s network: '%s'", type, essid); if (wpa_ascii) printf(" with passphrase: '%s'", wpa_ascii); fflush(NULL); // test network network_action('t', essid, wpa_ascii); u8 last_rsp = -1; char rsp = '\0'; for (i=0; i < 200; i++) { struct byte_response *r; issue_noarg_command('s'); r = buf; rsp = r->response; char *state = net_test_state_name(rsp); if (rsp == last_rsp) { printf("."); fflush(NULL);; } else { if (rsp) printf("\nTesting connecion to '%s' (%d): %s", essid, rsp, state); last_rsp = rsp; } if (!strcmp("success", state)) { ret = 0; break; } if (!strcmp("not scanning", state)) break; if (!strcmp("unknown", state)) break; } printf("\n"); if (!ret) { printf("Succeeded connecting to: '%s'\n", essid); } else { printf("Unable to connect to: '%s' (final state: %d/'%s')\n", essid, rsp, net_test_state_name(rsp)); } return ret; } struct fetch_log_cmd { char m; be32 offset; } __attribute__((packed)); /* * When you ask for the log at offset 0x0, you * get back 8 bytes of offsets into the rest of * the data */ struct first_log_response { be32 log_end; be32 log_start; u8 data[EYEFI_BUF_SIZE-8]; } __attribute__((packed)); struct rest_log_response { u8 data[EYEFI_BUF_SIZE]; } __attribute__((packed)); unsigned char *get_log_at_offset(u32 offset) { struct fetch_log_cmd cmd; cmd.m = 'm'; cmd.offset = u32_to_be32(offset); debug_printf(2, "getting log at offset: %08lx\n", offset); write_struct(REQM, &cmd); wait_for_response(); return buf; } int get_log(void) { int total_bytes = 0; int i; u32 log_start; u32 log_end; u32 log_size = fetch_log_length(); char *resbuf = malloc(log_size); int nr_bufs_per_log = log_size/EYEFI_BUF_SIZE; for (i = 0; i < log_size/EYEFI_BUF_SIZE; i++) { debug_printf(1, "fetching EyeFi card log part %d/%d...", i+1, nr_bufs_per_log); fflush(NULL); get_log_at_offset(EYEFI_BUF_SIZE*i); debug_printf(1, "done\n"); u32 log_size; u8 *log_data; if (i == 0) { struct first_log_response *log = buf; log_end = be32_to_u32(log->log_end); log_start = be32_to_u32(log->log_start); debug_printf(2, "log end: 0x%04lx\n", log_end); debug_printf(2, "log start: 0x%04lx\n", log_start); log_data = &log->data[0]; log_size = ARRAY_SIZE(log->data); } else { struct rest_log_response *log = buf; log_data = &log->data[0]; log_size = ARRAY_SIZE(log->data); } debug_printf(3, "writing %ld bytes to resbuf[%d]\n", log_size, total_bytes); memcpy(&resbuf[total_bytes], log_data, log_size); total_bytes += log_size; } // The last byte *should* be a null, and the // official software does not print it. for (i = 0; i < total_bytes-1; i++) { int offset = (log_start+i)%total_bytes; char c = resbuf[offset]; // the official software converts UNIX to DOS-style // line breaks, so we'll do the same if (c == '\n') printf("%c", '\r'); printf("%c", c); } printf("\n"); // just some simple sanity checking to make sure what // we are fetching looks valid int null_bytes_left = 20; if (resbuf[log_end] != 0) { fprintf(stderr, "error: unexpected last byte (%ld/0x%lx) of log: %02x\n", log_end, log_end, resbuf[log_end]); for (i=0; i