WIP Kerberized iwhd
Pete Zaitcev
zaitcev at redhat.com
Mon Jun 27 23:09:56 UTC 2011
Dear Jim:
If I understood it right, you expressed interest in patches that do not
work... and this one does not. Still needs a way to set up a header for
all future transfers. But I think generally this is how it is going to
look.
Please let me know if you see anything terrible.
Cheers,
-- Pete
diff --git a/configure.ac b/configure.ac
index 8945f7c..9820033 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 -lgssapi_krb5"
+
PKG_CHECK_MODULES([HAIL],[libhail >= 0.8])
AC_SUBST([HAIL_LIBS])
AC_SUBST([HAIL_CFLAGS])
@@ -85,6 +88,12 @@ AC_CHECK_HEADER([mongo/client/dbclient.h], ,
[#undef VERSION])
AC_LANG_POP
+AC_CHECK_HEADER([gssapi.h], [],
+ [AC_MSG_ERROR([Missing gssapi.h header])])
+dnl Maybe we should use setenv("KRB5_KTNAME",...) after all? That always works.
+AC_CHECK_HEADER([gssapi/gssapi_krb5.h], [],
+ [AC_MSG_ERROR([Missing gssapi/gssapi_krb5.h header])])
+
# from http://www.gnu.org/software/autoconf-archive/
AX_BOOST_BASE
AX_BOOST_SYSTEM
diff --git a/rest.c b/rest.c
index 11b2971..0826176 100644
--- a/rest.c
+++ b/rest.c
@@ -29,10 +29,13 @@
#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
+#include <stdbool.h>
#include <microhttpd.h>
#include <hstor.h> /* only for ARRAY_SIZE at this point */
#include <curl/curl.h>
+#include <gssapi.h>
+#include <gssapi/gssapi_krb5.h>
#include "configmake.h" /* for LOCALEDIR */
#include "dirname.h"
@@ -50,6 +53,7 @@
#include "mpipe.h"
#include "state_defs.h"
#include "version-etc.h"
+#include "base64.h"
const char version_etc_copyright[] =
/* Do *not* mark this string for translation. %s is a copyright
@@ -104,7 +108,70 @@ typedef struct {
MHD_AccessHandlerCallback handler;
} rule;
+static void log_gss_error(const char *msg, unsigned int gss_min)
+{
+ char buf[700];
+ int len, rem;
+ int rc;
+ unsigned int gss_maj, dummy_min;
+ unsigned int msg_ctx = 0;
+ gss_buffer_desc gss_str;
+
+ rem = sizeof(buf);
+ len = 0;
+ buf[0] = 0;
+
+ rc = snprintf(buf+len, rem, "%s", msg);
+ if (rc >= rem)
+ goto out;
+ rem -= rc;
+ len += rc;
+
+ /*
+ * See RFC-2744 clause 5.11. However, the practical code is different.
+ * - Everyone relies on the returned string nul-terminated,
+ * while RFC uses gss_buf.length.
+ * - Some people parse major codes (using different mech_type),
+ * while RFC gives no hint how.
+ */
+ do {
+ gss_maj = gss_display_status(&dummy_min, gss_min,
+ GSS_C_MECH_CODE, GSS_C_NO_OID,
+ &msg_ctx, &gss_str);
+ if (gss_maj != GSS_S_COMPLETE) {
+ /*
+ * If we put "unknown" here, someone may localize it.
+ */
+ rc = snprintf(buf+len, rem, ": UNK");
+ if (rc >= rem)
+ goto out;
+ rem -= rc;
+ len += rc;
+ goto out;
+ }
+
+ rc = snprintf(buf+len, rem, ": %s", (char*)gss_str.value);
+ if (rc >= rem)
+ goto out;
+ rem -= rc;
+ len += rc;
+
+ gss_release_buffer(&dummy_min, &gss_str);
+ } while(msg_ctx);
+ out:
+ /* there's always a nul at buf[len] */
+ fprintf(stderr, "%s\n", buf);
+}
+
static unsigned short my_port = MY_PORT;
+static char my_hostname[HOST_NAME_MAX+1];
+static bool kerberos = false;
+/* XXX Make keytab location to follow --prefix. */
+static const char *krb_keytab = "/etc/iwhd/krb.keytab";
+static const char *krb_svc_name = "IWHD";
+#if 0 /* RFC says use default */
+static gss_cred_id_t gss_creds; /* struct* */ /* XXX needed? */
+#endif
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};
@@ -2221,6 +2288,160 @@ parse_url (const char *url, my_state *ms)
return eindex;
}
+static bool nego_token(char **pbuf, size_t *plen, const char *val)
+{
+ char *buf;
+ size_t len;
+ const char *p;
+ bool rcb;
+
+ p = val;
+ while (*p == ' ') {
+ if (*p == 0) {
+ return false;
+ }
+ p++;
+ }
+ if (strncmp(p, "Negotiate ", sizeof("Negotiate ")-1) != 0)
+ return false;
+ p += sizeof("Negotiate ")-1;
+ while (*p == ' ') {
+ if (*p == 0) {
+ return false;
+ }
+ p++;
+ }
+
+ len = strlen(p);
+ if (len == 0) {
+ return false;
+ }
+
+ /* actually should be 3/4, but whatever */
+ buf = malloc(len);
+ if (!buf)
+ return false; // or maybe just abort()?
+
+ rcb = base64_decode(p, len, buf, &len);
+ if (!rcb) {
+ // free(buf);
+ return false;
+ }
+
+ *pbuf = buf;
+ *plen = len;
+ return true;
+}
+
+static int authenticate(struct MHD_Connection *conn)
+{
+ const char *val;
+ char *buf;
+ size_t len;
+ unsigned int gss_maj, gss_min, dummy_min;
+ gss_ctx_id_t gss_ctx = GSS_C_NO_CONTEXT;
+ gss_buffer_desc gss_itok;
+ gss_buffer_desc gss_otok = GSS_C_EMPTY_BUFFER;
+ gss_name_t gss_cli_name = GSS_C_NO_NAME;
+ gss_cred_id_t gss_dcred = GSS_C_NO_CREDENTIAL;
+ struct MHD_Response *resp;
+ unsigned int gss_flags;
+ bool rcb;
+
+ 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","Negotiate");
+ MHD_queue_response(conn,MHD_HTTP_UNAUTHORIZED,resp);
+ MHD_destroy_response(resp);
+ return 1;
+ }
+
+ len = strlen(val);
+/* P3 */ printf("[%ld] %s\n", (long)len, val);
+
+ rcb = nego_token(&buf, &gss_itok.length, val);
+ if (!rcb) {
+/* P3 */ printf("failed to extract token\n");
+ resp = MHD_create_response_from_data(0,NULL,MHD_NO,MHD_NO);
+ if (!resp) {
+ return -1;
+ }
+ MHD_queue_response(conn,MHD_HTTP_BAD_REQUEST,resp);
+ MHD_destroy_response(resp);
+ return -1;
+ }
+ gss_itok.value = buf; /* gcc */
+
+ gss_maj = gss_accept_sec_context(&gss_min,
+ &gss_ctx,
+ GSS_C_NO_CREDENTIAL,
+ &gss_itok,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &gss_cli_name,
+ NULL,
+ &gss_otok,
+ &gss_flags,
+ NULL,
+ &gss_dcred);
+ if (gss_otok.length) {
+ /* has something to send - put into WWW-Authenticate */
+ /* even if it was error */
+ /* XXX */
+ }
+ if (GSS_ERROR(gss_maj)) {
+ if (verbose) {
+ log_gss_error("gss_accept_sec_context", gss_min);
+ }
+ goto err_out;
+ }
+
+ if (gss_maj & GSS_S_CONTINUE_NEEDED) {
+ /* not in Kerberos */
+ error (EXIT_FAILURE, 0, _("gss_accept_sec_context continuing"));
+ }
+
+ gss_delete_sec_context(&dummy_min, &gss_ctx, GSS_C_NO_BUFFER);
+ return 0;
+
+ err_out:
+ resp = MHD_create_response_from_data(3,"403",MHD_NO,1);
+ if (!resp) {
+ /* XXX rc=-1; goto bail_out; */
+ if (gss_ctx != GSS_C_NO_CONTEXT) {
+ gss_delete_sec_context(&dummy_min, &gss_ctx,
+ GSS_C_NO_BUFFER);
+ }
+ if (gss_dcred != GSS_C_NO_CREDENTIAL) {
+ gss_release_cred(&dummy_min, &gss_dcred);
+ }
+ return -1;
+ }
+ MHD_queue_response(conn,MHD_HTTP_FORBIDDEN,resp);
+ MHD_destroy_response(resp);
+
+ if (gss_ctx != GSS_C_NO_CONTEXT) {
+ gss_delete_sec_context(&dummy_min, &gss_ctx, GSS_C_NO_BUFFER);
+ }
+ if (gss_dcred != GSS_C_NO_CREDENTIAL) {
+ gss_release_cred(&dummy_min, &gss_dcred);
+ }
+ return 1;
+}
+
+/* P3 */ /* actually maybe ok to guard with verbose >= 2 */
+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,
@@ -2230,6 +2451,18 @@ access_handler_0 (void *cctx, struct MHD_Connection *conn, const char *url,
url_type utype;
struct MHD_Response *resp;
my_state *ms = *rctx;
+ int rc;
+
+/* P3 */ printf(">> HTTP\n");
+/* P3 */ printf("headers: %d\n", MHD_get_connection_values(conn, MHD_HEADER_KIND, kv_iter_dump, NULL));
+
+ if (kerberos) {
+ rc = authenticate(conn);
+ if (rc < 0)
+ return MHD_NO;
+ if (rc > 0)
+ return MHD_YES;
+ }
if (ms) {
return ms->handler(cctx,conn,url,method,version,
@@ -2304,8 +2537,11 @@ static const struct option my_options[] = {
{ "autostart", no_argument, NULL, 'a' },
{ "config", required_argument, NULL, 'c' },
{ "db", required_argument, NULL, 'd' },
+ { "kerberos", no_argument, NULL, 'k' },
{ "master", required_argument, NULL, 'm' },
+ { "krbsvcnm", required_argument, NULL, 'n' },
{ "port", required_argument, NULL, 'p' },
+ { "krbkeytab", required_argument, NULL, 't' },
{ "verbose", no_argument, NULL, 'v' },
{ "version", no_argument, NULL, GETOPT_VERSION_CHAR },
{ "help", no_argument, NULL, GETOPT_HELP_CHAR },
@@ -2331,6 +2567,7 @@ A configuration file must be specified.\n\
-a, --autostart start necessary back-end services\n\
-c, --config=FILE config file [required]\n\
-d, --db=HOST_PORT database server as ip[:port]\n\
+ -k, --krbsvc=SVC_NAME Kerberos service name\n\
-m, --master=HOST_PORT master (upstream) server as ip[:port]\n\
-p, --port=PORT alternate listen port (default 9090)\n\
-v, --verbose verbose/debug output\n\
@@ -2347,6 +2584,77 @@ Report %s bugs to %s.\n\
exit (status);
}
+static void gss_init(void)
+{
+ char *gss_sname;
+ gss_buffer_desc gss_nbuf; /* struct */
+ unsigned int gss_maj, gss_min, dummy_min;
+ gss_name_t gss_iname; /* struct* */
+ int rc;
+
+ gss_maj = krb5_gss_register_acceptor_identity(krb_keytab);
+ if (gss_maj != 0)
+ error (EXIT_FAILURE, 0, _("cannot set keytab %s"), krb_keytab);
+
+ if (gethostname(my_hostname, HOST_NAME_MAX) != 0)
+ error (EXIT_FAILURE, errno, _("gethostname"));
+ my_hostname[HOST_NAME_MAX] = 0;
+
+ if (strchr(krb_svc_name, '@') != NULL ||
+ strchr(krb_svc_name, '/') != NULL) {
+ gss_sname = strdup(krb_svc_name);
+ if (!gss_sname)
+ error (EXIT_FAILURE, 0, _("out of memory"));
+ } else {
+ rc = asprintf(&gss_sname, "%s@%s", krb_svc_name, my_hostname);
+ if (rc < 0)
+ error (EXIT_FAILURE, 0, _("out of memory"));
+ }
+
+ gss_nbuf.length = strlen(gss_sname)+1;
+ gss_nbuf.value = gss_sname;
+
+ gss_maj = gss_import_name(&gss_min, &gss_nbuf,
+ GSS_C_NT_HOSTBASED_SERVICE, &gss_iname);
+ if (GSS_ERROR(gss_maj)) {
+ log_gss_error("gss_import_name error", gss_min);
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Why re-import only to print? We just exported the gss_sname, right?
+ * Well, who knows, maybe something went pear-shaped.
+ * Not sure what to do with the type OID though, so just NULL it.
+ */
+ if (verbose) {
+ gss_maj = gss_display_name(&gss_min, gss_iname, &gss_nbuf,NULL);
+ if (GSS_ERROR(gss_maj)) {
+ log_gss_error("gss_display_name error", gss_min);
+ exit(EXIT_FAILURE);
+ }
+ printf("kerberos service: %s\n", (char*)gss_nbuf.value);
+ gss_release_buffer(&dummy_min, &gss_nbuf);
+ }
+
+#if 0 /* RFC says use default */
+ /*
+ * The GSS_C_INDEFINITE does not yield a credential with an
+ * indefinite lifetime, merely "the maximum permitted lifetime".
+ * So, this function has to be called from the main loop somewhere. XXX
+ * Then, we need to call gss_release_cred(), too.
+ */
+ gss_maj = gss_acquire_cred(&gss_min, gss_iname, GSS_C_INDEFINITE,
+ GSS_C_NO_OID_SET, GSS_C_ACCEPT, &gss_creds,
+ NULL, NULL);
+ if (GSS_ERROR(gss_maj)) {
+ log_gss_error("gss_acquire_cred error", gss_min);
+ exit(EXIT_FAILURE);
+ }
+#endif
+
+ gss_release_name(&dummy_min, &gss_iname);
+ // free(gss_sname); -- don't crash GC
+}
int
main (int argc, char **argv)
@@ -2356,7 +2664,7 @@ main (int argc, char **argv)
char *stctx = NULL;
char *port_tmp;
bool autostart = false;
- char *cfg_file = NULL;
+ char *cfg_file = NULL;
set_program_name (argv[0]);
setlocale (LC_ALL, "");
@@ -2367,7 +2675,9 @@ main (int argc, char **argv)
GC_INIT ();
- for (;;) switch (getopt_long(argc,argv,"ac:d:m:p:v",my_options,NULL)) {
+ for (;;)
+ switch (getopt_long(argc,argv,"ac:d:km:n:p:t:v",my_options,NULL))
+ {
case 'a':
autostart = true;
break;
@@ -2382,6 +2692,9 @@ main (int argc, char **argv)
db_port = (unsigned short)strtoul(port_tmp,NULL,10);
}
break;
+ case 'k':
+ kerberos = true;
+ break;
case 'm':
assert (optarg);
master_host = strtok_r(optarg,":",&stctx);
@@ -2390,9 +2703,15 @@ main (int argc, char **argv)
master_port = (unsigned short)strtoul(port_tmp,NULL,10);
}
break;
+ case 'n':
+ krb_svc_name = optarg;
+ break;
case 'p':
my_port = (unsigned short)strtoul(optarg,NULL,10);
break;
+ case 't':
+ krb_keytab = optarg;
+ break;
case 'v':
++verbose;
break;
@@ -2445,6 +2764,9 @@ args_done:
usage (EXIT_FAILURE);
}
+ if (kerberos)
+ gss_init();
+
sem_init(&the_sem,0,0);
if (verbose) {
More information about the iwhd-devel
mailing list