1/* MODULE: auth_krb5 */ 2 3/* COPYRIGHT 4 * Copyright (c) 1997 Messaging Direct Ltd. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY MESSAGING DIRECT LTD. ``AS IS'' AND ANY 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MESSAGING DIRECT LTD. OR 20 * ITS EMPLOYEES OR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 23 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 26 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 27 * DAMAGE. 28 * END COPYRIGHT */ 29 30#ifdef __GNUC__ 31#ident "$Id: auth_krb5.c,v 1.18 2008/01/23 15:39:34 murch Exp $" 32#endif 33 34/* ok, this is wrong but the most convenient way of doing 35 * it for now. We assume (possibly incorrectly) that if GSSAPI exists then 36 * the Kerberos 5 headers and libraries exist. 37 * What really should be done is a configure.in check for krb5.h and use 38 * that since none of this code is GSSAPI but rather raw Kerberos5. 39 */ 40 41 42/* Also, at some point one would hope it would be possible to 43 * have less divergence between Heimdal and MIT Kerberos 5. 44 * 45 * As of the summer of 2003, the obvious issues are that 46 * MIT doesn't have krb5_verify_opt_*() and Heimdal doesn't 47 * have krb5_sname_to_principal(). 48 */ 49 50/* PUBLIC DEPENDENCIES */ 51#include "mechanisms.h" 52#include "globals.h" /* mech_option */ 53#include "cfile.h" 54#include "krbtf.h" 55 56#ifdef AUTH_KRB5 57# include <krb5.h> 58static cfile config = 0; 59static char *keytabname = NULL; /* "system default" */ 60static char *verify_principal = "host"; /* a principal in the default keytab */ 61#endif /* AUTH_KRB5 */ 62 63#include <errno.h> 64#include <stdio.h> 65#include <stdlib.h> 66#include <string.h> 67#include <syslog.h> 68#include <unistd.h> 69#include <sys/stat.h> 70#include "auth_krb5.h" 71 72/* END PUBLIC DEPENDENCIES */ 73 74int /* R: -1 on failure, else 0 */ 75auth_krb5_init ( 76 /* PARAMETERS */ 77 void /* no parameters */ 78 /* END PARAMETERS */ 79 ) 80{ 81#ifdef AUTH_KRB5 82 int rc; 83 char *configname = 0; 84 85 if (krbtf_init() == -1) { 86 syslog(LOG_ERR, "auth_krb5_init krbtf_init failed"); 87 return -1; 88 } 89 90 if (mech_option) 91 configname = mech_option; 92 else if (access(SASLAUTHD_CONF_FILE_DEFAULT, F_OK) == 0) 93 configname = SASLAUTHD_CONF_FILE_DEFAULT; 94 95 if (configname) { 96 char complaint[1024]; 97 98 if (!(config = cfile_read(configname, complaint, sizeof (complaint)))) { 99 syslog(LOG_ERR, "auth_krb5_init %s", complaint); 100 return -1; 101 } 102 } 103 104 if (config) { 105 keytabname = (char *)cfile_getstring(config, "krb5_keytab", keytabname); /* APPLE: cast */ 106 verify_principal = (char *)cfile_getstring(config, "krb5_verify_principal", verify_principal); /* APPLE: cast */ 107 } 108 109 return 0; 110 111#else 112 return -1; 113#endif 114} 115 116#ifdef AUTH_KRB5 117 118static int 119form_principal_name ( 120 const char *user, 121 const char *service, 122 const char *realm, 123 char *pname, 124 int pnamelen 125 ) 126{ 127 const char *forced_instance = 0; 128 int plen; 129 130 if (config) { 131 char keyname[1024]; 132 133 snprintf(keyname, sizeof (keyname), "krb5_%s_instance", service); 134 forced_instance = cfile_getstring(config, keyname, 0); 135 } 136 137 if (forced_instance) { 138 char *user_specified; 139 140 if ((user_specified = strchr(user, '/'))) { /* APPLE: add parenthesizes */ 141 if (strcmp(user_specified + 1, forced_instance)) { 142 /* user not allowed to override sysadmin */ 143 return -1; 144 } else { 145 /* don't need to force--user already asked for it */ 146 forced_instance = 0; 147 } 148 } 149 } 150 151 /* form user[/instance][@realm] */ 152 plen = snprintf(pname, pnamelen, "%s%s%s%s%s", 153 user, 154 (forced_instance ? "/" : ""), 155 (forced_instance ? forced_instance : ""), 156 ((realm && realm[0]) ? "@" : ""), 157 ((realm && realm[0]) ? realm : "") 158 ); 159 if ((plen <= 0) || (plen >= pnamelen)) 160 return -1; 161 162 /* Perhaps we should uppercase the realm? */ 163 164 return 0; 165} 166 167#ifdef KRB5_HEIMDAL 168 169char * /* R: allocated response string */ 170auth_krb5 ( 171 /* PARAMETERS */ 172 const char *user, /* I: plaintext authenticator */ 173 const char *password, /* I: plaintext password */ 174 const char *service, /* I: service authenticating to */ 175 const char *realm /* I: user's realm */ 176 /* END PARAMETERS */ 177 ) 178{ 179 /* VARIABLES */ 180 krb5_context context; 181 krb5_ccache ccache = NULL; 182 krb5_keytab kt = NULL; 183 krb5_principal auth_user; 184 krb5_verify_opt opt; 185 char * result; 186 char tfname[2048]; 187 char principalbuf[2048]; 188 /* END VARIABLES */ 189 190 if (!user || !password) { 191 syslog(LOG_ERR, "auth_krb5: NULL password or username?"); 192 return strdup("NO saslauthd internal NULL password or username"); 193 } 194 195 if (krb5_init_context(&context)) { 196 syslog(LOG_ERR, "auth_krb5: krb5_init_context"); 197 return strdup("NO saslauthd internal krb5_init_context error"); 198 } 199 200 if (form_principal_name(user, service, realm, principalbuf, sizeof (principalbuf))) { 201 syslog(LOG_ERR, "auth_krb5: form_principal_name"); 202 return strdup("NO saslauthd principal name error"); 203 } 204 205 if (krb5_parse_name (context, principalbuf, &auth_user)) { 206 krb5_free_context(context); 207 syslog(LOG_ERR, "auth_krb5: krb5_parse_name"); 208 return strdup("NO saslauthd internal krb5_parse_name error"); 209 } 210 211 if (krbtf_name(tfname, sizeof (tfname)) != 0) { 212 syslog(LOG_ERR, "auth_krb5: could not generate ccache name"); 213 return strdup("NO saslauthd internal error"); 214 } 215 216 if (krb5_cc_resolve(context, tfname, &ccache)) { 217 krb5_free_principal(context, auth_user); 218 krb5_free_context(context); 219 syslog(LOG_ERR, "auth_krb5: krb5_cc_resolve"); 220 return strdup("NO saslauthd internal error"); 221 } 222 223 if (keytabname) { 224 if (krb5_kt_resolve(context, keytabname, &kt)) { 225 krb5_free_principal(context, auth_user); 226 krb5_cc_destroy(context, ccache); 227 krb5_free_context(context); 228 syslog(LOG_ERR, "auth_krb5: krb5_kt_resolve"); 229 return strdup("NO saslauthd internal error"); 230 } 231 } 232 233 krb5_verify_opt_init(&opt); 234 krb5_verify_opt_set_secure(&opt, 1); 235 krb5_verify_opt_set_ccache(&opt, ccache); 236 if (kt) 237 krb5_verify_opt_set_keytab(&opt, kt); 238 krb5_verify_opt_set_service(&opt, verify_principal); 239 240 if (krb5_verify_user_opt(context, auth_user, password, &opt)) { 241 result = strdup("NO krb5_verify_user_opt failed"); 242 } else { 243 result = strdup("OK"); 244 } 245 246 krb5_free_principal(context, auth_user); 247 krb5_cc_destroy(context, ccache); 248 if (kt) 249 krb5_kt_close(context, kt); 250 krb5_free_context(context); 251 252 return result; 253} 254 255#else /* !KRB5_HEIMDAL */ 256 257static void k5support_log_err(krb5_context context, 258 krb5_error_code code, 259 char const *msg) 260{ 261 const char *k5_msg = krb5_get_error_message(context, code); 262 263 syslog(LOG_DEBUG, "auth_krb5: %s: %s (%d)\n", msg, k5_msg, code); 264 krb5_free_error_message(context, k5_msg); 265} 266 267/* returns 0 for failure, 1 for success */ 268static int k5support_verify_tgt(krb5_context context, 269 krb5_ccache ccache) 270{ 271 krb5_principal server; 272 krb5_data packet; 273 krb5_keyblock *keyblock = NULL; 274 krb5_auth_context auth_context = NULL; 275 krb5_error_code k5_retcode; 276 krb5_keytab kt = NULL; 277 char thishost[BUFSIZ]; 278 int result = 0; 279 280 memset(&packet, 0, sizeof(packet)); 281 282 if ((k5_retcode = krb5_sname_to_principal(context, NULL, verify_principal, 283 KRB5_NT_SRV_HST, &server))) { 284 k5support_log_err(context, k5_retcode, "krb5_sname_to_principal()"); 285 return 0; 286 } 287 288 if (keytabname) { 289 if ((k5_retcode = krb5_kt_resolve(context, keytabname, &kt))) { 290 k5support_log_err(context, k5_retcode, "krb5_kt_resolve()"); 291 goto fini; 292 } 293 } 294 295 if ((k5_retcode = krb5_kt_read_service_key(context, kt, server, 0, 296 0, &keyblock))) { 297 k5support_log_err(context, k5_retcode, "krb5_kt_read_service_key()"); 298 goto fini; 299 } 300 301 if (keyblock) { 302 krb5_free_keyblock(context, keyblock); 303 } 304 305 /* this duplicates work done in krb5_sname_to_principal 306 * oh well. 307 */ 308 if (gethostname(thishost, BUFSIZ) < 0) { 309 goto fini; 310 } 311 thishost[BUFSIZ-1] = '\0'; 312 313 if ((k5_retcode = krb5_mk_req(context, &auth_context, 0, verify_principal, 314 thishost, NULL, ccache, &packet))) { 315 k5support_log_err(context, k5_retcode, "krb5_mk_req()"); 316 } 317 318 if (auth_context) { 319 krb5_auth_con_free(context, auth_context); 320 auth_context = NULL; 321 } 322 323 if (k5_retcode) { 324 goto fini; 325 } 326 327 if ((k5_retcode = krb5_rd_req(context, &auth_context, &packet, 328 server, NULL, NULL, NULL))) { 329 k5support_log_err(context, k5_retcode, "krb5_rd_req()"); 330 goto fini; 331 } 332 333 if (auth_context) { 334 krb5_auth_con_free(context, auth_context); 335 auth_context = NULL; 336 } 337 338 /* all is good now */ 339 result = 1; 340 fini: 341 if (!k5_retcode) { 342 krb5_free_data_contents(context, &packet); 343 } 344 krb5_free_principal(context, server); 345 346 return result; 347} 348 349/* FUNCTION: auth_krb5 */ 350 351/* SYNOPSIS 352 * Authenticate against Kerberos V. 353 * END SYNOPSIS */ 354 355char * /* R: allocated response string */ 356auth_krb5 ( 357 /* PARAMETERS */ 358 const char *user, /* I: plaintext authenticator */ 359 const char *password, /* I: plaintext password */ 360 const char *service, /* I: service authenticating to */ 361 const char *realm /* I: user's realm */ 362 /* END PARAMETERS */ 363 ) 364{ 365 /* VARIABLES */ 366 krb5_context context; 367 krb5_ccache ccache = NULL; 368 krb5_principal auth_user; 369 krb5_creds creds; 370 krb5_get_init_creds_opt opts; 371 char * result; 372 char tfname[2048]; 373 char principalbuf[2048]; 374 krb5_error_code code; 375 /* END VARIABLES */ 376 377 if (!user|| !password) { 378 syslog(LOG_ERR, "auth_krb5: NULL password or username?"); 379 return strdup("NO saslauthd internal error"); 380 } 381 382 if (krb5_init_context(&context)) { 383 syslog(LOG_ERR, "auth_krb5: krb5_init_context"); 384 return strdup("NO saslauthd internal error"); 385 } 386 387 if (form_principal_name(user, service, realm, principalbuf, sizeof (principalbuf))) { 388 syslog(LOG_ERR, "auth_krb5: form_principal_name"); 389 return strdup("NO saslauthd principal name error"); 390 } 391 392 if (krb5_parse_name (context, principalbuf, &auth_user)) { 393 krb5_free_context(context); 394 syslog(LOG_ERR, "auth_krb5: krb5_parse_name"); 395 return strdup("NO saslauthd internal error"); 396 } 397 398 if (krbtf_name(tfname, sizeof (tfname)) != 0) { 399 syslog(LOG_ERR, "auth_krb5: could not generate ticket file name"); 400 return strdup("NO saslauthd internal error"); 401 } 402 403 if (krb5_cc_resolve(context, tfname, &ccache)) { 404 krb5_free_principal(context, auth_user); 405 krb5_free_context(context); 406 syslog(LOG_ERR, "auth_krb5: krb5_cc_resolve"); 407 return strdup("NO saslauthd internal error"); 408 } 409 410 if (krb5_cc_initialize (context, ccache, auth_user)) { 411 krb5_free_principal(context, auth_user); 412 krb5_free_context(context); 413 syslog(LOG_ERR, "auth_krb5: krb5_cc_initialize"); 414 return strdup("NO saslauthd internal error"); 415 } 416 417 krb5_get_init_creds_opt_init(&opts); 418 /* 15 min should be more than enough */ 419 krb5_get_init_creds_opt_set_tkt_life(&opts, 900); 420 if ((code = krb5_get_init_creds_password(context, &creds, 421 auth_user, (char *)password, NULL, NULL, 422 0, NULL, &opts))) { /* APPLE: add parenthesizes, cast */ 423 krb5_cc_destroy(context, ccache); 424 krb5_free_principal(context, auth_user); 425 krb5_free_context(context); 426 syslog(LOG_ERR, "auth_krb5: krb5_get_init_creds_password: %d", code); 427 return strdup("NO saslauthd internal error"); 428 } 429 430 /* at this point we should have a TGT. Let's make sure it is valid */ 431 if (krb5_cc_store_cred(context, ccache, &creds)) { 432 krb5_free_principal(context, auth_user); 433 krb5_cc_destroy(context, ccache); 434 krb5_free_context(context); 435 syslog(LOG_ERR, "auth_krb5: krb5_cc_store_cred"); 436 return strdup("NO saslauthd internal error"); 437 } 438 439 if (!k5support_verify_tgt(context, ccache)) { 440 syslog(LOG_ERR, "auth_krb5: k5support_verify_tgt"); 441 result = strdup("NO saslauthd internal error"); 442 goto fini; 443 } 444 445 /* 446 * fall through -- user is valid beyond this point 447 */ 448 449 result = strdup("OK"); 450 fini: 451/* destroy any tickets we had */ 452 krb5_free_cred_contents(context, &creds); 453 krb5_free_principal(context, auth_user); 454 krb5_cc_destroy(context, ccache); 455 krb5_free_context(context); 456 457 return result; 458} 459 460#endif /* KRB5_HEIMDAL */ 461 462#else /* ! AUTH_KRB5 */ 463 464char * 465auth_krb5 ( 466 const char *login __attribute__((unused)), 467 const char *password __attribute__((unused)), 468 const char *service __attribute__((unused)), 469 const char *realm __attribute__((unused)) 470 ) 471{ 472 return NULL; 473} 474 475#endif /* ! AUTH_KRB5 */ 476 477/* END FUNCTION: auth_krb5 */ 478 479/* END MODULE: auth_krb5 */ 480