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