/* Copyright (C) 2013 Simo Sorce This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "gss_ntlmssp.h" static uint32_t string_split(uint32_t *minor_status, char sep, const char *str, size_t len, char **s1, char **s2) { uint32_t retmaj; uint32_t retmin; char *r1 = NULL; char *r2 = NULL; const char *p; size_t l; p = memchr(str, sep, len); if (!p) return GSSERRS(0, GSS_S_UNAVAILABLE); if (s1) { l = p - str; r1 = strndup(str, l); if (!r1) { set_GSSERR(ENOMEM); goto done; } } if (s2) { p++; l = len - (p - str); r2 = strndup(p, l); if (!r2) { set_GSSERR(ENOMEM); goto done; } } set_GSSERRS(0, GSS_S_COMPLETE); done: if (retmaj) { free(r1); free(r2); } else { if (s1) *s1 = r1; if (s2) *s2 = r2; } return GSSERR(); } #define MAX_NAME_LEN 1024 static uint32_t get_enterprise_name(uint32_t *minor_status, const char *str, size_t len, char **username) { uint32_t retmaj; uint32_t retmin; char *buf; char *e; if (len > MAX_NAME_LEN) { return GSSERRS(ERR_NAMETOOLONG, GSS_S_BAD_NAME); } buf = alloca(len + 1); memcpy(buf, str, len); buf[len] = '\0'; e = strstr(buf, "\\@"); if (e) { /* remove escape */ memmove(e, e + 1, len - (e - buf)); } else { /* check if domain part contains dot */ e = strchr(buf, '@'); if (e) { e = strchr(e, '.'); } } if (!e) return GSSERRS(0, GSS_S_UNAVAILABLE); *username = strdup(buf); if (NULL == *username) { set_GSSERR(ENOMEM); goto done; } set_GSSERRS(0, GSS_S_COMPLETE); done: return GSSERR(); } static uint32_t uid_to_name(uint32_t *minor_status, uid_t uid, char **name) { uint32_t retmaj; uint32_t retmin; struct passwd *pw; pw = getpwuid(uid); if (pw) { return GSSERRS(ERR_NOUSRFOUND, GSS_S_FAILURE); } *name = strdup(pw->pw_name); if (!*name) { set_GSSERR(ENOMEM); goto done; } set_GSSERRS(0, GSS_S_COMPLETE); done: return GSSERR(); } uint32_t gssntlm_import_name_by_mech(uint32_t *minor_status, gss_const_OID mech_type, gss_buffer_t input_name_buffer, gss_OID input_name_type, gss_name_t *output_name) { char hostname[HOST_NAME_MAX + 1] = { 0 }; char struid[12] = { 0 }; uid_t uid; struct gssntlm_name *name = NULL; uint32_t retmaj; uint32_t retmin; /* TODO: check mech_type == gssntlm_oid */ if (mech_type == GSS_C_NO_OID) { return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); } name = calloc(1, sizeof(struct gssntlm_name)); if (!name) { set_GSSERR(ENOMEM); goto done; } /* treat null OID like NT_USER_NAME */ if (input_name_type == GSS_C_NULL_OID) { input_name_type = GSS_C_NT_USER_NAME; } if (gss_oid_equal(input_name_type, GSS_C_NT_HOSTBASED_SERVICE) || gss_oid_equal(input_name_type, GSS_C_NT_HOSTBASED_SERVICE_X)) { name->type = GSSNTLM_NAME_SERVER; retmaj = string_split(&retmin, '@', input_name_buffer->value, input_name_buffer->length, NULL, &name->data.server.name); if ((retmaj == GSS_S_COMPLETE) || (retmaj != GSS_S_UNAVAILABLE)) { goto done; } /* no seprator, assume only service is provided and try to source * the local host name */ retmin = gethostname(hostname, HOST_NAME_MAX); if (retmin) { set_GSSERR(retmin); goto done; } hostname[HOST_NAME_MAX] = '\0'; name->data.server.name = strdup(hostname); if (!name->data.server.name) { set_GSSERR(ENOMEM); } set_GSSERRS(0, GSS_S_COMPLETE); } else if (gss_oid_equal(input_name_type, GSS_C_NT_USER_NAME)) { name->type = GSSNTLM_NAME_USER; name->data.user.domain = NULL; /* Check if enterprise name first */ retmaj = get_enterprise_name(&retmin, input_name_buffer->value, input_name_buffer->length, &name->data.user.name); if ((retmaj == GSS_S_COMPLETE) || (retmaj != GSS_S_UNAVAILABLE)) { goto done; } /* Check if in classic DOMAIN\User windows format */ retmaj = string_split(&retmin, '\\', input_name_buffer->value, input_name_buffer->length, &name->data.user.domain, &name->data.user.name); if ((retmaj == GSS_S_COMPLETE) || (retmaj != GSS_S_UNAVAILABLE)) { goto done; } /* else accept a user@domain format too */ retmaj = string_split(&retmin, '@', input_name_buffer->value, input_name_buffer->length, &name->data.user.name, &name->data.user.domain); if ((retmaj == GSS_S_COMPLETE) || (retmaj != GSS_S_UNAVAILABLE)) { goto done; } /* finally, take string as simple user name */ name->data.user.name = strndup(input_name_buffer->value, input_name_buffer->length); if (!name->data.user.name) { set_GSSERR(ENOMEM); } retmaj = GSS_S_COMPLETE; } else if (gss_oid_equal(input_name_type, GSS_C_NT_MACHINE_UID_NAME)) { name->type = GSSNTLM_NAME_USER; name->data.user.domain = NULL; uid = *(uid_t *)input_name_buffer->value; retmaj = uid_to_name(&retmin, uid, &name->data.user.name); } else if (gss_oid_equal(input_name_type, GSS_C_NT_STRING_UID_NAME)) { name->type = GSSNTLM_NAME_USER; name->data.user.domain = NULL; if (input_name_buffer->length > 12) { set_GSSERR(ERR_BADARG); goto done; } memcpy(struid, input_name_buffer->value, input_name_buffer->length); struid[11] = '\0'; errno = 0; uid = strtol(struid, NULL, 10); if (errno) { set_GSSERR(ERR_BADARG); goto done; } retmaj = uid_to_name(&retmin, uid, &name->data.user.name); } else if (gss_oid_equal(input_name_type, GSS_C_NT_ANONYMOUS)) { name->type = GSSNTLM_NAME_ANON; set_GSSERRS(0, GSS_S_COMPLETE); } else if (gss_oid_equal(input_name_type, GSS_C_NT_EXPORT_NAME)) { /* TODO */ set_GSSERRS(ERR_NOTSUPPORTED, GSS_S_BAD_NAMETYPE); } else { set_GSSERRS(ERR_BADARG, GSS_S_BAD_NAMETYPE); } done: if (retmaj != GSS_S_COMPLETE) { uint32_t tmpmin; gssntlm_release_name(&tmpmin, (gss_name_t *)&name); } else { *output_name = (gss_name_t)name; } return GSSERR(); } uint32_t gssntlm_import_name(uint32_t *minor_status, gss_buffer_t input_name_buffer, gss_OID input_name_type, gss_name_t *output_name) { return gssntlm_import_name_by_mech(minor_status, discard_const(&gssntlm_oid), input_name_buffer, input_name_type, output_name); } int gssntlm_copy_name(struct gssntlm_name *src, struct gssntlm_name *dst) { char *dom = NULL, *usr = NULL, *srv = NULL; int ret; dst->type = src->type; switch (src->type) { case GSSNTLM_NAME_NULL: case GSSNTLM_NAME_ANON: break; case GSSNTLM_NAME_USER: if (src->data.user.domain) { dom = strdup(src->data.user.domain); if (!dom) { ret = ENOMEM; goto done; } } if (src->data.user.name) { usr = strdup(src->data.user.name); if (!usr) { ret = ENOMEM; goto done; } } dst->data.user.domain = dom; dst->data.user.name = usr; break; case GSSNTLM_NAME_SERVER: if (src->data.server.name) { srv = strdup(src->data.server.name); if (!srv) { ret = ENOMEM; goto done; } } dst->data.server.name = srv; break; } ret = 0; done: if (ret) { safefree(dom); safefree(usr); safefree(srv); } return ret; } uint32_t gssntlm_duplicate_name(uint32_t *minor_status, const gss_name_t input_name, gss_name_t *dest_name) { struct gssntlm_name *in; struct gssntlm_name *out; uint32_t retmin; uint32_t retmaj; if (input_name == GSS_C_NO_NAME || dest_name == NULL) { return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); } in = (struct gssntlm_name *)input_name; if (in->type == GSSNTLM_NAME_NULL) { *dest_name = GSS_C_NO_NAME; return GSSERRS(0, GSS_S_COMPLETE); } out = calloc(1, sizeof(struct gssntlm_name)); if (!out) { set_GSSERR(ENOMEM); goto done; } retmin = gssntlm_copy_name(in, out); if (retmin) { set_GSSERR(retmin); goto done; } set_GSSERRS(0, GSS_S_COMPLETE); done: if (retmaj) { safefree(out); } *dest_name = (gss_name_t)out; return GSSERR(); } void gssntlm_int_release_name(struct gssntlm_name *name) { if (!name) return; switch (name->type) { case GSSNTLM_NAME_NULL: return; case GSSNTLM_NAME_ANON: break; case GSSNTLM_NAME_USER: safefree(name->data.user.domain); safefree(name->data.user.name); break; case GSSNTLM_NAME_SERVER: safefree(name->data.server.name); break; } name->type = GSSNTLM_NAME_NULL; } uint32_t gssntlm_release_name(uint32_t *minor_status, gss_name_t *input_name) { uint32_t retmaj; uint32_t retmin; if (!input_name) { return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); } gssntlm_int_release_name((struct gssntlm_name *)*input_name); safefree(*input_name); return GSSERRS(0, GSS_S_COMPLETE); } uint32_t gssntlm_display_name(uint32_t *minor_status, gss_name_t input_name, gss_buffer_t output_name_buffer, gss_OID *output_name_type) { struct gssntlm_name *in; gss_buffer_t out; uint32_t retmaj; uint32_t retmin; int ret; if (input_name == GSS_C_NO_NAME || output_name_buffer == NULL) { return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); } in = (struct gssntlm_name *)input_name; out = output_name_buffer; switch (in->type) { case GSSNTLM_NAME_NULL: return GSSERRS(ERR_BADARG, GSS_S_BAD_NAME); case GSSNTLM_NAME_ANON: out->value = strdup("NT AUTHORITY\\ANONYMOUS LOGON"); if (!out->value) { set_GSSERR(ENOMEM); goto done; } out->length = strlen(out->value) + 1; if (output_name_type) { *output_name_type = GSS_C_NT_ANONYMOUS; } break; case GSSNTLM_NAME_USER: if (in->data.user.domain) { ret = asprintf((char **)&out->value, "%s\\%s", in->data.user.domain, in->data.user.name); if (ret == -1) { out->value = NULL; } } else { out->value = strdup(in->data.user.name); } if (!out->value) { set_GSSERR(ENOMEM); goto done; } out->length = strlen(out->value) + 1; if (output_name_type) { *output_name_type = GSS_C_NT_USER_NAME; } break; case GSSNTLM_NAME_SERVER: out->value = strdup(in->data.server.name); if (!out->value) { set_GSSERR(ENOMEM); goto done; } out->length = strlen(out->value) + 1; if (output_name_type) { *output_name_type = GSS_C_NT_HOSTBASED_SERVICE; } break; } set_GSSERRS(0, GSS_S_COMPLETE); done: return GSSERR(); } #define PWBUFLEN 1024 uint32_t gssntlm_localname(uint32_t *minor_status, const gss_name_t name, gss_const_OID mech_type, gss_buffer_t localname) { struct gssntlm_name *in; char *uname = NULL; char pwbuf[PWBUFLEN]; struct passwd pw, *res; uint32_t retmaj; uint32_t retmin; int ret; in = (struct gssntlm_name *)name; if (in->type != GSSNTLM_NAME_USER) { set_GSSERRS(ERR_BADARG, GSS_S_BAD_NAME); goto done; } /* TODO: hook up with winbindd/sssd for name resolution ? */ if (in->data.user.domain) { ret = asprintf(&uname, "%s\\%s", in->data.user.domain, in->data.user.name); if (ret == -1) { set_GSSERR(ENOMEM); goto done; } ret = getpwnam_r(uname, &pw, pwbuf, PWBUFLEN, &res); if (ret) { set_GSSERR(ret); goto done; } safefree(uname); if (res) { uname = strdup(res->pw_name); } } if (uname == NULL) { ret = getpwnam_r(in->data.user.name, &pw, pwbuf, PWBUFLEN, &res); if (ret != 0 || res == NULL) { set_GSSERR(ret); goto done; } uname = strdup(res->pw_name); } if (!uname) { set_GSSERR(ENOMEM); goto done; } set_GSSERRS(0, GSS_S_COMPLETE); done: if (retmaj) { safefree(uname); } else { localname->value = uname; localname->length = strlen(uname) + 1; } return GSSERR(); } uint32_t netbios_get_names(char *computer_name, char **netbios_host, char **netbios_domain) { char *nb_computer_name = NULL; char *nb_domain_name = NULL; char *env_name; uint32_t ret; env_name = getenv("NETBIOS_COMPUTER_NAME"); if (env_name) { nb_computer_name = strdup(env_name); if (!nb_computer_name) { ret = ENOMEM; goto done; } } env_name = getenv("NETBIOS_DOMAIN_NAME"); if (env_name) { nb_domain_name = strdup(env_name); if (!nb_domain_name) { ret = ENOMEM; goto done; } } if (!nb_computer_name || !nb_domain_name) { /* fetch only mising ones */ ret = external_netbios_get_names( nb_computer_name ? NULL : &nb_computer_name, nb_domain_name ? NULL : &nb_domain_name); if ((ret != 0) && (ret != ENOENT) && (ret != ERR_NOTAVAIL)) { goto done; } } if (!nb_computer_name) { char *p; p = strchr(computer_name, '.'); if (p) { nb_computer_name = strndup(computer_name, p - computer_name); } else { nb_computer_name = strdup(computer_name); } for (p = nb_computer_name; p && *p; p++) { /* Can only be ASCII, so toupper is safe */ *p = toupper(*p); } if (!nb_computer_name) { ret = ENOMEM; goto done; } } if (!nb_domain_name) { nb_domain_name = strdup(DEF_NB_DOMAIN); if (!nb_domain_name) { ret = ENOMEM; goto done; } } ret = 0; done: if (ret) { safefree(nb_computer_name); safefree(nb_domain_name); } *netbios_domain = nb_domain_name; *netbios_host = nb_computer_name; return ret; } uint32_t gssntlm_inquire_name(uint32_t *minor_status, gss_name_t name, int *name_is_MN, gss_OID *MN_mech, gss_buffer_set_t *attrs) { return GSS_S_UNAVAILABLE; }