Commit df80b806 authored by David Woodhouse's avatar David Woodhouse

Move HTTP authentication out into http-auth.c

Signed-off-by: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
parent 70dd287c
......@@ -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
......
/*
* 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;
}
This diff is collapsed.
......@@ -932,17 +932,14 @@ int buf_ensure_space(struct oc_text_buf *buf, int len);
void __attribute__ ((format (printf, 2, 3)))
buf_append(struct oc_text_buf *buf, const char *fmt, ...);
void buf_append_bytes(struct oc_text_buf *buf, const void *bytes, int len);
void buf_append_base64(struct oc_text_buf *buf, const void *bytes, int len);
int buf_append_utf16le(struct oc_text_buf *buf, const char *utf8);
int get_utf8char(const char **utf8);
void buf_append_from_utf16le(struct oc_text_buf *buf, const void *utf16);
void *openconnect_base64_decode(int *len, const char *in);
void buf_truncate(struct oc_text_buf *buf);
void buf_append_urlencoded(struct oc_text_buf *buf, char *str);
int buf_error(struct oc_text_buf *buf);
int buf_free(struct oc_text_buf *buf);
char *openconnect_create_useragent(const char *base);
void cleanup_proxy_auth(struct openconnect_info *vpninfo);
int process_proxy(struct openconnect_info *vpninfo, int ssl_sock);
int internal_parse_url(const char *url, char **res_proto, char **res_host,
int *res_port, char **res_path, int default_port);
......@@ -957,6 +954,14 @@ int process_http_response(struct openconnect_info *vpninfo, int connect,
int handle_redirect(struct openconnect_info *vpninfo);
void http_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf);
/* http-auth.c */
void buf_append_base64(struct oc_text_buf *buf, const void *bytes, int len);
void *openconnect_base64_decode(int *len, const char *in);
void clear_auth_states(struct openconnect_info *vpninfo,
struct http_auth_state *auth_states, int reset);
int proxy_auth_hdrs(struct openconnect_info *vpninfo, char *hdr, char *val);
int gen_authorization_hdr(struct openconnect_info *vpninfo, int proxy,
struct oc_text_buf *buf);
/* ntlm.c */
int ntlm_authorization(struct openconnect_info *vpninfo, int proxy, struct http_auth_state *auth_state, struct oc_text_buf *buf);
void cleanup_ntlm_auth(struct openconnect_info *vpninfo, struct http_auth_state *auth_state);
......@@ -978,6 +983,14 @@ void openconnect_set_juniper(struct openconnect_info *vpninfo);
/* version.c */
extern const char *openconnect_version_str;
/* strncasecmp() just checks that the first n characters match. This
function ensures that the first n characters of the left-hand side
are a *precise* match for the right-hand side. */
static inline int strprefix_match(const char *str, int len, const char *match)
{
return len == strlen(match) && !strncasecmp(str, match, len);
}
#define STRDUP(res, arg) \
do { \
free(res); \
......
......@@ -369,7 +369,7 @@ int connect_https_socket(struct openconnect_info *vpninfo)
out:
/* If proxy processing returned -EAGAIN to reconnect before attempting
further auth, and we failed to reconnect, we have to clean up here. */
cleanup_proxy_auth(vpninfo);
clear_auth_states(vpninfo, vpninfo->proxy_auth, 1);
return ssl_sock;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment