OAuth in iwhd preview patch

Pete Zaitcev zaitcev at redhat.com
Wed Sep 28 03:58:06 UTC 2011


Guys:

Below is what I have now for 2-legged OAuth in iwhd. It actually works,
with oauthtest2 from liboauth.

The patch is only a small piece, since it has no client, which we need
to run the tests, has no documentation, and no user list. However, it
retires most of the risk. We know that it can be done, this way.
The biggest risk left is interoperability with Aeolus.

BTW, the OAuth header looks like this:

 Authorization: OAuth realm="http://example.org/", oauth_consumer_key="chiaki",
  oauth_nonce="mitPzrhrCMHABJiYava_", oauth_signature_method="HMAC-SHA1",
  oauth_timestamp="1317182031", oauth_version="1.0",
  oauth_signature="Ya36%2FkXTG5YATRx%2BWAXK9NXzZCg%3D"

-- Pete

diff --git a/configure.ac b/configure.ac
index e23d1fa..8b115cb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,6 +70,9 @@ AC_CHECK_LIB([uuid], [uuid_generate_random],
 	[AC_MSG_ERROR([Missing required uuid lib])])
 AC_SUBST([UUID_LIB])
 
+dnl How to check properly?
+LIBS="$LIBS -loauth"
+
 PKG_CHECK_MODULES([HAIL],[libhail >= 0.8])
 AC_SUBST([HAIL_LIBS])
 AC_SUBST([HAIL_CFLAGS])
@@ -97,6 +100,9 @@ AC_CACHE_CHECK([whether json_load_file takes 3 arguments],
 AC_DEFINE_UNQUOTED([JANSSON_LOAD_FLAG], $iw_cv_func_jansson_flag,
   [Define to "0," if json_load_file and json_loads require a flags arguments.])
 
+AC_CHECK_HEADER([oauth.h], ,
+  [AC_MSG_ERROR([Missing OAuth development library: liboauth-devel])])
+
 # from http://www.gnu.org/software/autoconf-archive/
 AX_BOOST_BASE
 AX_BOOST_SYSTEM
diff --git a/rest.c b/rest.c
index 92f36cb..fec602c 100644
--- a/rest.c
+++ b/rest.c
@@ -15,12 +15,14 @@
 
 #include <config.h>
 
+#include <ctype.h>
 #include <error.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <poll.h>
 #include <pthread.h>
 #include <semaphore.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -33,6 +35,7 @@
 #include <microhttpd.h>
 #include <hstor.h>	/* only for ARRAY_SIZE at this point */
 #include <curl/curl.h>
+#include <oauth.h>
 
 #include "configmake.h" /* for LOCALEDIR */
 #include "dirname.h"
@@ -113,6 +116,9 @@ static unsigned short		 my_port	= MY_PORT;
 static LIST_HEAD(gc_list, _my_state) my_states;	/* this keeps GC informed */
 static pthread_mutex_t		 my_lock = PTHREAD_MUTEX_INITIALIZER;
 static const char		*log_name;
+static char			 my_hostname[HOST_NAME_MAX+1];
+static bool			 oauth		= false;
+static const char		*username, *userpass;
 
 static const char *const (reserved_name[]) = {"_default", "_new", "_policy", "_query", NULL};
 static const char *const (reserved_attr[]) = {"_attrs", "_bucket", "_date", "_etag", "_key", "_loc", "_size", NULL};
@@ -2091,6 +2097,454 @@ parse_url (const char *url, my_state *ms)
 	return eindex;
 }
 
