/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org> * * * This program 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 2 of the License, or * (at your option) any later version. * * 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 #define _GNU_SOURCE #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <getopt.h> #include <signal.h> #include <sys/poll.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <bluetooth/bluetooth.h> #include <bluetooth/hci.h> #include <bluetooth/hci_lib.h> #include <bluetooth/l2cap.h> #include <bluetooth/sdp.h> #include <bluetooth/sdp_lib.h> #include <bluetooth/cmtp.h> #ifdef NEED_PPOLL #include "ppoll.h" #endif static volatile sig_atomic_t __io_canceled = 0; static void sig_hup(int sig) { return; } static void sig_term(int sig) { __io_canceled = 1; } static char *cmtp_state[] = { "unknown", "connected", "open", "bound", "listening", "connecting", "connecting", "config", "disconnecting", "closed" }; static char *cmtp_flagstostr(uint32_t flags) { static char str[100] = ""; strcat(str, "["); if (flags & (1 << CMTP_LOOPBACK)) strcat(str, "loopback"); strcat(str, "]"); return str; } static int get_psm(bdaddr_t *src, bdaddr_t *dst, unsigned short *psm) { sdp_session_t *s; sdp_list_t *srch, *attrs, *rsp; uuid_t svclass; uint16_t attr; int err; if (!(s = sdp_connect(src, dst, 0))) return -1; sdp_uuid16_create(&svclass, CIP_SVCLASS_ID); srch = sdp_list_append(NULL, &svclass); attr = SDP_ATTR_PROTO_DESC_LIST; attrs = sdp_list_append(NULL, &attr); err = sdp_service_search_attr_req(s, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp); sdp_close(s); if (err) return 0; for (; rsp; rsp = rsp->next) { sdp_record_t *rec = (sdp_record_t *) rsp->data; sdp_list_t *protos; if (!sdp_get_access_protos(rec, &protos)) { unsigned short p = sdp_get_proto_port(protos, L2CAP_UUID); if (p > 0) { *psm = p; return 1; } } } return 0; } static int do_connect(int ctl, int dev_id, bdaddr_t *src, bdaddr_t *dst, unsigned short psm, uint32_t flags) { struct cmtp_connadd_req req; struct hci_dev_info di; struct sockaddr_l2 addr; struct l2cap_options opts; socklen_t size; int sk; hci_devinfo(dev_id, &di); if (!(di.link_policy & HCI_LP_RSWITCH)) { printf("Local device is not accepting role switch\n"); } if ((sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) { perror("Can't create L2CAP socket"); exit(1); } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, src); if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("Can't bind L2CAP socket"); close(sk); exit(1); } memset(&opts, 0, sizeof(opts)); size = sizeof(opts); if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &size) < 0) { perror("Can't get L2CAP options"); close(sk); exit(1); } opts.imtu = CMTP_DEFAULT_MTU; opts.omtu = CMTP_DEFAULT_MTU; opts.flush_to = 0xffff; if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) { perror("Can't set L2CAP options"); close(sk); exit(1); } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, dst); addr.l2_psm = htobs(psm); if (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("Can't connect L2CAP socket"); close(sk); exit(1); } req.sock = sk; req.flags = flags; if (ioctl(ctl, CMTPCONNADD, &req) < 0) { perror("Can't create connection"); exit(1); } return sk; } static void cmd_show(int ctl, bdaddr_t *bdaddr, int argc, char **argv) { struct cmtp_connlist_req req; struct cmtp_conninfo ci[16]; char addr[18]; unsigned int i; req.cnum = 16; req.ci = ci; if (ioctl(ctl, CMTPGETCONNLIST, &req) < 0) { perror("Can't get connection list"); exit(1); } for (i = 0; i < req.cnum; i++) { ba2str(&ci[i].bdaddr, addr); printf("%d %s %s %s\n", ci[i].num, addr, cmtp_state[ci[i].state], ci[i].flags ? cmtp_flagstostr(ci[i].flags) : ""); } } static void cmd_search(int ctl, bdaddr_t *bdaddr, int argc, char **argv) { inquiry_info *info = NULL; bdaddr_t src, dst; unsigned short psm; int i, dev_id, num_rsp, length, flags; char addr[18]; uint8_t class[3]; ba2str(bdaddr, addr); dev_id = hci_devid(addr); if (dev_id < 0) { dev_id = hci_get_route(NULL); hci_devba(dev_id, &src); } else bacpy(&src, bdaddr); length = 8; /* ~10 seconds */ num_rsp = 0; flags = 0; printf("Searching ...\n"); num_rsp = hci_inquiry(dev_id, length, num_rsp, NULL, &info, flags); for (i = 0; i < num_rsp; i++) { memcpy(class, (info+i)->dev_class, 3); if ((class[1] == 2) && ((class[0] / 4) == 5)) { bacpy(&dst, &(info+i)->bdaddr); ba2str(&dst, addr); printf("\tChecking service for %s\n", addr); if (!get_psm(&src, &dst, &psm)) continue; bt_free(info); printf("\tConnecting to device %s\n", addr); do_connect(ctl, dev_id, &src, &dst, psm, 0); return; } } bt_free(info); fprintf(stderr, "\tNo devices in range or visible\n"); exit(1); } static void cmd_create(int ctl, bdaddr_t *bdaddr, int argc, char **argv) { bdaddr_t src, dst; unsigned short psm; int dev_id; char addr[18]; if (argc < 2) return; str2ba(argv[1], &dst); ba2str(bdaddr, addr); dev_id = hci_devid(addr); if (dev_id < 0) { dev_id = hci_get_route(&dst); hci_devba(dev_id, &src); } else bacpy(&src, bdaddr); if (argc < 3) { if (!get_psm(&src, &dst, &psm)) psm = 4099; } else psm = atoi(argv[2]); do_connect(ctl, dev_id, &src, &dst, psm, 0); } static void cmd_release(int ctl, bdaddr_t *bdaddr, int argc, char **argv) { struct cmtp_conndel_req req; struct cmtp_connlist_req cl; struct cmtp_conninfo ci[16]; if (argc < 2) { cl.cnum = 16; cl.ci = ci; if (ioctl(ctl, CMTPGETCONNLIST, &cl) < 0) { perror("Can't get connection list"); exit(1); } if (cl.cnum == 0) return; if (cl.cnum != 1) { fprintf(stderr, "You have to specifiy the device address.\n"); exit(1); } bacpy(&req.bdaddr, &ci[0].bdaddr); } else str2ba(argv[1], &req.bdaddr); if (ioctl(ctl, CMTPCONNDEL, &req) < 0) { perror("Can't release connection"); exit(1); } } static void cmd_loopback(int ctl, bdaddr_t *bdaddr, int argc, char **argv) { struct cmtp_conndel_req req; struct sigaction sa; struct pollfd p; sigset_t sigs; bdaddr_t src, dst; unsigned short psm; int dev_id, sk; char addr[18]; if (argc < 2) return; str2ba(argv[1], &dst); ba2str(bdaddr, addr); dev_id = hci_devid(addr); if (dev_id < 0) { dev_id = hci_get_route(&dst); hci_devba(dev_id, &src); } else bacpy(&src, bdaddr); ba2str(&dst, addr); printf("Connecting to %s in loopback mode\n", addr); if (argc < 3) { if (!get_psm(&src, &dst, &psm)) psm = 4099; } else psm = atoi(argv[2]); sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK)); printf("Press CTRL-C for hangup\n"); memset(&sa, 0, sizeof(sa)); sa.sa_flags = SA_NOCLDSTOP; sa.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); sa.sa_handler = sig_term; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sa.sa_handler = sig_hup; sigaction(SIGHUP, &sa, NULL); sigfillset(&sigs); sigdelset(&sigs, SIGCHLD); sigdelset(&sigs, SIGPIPE); sigdelset(&sigs, SIGTERM); sigdelset(&sigs, SIGINT); sigdelset(&sigs, SIGHUP); p.fd = sk; p.events = POLLERR | POLLHUP; while (!__io_canceled) { p.revents = 0; if (ppoll(&p, 1, NULL, &sigs) > 0) break; } bacpy(&req.bdaddr, &dst); ioctl(ctl, CMTPCONNDEL, &req); } static struct { char *cmd; char *alt; void (*func)(int ctl, bdaddr_t *bdaddr, int argc, char **argv); char *opt; char *doc; } command[] = { { "show", "list", cmd_show, 0, "Show remote connections" }, { "search", "scan", cmd_search, 0, "Search for a remote device" }, { "connect", "create", cmd_create, "<bdaddr>", "Connect a remote device" }, { "release", "disconnect", cmd_release, "[bdaddr]", "Disconnect the remote device" }, { "loopback", "test", cmd_loopback, "<bdaddr>", "Loopback test of a device" }, { NULL, NULL, NULL, 0, 0 } }; static void usage(void) { int i; printf("ciptool - Bluetooth Common ISDN Access Profile (CIP)\n\n"); printf("Usage:\n" "\tciptool [options] [command]\n" "\n"); printf("Options:\n" "\t-i [hciX|bdaddr] Local HCI device or BD Address\n" "\t-h, --help Display help\n" "\n"); printf("Commands:\n"); for (i = 0; command[i].cmd; i++) printf("\t%-8s %-10s\t%s\n", command[i].cmd, command[i].opt ? command[i].opt : " ", command[i].doc); printf("\n"); } static struct option main_options[] = { { "help", 0, 0, 'h' }, { "device", 1, 0, 'i' }, { 0, 0, 0, 0 } }; int main(int argc, char *argv[]) { bdaddr_t bdaddr; int i, opt, ctl; bacpy(&bdaddr, BDADDR_ANY); while ((opt = getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) { switch(opt) { case 'i': if (!strncmp(optarg, "hci", 3)) hci_devba(atoi(optarg + 3), &bdaddr); else str2ba(optarg, &bdaddr); break; case 'h': usage(); exit(0); default: exit(0); } } argc -= optind; argv += optind; optind = 0; if (argc < 1) { usage(); return 0; } if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_CMTP)) < 0 ) { perror("Can't open CMTP control socket"); exit(1); } for (i = 0; command[i].cmd; i++) { if (strncmp(command[i].cmd, argv[0], 4) && strncmp(command[i].alt, argv[0], 4)) continue; command[i].func(ctl, &bdaddr, argc, argv); close(ctl); exit(0); } usage(); close(ctl); return 0; }