Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move HTTP authentication out into http-auth.c
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
Showing
5 changed files
with
379 additions
and
349 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
|
Oops, something went wrong.