Navigation Menu

Skip to content

Commit

Permalink
Move HTTP authentication out into http-auth.c
Browse files Browse the repository at this point in the history
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
  • Loading branch information
David Woodhouse authored and David Woodhouse committed Feb 24, 2015
1 parent 70dd287 commit df80b80
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 349 deletions.
2 changes: 1 addition & 1 deletion Makefile.am
Expand Up @@ -23,7 +23,7 @@ openconnect_SOURCES = xml.c main.c
openconnect_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBPSKC_CFLAGS) $(GSSAPI_CFLAGS) $(INTL_CFLAGS) $(ICONV_CFLAGS) $(LIBPCSCLITE_CFLAGS)
openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(INTL_LIBS) $(ICONV_LIBS)

library_srcs = ssl.c http.c auth-common.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c
library_srcs = ssl.c http.c http-auth.c auth-common.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c
lib_srcs_cisco = auth.c cstp.c dtls.c
lib_srcs_juniper = oncp.c lzo.c auth-juniper.c
lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c gnutls_tpm.c
Expand Down
358 changes: 358 additions & 0 deletions http-auth.c
@@ -0,0 +1,358 @@
/*
* OpenConnect (SSL + DTLS) VPN client
*
* Copyright © 2008-2015 Intel Corporation.
* Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
*
* Author: David Woodhouse <dwmw2@infradead.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/

#include <config.h>

#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#include "openconnect-internal.h"

/* Ick. Yet another wheel to reinvent. But although we could pull it
in from OpenSSL, we can't from GnuTLS */

static inline int b64_char(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A';
if (c >= 'a' && c <= 'z')
return c - 'a' + 26;
if (c >= '0' && c <= '9')
return c - '0' + 52;
if (c == '+')
return 62;
if (c == '/')
return 63;
return -1;
}

void *openconnect_base64_decode(int *ret_len, const char *in)
{
unsigned char *buf;
int b[4];
int len = strlen(in);

if (len & 3) {
*ret_len = -EINVAL;
return NULL;
}
len = (len * 3) / 4;
buf = malloc(len);
if (!buf) {
*ret_len = -ENOMEM;
return NULL;
}

len = 0;
while (*in) {
if (!in[1] || !in[2] || !in[3])
goto err;
b[0] = b64_char(in[0]);
b[1] = b64_char(in[1]);
if (b[0] < 0 || b[1] < 0)
goto err;
buf[len++] = (b[0] << 2) | (b[1] >> 4);

if (in[2] == '=') {
if (in[3] != '=' || in[4] != 0)
goto err;
break;
}
b[2] = b64_char(in[2]);
if (b[2] < 0)
goto err;
buf[len++] = (b[1] << 4) | (b[2] >> 2);
if (in[3] == '=') {
if (in[4] != 0)
goto err;
break;
}
b[3] = b64_char(in[3]);
if (b[3] < 0)
goto err;
buf[len++] = (b[2] << 6) | b[3];
in += 4;
}
*ret_len = len;
return buf;

err:
free(buf);
*ret_len = EINVAL;
return NULL;
}

static const char b64_table[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};

void buf_append_base64(struct oc_text_buf *buf, const void *bytes, int len)
{
const unsigned char *in = bytes;
int hibits;

if (!buf || buf->error)
return;

if (buf_ensure_space(buf, (4 * (len + 2) / 3) + 1))
return;

while (len > 0) {
buf->data[buf->pos++] = b64_table[in[0] >> 2];
hibits = (in[0] << 4) & 0x30;
if (len == 1) {
buf->data[buf->pos++] = b64_table[hibits];
buf->data[buf->pos++] = '=';
buf->data[buf->pos++] = '=';
break;
}
buf->data[buf->pos++] = b64_table[hibits | (in[1] >> 4)];
hibits = (in[1] << 2) & 0x3c;
if (len == 2) {
buf->data[buf->pos++] = b64_table[hibits];
buf->data[buf->pos++] = '=';
break;
}
buf->data[buf->pos++] = b64_table[hibits | (in[2] >> 6)];
buf->data[buf->pos++] = b64_table[in[2] & 0x3f];
in += 3;
len -= 3;
}
buf->data[buf->pos] = 0;
}

static int basic_authorization(struct openconnect_info *vpninfo, int proxy,
struct http_auth_state *auth_state,
struct oc_text_buf *hdrbuf)
{
struct oc_text_buf *text;
const char *user, *pass;

if (proxy) {
user = vpninfo->proxy_user;
pass = vpninfo->proxy_pass;
} else {
/* Need to parse this out of the URL */
return -EINVAL;
}

if (!user || !pass)
return -EINVAL;

if (!vpninfo->authmethods_set) {
vpn_progress(vpninfo, PRG_ERR,
_("Proxy requested Basic authentication which is disabled by default\n"));
auth_state->state = AUTH_FAILED;
return -EINVAL;
}

if (auth_state->state == AUTH_IN_PROGRESS) {
auth_state->state = AUTH_FAILED;
return -EAGAIN;
}

text = buf_alloc();
buf_append(text, "%s:%s", user, pass);
if (buf_error(text))
return buf_free(text);

buf_append(hdrbuf, "%sAuthorization: Basic ", proxy ? "Proxy-" : "");
buf_append_base64(hdrbuf, text->data, text->pos);
buf_append(hdrbuf, "\r\n");

memset(text->data, 0, text->pos);
buf_free(text);

if (proxy)
vpn_progress(vpninfo, PRG_INFO, _("Attempting HTTP Basic authentication to proxy\n"));
else
vpn_progress(vpninfo, PRG_INFO, _("Attempting HTTP Basic authentication to server '%s'\n"),
vpninfo->hostname);

auth_state->state = AUTH_IN_PROGRESS;
return 0;
}

