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.10 2006/02/03 22:33:14 snsimon 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 const char *keytabname = NULL; /* "system default" */ 60static const 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, (int)sizeof (complaint)))) { 99 syslog(LOG_ERR, "auth_krb5_init %s", complaint); 100 return -1; 101 } 102 } 103 104 if (config) { 105 keytabname = cfile_getstring(config, "krb5_keytab", keytabname); 106 verify_principal = cfile_getstring(config, "krb5_verify_principal", verify_principal); 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, '/')) { 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 257/* returns 0 for failure, 1 for success */ 258static int k5support_verify_tgt(krb5_context context, 259 krb5_ccache ccache) 260{ 261 krb5_principal server; 262 krb5_data packet; 263 krb5_keyblock *keyblock = NULL; 264 krb5_auth_context auth_context = NULL; 265 krb5_error_code k5_retcode; 266 krb5_keytab kt = NULL; 267 char thishost[BUFSIZ]; 268 int result = 0; 269 270 memset(&packet, 0, sizeof(packet)); 271 272 if (krb5_sname_to_principal(context, NULL, verify_principal, 273 KRB5_NT_SRV_HST, &server)) { 274 return 0; 275 } 276 277 if (keytabname) { 278 if (krb5_kt_resolve(context, keytabname, &kt)) { 279 goto fini; 280 } 281 } 282 283 if (krb5_kt_read_service_key(context, kt, server, 0, 284 0, &keyblock)) { 285 goto fini; 286 } 287 288 if (keyblock) { 289 krb5_free_keyblock(context, keyblock); 290 } 291 292 /* this duplicates work done in krb5_sname_to_principal 293 * oh well. 294 */ 295 if (gethostname(thishost, BUFSIZ) < 0) { 296 goto fini; 297 } 298 thishost[BUFSIZ-1] = '\0'; 299 300 k5_retcode = krb5_mk_req(context, &auth_context, 0, (char*)verify_principal, thishost, NULL, ccache, &packet); 301 if (auth_context) { 302 krb5_auth_con_free(context, auth_context); 303 auth_context = NULL; 304 } 305 306 if (k5_retcode) { 307 goto fini; 308 } 309 310 if (krb5_rd_req(context, &auth_context, &packet, 311 server, NULL, NULL, NULL)) { 312 goto fini; 313 } 314 315 if (auth_context) { 316 krb5_auth_con_free(context, auth_context); 317 auth_context = NULL; 318 } 319 320 /* all is good now */ 321 result = 1; 322 fini: 323 krb5_free_data_contents(context, &packet); 324 krb5_free_principal(context, server); 325 326 return result; 327} 328 329/* FUNCTION: auth_krb5 */ 330 331/* SYNOPSIS 332 * Authenticate against Kerberos V. 333 * END SYNOPSIS */ 334 335char * /* R: allocated response string */ 336auth_krb5 ( 337 /* PARAMETERS */ 338 const char *user, /* I: plaintext authenticator */ 339 const char *password, /* I: plaintext password */ 340 const char *service, /* I: service authenticating to */ 341 const char *realm /* I: user's realm */ 342 /* END PARAMETERS */ 343 ) 344{ 345 /* VARIABLES */ 346 krb5_context context; 347 krb5_ccache ccache = NULL; 348 krb5_principal auth_user; 349 krb5_creds creds; 350 krb5_get_init_creds_opt opts; 351 char * result; 352 char tfname[2048]; 353 char principalbuf[2048]; 354 krb5_error_code code; 355 /* END VARIABLES */ 356 357 if (!user|| !password) { 358 syslog(LOG_ERR, "auth_krb5: NULL password or username?"); 359 return strdup("NO saslauthd internal error"); 360 } 361 362 if (krb5_init_context(&context)) { 363 syslog(LOG_ERR, "auth_krb5: krb5_init_context"); 364 return strdup("NO saslauthd internal error"); 365 } 366 367 if (form_principal_name(user, service, realm, principalbuf, (int)sizeof (principalbuf))) { 368 syslog(LOG_ERR, "auth_krb5: form_principal_name"); 369 return strdup("NO saslauthd principal name error"); 370 } 371 372 if (krb5_parse_name (context, principalbuf, &auth_user)) { 373 krb5_free_context(context); 374 syslog(LOG_ERR, "auth_krb5: krb5_parse_name"); 375 return strdup("NO saslauthd internal error"); 376 } 377 378 if (krbtf_name(tfname, (int)sizeof (tfname)) != 0) { 379 syslog(LOG_ERR, "auth_krb5: could not generate ticket file name"); 380 return strdup("NO saslauthd internal error"); 381 } 382 383 if (krb5_cc_resolve(context, tfname, &ccache)) { 384 krb5_free_principal(context, auth_user); 385 krb5_free_context(context); 386 syslog(LOG_ERR, "auth_krb5: krb5_cc_resolve"); 387 return strdup("NO saslauthd internal error"); 388 } 389 390 if (krb5_cc_initialize (context, ccache, auth_user)) { 391 krb5_free_principal(context, auth_user); 392 krb5_free_context(context); 393 syslog(LOG_ERR, "auth_krb5: krb5_cc_initialize"); 394 return strdup("NO saslauthd internal error"); 395 } 396 397 krb5_get_init_creds_opt_init(&opts); 398 /* 15 min should be more than enough */ 399 krb5_get_init_creds_opt_set_tkt_life(&opts, 900); 400 if (code = krb5_get_init_creds_password(context, &creds, 401 auth_user, (char*)password, NULL, NULL, 402 0, NULL, &opts)) { 403 krb5_cc_destroy(context, ccache); 404 krb5_free_principal(context, auth_user); 405 krb5_free_context(context); 406 syslog(LOG_ERR, "auth_krb5: krb5_get_init_creds_password: %d", code); 407 return strdup("NO saslauthd internal error"); 408 } 409 410 /* at this point we should have a TGT. Let's make sure it is valid */ 411 if (krb5_cc_store_cred(context, ccache, &creds)) { 412 krb5_free_principal(context, auth_user); 413 krb5_cc_destroy(context, ccache); 414 krb5_free_context(context); 415 syslog(LOG_ERR, "auth_krb5: krb5_cc_store_cred"); 416 return strdup("NO saslauthd internal error"); 417 } 418 419 if (!k5support_verify_tgt(context, ccache)) { 420 syslog(LOG_ERR, "auth_krb5: k5support_verify_tgt"); 421 result = strdup("NO saslauthd internal error"); 422 goto fini; 423 } 424 425 /* 426 * fall through -- user is valid beyond this point 427 */ 428 429 result = strdup("OK"); 430 fini: 431/* destroy any tickets we had */ 432 krb5_free_cred_contents(context, &creds); 433 krb5_free_principal(context, auth_user); 434 krb5_cc_destroy(context, ccache); 435 krb5_free_context(context); 436 437 return result; 438} 439 440#endif /* KRB5_HEIMDAL */ 441 442#else /* ! AUTH_KRB5 */ 443 444char * 445auth_krb5 ( 446 const char *login __attribute__((unused)), 447 const char *password __attribute__((unused)), 448 const char *service __attribute__((unused)), 449 const char *realm __attribute__((unused)) 450 ) 451{ 452 return NULL; 453} 454 455#endif /* ! AUTH_KRB5 */ 456 457/* END FUNCTION: auth_krb5 */ 458 459/* END MODULE: auth_krb5 */ 460