RFC: obexapp - virtual root folder for each device

Maksim Yevmenkin maksim.yevmenkin at gmail.com
Tue Apr 14 14:38:35 PDT 2009


2009/4/14 Mikhail T. <mi+thun at aldan.algebra.com>:

[...]

> Slow, painful, but still progress... :-)

please find attached revised patch that implements mi's (aka Mikhail)
initial suggestion, i.e. use lstat(2) instead of stat(2). i've also
changed it a bit to allow both cases, i.e.

(1) when virtual root folder is requested and -u <user> option is set,
obexapp will always run as <user>;

(2) when virtual root folder is requested and _no_ -u option was
specified, obexapp will run as the owner of the found virtual root
folder entry (where entry is either symlink or actual subdirectory
under default root folder);

i've also included a patch, submitted by Ronald Klop to disable
spinner in client mode for non-interactive client sessions.

[...]

> To summarize, you seem willing to consider the owner of the matching
> entry when determining, which UID to switch to when dropping
> root-privileges after chroot. The only remaining disagreement is whether
> to use lstat vs. stat for the purpose. It being, literally, a
> one-character change in the code, you can go ahead and begin coding the
> change to match your style preferences.
>
> In the mean time, consider the example I just gave, showing stat being a
> security hole...

yes, now i get it :) sorry for being such a bonehead :) hopefully the
latest patch will work for everyone.

thanks,
max
-------------- next part --------------
Index: event.c
===================================================================
RCS file: /usr/local/cvs/ports/obexapp/event.c,v
retrieving revision 1.6
diff -u -r1.6 event.c
--- event.c	5 Jan 2009 16:37:25 -0000	1.6
+++ event.c	14 Apr 2009 21:23:16 -0000
@@ -1,7 +1,7 @@
 /*
  * event.c
  *
- * Copyright (c) 2002 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+ * Copyright (c) 2002-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -32,6 +32,7 @@
 #include <bluetooth.h>
 #include <obex.h>
 #include <stdio.h>
+#include <unistd.h>
 
 #include "compat.h"
 #include "obexapp.h"
@@ -131,7 +132,7 @@
 
 	log_debug("%s(): Made some progress...", __func__);
 
-	if (!context->server) {
+	if (!context->server && !context->ni && isatty(STDOUT_FILENO)) {
 		static char	spinner[] = "\\|/-";
 		static uint32_t	spinner_idx = 0;
 
Index: main.c
===================================================================
RCS file: /usr/local/cvs/ports/obexapp/main.c,v
retrieving revision 1.13
diff -u -r1.13 main.c
--- main.c	23 Apr 2007 18:29:18 -0000	1.13
+++ main.c	14 Apr 2009 21:23:38 -0000
@@ -1,7 +1,7 @@
 /*
  * main.c
  *
- * Copyright (c) 2002 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+ * Copyright (c) 2002-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -65,7 +65,7 @@
 {
 	struct sigaction	sa;
 	char			*ep = NULL, *pri_name = NULL;
-	int			n, service, noninteractive;
+	int			n, service, detach;
 	context_t		context;
 	obex_ctrans_t		custfunc;
 
@@ -83,7 +83,7 @@
 	/* Prepare context */
 	memset(&context, 0, sizeof(context));
 	context.tfd = context.sfd = -1;
-	context.detach = 1;
+	detach = 1;
 
 	context.ls_size = OBEXAPP_BUFFER_SIZE;
 	if ((context.ls = (char *) malloc(context.ls_size)) == NULL)
@@ -118,8 +118,8 @@
 	custfunc.customdata = &context;
 
 	/* Process command line options */
