summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_krb5/krb5.c
blob: 153a10ccb65a0732994e9cf2574c5a221c3f7582 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
 *   This program 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.
 *
 *   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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/**
 * $Id$
 * @file krb5.h
 * @brief Context management functions for rlm_krb5
 *
 * @copyright 2013  The FreeRADIUS server project
 * @copyright 2013  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
 */
RCSID("$Id$")

#include <freeradius-devel/radiusd.h>
#include "krb5.h"

#ifdef HAVE_KRB5_GET_ERROR_MESSAGE
#  define KRB5_STRERROR_BUFSIZE (2048)

fr_thread_local_setup(char *, krb5_error_buffer)	/* macro */

/*
 *	Explicitly cleanup the memory allocated to the error buffer.
 */
static void _krb5_logging_free(void *arg)
{
	free(arg);
}

char const *rlm_krb5_error(krb5_context context, krb5_error_code code)
{
	char const *msg;
	char *buffer;

	buffer = fr_thread_local_init(krb5_error_buffer, _krb5_logging_free);
	if (!buffer) {
		int ret;

		/*
		 *	malloc is thread safe, talloc is not
		 */
		buffer = malloc(sizeof(char) * KRB5_STRERROR_BUFSIZE);
		if (!buffer) {
			ERROR("Failed allocating memory for krb5 error buffer");
			return NULL;
		}

		ret = fr_thread_local_set(krb5_error_buffer, buffer);
		if (ret != 0) {
			ERROR("Failed setting up TLS for krb5 error buffer: %s", fr_syserror(ret));
			free(buffer);
			return NULL;
		}
	}

	msg = krb5_get_error_message(context, code);
	if (msg) {
		strlcpy(buffer, msg, KRB5_STRERROR_BUFSIZE);
#  ifdef HAVE_KRB5_FREE_ERROR_MESSAGE
		krb5_free_error_message(context, msg);
#  elif defined(HAVE_KRB5_FREE_ERROR_STRING)
		{
			char *free;

			memcpy(&free, &msg, sizeof(free));
			krb5_free_error_string(context, free);
		}
#  else
#    error "No way to free error strings, missing krb5_free_error_message() and krb5_free_error_string()"
#  endif
	} else {
		strlcpy(buffer, "Unknown error", KRB5_STRERROR_BUFSIZE);
	}

	return buffer;
}
#endif

/** Frees libkrb5 resources associated with the handle
 *
 * Must not be called directly.
 *
 * @param conn to free.
 * @return 0 (always indicates success).
 */
static int _mod_conn_free(rlm_krb5_handle_t *conn) {
	krb5_free_context(conn->context);

	if (conn->keytab) krb5_kt_close(conn->context, conn->keytab);

#ifdef HEIMDAL_KRB5
	if (conn->ccache) krb5_cc_destroy(conn->context, conn->ccache);
#endif

	return 0;
}

/** Create and return a new connection
 *
 * libkrb5(s) can talk to the KDC over TCP. Were assuming something sane is implemented
 * by libkrb5 and that it does connection caching associated with contexts, so it's
 * worth using a connection pool to preserve connections when workers die.
 */
void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
{
	rlm_krb5_t *inst = instance;
	rlm_krb5_handle_t *conn;
	krb5_error_code ret;

	MEM(conn = talloc_zero(ctx, rlm_krb5_handle_t));
	ret = krb5_init_context(&conn->context);
	if (ret) {
		ERROR("rlm_krb5 (%s): Context initialisation failed: %s", inst->xlat_name,
		       rlm_krb5_error(NULL, ret));

		return NULL;
	}
	talloc_set_destructor(conn, _mod_conn_free);

	ret = inst->keytabname ?
		krb5_kt_resolve(conn->context, inst->keytabname, &conn->keytab) :
		krb5_kt_default(conn->context, &conn->keytab);
	if (ret) {
		ERROR("Resolving keytab failed: %s", rlm_krb5_error(conn->context, ret));

		goto cleanup;
	}

#ifdef HEIMDAL_KRB5
	ret = krb5_cc_new_unique(conn->context, "MEMORY", NULL, &conn->ccache);
	if (ret) {
		ERROR("rlm_krb5 (%s): Credential cache creation failed: %s", inst->xlat_name,
		      rlm_krb5_error(conn->context, ret));

		return NULL;
	}

	krb5_verify_opt_init(&conn->options);
	krb5_verify_opt_set_ccache(&conn->options, conn->ccache);

	krb5_verify_opt_set_keytab(&conn->options, conn->keytab);
	krb5_verify_opt_set_secure(&conn->options, true);

	if (inst->service) krb5_verify_opt_set_service(&conn->options, inst->service);
#else
	krb5_verify_init_creds_opt_set_ap_req_nofail(inst->vic_options, true);
#endif
	return conn;

cleanup:
	talloc_free(conn);
	return NULL;
}