1/* $NetBSD: aname_to_localname.c,v 1.2 2017/01/28 21:31:49 christos Exp $ */ 2 3/* 4 * Copyright (c) 1997 - 1999, 2002 - 2003 Kungliga Tekniska H��gskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include <string.h> 37#include "krb5_locl.h" 38#include "an2ln_plugin.h" 39#include "db_plugin.h" 40 41/* Default plugin (DB using binary search of sorted text file) follows */ 42static krb5_error_code KRB5_LIB_CALL an2ln_def_plug_init(krb5_context, void **); 43static void KRB5_LIB_CALL an2ln_def_plug_fini(void *); 44static krb5_error_code KRB5_LIB_CALL an2ln_def_plug_an2ln(void *, krb5_context, const char *, 45 krb5_const_principal, set_result_f, 46 void *); 47 48static krb5plugin_an2ln_ftable an2ln_def_plug = { 49 0, 50 an2ln_def_plug_init, 51 an2ln_def_plug_fini, 52 an2ln_def_plug_an2ln, 53}; 54 55/* Plugin engine code follows */ 56struct plctx { 57 krb5_const_principal aname; 58 heim_string_t luser; 59 const char *rule; 60}; 61 62static krb5_error_code KRB5_LIB_CALL 63set_res(void *userctx, const char *res) 64{ 65 struct plctx *plctx = userctx; 66 plctx->luser = heim_string_create(res); 67 if (plctx->luser == NULL) 68 return ENOMEM; 69 return 0; 70} 71 72static krb5_error_code KRB5_LIB_CALL 73plcallback(krb5_context context, 74 const void *plug, void *plugctx, void *userctx) 75{ 76 const krb5plugin_an2ln_ftable *locate = plug; 77 struct plctx *plctx = userctx; 78 79 if (plctx->luser) 80 return 0; 81 82 return locate->an2ln(plugctx, context, plctx->rule, plctx->aname, set_res, plctx); 83} 84 85static krb5_error_code 86an2ln_plugin(krb5_context context, const char *rule, krb5_const_principal aname, 87 size_t lnsize, char *lname) 88{ 89 krb5_error_code ret; 90 struct plctx ctx; 91 92 ctx.rule = rule; 93 ctx.aname = aname; 94 ctx.luser = NULL; 95 96 /* 97 * Order of plugin invocation is non-deterministic, but there should 98 * really be no more than one plugin that can handle any given kind 99 * rule, so the effect should be deterministic anyways. 100 */ 101 ret = _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_AN2LN, 102 KRB5_PLUGIN_AN2LN_VERSION_0, 0, &ctx, plcallback); 103 if (ret != 0) { 104 heim_release(ctx.luser); 105 return ret; 106 } 107 108 if (ctx.luser == NULL) 109 return KRB5_PLUGIN_NO_HANDLE; 110 111 if (strlcpy(lname, heim_string_get_utf8(ctx.luser), lnsize) >= lnsize) 112 ret = KRB5_CONFIG_NOTENUFSPACE; 113 114 heim_release(ctx.luser); 115 return ret; 116} 117 118static void 119reg_def_plugins_once(void *ctx) 120{ 121 krb5_context context = ctx; 122 123 krb5_plugin_register(context, PLUGIN_TYPE_DATA, KRB5_PLUGIN_AN2LN, 124 &an2ln_def_plug); 125} 126 127static int 128princ_realm_is_default(krb5_context context, 129 krb5_const_principal aname) 130{ 131 krb5_error_code ret; 132 krb5_realm *lrealms = NULL; 133 krb5_realm *r; 134 int valid; 135 136 ret = krb5_get_default_realms(context, &lrealms); 137 if (ret) 138 return 0; 139 140 valid = 0; 141 for (r = lrealms; *r != NULL; ++r) { 142 if (strcmp (*r, aname->realm) == 0) { 143 valid = 1; 144 break; 145 } 146 } 147 krb5_free_host_realm (context, lrealms); 148 return valid; 149} 150 151/* 152 * This function implements MIT's auth_to_local_names configuration for 153 * configuration compatibility. Specifically: 154 * 155 * [realms] 156 * <realm-name> = { 157 * auth_to_local_names = { 158 * <unparsed-principal-name> = <username> 159 * } 160 * } 161 * 162 * If multiple usernames are configured then the last one is taken. 163 * 164 * The configuration can only be expected to hold a relatively small 165 * number of mappings. For lots of mappings use a DB. 166 */ 167static krb5_error_code 168an2ln_local_names(krb5_context context, 169 krb5_const_principal aname, 170 size_t lnsize, 171 char *lname) 172{ 173 krb5_error_code ret; 174 char *unparsed; 175 char **values; 176 char *res; 177 size_t i; 178 179 if (!princ_realm_is_default(context, aname)) 180 return KRB5_PLUGIN_NO_HANDLE; 181 182 ret = krb5_unparse_name_flags(context, aname, 183 KRB5_PRINCIPAL_UNPARSE_NO_REALM, 184 &unparsed); 185 if (ret) 186 return ret; 187 188 ret = KRB5_PLUGIN_NO_HANDLE; 189 values = krb5_config_get_strings(context, NULL, "realms", aname->realm, 190 "auth_to_local_names", unparsed, NULL); 191 free(unparsed); 192 if (!values) 193 return ret; 194 /* Take the last value, just like MIT */ 195 for (res = NULL, i = 0; values[i]; i++) 196 res = values[i]; 197 if (res) { 198 ret = 0; 199 if (strlcpy(lname, res, lnsize) >= lnsize) 200 ret = KRB5_CONFIG_NOTENUFSPACE; 201 202 if (!*res || strcmp(res, ":") == 0) 203 ret = KRB5_NO_LOCALNAME; 204 } 205 206 krb5_config_free_strings(values); 207 return ret; 208} 209 210/* 211 * Heimdal's default aname2lname mapping. 212 */ 213static krb5_error_code 214an2ln_default(krb5_context context, 215 char *rule, 216 krb5_const_principal aname, 217 size_t lnsize, char *lname) 218{ 219 krb5_error_code ret; 220 const char *res; 221 int root_princs_ok; 222 223 if (strcmp(rule, "NONE") == 0) 224 return KRB5_NO_LOCALNAME; 225 226 if (strcmp(rule, "DEFAULT") == 0) 227 root_princs_ok = 0; 228 else if (strcmp(rule, "HEIMDAL_DEFAULT") == 0) 229 root_princs_ok = 1; 230 else 231 return KRB5_PLUGIN_NO_HANDLE; 232 233 if (!princ_realm_is_default(context, aname)) 234 return KRB5_PLUGIN_NO_HANDLE; 235 236 if (aname->name.name_string.len == 1) { 237 /* 238 * One component principal names in default realm -> the one 239 * component is the username. 240 */ 241 res = aname->name.name_string.val[0]; 242 } else if (root_princs_ok && aname->name.name_string.len == 2 && 243 strcmp (aname->name.name_string.val[1], "root") == 0) { 244 /* 245 * Two-component principal names in default realm where the 246 * first component is "root" -> root IFF the principal is in 247 * root's .k5login (or whatever krb5_kuserok() does). 248 */ 249 krb5_principal rootprinc; 250 krb5_boolean userok; 251 252 res = "root"; 253 254 ret = krb5_copy_principal(context, aname, &rootprinc); 255 if (ret) 256 return ret; 257 258 userok = _krb5_kuserok(context, rootprinc, res, FALSE); 259 krb5_free_principal(context, rootprinc); 260 if (!userok) 261 return KRB5_NO_LOCALNAME; 262 } else { 263 return KRB5_PLUGIN_NO_HANDLE; 264 } 265 266 if (strlcpy(lname, res, lnsize) >= lnsize) 267 return KRB5_CONFIG_NOTENUFSPACE; 268 269 return 0; 270} 271 272/** 273 * Map a principal name to a local username. 274 * 275 * Returns 0 on success, KRB5_NO_LOCALNAME if no mapping was found, or 276 * some Kerberos or system error. 277 * 278 * Inputs: 279 * 280 * @param context A krb5_context 281 * @param aname A principal name 282 * @param lnsize The size of the buffer into which the username will be written 283 * @param lname The buffer into which the username will be written 284 * 285 * @ingroup krb5_support 286 */ 287KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 288krb5_aname_to_localname(krb5_context context, 289 krb5_const_principal aname, 290 size_t lnsize, 291 char *lname) 292{ 293 static heim_base_once_t reg_def_plugins = HEIM_BASE_ONCE_INIT; 294 krb5_error_code ret; 295 krb5_realm realm; 296 size_t i; 297 char **rules = NULL; 298 char *rule; 299 300 if (lnsize) 301 lname[0] = '\0'; 302 303 heim_base_once_f(®_def_plugins, context, reg_def_plugins_once); 304 305 /* Try MIT's auth_to_local_names config first */ 306 ret = an2ln_local_names(context, aname, lnsize, lname); 307 if (ret != KRB5_PLUGIN_NO_HANDLE) 308 return ret; 309 310 ret = krb5_get_default_realm(context, &realm); 311 if (ret) 312 return ret; 313 314 rules = krb5_config_get_strings(context, NULL, "realms", realm, 315 "auth_to_local", NULL); 316 krb5_xfree(realm); 317 if (!rules) { 318 /* Heimdal's default rule */ 319 ret = an2ln_default(context, "HEIMDAL_DEFAULT", aname, lnsize, lname); 320 if (ret == KRB5_PLUGIN_NO_HANDLE) 321 return KRB5_NO_LOCALNAME; 322 return ret; 323 } 324 325 /* 326 * MIT rules. 327 * 328 * Note that RULEs and DBs only have white-list functionality, 329 * thus RULEs and DBs that we don't understand we simply ignore. 330 * 331 * This means that plugins that implement black-lists are 332 * dangerous: if a black-list plugin isn't found, the black-list 333 * won't be enforced. But black-lists are dangerous anyways. 334 */ 335 for (ret = KRB5_PLUGIN_NO_HANDLE, i = 0; rules[i]; i++) { 336 rule = rules[i]; 337 338 /* Try NONE, DEFAULT, and HEIMDAL_DEFAULT rules */ 339 ret = an2ln_default(context, rule, aname, lnsize, lname); 340 if (ret == KRB5_PLUGIN_NO_HANDLE) 341 /* Try DB, RULE, ... plugins */ 342 ret = an2ln_plugin(context, rule, aname, lnsize, lname); 343 344 if (ret == 0 && lnsize && !lname[0]) 345 continue; /* Success but no lname?! lies! */ 346 else if (ret != KRB5_PLUGIN_NO_HANDLE) 347 break; 348 } 349 350 if (ret == KRB5_PLUGIN_NO_HANDLE) { 351 if (lnsize) 352 lname[0] = '\0'; 353 ret = KRB5_NO_LOCALNAME; 354 } 355 356 krb5_config_free_strings(rules); 357 return ret; 358} 359 360static krb5_error_code KRB5_LIB_CALL 361an2ln_def_plug_init(krb5_context context, void **ctx) 362{ 363 *ctx = NULL; 364 return 0; 365} 366 367static void KRB5_LIB_CALL 368an2ln_def_plug_fini(void *ctx) 369{ 370} 371 372static heim_base_once_t sorted_text_db_init_once = HEIM_BASE_ONCE_INIT; 373 374static void 375sorted_text_db_init_f(void *arg) 376{ 377 (void) heim_db_register("sorted-text", NULL, &heim_sorted_text_file_dbtype); 378} 379 380static krb5_error_code KRB5_LIB_CALL 381an2ln_def_plug_an2ln(void *plug_ctx, krb5_context context, 382 const char *rule, 383 krb5_const_principal aname, 384 set_result_f set_res_f, void *set_res_ctx) 385{ 386 krb5_error_code ret; 387 const char *an2ln_db_fname; 388 heim_db_t dbh = NULL; 389 heim_dict_t db_options; 390 heim_data_t k, v; 391 heim_error_t error; 392 char *unparsed = NULL; 393 char *value = NULL; 394 395 _krb5_load_db_plugins(context); 396 heim_base_once_f(&sorted_text_db_init_once, NULL, sorted_text_db_init_f); 397 398 if (strncmp(rule, "DB:", strlen("DB:")) != 0) 399 return KRB5_PLUGIN_NO_HANDLE; 400 401 an2ln_db_fname = &rule[strlen("DB:")]; 402 if (!*an2ln_db_fname) 403 return KRB5_PLUGIN_NO_HANDLE; 404 405 ret = krb5_unparse_name(context, aname, &unparsed); 406 if (ret) 407 return ret; 408 409 db_options = heim_dict_create(11); 410 if (db_options != NULL) 411 heim_dict_set_value(db_options, HSTR("read-only"), 412 heim_number_create(1)); 413 dbh = heim_db_create(NULL, an2ln_db_fname, db_options, &error); 414 if (dbh == NULL) { 415 krb5_set_error_message(context, heim_error_get_code(error), 416 N_("Couldn't open aname2lname-text-db", "")); 417 ret = KRB5_PLUGIN_NO_HANDLE; 418 goto cleanup; 419 } 420 421 /* Binary search; file should be sorted (in C locale) */ 422 k = heim_data_ref_create(unparsed, strlen(unparsed), NULL); 423 if (k == NULL) { 424 ret = krb5_enomem(context); 425 goto cleanup; 426 } 427 v = heim_db_copy_value(dbh, NULL, k, &error); 428 heim_release(k); 429 if (v == NULL && error != NULL) { 430 krb5_set_error_message(context, heim_error_get_code(error), 431 N_("Lookup in aname2lname-text-db failed", "")); 432 ret = heim_error_get_code(error); 433 goto cleanup; 434 } else if (v == NULL) { 435 ret = KRB5_PLUGIN_NO_HANDLE; 436 goto cleanup; 437 } else { 438 /* found */ 439 if (heim_data_get_length(v) == 0) { 440 krb5_set_error_message(context, ret, 441 N_("Principal mapped to empty username", "")); 442 ret = KRB5_NO_LOCALNAME; 443 goto cleanup; 444 } 445 value = strndup(heim_data_get_ptr(v), heim_data_get_length(v)); 446 heim_release(v); 447 if (value == NULL) { 448 ret = krb5_enomem(context); 449 goto cleanup; 450 } 451 ret = set_res_f(set_res_ctx, value); 452 } 453 454cleanup: 455 heim_release(dbh); 456 free(unparsed); 457 free(value); 458 return ret; 459} 460 461