#if !defined(HAVE_GSSAPI) && !defined(_WIN32)
static int no_gssapi_authorization(struct openconnect_info *vpninfo,
struct http_auth_state *auth_state,
struct oc_text_buf *hdrbuf)
{
/* This comes last so just complain. We're about to bail. */
vpn_progress(vpninfo, PRG_ERR,
_("This version of OpenConnect was built without GSSAPI support\n"));
auth_state->state = AUTH_FAILED;
return -ENOENT;
}
#endif

struct auth_method {
int state_index;
const char *name;
int (*authorization)(struct openconnect_info *, int, struct http_auth_state *, struct oc_text_buf *);
void (*cleanup)(struct openconnect_info *, struct http_auth_state *);
} auth_methods[] = {
#if defined(HAVE_GSSAPI) || defined(_WIN32)
{ AUTH_TYPE_GSSAPI, "Negotiate", gssapi_authorization, cleanup_gssapi_auth },
#endif
{ AUTH_TYPE_NTLM, "NTLM", ntlm_authorization, cleanup_ntlm_auth },
{ AUTH_TYPE_DIGEST, "Digest", digest_authorization, NULL },
{ AUTH_TYPE_BASIC, "Basic", basic_authorization, NULL },
#if !defined(HAVE_GSSAPI) && !defined(_WIN32)
{ AUTH_TYPE_GSSAPI, "Negotiate", no_gssapi_authorization, NULL }
#endif
};

/* Generate Proxy-Authorization: header for request if appropriate */
int gen_authorization_hdr(struct openconnect_info *vpninfo, int proxy,
struct oc_text_buf *buf)
{
int ret;
int i;

for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++) {
struct http_auth_state *auth_state;
if (proxy)
auth_state = &vpninfo->proxy_auth[auth_methods[i].state_index];
else
auth_state = &vpninfo->http_auth[auth_methods[i].state_index];
if (auth_state->state > AUTH_UNSEEN) {
ret = auth_methods[i].authorization(vpninfo, proxy, auth_state, buf);
if (ret == -EAGAIN || !ret)
return ret;
}
}
vpn_progress(vpninfo, PRG_INFO, _("No more authentication methods to try\n"));
return -ENOENT;
}

/* Returns non-zero if it matched */
static int handle_auth_proto(struct openconnect_info *vpninfo,
struct auth_method *method, char *hdr)
{
struct http_auth_state *auth = &vpninfo->proxy_auth[method->state_index];
int l = strlen(method->name);

if (auth->state <= AUTH_FAILED)
return 0;

if (strncmp(method->name, hdr, l))
return 0;
if (hdr[l] != ' ' && hdr[l] != 0)
return 0;

if (auth->state == AUTH_UNSEEN)
auth->state = AUTH_AVAILABLE;

free(auth->challenge);
if (hdr[l])
auth->challenge = strdup(hdr + l + 1);
else
auth->challenge = NULL;

return 1;
}

int proxy_auth_hdrs(struct openconnect_info *vpninfo, char *hdr, char *val)
{
int i;

if (!strcasecmp(hdr, "Proxy-Connection") ||
!strcasecmp(hdr, "Connection")) {
if (!strcasecmp(val, "close"))
vpninfo->proxy_close_during_auth = 1;
return 0;
}

if (strcasecmp(hdr, "Proxy-Authenticate"))
return 0;

for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++) {
/* Return once we've found a match */
if (handle_auth_proto(vpninfo, &auth_methods[i], val))
return 0;
}

return 0;
}

void clear_auth_states(struct openconnect_info *vpninfo,
struct http_auth_state *auth_states, int reset)
{
int i;

for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++) {
struct http_auth_state *auth = &auth_states[auth_methods[i].state_index];

/* The 'reset' argument is set when we're connected successfully,
to fully reset the state to allow another connection to start
again. Otherwise, we need to remember which auth methods have
been tried and should not be attempted again. */
if (reset && auth_methods[i].cleanup)
auth_methods[i].cleanup(vpninfo, auth);

free(auth->challenge);
auth->challenge = NULL;
/* If it *failed* don't try it again even next time */
if (auth->state <= AUTH_FAILED)
return;
if (reset || auth->state == AUTH_AVAILABLE)
auth->state = AUTH_UNSEEN;
}
}

int openconnect_set_proxy_auth(struct openconnect_info *vpninfo, const char *methods)
{
int i, len;
const char *p;

for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++)
vpninfo->proxy_auth[auth_methods[i].state_index].state = AUTH_DISABLED;

while (methods) {
p = strchr(methods, ',');
if (p) {
len = p - methods;
p++;
} else
len = strlen(methods);

for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++) {
if (strprefix_match(methods, len, auth_methods[i].name) ||
(auth_methods[i].state_index == AUTH_TYPE_GSSAPI &&
strprefix_match(methods, len, "gssapi"))) {
vpninfo->proxy_auth[auth_methods[i].state_index].state = AUTH_UNSEEN;
break;
}
}
methods = p;
}
vpninfo->authmethods_set = 1;
return 0;
}

0 comments on commit df80b80

Please sign in to comment.