+/* TBD: evict this to "user.h" once -U is gone. */
+#define USERNAME_MAX 64
+#define USERPASS_MAX 64
+struct user {
+	char name[USERNAME_MAX+1];
+	char pass[USERPASS_MAX+1];
+};
+
+static struct user *
+user_lookup (const char *user)
+{
+	struct user *ret;
+
+	/*
+	 * We do not have a real user table yet, so just compare with -U.
+	 */
+	if (username == NULL || strcmp(user, username) != 0)
+		return NULL;
+
+	ret = malloc(sizeof(struct user));
+	if (!ret)
+		return NULL;
+	memset(ret,0,sizeof(struct user));
+	strcpy(ret->name, username);
+	strcpy(ret->pass, userpass);
+	return ret;
+}
+
+static char *
+rebuild_url(bool is_ssl, struct MHD_Connection *conn, const char *path)
+{
+	const char	*val;
+	char		*ret;
+	int		 rc;
+
+	/*
+	 * Luckily for us, this header includes the colon-port.
+	 * It really should be called "Netloc", not "Host".
+	 */
+	val = MHD_lookup_connection_value(conn,MHD_HEADER_KIND,"Host");
+	if (!val) {
+		if (my_hostname[0] == 0) {
+			if (gethostname(my_hostname, HOST_NAME_MAX) != 0) {
+				/* TBD: This may flood - user-driven. */
+				error (0, errno, _("gethostname"));
+				return NULL;
+			}
+			my_hostname[HOST_NAME_MAX] = 0;
+		}
+		/* TBD: And port? But this is a fall-back anyway. */
+		val = my_hostname;
+	}
+
+	rc = asprintf(&ret, "%s://%s%s", is_ssl? "https": "http", val, path);
+	if (rc < 0) {
+		DPRINTF("No core");
+		return NULL;
+	}
+	return ret;
+}
+
+static const char *
+auth_type_check (const char *val, const char *type)
+{
+	const char *p;
+	size_t tlen = strlen(type);
+
+	p = val;
+	while (*p == ' ') {
+		if (*p == 0) {
+			return false;
+		}
+		p++;
+	}
+	if (strncmp(p, type, tlen) != 0)
+		return false;
+	p += tlen;
+	if (*p != ' ')
+		return false;
+	p++;
+	while (*p == ' ') {
+		if (*p == 0) {
+			return false;
+		}
+		p++;
+	}
+	return p;
+}
+
+/*
+ * Split up an Authorization header.
+ * Note that this decodes the strings.
+ */
+static bool
+auth_hdr_split (int *pargc, char ***pargv, const char *val)
+{
+	enum { ARGC_MAX = 20 };		/* better than 2-pass */
+	enum { VAL_MAX = 512 };		/* a constant is not great, but... */
+	enum {
+		ST_NONE, ST_ERR, ST_KEY, ST_EQ, ST_VAL, ST_Q2, ST_COM,
+		ST_PCENT1, ST_PCENT2
+	} state;
+	int argc;
+	char **argv;
+	const char *p;
+	const char *key;
+	size_t klen = 0;
+	size_t vlen = 0;
+	char vbuf[VAL_MAX+1];
+	unsigned int acc = 0, n;
+	char *arg;
+
+	if (!(val = auth_type_check(val, "OAuth")))
+		return false;
+
+	argv = malloc((ARGC_MAX+1) * sizeof(char*));
+	if (!argv) {
+		return false;
+	}
+	argc = 0;
+
+	arg = NULL;
+	state = ST_NONE;
+	key = NULL;
+	for (p = val; *p != 0; p++) {
+		switch (state) {
+		case ST_NONE:
+			if (*p == ' ') {
+				;
+			}
+			else if (!isascii(*p) || !isalpha(*p)) {
+				state = ST_ERR;
+			}
+			else {
+				state = ST_KEY;
+				key = p;
+			}
+			break;
+		case ST_KEY:
+			if (*p == '=') {
+				state = ST_EQ;
+				klen = p-key;
+			}
+			break;
+		case ST_EQ:
+			if (*p == '"') {
+				state = ST_VAL;
+				vlen = 0;
+			}
+			else {
+				/*
+				 * A compiler writer would've had an ulcer
+				 * from this, because obviously we are going
+				 * to recover in a dirty way. But whatever,
+				 * all we want is an error indication.
+				 */
+				state = ST_ERR;
+			}
+			break;
+		case ST_VAL:
+			if (*p == '"') {
+				vbuf[vlen] = 0;
+				state = ST_Q2;
+			}
+			else if (*p == '%') {
+				state = ST_PCENT1;
+				acc = 0;
+			}
+			else {
+				vbuf[vlen++] = *p;
+			}
+			break;
+		case ST_PCENT1:
+			if (isxdigit(*p)) {
+				if (*p <= '9') {
+					n = toupper(*p) - '0';
+				} else {
+					n = toupper(*p) - 'A' + 10;
+				}
+				acc = (acc << 4) | (n & 0x0f);
+				state = ST_PCENT2;
+			} else {
+				state = ST_ERR;
+			}
+			break;
+		case ST_PCENT2:
+			if (isxdigit(*p)) {
+				if (*p <= '9') {
+					n = toupper(*p) - '0';
+				} else {
+					n = toupper(*p) - 'A' + 10;
+				}
+				acc = (acc << 4) | (n & 0x0f);
+				if (iscntrl(acc))
+					acc = '?';
+				if (vlen >= VAL_MAX) {
+					state = ST_ERR;
+					break;
+				}
+				vbuf[vlen++] = acc;
+				state = ST_VAL;
+			}
+			else {
+				state = ST_ERR;
+			}
+			break;
+		case ST_Q2:
+			if (*p == ',') {
+				arg = malloc(klen+1+vlen+1);
+				if (!arg) {
+					state = ST_ERR;
+					break;
+				}
+				memcpy(arg, key, klen);
+				arg[klen] = '=';
+				memcpy(arg+klen+1, vbuf, vlen);
+				arg[klen+1+vlen] = 0;
+
+				state = ST_COM;
+				key = NULL;
+			}
+			else {
+				state = ST_ERR;
+			}
+			break;
+		case ST_COM:
+			if (*p == ' ') {
+				if (!arg) {
+					state = ST_NONE;
+					key = NULL;
+					break;
+				}
+				if (argc >= ARGC_MAX) {
+					/* TBD: error out immediately */
+					state = ST_ERR;
+					break;
+				}
+				argv[argc++] = arg;
+				arg = NULL;
+				state = ST_NONE;
+				key = NULL;
+			}
+			else {
+				state = ST_ERR;
+			}
+		default:	// ST_ERR
+			if (*p == ',') {
+				state = ST_COM;
+			}
+		}
+	}
+
+	if (arg) {
+		DPRINTF("auth_hdr_split ended with arg %s\n", arg);
+		free (arg);
+	}
+
+	if (state == ST_Q2) {
+		if (key) {
+			arg = malloc(klen+1+vlen+1);
+			if (arg && argc < ARGC_MAX) {
+				memcpy(arg, key, klen);
+				arg[klen] = '=';
+				memcpy(arg+klen+1, vbuf, vlen);
+				arg[klen+1+vlen] = 0;
+				argv[argc++] = arg;
+			}
+		}
+	}
+	argv[argc] = NULL;
+
+	*pargc = argc;
+	*pargv = argv;
+	return true;
+}
+
+/* Extract the value from key="val". */
+static char *
+oauth_param_val(const char *arg) {
+	char *val;
+
+	val = strchr(arg, '=');
+	if (!val)
+		return NULL;
+	val++;
+	if (*val == 0)
+		return NULL;
+	return strdup(val);
+}
+
+/* clone of oauth_param_exists with minimal changes */
+static int
+oauth_param_find(char **argv, int argc, const char *key) {
+  int i;
+  size_t l= strlen(key);
+  for (i=0;i<argc;i++)
+    if (strlen(argv[i])>l && !strncmp(argv[i],key,l) && argv[i][l] == '=') return i;
+  return -1;
+}
+
+static int
+do_oauth (struct MHD_Connection *conn, my_state *ms,
+    const char *url, const char *method)
+{
+	const char		*val;
+	struct MHD_Response	*resp;
+	int			 argc;
+	char			**argv;
+	int			 oargc;
+	char			**oargv;
+	int			 x;
+	struct user		*u;
+	char			*sig_supplied, *sig_calculated;
+
+	val = MHD_lookup_connection_value(conn,MHD_HEADER_KIND,"Authorization");
+	if (!val) {
+		resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
+		if (!resp) {
+			return -1;
+		}
+		MHD_add_response_header(resp,"WWW-Authenticate","OAuth");
+		MHD_queue_response(conn,MHD_HTTP_UNAUTHORIZED,resp);
+		MHD_destroy_response(resp);
+		return 1;
+	}
+
+	if (!auth_hdr_split(&argc, &argv, val)) {
+		do_resp(conn,NULL,MHD_HTTP_BAD_REQUEST,NULL,"400\r\n");
+		return -1;
+	}
+	if (verbose >= 2) {
+		int i;
+		printf("OAuth supplied [%d]\n", argc);
+		for (i = 0; i < argc; i++) {
+			printf(" %s\n", argv[i]);
+		}
+	}
+
+	x = oauth_param_find(argv,argc,"oauth_consumer_key");
+	if (x == -1) {
+		do_resp(conn,NULL,MHD_HTTP_BAD_REQUEST,NULL,"400 no user\r\n");
+		return -1;
+	}
+
+	u = user_lookup(oauth_param_val(argv[x]));
+	if (!u) {
+		do_resp(conn,NULL,MHD_HTTP_FORBIDDEN,NULL,"403 bad user\r\n");
+		return -1;
+	}
+
+	/*
+	 * Putting marshalling arguments for and extracting results from
+	 * oauth_sign_array2_process makes you dirty, but this way offloads
+	 * some tricks to liboauth, so there.
+	 * XXX - verify HMAC or other type
+	 */
+	oargc = 1+argc;		/* add a slot for URL */
+
+	/*
+	 * If we just call malloc() here, we actually involve a function
+	 * intercepted by GC. Then, glibc will abort inside liboauth
+	 * oauth_sign_array2_process->xrealloc->free() => "invalid pointer".
+	 * Solution: use a random liboauth function which allocates
+	 * the memory we need. The result can be safely passed to liboauth.
+	 */
+	oargv = malloc((oargc+1)*sizeof(char*));
+
+	memset((void*)oargv, '0', (oargc+1)*sizeof(char*));
+	((char *)oargv)[(oargc+1)*sizeof(char*) - 1] = 0;
+	oargv = (char **) oauth_url_escape((char *)oargv);
+
+	if (!oargv) {
+		do_resp(conn,NULL,MHD_HTTP_INTERNAL_SERVER_ERROR,NULL,
+			"500\r\n");
+		return -1;
+	}
+	oargv[0] = rebuild_url(false, conn, url);	/* TBD: SSL */
+	if (!oargv[0]) {
+		do_resp(conn,NULL,MHD_HTTP_INTERNAL_SERVER_ERROR,NULL,
+			"500\r\n");
+		return -1;
+	}
+
+	// no good, must filter
+	// memcpy(&oargv[1], argv, (argc+1)*sizeof(char*));
+
+	oargc = 1;
+
+	x = oauth_param_find(argv,argc,"oauth_nonce");
+	if (x == -1) {
+		do_resp(conn,NULL,MHD_HTTP_BAD_REQUEST,NULL,"400 no nonce\r\n");
+		return -1;
+	}
+	oargv[oargc++] = argv[x];
+
+	x = oauth_param_find(argv,argc,"oauth_timestamp");
+	if (x == -1) {
+		do_resp(conn,NULL,MHD_HTTP_BAD_REQUEST,NULL,
+			"400 no timestamp\r\n");
+		return -1;
+	}
+	oargv[oargc++] = argv[x];
+
+	oauth_sign_array2_process(&oargc, &oargv, NULL, OA_HMAC, method,
+		u->name, u->pass, NULL, NULL);
+	if (verbose >= 2) {
+		int i;
+		printf("OAuth calculated\n");
+		for (i = 0; i < oargc; i++) {
+			printf(" %s\n", oargv[i]);
+		}
+	}
+
+	/*
+	 * We've got the signature, now verify.
+	 */
+	x = oauth_param_find(argv,argc,"oauth_signature");
+	if (x == -1) {
+		do_resp(conn,NULL,MHD_HTTP_BAD_REQUEST,NULL,
+			"400 no signature\r\n");
+		return -1;
+	}
+	sig_supplied = oauth_param_val(argv[x]);
+
+	x = oauth_param_find(oargv,oargc,"oauth_signature");
+	if (x == -1) {
+		do_resp(conn,NULL,MHD_HTTP_INTERNAL_SERVER_ERROR,NULL,
+			"500 no signature\r\n");
+		return -1;
+	}
+	sig_calculated = oauth_param_val(oargv[x]);
+
+	if (strcmp(sig_supplied, sig_calculated) != 0) {
+		DPRINTF("OAuth signature mismatch");
+		return -1;
+	}
+	return 0;
+}
+
+static int kv_iter_dump(void *cls, enum MHD_ValueKind kind,
+			const char *key, const char *value)
+{
+	/* ok to use printf when MHD_USE_THREAD_PER_CONNECTION maybe? */
+	printf("%s: %s\n", key, value);
+
+	return MHD_YES;
+}
+
 static int
 access_handler_0 (void *cctx, struct MHD_Connection *conn, const char *url,
 		  const char *method, const char *version, const char *data,
@@ -2099,10 +2553,24 @@ access_handler_0 (void *cctx, struct MHD_Connection *conn, const char *url,
 	unsigned int		 i;
 	url_type		 utype;
 	my_state		*ms	= *rctx;
+	int			 rc;
 
 	log_check();
+	if (verbose >= 2) {
+		printf(">> HTTP\n");
+		rc = MHD_get_connection_values(conn, MHD_HEADER_KIND,
+					       kv_iter_dump, NULL);
+		printf(">> headers: %d\n", rc);
+	}
 
 	if (ms) {
+		if (oauth) {
+			rc = do_oauth(conn,ms,url,method);
+			if (rc < 0)
+				return MHD_NO;
+			if (rc > 0)
+				return MHD_YES;
+		}
 		return ms->handler(cctx,conn,url,method,version,
 			data,data_size,rctx);
 	}
@@ -2131,12 +2599,20 @@ access_handler_0 (void *cctx, struct MHD_Connection *conn, const char *url,
 		ms->conn	= conn;
 		*rctx = ms;
 		pthread_mutex_lock(&my_lock);
-		LIST_INSERT_HEAD(&my_states, ms, gc_link);
+		LIST_INSERT_HEAD(&my_states,ms,gc_link);
 		pthread_mutex_unlock(&my_lock);
+		if (oauth) {
+			rc = do_oauth(conn,ms,url,method);
+			if (rc < 0)
+				return MHD_NO;
+			if (rc > 0)
+				return MHD_YES;
+		}
 		return ms->handler(cctx,conn,url,method,version,
 			data,data_size,rctx);
 	}
 
+	/* This is *unauthenticated*. Remove altogether? TBD */
 	if (!strcmp(method,"QUIT")) {
 		(void)sem_post((sem_t *)cctx);
 		return MHD_NO;
@@ -2194,7 +2670,9 @@ static const struct option my_options[] = {
 	{ "db",      required_argument, NULL, 'd' },
 	{ "logfile", required_argument, NULL, 'l' },
 	{ "master",  required_argument, NULL, 'm' },
+	{ "oauth",   no_argument,       NULL, 'o' },
 	{ "port",    required_argument, NULL, 'p' },
+	{ "usercred", required_argument, NULL, 'U' },
 	{ "verbose", no_argument,       NULL, 'v' },
 	{ "version", no_argument,       NULL, GETOPT_VERSION_CHAR },
 	{ "help", no_argument,          NULL, GETOPT_HELP_CHAR },
@@ -2222,7 +2700,9 @@ A configuration file must be specified.\n\
   -d, --db=HOST_PORT      database server as ip[:port]\n\
   -l, --logfile=FILE      logfile (default stdout/stderr)\n\
   -m, --master=HOST_PORT  master (upstream) server as ip[:port]\n\
+  -o, --oauth             enable OAuth\n\
   -p, --port=PORT         alternate listen port (default 9090)\n\
+  -U, --usercred=U:P      singleton pair of username:password\n\
   -v, --verbose           verbose/debug output\n\
 \n\
       --help     display this help and exit\n\
@@ -2275,6 +2755,7 @@ main (int argc, char **argv)
 	sem_t			 the_sem;
 	bool			 autostart = false;
 	char *cfg_file = NULL;
+	char			*usercred = NULL;
 
 	set_program_name (argv[0]);
 	setlocale (LC_ALL, "");
@@ -2287,7 +2768,8 @@ main (int argc, char **argv)
 	db_host = xstrdup ("localhost");
 
 	for (;;)
-	switch (getopt_long(argc,argv,"ac:d:l:m:p:v",my_options,NULL)) {
+	switch (getopt_long(argc,argv,"ac:d:l:m:op:U:v",my_options,NULL))
+	{
 	case 'a':
 		autostart = true;
 		break;
@@ -2308,10 +2790,16 @@ main (int argc, char **argv)
 		free (master_host); master_host = NULL;
 		extract_host_port (optarg, &master_host, &master_port);
 		break;
+	case 'o':
+		oauth = true;
+		break;
 	case 'p':
 		assert (optarg);
 		my_port = get_port (optarg);
 		break;
+	case 'U':	/* Uppercase because very temporary until -u appears. */
+		usercred = optarg;
+		break;
 	case 'v':
 		++verbose;
 		break;
@@ -2366,6 +2854,30 @@ args_done:
 		usage (EXIT_FAILURE);
 	}
 
+	if (usercred) {
+		const char *p = strchr(usercred,':');
+		if (!p) {
+			error (0, 0, _("The -U argument must contain a colon"));
+			usage (EXIT_FAILURE);
+		}
+		if (p == usercred) {
+			error (0, 0,
+			       _("The user part of -U argument is empty"));
+			usage (EXIT_FAILURE);
+		}
+		username = strndup(usercred,p-usercred);
+		userpass = strdup(p+1);
+		if (!username || !userpass) {
+			error (EXIT_FAILURE, 0, _("No core"));
+		}
+		if (strlen(username) > USERNAME_MAX) {
+			error (EXIT_FAILURE, 0, _("User name too long"));
+		}
+		if (strlen(userpass) > USERPASS_MAX) {
+			error (EXIT_FAILURE, 0, _("User password too long"));
+		}
+	}
+
 	char *t;
 	char const *tmpdir = ((t = getenv ("TMPDIR")) ? t : "/tmp");
 	tmpfile_template = xasprintf ("%s/iwhd.XXXXXX", tmpdir);
@@ -2382,6 +2894,9 @@ args_done:
 		printf("db is at %s:%u\n",db_host,db_port);
 		printf("will listen on port %u\n",my_port);
 		printf("my location is \"%s\"\n",me);
+		if (oauth) {
+			printf("autheticated with OAuth\n");
+		}
 		if (fflush(stdout) || ferror(stdout))
 			error(EXIT_FAILURE, 0, _("write failed"));
 	}


More information about the iwhd-devel mailing list