xref: /netbsd-src/usr.bin/rfcomm_sppd/rfcomm_sppd.c (revision ce2c90c7c172d95d2402a5b3d96d8f8e6d138a21)
1 /*	$NetBSD: rfcomm_sppd.c,v 1.1 2006/06/19 15:44:56 gdamore Exp $	*/
2 
3 /*-
4  * Copyright (c) 2006 Itronix Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. The name of Itronix Inc. may not be used to endorse
16  *    or promote products derived from this software without specific
17  *    prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 /*
32  * rfcomm_sppd.c
33  *
34  * Copyright (c) 2003 Maksim Yevmenkin <m_evmenkin@yahoo.com>
35  * All rights reserved.
36  *
37  * Redistribution and use in source and binary forms, with or without
38  * modification, are permitted provided that the following conditions
39  * are met:
40  * 1. Redistributions of source code must retain the above copyright
41  *    notice, this list of conditions and the following disclaimer.
42  * 2. Redistributions in binary form must reproduce the above copyright
43  *    notice, this list of conditions and the following disclaimer in the
44  *    documentation and/or other materials provided with the distribution.
45  *
46  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
47  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
49  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
50  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
51  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
52  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
53  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
54  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
55  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
56  * SUCH DAMAGE.
57  *
58  * $Id: rfcomm_sppd.c,v 1.1 2006/06/19 15:44:56 gdamore Exp $
59  * $FreeBSD: src/usr.bin/bluetooth/rfcomm_sppd/rfcomm_sppd.c,v 1.8 2005/12/07 19:41:58 emax Exp $
60  */
61 
62 #include <sys/cdefs.h>
63 __COPYRIGHT("@(#) Copyright (c) 2006 Itronix, Inc.\n"
64 	    "@(#) Copyright (c) 2003 Maksim Yevmenkin <m_evmenkin@yahoo.com>\n"
65 	    "All rights reserved.\n");
66 __RCSID("$NetBSD: rfcomm_sppd.c,v 1.1 2006/06/19 15:44:56 gdamore Exp $");
67 
68 #include <bluetooth.h>
69 #include <ctype.h>
70 #include <err.h>
71 #include <errno.h>
72 #include <fcntl.h>
73 #include <grp.h>
74 #include <limits.h>
75 #include <paths.h>
76 #include <sdp.h>
77 #include <signal.h>
78 #include <stdarg.h>
79 #include <stdio.h>
80 #include <stdlib.h>
81 #include <string.h>
82 #include <syslog.h>
83 #include <termios.h>
84 #include <unistd.h>
85 
86 #include "rfcomm_sdp.h"
87 
88 #define SPPD_IDENT		"rfcomm_sppd"
89 #define SPPD_BUFFER_SIZE	1024
90 #define max(a, b)		(((a) > (b))? (a) : (b))
91 
92 static int	sppd_ttys_open	(char const *tty, int *amaster, int *aslave);
93 static int	sppd_read	(int fd, char *buffer, size_t size);
94 static int	sppd_write	(int fd, char *buffer, size_t size);
95 static void	sppd_sighandler	(int s);
96 static void	usage		(void);
97 
98 static int	done;	/* are we done? */
99 
100 /* Main */
101 int
102 main(int argc, char *argv[])
103 {
104 	struct sigaction	 sa;
105 	struct sockaddr_bt	 ra;
106 	bdaddr_t		 laddr, raddr;
107 	uint8_t			 channel;
108 	int			 n, background, service,
109 				 s, amaster, aslave, fd;
110 	fd_set			 rfd;
111 	char			*tty = NULL, *ep = NULL, buf[SPPD_BUFFER_SIZE];
112 
113 	bdaddr_copy(&laddr, BDADDR_ANY);
114 	bdaddr_copy(&raddr, BDADDR_ANY);
115 	background = channel = 0;
116 	service = SDP_SERVICE_CLASS_SERIAL_PORT;
117 
118 	/* Parse command line options */
119 	while ((n = getopt(argc, argv, "a:bc:d:t:h")) != -1) {
120 		switch (n) {
121 		case 'a': /* BDADDR */
122 			if (!bt_aton(optarg, &raddr)) {
123 				struct hostent	*he = NULL;
124 
125 				if ((he = bt_gethostbyname(optarg)) == NULL)
126 					errx(EXIT_FAILURE,
127 					    "%s: %s", optarg, hstrerror(h_errno));
128 
129 				bdaddr_copy(&raddr, (bdaddr_t *)he->h_addr);
130 			}
131 			break;
132 
133 		case 'c': /* RFCOMM channel */
134 			channel = strtoul(optarg, &ep, 10);
135 			if (*ep != '\0') {
136 				channel = 0;
137 				switch (tolower((int)optarg[0])) {
138 				case 'd': /* DialUp Networking */
139 					service = SDP_SERVICE_CLASS_DIALUP_NETWORKING;
140 					break;
141 
142 				case 'f': /* Fax */
143 					service = SDP_SERVICE_CLASS_FAX;
144 					break;
145 
146 				case 'l': /* LAN */
147 					service = SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP;
148 					break;
149 
150 				case 's': /* Serial Port */
151 					service = SDP_SERVICE_CLASS_SERIAL_PORT;
152 					break;
153 
154 				default:
155 					errx(EXIT_FAILURE, "Unknown service name: %s",
156 						optarg);
157 					/* NOT REACHED */
158 				}
159 			}
160 			break;
161 
162 		case 'b': /* Run in background */
163 			background = 1;
164 			break;
165 
166 		case 'd': /* device */
167 			if (!bt_devaddr(optarg, &laddr))
168 				err(EXIT_FAILURE, "%s", optarg);
169 			break;
170 
171 		case 't': /* Slave TTY name */
172 			if (optarg[0] != '/')
173 				asprintf(&tty, "%s%s", _PATH_DEV, optarg);
174 			else
175 				tty = optarg;
176 			break;
177 
178 		case 'h':
179 		default:
180 			usage();
181 			/* NOT REACHED */
182 		}
183 	}
184 
185 	/* Check if we have everything we need */
186 	if (bdaddr_any(&raddr))
187 		usage();
188 		/* NOT REACHED */
189 
190 	/* Set signal handlers */
191 	memset(&sa, 0, sizeof(sa));
192 	sa.sa_handler = sppd_sighandler;
193 
194 	if (sigaction(SIGTERM, &sa, NULL) < 0)
195 		err(EXIT_FAILURE, "Could not sigaction(SIGTERM)");
196 
197 	if (sigaction(SIGHUP, &sa, NULL) < 0)
198 		err(EXIT_FAILURE, "Could not sigaction(SIGHUP)");
199 
200 	if (sigaction(SIGINT, &sa, NULL) < 0)
201 		err(EXIT_FAILURE, "Could not sigaction(SIGINT)");
202 
203 	sa.sa_handler = SIG_IGN;
204 	sa.sa_flags = SA_NOCLDWAIT;
205 
206 	if (sigaction(SIGCHLD, &sa, NULL) < 0)
207 		err(EXIT_FAILURE, "Could not sigaction(SIGCHLD)");
208 
209 	/* Check channel, if was not set then obtain it via SDP */
210 	if (channel == 0 && service != 0)
211 		if (rfcomm_channel_lookup(&laddr, &raddr,
212 			    service, &channel, &n) != 0)
213 			errx(EXIT_FAILURE,
214 				"Could not obtain RFCOMM channel: %s",
215 				strerror(n));
216 
217 	if (channel < 1 || channel > 30)
218 		errx(EXIT_FAILURE,
219 			"Invalid RFCOMM channel number %d", channel);
220 
221 	/* Open TTYs */
222 	if (tty == NULL) {
223 		if (background)
224 			usage();
225 
226 		aslave = 0;
227 		amaster = STDIN_FILENO;
228 		fd = STDOUT_FILENO;
229 	} else {
230 		if (sppd_ttys_open(tty, &amaster, &aslave) < 0)
231 			exit(EXIT_FAILURE);
232 
233 		fd = amaster;
234 	}
235 
236 	/* Open RFCOMM connection */
237 	memset(&ra, 0, sizeof(ra));
238 	ra.bt_len = sizeof(ra);
239 	ra.bt_family = AF_BLUETOOTH;
240 
241 	s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
242 	if (s < 0)
243 		err(EXIT_FAILURE, "Could not create socket");
244 
245 	bdaddr_copy(&ra.bt_bdaddr, &laddr);
246 	if (bind(s, (struct sockaddr *) &ra, sizeof(ra)) < 0)
247 		err(EXIT_FAILURE, "Could not bind socket");
248 
249 	ra.bt_channel = channel;
250 	bdaddr_copy(&ra.bt_bdaddr, &raddr);
251 
252 	if (connect(s, (struct sockaddr *) &ra, sizeof(ra)) < 0)
253 		err(EXIT_FAILURE, "Could not connect socket");
254 
255 	/* Became daemon if required */
256 	if (background) {
257 		switch (fork()) {
258 		case -1:
259 			err(EXIT_FAILURE, "Could not fork()");
260 			/* NOT REACHED */
261 
262 		case 0:
263 			exit(EXIT_SUCCESS);
264 			/* NOT REACHED */
265 
266 		default:
267 			if (daemon(0, 0) < 0)
268 				err(EXIT_FAILURE, "Could not daemon()");
269 			break;
270 		}
271 	}
272 
273 	openlog(SPPD_IDENT, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_DAEMON);
274 	syslog(LOG_INFO, "Starting on %s...",
275 			(tty != NULL) ? tty : "stdin/stdout");
276 
277 	for (done = 0; !done; ) {
278 		FD_ZERO(&rfd);
279 		FD_SET(amaster, &rfd);
280 		FD_SET(s, &rfd);
281 
282 		n = select(max(amaster, s) + 1, &rfd, NULL, NULL, NULL);
283 		if (n < 0) {
284 			if (errno == EINTR)
285 				continue;
286 
287 			syslog(LOG_ERR, "Could not select(). %s",
288 					strerror(errno));
289 			exit(EXIT_FAILURE);
290 		}
291 
292 		if (n == 0)
293 			continue;
294 
295 		if (FD_ISSET(amaster, &rfd)) {
296 			n = sppd_read(amaster, buf, sizeof(buf));
297 			if (n < 0) {
298 				syslog(LOG_ERR, "Could not read master pty, "
299 					"fd=%d. %s", amaster, strerror(errno));
300 				exit(EXIT_FAILURE);
301 			}
302 
303 			if (n == 0)
304 				break; /* XXX */
305 
306 			if (sppd_write(s, buf, (size_t)n) < 0) {
307 				syslog(LOG_ERR, "Could not write to socket, "
308 					"fd=%d, size=%d. %s",
309 					s, n, strerror(errno));
310 				exit(EXIT_FAILURE);
311 			}
312 		}
313 
314 		if (FD_ISSET(s, &rfd)) {
315 			n = sppd_read(s, buf, sizeof(buf));
316 			if (n < 0) {
317 				syslog(LOG_ERR, "Could not read socket, " \
318 					"fd=%d. %s", s, strerror(errno));
319 				exit(EXIT_FAILURE);
320 			}
321 
322 			if (n == 0)
323 				break;
324 
325 			if (sppd_write(fd, buf, (size_t)n) < 0) {
326 				syslog(LOG_ERR, "Could not write to master " \
327 					"pty, fd=%d, size=%d. %s",
328 					fd, n, strerror(errno));
329 				exit(EXIT_FAILURE);
330 			}
331 		}
332 	}
333 
334 	syslog(LOG_INFO, "Completed on %s", (tty != NULL)? tty : "stdin/stdout");
335 	closelog();
336 
337 	close(s);
338 
339 	if (tty != NULL) {
340 		close(aslave);
341 		close(amaster);
342 	}
343 
344 	return (0);
345 }
346 
347 /* Open TTYs */
348 static int
349 sppd_ttys_open(char const *tty, int *amaster, int *aslave)
350 {
351 	char		 pty[PATH_MAX], *slash;
352 	struct group	*gr = NULL;
353 	gid_t		 ttygid;
354 	struct termios	 tio;
355 
356 	/*
357 	 * Construct master PTY name. The slave tty name must be less then
358 	 * PATH_MAX characters in length, must contain '/' character and
359 	 * must not end with '/'.
360 	 */
361 
362 	if (strlen(tty) >= sizeof(pty)) {
363 		syslog(LOG_ERR, "Slave tty name is too long");
364 		return (-1);
365 	}
366 
367 	strlcpy(pty, tty, sizeof(pty));
368 	slash = strrchr(pty, '/');
369 	if (slash == NULL || slash[1] == '\0') {
370 		syslog(LOG_ERR, "Invalid slave tty name (%s)", tty);
371 		return (-1);
372 	}
373 
374 	slash[1] = 'p';
375 
376 	if (strcmp(pty, tty) == 0) {
377 		syslog(LOG_ERR, "Master and slave tty are the same (%s)", tty);
378 		return (-1);
379 	}
380 
381 	if ((*amaster = open(pty, O_RDWR, 0)) < 0) {
382 		syslog(LOG_ERR, "Could not open(%s). %s", pty, strerror(errno));
383 		return (-1);
384 	}
385 
386 	/*
387 	 * Slave TTY
388 	 */
389 
390 	if ((gr = getgrnam("tty")) != NULL)
391 		ttygid = gr->gr_gid;
392 	else
393 		ttygid = (gid_t)-1;
394 
395 	(void) chown(tty, getuid(), ttygid);
396 	(void) chmod(tty, S_IRUSR|S_IWUSR|S_IWGRP);
397 	(void) revoke(tty);
398 
399 	if ((*aslave = open(tty, O_RDWR, 0)) < 0) {
400 		syslog(LOG_ERR, "Could not open(%s). %s", tty, strerror(errno));
401 		close(*amaster);
402 		return (-1);
403 	}
404 
405 	/*
406 	 * Make slave TTY raw
407 	 */
408 
409 	cfmakeraw(&tio);
410 
411 	if (tcsetattr(*aslave, TCSANOW, &tio) < 0) {
412 		syslog(LOG_ERR, "Could not tcsetattr(). %s", strerror(errno));
413 		close(*aslave);
414 		close(*amaster);
415 		return (-1);
416 	}
417 
418 	return (0);
419 } /* sppd_ttys_open */
420 
421 /* Read data */
422 static int
423 sppd_read(int fd, char *buffer, size_t size)
424 {
425 	int	n;
426 
427 again:
428 	n = read(fd, buffer, size);
429 	if (n < 0) {
430 		if (errno == EINTR)
431 			goto again;
432 
433 		return (-1);
434 	}
435 
436 	return (n);
437 } /* sppd_read */
438 
439 /* Write data */
440 static int
441 sppd_write(int fd, char *buffer, size_t size)
442 {
443 	int	n, wrote;
444 
445 	for (wrote = 0; size > 0; ) {
446 		n = write(fd, buffer, size);
447 		switch (n) {
448 		case -1:
449 			if (errno != EINTR)
450 				return (-1);
451 			break;
452 
453 		case 0:
454 			/* XXX can happen? */
455 			break;
456 
457 		default:
458 			wrote += n;
459 			buffer += n;
460 			size -= n;
461 			break;
462 		}
463 	}
464 
465 	return (wrote);
466 } /* sppd_write */
467 
468 /* Signal handler */
469 static void
470 sppd_sighandler(int s)
471 {
472 	syslog(LOG_INFO, "Signal %d received. Total %d signals received\n",
473 			s, ++ done);
474 } /* sppd_sighandler */
475 
476 /* Display usage and exit */
477 static void
478 usage(void)
479 {
480 	fprintf(stdout,
481 "Usage: %s options\n" \
482 "Where options are:\n" \
483 "\t-a address Address to connect to (required)\n" \
484 "\t-b         Run in background\n" \
485 "\t-c channel RFCOMM channel to connect to\n" \
486 "\t-d device  Device to connect from\n" \
487 "\t-t tty     TTY name (required in background mode)\n" \
488 "\t-h         Display this message\n", SPPD_IDENT);
489 
490 	exit(EXIT_FAILURE);
491 } /* usage */
492