| /* |
| * |
| * Near Field Communication nfctool |
| * |
| * Copyright (C) 2012 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <glib.h> |
| |
| #include <netlink/genl/genl.h> |
| |
| #include <near/nfc_copy.h> |
| |
| #include "nfctool.h" |
| #include "adapter.h" |
| #include "netlink.h" |
| #include "sniffer.h" |
| |
| #define LLCP_MAX_LTO 0xff |
| #define LLCP_MAX_RW 0x0f |
| #define LLCP_MAX_MIUX 0x7ff |
| |
| static GMainLoop *main_loop = NULL; |
| |
| static int nfctool_poll_cb(guint8 cmd, guint32 idx, gpointer data); |
| static int nfctool_snl_cb(guint8 cmd, guint32 idx, gpointer data); |
| |
| static void nfctool_quit(bool force); |
| |
| static gchar *nfctool_poll_mode_str(int mode) |
| { |
| if (mode == POLLING_MODE_TARGET) |
| return "target"; |
| |
| if (mode == POLLING_MODE_BOTH) |
| return "both initiator and target"; |
| |
| return "initiator"; |
| } |
| |
| static int nfctool_start_poll(void) |
| { |
| int err; |
| |
| struct nfc_adapter *adapter; |
| |
| adapter = adapter_get(opts.adapter_idx); |
| |
| if (!adapter) { |
| print_error("Invalid adapter index: %u", opts.adapter_idx); |
| |
| return -ENODEV; |
| } |
| |
| nl_add_event_handler(NFC_EVENT_TARGETS_FOUND, nfctool_poll_cb); |
| nl_add_event_handler(NFC_EVENT_TM_ACTIVATED, nfctool_poll_cb); |
| |
| err = nl_start_poll(adapter, opts.poll_mode); |
| |
| if (err == 0) { |
| printf("Start polling on nfc%u as %s\n\n", |
| adapter->idx, nfctool_poll_mode_str(opts.poll_mode)); |
| return 0; |
| } |
| |
| if (err != -EBUSY) |
| return err; |
| |
| if (adapter->rf_mode == NFC_RF_NONE) |
| printf("nfc%u already in polling mode\n\n", adapter->idx); |
| else |
| printf("nfc%u already activated\n\n", adapter->idx); |
| |
| /* Don't fail if there is a pending SNL request */ |
| if (opts.snl) |
| return 0; |
| |
| return err; |
| } |
| |
| static int nfctool_set_params(void) |
| { |
| struct nfc_adapter *adapter; |
| int err; |
| |
| adapter = adapter_get(opts.adapter_idx); |
| if (!adapter) |
| return -ENODEV; |
| |
| err = nl_set_params(adapter, opts.lto, opts.rw, opts.miux); |
| if (err) { |
| print_error("Error setting one of the parameters."); |
| goto exit; |
| } |
| |
| nl_get_params(adapter); |
| |
| adapter_print_info(adapter, NULL); |
| |
| exit: |
| return err; |
| } |
| |
| static int nfctool_snl_send_request(struct nfc_adapter *adapter) |
| { |
| int err; |
| |
| nl_add_event_handler(NFC_EVENT_LLC_SDRES, nfctool_snl_cb); |
| |
| err = nl_send_sdreq(adapter, opts.snl_list); |
| if (err) |
| print_error("Can't send SNL request: %s", strerror(-err)); |
| |
| return err; |
| } |
| |
| static int nfctool_set_powered(bool powered) |
| { |
| struct nfc_adapter *adapter; |
| int err; |
| |
| adapter = adapter_get(opts.adapter_idx); |
| if (!adapter) |
| return -ENODEV; |
| |
| err = nl_set_powered(adapter, powered); |
| |
| if (err == 0) |
| adapter->powered = powered; |
| |
| return err; |
| } |
| |
| static int nfctool_fw_download_cb(guint8 cmd, guint32 adapter_idx, |
| gpointer data) |
| { |
| int err; |
| gchar *fw_filename; |
| struct nlattr **nl_attr = data; |
| |
| if (nl_attr[NFC_ATTR_FIRMWARE_DOWNLOAD_STATUS] != NULL) |
| err = nla_get_u32(nl_attr[NFC_ATTR_FIRMWARE_DOWNLOAD_STATUS]); |
| else |
| err = -ENOTSUP; |
| |
| if (nl_attr[NFC_ATTR_FIRMWARE_NAME] != NULL) |
| fw_filename = nla_get_string(nl_attr[NFC_ATTR_FIRMWARE_NAME]); |
| else |
| fw_filename = "UNKNOWN"; |
| |
| printf("Firmware download operation for %s terminated with status %s\n", |
| fw_filename, err ? strerror(-err) : "OK"); |
| |
| nfctool_quit(false); |
| |
| return 0; |
| } |
| |
| static int nfctool_fw_download(gchar *fw_filename) |
| { |
| struct nfc_adapter *adapter; |
| int err; |
| |
| adapter = adapter_get(opts.adapter_idx); |
| if (!adapter) |
| return -ENODEV; |
| |
| nl_add_event_handler(NFC_CMD_FW_DOWNLOAD, nfctool_fw_download_cb); |
| |
| err = nl_fw_download(adapter, fw_filename); |
| if (err) |
| print_error("Firmware download failed: %s", strerror(-err)); |
| |
| return err; |
| } |
| |
| static int nfctool_dep_link_up_cb(guint8 cmd, guint32 idx, gpointer data) |
| { |
| struct nfc_adapter *adapter; |
| |
| printf("Link is UP for adapter nfc%u\n\n", idx); |
| |
| if (idx != opts.adapter_idx) |
| return -ENODEV; |
| |
| adapter = adapter_get(idx); |
| if (!adapter) |
| return -ENODEV; |
| |
| nfctool_snl_send_request(adapter); |
| |
| return 0; |
| } |
| |
| static int nfctool_dep_link_down_cb(guint8 cmd, guint32 idx, gpointer data) |
| { |
| if (idx != opts.adapter_idx) |
| return -ENODEV; |
| |
| printf("Link is DOWN for adapter nfc%u\n\n", idx); |
| |
| opts.snl = false; |
| |
| nfctool_quit(false); |
| |
| return 0; |
| } |
| |
| static int nfctool_snl(void) |
| { |
| struct nfc_adapter *adapter; |
| |
| adapter = adapter_get(opts.adapter_idx); |
| if (!adapter) |
| return -ENODEV; |
| |
| if (adapter->polling) { |
| /* Delay netlink message until the link is established */ |
| nl_add_event_handler(NFC_CMD_DEP_LINK_UP, |
| nfctool_dep_link_up_cb); |
| |
| nl_add_event_handler(NFC_CMD_DEP_LINK_DOWN, |
| nfctool_dep_link_down_cb); |
| |
| return 0; |
| } |
| |
| if (adapter->rf_mode == NFC_RF_NONE) { |
| print_error("Can't send SNL request: No active link"); |
| |
| opts.snl = false; |
| |
| return -ENOLINK; |
| } |
| |
| return nfctool_snl_send_request(adapter); |
| } |
| |
| static void nfctool_send_dep_link_up(gpointer target_idx, gpointer adapter_idx) |
| { |
| nl_send_dep_link_up(GPOINTER_TO_INT(adapter_idx), GPOINTER_TO_INT(target_idx)); |
| } |
| |
| static int nfctool_targets_found(guint32 adapter_idx) |
| { |
| int err; |
| struct nfc_adapter *adapter; |
| |
| DBG("adapter_idx: %u", adapter_idx); |
| |
| if (adapter_idx == INVALID_ADAPTER_IDX) |
| return -ENODEV; |
| |
| adapter = adapter_get(adapter_idx); |
| |
| if (!adapter) |
| return -ENODEV; |
| |
| err = nl_get_targets(adapter); |
| if (err) { |
| print_error("Error getting targets\n"); |
| goto exit; |
| } |
| |
| printf("Targets found for nfc%u\n", adapter_idx); |
| adpater_print_targets(adapter, " "); |
| printf("\n"); |
| |
| if (adapter->polling) { |
| g_slist_foreach(adapter->devices, |
| (GFunc)nfctool_send_dep_link_up, |
| GINT_TO_POINTER(adapter_idx)); |
| |
| adapter->polling = FALSE; |
| } |
| |
| exit: |
| return err; |
| } |
| |
| static int nfctool_poll_cb(guint8 cmd, guint32 idx, gpointer data) |
| { |
| int err = 0; |
| |
| if (idx != opts.adapter_idx) |
| return 0; |
| |
| DBG("cmd: %u, idx: %u", cmd, idx); |
| |
| switch (cmd) { |
| case NFC_EVENT_TARGETS_FOUND: |
| err = nfctool_targets_found(idx); |
| break; |
| case NFC_EVENT_TM_ACTIVATED: |
| printf("Target mode activated\n"); |
| break; |
| } |
| |
| nfctool_quit(false); |
| |
| return err; |
| } |
| |
| static void nfctool_print_and_remove_snl(struct nfc_snl *sdres, |
| gpointer adapter_idx) |
| { |
| GSList *elem; |
| |
| printf(" uri: %s - sap: %d\n", sdres->uri, sdres->sap); |
| |
| if (GPOINTER_TO_UINT(adapter_idx) == opts.adapter_idx) { |
| elem = g_slist_find_custom(opts.snl_list, sdres->uri, |
| (GCompareFunc)g_strcmp0); |
| |
| if (elem) { |
| g_free(elem->data); |
| opts.snl_list = g_slist_delete_link(opts.snl_list, |
| elem); |
| } |
| } |
| } |
| |
| static int nfctool_snl_cb(guint8 cmd, guint32 idx, gpointer data) |
| { |
| GSList *sdres_list = (GSList *)data; |
| |
| printf("nfc%u: Service Name lookup:\n", idx); |
| |
| g_slist_foreach(sdres_list, (GFunc)nfctool_print_and_remove_snl, |
| GINT_TO_POINTER(idx)); |
| |
| printf("\n"); |
| |
| if (!opts.snl_list) { |
| opts.snl = false; |
| nfctool_quit(false); |
| } |
| |
| return 0; |
| } |
| |
| struct nfc_snl *nfctool_snl_alloc(gsize uri_size) |
| { |
| struct nfc_snl *snl; |
| |
| snl = g_malloc(sizeof(struct nfc_snl)); |
| |
| snl->uri = g_malloc0(uri_size); |
| snl->uri_size = uri_size; |
| snl->sap = 0; |
| |
| return snl; |
| } |
| |
| void nfctool_sdres_free(struct nfc_snl *snl) |
| { |
| if (snl->uri_size) |
| g_free(snl->uri); |
| |
| g_free(snl); |
| } |
| |
| static volatile sig_atomic_t __terminated = 0; |
| |
| static void sig_term(int sig) |
| { |
| if (__terminated > 0) |
| return; |
| |
| __terminated = 1; |
| |
| DBG("Terminating"); |
| |
| nfctool_quit(true); |
| } |
| |
| struct nfctool_options opts = { |
| .show_version = FALSE, |
| .list = FALSE, |
| .poll = FALSE, |
| .poll_mode = POLLING_MODE_INITIATOR, |
| .device_name = NULL, |
| .adapter_idx = INVALID_ADAPTER_IDX, |
| .enable_dev = FALSE, |
| .disable_dev = FALSE, |
| .set_param = FALSE, |
| .lto = -1, |
| .rw = -1, |
| .miux = -1, |
| .need_netlink = FALSE, |
| .snl = FALSE, |
| .snl_list = NULL, |
| .sniff = FALSE, |
| .snap_len = 0, |
| .dump_symm = FALSE, |
| .show_timestamp = SNIFFER_SHOW_TIMESTAMP_NONE, |
| .snep_sap = 0x04, |
| .pcap_filename = NULL, |
| }; |
| |
| static bool opt_parse_poll_arg(const gchar *option_name, const gchar *value, |
| gpointer data, GError **error) |
| { |
| opts.poll = true; |
| |
| opts.poll_mode = POLLING_MODE_INITIATOR; |
| |
| if (value) { |
| if (*value == 't' || *value == 'T') |
| opts.poll_mode = POLLING_MODE_TARGET; |
| else if (*value == 'b' || *value == 'B') |
| opts.poll_mode = POLLING_MODE_BOTH; |
| } |
| |
| return true; |
| } |
| |
| static bool opt_parse_set_param_arg(const gchar *option_name, |
| const gchar *value, |
| gpointer data, GError **error) |
| { |
| gchar **params = NULL, **keyval = NULL; |
| gchar *end; |
| gint i, intval; |
| bool result; |
| |
| params = g_strsplit(value, ",", -1); |
| |
| i = 0; |
| while (params[i]) { |
| keyval = g_strsplit(params[i], "=", 2); |
| |
| if (!keyval[0] || !keyval[1]) { |
| result = false; |
| goto exit; |
| } |
| |
| intval = strtol(keyval[1], &end, 10); |
| if (keyval[1] == end) { |
| result = false; |
| goto exit; |
| } |
| |
| if (g_ascii_strcasecmp(keyval[0], "lto") == 0) { |
| if (intval < 0 || intval > LLCP_MAX_LTO) { |
| print_error("Bad value: max lto value is %d", |
| LLCP_MAX_LTO); |
| result = false; |
| goto exit; |
| } |
| |
| opts.lto = intval; |
| } else if (g_ascii_strcasecmp(keyval[0], "rw") == 0) { |
| if (intval < 0 || intval > LLCP_MAX_RW) { |
| print_error("Bad value: max rw value is %d", |
| LLCP_MAX_RW); |
| result = false; |
| goto exit; |
| } |
| |
| opts.rw = intval; |
| } else if (g_ascii_strcasecmp(keyval[0], "miux") == 0) { |
| if (intval < 0 || intval > LLCP_MAX_MIUX) { |
| print_error("Bad value: max miux value is %d", |
| LLCP_MAX_MIUX); |
| result = false; |
| goto exit; |
| } |
| |
| opts.miux = intval; |
| } else { |
| result = false; |
| goto exit; |
| } |
| |
| opts.set_param = true; |
| |
| g_strfreev(keyval); |
| keyval = NULL; |
| |
| i++; |
| } |
| |
| result = true; |
| |
| exit: |
| if (params) |
| g_strfreev(params); |
| |
| if (keyval) |
| g_strfreev(keyval); |
| |
| return result; |
| } |
| |
| static bool opt_parse_show_timestamp_arg(const gchar *option_name, |
| const gchar *value, |
| gpointer data, GError **error) |
| { |
| if (value && (*value == 'a' || *value == 'A')) |
| opts.show_timestamp = SNIFFER_SHOW_TIMESTAMP_ABS; |
| else |
| opts.show_timestamp = SNIFFER_SHOW_TIMESTAMP_DELTA; |
| |
| return true; |
| } |
| |
| static bool opt_parse_snl_arg(const gchar *option_name, const gchar *value, |
| gpointer data, GError **error) |
| { |
| gchar *uri; |
| |
| opts.snl = true; |
| |
| uri = g_strdup(value); |
| |
| opts.snl_list = g_slist_prepend(opts.snl_list, uri); |
| |
| return true; |
| } |
| |
| static GOptionEntry option_entries[] = { |
| { "version", 'v', 0, G_OPTION_ARG_NONE, &opts.show_version, |
| "show version information and exit" }, |
| { "list", 'l', 0, G_OPTION_ARG_NONE, &opts.list, |
| "list attached NFC devices", NULL }, |
| { "device", 'd', 0, G_OPTION_ARG_STRING, &opts.device_name, |
| "specify a nfc device", "nfcX" }, |
| { "poll", 'p', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, |
| opt_parse_poll_arg, "start polling as initiator, target, or both; " |
| "default mode is initiator", "[Initiator|Target|Both]" }, |
| { "enable", '1', 0, G_OPTION_ARG_NONE, &opts.enable_dev, |
| "enable device", NULL }, |
| { "disable", '0', 0, G_OPTION_ARG_NONE, &opts.disable_dev, |
| "disable device", NULL }, |
| { "fw-download", 'w', 0, G_OPTION_ARG_STRING, &opts.fw_filename, |
| "Put the device in firmware download mode", "fw_filename" }, |
| { "set-param", 's', 0, G_OPTION_ARG_CALLBACK, opt_parse_set_param_arg, |
| "set lto, rw, and/or miux parameters", "lto=150,rw=1,miux=100" }, |
| { "snl", 'k', 0, G_OPTION_ARG_CALLBACK, &opt_parse_snl_arg, |
| "Send a Service Name Lookup request", "urn:nfc:sn:snep"}, |
| { "sniff", 'n', 0, G_OPTION_ARG_NONE, &opts.sniff, |
| "start LLCP sniffer on the specified device", NULL }, |
| { "snapshot-len", 'a', 0, G_OPTION_ARG_INT, &opts.snap_len, |
| "packet snapshot length (in bytes); only relevant with -n", "1024" }, |
| { "dump-symm", 'y', 0, G_OPTION_ARG_NONE, &opts.dump_symm, |
| "dump SYMM packets to stdout (flooding); only relevant with -n", |
| NULL }, |
| { "snep-sap", 'e', 0, G_OPTION_ARG_INT, &opts.snep_sap, |
| "Specify the sap number to be used for snep decoding; " |
| "only relevant with -n", "0x04" }, |
| { "show-timestamp", 't', G_OPTION_FLAG_OPTIONAL_ARG, |
| G_OPTION_ARG_CALLBACK, opt_parse_show_timestamp_arg, |
| "show packet timestamp as the delta from first frame (default) " |
| "or absolute value; only relevant with -n", "[delta|abs]" }, |
| { "pcap-file", 'f', 0, G_OPTION_ARG_STRING, &opts.pcap_filename, |
| "specify a filename to save traffic in pcap format; " |
| "only relevant with -n", "filename" }, |
| { NULL } |
| }; |
| |
| static int nfctool_options_parse(int argc, char **argv) |
| { |
| GOptionContext *context; |
| GError *error = NULL; |
| gchar *start, *end; |
| int err = -EINVAL; |
| |
| context = g_option_context_new("- A small NFC tool box"); |
| |
| g_option_context_add_main_entries(context, option_entries, NULL); |
| |
| if (!g_option_context_parse(context, &argc, &argv, &error)) { |
| print_error("%s: %s", argv[0], error->message); |
| |
| g_error_free(error); |
| |
| goto exit; |
| } |
| |
| if (opts.show_version) { |
| printf("%s\n", VERSION); |
| goto done; |
| } |
| |
| if (opts.device_name) { |
| if (strncmp("nfc", opts.device_name, 3) != 0) { |
| print_error("Invalid device name: %s", |
| opts.device_name); |
| |
| goto exit; |
| } |
| |
| start = opts.device_name + 3; |
| |
| opts.adapter_idx = strtol(start, &end, 10); |
| if (start == end) { |
| print_error("Invalid NFC adapter %s", opts.device_name); |
| |
| goto exit; |
| } |
| } |
| |
| if (opts.enable_dev || opts.disable_dev) |
| opts.list = true; |
| |
| if (opts.poll) |
| opts.enable_dev = true; |
| |
| opts.need_netlink = opts.list || opts.poll || opts.set_param || |
| opts.snl || opts.fw_filename; |
| |
| if (!opts.need_netlink && !opts.sniff) { |
| printf("%s", g_option_context_get_help(context, TRUE, NULL)); |
| |
| goto exit; |
| } |
| |
| if ((opts.poll || opts.set_param || opts.sniff || opts.snl || |
| opts.enable_dev || opts.disable_dev || opts.fw_filename) && |
| opts.adapter_idx == INVALID_ADAPTER_IDX) { |
| print_error("Please specify a device with -d nfcX option"); |
| |
| goto exit; |
| } |
| |
| done: |
| err = 0; |
| |
| exit: |
| g_option_context_free(context); |
| |
| return err; |
| } |
| |
| static void nfctool_main_loop_start(void) |
| { |
| struct sigaction sa; |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_handler = sig_term; |
| sigaction(SIGINT, &sa, NULL); |
| sigaction(SIGTERM, &sa, NULL); |
| |
| main_loop = g_main_loop_new(NULL, FALSE); |
| |
| g_main_loop_run(main_loop); |
| } |
| |
| static void nfctool_options_cleanup(void) |
| { |
| if (opts.device_name) |
| g_free(opts.device_name); |
| |
| if (opts.pcap_filename) |
| g_free(opts.pcap_filename); |
| |
| if (opts.fw_filename != NULL) |
| g_free(opts.fw_filename); |
| |
| g_slist_free_full(opts.snl_list, g_free); |
| } |
| |
| static void nfctool_main_loop_clean(void) |
| { |
| if (main_loop) |
| g_main_loop_unref(main_loop); |
| } |
| |
| static void nfctool_quit(bool force) |
| { |
| if (force || (!opts.sniff && !opts.snl)) |
| g_main_loop_quit(main_loop); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int err; |
| |
| err = nfctool_options_parse(argc, argv); |
| if (err) |
| goto exit_err; |
| |
| if (opts.show_version) |
| goto done; |
| |
| adapter_init(); |
| |
| if (opts.need_netlink) { |
| err = nl_init(); |
| if (err) |
| goto exit_err; |
| |
| err = adapter_all_get_devices(); |
| if (err) |
| goto exit_err; |
| } |
| |
| if (opts.fw_filename) { |
| err = nfctool_fw_download(opts.fw_filename); |
| if (err) |
| goto exit_err; |
| |
| goto start_loop; |
| } |
| |
| if (opts.sniff) { |
| err = sniffer_init(); |
| if (err) |
| goto exit_err; |
| } |
| |
| if (opts.enable_dev || opts.disable_dev) { |
| err = nfctool_set_powered(opts.enable_dev); |
| |
| if (err && err != -EALREADY) |
| goto exit_err; |
| |
| err = 0; |
| } |
| |
| if (opts.list && !opts.set_param) |
| adapter_idx_print_info(opts.adapter_idx); |
| |
| if (opts.set_param) { |
| err = nfctool_set_params(); |
| if (err) |
| goto exit_err; |
| } |
| |
| if (opts.poll) { |
| err = nfctool_start_poll(); |
| |
| if (err == -EBUSY && opts.sniff) |
| err = 0; |
| |
| if (err) |
| goto exit_err; |
| } |
| |
| if (opts.snl) |
| nfctool_snl(); |
| |
| start_loop: |
| if (opts.poll || opts.sniff || opts.snl || opts.fw_filename) |
| nfctool_main_loop_start(); |
| |
| done: |
| err = 0; |
| |
| exit_err: |
| nfctool_main_loop_clean(); |
| |
| adapter_cleanup(); |
| |
| nl_cleanup(); |
| |
| sniffer_cleanup(); |
| |
| nfctool_options_cleanup(); |
| |
| if (err) |
| print_error("%s", strerror(-err)); |
| |
| return err; |
| } |