/* dund - Bluetooth LAN/DUN daemon for BlueZ Copyright (C) 2002 Maxim Krasnyansky 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * $Id$ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dund.h" #include "lib.h" volatile sig_atomic_t __io_canceled; /* MS dialup networking support (i.e. CLIENT / CLIENTSERVER thing) */ static int msdun = 0; static char *pppd = "/usr/sbin/pppd"; static char *pppd_opts[DUN_MAX_PPP_OPTS] = { /* First 3 are reserved */ "", "", "", "noauth", "noipdefault", NULL }; static int detach = 1; static int persist; static int use_sdp = 1; static int encrypt; static int master; static int search_duration = 10; static uint use_cache; static int channel; static struct { uint valid; char dst[40]; bdaddr_t bdaddr; int channel; } cache; static bdaddr_t src_addr = *BDADDR_ANY; static int src_dev = -1; volatile int terminate; enum { NONE, SHOW, LISTEN, CONNECT, KILL } modes; static int do_listen(void) { struct sockaddr_rc sa; int sk; if (!channel) channel = DUN_DEFAULT_CHANNEL; if (use_sdp) dun_sdp_register(channel); // Create RFCOMM socket sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); if (sk < 0) { syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)", strerror(errno), errno); return -1; } sa.rc_family = AF_BLUETOOTH; sa.rc_channel = channel; sa.rc_bdaddr = src_addr; if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) { syslog(LOG_ERR, "Bind failed. %s(%d)", strerror(errno), errno); return -1; } listen(sk, 10); while (!terminate) { int alen = sizeof(sa), nsk; char ba[40]; char ch[10]; nsk = accept(sk, (struct sockaddr *) &sa, &alen); if (nsk < 0) { syslog(LOG_ERR, "Accept failed. %s(%d)", strerror(errno), errno); continue; } switch (fork()) { case 0: break; case -1: syslog(LOG_ERR, "Fork failed. %s(%d)", strerror(errno), errno); default: close(nsk); continue; } if (msdun && ms_dun(nsk, 1, msdun) < 0) { syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno); exit(0); } ba2str(&sa.rc_bdaddr, ba); sprintf(ch, "%d", channel); /* Setup environment */ setenv("DUN_BDADDR", ba, 1); setenv("DUN_CHANNEL", ch, 1); if (!dun_open_connection(nsk, pppd, pppd_opts, 0)) syslog(LOG_INFO, "New connection from %s", ba); close(nsk); exit(0); } if (use_sdp) dun_sdp_unregister(); return 0; } /* Connect and initiate RFCOMM session * Returns: * -1 - critical error (exit persist mode) * 1 - non critical error * 0 - success */ static int create_connection(char *dst, bdaddr_t *bdaddr) { struct sockaddr_rc sa; int sk, err = 0, ch; if (use_cache && cache.valid && cache.channel) { /* Use cached channel */ ch = cache.channel; } else if (!channel) { syslog(LOG_INFO, "Searching for %s on %s", "LAP", dst); if (dun_sdp_search(&src_addr, bdaddr, &ch) <= 0) return 0; } else ch = channel; syslog(LOG_INFO, "Connecting to %s channel %d", dst, ch); sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); if (sk < 0) { syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)", strerror(errno), errno); return -1; } sa.rc_family = AF_BLUETOOTH; sa.rc_channel = 0; sa.rc_bdaddr = src_addr; if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) syslog(LOG_ERR, "Bind failed. %s(%d)", strerror(errno), errno); sa.rc_channel = ch; sa.rc_bdaddr = *bdaddr; if (!connect(sk, (struct sockaddr *) &sa, sizeof(sa)) ) { syslog(LOG_INFO, "Connection established"); if (msdun && ms_dun(sk, 0, msdun) < 0) { syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno); err = 1; goto out; } if (!dun_open_connection(sk, pppd, pppd_opts, (persist > 0))) err = 0; else err = 1; } else { syslog(LOG_ERR, "Connect to %s failed. %s(%d)", dst, strerror(errno), errno); err = 1; } out: if (use_cache) { if (!err) { /* Succesesful connection, validate cache */ strcpy(cache.dst, dst); bacpy(&cache.bdaddr, bdaddr); cache.channel = ch; cache.valid = use_cache; } else { cache.channel = 0; cache.valid--; } } close(sk); return err; } /* Search and connect * Returns: * -1 - critical error (exit persist mode) * 1 - non critical error * 0 - success */ static int do_connect(void) { inquiry_info *ii; int reconnect = 0; int i, n, r = 0; do { if (reconnect) sleep(persist); reconnect = 1; if (cache.valid) { /* Use cached bdaddr */ r = create_connection(cache.dst, &cache.bdaddr); if (r < 0) { terminate = 1; break; } continue; } syslog(LOG_INFO, "Inquiring"); /* FIXME: Should we use non general LAP here ? */ ii = NULL; n = hci_inquiry(src_dev, search_duration, 10, NULL, &ii, 0); if (n < 0) { syslog(LOG_ERR, "Inquiry failed. %s(%d)", strerror(errno), errno); continue; } for (i = 0; i < n; i++) { char dst[40]; ba2str(&ii[i].bdaddr, dst); r = create_connection(dst, &ii[i].bdaddr); if (r < 0) { terminate = 1; break; } } free(ii); } while (!terminate && persist); return r; } static void do_show(void) { dun_show_connections(); } static void do_kill(char *dst) { if (dst) { bdaddr_t ba; str2ba(dst, &ba); dun_kill_connection((void *) &ba); } else dun_kill_all_connections(); } void sig_hup(int sig) { return; } void sig_term(int sig) { io_cancel(); terminate = 1; } static struct option main_lopts[] = { { "help", 0, 0, 'h' }, { "listen", 0, 0, 's' }, { "connect", 1, 0, 'c' }, { "search", 2, 0, 'Q' }, { "kill", 1, 0, 'k' }, { "killall", 0, 0, 'K' }, { "channel", 1, 0, 'P' }, { "source", 1, 0, 'S' }, { "nosdp", 0, 0, 'D' }, { "list", 0, 0, 'l' }, { "show", 0, 0, 'l' }, { "nodetach", 0, 0, 'n' }, { "persist", 2, 0, 'p' }, { "encrypt", 0, 0, 'E' }, { "master", 0, 0, 'M' }, { "cache", 0, 0, 'C' }, { "pppd", 1, 0, 'd' }, { "msdun", 2, 0, 'X' }, { 0, 0, 0, 0 } }; static char main_sopts[] = "hsc:k:Kr:S:lnp::DQ::EMP:C::P:X"; static char main_help[] = "LAP (LAN Access over PPP) daemon version " VERSION " \n" "Usage:\n" "\tdund [pppd options]\n" "Options:\n" "\t--show --list -l Show active LAP connections\n" "\t--listen -s Listen for LAP connections\n" "\t--connect -c Create LAP connection\n" "\t--search -Q[duration] Search and connect\n" "\t--kill -k Kill LAP connection\n" "\t--killall -K Kill all LAP connections\n" "\t--channel -P RFCOMM channel\n" "\t--source -S Source bdaddr\n" "\t--nosdp -D Disable SDP\n" "\t--nodetach -n Do not become a daemon\n" "\t--persist -p[interval] Persist mode\n" "\t--pppd -d Location of the PPP daemon (pppd)\n" "\t--msdun -X[timeo] Enable Microsoft dialup networking support\n" "\t--cache -C[valid] Enable addess cache\n"; int main(int argc, char **argv) { char *dst = NULL, *src = NULL; struct sigaction sa; int mode = NONE; int opt; while ((opt=getopt_long(argc, argv, main_sopts, main_lopts, NULL)) != -1) { switch(opt) { case 'l': mode = SHOW; detach = 0; break; case 's': mode = LISTEN; break; case 'c': mode = CONNECT; dst = strdup(optarg); break; case 'Q': mode = CONNECT; dst = NULL; if (optarg) search_duration = atoi(optarg); break; case 'k': mode = KILL; detach = 0; dst = strdup(optarg); break; case 'K': mode = KILL; detach = 0; dst = NULL; break; case 'P': channel = atoi(optarg); break; case 'S': src = strdup(optarg); break; case 'D': use_sdp = 0; break; case 'E': encrypt = 1; break; case 'M': master = 1; break; case 'n': detach = 0; break; case 'p': if (optarg) persist = atoi(optarg); else persist = 5; break; case 'C': if (optarg) use_cache = atoi(optarg); else use_cache = 2; break; case 'd': pppd = strdup(optarg); break; case 'X': if (optarg) msdun = atoi(optarg); else msdun = 10; break; case 'h': default: printf(main_help); exit(0); } } argc -= optind; argv += optind; /* The rest is pppd options */ if (argc > 0) { for (opt = 3; argc && opt < DUN_MAX_PPP_OPTS; argc--, opt++) pppd_opts[opt] = *argv++; pppd_opts[opt] = NULL; } io_init(); if (dun_init()) return -1; /* Check non daemon modes first */ switch (mode) { case SHOW: do_show(); return 0; case KILL: do_kill(dst); return 0; case NONE: printf(main_help); return 0; } /* Initialize signals */ 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); if (detach) { int fd; if (fork()) exit(0); /* Direct stdin,stdout,stderr to '/dev/null' */ fd = open("/dev/null", O_RDWR); dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); setsid(); chdir("/"); } openlog("dund", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); syslog(LOG_INFO, "DUN daemon ver %s", VERSION); if (src) { src_dev = hci_devid(src); if (src_dev < 0 || hci_devba(src_dev, &src_addr) < 0) { syslog(LOG_ERR, "Invalid source. %s(%d)", strerror(errno), errno); return -1; } } if (dst) { strncpy(cache.dst, dst, sizeof(cache.dst) - 1); str2ba(dst, &cache.bdaddr); /* Disable cache invalidation */ use_cache = cache.valid = ~0; } switch (mode) { case CONNECT: do_connect(); break; case LISTEN: do_listen(); break; } return 0; }