mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-02-10 16:57:00 +08:00
366 lines
9.4 KiB
C
366 lines
9.4 KiB
C
/*
|
|
* Copyright (C) 2015 Red Hat, Inc.
|
|
*
|
|
* This file is part of ocserv.
|
|
*
|
|
* ocserv is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* ocserv 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef HAVE_GSSAPI
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
#include <vpn.h>
|
|
#include <c-ctype.h>
|
|
#include "gssapi.h"
|
|
#include "auth/common.h"
|
|
#include <gssapi/gssapi.h>
|
|
#include <gssapi/gssapi_ext.h>
|
|
#include <gssapi/gssapi_krb5.h>
|
|
#include <gl/base64.h>
|
|
#include "cfg.h"
|
|
|
|
static gss_cred_id_t glob_creds;
|
|
static gss_OID_set glob_oids;
|
|
static unsigned no_local_map = 0;
|
|
static time_t ticket_freshness_secs = 0;
|
|
|
|
struct gssapi_ctx_st {
|
|
char username[MAX_USERNAME_SIZE];
|
|
gss_ctx_id_t gssctx;
|
|
|
|
gss_cred_id_t delegated_creds;
|
|
gss_buffer_desc msg;
|
|
};
|
|
|
|
/* Taken from openconnect's gssapi */
|
|
static void print_gss_err(const char *where,
|
|
gss_OID mech, OM_uint32 err_maj,
|
|
OM_uint32 err_min)
|
|
{
|
|
OM_uint32 major, minor, msg_ctx = 0;
|
|
gss_buffer_desc status = GSS_C_EMPTY_BUFFER;
|
|
|
|
do {
|
|
major = gss_display_status(&minor, err_maj, GSS_C_GSS_CODE,
|
|
mech, &msg_ctx, &status);
|
|
if (GSS_ERROR(major))
|
|
break;
|
|
syslog(LOG_ERR, "gssapi: %s[maj]: %s\n", where, (char *)status.value);
|
|
gss_release_buffer(&minor, &status);
|
|
} while (msg_ctx);
|
|
|
|
msg_ctx = 0;
|
|
do {
|
|
major = gss_display_status(&minor, err_min, GSS_C_MECH_CODE,
|
|
mech, &msg_ctx, &status);
|
|
if (GSS_ERROR(major))
|
|
break;
|
|
syslog(LOG_ERR, "gssapi: %s[min]: %s\n", where, (char *)status.value);
|
|
gss_release_buffer(&minor, &status);
|
|
} while (msg_ctx);
|
|
}
|
|
|
|
const gss_OID_desc spnego_mech = {6, (void *)"\x2b\x06\x01\x05\x05\x02"};
|
|
const gss_OID_set_desc desired_mechs = {
|
|
.count = 1,
|
|
.elements = (gss_OID)&spnego_mech
|
|
};
|
|
|
|
static void gssapi_global_init(void *pool, void *additional)
|
|
{
|
|
int ret;
|
|
OM_uint32 time, minor;
|
|
gss_name_t name = GSS_C_NO_NAME;
|
|
gssapi_cfg_st *config = additional;
|
|
|
|
if (config) {
|
|
no_local_map = config->no_local_map;
|
|
ticket_freshness_secs = config->ticket_freshness_secs;
|
|
}
|
|
|
|
if (config && config->keytab) {
|
|
gss_key_value_element_desc element;
|
|
gss_key_value_set_desc cred_store;
|
|
|
|
element.key = "keytab";
|
|
element.value = config->keytab;
|
|
cred_store.count = 1;
|
|
cred_store.elements = &element;
|
|
|
|
ret = gss_acquire_cred_from(&minor, name, 0, (gss_OID_set)&desired_mechs, 2,
|
|
&cred_store, &glob_creds, &glob_oids, &time);
|
|
|
|
if (ret != GSS_S_COMPLETE) {
|
|
ret = -1;
|
|
print_gss_err("gss_acquire_cred(keytab)", GSS_C_NO_OID, ret, minor);
|
|
exit(1);
|
|
}
|
|
} else {
|
|
ret = gss_acquire_cred(&minor, name, 0, (gss_OID_set)&desired_mechs, 2,
|
|
&glob_creds, &glob_oids, &time);
|
|
|
|
if (ret != GSS_S_COMPLETE) {
|
|
ret = -1;
|
|
print_gss_err("gss_acquire_cred", GSS_C_NO_OID, ret, minor);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (name != GSS_C_NO_NAME)
|
|
gss_release_name(&minor, &name);
|
|
|
|
return;
|
|
}
|
|
|
|
static void gssapi_global_deinit()
|
|
{
|
|
OM_uint32 minor;
|
|
|
|
if (glob_creds != NULL)
|
|
gss_release_cred(&minor, &glob_creds);
|
|
}
|
|
|
|
static int get_name(struct gssapi_ctx_st *pctx, gss_name_t client, gss_OID mech_type)
|
|
{
|
|
int ret;
|
|
OM_uint32 minor;
|
|
gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
|
|
|
|
pctx->username[0] = 0;
|
|
|
|
ret = gss_display_name(&minor, client, &name, NULL);
|
|
if (GSS_ERROR(ret)) {
|
|
print_gss_err("gss_display_name", mech_type, ret, minor);
|
|
return -1;
|
|
}
|
|
|
|
if (name.length < sizeof(pctx->username)) {
|
|
memcpy(pctx->username, name.value, name.length);
|
|
pctx->username[name.length] = 0;
|
|
}
|
|
|
|
syslog(LOG_DEBUG, "gssapi: authenticated GSSAPI user: %.*s", (unsigned)name.length, (char*)name.value);
|
|
gss_release_buffer(&minor, &name);
|
|
|
|
if (no_local_map == 0) {
|
|
ret = gss_localname(&minor, client, mech_type, &name);
|
|
if (GSS_ERROR(ret) || name.length >= MAX_USERNAME_SIZE) {
|
|
print_gss_err("gss_localname", mech_type, ret, minor);
|
|
syslog(LOG_INFO, "gssapi: authenticated user doesn't map to a local user");
|
|
return -1;
|
|
}
|
|
|
|
memcpy(pctx->username, name.value, name.length);
|
|
pctx->username[name.length] = 0;
|
|
syslog(LOG_INFO, "gssapi: authenticated local user: %s", pctx->username);
|
|
|
|
gss_release_buffer(&minor, &name);
|
|
}
|
|
|
|
if (pctx->username[0] == 0)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int verify_krb5_constraints(struct gssapi_ctx_st *pctx, gss_OID mech_type)
|
|
{
|
|
int ret;
|
|
OM_uint32 minor;
|
|
krb5_timestamp authtime;
|
|
|
|
if (mech_type == NULL ||
|
|
((mech_type->length != gss_mech_krb5->length || memcmp(mech_type->elements, gss_mech_krb5->elements, mech_type->length) != 0) &&
|
|
(mech_type->length != gss_mech_krb5_old->length || memcmp(mech_type->elements, gss_mech_krb5_old->elements, mech_type->length) != 0)) ||
|
|
ticket_freshness_secs == 0) {
|
|
return 0;
|
|
}
|
|
|
|
ret = gsskrb5_extract_authtime_from_sec_context (&minor, pctx->gssctx, &authtime);
|
|
if (GSS_ERROR(ret)) {
|
|
print_gss_err("gsskrb5_extract_authtime_from_sec_context", mech_type, ret, minor);
|
|
return -1;
|
|
}
|
|
|
|
if (time(0) > authtime + ticket_freshness_secs) {
|
|
syslog(LOG_INFO, "gssapi: the presented kerberos ticket for %s is too old", pctx->username);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gssapi_auth_init(void **ctx, void *pool, const char *spnego, const char *ip, const char *our_ip, unsigned pid)
|
|
{
|
|
struct gssapi_ctx_st *pctx;
|
|
OM_uint32 minor, flags, time;
|
|
gss_buffer_desc buf= GSS_C_EMPTY_BUFFER;
|
|
gss_name_t client = GSS_C_NO_NAME;
|
|
gss_OID mech_type = GSS_C_NO_OID;
|
|
int ret;
|
|
size_t raw_len;
|
|
char *raw;
|
|
|
|
if (spnego == NULL || spnego[0] == 0) {
|
|
syslog(LOG_ERR, "gssapi: error in spnego data %s", __func__);
|
|
return ERR_AUTH_FAIL;
|
|
}
|
|
|
|
pctx = talloc_zero(pool, struct gssapi_ctx_st);
|
|
if (pctx == NULL)
|
|
return ERR_AUTH_FAIL;
|
|
|
|
ret = base64_decode_alloc(spnego, strlen(spnego), &raw, &raw_len);
|
|
if (ret == 0) {
|
|
syslog(LOG_ERR, "gssapi: error in base64 decoding %s", __func__);
|
|
return ERR_AUTH_FAIL;
|
|
}
|
|
|
|
buf.value = raw;
|
|
buf.length = raw_len;
|
|
ret = gss_accept_sec_context(&minor, &pctx->gssctx, glob_creds, &buf,
|
|
GSS_C_NO_CHANNEL_BINDINGS, &client, &mech_type, &pctx->msg,
|
|
&flags, &time, &pctx->delegated_creds);
|
|
free(raw);
|
|
|
|
if (ret == GSS_S_CONTINUE_NEEDED) {
|
|
gss_release_name(&minor, &client);
|
|
ret = ERR_AUTH_CONTINUE;
|
|
} else if (ret == GSS_S_COMPLETE) {
|
|
ret = get_name(pctx, client, mech_type);
|
|
gss_release_name(&minor, &client);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = verify_krb5_constraints(pctx, mech_type);
|
|
} else {
|
|
print_gss_err("gss_accept_sec_context", mech_type, ret, minor);
|
|
return ERR_AUTH_FAIL;
|
|
}
|
|
|
|
*ctx = pctx;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gssapi_auth_group(void *ctx, const char *suggested, char *groupname, int groupname_size)
|
|
{
|
|
groupname[0] = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int gssapi_auth_user(void *ctx, char *username, int username_size)
|
|
{
|
|
struct gssapi_ctx_st *pctx = ctx;
|
|
|
|
strlcpy(username, pctx->username, username_size);
|
|
return 0;
|
|
}
|
|
|
|
/* Returns 0 if the user is successfully authenticated, and sets the appropriate group name.
|
|
*/
|
|
static int gssapi_auth_pass(void *ctx, const char *spnego, unsigned spnego_len)
|
|
{
|
|
struct gssapi_ctx_st *pctx = ctx;
|
|
OM_uint32 minor, flags, time;
|
|
gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
|
|
gss_name_t client = GSS_C_NO_NAME;
|
|
gss_OID mech_type = GSS_C_NO_OID;
|
|
size_t raw_len;
|
|
char *raw;
|
|
int ret;
|
|
|
|
/* nothing to be done */
|
|
ret = base64_decode_alloc(spnego, spnego_len, &raw, &raw_len);
|
|
if (ret == 0) {
|
|
syslog(LOG_ERR, "gssapi: error in base64 decoding %s", __func__);
|
|
return ERR_AUTH_FAIL;
|
|
}
|
|
|
|
buf.value = raw;
|
|
buf.length = raw_len;
|
|
ret = gss_accept_sec_context(&minor, &pctx->gssctx, glob_creds, &buf,
|
|
GSS_C_NO_CHANNEL_BINDINGS, &client, &mech_type, &pctx->msg,
|
|
&flags, &time, &pctx->delegated_creds);
|
|
free(raw);
|
|
|
|
if (ret == GSS_S_CONTINUE_NEEDED) {
|
|
gss_release_name(&minor, &client);
|
|
return ERR_AUTH_CONTINUE;
|
|
} else if (ret == GSS_S_COMPLETE) {
|
|
ret = get_name(pctx, client, mech_type);
|
|
gss_release_name(&minor, &client);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = verify_krb5_constraints(pctx, mech_type);
|
|
return ret;
|
|
} else {
|
|
print_gss_err("gss_accept_sec_context", mech_type, ret, minor);
|
|
return ERR_AUTH_FAIL;
|
|
}
|
|
}
|
|
|
|
static int gssapi_auth_msg(void *ctx, void *pool, char **msg)
|
|
{
|
|
struct gssapi_ctx_st *pctx = ctx;
|
|
OM_uint32 min;
|
|
unsigned length;
|
|
|
|
/* our msg is our SPNEGO reply */
|
|
if (pctx->msg.value != NULL) {
|
|
length = BASE64_LENGTH(pctx->msg.length)+1;
|
|
*msg = talloc_size(pool, length);
|
|
|
|
base64_encode((char *)pctx->msg.value, pctx->msg.length, *msg, length);
|
|
gss_release_buffer(&min, &pctx->msg);
|
|
pctx->msg.value = NULL;
|
|
} else {
|
|
*msg = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void gssapi_auth_deinit(void *ctx)
|
|
{
|
|
struct gssapi_ctx_st *pctx = ctx;
|
|
OM_uint32 min;
|
|
|
|
gss_delete_sec_context(&min, &pctx->gssctx, GSS_C_NO_BUFFER);
|
|
gss_release_cred(&min, &pctx->delegated_creds);
|
|
gss_release_buffer(&min, &pctx->msg);
|
|
talloc_free(ctx);
|
|
}
|
|
|
|
const struct auth_mod_st gssapi_auth_funcs = {
|
|
.type = AUTH_TYPE_GSSAPI,
|
|
.auth_init = gssapi_auth_init,
|
|
.auth_deinit = gssapi_auth_deinit,
|
|
.auth_msg = gssapi_auth_msg,
|
|
.auth_pass = gssapi_auth_pass,
|
|
.auth_user = gssapi_auth_user,
|
|
.auth_group = gssapi_auth_group,
|
|
.global_init = gssapi_global_init,
|
|
.global_deinit = gssapi_global_deinit,
|
|
};
|
|
|
|
#endif
|