All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
rlm_krb5.c
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or
5  * (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: 0cb5c2bb861a2228022eb93616f3da40e4333c1f $
19  * @file rlm_krb5.c
20  * @brief Authenticate users, retrieving their TGT from a Kerberos V5 TDC.
21  *
22  * @copyright 2000,2006,2012-2013 The FreeRADIUS server project
23  * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
24  * @copyright 2000 Nathan Neulinger <nneul@umr.edu>
25  * @copyright 2000 Alan DeKok <aland@ox.org>
26  */
27 RCSID("$Id: 0cb5c2bb861a2228022eb93616f3da40e4333c1f $")
28 
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/modules.h>
31 #include <freeradius-devel/rad_assert.h>
32 #include "krb5.h"
33 
34 static const CONF_PARSER module_config[] = {
35  { FR_CONF_OFFSET("keytab", PW_TYPE_STRING, rlm_krb5_t, keytabname) },
36  { FR_CONF_OFFSET("service_principal", PW_TYPE_STRING, rlm_krb5_t, service_princ) },
38 };
39 
40 static int mod_detach(void *instance)
41 {
42  rlm_krb5_t *inst = instance;
43 
44 #ifndef HEIMDAL_KRB5
45  talloc_free(inst->vic_options);
46 
47  if (inst->gic_options) krb5_get_init_creds_opt_free(inst->context, inst->gic_options);
48  if (inst->server) krb5_free_principal(inst->context, inst->server);
49 #endif
50 
51  /* Don't free hostname, it's just a pointer into service_princ */
52  talloc_free(inst->service);
53 
54  if (inst->context) krb5_free_context(inst->context);
55 #ifdef KRB5_IS_THREAD_SAFE
56  fr_connection_pool_free(inst->pool);
57 #endif
58 
59  return 0;
60 }
61 
62 static int mod_instantiate(CONF_SECTION *conf, void *instance)
63 {
64  rlm_krb5_t *inst = instance;
65  krb5_error_code ret;
66 #ifndef HEIMDAL_KRB5
67  krb5_keytab keytab;
68  char keytab_name[200];
69  char *princ_name;
70 #endif
71 
72 #ifdef HEIMDAL_KRB5
73  DEBUG("Using Heimdal Kerberos library");
74 #else
75  DEBUG("Using MIT Kerberos library");
76 #endif
77 
78 
79  if (!krb5_is_thread_safe()) {
80 /*
81  * rlm_krb5 was built as threadsafe
82  */
83 #ifdef KRB5_IS_THREAD_SAFE
84  ERROR("Build time libkrb5 was threadsafe, but run time library claims not to be");
85  ERROR("Modify runtime linker path (LD_LIBRARY_PATH on most systems), to prefer threadsafe libkrb5");
86  return -1;
87 /*
88  * rlm_krb5 was not built as threadsafe
89  */
90 #else
91  radlog(L_WARN, "libkrb5 is not threadsafe, recompile it with thread support enabled ("
92 # ifdef HEIMDAL_KRB5
93  "--enable-pthread-support"
94 # else
95  "--disable-thread-support=no"
96 # endif
97  ")");
98  WARN("rlm_krb5 will run in single threaded mode, performance may be degraded");
99  } else {
100  WARN("Build time libkrb5 was not threadsafe, but run time library claims to be");
101  WARN("Reconfigure and recompile rlm_krb5 to enable thread support");
102 #endif
103  }
104 
105  inst->xlat_name = cf_section_name2(conf);
106  if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
107 
108  ret = krb5_init_context(&inst->context);
109  if (ret) {
110  ERROR("rlm_krb5 (%s): context initialisation failed: %s", inst->xlat_name,
111  rlm_krb5_error(NULL, ret));
112 
113  return -1;
114  }
115 
116  /*
117  * Split service principal into service and host components
118  * they're needed to build the server principal in MIT,
119  * and to set the validation service in Heimdal.
120  */
121  if (inst->service_princ) {
122  size_t len;
123  /* Service principal appears to contain a host component */
124  inst->hostname = strchr(inst->service_princ, '/');
125  if (inst->hostname) {
126  len = (inst->hostname - inst->service_princ);
127  inst->hostname++;
128  } else {
129  len = strlen(inst->service_princ);
130  }
131 
132  if (len) {
133  inst->service = talloc_array(inst, char, (len + 1));
134  strlcpy(inst->service, inst->service_princ, len + 1);
135  }
136  }
137 
138 #ifdef HEIMDAL_KRB5
139  if (inst->hostname) DEBUG("rlm_krb5 (%s): Ignoring hostname component of service principal \"%s\", not "
140  "needed/supported by Heimdal", inst->xlat_name, inst->hostname);
141 #else
142 
143  /*
144  * Convert the service principal string to a krb5 principal.
145  */
146  ret = krb5_sname_to_principal(inst->context, inst->hostname, inst->service, KRB5_NT_SRV_HST, &(inst->server));
147  if (ret) {
148  ERROR("rlm_krb5 (%s): Failed parsing service principal: %s", inst->xlat_name,
149  rlm_krb5_error(inst->context, ret));
150 
151  return -1;
152  }
153 
154  ret = krb5_unparse_name(inst->context, inst->server, &princ_name);
155  if (ret) {
156  /* Uh? */
157  ERROR("rlm_krb5 (%s): Failed constructing service principal string: %s", inst->xlat_name,
158  rlm_krb5_error(inst->context, ret));
159 
160  return -1;
161  }
162 
163  /*
164  * Not necessarily the same as the config item
165  */
166  DEBUG("rlm_krb5 (%s): Using service principal \"%s\"", inst->xlat_name, princ_name);
167  krb5_free_unparsed_name(inst->context, princ_name);
168 
169  /*
170  * Setup options for getting credentials and verifying them
171  */
172  ret = krb5_get_init_creds_opt_alloc(inst->context, &(inst->gic_options)); /* For some reason the 'init' version
173  of this function is deprecated */
174  if (ret) {
175  ERROR("rlm_krb5 (%s): Couldn't allocated inital credential options: %s", inst->xlat_name,
176  rlm_krb5_error(inst->context, ret));
177 
178  return -1;
179  }
180 
181  /*
182  * Perform basic checks on the keytab
183  */
184  ret = inst->keytabname ?
185  krb5_kt_resolve(inst->context, inst->keytabname, &keytab) :
186  krb5_kt_default(inst->context, &keytab);
187  if (ret) {
188  ERROR("rlm_krb5 (%s): Resolving keytab failed: %s", inst->xlat_name,
189  rlm_krb5_error(inst->context, ret));
190 
191  return -1;
192  }
193 
194  ret = krb5_kt_get_name(inst->context, keytab, keytab_name, sizeof(keytab_name));
195  krb5_kt_close(inst->context, keytab);
196  if (ret) {
197  ERROR("rlm_krb5 (%s): Can't retrieve keytab name: %s", inst->xlat_name,
198  rlm_krb5_error(inst->context, ret));
199 
200  return -1;
201  }
202 
203  DEBUG("rlm_krb5 (%s): Using keytab \"%s\"", inst->xlat_name, keytab_name);
204 
205  MEM(inst->vic_options = talloc_zero(inst, krb5_verify_init_creds_opt));
206  krb5_verify_init_creds_opt_init(inst->vic_options);
207 #endif
208 
209 #ifdef KRB5_IS_THREAD_SAFE
210  /*
211  * Initialize the socket pool.
212  */
213  inst->pool = module_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL);
214  if (!inst->pool) return -1;
215 #else
216  inst->conn = mod_conn_create(inst, inst, NULL);
217  if (!inst->conn) return -1;
218 #endif
219  return 0;
220 }
221 
222 /** Common function for transforming a User-Name string into a principal.
223  *
224  * @param[out] client Where to write the client principal.
225  * @param[in] request Current request.
226  * @param[in] context Kerberos context.
227  */
228 static rlm_rcode_t krb5_parse_user(krb5_principal *client, REQUEST *request, krb5_context context)
229 {
230  krb5_error_code ret;
231  char *princ_name;
232 
233  /*
234  * We can only authenticate user requests which HAVE
235  * a User-Name attribute.
236  */
237  if (!request->username) {
238  REDEBUG("Attribute \"User-Name\" is required for authentication");
239 
240  return RLM_MODULE_INVALID;
241  }
242 
243  /*
244  * We can only authenticate user requests which HAVE
245  * a User-Password attribute.
246  */
247  if (!request->password) {
248  REDEBUG("Attribute \"User-Password\" is required for authentication");
249 
250  return RLM_MODULE_INVALID;
251  }
252 
253  /*
254  * Ensure that we're being passed a plain-text password,
255  * and not anything else.
256  */
257  if (request->password->da->attr != PW_USER_PASSWORD) {
258  REDEBUG("Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".",
259  request->password->da->name);
260 
261  return RLM_MODULE_INVALID;
262  }
263 
264  ret = krb5_parse_name(context, request->username->vp_strvalue, client);
265  if (ret) {
266  REDEBUG("Failed parsing username as principal: %s", rlm_krb5_error(context, ret));
267 
268  return RLM_MODULE_FAIL;
269  }
270 
271  krb5_unparse_name(context, *client, &princ_name);
272  RDEBUG("Using client principal \"%s\"", princ_name);
273 #ifdef HEIMDAL_KRB5
274  free(princ_name);
275 #else
276  krb5_free_unparsed_name(context, princ_name);
277 #endif
278  return RLM_MODULE_OK;
279 }
280 
281 /** Log error message and return appropriate rcode
282  *
283  * Translate kerberos error codes into return codes.
284  * @param request Current request.
285  * @param ret code from kerberos.
286  * @param conn used in the last operation.
287  */
288 static rlm_rcode_t krb5_process_error(REQUEST *request, rlm_krb5_handle_t *conn, int ret)
289 {
290  rad_assert(ret != 0);
291  rad_assert(conn); /* Silences warnings */
292 
293  switch (ret) {
294  case KRB5_LIBOS_BADPWDMATCH:
295  case KRB5KRB_AP_ERR_BAD_INTEGRITY:
296  REDEBUG("Provided password was incorrect (%i): %s", ret, rlm_krb5_error(conn->context, ret));
297  return RLM_MODULE_REJECT;
298 
299  case KRB5KDC_ERR_KEY_EXP:
300  case KRB5KDC_ERR_CLIENT_REVOKED:
301  case KRB5KDC_ERR_SERVICE_REVOKED:
302  REDEBUG("Account has been locked out (%i): %s", ret, rlm_krb5_error(conn->context, ret));
303  return RLM_MODULE_USERLOCK;
304 
305  case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
306  RDEBUG("User not found (%i): %s", ret, rlm_krb5_error(conn->context, ret));
307  return RLM_MODULE_NOTFOUND;
308 
309  default:
310  REDEBUG("Error verifying credentials (%i): %s", ret, rlm_krb5_error(conn->context, ret));
311  return RLM_MODULE_FAIL;
312  }
313 }
314 
315 #ifdef HEIMDAL_KRB5
316 
317 /*
318  * Validate user/pass (Heimdal)
319  */
320 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
321 {
322  rlm_krb5_t *inst = instance;
323  rlm_rcode_t rcode;
324  krb5_error_code ret;
325 
326  rlm_krb5_handle_t *conn;
327 
328  krb5_principal client;
329 
330 # ifdef KRB5_IS_THREAD_SAFE
331  conn = fr_connection_get(inst->pool);
332  if (!conn) return RLM_MODULE_FAIL;
333 # else
334  conn = inst->conn;
335 # endif
336 
337  /*
338  * Zero out local storage
339  */
340  memset(&client, 0, sizeof(client));
341 
342  rcode = krb5_parse_user(&client, request, conn->context);
343  if (rcode != RLM_MODULE_OK) goto cleanup;
344 
345  /*
346  * Verify the user, using the options we set in instantiate
347  */
348  ret = krb5_verify_user_opt(conn->context, client, request->password->vp_strvalue, &conn->options);
349  if (ret) {
350  rcode = krb5_process_error(request, conn, ret);
351  goto cleanup;
352  }
353 
354  /*
355  * krb5_verify_user_opt adds the credentials to the ccache
356  * we specified with krb5_verify_opt_set_ccache.
357  *
358  * To make sure we don't accumulate thousands of sets of
359  * credentials, remove them again here.
360  *
361  * @todo This should definitely be optional, which means writing code for the MIT
362  * variant as well.
363  */
364  {
365  krb5_cc_cursor cursor;
366  krb5_creds cred;
367 
368  krb5_cc_start_seq_get(conn->context, conn->ccache, &cursor);
369  for (ret = krb5_cc_next_cred(conn->context, conn->ccache, &cursor, &cred);
370  ret == 0;
371  ret = krb5_cc_next_cred(conn->context, conn->ccache, &cursor, &cred)) {
372  krb5_cc_remove_cred(conn->context, conn->ccache, 0, &cred);
373  }
374  krb5_cc_end_seq_get(conn->context, conn->ccache, &cursor);
375  }
376 
377 cleanup:
378  if (client) {
379  krb5_free_principal(conn->context, client);
380  }
381 
382 # ifdef KRB5_IS_THREAD_SAFE
383  fr_connection_release(inst->pool, conn);
384 # endif
385  return rcode;
386 }
387 
388 #else /* HEIMDAL_KRB5 */
389 
390 /*
391  * Validate userid/passwd (MIT)
392  */
393 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
394 {
395  rlm_krb5_t *inst = instance;
396  rlm_rcode_t rcode;
397  krb5_error_code ret;
398 
399  rlm_krb5_handle_t *conn;
400 
401  krb5_principal client;
402  krb5_creds init_creds;
403  char *password; /* compiler warnings */
404 
405  rad_assert(inst->context);
406 
407 # ifdef KRB5_IS_THREAD_SAFE
408  conn = fr_connection_get(inst->pool);
409  if (!conn) return RLM_MODULE_FAIL;
410 # else
411  conn = inst->conn;
412 # endif
413 
414  /*
415  * Zero out local storage
416  */
417  memset(&client, 0, sizeof(client));
418  memset(&init_creds, 0, sizeof(init_creds));
419 
420  /*
421  * Check we have all the required VPs, and convert the username
422  * into a principal.
423  */
424  rcode = krb5_parse_user(&client, request, conn->context);
425  if (rcode != RLM_MODULE_OK) goto cleanup;
426 
427  /*
428  * Retrieve the TGT from the TGS/KDC and check we can decrypt it.
429  */
430  memcpy(&password, &request->password->vp_strvalue, sizeof(password));
431  RDEBUG("Retrieving and decrypting TGT");
432  ret = krb5_get_init_creds_password(conn->context, &init_creds, client, password,
433  NULL, NULL, 0, NULL, inst->gic_options);
434  if (ret) {
435  rcode = krb5_process_error(request, conn, ret);
436  goto cleanup;
437  }
438 
439  RDEBUG("Attempting to authenticate against service principal");
440  ret = krb5_verify_init_creds(conn->context, &init_creds, inst->server, conn->keytab, NULL, inst->vic_options);
441  if (ret) rcode = krb5_process_error(request, conn, ret);
442 
443 cleanup:
444  if (client) krb5_free_principal(conn->context, client);
445  krb5_free_cred_contents(conn->context, &init_creds);
446 
447 # ifdef KRB5_IS_THREAD_SAFE
448  fr_connection_release(inst->pool, conn);
449 # endif
450  return rcode;
451 }
452 
453 #endif /* MIT_KRB5 */
454 
455 extern module_t rlm_krb5;
456 module_t rlm_krb5 = {
458  .name = "krb5",
459  .type = RLM_TYPE_HUP_SAFE
460 #ifdef KRB5_IS_THREAD_SAFE
462 #endif
463  ,
464  .inst_size = sizeof(rlm_krb5_t),
465  .config = module_config,
466  .instantiate = mod_instantiate,
467  .detach = mod_detach,
468  .methods = {
470  },
471 };
The module is OK, continue.
Definition: radiusd.h:91
Instance configuration for rlm_krb5.
Definition: krb5.h:49
Metadata exported by the module.
Definition: modules.h:134
int radlog(log_type_t lvl, char const *fmt,...) CC_HINT(format(printf
#define MEM(x)
Definition: radiusd.h:396
Warning.
Definition: log.h:37
krb5_context context
The kerberos context (cloned once per request).
Definition: krb5.h:65
#define RLM_TYPE_THREAD_SAFE
Module is threadsafe.
Definition: modules.h:75
VALUE_PAIR * username
Cached username VALUE_PAIR from request RADIUS_PACKET.
Definition: radiusd.h:222
krb5_get_init_creds_opt * gic_options
Options to pass to the get_initial_credentials function.
Definition: krb5.h:68
#define RLM_MODULE_INIT
Definition: modules.h:86
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
VALUE_PAIR * password
Cached password VALUE_PAIR from request RADIUS_PACKET.
Definition: radiusd.h:223
static int mod_instantiate(CONF_SECTION *conf, void *instance)
Definition: rlm_krb5.c:62
#define inst
struct rlm_krb5_t rlm_krb5_t
Instance configuration for rlm_krb5.
The module considers the request invalid.
Definition: radiusd.h:93
#define RLM_TYPE_HUP_SAFE
Will be restarted on HUP.
Definition: modules.h:79
#define rlm_krb5_error(_x, _y)
Definition: krb5.h:88
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) CC_HINT(nonnull)
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
#define rad_assert(expr)
Definition: rad_assert.h:38
Reject the request (user is locked out).
Definition: radiusd.h:94
char const * keytabname
The keytab to resolve the service in.
Definition: krb5.h:57
static rlm_rcode_t krb5_parse_user(krb5_principal *client, REQUEST *request, krb5_context context)
Common function for transforming a User-Name string into a principal.
Definition: rlm_krb5.c:228
fr_connection_pool_t * module_connection_pool_init(CONF_SECTION *module, void *opaque, fr_connection_create_t c, fr_connection_alive_t a, char const *prefix)
Initialise a module specific connection pool.
Definition: modules.c:1759
#define DEBUG(fmt,...)
Definition: log.h:175
static void * mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout)
Create a new memcached handle.
rlm_krb5_handle_t * conn
Definition: krb5.h:53
static const CONF_PARSER module_config[]
Definition: rlm_krb5.c:34
unsigned int attr
Attribute number.
Definition: dict.h:79
Immediately reject the request.
Definition: radiusd.h:89
0 methods index for authenticate section.
Definition: modules.h:41
char * hostname
The hostname component of service_princ, or NULL.
Definition: krb5.h:61
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
static rs_t * conf
Definition: radsniff.c:46
module_t rlm_krb5
Definition: rlm_krb5.c:456
static bool cleanup
Definition: radsniff.c:53
char const * cf_section_name1(CONF_SECTION const *cs)
Definition: conffile.c:3592
char * service
The service component of service_princ, or NULL.
Definition: krb5.h:63
char name[1]
Attribute name.
Definition: dict.h:89
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
Module failed, don't reply.
Definition: radiusd.h:90
#define FR_CONF_OFFSET(_n, _t, _s, _f)
Definition: conffile.h:168
static int mod_detach(void *instance)
Definition: rlm_krb5.c:40
char const * xlat_name
This module's instance name.
Definition: krb5.h:56
void * fr_connection_get(fr_connection_pool_t *pool)
Reserve a connection in the connection pool.
Definition: connection.c:1291
krb5_principal server
A structure representing the parsed service_princ.
Definition: krb5.h:73
#define WARN(fmt,...)
Definition: log.h:144
Context management functions for rlm_krb5.
#define REDEBUG(fmt,...)
Definition: log.h:254
char const * service_princ
The service name provided by the config parser.
Definition: krb5.h:58
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:38
fr_dict_attr_t const * da
Dictionary attribute defines the attribute.
Definition: pair.h:113
void fr_connection_release(fr_connection_pool_t *pool, void *conn)
Release a connection.
Definition: connection.c:1305
String of printable characters.
Definition: radius.h:33
User not found.
Definition: radiusd.h:95
#define RCSID(id)
Definition: build.h:135
krb5_verify_init_creds_opt * vic_options
Options to pass to the validate_initial_creds function.
Definition: krb5.h:70
#define RDEBUG(fmt,...)
Definition: log.h:243
#define ERROR(fmt,...)
Definition: log.h:145
USES_APPLE_DEPRECATED_API struct rlm_krb5_handle rlm_krb5_handle_t
static rlm_rcode_t krb5_process_error(REQUEST *request, rlm_krb5_handle_t *conn, int ret)
Log error message and return appropriate rcode.
Definition: rlm_krb5.c:288
char const * cf_section_name2(CONF_SECTION const *cs)
Definition: conffile.c:3601
static rlm_rcode_t CC_HINT(nonnull)
Definition: rlm_krb5.c:393
void fr_connection_pool_free(fr_connection_pool_t *pool)
Delete a connection pool.
Definition: connection.c:1226