-	service = noninteractive = 0;
-	while ((n = getopt(argc, argv, "a:A:cC:dDfhl:m:nr:Ssu:")) != -1) {
+	service = 0;
+	while ((n = getopt(argc, argv, "a:A:cC:dDfhl:m:nr:RsSu:")) != -1) {
 		switch (n) {
 		case 'a':
 			if (!bt_aton(optarg, &context.raddr)) {
@@ -180,7 +180,7 @@
 			break;
 
 		case 'd': /* do not detach server */
-			context.detach = 0;
+			detach = 0;
 			break;
 
 		case 'D': /* use stdin/stdout */
@@ -209,7 +209,7 @@
 				usage(basename(argv[0]));
 				/* NOT REACHED */
 
-			noninteractive = 1;
+			context.ni = 1;
 			break;
 
 		case 'r': /* root */
@@ -217,8 +217,13 @@
 				err(1, "Could not realpath(%s)", optarg);
 			break;
 
+		case 'R': /* virtualize root for each device */
+			context.vroot = 1;
+			context.secure = 1;
+			break;
+
 		case 's': /* server */
-			if (noninteractive)
+			if (context.ni)
 				usage(basename(argv[0]));
 				/* NOT REACHED */
 
@@ -269,23 +274,10 @@
 	log_open("obexapp", pri_name, 0);
 
 	/* Detach server (if required) */
-	if (context.server && context.detach) {
-		pid_t	pid = fork();
-
-		if (pid == (pid_t) -1) {
-			log_err("%s(): Could not fork. %s (%d)",
-				__func__, strerror(errno), errno);
-			exit(1);
-		}
-
-		if (pid != 0)
-			exit(0);
-
-		if (daemon(0, 0) < 0) {
-			log_err("%s(): Could not daemon. %s (%d)",
-				__func__, strerror(errno), errno);
-			exit(1);
-		}
+	if (context.server && detach && daemon(0, 0) < 0) {
+		log_err("%s(): Could not daemon. %s (%d)",
+			__func__, strerror(errno), errno);
+		exit(1);
 	}
 
 	/* Initialize OBEX */
@@ -305,7 +297,7 @@
 
 	if (context.server)
 		n = obexapp_server(context.handle);
-	else if (noninteractive)
+	else if (context.ni)
 		n = obexapp_non_interactive_client(context.handle, argc, argv);
 	else
 		n = obexapp_client(context.handle);
Index: obexapp.1
===================================================================
RCS file: /usr/local/cvs/ports/obexapp/obexapp.1,v
retrieving revision 1.15
diff -u -r1.15 obexapp.1
--- obexapp.1	21 May 2007 15:55:35 -0000	1.15
+++ obexapp.1	14 Apr 2009 21:24:08 -0000
@@ -1,6 +1,6 @@
 .\" obexapp.1
 .\"
-.\" Copyright (c) 2001-2003 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+.\" Copyright (c) 2001-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
 .\" All rights reserved.
 .\"
 .\" Redistribution and use in source and binary forms, with or without
@@ -27,7 +27,7 @@
 .\" $Id: obexapp.1,v 1.15 2007/05/21 15:55:35 max Exp $
 .\" $FreeBSD$
 .\"
-.Dd April 10, 2007
+.Dd April 14, 2009
 .Dt OBEXAPP 1
 .Os
 .Sh NAME
@@ -54,7 +54,7 @@
 .Ar parameters
 .Nm
 .Fl s
-.Op Fl dDSh
+.Op Fl dDSRh
 .Op Fl A Ar BD_ADDR
 .Fl C Ar channel
 .Op Fl m Ar MTU
@@ -193,6 +193,12 @@
 Defaults to the maximum supported value.
 .It Fl n 
 Work in the non-interactive client mode.
+.It Fl R
+Virtualize root folder for each client device in server mode.
+Will automatically turn on secure mode, i.e.
+.Fl S
+option.
+Please read section below for a complete description.
 .It Fl r Ar path
 Specify root folder.
 Default root folder in the server mode is
@@ -216,6 +222,57 @@
 The value specified may be either a username or a numeric user id.
 This only works if server was started as root.
 .El
+.Sh VIRTUAL ROOT FOLDERS
+When accepting connections in server mode, 
+.Nm
+will attempt to find an entry that would act as a virtual root
+folder for the connecting device.
+Virtual root folders must reside under default root folder which is set
+with
+.Fl r
+option.
+The rules are as follows:
+.Bl -enum -offset indent -compact
+.It
+.Nm
+will try to resolve connecting device's BD_ADDR using
+.Xr bt_gethostbyaddr 3
+call and check for an entry that matches resolved name (if any);
+.It
+.Nm
+will check for an entry that matches connecting device's BD_ADDR;
+.It
+.Nm
+will check for an entry, named
+.Dq default ;
+.El
+If none of the above matches, then the connection to the client is terminated.
+Otherwise,
+.Nm
+will try to change default root folder the the found entry.
+.Pp
+If
+.Fl u
+option was specified, the
+.Nm
+will try to change to the specified user.
+Otherwise
+.Nm
+will try change to the user, that owns the found entry.
+That is, if the found entry is a symlink, the
+.Nm
+will try change to the user, that owns symlink and not to the user, that
+owns the entry symlink points to.
+.Pp
+This allows the same system to intelligently distinguish different
+client devices as belonging to different users.
+An administrator can set up the subdirectories for
+known devices under
+.Pa /var/spool/obex
+(or wherever, see
+.Fl r
+option) for each user, or even as symlinks to each user's home directory
+(or a subdirectory thereof).
 .Sh LOCALE SUPPORT
 The
 .Nm
@@ -325,6 +382,13 @@
 .Dv ANY
 address and RFCOMM channel
 .Li 1 .
+.It ln -s Ar /home/wallaby Ar /var/spool/obex/00:01:02:03:04:05
+.It chown -h wallaby Ar /var/spool/obex/00:01:02:03:04:05
+Whenever the device with BD_ADDR of 00:01:02:03:04:05 connects,
+.Nm
+running in server mode will switch to user ID
+.Ar wallaby
+and use their home directory as the top-level for the connection. 
 .El
 .Ss Level 1 Information Access
 The first level involves the basic ability to put an object (such as a vCard)
Index: obexapp.h
===================================================================
RCS file: /usr/local/cvs/ports/obexapp/obexapp.h,v
retrieving revision 1.9
diff -u -r1.9 obexapp.h
--- obexapp.h	23 Apr 2007 18:29:18 -0000	1.9
+++ obexapp.h	14 Apr 2009 21:26:44 -0000
@@ -1,7 +1,7 @@
 /*
  * obexapp.h
  *
- * Copyright (c) 2002 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+ * Copyright (c) 2002-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -87,9 +87,10 @@
 	unsigned		 server     : 1; /* server mode? */
 	unsigned		 secure     : 1; /* secure mode? */
 	unsigned		 done       : 1; /* done? */
-	unsigned		 detach     : 1; /* detach server? */
 	unsigned 		 fbs        : 1; /* Folder Browsing Service */
-	unsigned		 reserved   : 2;
+	unsigned		 vroot	    : 1; /* virtualize device's root */
+	unsigned		 ni         : 1; /* non-interactive? */
+	unsigned		 reserved   : 1;
 
 	/* local SDP session (server only) */
 	void			*ss;
@@ -111,6 +112,10 @@
 	uint8_t			*sbuffer;
 
 	int			 mtu;            /* OBEX MTU */
+
+	/* credentials */
+	uid_t			 uid;
+	gid_t			 gid;
 };
 typedef struct context		context_t;
 typedef struct context *	context_p;
Index: server.c
===================================================================
RCS file: /usr/local/cvs/ports/obexapp/server.c,v
retrieving revision 1.11
diff -u -r1.11 server.c
--- server.c	9 Apr 2009 23:16:31 -0000	1.11
+++ server.c	14 Apr 2009 20:55:37 -0000
@@ -1,7 +1,7 @@
 /*
  * server.c
  *
- * Copyright (c) 2002 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+ * Copyright (c) 2002-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -89,6 +89,10 @@
 static char const * const	ls_parent_folder =
 	"<parent-folder/>\n";
 
+static int obexapp_server_set_initial_root (context_p context);
+static int obexapp_server_set_device_root  (context_p context);
+static int obexapp_server_set_final_root   (context_p context);
+
 /* OBEX request handlers */
 static obexapp_request_handler_t	obexapp_server_request_connect;
 static obexapp_request_handler_t	obexapp_server_request_disconnect;
@@ -114,7 +118,6 @@
 obexapp_server(obex_t *handle)
 {
 	context_p		 context = (context_p) OBEX_GetUserData(handle);
-	struct passwd		*pw = NULL;
 	int			 error = -1;
 	struct sockaddr_rfcomm	 addr;
 
@@ -131,26 +134,6 @@
 		goto done;
 	}
 
-	if (context->user != NULL) {
-		if (atoi(context->user) != 0)
-			pw = getpwuid(atoi(context->user));
-		else
-			pw = getpwnam(context->user);
-
-		if (pw == NULL) {
-			log_err("%s(): Unknown user %s", __func__, 
-				context->user);
-			goto done;
-		}
-	}
-
-	if (context->root[0] == '\0') {
-		if (pw == NULL)
-			strlcpy(context->root, OBEXAPP_ROOT_DIR, PATH_MAX);
-		else
-			strlcpy(context->root, pw->pw_dir, PATH_MAX);
-	}
-
 	log_info("%s: Starting OBEX server", __func__);
 
 	if (OBEX_SetTransportMTU(handle, context->mtu, context->mtu) < 0) {
@@ -162,7 +145,7 @@
 	addr.rfcomm_len = sizeof(addr);
 	addr.rfcomm_family = AF_BLUETOOTH;
 	addr.rfcomm_channel = context->channel;
-	memcpy(&addr.rfcomm_bdaddr, &context->raddr, sizeof(context->raddr));
+	memcpy(&addr.rfcomm_bdaddr, &context->laddr, sizeof(context->laddr));
 
 	if (OBEX_ServerRegister(handle, (struct sockaddr *) &addr,
 			sizeof(addr)) < 0) {
@@ -170,40 +153,12 @@
 		goto done;
 	}
 
-	if (getuid() == 0) {
-		if (context->secure) {
-			if (chroot(context->root) < 0) {
-				log_err("%s(): Could not chroot(%s). %s (%d)",
-					__func__, context->root,
-					strerror(errno), errno);
-				goto done;
-			}
-
-			strlcpy(context->root, "/", PATH_MAX);
-		}
-
-		if (pw != NULL) {
-			if (setgid(pw->pw_gid) < 0) {
-				log_err("%s(): Could not setgid(%d). %s (%d)",
-					__func__, pw->pw_gid, strerror(errno),
-					errno);
-				goto done;
-			}
-
-			if (setuid(pw->pw_uid) < 0) {
-				log_err("%s(): Could not setuid(%d). %s (%d)",
-					__func__, pw->pw_uid, strerror(errno),
-					errno);
-				goto done;
-			}
-		}
-	}
-
-	if (chdir(context->root) < 0) {
-		log_err("%s(): Could not chdir(%s). %s (%d)",
-			__func__, context->root, strerror(errno), errno);
+	if (obexapp_server_set_initial_root(context) < 0)
+		goto done;
+	if (context->vroot && obexapp_server_set_device_root(context) < 0)
+		goto done;
+	if (obexapp_server_set_final_root(context) < 0)
 		goto done;
-	}
 
 	log_debug("%s(): Entering event processing loop...", __func__);
 
@@ -227,6 +182,165 @@
 } /* obexapp_server */
 
 /*
+ * Set initial server root
+ */
+
+static int
+obexapp_server_set_initial_root(context_p context)
+{
+	struct passwd	*pw = NULL;
+	char		*ep;
+
+	if (context->user != NULL) {
+		context->uid = strtoul(context->user, &ep, 10);
+		if (*ep != '\0')
+			pw = getpwnam(context->user);
+		else
+			pw = getpwuid(context->uid);
+
+		if (pw == NULL) {
+			log_err("%s(): Unknown user '%s'",
+				__func__, context->user);
+			return (-1);
+		}
+
+		log_debug("%s(): Requested to run as '%s', uid=%d, gid=%d",
+			__func__, context->user, pw->pw_uid, pw->pw_gid);
+
+		context->uid = pw->pw_uid;
+		context->gid = pw->pw_gid;
+	} else {
+		context->uid = getuid();
+		context->gid = getgid();
+	}
+
+	/* Set default root */
+	if (context->root[0] == '\0') {
+		if (pw == NULL)
+			strlcpy(context->root, OBEXAPP_ROOT_DIR, PATH_MAX);
+		else
+			strlcpy(context->root, pw->pw_dir, PATH_MAX);
+	}
+
+	if (chdir(context->root) < 0) {
+		log_err("%s(): Could not chdir(%s). %s (%d)",
+			__func__, context->root, strerror(errno), errno);
+		return (-1);
+	}
+
+	log_debug("%s(): Using initial root %s", __func__, context->root);
+
+	return (0);
+} /* obexapp_server_set_initial_root */
+
+/*
+ * Set device specific server root
+ */
+
+static int
+obexapp_server_set_device_root(context_p context)
+{
+	char const	*root[] = { NULL, NULL, NULL };
+	struct hostent	*he;
+	struct stat	sb;
+	int		n;
+
+	n = 0;
+
+	he = bt_gethostbyaddr((char const *) &context->raddr,
+			sizeof(bdaddr_t), AF_BLUETOOTH);
+	if (he != NULL)
+		root[n ++] = (char const *) he->h_name;
+
+	root[n ++] = bt_ntoa(&context->raddr, NULL);
+
+	root[n ++] = "default";
+
+	for (n = 0; n < 3; n ++) {
+		if (root[n] == NULL)
+			break;
+
+		log_debug("%s(): Checking for %s/%s subdirectory",
+			__func__, context->root, root[n]);
+
+		if (lstat(root[n], &sb) < 0) {
+			if (errno == ENOENT)
+				continue;
+
+			log_err("%s(): Could not lstat(%s/%s). %s (%d)",
+				__func__, context->root, root[n],
+				strerror(errno), errno);
+
+			return (-1);
+		}
+
+		strlcat(context->root, "/", PATH_MAX);
+		strlcat(context->root, root[n], PATH_MAX);
+
+		if (chdir(root[n]) < 0) {
+			log_err("%s(): Could not chdir(%s). %s (%d)",
+				__func__, context->root, strerror(errno),
+				errno);
+			return (-1);
+		}
+
+		/* If user was not set before, take it from lstat() data */
+		if (context->user == NULL) {
+			context->uid = sb.st_uid;
+			context->gid = sb.st_gid;
+		}
+
+		log_debug("%s(): Using device specific root %s, uid=%d, gid=%d",
+			__func__, context->root, context->uid, context->gid);
+
+		return (1);
+	}
+
+	log_err("%s(): Could not find device specific root for the device " \
+		"bdaddr %s (%s)", __func__, root[1],
+		root[0]? root[0] : "-no-name-");
+
+	return (-1);
+} /* obexapp_server_set_device_root */
+
+/*
+ * Finalize server root
+ */
+
+static int
+obexapp_server_set_final_root(context_p context)
+{
+	if (context->secure) {
+		if (chroot(context->root) < 0) {
+			log_err("%s(): Could not chroot(%s). %s (%d)",
+				__func__, context->root,
+				strerror(errno), errno);
+			return (-1);
+		}
+
+		strlcpy(context->root, "/", PATH_MAX);
+	}
+
+	if (context->gid != getgid() && setgid(context->gid) < 0) {
+		log_err("%s(): Could not setgid(%d). %s (%d)",
+			__func__, context->gid, strerror(errno), errno);
+		return (-1);
+	}
+
+	if (context->uid != getuid() && setuid(context->uid) < 0) {
+		log_err("%s(): Could not setuid(%d). %s (%d)",
+			__func__, context->uid, strerror(errno), errno);
+		return (-1);
+	}
+
+	log_notice("%s(): Using root %s; Secure mode %s; "
+		"Running as uid=%d, gid=%d", __func__, context->root,
+		context->secure? "enabled" : "disabled", getuid(), getgid());
+
+	return (0);
+} /* obexapp_server_set_final_root */
+
+/*
  * Process OBEX_EV_REQHINT event
  */
 
@@ -565,6 +679,15 @@
 			}
 		}
 
+		if (chmod(context->temp, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) {
+			log_err("%s(): Could not chmod(%s). %s (%d)",
+				__func__, context->temp,
+				strerror(errno), errno);
+
+			codes = obexapp_util_errno2response(errno);
+			goto done;
+		}
+
 		if (rename(context->temp, context->file) < 0) {
 			log_err("%s(): Could not rename(%s, %s). %s (%d)",
 				__func__, context->temp,
Index: transport.c
===================================================================
RCS file: /usr/local/cvs/ports/obexapp/transport.c,v
retrieving revision 1.13
diff -u -r1.13 transport.c
--- transport.c	21 May 2007 15:55:35 -0000	1.13
+++ transport.c	14 Apr 2009 21:26:02 -0000
@@ -1,7 +1,7 @@
 /*
  * transport.c
  *
- * Copyright (c) 2001 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+ * Copyright (c) 2001-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -280,6 +280,9 @@
 				return (-1);
 			}
 
+			memcpy(&context->raddr, &addr.rfcomm_bdaddr,
+				sizeof(context->raddr));
+
 			return (1);
 		}
 
Index: util.c
===================================================================
RCS file: /usr/local/cvs/ports/obexapp/util.c,v
retrieving revision 1.14
diff -u -r1.14 util.c
--- util.c	10 Apr 2009 17:26:03 -0000	1.14
+++ util.c	14 Apr 2009 21:26:08 -0000
@@ -1,7 +1,7 @@
 /*
  * util.c
  *
- * Copyright (c) 2002 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+ * Copyright (c) 2002-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -425,9 +425,7 @@
 	 * string, so always pass a copy.
 	 */
 
-	strncpy(n, name, sizeof(n));
-	n[sizeof(n) - 1] = '\0';
-
+	strlcpy(n, name, sizeof(n));
 	snprintf(temp, temp_size, "%s/XXXXXXXX", dirname(n));
 
 	return (mkstemp(temp));


More information about the freebsd-bluetooth mailing list