/*
* btxml.c
*
* Creates a backup of the Nokia 6310i via bluetooth. Outputs data to
* stdout in xml format. This is plug'n'play, no need to enter any data
* on the host or phone side.
* Just saw that it somehow works for Ericsson T610 and T68i, too. They
* don't support text mode sms... :-(
*
* Copyright (C) 2004 by Andreas Oberritter <obi@saftware.de>
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* rev 0.3 (2004/02/14)
* - ATE0 to disable echo on ericsson
*
* rev 0.2 (2004/02/14)
* - set auth & encrypt to off
*
* rev 0.1 (2004/02/12)
* - initial release
*
* TODO: pdu parser for sms
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/rfcomm.h>
/******************************************************************************/
#define CACHE_TIMEOUT 60
#define CACHE_SIZE_MAX 0x10000
struct cache_item {
bdaddr_t addr;
time_t time;
bool valid;
};
static enum {
MANUF_UNKNOWN,
MANUF_ERICSSON,
MANUF_NOKIA,
} manuf;
static struct cache_item cache[CACHE_SIZE_MAX];
static size_t cache_size;
/******************************************************************************/
static void bt_cache_add(bdaddr_t *addr)
{
struct cache_item *item;
for (item = &cache[0]; item < &cache[CACHE_SIZE_MAX]; item++) {
if (item->valid)
continue;
bacpy(&item->addr, addr);
item->time = time(NULL);
item->valid = true;
cache_size++;
}
}
/******************************************************************************/
static void bt_cache_clear(void)
{
struct cache_item *item;
time_t now;
size_t removed = 0;
size_t count = 0;
now = time(NULL);
for (item = &cache[0]; item < &cache[CACHE_SIZE_MAX]; item++) {
if (count == cache_size)
break;
if (!item->valid)
continue;
count++;
if (now - item->time < CACHE_TIMEOUT)
continue;
item->valid = false;
removed++;
}
cache_size -= removed;
}
/******************************************************************************/
static bool bt_cache_find(bdaddr_t *addr)
{
struct cache_item *item;
size_t count = 0;
for (item = &cache[0]; item < &cache[CACHE_SIZE_MAX]; item++) {
if (count == cache_size)
break;
if (!item->valid)
continue;
if (!bacmp(&item->addr, addr))
return true;
count++;
}
return false;
}
/******************************************************************************/
static void at_send(FILE *fp, const char *fmt, va_list ap)
{
fprintf(fp, "AT");
vfprintf(fp, fmt, ap);
fprintf(fp, "\r\n");
}
/******************************************************************************/
static ssize_t at_recv(FILE *fp, char *dest)
{
char *line = NULL;
size_t len = 0;
size_t ret = 0;
ssize_t read;
while ((read = getline(&line, &len, fp)) != -1) {
if (!read)
continue;
if ((line[read - 1] == '\n') && (--read == 0))
continue;
if ((line[read - 1] == '\r') && (--read == 0))
continue;
line[read++] = '\0';
if (!strcmp(line, "OK"))
break;
if ((!strcmp(line, "ERROR")) ||
(!strncmp(line, "+CME ERROR:", 10)) ||
(!strncmp(line, "+CMS ERROR:", 10))) {
ret = -1;
break;
}
if (dest) {
if (ret)
dest[-1] = ' ';
memcpy(dest, line, read);
dest += read;
}
ret++;
}
free(line);
return ret;
}
/******************************************************************************/
static int at_cmd(FILE *fp, char *buf, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
at_send(fp, fmt, ap);
va_end(ap);
return at_recv(fp, buf);
}
/******************************************************************************/
static int at_parse_phonebook_entry(FILE *fp, size_t num)
{
char buf[0x1000], *ptr, *start, *end;
char *number_ptr, *name_ptr;
ssize_t number_len, name_len;
if (at_cmd(fp, buf, "+CPBR=%u", num) != 1)
return -1;
ptr = buf;
if (!strncmp(ptr, "+CPBR: ", 7))
ptr += 7;
puts("\t\t<contact>");
if (((start = strchr(ptr, '\"'))) && (end = strchr(++start, '\"'))) {
number_ptr = start;
number_len = end - start;
}
else {
number_ptr = NULL;
}
if (((start = strchr(++end, '\"'))) &&
(end = strrchr(&ptr[strlen(ptr) - 1], '\"'))) {
name_ptr = ++start;
name_len = end - start;
}
else {
name_ptr = NULL;
}
if ((number_ptr) && (name_ptr)) {
printf("\t\t\t<name>%.*s</name>\n", name_len, name_ptr);
printf("\t\t\t<number>%.*s</number>\n", number_len, number_ptr);
}
else {
printf("\t\t\t<raw>%s</raw>\n", ptr);
}
puts("\t\t</contact>");
fflush(stdout);
return 0;
}
/******************************************************************************/
static int at_parse_phonebook(FILE *fp, const char *name)
{
char buf[0x1000], *ptr;
size_t start, end, used, size, i, found;
if (at_cmd(fp, NULL, "+CPBS=%s", name) != 0)
return -1;
if ((manuf == MANUF_NOKIA) || (manuf == MANUF_UNKNOWN)) {
if (at_cmd(fp, buf, "+CPBS?") != 1)
return -1;
if (!(ptr = strchr(buf, ',')))
return -1;
if (sscanf(++ptr, "%u,%u", &used, &size) != 2)
return -1;
if (!used)
return -1;
}
if (at_cmd(fp, buf, "+CPBR=?") != 1)
return -1;
if (sscanf(buf, "+CPBR: (%u-%u)", &start, &end) != 2)
return -1;
if (manuf == MANUF_ERICSSON) {
// FIXME
used = size = end;
}
printf("\t<phonebook name=%s size=\"%u\">\n", name, size);
for (i = start, found = 0; i <= end && found < used; i++)
if (!at_parse_phonebook_entry(fp, i))
found++;
printf("\t</phonebook>\n");
fflush(stdout);
return 0;
}
/******************************************************************************/
static int at_parse_brackets(FILE *fp, char *buf, int (*cb)(FILE*, const char*))
{
char *start, *end, *str;
if ((!(start = strchr(buf, '('))) || (!(end = strchr(++start, ')'))))
return -1;
*end = '\0';
while ((str = strsep(&start, ",")))
cb(fp, str);
return 0;
}
/******************************************************************************/
static int at_parse_phonebook_list(FILE *fp)
{
char buf[0x1000];
if (at_cmd(fp, buf, "+CPBS=?") != 1)
return -1;
return at_parse_brackets(fp, buf, at_parse_phonebook);
}
/******************************************************************************/
static int at_parse_manufacturer_identification(FILE *fp)
{
char buf[0x1000];
if (at_cmd(fp, buf, "+GMI") < 1)
return -1;
if (strstr(buf, "Ericsson"))
manuf = MANUF_ERICSSON;
else if (strstr(buf, "Nokia"))
manuf = MANUF_NOKIA;
else
manuf = MANUF_UNKNOWN;
printf("\t<manufacturer>%s</manufacturer>\n", buf);
return 0;
}
/******************************************************************************/
static int at_parse_model_identification(FILE *fp)
{
char buf[0x1000];
if (at_cmd(fp, buf, "+GMM") < 1)
return -1;
printf("\t<model>%s</model>\n", buf);
return 0;
}
/******************************************************************************/
static int at_parse_revision_identification(FILE *fp)
{
char buf[0x1000];
if (at_cmd(fp, buf, "+GMR") < 1)
return -1;
printf("\t<revision>%s</revision>\n", buf);
return 0;
}
/******************************************************************************/
static int at_parse_psn_identification(FILE *fp)
{
char buf[0x1000];
if (at_cmd(fp, buf, "+GSN") < 1)
return -1;
printf("\t<imei>%s</imei>\n", buf);
return 0;
}
/******************************************************************************/
static int at_parse_identification(FILE *fp)
{
at_parse_manufacturer_identification(fp);
at_parse_model_identification(fp);
at_parse_revision_identification(fp);
at_parse_psn_identification(fp);
fflush(stdout);
return 0;
}
/******************************************************************************/
static int at_parse_message(FILE *fp, size_t num)
{
char buf[0x1000], *ptr;
if (at_cmd(fp, buf, "+CMGR=%u", num) < 1)
return -1;
ptr = buf;
if (!strncmp(ptr, "+CMGR: ", 7))
ptr += 7;
printf("\t\t<message>%s</message>\n", ptr);
fflush(stdout);
return 0;
}
/******************************************************************************/
static int at_parse_message_storage(FILE *fp, const char *name)
{
char buf[0x1000];
size_t i, msgnum, size;
if (at_cmd(fp, buf, "+CPMS=%s", name) != 1)
return -1;
if (sscanf(buf, "+CPMS: %u,%u", &msgnum, &size) != 2)
return -1;
printf("\t<msgstorage name=%s>\n", name);
for (i = 1; i <= msgnum; i++)
at_parse_message(fp, i);
puts("\t</msgstorage>");
return 0;
}
/******************************************************************************/
static int at_parse_message_list(FILE *fp)
{
char buf[0x1000];
if (at_cmd(fp, NULL, "+CMGF=1") != 0)
return -1;
if (at_cmd(fp, buf, "+CPMS=?") != 1)
return -1;
return at_parse_brackets(fp, buf, at_parse_message_storage);
}
static int at_disable_echo(FILE *fp)
{
at_cmd(fp, NULL, "E0");
}
/******************************************************************************/
static int bt_rfcomm_config(int fd)
{
struct termios t;
int ret;
if ((ret = tcgetattr(fd, &t)))
perror("tcgetattr");
else {
t.c_iflag = IGNBRK;
t.c_oflag = 0;
t.c_cflag = CLOCAL | CREAD | CS8 | B115200;
t.c_lflag = 0;
t.c_line = 0;
t.c_ispeed = B115200;
t.c_ospeed = B115200;
if ((ret = tcsetattr(fd, TCSADRAIN, &t)))
perror("tcsetattr");
}
return ret;
}
/******************************************************************************/
static int bt_rfcomm(int dev_id)
{
static const char *rfcomm_fmt = "/dev/bluetooth/rfcomm/%u";
char filename[FILENAME_MAX];
FILE *fp = NULL;
snprintf(filename, FILENAME_MAX, rfcomm_fmt, dev_id);
if (!(fp = fopen(filename, "r+"))) {
perror(filename);
return -1;
}
if (bt_rfcomm_config(fileno(fp)) == 0) {
at_disable_echo(fp);
at_parse_identification(fp);
at_parse_phonebook_list(fp);
at_parse_message_list(fp);
sleep(1);
}
fclose(fp);
return 0;
}
/******************************************************************************/
static int bt_release(int sock, int dev_id)
{
struct rfcomm_dev_req req;
int ret;
req.dev_id = dev_id;
req.flags = 0;
bacpy(&req.src, BDADDR_ANY);
bacpy(&req.dst, BDADDR_ANY);
req.channel = 0;
if ((ret = ioctl(sock, RFCOMMRELEASEDEV, &req)))
perror("RFCOMMRELEASEDEV");
return ret;
}
/******************************************************************************/
static int bt_bind(int sock, int dev_id, bdaddr_t *bdaddr)
{
struct rfcomm_dev_req req;
int ret;
req.dev_id = dev_id;
req.flags = 0;
bacpy(&req.src, BDADDR_ANY);
bacpy(&req.dst, bdaddr);
req.channel = 17; // 18
if (ioctl(sock, RFCOMMCREATEDEV, &req) == 0)
return 0;
if (errno != EADDRINUSE)
perror("RFCOMMCREATEDEV");
else if ((ret = bt_release(sock, dev_id)))
;
else if ((ret = ioctl(sock, RFCOMMCREATEDEV, &req)))
perror("RFCOMMCREATEDEV");
return ret;
}
/******************************************************************************/
static int scan(int dev_id, int s)
{
inquiry_info *info = NULL;
int max, len, flags;
char addr[18], name[256];
int i, sock;
len = 4;
max = 100;
flags = IREQ_CACHE_FLUSH;
max = hci_inquiry(dev_id, len, max, NULL, &info, flags);
if (max == -1) {
perror("hci_inquiry");
return -1;
}
for (i = 0; i < max; i++) {
if (bt_cache_find(&info[i].bdaddr))
continue;
sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM);
if (sock == -1) {
perror("socket");
continue;
}
if (bt_bind(sock, dev_id, &info[i].bdaddr))
continue;
if (hci_read_remote_name(s, &info[i].bdaddr, sizeof(name), name, 2))
name[0] = '\0';
ba2str(&info[i].bdaddr, addr);
printf("<phone btaddr=\"%s\" name=\"%s\">\n", addr, name);
fflush(stdout);
bt_rfcomm(dev_id);
bt_release(sock, dev_id);
close(sock);
puts("</phone>");
fflush(stdout);
bt_cache_add(&info[i].bdaddr);
}
free(info);
return 0;
}
/******************************************************************************/
static bool bt_set_auth(int dev_id, int s)
{
struct hci_dev_req dr;
int ret;
dr.dev_id = dev_id;
dr.dev_opt = AUTH_DISABLED;
if ((ret = ioctl(s, HCISETAUTH, &dr)))
perror("HCISETAUTH");
return (ret == 0);
}
/******************************************************************************/
static bool bt_set_encrypt(int dev_id, int s)
{
struct hci_dev_req dr;
int ret;
dr.dev_id = dev_id;
dr.dev_opt = ENCRYPT_DISABLED;
if ((ret = ioctl(s, HCISETENCRYPT, &dr)))
perror("HCISETENCRYPT");
return (ret == 0);
}
/******************************************************************************/
static bool bt_set_name(int s)
{
change_local_name_cp cp;
int ret;
memset(cp.name, ' ', CHANGE_LOCAL_NAME_CP_SIZE);
ret = hci_send_cmd(s, OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME,
CHANGE_LOCAL_NAME_CP_SIZE, (void *) &cp);
if (ret == -1)
perror("OCF_CHANGE_LOCAL_NAME");
return (ret == 0);
}
/******************************************************************************/
static bool bt_configure(int dev_id, int s)
{
return (bt_set_auth(dev_id, s) &&
bt_set_encrypt(dev_id, s) &&
bt_set_name(s));
}
/******************************************************************************/
int main(void)
{
int dev_id, s;
time_t now, prev;
if ((dev_id = hci_get_route(NULL)) == -1) {
perror("hci_get_route");
return EXIT_FAILURE;
}
if ((s = hci_open_dev(dev_id)) == -1) {
perror("hci_open_dev");
return -1;
}
bt_configure(dev_id, s);
prev = time(NULL);
puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
while (1) {
scan(dev_id, s);
now = time(NULL);
if (now != prev) {
bt_cache_clear();
prev = now;
}
else {
usleep(0);
}
}
if (hci_close_dev(s))
perror("hci_close_dev");
return EXIT_SUCCESS;
}