# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # Copyright 2014 Nexenta Systems, Inc. All rights reserved. # # Makefile for native identity mapping service # SUBDIRS= idmap idmapd nltest test-getdc all : TARGET = all install : TARGET = install clean : TARGET = clean clobber : TARGET = clobber lint : TARGET = lint _msg : TARGET = _msg .KEEP_STATE: all install lint clean clobber _msg: $(SUBDIRS) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. PROG = idmap CLIENTOBJS = idmap.o idmap_engine.o namemaps.o # idmap_clnt.o POFILES = $(CLIENTOBJS:.o=.po) OBJS = $(CLIENTOBJS) IDMAP_PROT_DIR = $(SRC)/head/rpcsvc include ../../Makefile.cmd CERRWARN += -Wno-parentheses CERRWARN += -Wno-switch CERRWARN += -Wno-char-subscripts CERRWARN += -Wno-unused-function CERRWARN += $(CNOWARN_UNINIT) CERRWARN += -Wno-address # not linted SMATCH=off POFILE = $(PROG)_all.po LDLIBS += -lidmap -ladutils -lsldap -lldap FILEMODE = 0555 INCS += -I. \ -I../../../lib/libidmap/common \ -I../../../lib/libadutils/common \ -I../../../lib/libsldap/common \ -I$(IDMAP_PROT_DIR) CFLAGS += $(CCVERBOSE) $(OBJS) : CPPFLAGS += $(INCS) -D_REENTRANT $(POFILE) : CPPFLAGS += $(INCS) .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(CCGDEBUG) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) $(POFILE): $(POFILES) $(RM) $@ cat $(POFILES) > $@ install: all $(ROOTUSRSBINPROG) clean: $(RM) $(OBJS) include ../../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include "idmap_engine.h" #include "idmap_priv.h" #include "namemaps.h" #include "libadutils.h" /* Initialization values for pids/rids: */ #define UNDEFINED_UID (uid_t)-1 #define UNDEFINED_GID (gid_t)-1 #define UNDEFINED_RID (idmap_rid_t)-1; #define CHECK_NULL(s) (s != NULL ? s : "null") /* * used in do_show for the type of argument, which can be winname, * unixname, uid, gid, sid or not given at all: */ #define TYPE_SID 0x010 /* sid */ #define TYPE_USID 0x011 /* usid */ #define TYPE_GSID 0x012 /* gsid */ #define TYPE_WN 0x110 /* winname */ #define TYPE_WU 0x111 /* winuser */ #define TYPE_WG 0x112 /* wingroup */ #define TYPE_UID 0x001 /* uid */ #define TYPE_GID 0x002 /* gid */ #define TYPE_PID 0x000 /* pid */ #define TYPE_UN 0x100 /* unixname */ #define TYPE_UU 0x101 /* unixuser */ #define TYPE_UG 0x102 /* unixgroup */ #define IS_WIN 0x010 /* mask for the windows types */ #define IS_NAME 0x100 /* mask for string name types */ #define IS_USER 0x001 /* mask for user types */ #define IS_GROUP 0x002 /* mask for group types */ #define TYPE_INVALID 0x1000 /* Invalid input */ #define TYPE_AUTO 0xaaa /* Autodetection required */ /* Identity type strings */ #define ID_WINNAME "winname" #define ID_UNIXUSER "unixuser" #define ID_UNIXGROUP "unixgroup" #define ID_WINUSER "winuser" #define ID_WINGROUP "wingroup" #define ID_USID "usid" #define ID_GSID "gsid" #define ID_SID "sid" #define ID_UID "uid" #define ID_GID "gid" #define ID_UNKNOWN "unknown" #define INHIBITED(str) (str == NULL || *str == 0 || strcmp(str, "\"\"") == 0) typedef struct { char *identity; int code; } id_code_t; id_code_t identity2code[] = { {ID_WINNAME, TYPE_WN}, {ID_UNIXUSER, TYPE_UU}, {ID_UNIXGROUP, TYPE_UG}, {ID_WINUSER, TYPE_WU}, {ID_WINGROUP, TYPE_WG}, {ID_USID, TYPE_USID}, {ID_GSID, TYPE_GSID}, {ID_SID, TYPE_SID}, {ID_UID, TYPE_UID}, {ID_GID, TYPE_GID} }; /* Flags */ #define f_FLAG 'f' #define t_FLAG 't' #define d_FLAG 'd' #define D_FLAG 'D' #define F_FLAG 'F' #define a_FLAG 'a' #define n_FLAG 'n' #define c_FLAG 'c' #define v_FLAG 'v' #define V_FLAG 'V' #define j_FLAG 'j' /* used in the function do_import */ #define MAX_INPUT_LINE_SZ 2047 typedef struct { int is_user; int is_wuser; int direction; boolean_t is_nt4; char *unixname; char *winname; char *windomain; char *sidprefix; idmap_rid_t rid; uid_t pid; } name_mapping_t; /* * Formats of the output: * * Idmap reads/prints mappings in several formats: ordinary mappings, * name mappings in Samba username map format (smbusers), Netapp * usermap.cfg. * * DEFAULT_FORMAT are in fact the idmap subcommands suitable for * piping to idmap standart input. For example * add -d winuser:bob@foo.com unixuser:fred * add -d winuser:bob2bar.com unixuser:fred * * SMBUSERS is the format of Samba username map (smbusers). For full * documentation, search for "username map" in smb.conf manpage. * The format is for example * fred = bob@foo.com bob2@bar.com * * USERMAP_CFG is the format of Netapp usermap.cfg file. Search * http://www.netapp.com/ for more documentation. IP qualifiers are not * supported. * The format is for example * bob@foo.com => fred * "Bob With Spaces"@bar.com => fred #comment * * The previous formats were for name rules. MAPPING_NAME and * MAPPING_ID are for the actual mappings, as seen in show/dump * commands. MAPPING_NAME prefers the string names of the user over * their numerical identificators. MAPPING_ID prints just the * identificators. * Example of the MAPPING_NAME: * winname:bob@foo.com -> unixname:fred * * Example of the MAPPING_ID: * sid:S-1-2-3-4 -> uid:5678 */ typedef enum { UNDEFINED_FORMAT = -1, DEFAULT_FORMAT = 0, MAPPING_ID, MAPPING_NAME, USERMAP_CFG, SMBUSERS } format_t; typedef struct { format_t format; FILE *file; name_mapping_t *last; } print_handle_t; /* * idmap_api batch related variables: * * idmap can operate in two modes. It the batch mode, the idmap_api * batch is committed at the end of a batch of several * commands. At the end of input file, typically. This mode is used * for processing input from a file. * In the non-batch mode, each command is committed immediately. This * mode is used for tty input. */ /* Are we in the batch mode? */ static int batch_mode = 0; /* Self describing stricture for positions */ struct pos_sds { int size; int last; cmd_pos_t *pos[1]; }; static struct pos_sds *positions; /* Handles for idmap_api batch */ static idmap_udt_handle_t *udt = NULL; typedef struct { char *user; char *passwd; char *auth; char *windomain; int direction; idmap_nm_handle_t *handle; } namemaps_t; static namemaps_t namemaps = {NULL, NULL, NULL, NULL, 0, NULL}; /* Do we need to commit the udt batch at the end? */ static int udt_used; /* Command handlers */ static int do_show_mapping(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_dump(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_import(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_list_name_mappings(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_add_name_mapping(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_remove_name_mapping(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_flush(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_exit(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_export(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_help(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_set_namemap(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_unset_namemap(flag_t *f, int argc, char **argv, cmd_pos_t *pos); static int do_get_namemap(flag_t *f, int argc, char **argv, cmd_pos_t *pos); /* Command names and their handlers to be passed to idmap_engine */ static cmd_ops_t commands[] = { { "show", "c(create)v(verbose)V(trace)", do_show_mapping }, { "dump", "n(names)v(verbose)", do_dump }, { "import", "F(flush)f:(file)", do_import }, { "export", "f:(file)", do_export }, { "list", "", do_list_name_mappings }, { "add", "d(directional)", do_add_name_mapping }, { "remove", "a(all)t(to)f(from)d(directional)", do_remove_name_mapping }, { "flush", "a(all)", do_flush }, { "exit", "", do_exit }, { "help", "", do_help }, { "set-namemap", "a:(authentication)D:(bindDN)j:(passwd-file)", do_set_namemap }, { "get-namemap", "", do_get_namemap }, { "unset-namemap", "a:(authentication)D:(bindDN)j:(passwd-file):", do_unset_namemap } }; /* Print error message, possibly with a position */ /* printflike */ static void print_error(cmd_pos_t *pos, const char *format, ...) { size_t length; va_list ap; va_start(ap, format); if (pos != NULL) { length = strlen(pos->line); /* Skip newlines etc at the end: */ while (length > 0 && isspace(pos->line[length - 1])) length--; (void) fprintf(stderr, gettext("Error at line %d: %.*s\n"), pos->linenum, length, pos->line); } (void) vfprintf(stderr, format, ap); va_end(ap); } /* Inits positions sds. 0 means everything went OK, -1 for errors */ static int init_positions() { int init_size = 32; /* Initial size of the positions array */ positions = (struct pos_sds *) malloc(sizeof (struct pos_sds) + (init_size - 1) * sizeof (cmd_pos_t *)); if (positions == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } positions->size = init_size; positions->last = 0; return (0); } /* Free the positions array */ static void fini_positions() { int i; for (i = 0; i < positions->last; i++) { if (positions->pos[i] == NULL) continue; free(positions->pos[i]->line); free(positions->pos[i]); } free(positions); positions = NULL; } /* * Add another position to the positions array. 0 means everything * went OK, -1 for errors */ static int positions_add(cmd_pos_t *pos) { if (positions->last >= positions->size) { positions->size *= 2; positions = (struct pos_sds *)realloc(positions, sizeof (struct pos_sds) + (positions->size - 1) * sizeof (cmd_pos_t *)); if (positions == NULL) goto nomemory; } if (pos == NULL) positions->pos[positions->last] = NULL; else { positions->pos[positions->last] = (cmd_pos_t *)calloc(1, sizeof (cmd_pos_t)); if (positions->pos[positions->last] == NULL) goto nomemory; *positions->pos[positions->last] = *pos; positions->pos[positions->last]->line = strdup(pos->line); if (positions->pos[positions->last]->line == NULL) goto nomemory; } positions->last++; return (0); nomemory: print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } /* * Compare two strings just like strcmp, but stop before the end of * the s2 */ static int strcmp_no0(const char *s1, const char *s2) { return (strncmp(s1, s2, strlen(s2))); } /* Print help message */ static void help() { (void) fprintf(stderr, "idmap\n" "idmap -f command-file\n" "idmap add [-d] name1 name2\n" "idmap dump [-n] [-v]\n" "idmap export [-f file] format\n" "idmap flush [-a]\n" "idmap get-namemap name\n" "idmap help\n" "idmap import [-F] [-f file] format\n" "idmap list\n" "idmap remove -a\n" "idmap remove [-f|-t] name\n" "idmap remove [-d] name1 name2\n" "idmap set-namemap [-a authenticationMethod] [-D bindDN]\n" " [-j passwdfile] name1 name2\n" "idmap show [-c] [-v] identity [targettype]\n" "idmap unset-namemap [-a authenticationMethod] [-D bindDN]\n" " [-j passwdfile] name [targettype]\n"); } /* The handler for the "help" command. */ static int /* LINTED E_FUNC_ARG_UNUSED */ do_help(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { help(); return (0); } /* Initialization of the commands which perform write operations */ static int init_udt_batch() { idmap_stat stat; stat = idmap_udt_create(&udt); if (stat != IDMAP_SUCCESS) { print_error(NULL, gettext("Error initiating transaction (%s)"), idmap_stat2string(stat)); return (-1); } if (init_positions() < 0) return (-1); return (0); } /* Finalization of the write commands */ static int init_udt_command() { udt_used = 1; if (batch_mode) return (0); return (init_udt_batch()); } /* If everythings is OK, send the udt batch to idmapd */ static int fini_udt_command(int ok, cmd_pos_t *pos) { int rc = 0; int64_t failpos; idmap_stat stat, stat1; cmd_pos_t *reported_pos; if (batch_mode) return (0); if (udt == NULL) { print_error(pos, gettext("Internal error: uninitiated batch.\n")); return (-1); } if (ok && udt_used) { stat = idmap_udt_commit(udt); if (stat == IDMAP_SUCCESS) goto out; rc = -1; stat1 = idmap_udt_get_error_index(udt, &failpos); if (stat1 != IDMAP_SUCCESS) { print_error(NULL, gettext("Error diagnosing transaction (%s)\n"), idmap_stat2string(stat1)); goto out; } if (failpos < 0) reported_pos = pos; else reported_pos = positions->pos[failpos]; print_error(reported_pos, gettext("Error commiting transaction (%s)\n"), idmap_stat2string(stat)); } out: idmap_udt_destroy(udt); udt = NULL; udt_used = 0; fini_positions(); return (rc); } /* * Compare two possibly NULL strings */ static int strcasecmp_null(char *a, char *b) { if (a == NULL && b == NULL) return (0); if (a == NULL) return (-1); if (b == NULL) return (1); return (strcasecmp(a, b)); } /* * Compare two possibly NULL strings */ static int strcmp_null(char *a, char *b) { if (a == NULL && b == NULL) return (0); if (a == NULL) return (-1); if (b == NULL) return (1); return (strcmp(a, b)); } static void free_null(char **ptr) { if (*ptr != NULL) { free(*ptr); *ptr = NULL; } } static void namemaps_free() { free_null(&namemaps.user); if (namemaps.passwd != NULL) (void) memset(namemaps.passwd, 0, strlen(namemaps.passwd)); free_null(&namemaps.passwd); free_null(&namemaps.auth); free_null(&namemaps.windomain); namemaps.direction = IDMAP_DIRECTION_UNDEF; if (namemaps.handle != NULL) { idmap_fini_namemaps(namemaps.handle); namemaps.handle = NULL; } } /* Initialization of the commands which perform write operations */ static int init_nm_command(char *user, char *passwd, char *auth, char *windomain, int direction, cmd_pos_t *pos) { idmap_stat stat; if (namemaps.handle != NULL && ( strcmp_null(user, namemaps.user) != 0 || strcmp_null(passwd, namemaps.passwd) != 0 || strcasecmp_null(auth, namemaps.auth) != 0 || strcasecmp_null(windomain, namemaps.windomain) != 0 || direction != namemaps.direction)) { namemaps_free(); } if (namemaps.handle == NULL) { stat = idmap_init_namemaps(&namemaps.handle, user, passwd, auth, windomain, direction); if (stat != IDMAP_SUCCESS) { print_error(pos, gettext("Error: could not perform directory-based " "name mapping operation (%s)"), idmap_stat2string(stat)); namemaps_free(); return (-1); } if (user != NULL && (namemaps.user = strdup(user)) == NULL || passwd != NULL && (namemaps.passwd = strdup(passwd)) == NULL || auth != NULL && (namemaps.auth = strdup(auth)) == NULL || windomain != NULL && (namemaps.windomain = strdup(windomain)) == NULL) { print_error(pos, "%s.\n", strerror(ENOMEM)); namemaps_free(); return (-1); } namemaps.direction = direction; } return (0); } /* Cleanup after the xxx-namemaps commands */ static void fini_nm_command() { if (batch_mode) return; namemaps_free(); } /* Convert numeric expression of the direction to it's string form */ static char * direction2string(int direction) { switch (direction) { case IDMAP_DIRECTION_BI: return ("=="); case IDMAP_DIRECTION_W2U: return ("=>"); case IDMAP_DIRECTION_U2W: return ("<="); default: /* This can never happen: */ print_error(NULL, gettext("Internal error: invalid direction.\n")); return (""); } /* never reached */ } /* * Returns 1 if c is a shell-meta-character requiring quoting, 0 * otherwise. * * We don't quote '*' and ':' because they cannot do any harm * a) they have no meaning to idmap_engine b) even ifsomebody copy & * paste idmap output to a shell commandline, there is the identity * type string in front of them. On the other hand, '*' and ':' are * everywhere. */ static int is_shell_special(char c) { if (isspace(c)) return (1); if (strchr("&^{}#;'\"\\`!$()[]><|~", c) != NULL) return (1); return (0); } /* * Returns 1 if c is a shell-meta-character requiring quoting even * inside double quotes, 0 otherwise. It means \, " and $ . * * This set of characters is a subset of those in is_shell_special(). */ static int is_dq_special(char c) { if (strchr("\\\"$", c) != NULL) return (1); return (0); } /* * Quote any shell meta-characters in the given string. If 'quote' is * true then use double-quotes to quote the whole string, else use * back-slash to quote each individual meta-character. * * The resulting string is placed in *res. Callers must free *res if the * return value isn't 0 (even if the given string had no meta-chars). * If there are any errors this returns -1, else 0. */ static int shell_app(char **res, char *string, int quote) { int i, j; uint_t noss = 0; /* Number Of Shell Special chars in the input */ uint_t noqb = 0; /* Number Of Quotes and Backslahes in the input */ char *out; size_t len_orig = strlen(string); size_t len; if (INHIBITED(string)) { out = strdup("\"\""); if (out == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } *res = out; return (0); } /* First, let us count how many characters we need to quote: */ for (i = 0; i < len_orig; i++) { if (is_shell_special(string[i])) { noss++; if (is_dq_special(string[i])) noqb++; } } /* Do we need to quote at all? */ if (noss == 0) { out = strdup(string); if (out == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } *res = out; return (0); } /* What is the length of the result? */ if (quote) len = strlen(string) + 2 + noqb + 1; /* 2 for quotation marks */ else len = strlen(string) + noss + 1; out = (char *)malloc(len); if (out == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } j = 0; if (quote) out[j++] = '"'; for (i = 0; i < len_orig; i++) { /* Quote the dangerous chars by a backslash */ if (quote && is_dq_special(string[i]) || (!quote && is_shell_special(string[i]))) { out[j++] = '\\'; } out[j++] = string[i]; } if (quote) out[j++] = '"'; out[j] = '\0'; *res = out; return (0); } /* Assemble string form sid */ static char * sid_format(name_mapping_t *nm) { char *to; size_t len; char *typestring; switch (nm->is_wuser) { case IDMAP_YES: typestring = ID_USID; break; case IDMAP_NO: typestring = ID_GSID; break; default: typestring = ID_SID; break; } /* 'usid:' + sidprefix + '-' + rid + '\0' */ len = strlen(nm->sidprefix) + 7 + 3 * sizeof (nm->rid); to = (char *)malloc(len); if (to == NULL) return (NULL); (void) snprintf(to, len, "%s:%s-%u", typestring, nm->sidprefix, nm->rid); return (to); } /* Assemble string form uid or gid */ static char * pid_format(uid_t from, int is_user) { char *to; size_t len; /* ID_UID ":" + uid + '\0' */ len = 5 + 3 * sizeof (uid_t); to = (char *)malloc(len); if (to == NULL) return (NULL); (void) snprintf(to, len, "%s:%u", is_user ? ID_UID : ID_GID, from); return (to); } /* Assemble winname, e.g. "winuser:bob@foo.sun.com", from name_mapping_t */ static int nm2winqn(name_mapping_t *nm, char **winqn) { char *out; size_t length = 0; int is_domain = 1; char *prefix; /* Sometimes there are no text names. Return a sid, then. */ if (nm->winname == NULL && nm->sidprefix != NULL) { *winqn = sid_format(nm); return (0); } switch (nm->is_wuser) { case IDMAP_YES: prefix = ID_WINUSER ":"; break; case IDMAP_NO: prefix = ID_WINGROUP ":"; break; case IDMAP_UNKNOWN: prefix = ID_WINNAME ":"; break; } length = strlen(prefix); if (nm->winname != NULL) length += strlen(nm->winname); /* Windomain is not mandatory: */ if (nm->windomain == NULL || INHIBITED(nm->winname)) is_domain = 0; else length += strlen(nm->windomain) + 1; out = (char *)malloc(length + 1); if (out == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } (void) strcpy(out, prefix); /* LINTED E_NOP_IF_STMT */ if (nm->winname == NULL) ; else if (!is_domain) (void) strcat(out, nm->winname); else if (nm->is_nt4) { (void) strcat(out, nm->windomain); (void) strcat(out, "\\"); (void) strcat(out, nm->winname); } else { (void) strcat(out, nm->winname); (void) strcat(out, "@"); (void) strcat(out, nm->windomain); } *winqn = out; return (0); } /* * Assemble a text unixname, e.g. unixuser:fred. Use only for * mapping, not namerules - there an empty name means inhibited * mappings, while here pid is printed if there is no name. */ static int nm2unixname(name_mapping_t *nm, char **unixname) { size_t length = 0; char *out, *it, *prefix; /* Sometimes there is no name, just pid: */ if (nm->unixname == NULL) { if (nm->pid == UNDEFINED_UID) return (-1); *unixname = pid_format(nm->pid, nm->is_user); return (0); } if (shell_app(&it, nm->unixname, 0)) return (-1); switch (nm->is_user) { case IDMAP_YES: prefix = ID_UNIXUSER ":"; break; case IDMAP_NO: prefix = ID_UNIXGROUP ":"; break; case IDMAP_UNKNOWN: prefix = ID_UNIXUSER ":"; break; } length = strlen(prefix) + strlen(it); out = (char *)malloc(length + 1); if (out == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); free(it); return (-1); } (void) strcpy(out, prefix); (void) strcat(out, it); free(it); *unixname = out; return (0); } /* Allocate a new name_mapping_t and initialize the values. */ static name_mapping_t * name_mapping_init() { name_mapping_t *nm = (name_mapping_t *)malloc(sizeof (name_mapping_t)); if (nm == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (NULL); } nm->winname = nm->windomain = nm->unixname = nm->sidprefix = NULL; nm->rid = UNDEFINED_RID; nm->is_nt4 = B_FALSE; nm->is_user = IDMAP_UNKNOWN; nm->is_wuser = IDMAP_UNKNOWN; nm->direction = IDMAP_DIRECTION_UNDEF; nm->pid = UNDEFINED_UID; return (nm); } /* Free name_mapping_t */ static void name_mapping_fini(name_mapping_t *nm) { free(nm->winname); free(nm->windomain); free(nm->unixname); free(nm->sidprefix); free(nm); } static int name_mapping_cpy(name_mapping_t *to, name_mapping_t *from) { free(to->winname); free(to->windomain); free(to->unixname); free(to->sidprefix); (void) memcpy(to, from, sizeof (name_mapping_t)); to->winname = to->windomain = to->unixname = to->sidprefix = NULL; if (from->winname != NULL) { to->winname = strdup(from->winname); if (to->winname == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } } if (from->windomain != NULL) { to->windomain = strdup(from->windomain); if (to->windomain == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } } if (from->unixname != NULL) { to->unixname = strdup(from->unixname); if (to->unixname == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } } if (from->sidprefix != NULL) { to->sidprefix = strdup(from->sidprefix); if (to->sidprefix == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (-1); } } return (0); } static int name_mapping_format(name_mapping_t *nm, char **out) { char *winname = NULL; char *winname1 = NULL; char *unixname = NULL; int maxlen; *out = NULL; if (nm2winqn(nm, &winname1) < 0) return (-1); if (shell_app(&winname, winname1, 1)) { free(winname1); return (-1); } free(winname1); if (nm2unixname(nm, &unixname)) { free(winname); return (-1); } /* 10 is strlen("add -d\t\t\n") + 1 */ maxlen = 10 + strlen(unixname) + strlen(winname); *out = (char *)malloc(maxlen); if (nm->direction == IDMAP_DIRECTION_U2W) { (void) snprintf(*out, maxlen, "add -d\t%s\t%s\n", unixname, winname); } else { (void) snprintf(*out, maxlen, "add %s\t%s\t%s\n", nm->direction == IDMAP_DIRECTION_BI? "" : "-d", winname, unixname); } free(winname); free(unixname); return (0); } /* Initialize print_mapping variables. Must be called before print_mapping */ static print_handle_t * print_mapping_init(format_t f, FILE *fi) { print_handle_t *out; out = (print_handle_t *)malloc(sizeof (print_handle_t)); if (out == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); return (NULL); } out->format = f; out->file = fi; out->last = name_mapping_init(); if (out->last == NULL) return (NULL); return (out); } /* Finalize print_mapping. */ static int print_mapping_fini(print_handle_t *pnm) { char *out = NULL; int rc = 0; switch (pnm->format) { case SMBUSERS: if (pnm->last->unixname != NULL) { (void) fprintf(pnm->file, "\n"); } break; case DEFAULT_FORMAT: if (pnm->last->unixname == NULL) break; rc = name_mapping_format(pnm->last, &out); if (rc >= 0) { (void) fprintf(pnm->file, "%s", out); free(out); } break; default: ; } name_mapping_fini(pnm->last); free(pnm); return (rc); } static char * usermap_cfg_string(char *in) { int len; char *out; if (INHIBITED(in)) return (strdup("\"\"")); len = strlen(in); if (len == strcspn(in, " \t#")) return (strdup(in)); out = malloc(len + 3); if (out == NULL) return (NULL); (void) snprintf(out, len + 3, "\"%s\"", in); return (out); } /* * This prints both name rules and ordinary mappings, based on the pnm_format * set in print_mapping_init(). */ static int print_mapping(print_handle_t *pnm, name_mapping_t *nm) { char *dirstring; char *winname = NULL; char *windomain = NULL; char *unixname = NULL; FILE *f = pnm->file; switch (pnm->format) { case MAPPING_NAME: if (nm2winqn(nm, &winname) < 0) return (-1); if (nm2unixname(nm, &unixname) < 0) { free(winname); return (-1); } /* FALLTHROUGH */ case MAPPING_ID: if (pnm->format == MAPPING_ID) { if (nm->sidprefix == NULL) { print_error(NULL, gettext("SID not given.\n")); return (-1); } winname = sid_format(nm); if (winname == NULL) return (-1); unixname = pid_format(nm->pid, nm->is_user); if (unixname == NULL) { free(winname); return (-1); } } dirstring = direction2string(nm->direction); (void) fprintf(f, "%s\t%s\t%s\n", winname, dirstring, unixname); break; case SMBUSERS: if (nm->is_user != IDMAP_YES || nm->is_wuser != IDMAP_YES) { print_error(NULL, gettext("Group rule: ")); f = stderr; } else if (nm->direction == IDMAP_DIRECTION_U2W) { print_error(NULL, gettext("Opposite direction of the mapping: ")); f = stderr; } else if (INHIBITED(nm->winname) || INHIBITED(nm->unixname)) { print_error(NULL, gettext("Inhibited rule: ")); f = stderr; } if (shell_app(&winname, nm->winname, 1)) return (-1); unixname = INHIBITED(nm->unixname) ? "\"\"" : nm->unixname; if (pnm->file != f) { (void) fprintf(f, "%s=%s\n", unixname, winname); } else if (pnm->last->unixname != NULL && strcmp(pnm->last->unixname, unixname) == 0) { (void) fprintf(f, " %s", winname); } else { if (pnm->last->unixname != NULL) { (void) fprintf(f, "\n"); free(pnm->last->unixname); } pnm->last->unixname = strdup(unixname); if (pnm->last->unixname == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); } (void) fprintf(f, "%s=%s", unixname, winname); } unixname = NULL; break; case USERMAP_CFG: if (nm->is_user != IDMAP_YES || nm->is_wuser != IDMAP_YES) { print_error(NULL, gettext("Group rule: ")); f = stderr; } dirstring = direction2string(nm->direction); if ((winname = usermap_cfg_string(nm->winname)) == NULL || (unixname = usermap_cfg_string(nm->unixname)) == NULL || (windomain = usermap_cfg_string(nm->windomain)) == NULL) { print_error(NULL, "%s.\n", strerror(ENOMEM)); free(winname); free(unixname); free(windomain); return (-1); } if (nm->windomain == NULL) { (void) fprintf(f, "%s\t%s\t%s\n", winname, dirstring, unixname); } else (void) fprintf(f, nm->is_nt4 ? "%s\\%s\t%s\t%s\n" : "%2$s@%1$s\t%3$s\t%4$s\n", windomain, winname, dirstring, unixname); break; /* This is a format for namerules */ case DEFAULT_FORMAT: /* * If nm is the same as the last one except is_wuser, we combine * winuser & wingroup to winname */ if (nm->direction == pnm->last->direction && nm->is_user == pnm->last->is_user && strcmp_null(pnm->last->unixname, nm->unixname) == 0 && strcmp_null(pnm->last->winname, nm->winname) == 0 && strcmp_null(pnm->last->windomain, nm->windomain) == 0) { pnm->last->is_wuser = IDMAP_UNKNOWN; } else { if (pnm->last->unixname != NULL || pnm->last->winname != NULL) { char *out = NULL; if (name_mapping_format(pnm->last, &out) < 0) return (-1); (void) fprintf(f, "%s", out); free(out); } if (name_mapping_cpy(pnm->last, nm) < 0) return (-1); } break; default: /* This can never happen: */ print_error(NULL, gettext("Internal error: invalid print format.\n")); return (-1); } free(winname); free(unixname); free(windomain); return (0); } static void print_how(idmap_how *how) { idmap_namerule *rule; name_mapping_t nm; char *rule_text; switch (how->map_type) { case IDMAP_MAP_TYPE_DS_AD: (void) printf(gettext("Method:\tAD Directory\n")); (void) printf(gettext("DN:\t%s\n"), CHECK_NULL(how->idmap_how_u.ad.dn)); (void) printf(gettext("Attribute:\t%s=%s\n"), CHECK_NULL(how->idmap_how_u.ad.attr), CHECK_NULL(how->idmap_how_u.ad.value)); break; case IDMAP_MAP_TYPE_DS_NLDAP: (void) printf(gettext("Method:\tNative LDAP Directory\n")); (void) printf(gettext("DN:\t%s\n"), CHECK_NULL(how->idmap_how_u.nldap.dn)); (void) printf(gettext("Attribute:\t%s=%s\n"), CHECK_NULL(how->idmap_how_u.nldap.attr), CHECK_NULL(how->idmap_how_u.nldap.value)); break; case IDMAP_MAP_TYPE_RULE_BASED: (void) printf(gettext("Method:\tName Rule\n")); rule = &how->idmap_how_u.rule; /* * The name rules as specified by the user can have a * "winname", "winuser" or "wingroup". "Winname" rules are * decomposed to a "winuser" and "wingroup" rules by idmap. * Currently is_wuser is a boolean. Due to these reasons * the returned is_wuser does not represent the original rule. * It is therefore better set is_wuser to unknown. */ nm.is_user = rule->is_user; nm.is_wuser = IDMAP_UNKNOWN; nm.direction = rule->direction; nm.winname = rule->winname; nm.windomain = rule->windomain; nm.unixname = rule->unixname; nm.is_nt4 = rule->is_nt4; if (name_mapping_format(&nm, &rule_text) == 0) { (void) printf(gettext("Rule:\t%s"), rule_text); free(rule_text); } break; case IDMAP_MAP_TYPE_EPHEMERAL: (void) printf(gettext("Method:\tEphemeral\n")); break; case IDMAP_MAP_TYPE_LOCAL_SID: (void) printf(gettext("Method:\tLocal SID\n")); break; case IDMAP_MAP_TYPE_KNOWN_SID: (void) printf(gettext("Method:\tWell-Known mapping\n")); break; case IDMAP_MAP_TYPE_IDMU: (void) printf(gettext("Method:\tIDMU\n")); (void) printf(gettext("DN:\t%s\n"), CHECK_NULL(how->idmap_how_u.idmu.dn)); (void) printf(gettext("Attribute:\t%s=%s\n"), CHECK_NULL(how->idmap_how_u.idmu.attr), CHECK_NULL(how->idmap_how_u.idmu.value)); break; } } static void print_info(idmap_info *info) { if (info->how.map_type != IDMAP_MAP_TYPE_UNKNOWN) { switch (info->src) { case IDMAP_MAP_SRC_NEW: (void) printf(gettext("Source:\tNew\n")); break; case IDMAP_MAP_SRC_CACHE: (void) printf(gettext("Source:\tCache\n")); break; case IDMAP_MAP_SRC_HARD_CODED: (void) printf(gettext("Source:\tHard Coded\n")); break; case IDMAP_MAP_SRC_ALGORITHMIC: (void) printf(gettext("Source:\tAlgorithmic\n")); break; } print_how(&info->how); } if (info->trace != NULL) { (void) printf(gettext("Trace:\n")); idmap_trace_print(stdout, "\t", info->trace); } } static void print_error_info(idmap_info *info) { idmap_how *how = &info->how; idmap_namerule *rule; name_mapping_t nm; char *rule_text; (void) memset(&nm, 0, sizeof (nm)); switch (how->map_type) { case IDMAP_MAP_TYPE_DS_AD: (void) fprintf(stderr, gettext("Failed Method:\tAD Directory\n")); (void) fprintf(stderr, gettext("DN:\t%s\n"), how->idmap_how_u.ad.dn); (void) fprintf(stderr, gettext("Attribute:\t%s=%s\n"), how->idmap_how_u.ad.attr, how->idmap_how_u.ad.value); break; case IDMAP_MAP_TYPE_DS_NLDAP: (void) fprintf(stderr, gettext("Failed Method:\tNative LDAP Directory\n")); (void) fprintf(stderr, gettext("DN:\t%s\n"), how->idmap_how_u.nldap.dn); (void) fprintf(stderr, gettext("Attribute:\t%s=%s\n"), how->idmap_how_u.nldap.attr, how->idmap_how_u.nldap.value); break; case IDMAP_MAP_TYPE_RULE_BASED: (void) fprintf(stderr, gettext("Failed Method:\tName Rule\n")); rule = &how->idmap_how_u.rule; /* * The name rules as specified by the user can have a * "winname", "winuser" or "wingroup". "Winname" rules are * decomposed to a "winuser" and "wingroup" rules by idmap. * Currently is_wuser is a boolean. Due to these reasons * the returned is_wuser does not represent the original rule. * It is therefore better to set is_wuser to unknown. */ nm.is_user = rule->is_user; nm.is_wuser = IDMAP_UNKNOWN; nm.direction = rule->direction; nm.winname = rule->winname; nm.windomain = rule->windomain; nm.unixname = rule->unixname; nm.is_nt4 = rule->is_nt4; if (name_mapping_format(&nm, &rule_text) == 0) { (void) fprintf(stderr, gettext("Rule:\t%s"), rule_text); free(rule_text); } break; case IDMAP_MAP_TYPE_EPHEMERAL: (void) fprintf(stderr, gettext("Failed Method:\tEphemeral\n")); break; case IDMAP_MAP_TYPE_LOCAL_SID: (void) fprintf(stderr, gettext("Failed Method:\tLocal SID\n")); break; case IDMAP_MAP_TYPE_KNOWN_SID: (void) fprintf(stderr, gettext("Failed Method:\tWell-Known mapping\n")); break; case IDMAP_MAP_TYPE_IDMU: (void) fprintf(stderr, gettext("Failed Method:\tIDMU\n")); (void) fprintf(stderr, gettext("DN:\t%s\n"), CHECK_NULL(how->idmap_how_u.idmu.dn)); (void) fprintf(stderr, gettext("Attribute:\t%s=%s\n"), CHECK_NULL(how->idmap_how_u.idmu.attr), CHECK_NULL(how->idmap_how_u.idmu.value)); break; } if (info->trace != NULL) { (void) printf(gettext("Trace:\n")); idmap_trace_print(stderr, "\t", info->trace); } } /* dump command handler */ static int /* LINTED E_FUNC_ARG_UNUSED */ do_dump(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { idmap_stat stat; idmap_iter_t *ihandle; int rc = 0; boolean_t is_user; boolean_t is_wuser; print_handle_t *ph; int flag = 0; idmap_info info; ph = print_mapping_init(f[n_FLAG] != NULL ? MAPPING_NAME : MAPPING_ID, stdout); if (ph == NULL) return (-1); if (f[v_FLAG] != NULL) flag = IDMAP_REQ_FLG_MAPPING_INFO; stat = idmap_iter_mappings(&ihandle, flag); if (stat < 0) { print_error(pos, gettext("Iteration handle not obtained (%s)\n"), idmap_stat2string(stat)); rc = -1; goto cleanup; } do { name_mapping_t *nm = name_mapping_init(); if (nm == NULL) { rc = -1; goto cleanup; } stat = idmap_iter_next_mapping(ihandle, &nm->sidprefix, &nm->rid, &nm->pid, &nm->winname, &nm->windomain, &nm->unixname, &is_user, &is_wuser, &nm->direction, &info); nm->is_user = is_user ? IDMAP_YES : IDMAP_NO; nm->is_wuser = is_wuser ? IDMAP_YES : IDMAP_NO; if (stat >= 0) { (void) print_mapping(ph, nm); print_how(&info.how); idmap_info_free(&info); } name_mapping_fini(nm); } while (stat > 0); /* IDMAP_ERR_NOTFOUND indicates end of the list */ if (stat < 0 && stat != IDMAP_ERR_NOTFOUND) { print_error(pos, gettext("Error during iteration (%s)\n"), idmap_stat2string(stat)); rc = -1; goto cleanup; } idmap_iter_destroy(ihandle); cleanup: (void) print_mapping_fini(ph); return (rc); } /* * Convert pid from string to it's numerical representation. If it is * a valid string, i.e. number of a proper length, return 1. Otherwise * print an error message and return 0. */ static int pid_convert(char *string, uid_t *number, int type, cmd_pos_t *pos) { int i; long long ll; char *type_string; size_t len = strlen(string); if (type == TYPE_GID) type_string = ID_GID; else if (type == TYPE_UID) type_string = ID_UID; else return (0); for (i = 0; i < len; i++) { if (!isdigit(string[i])) { print_error(pos, gettext("\"%s\" is not a valid %s: the non-digit" " character '%c' found.\n"), string, type_string, string[i]); return (0); } } ll = atoll(string); /* Isn't it too large? */ if (type == TYPE_UID && (uid_t)ll != ll || type == TYPE_GID && (gid_t)ll != ll) { print_error(pos, gettext("%llu: too large for a %s.\n"), ll, type_string); return (0); } *number = (uid_t)ll; return (1); } /* * Convert SID from string to prefix and rid. If it has a valid * format, i.e. S(\-\d+)+, return 1. Otherwise print an error * message and return 0. */ static int sid_convert(char *from, char **prefix, idmap_rid_t *rid, cmd_pos_t *pos) { int i, j; char *cp; char *ecp; char *prefix_end; u_longlong_t a; unsigned long r; if (strcmp_no0(from, "S-1-") != 0) { print_error(pos, gettext("Invalid %s \"%s\": it doesn't start " "with \"%s\".\n"), ID_SID, from, "S-1-"); return (0); } if (strlen(from) <= strlen("S-1-")) { print_error(pos, gettext("Invalid %s \"%s\": the authority and RID parts are" " missing.\n"), ID_SID, from); return (0); } /* count '-'s */ for (j = 0, cp = strchr(from, '-'); cp != NULL; j++, cp = strchr(cp + 1, '-')) { /* can't end on a '-' */ if (*(cp + 1) == '\0') { print_error(pos, gettext("Invalid %s \"%s\": '-' at the end.\n"), ID_SID, from); return (0); } else if (*(cp + 1) == '-') { print_error(pos, gettext("Invalid %s \"%s\": double '-'.\n"), ID_SID, from); return (0); } } /* check that we only have digits and '-' */ i = strspn(from + 1, "0123456789-") + 1; if (i < strlen(from)) { print_error(pos, gettext("Invalid %s \"%s\": invalid character '%c'.\n"), ID_SID, from, from[i]); return (0); } cp = from + strlen("S-1-"); /* 64-bit safe parsing of unsigned 48-bit authority value */ errno = 0; a = strtoull(cp, &ecp, 10); /* errors parsing the authority or too many bits */ if (cp == ecp || (a == 0 && errno == EINVAL)) { print_error(pos, gettext("Invalid %s \"%s\": unable to parse the " "authority \"%.*s\".\n"), ID_SID, from, ecp - cp, cp); return (0); } if ((a == ULLONG_MAX && errno == ERANGE) || (a & 0x0000ffffffffffffULL) != a) { print_error(pos, gettext("Invalid %s \"%s\": the authority " "\"%.*s\" is too large.\n"), ID_SID, from, ecp - cp, cp); return (0); } cp = ecp; if (j < 3) { print_error(pos, gettext("Invalid %s \"%s\": must have at least one RID.\n"), ID_SID, from); return (0); } for (i = 2; i < j; i++) { if (*cp++ != '-') { /* Should never happen */ print_error(pos, gettext("Invalid %s \"%s\": internal error:" " '-' missing.\n"), ID_SID, from); return (0); } /* 32-bit safe parsing of unsigned 32-bit RID */ errno = 0; r = strtoul(cp, &ecp, 10); /* errors parsing the RID */ if (cp == ecp || (r == 0 && errno == EINVAL)) { /* should never happen */ print_error(pos, gettext("Invalid %s \"%s\": internal error: " "unable to parse the RID " "after \"%.*s\".\n"), ID_SID, from, cp - from, from); return (0); } if (r == ULONG_MAX && errno == ERANGE) { print_error(pos, gettext("Invalid %s \"%s\": the RID \"%.*s\"" " is too large.\n"), ID_SID, from, ecp - cp, cp); return (0); } prefix_end = cp; cp = ecp; } /* check that all of the string SID has been consumed */ if (*cp != '\0') { /* Should never happen */ print_error(pos, gettext("Invalid %s \"%s\": internal error: " "something is still left.\n"), ID_SID, from); return (0); } *rid = (idmap_rid_t)r; /* -1 for the '-' at the end: */ *prefix = strndup(from, prefix_end - from - 1); if (*prefix == NULL) { print_error(pos, "%s.\n", strerror(ENOMEM)); return (0); } return (1); } /* Does the line start with USERMAP_CFG IP qualifier? */ static int ucp_is_IP_qualifier(char *line) { char *it; it = line + strcspn(line, " \t\n#:"); return (*(it + 1) == ':' ? 1 : 0); } /* * returns interior of quotation marks in USERMAP_CFG. In this format, * there cannot be a protected quotation mark inside. */ static char * ucp_qm_interior(char **line, cmd_pos_t *pos) { char *out; char *qm = strchr(*line + 1, '"'); if (qm == NULL) { print_error(pos, gettext("Unclosed quotations\n")); return (NULL); } out = strndup(*line + 1, qm - *line - 1); *line = qm + 1; return (out); } /* * Grab next token from the line in USERMAP_CFG format. terminators, * the 3rd parameter, contains all the characters which can terminate * the token. line_num is the line number of input used for error * reporting. */ static char * ucp_grab_token(char **line, cmd_pos_t *pos, const char *terminators) { char *token; if (**line == '"') token = ucp_qm_interior(line, pos); else { int length = strcspn(*line, terminators); token = strndup(*line, length); *line += length; } return (token); } /* * Convert a line in usermap.cfg format to name_mapping. * * Return values: -1 for error, 0 for empty line, 1 for a mapping * found. */ static int ucp_line2nm(char *line, cmd_pos_t *pos, name_mapping_t *nm) { char *it; char *token; char *token2; char separator; int is_direction = 0; it = line + strspn(line, " \t\n"); /* empty or comment lines are OK: */ if (*it == '\0' || *it == '#') return (0); /* We do not support network qualifiers */ if (ucp_is_IP_qualifier(it)) { print_error(pos, gettext("Unable to handle network qualifier.\n")); return (-1); } /* The windows name: */ token = ucp_grab_token(&it, pos, " \t#\\\n@=<"); if (token == NULL) return (-1); separator = *it; /* Didn't we bump to the end of line? */ if (separator == '\0' || separator == '#') { free(token); print_error(pos, gettext("UNIX_name not found.\n")); return (-1); } /* Do we have a domainname? */ if (separator == '\\' || separator == '@') { it ++; token2 = ucp_grab_token(&it, pos, " \t\n#"); if (token2 == NULL) { free(token); return (-1); } else if (*it == '\0' || *it == '#') { free(token); free(token2); print_error(pos, gettext("UNIX_name not found.\n")); } if (separator == '\\') { nm->windomain = token; nm->winname = token2; nm->is_nt4 = 1; } else { nm->windomain = token2; nm->winname = token; nm->is_nt4 = 0; } } else { nm->windomain = NULL; nm->winname = token; nm->is_nt4 = 0; } it = it + strspn(it, " \t\n"); /* Direction string is optional: */ if (strncmp(it, "==", 2) == 0) { nm->direction = IDMAP_DIRECTION_BI; is_direction = 1; } else if (strncmp(it, "<=", 2) == 0) { nm->direction = IDMAP_DIRECTION_U2W; is_direction = 1; } else if (strncmp(it, "=>", 2) == 0) { nm->direction = IDMAP_DIRECTION_W2U; is_direction = 1; } else { nm->direction = IDMAP_DIRECTION_BI; is_direction = 0; } if (is_direction) { it += 2; it += strspn(it, " \t\n"); if (*it == '\0' || *it == '#') { print_error(pos, gettext("UNIX_name not found.\n")); return (-1); } } /* Now unixname: */ it += strspn(it, " \t\n"); token = ucp_grab_token(&it, pos, " \t\n#"); if (token == NULL) /* nm->winname to be freed by name_mapping_fini */ return (-1); /* Neither here we support IP qualifiers */ if (ucp_is_IP_qualifier(token)) { print_error(pos, gettext("Unable to handle network qualifier.\n")); free(token); return (-1); } nm->unixname = token; it += strspn(it, " \t\n"); /* Does something remain on the line */ if (*it != '\0' && *it != '#') { print_error(pos, gettext("Unrecognized parameters \"%s\".\n"), it); return (-1); } return (1); } /* * Parse SMBUSERS line to name_mapping_t. if line is NULL, then * pasrsing of the previous line is continued. line_num is input line * number used for error reporting. * Return values: * rc -1: error * rc = 0: mapping found and the line is finished, * rc = 1: mapping found and there remains other on the line */ static int sup_line2nm(char *line, cmd_pos_t *pos, name_mapping_t *nm) { static char *ll = NULL; static char *unixname = NULL; static size_t unixname_l = 0; char *token; if (line != NULL) { ll = line; unixname = ll += strspn(ll, " \t"); if (*ll == '\0' || *ll == '#') return (0); unixname_l = strcspn(ll, " \t:=#\n"); ll += unixname_l; if (*ll == '\0'|| *ll == '#') return (0); ll += strspn(ll, " \t:=#\n"); } if (*ll == '\0'|| *ll == '#') return (0); token = ucp_grab_token(&ll, pos, " \t\n"); if (token == NULL) return (-1); nm->is_nt4 = 0; nm->direction = IDMAP_DIRECTION_W2U; nm->windomain = NULL; nm->winname = token; nm->unixname = strndup(unixname, unixname_l); if (nm->unixname == NULL) return (-1); ll += strspn(ll, " \t\n"); return (1); } /* Parse line to name_mapping_t. Basicaly just a format switch. */ static int line2nm(char *line, cmd_pos_t *pos, name_mapping_t *nm, format_t f) { switch (f) { case USERMAP_CFG: if (line == NULL) return (0); else return (ucp_line2nm(line, pos, nm)); case SMBUSERS: return (sup_line2nm(line, pos, nm)); default: /* This can never happen */ print_error(pos, gettext("Internal error: invalid line format.\n")); } return (-1); } /* Examine -f flag and return the appropriate format_t */ static format_t ff2format(char *ff, int is_mandatory) { if (ff == NULL && is_mandatory) { print_error(NULL, gettext("Format not given.\n")); return (UNDEFINED_FORMAT); } if (ff == NULL) return (DEFAULT_FORMAT); if (strcasecmp(ff, "usermap.cfg") == 0) return (USERMAP_CFG); if (strcasecmp(ff, "smbusers") == 0) return (SMBUSERS); print_error(NULL, gettext("The only known formats are: \"usermap.cfg\" and " "\"smbusers\".\n")); return (UNDEFINED_FORMAT); } /* Delete all namerules of the given type */ static int flush_nm(boolean_t is_user, cmd_pos_t *pos) { idmap_stat stat; stat = idmap_udt_flush_namerules(udt); if (stat < 0) { print_error(pos, is_user ? gettext("Unable to flush users (%s).\n") : gettext("Unable to flush groups (%s).\n"), idmap_stat2string(stat)); return (-1); } if (positions_add(pos) < 0) return (-1); return (0); } /* import command handler */ static int /* LINTED E_FUNC_ARG_UNUSED */ do_import(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { name_mapping_t *nm; cmd_pos_t pos2; char line[MAX_INPUT_LINE_SZ]; format_t format; int rc = 0; idmap_stat stat; FILE *file = NULL; if (batch_mode) { print_error(pos, gettext("Import is not allowed in the batch mode.\n")); return (-1); } format = ff2format(argv[0], 1); if (format == UNDEFINED_FORMAT) return (-1); if (init_udt_command()) return (-1); /* We don't flush groups in the usermap.cfg nor smbusers format */ if (f[F_FLAG] != NULL && flush_nm(B_TRUE, pos) < 0 && (format == USERMAP_CFG || format == SMBUSERS || flush_nm(B_FALSE, pos) < 0)) { rc = -1; goto cleanup; } /* Where we import from? */ if (f[f_FLAG] == NULL) file = stdin; else { file = fopen(f[f_FLAG], "r"); if (file == NULL) { perror(f[f_FLAG]); goto cleanup; } } pos2.linenum = 0; pos2.line = line; while (fgets(line, MAX_INPUT_LINE_SZ, file)) { char *line2 = line; pos2.linenum++; /* * In SMBUSERS format there can be more mappings on * each line. So we need the internal cycle for each line. */ do { nm = name_mapping_init(); if (nm == NULL) { rc = -1; goto cleanup; } rc = line2nm(line2, &pos2, nm, format); line2 = NULL; if (rc < 1) { name_mapping_fini(nm); break; } stat = idmap_udt_add_namerule(udt, nm->windomain, nm->is_user ? B_TRUE : B_FALSE, nm->is_wuser ? B_TRUE : B_FALSE, nm->winname, nm->unixname, nm->is_nt4, nm->direction); if (stat < 0) { print_error(&pos2, gettext("Transaction error (%s)\n"), idmap_stat2string(stat)); rc = -1; } if (rc >= 0) rc = positions_add(&pos2); name_mapping_fini(nm); } while (rc >= 0); if (rc < 0) { print_error(NULL, gettext("Import canceled.\n")); break; } } cleanup: if (fini_udt_command((rc < 0 ? 0 : 1), pos)) rc = -1; if (file != NULL && file != stdin) (void) fclose(file); return (rc); } /* * List name mappings in the format specified. list_users / * list_groups determine which type to list. The output goes to the * file fi. */ static int list_name_mappings(format_t format, FILE *fi) { idmap_stat stat; idmap_iter_t *ihandle; name_mapping_t *nm; boolean_t is_user; boolean_t is_wuser; print_handle_t *ph; stat = idmap_iter_namerules(NULL, 0, 0, NULL, NULL, &ihandle); if (stat < 0) { print_error(NULL, gettext("Iteration handle not obtained (%s)\n"), idmap_stat2string(stat)); idmap_iter_destroy(ihandle); return (-1); } ph = print_mapping_init(format, fi); if (ph == NULL) return (-1); do { nm = name_mapping_init(); if (nm == NULL) { idmap_iter_destroy(ihandle); return (-1); } stat = idmap_iter_next_namerule(ihandle, &nm->windomain, &nm->winname, &nm->unixname, &is_user, &is_wuser, &nm->is_nt4, &nm->direction); if (stat >= 0) { nm->is_user = is_user ? IDMAP_YES : IDMAP_NO; nm->is_wuser = is_wuser ? IDMAP_YES : IDMAP_NO; (void) print_mapping(ph, nm); } name_mapping_fini(nm); } while (stat > 0); (void) print_mapping_fini(ph); if (stat < 0 && stat != IDMAP_ERR_NOTFOUND) { print_error(NULL, gettext("Error during iteration (%s)\n"), idmap_stat2string(stat)); idmap_iter_destroy(ihandle); return (-1); } idmap_iter_destroy(ihandle); return (0); } /* Export command handler */ static int /* LINTED E_FUNC_ARG_UNUSED */ do_export(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { int rc; format_t format; FILE *fi; format = ff2format(argv[0], 1); if (format == UNDEFINED_FORMAT) return (-1); /* Where do we output to? */ if (f[f_FLAG] == NULL) fi = stdout; else { fi = fopen(f[f_FLAG], "w"); if (fi == NULL) { perror(f[f_FLAG]); return (-1); } } /* List the requested types: */ rc = list_name_mappings(format, fi); if (fi != NULL && fi != stdout) (void) fclose(fi); return (rc); } /* List command handler */ static int /* LINTED E_FUNC_ARG_UNUSED */ do_list_name_mappings(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { int rc; /* List the requested types: */ rc = list_name_mappings(DEFAULT_FORMAT, stdout); return (rc); } /* This is just a debug function for dumping flags */ static void print_flags(flag_t *f) { int c; for (c = 0; c < FLAG_ALPHABET_SIZE; c++) { if (f[c] == FLAG_SET) (void) printf("FLAG: -%c, VALUE: %p\n", c, (void *) f[c]); else if (f[c]) (void) printf("FLAG: -%c, VALUE: %s\n", c, f[c]); } } /* Convert string like sid or winname to the identity type code */ static int string2type(char *str, cmd_pos_t *pos) { int i; int code = TYPE_INVALID; for (i = 0; i < sizeof (identity2code) / sizeof (id_code_t); i++) { if (strcasecmp(identity2code[i].identity, str) == 0) { code = identity2code[i].code; break; } } if (code == TYPE_INVALID) { print_error(pos, gettext("Error: invalid identity type \"%s\"\n"), str); } return (code); } /* * Split argument to its identity code and a name part * return values: * TYPE_INVALID for unknown identity * TYPE_AUTO for no identity (to be autodetected) * for known identity */ static int get_identity(char *arg, char **name, cmd_pos_t *pos) { char *it; int code = TYPE_INVALID; if ((it = strchr(arg, ':')) == NULL) { *name = arg; return (TYPE_AUTO); } *it = '\0'; code = string2type(arg, pos); *it = ':'; /* restore the original string: */ *name = it + 1; return (code); } /* * This function splits name to the relevant pieces: is_user, winname, * windomain unixname. E.g. for winname, it strdups nm->winname and possibly * nm->windomain and return TYPE_WN. * * If there is already one of the text fields allocated, it is OK. * Return values: * -1 ... syntax error * 0 ... it wasnt possible to determine * otherwise */ static int name2parts(char *name, name_mapping_t *nm, cmd_pos_t *pos) { char *it; int code; code = get_identity(name, &it, pos); switch (code) { case TYPE_INVALID: /* syntax error: */ return (-1); case TYPE_AUTO: /* autodetection: */ if (nm->winname != NULL && nm->is_wuser != IDMAP_UNKNOWN) code = nm->is_wuser == IDMAP_YES ? TYPE_UU : TYPE_UG; else if (nm->unixname != NULL || strchr(name, '@') != NULL || strchr(name, '\\') != NULL) /* btw, nm->is_user can never be IDMAP_UNKNOWN here */ code = TYPE_WN; else return (0); /* If the code was guessed succesfully, we are OK. */ break; default: name = it; } if (code & IS_WIN) { if (code & IS_USER) nm->is_wuser = IDMAP_YES; else if (code & IS_GROUP) nm->is_wuser = IDMAP_NO; } else { if (code & IS_USER) nm->is_user = IDMAP_YES; else if (code & IS_GROUP) nm->is_user = IDMAP_NO; } if (code & IS_WIN && code & IS_NAME) { if (nm->winname != NULL || nm->windomain != NULL) return (code); if ((it = strchr(name, '@')) != NULL) { int length = it - name + 1; nm->winname = (char *)malloc(length); (void) strncpy(nm->winname, name, length - 1); nm->winname[length - 1] = '\0'; nm->windomain = strdup(it + 1); } else if ((it = strrchr(name, '\\')) != NULL) { int length = it - name + 1; nm->windomain = (char *)malloc(length); (void) strncpy(nm->windomain, name, length - 1); nm->windomain[length - 1] = '\0'; nm->winname = strdup(it + 1); nm->is_nt4 = B_TRUE; } else nm->winname = strdup(name); return (code); } if (!(code & IS_WIN) && code & IS_NAME) { if (nm->unixname != NULL) return (code); if (strlen(name) == 0) nm->unixname = strdup("\"\""); else nm->unixname = strdup(name); return (code); } if (code & IS_WIN && !(code & IS_NAME)) { if (!sid_convert(name, &nm->sidprefix, &nm->rid, pos)) return (-1); else return (code); } /* * it is (!(code & TYPE_WIN) && !(code & TYPE_NAME)) here - the other * possiblities are exhausted. */ if (!pid_convert(name, &nm->pid, code, pos)) return (-1); else return (code); } /* * Cycle through add/remove arguments until they are identified or found * invalid. */ static name_mapping_t * args2nm(int *is_first_win, int argc, char **argv, cmd_pos_t *pos) { int code; int i; name_mapping_t *nm; nm = name_mapping_init(); if (nm == NULL) return (NULL); for (i = 0; i < 2 * argc - 1; i++) { code = name2parts(argv[i % 2], nm, pos); switch (code) { case -1: goto fail; case 0: if (i > 0) { print_error(pos, gettext("Missing identity type" " cannot be determined for %s.\n"), argv[i % 2]); goto fail; } break; default: if (!(code & IS_NAME)) { print_error(pos, gettext("%s is not a valid name\n"), argv[i % 2]); goto fail; } } } if (argc == 2 && nm->winname == NULL) { print_error(pos, gettext("No windows identity found.\n")); goto fail; } if (argc == 2 && nm->unixname == NULL) { print_error(pos, gettext("No unix identity found.\n")); goto fail; } if (argc == 1 && nm->winname == NULL && nm->unixname == NULL) { print_error(pos, gettext("No identity type determined.\n")); goto fail; } if (is_first_win != NULL) *is_first_win = code & IS_WIN; return (nm); fail: name_mapping_fini(nm); return (NULL); } /* add command handler. */ static int do_add_name_mapping(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { name_mapping_t *nm; int rc = 0; int is_first_win; idmap_stat stat; int is_wuser; print_handle_t *ph; /* Exactly two arguments must be specified */ if (argc < 2) { print_error(pos, gettext("Not enough arguments.\n")); return (-1); } else if (argc > 2) { print_error(pos, gettext("Too many arguments.\n")); return (-1); } nm = args2nm(&is_first_win, argc, argv, pos); if (nm == NULL) return (-1); if (f[d_FLAG] != NULL) nm->direction = is_first_win ? IDMAP_DIRECTION_W2U : IDMAP_DIRECTION_U2W; else nm->direction = IDMAP_DIRECTION_BI; /* Now let us write it: */ if (init_udt_command()) { name_mapping_fini(nm); return (-1); } for (is_wuser = IDMAP_YES; is_wuser >= IDMAP_NO; is_wuser--) { /* nm->is_wuser can be IDMAP_YES, IDMAP_NO or IDMAP_UNKNOWN */ if ((is_wuser == IDMAP_YES && nm->is_wuser == IDMAP_NO) || (is_wuser == IDMAP_NO && nm->is_wuser == IDMAP_YES)) continue; stat = idmap_udt_add_namerule(udt, nm->windomain, nm->is_user ? B_TRUE : B_FALSE, is_wuser ? B_TRUE : B_FALSE, nm->winname, nm->unixname, nm->is_nt4, nm->direction); } /* We echo the mapping */ ph = print_mapping_init(DEFAULT_FORMAT, stdout); if (ph == NULL) { rc = -1; goto cleanup; } (void) print_mapping(ph, nm); (void) print_mapping_fini(ph); if (stat != IDMAP_SUCCESS) { print_error(pos, gettext("Mapping not created (%s)\n"), idmap_stat2string(stat)); rc = -1; } if (rc == 0) rc = positions_add(pos); cleanup: name_mapping_fini(nm); if (fini_udt_command(1, pos)) rc = -1; return (rc); } /* remove command handler */ static int do_remove_name_mapping(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { name_mapping_t *nm; int rc = 0; idmap_stat stat; int is_first_win; int is_wuser; /* "-a" means we flush all of them */ if (f[a_FLAG] != NULL) { if (argc) { print_error(pos, gettext("Too many arguments.\n")); return (-1); } if (init_udt_command()) return (-1); rc = flush_nm(B_TRUE, pos); if (rc >= 0) rc = flush_nm(B_FALSE, pos); if (fini_udt_command(rc ? 0 : 1, pos)) rc = -1; return (rc); } /* Contrary to add_name_mapping, we can have only one argument */ if (argc < 1) { print_error(pos, gettext("Not enough arguments.\n")); return (-1); } else if (argc > 2) { print_error(pos, gettext("Too many arguments.\n")); return (-1); } else if ( /* both -f and -t: */ f[f_FLAG] != NULL && f[t_FLAG] != NULL || /* -d with a single argument: */ argc == 1 && f[d_FLAG] != NULL || /* -f or -t with two arguments: */ argc == 2 && (f[f_FLAG] != NULL || f[t_FLAG] != NULL)) { print_error(pos, gettext("Direction ambiguous.\n")); return (-1); } /* * Similar to do_add_name_mapping - see the comments * there. Except we may have only one argument here. */ nm = args2nm(&is_first_win, argc, argv, pos); if (nm == NULL) return (-1); /* * If the direction is not specified by a -d/-f/-t flag, then it * is IDMAP_DIRECTION_UNDEF, because in that case we want to * remove any mapping. If it was IDMAP_DIRECTION_BI, idmap_api would * delete a bidirectional one only. */ if (f[d_FLAG] != NULL || f[f_FLAG] != NULL) nm->direction = is_first_win ? IDMAP_DIRECTION_W2U : IDMAP_DIRECTION_U2W; else if (f[t_FLAG] != NULL) nm->direction = is_first_win ? IDMAP_DIRECTION_U2W : IDMAP_DIRECTION_W2U; else nm->direction = IDMAP_DIRECTION_UNDEF; if (init_udt_command()) { name_mapping_fini(nm); return (-1); } for (is_wuser = IDMAP_YES; is_wuser >= IDMAP_NO; is_wuser--) { if ((is_wuser == IDMAP_YES && nm->is_wuser == IDMAP_NO) || (is_wuser == IDMAP_NO && nm->is_wuser == IDMAP_YES)) continue; stat = idmap_udt_rm_namerule(udt, nm->is_user ? B_TRUE : B_FALSE, is_wuser ? B_TRUE : B_FALSE, nm->windomain, nm->winname, nm->unixname, nm->direction); if (stat != IDMAP_SUCCESS) { print_error(pos, gettext("Mapping not deleted (%s)\n"), idmap_stat2string(stat)); rc = -1; break; } } if (rc == 0) rc = positions_add(pos); name_mapping_fini(nm); if (fini_udt_command(1, pos)) rc = -1; return (rc); } /* flush command handler */ static int do_flush(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { NOTE(ARGUNUSED(argv)) idmap_flush_op op; idmap_stat stat; int rc = 0; if (argc > 0) { print_error(pos, gettext("Too many arguments.\n")); return (-1); } if (f[a_FLAG] != NULL) op = IDMAP_FLUSH_DELETE; else op = IDMAP_FLUSH_EXPIRE; stat = idmap_flush(op); if (stat != IDMAP_SUCCESS) { print_error(pos, gettext("%s\n"), idmap_stat2string(stat)); rc = -1; } return (rc); } /* exit command handler */ static int /* LINTED E_FUNC_ARG_UNUSED */ do_exit(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { return (0); } /* debug command handler: just print the parameters */ static int /* LINTED E_STATIC_UNUSED */ debug_print_params(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { int i; #if 0 char *leaktest = (char *)malloc(100); #endif print_flags(f); for (i = 0; i < argc; i++) { (void) printf("Argument %d: %s\n", i, argv[i]); } (void) fflush(stdout); return (0); } /* * From name_mapping_t, asseble a string containing identity of the * given type. */ static int nm2type(name_mapping_t *nm, int type, char **to) { switch (type) { case TYPE_SID: case TYPE_USID: case TYPE_GSID: if (nm->sidprefix == NULL) return (-1); *to = sid_format(nm); return (0); case TYPE_WN: case TYPE_WU: case TYPE_WG: return (nm2winqn(nm, to)); case TYPE_UID: case TYPE_GID: case TYPE_PID: *to = pid_format(nm->pid, nm->is_user); if (*to == NULL) return (-1); else return (0); case TYPE_UN: case TYPE_UU: case TYPE_UG: return (nm2unixname(nm, to)); default: /* This can never happen: */ print_error(NULL, gettext("Internal error: invalid name type.\n")); return (-1); } /* never reached */ } /* show command handler */ static int do_show_mapping(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { idmap_stat stat = 0; int flag; idmap_stat map_stat = 0; int type_from; int type_to; name_mapping_t *nm = NULL; char *fromname; char *toname; idmap_info info; (void) memset(&info, 0, sizeof (info)); if (argc == 0) { print_error(pos, gettext("No identity given\n")); return (-1); } else if (argc > 2) { print_error(pos, gettext("Too many arguments.\n")); return (-1); } flag = 0; if (f[c_FLAG] == NULL) flag |= IDMAP_REQ_FLG_NO_NEW_ID_ALLOC; if (f[v_FLAG] != NULL) flag |= IDMAP_REQ_FLG_MAPPING_INFO; if (f[V_FLAG] != NULL) flag |= IDMAP_REQ_FLG_TRACE; nm = name_mapping_init(); if (nm == NULL) goto cleanup; type_from = name2parts(argv[0], nm, pos); if (type_from <= 0) { stat = IDMAP_ERR_ARG; goto cleanup; } /* Second, determine type_to: */ if (argc < 2) { type_to = type_from & IS_WIN ? TYPE_PID : TYPE_SID; if (type_from & IS_NAME) type_to |= IS_NAME; } else { type_to = string2type(argv[1], pos); if (type_to == TYPE_INVALID) { stat = IDMAP_ERR_ARG; goto cleanup; } } if (type_to & IS_WIN) { if (type_to & IS_USER) nm->is_wuser = IDMAP_YES; else if (type_to & IS_GROUP) nm->is_wuser = IDMAP_NO; else nm->is_wuser = IDMAP_UNKNOWN; } else { if (type_to & IS_USER) nm->is_user = IDMAP_YES; else if (type_to & IS_GROUP) nm->is_user = IDMAP_NO; } /* Are both arguments the same OS side? */ if (!(type_from & IS_WIN ^ type_to & IS_WIN)) { print_error(pos, gettext("Direction ambiguous.\n")); stat = IDMAP_ERR_ARG; goto cleanup; } /* * We have two interfaces for retrieving the mappings: * idmap_get_sidbyuid & comp (the batch interface) and * idmap_get_w2u_mapping & comp. We want to use both of them, because * the former mimicks kernel interface better and the later offers the * string names. In the batch case, our batch has always size 1. * * Btw, type_from cannot be IDMAP_PID, because there is no type string * for it. */ if (type_from & IS_NAME || type_to & IS_NAME || type_from == TYPE_GSID || type_from == TYPE_USID || type_to == TYPE_GSID || type_to == TYPE_USID) { if (type_from & IS_WIN) { map_stat = idmap_get_w2u_mapping( nm->sidprefix, &nm->rid, nm->winname, nm->windomain, flag, &nm->is_user, &nm->is_wuser, &nm->pid, &nm->unixname, &nm->direction, &info); } else { map_stat = idmap_get_u2w_mapping( &nm->pid, nm->unixname, flag, nm->is_user, &nm->is_wuser, &nm->sidprefix, &nm->rid, &nm->winname, &nm->windomain, &nm->direction, &info); } } else { /* batch handle */ idmap_get_handle_t *ghandle = NULL; /* To be passed to idmap_get_uidbysid */ gid_t gid = UNDEFINED_GID; /* To be passed to idmap_get_gidbysid */ uid_t uid = UNDEFINED_UID; /* Create an in-memory structure for all the batch: */ stat = idmap_get_create(&ghandle); if (stat != IDMAP_SUCCESS) { print_error(pos, gettext("Unable to create handle for communicating" " with idmapd(8) (%s)\n"), idmap_stat2string(stat)); idmap_get_destroy(ghandle); goto cleanup; } /* Schedule the request: */ if (type_to == TYPE_UID) { stat = idmap_getext_uidbysid(ghandle, nm->sidprefix, nm->rid, flag, &uid, &info, &map_stat); } else if (type_to == TYPE_GID) { stat = idmap_getext_gidbysid(ghandle, nm->sidprefix, nm->rid, flag, &gid, &info, &map_stat); } else if (type_to == TYPE_PID) { stat = idmap_getext_pidbysid(ghandle, nm->sidprefix, nm->rid, flag, &nm->pid, &nm->is_user, &info, &map_stat); } else if (type_from == TYPE_UID) { stat = idmap_getext_sidbyuid(ghandle, nm->pid, flag, &nm->sidprefix, &nm->rid, &info, &map_stat); } else if (type_from == TYPE_GID) { stat = idmap_getext_sidbygid(ghandle, (gid_t)nm->pid, flag, &nm->sidprefix, &nm->rid, &info, &map_stat); } else { /* This can never happen: */ print_error(pos, gettext("Internal error in show.\n")); exit(1); } if (stat < 0) { print_error(pos, gettext("Request for %.3s not sent (%s)\n"), argv[0], idmap_stat2string(stat)); idmap_get_destroy(ghandle); goto cleanup; } /* Send the batch to idmapd and obtain results: */ stat = idmap_get_mappings(ghandle); if (stat < 0) { print_error(pos, gettext("Mappings not obtained because of" " RPC problem (%s)\n"), idmap_stat2string(stat)); idmap_get_destroy(ghandle); goto cleanup; } /* Destroy the batch handle: */ idmap_get_destroy(ghandle); if (type_to == TYPE_UID) nm->pid = uid; else if (type_to == TYPE_GID) nm->pid = (uid_t)gid; } /* * If there was -c flag, we do output whatever we can even in * the case of error: */ if (map_stat < 0 && flag & IDMAP_REQ_FLG_NO_NEW_ID_ALLOC) goto errormsg; /* * idmapd returns fallback uid/gid in case of errors. However * it uses special sentinel value i.e 4294967295 (or -1) to * indicate that falbback pid is not available either. In such * case idmap(8) should not display the mapping because there * is no fallback mapping. */ if ((type_to == TYPE_UID || type_to == TYPE_GID || type_to == TYPE_PID) && nm->pid == UNDEFINED_UID) goto errormsg; if (nm2type(nm, type_from, &fromname) < 0) goto errormsg; if (nm2type(nm, type_to, &toname) < 0) { if (!(flag & IDMAP_REQ_FLG_NO_NEW_ID_ALLOC)) (void) printf("%s -> %s:%u\n", fromname, type_to & IS_GROUP ? ID_GID : ID_UID, UID_NOBODY); free(fromname); } else { (void) printf("%s -> %s\n", fromname, toname); free(fromname); free(toname); } errormsg: if (map_stat < 0) { print_error(pos, gettext("Error:\t%s\n"), idmap_stat2string(map_stat)); print_error_info(&info); } else { print_info(&info); } idmap_info_free(&info); cleanup: if (nm != NULL) name_mapping_fini(nm); return (stat < 0 || map_stat < 0 ? -1 : 0); } static int flags2cred(flag_t *f, char **user, char **passwd, cmd_pos_t *pos) { *user = NULL; *passwd = NULL; if (f[D_FLAG] == NULL) return (0); /* GSSAPI authentification => OK */ *user = strdup(f[D_FLAG]); if (*user == NULL) { print_error(pos, "%s.\n", strerror(ENOMEM)); return (-1); } /* Password: */ if (f[j_FLAG] != NULL) { char line[MAX_INPUT_LINE_SZ]; int i; FILE *file = fopen(f[j_FLAG], "r"); if (file == NULL) { print_error(pos, gettext("Failed to open password file \"%s\": (%s)" ".\n"), f[j_FLAG], strerror(errno)); goto fail; } /* The password is the fist line, we ignore the rest: */ if (fgets(line, MAX_INPUT_LINE_SZ, file) == NULL) { print_error(pos, gettext("The password file \"%s\" is empty.\n"), f[j_FLAG]); (void) fclose(file); goto fail; } if (fclose(file) != 0) { print_error(pos, gettext("Unable to close the password file \"%s\"" ".\n"), f[j_FLAG], strerror(errno)); goto fail; } /* Trim the eol: */ for (i = strlen(line) - 1; i >= 0 && (line[i] == '\r' || line[i] == '\n'); i--) line[i] = '\0'; *passwd = strdup(line); if (*passwd == NULL) { print_error(pos, "%s.\n", strerror(ENOMEM)); goto fail; } } else if (!batch_mode) { /* If in the interactive mode, read the terminal input: */ char *it = getpassphrase("Enter password:"); if (it == NULL) { print_error(NULL, gettext("Failed to get password (%s).\n"), strerror(errno)); goto fail; } *passwd = strdup(it); (void) memset(it, 0, strlen(it)); if (*passwd == NULL) { print_error(pos, "%s.\n", strerror(ENOMEM)); goto fail; } } else { print_error(pos, gettext("No password given.\n")); goto fail; } return (0); fail: if (*passwd != NULL) { (void) memset(*passwd, 0, strlen(*passwd)); free(*passwd); *passwd = NULL; } free(*user); return (-1); } static int do_set_namemap(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { idmap_stat stat; name_mapping_t *nm; int is_first_win; char *user; char *passwd; if (argc < 2) { print_error(pos, gettext("Not enough arguments: two names needed for a " "namemap.\n")); return (-1); } else if (argc > 2) { print_error(pos, gettext("Too many arguments: two names needed for a " "namemap.\n")); return (-1); } nm = args2nm(&is_first_win, argc, argv, pos); if (nm == NULL) return (-1); if (flags2cred(f, &user, &passwd, pos) < 0) return (-1); nm->direction = is_first_win ? IDMAP_DIRECTION_W2U : IDMAP_DIRECTION_U2W; if (init_nm_command(user, passwd, f[a_FLAG], nm->windomain, nm->direction, pos) < 0) return (-1); stat = idmap_set_namemap(namemaps.handle, nm->winname, nm->unixname, nm->is_user, nm->is_wuser, nm->direction); if (stat != IDMAP_SUCCESS) { print_error(pos, gettext("Failed to set namemap (%s).\n"), idmap_stat2string(stat)); } if (passwd != NULL) { (void) memset(passwd, 0, strlen(passwd)); free(passwd); } free(user); fini_nm_command(); name_mapping_fini(nm); return (stat != IDMAP_SUCCESS ? -1 : 0); } static int do_unset_namemap(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { idmap_stat stat; name_mapping_t *nm; int is_first_win; char *user; char *passwd; if (argc < 1) { print_error(pos, gettext("Not enough arguments: a name needed to unset a " "namemap.\n")); return (-1); } else if (argc > 2) { print_error(pos, gettext("Too many arguments: Only target name and type is " "needed to unset namemap.\n")); return (-1); } nm = args2nm(&is_first_win, 1, argv, pos); if (nm == NULL) return (-1); if (flags2cred(f, &user, &passwd, pos) < 0) return (-1); nm->direction = is_first_win ? IDMAP_DIRECTION_W2U : IDMAP_DIRECTION_U2W; if (argc > 1 && !is_first_win) { print_error(pos, gettext("Target type \"%s\" is redundant.\n"), argv[1]); stat = IDMAP_ERR_ARG; goto cleanup; } else if (argc > 1) { switch (string2type(argv[1], pos)) { case TYPE_INVALID: name_mapping_fini(nm); return (-1); case TYPE_UU: nm->is_user = IDMAP_YES; break; case TYPE_UG: nm->is_user = IDMAP_NO; break; default: print_error(pos, gettext("Invalid target type \"%s\": here the " "possible target type is unixuser or " "unixgroup.\n"), argv[1]); stat = IDMAP_ERR_ARG; goto cleanup; } } if (init_nm_command(user, passwd, f[a_FLAG], nm->windomain, nm->direction, pos) < 0) return (-1); stat = idmap_unset_namemap(namemaps.handle, nm->winname, nm->unixname, nm->is_user, nm->is_wuser, nm->direction); if (stat != IDMAP_SUCCESS) { print_error(pos, gettext("Failed to unset namemap (%s).\n"), idmap_stat2string(stat)); } cleanup: if (passwd != NULL) { (void) memset(passwd, 0, strlen(passwd)); free(passwd); } free(user); fini_nm_command(); name_mapping_fini(nm); return (stat == IDMAP_SUCCESS ? 0 : -1); } static int /* LINTED E_FUNC_ARG_UNUSED */ do_get_namemap(flag_t *f, int argc, char **argv, cmd_pos_t *pos) { idmap_stat stat; name_mapping_t *nm; int is_first_win; int is_source_ad; char *winname = NULL; char *unixname = NULL; char *unixuser = NULL; char *unixgroup = NULL; if (argc < 1) { print_error(pos, gettext("Not enough arguments: a name needed to get a " "namemap.\n")); return (-1); } else if (argc > 1) { print_error(pos, gettext("Too many arguments: just one name needed to get " "a namemap.\n")); return (-1); } nm = args2nm(&is_first_win, argc, argv, pos); if (nm == NULL) return (-1); nm->direction = is_first_win ? IDMAP_DIRECTION_W2U : IDMAP_DIRECTION_U2W; /* nm->is_user is IDMAP_UNKNOWN for IDMAP_DIRECTION_W2U */ if (nm->is_user == IDMAP_YES) { unixuser = strdup(nm->unixname); if (unixuser == NULL) { print_error(pos, "%s.\n", strerror(ENOMEM)); goto cleanup; } } else if (nm->is_user == IDMAP_NO) { unixgroup = strdup(nm->unixname); if (unixgroup == NULL) { print_error(pos, "%s.\n", strerror(ENOMEM)); goto cleanup; } } if (init_nm_command(NULL, NULL, NULL, nm->windomain, nm->direction, pos) < 0) return (-1); stat = idmap_get_namemap(namemaps.handle, &is_source_ad, &nm->winname, &nm->windomain, &nm->is_wuser, &unixuser, &unixgroup); if (stat != IDMAP_SUCCESS) { print_error(pos, gettext("Failed to get namemap info (%s).\n"), idmap_stat2string(stat)); goto cleanup; } if (nm2winqn(nm, &winname) < 0) goto cleanup; switch (is_source_ad) { case IDMAP_YES: if (unixuser == NULL && unixgroup == NULL) (void) printf(gettext("\t\tNo namemap found in AD.\n")); else { (void) printf(gettext("AD namemaps for %s\n"), winname); if (unixuser != NULL) (void) printf(gettext("\t\t->\t%s:%s\n"), ID_UNIXUSER, unixuser); if (unixgroup != NULL) (void) printf(gettext("\t\t->\t%s:%s\n"), ID_UNIXGROUP, unixgroup); } break; case IDMAP_NO: if (nm2unixname(nm, &unixname) < 0) goto cleanup; if (nm->winname == NULL) (void) printf(gettext("\t\tNo namemap found in " "native LDAP.\n")); else { (void) printf(gettext("Native LDAP namemap for %s\n"), unixname); (void) printf(gettext("\t\t->\t%s\n"), winname); } break; default: /* * This can never happen; the error must be recognized in * args2nm */ print_error(pos, gettext("Internal error: unknown source of namemaps.\n")); } cleanup: fini_nm_command(); name_mapping_fini(nm); if (winname != NULL) free(winname); if (unixuser != NULL) free(unixuser); if (unixgroup != NULL) free(unixgroup); return (stat == IDMAP_SUCCESS ? 0 : -1); } /* printflike */ static void idmap_cli_logger(int pri, const char *format, ...) { va_list args; if (pri == LOG_DEBUG) return; va_start(args, format); (void) vfprintf(stderr, format, args); (void) fprintf(stderr, "\n"); va_end(args); } /* main function. Returns 1 for error, 0 otherwise */ int main(int argc, char *argv[]) { int rc; /* set locale and domain for internationalization */ (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* Redirect logging */ idmap_set_logger(idmap_cli_logger); adutils_set_logger(idmap_cli_logger); /* idmap_engine determines the batch_mode: */ rc = engine_init(sizeof (commands) / sizeof (cmd_ops_t), commands, argc - 1, argv + 1, &batch_mode); if (rc < 0) { (void) engine_fini(); if (rc == IDMAP_ENG_ERROR_SILENT) help(); return (1); } udt_used = 0; if (batch_mode) { if (init_udt_batch() < 0) return (1); } rc = run_engine(argc - 1, argv + 1); if (batch_mode) { batch_mode = 0; if (fini_udt_command(rc == 0 ? 1 : 0, NULL)) rc = -1; fini_nm_command(); } (void) engine_fini(); return (rc == 0 ? 0 : 1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #ifdef WITH_LIBTECLA #include #endif #include "idmap_engine.h" /* The maximal line length. Longer lines may not be parsed OK. */ #define MAX_CMD_LINE_SZ 1023 #ifdef WITH_LIBTECLA #define MAX_HISTORY_LINES 1023 static GetLine * gl_h; /* LINTED E_STATIC_UNUSED */ #endif /* Array for arguments of the actuall command */ static char ** my_argv; /* Allocated size for my_argv */ static int my_argv_size = 16; /* Actuall length of my_argv */ static int my_argc; /* Array for subcommands */ static cmd_ops_t *my_comv; /* my_comc length */ static int my_comc; /* Input filename specified by the -f flag */ static char *my_filename; /* * Batch mode means reading file, stdin or libtecla input. Shell input is * a non-batch mode. */ static int my_batch_mode; /* Array of all possible flags */ static flag_t flags[FLAG_ALPHABET_SIZE]; /* getopt variables */ extern char *optarg; extern int optind, optopt, opterr; /* Fill the flags array: */ static int options_parse(int argc, char *argv[], const char *options) { int c; optind = 1; while ((c = getopt(argc, argv, options)) != EOF) { switch (c) { case '?': return (-1); case ':': /* This is relevant only if options starts with ':': */ (void) fprintf(stderr, gettext("Option %s: missing parameter\n"), argv[optind - 1]); return (-1); default: if (optarg == NULL) flags[c] = FLAG_SET; else flags[c] = optarg; } } return (optind); } /* Unset all flags */ static void options_clean() { (void) memset(flags, 0, FLAG_ALPHABET_SIZE * sizeof (flag_t)); } /* determine which subcommand is argv[0] and execute its handler */ static int run_command(int argc, char **argv, cmd_pos_t *pos) { int i; if (argc == 0) { if (my_batch_mode) return (0); return (-1); } for (i = 0; i < my_comc; i++) { int optind; int rc; if (strcmp(my_comv[i].cmd, argv[0]) != 0) continue; /* We found it. Now execute the handler. */ options_clean(); optind = options_parse(argc, argv, my_comv[i].options); if (optind < 0) { return (-1); } rc = my_comv[i].p_do_func(flags, argc - optind, argv + optind, pos); return (rc); } (void) fprintf(stderr, gettext("Unknown command %s\n"), argv[0]); return (-1); } /* * Read another parameter from "from", up to a space char (unless it * is quoted). Duplicate it to "to". Remove quotation, if any. */ static int get_param(char **to, const char *from) { int to_i, from_i; char c; int last_slash = 0; /* Preceded by a slash? */ int in_string = 0; /* Inside quites? */ int is_param = 0; size_t buf_size = 20; /* initial length of the buffer. */ char *buf = (char *)malloc(buf_size * sizeof (char)); from_i = 0; while (isspace(from[from_i])) from_i++; for (to_i = 0; '\0' != from[from_i]; from_i++) { c = from[from_i]; if (to_i >= buf_size - 1) { buf_size *= 2; buf = (char *)realloc(buf, buf_size * sizeof (char)); } if (c == '"' && !last_slash) { in_string = !in_string; is_param = 1; continue; } else if (c == '\\' && !last_slash) { last_slash = 1; continue; } else if (!last_slash && !in_string && isspace(c)) { break; } buf[to_i++] = from[from_i]; last_slash = 0; } if (to_i == 0 && !is_param) { free(buf); *to = NULL; return (0); } buf[to_i] = '\0'; *to = buf; if (in_string) return (-1); return (from_i); } /* * Split a string to a parameter array and append it to the specified position * of the array */ static int line2array(const char *line) { const char *cur; char *param; int len; for (cur = line; len = get_param(¶m, cur); cur += len) { if (my_argc >= my_argv_size) { my_argv_size *= 2; my_argv = (char **)realloc(my_argv, my_argv_size * sizeof (char *)); } my_argv[my_argc] = param; ++my_argc; /* quotation not closed */ if (len < 0) return (-1); } return (0); } /* Clean all aruments from my_argv. Don't deallocate my_argv itself. */ static void my_argv_clean() { int i; for (i = 0; i < my_argc; i++) { free(my_argv[i]); my_argv[i] = NULL; } my_argc = 0; } #ifdef WITH_LIBTECLA /* This is libtecla tab completion. */ static CPL_MATCH_FN(command_complete) { /* * WordCompletion *cpl; const char *line; int word_end are * passed from the CPL_MATCH_FN macro. */ int i; char *prefix; int prefix_l; /* We go on even if quotation is not closed */ (void) line2array(line); /* Beginning of the line: */ if (my_argc == 0) { for (i = 0; i < my_comc; i++) (void) cpl_add_completion(cpl, line, word_end, word_end, my_comv[i].cmd, "", " "); goto cleanup; } /* Is there something to complete? */ if (isspace(line[word_end - 1])) goto cleanup; prefix = my_argv[my_argc - 1]; prefix_l = strlen(prefix); /* Subcommand name: */ if (my_argc == 1) { for (i = 0; i < my_comc; i++) if (strncmp(prefix, my_comv[i].cmd, prefix_l) == 0) (void) cpl_add_completion(cpl, line, word_end - prefix_l, word_end, my_comv[i].cmd + prefix_l, "", " "); goto cleanup; } /* Long options: */ if (prefix[0] == '-' && prefix [1] == '-') { char *options2 = NULL; char *paren; char *thesis; int i; for (i = 0; i < my_comc; i++) if (0 == strcmp(my_comv[i].cmd, my_argv[0])) { options2 = strdup(my_comv[i].options); break; } /* No such subcommand, or not enough memory: */ if (options2 == NULL) goto cleanup; for (paren = strchr(options2, '('); paren && ((thesis = strchr(paren + 1, ')')) != NULL); paren = strchr(thesis + 1, '(')) { /* Short option or thesis must precede, so this is safe: */ *(paren - 1) = '-'; *paren = '-'; *thesis = '\0'; if (strncmp(paren - 1, prefix, prefix_l) == 0) { (void) cpl_add_completion(cpl, line, word_end - prefix_l, word_end, paren - 1 + prefix_l, "", " "); } } free(options2); /* "--" is a valid completion */ if (prefix_l == 2) { (void) cpl_add_completion(cpl, line, word_end - 2, word_end, "", "", " "); } } cleanup: my_argv_clean(); return (0); } /* libtecla subshell: */ static int interactive_interp() { int rc = 0; char *prompt; const char *line; (void) sigset(SIGINT, SIG_IGN); gl_h = new_GetLine(MAX_CMD_LINE_SZ, MAX_HISTORY_LINES); if (gl_h == NULL) { (void) fprintf(stderr, gettext("Error reading terminal: %s.\n"), gl_error_message(gl_h, NULL, 0)); return (-1); } (void) gl_customize_completion(gl_h, NULL, command_complete); for (;;) { new_line: my_argv_clean(); prompt = "> "; continue_line: line = gl_get_line(gl_h, prompt, NULL, -1); if (line == NULL) { switch (gl_return_status(gl_h)) { case GLR_SIGNAL: gl_abandon_line(gl_h); goto new_line; case GLR_EOF: (void) line2array("exit"); break; case GLR_ERROR: (void) fprintf(stderr, gettext("Error reading terminal: %s.\n"), gl_error_message(gl_h, NULL, 0)); rc = -1; goto end_of_input; default: (void) fprintf(stderr, "Internal error.\n"); exit(1); } } else { if (line2array(line) < 0) { (void) fprintf(stderr, gettext("Quotation not closed\n")); goto new_line; } if (my_argc == 0) { goto new_line; } if (strcmp(my_argv[my_argc-1], "\n") == 0) { my_argc--; free(my_argv[my_argc]); (void) strcpy(prompt, "> "); goto continue_line; } } rc = run_command(my_argc, my_argv, NULL); if (strcmp(my_argv[0], "exit") == 0 && rc == 0) { break; } } end_of_input: gl_h = del_GetLine(gl_h); my_argv_clean(); return (rc); } #endif /* Interpretation of a source file given by "name" */ static int source_interp(const char *name) { FILE *f; int is_stdin; int rc = -1; char line[MAX_CMD_LINE_SZ]; cmd_pos_t pos; if (name == NULL || strcmp("-", name) == 0) { f = stdin; is_stdin = 1; } else { is_stdin = 0; f = fopen(name, "r"); if (f == NULL) { perror(name); return (-1); } } pos.linenum = 0; pos.line = line; while (fgets(line, MAX_CMD_LINE_SZ, f)) { pos.linenum ++; if (line2array(line) < 0) { (void) fprintf(stderr, gettext("Quotation not closed\n")); my_argv_clean(); continue; } /* We do not wan't "\n" as the last parameter */ if (my_argc != 0 && strcmp(my_argv[my_argc-1], "\n") == 0) { my_argc--; free(my_argv[my_argc]); continue; } if (my_argc != 0 && strcmp(my_argv[0], "exit") == 0) { rc = 0; my_argv_clean(); break; } rc = run_command(my_argc, my_argv, &pos); my_argv_clean(); } if (my_argc > 0) { (void) fprintf(stderr, gettext("Line continuation missing\n")); rc = 1; my_argv_clean(); } if (!is_stdin) (void) fclose(f); return (rc); } /* * Initialize the engine. * comc, comv is the array of subcommands and its length, * argc, argv are arguments to main to be scanned for -f filename and * the length og the array, * is_batch_mode passes to the caller the information if the * batch mode is on. * * Return values: * 0: ... OK * IDMAP_ENG_ERROR: error and message printed already * IDMAP_ENG_ERROR_SILENT: error and message needs to be printed * */ int engine_init(int comc, cmd_ops_t *comv, int argc, char **argv, int *is_batch_mode) { int c; my_comc = comc; my_comv = comv; my_argc = 0; my_argv = (char **)calloc(my_argv_size, sizeof (char *)); if (argc < 1) { my_filename = NULL; if (isatty(fileno(stdin))) { #ifdef WITH_LIBTECLA my_batch_mode = 1; #else my_batch_mode = 0; return (IDMAP_ENG_ERROR_SILENT); #endif } else my_batch_mode = 1; goto the_end; } my_batch_mode = 0; optind = 0; while ((c = getopt(argc, argv, "f:(command-file)")) != EOF) { switch (c) { case '?': return (IDMAP_ENG_ERROR); case 'f': my_batch_mode = 1; my_filename = optarg; break; default: (void) fprintf(stderr, "Internal error.\n"); exit(1); } } the_end: if (is_batch_mode != NULL) *is_batch_mode = my_batch_mode; return (0); } /* finitialize the engine */ int engine_fini() { my_argv_clean(); free(my_argv); return (0); } /* * Interpret the subcommands defined by the arguments, unless * my_batch_mode was set on in egnine_init. */ int run_engine(int argc, char **argv) { int rc = -1; if (my_batch_mode) { #ifdef WITH_LIBTECLA if (isatty(fileno(stdin))) rc = interactive_interp(); else #endif rc = source_interp(my_filename); goto cleanup; } rc = run_command(argc, argv, NULL); cleanup: return (rc); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _IDMAP_ENGINE_H #define _IDMAP_ENGINE_H /* Debug macros */ #define DPTR(a) printf("%s::%d %s = %p\n", __FILE__, __LINE__, #a, a); #define DSTRING(a) printf("%s::%d %s = \"%s\"\n", __FILE__, __LINE__, #a, \ a ? a : "(null)"); #define DINT(a) printf("%s::%d %s = %d\n", __FILE__, __LINE__, #a, a); #define DHEX(a) printf("%s::%d %s = %X\n", __FILE__, __LINE__, #a, a); #ifdef __cplusplus extern "C" { #endif typedef char *flag_t; #define FLAG_SET (char *)1 #define FLAG_ALPHABET_SIZE 255 #define IDMAP_ENG_OK 0 #define IDMAP_ENG_ERROR -1 #define IDMAP_ENG_ERROR_SILENT -2 typedef struct cmd_pos { int linenum; /* line number */ char *line; /* line content */ } cmd_pos_t; typedef struct cmd_ops { const char *cmd; /* the subcommand */ const char *options; /* getopt string for the subcommand params */ int (*p_do_func)(flag_t *f, int argc, char **argv, cmd_pos_t *pos); /* handle */ } cmd_ops_t; extern int engine_init(int comc, cmd_ops_t *comv, int argc, char **argv, int *is_batch_mode); extern int engine_fini(); extern int run_engine(int argc, char **argv); #ifdef __cplusplus } #endif #endif /* _IDMAP_ENGINE_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include "addisc.h" #include "libadutils.h" #include "idmap_priv.h" #include "ns_sldap.h" #include "namemaps.h" /* From adutils.c: */ /* A single DS */ struct idmap_nm_handle { LDAP *ad; /* LDAP connection */ /* LDAP DS info */ char *ad_host; int ad_port; /* hardwired to SASL GSSAPI only for now */ char *saslmech; unsigned saslflags; char *windomain; char *ad_unixuser_attr; char *ad_unixgroup_attr; char *nldap_winname_attr; char *default_domain; bool_t is_nldap; bool_t is_ad; int direction; ns_cred_t nsc; }; /* PRINTFLIKE1 */ static void namemap_log(char *fmt, ...) { va_list va; va_start(va, fmt); (void) vfprintf(stderr, fmt, va); va_end(va); (void) fprintf(stderr, "\n"); } static idmap_stat string2auth(const char *from, ns_auth_t *na) { if (from == NULL) { na->type = NS_LDAP_AUTH_SASL; na->tlstype = NS_LDAP_TLS_SASL; na->saslmech = NS_LDAP_SASL_GSSAPI; na->saslopt = NS_LDAP_SASLOPT_PRIV | NS_LDAP_SASLOPT_INT; return (IDMAP_SUCCESS); } if (strcasecmp(from, "simple") == 0) { na->type = NS_LDAP_AUTH_SIMPLE; na->tlstype = NS_LDAP_TLS_NONE; na->saslmech = NS_LDAP_SASL_NONE; na->saslopt = NS_LDAP_SASLOPT_NONE; } else if (strcasecmp(from, "sasl/CRAM-MD5") == 0) { na->type = NS_LDAP_AUTH_SASL; na->tlstype = NS_LDAP_TLS_SASL; na->saslmech = NS_LDAP_SASL_CRAM_MD5; na->saslopt = NS_LDAP_SASLOPT_NONE; } else if (strcasecmp(from, "sasl/DIGEST-MD5") == 0) { na->type = NS_LDAP_AUTH_SASL; na->tlstype = NS_LDAP_TLS_SASL; na->saslmech = NS_LDAP_SASL_DIGEST_MD5; na->saslopt = NS_LDAP_SASLOPT_NONE; } else if (strcasecmp(from, "sasl/GSSAPI") == 0) { na->type = NS_LDAP_AUTH_SASL; na->tlstype = NS_LDAP_TLS_SASL; na->saslmech = NS_LDAP_SASL_GSSAPI; na->saslopt = NS_LDAP_SASLOPT_PRIV | NS_LDAP_SASLOPT_INT; } else if (strcasecmp(from, "tls:simple") == 0) { na->type = NS_LDAP_AUTH_TLS; na->tlstype = NS_LDAP_TLS_SIMPLE; na->saslmech = NS_LDAP_SASL_NONE; na->saslopt = NS_LDAP_SASLOPT_NONE; } else if (strcasecmp(from, "tls:sasl/CRAM-MD5") == 0) { na->type = NS_LDAP_AUTH_TLS; na->tlstype = NS_LDAP_TLS_SASL; na->saslmech = NS_LDAP_SASL_CRAM_MD5; na->saslopt = NS_LDAP_SASLOPT_NONE; } else if (strcasecmp(from, "tls:sasl/DIGEST-MD5") == 0) { na->type = NS_LDAP_AUTH_TLS; na->tlstype = NS_LDAP_TLS_SASL; na->saslmech = NS_LDAP_SASL_DIGEST_MD5; na->saslopt = NS_LDAP_SASLOPT_NONE; } else { namemap_log( gettext("Invalid authentication method \"%s\" specified\n"), from); return (IDMAP_ERR_ARG); } return (IDMAP_SUCCESS); } static idmap_stat strings2cred(ns_cred_t *nsc, char *user, char *passwd, char *auth) { idmap_stat rc; (void) memset(nsc, 0, sizeof (ns_cred_t)); if ((rc = string2auth(auth, &nsc->auth)) != IDMAP_SUCCESS) return (rc); if (user != NULL) { nsc->cred.unix_cred.userID = strdup(user); if (nsc->cred.unix_cred.userID == NULL) return (IDMAP_ERR_MEMORY); } if (passwd != NULL) { nsc->cred.unix_cred.passwd = strdup(passwd); if (nsc->cred.unix_cred.passwd == NULL) { free(nsc->cred.unix_cred.userID); return (IDMAP_ERR_MEMORY); } } return (IDMAP_SUCCESS); } /*ARGSUSED*/ static int idmap_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts) { sasl_interact_t *interact; if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE) return (LDAP_PARAM_ERROR); /* There should be no extra arguemnts for SASL/GSSAPI authentication */ for (interact = prompts; interact->id != SASL_CB_LIST_END; interact++) { interact->result = NULL; interact->len = 0; } return (LDAP_SUCCESS); } static idmap_stat idmap_open_ad_conn(idmap_nm_handle_t *adh) { int zero = 0; int timeoutms = 30 * 1000; int ldversion, ldap_rc; idmap_stat rc = IDMAP_SUCCESS; /* Open and bind an LDAP connection */ adh->ad = ldap_init(adh->ad_host, adh->ad_port); if (adh->ad == NULL) { namemap_log( gettext("ldap_init() to server %s port %d failed. (%s)"), CHECK_NULL(adh->ad_host), adh->ad_port, strerror(errno)); rc = IDMAP_ERR_INTERNAL; goto out; } ldversion = LDAP_VERSION3; (void) ldap_set_option(adh->ad, LDAP_OPT_PROTOCOL_VERSION, &ldversion); (void) ldap_set_option(adh->ad, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); (void) ldap_set_option(adh->ad, LDAP_OPT_TIMELIMIT, &zero); (void) ldap_set_option(adh->ad, LDAP_OPT_SIZELIMIT, &zero); (void) ldap_set_option(adh->ad, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms); (void) ldap_set_option(adh->ad, LDAP_OPT_RESTART, LDAP_OPT_ON); ldap_rc = ldap_sasl_interactive_bind_s(adh->ad, "" /* binddn */, adh->saslmech, NULL, NULL, adh->saslflags, &idmap_saslcallback, NULL); if (ldap_rc != LDAP_SUCCESS) { (void) ldap_unbind(adh->ad); adh->ad = NULL; namemap_log( gettext("ldap_sasl_interactive_bind_s() to server " "%s port %d failed. (%s)"), CHECK_NULL(adh->ad_host), adh->ad_port, ldap_err2string(ldap_rc)); rc = IDMAP_ERR_INTERNAL; } out: return (rc); } static idmap_stat idmap_init_nldap(idmap_nm_handle_t *p) { /* * For now, there is nothing to initialize in nldap. This is just to * make it future-proof, especially standalone libsldap-proof */ p->is_nldap = TRUE; return (0); } static idmap_stat idmap_init_ad(idmap_nm_handle_t *p) { idmap_stat rc = IDMAP_SUCCESS; ad_disc_ds_t *dc = NULL; ad_disc_t ad_ctx; ad_ctx = ad_disc_init(); if (ad_ctx == NULL) { namemap_log( gettext("AD autodiscovery initialization failed")); return (IDMAP_ERR_INTERNAL); } ad_disc_refresh(ad_ctx); /* Based on the supplied or default domain, find the proper AD: */ if (ad_disc_set_DomainName(ad_ctx, p->windomain)) { rc = IDMAP_ERR_INTERNAL; namemap_log( gettext("Setting a domain name \"%s\" for autodiscovery" " failed, most likely not enough memory"), p->windomain); goto cleanup; } dc = ad_disc_get_DomainController(ad_ctx, AD_DISC_GLOBAL, NULL); if (dc == NULL) { rc = IDMAP_ERR_ARG; namemap_log( gettext("A domain controller for the " "domain \"%s\" not found."), p->windomain); goto cleanup; } p->ad_port = dc->port; p->ad_host = strdup(dc->host); if (p->ad_host == NULL) { rc = IDMAP_ERR_MEMORY; goto cleanup; } p->saslflags = LDAP_SASL_INTERACTIVE; p->saslmech = strdup("GSSAPI"); if (p->saslmech == NULL) { rc = IDMAP_ERR_MEMORY; goto cleanup; } rc = idmap_open_ad_conn(p); if (rc != IDMAP_SUCCESS) goto cleanup; p->is_ad = TRUE; cleanup: ad_disc_fini(ad_ctx); free(dc); return (rc); } void idmap_fini_namemaps(idmap_nm_handle_t *p) { if (p == NULL) return; if (p->ad_unixgroup_attr != NULL) free(p->ad_unixgroup_attr); if (p->ad_unixuser_attr != NULL) free(p->ad_unixuser_attr); if (p->nldap_winname_attr) free(p->nldap_winname_attr); if (p->windomain != NULL) free(p->windomain); if (p->default_domain != NULL) free(p->default_domain); if (p->saslmech != NULL) free(p->saslmech); if (p->ad_host != NULL) free(p->ad_host); if (p->nsc.cred.unix_cred.userID != NULL) { free(p->nsc.cred.unix_cred.userID); } if (p->nsc.cred.unix_cred.passwd != NULL) { /* No archeology: */ (void) memset(p->nsc.cred.unix_cred.passwd, 0, strlen(p->nsc.cred.unix_cred.passwd)); free(p->nsc.cred.unix_cred.passwd); } if (p->ad) (void) ldap_unbind(p->ad); free(p); } idmap_stat idmap_init_namemaps(idmap_nm_handle_t **adh, char *user, char *passwd, char *auth, char *windomain, int direction) { idmap_stat rc; idmap_nm_handle_t *p; p = (idmap_nm_handle_t *)calloc(1, sizeof (idmap_nm_handle_t)); if (p == NULL) return (IDMAP_ERR_MEMORY); rc = idmap_get_prop_str(PROP_DEFAULT_DOMAIN, &p->default_domain); if (rc != IDMAP_SUCCESS) { namemap_log( gettext("Error obtaining default domain from idmapd (%s)"), idmap_stat2string(rc)); goto cleanup; } rc = idmap_get_prop_str(PROP_AD_UNIXUSER_ATTR, &p->ad_unixuser_attr); if (rc != IDMAP_SUCCESS) { namemap_log( gettext("Error obtaining AD unixuser attribute (%s)"), idmap_stat2string(rc)); goto cleanup; } rc = idmap_get_prop_str(PROP_AD_UNIXGROUP_ATTR, &p->ad_unixgroup_attr); if (rc != IDMAP_SUCCESS) { namemap_log( gettext("Error obtaining AD unixgroup attribute (%s)"), idmap_stat2string(rc)); goto cleanup; } rc = idmap_get_prop_str(PROP_NLDAP_WINNAME_ATTR, &p->nldap_winname_attr); if (rc != IDMAP_SUCCESS) { namemap_log( gettext("Error obtaining AD unixgroup attribute (%s)"), idmap_stat2string(rc)); goto cleanup; } if (windomain != NULL) { p->windomain = strdup(windomain); if (p->windomain == NULL) { rc = IDMAP_ERR_MEMORY; goto cleanup; } } else if (!EMPTY_STRING(p->default_domain)) { p->windomain = strdup(p->default_domain); if (p->windomain == NULL) { rc = IDMAP_ERR_MEMORY; goto cleanup; } } else if (direction == IDMAP_DIRECTION_W2U) { namemap_log( gettext("Windows domain not given and idmapd daemon" " didn't provide a default one")); rc = IDMAP_ERR_ARG; goto cleanup; } p->direction = direction; if ((p->ad_unixuser_attr != NULL || p->ad_unixgroup_attr != NULL) && direction != IDMAP_DIRECTION_U2W) { rc = idmap_init_ad(p); if (rc != IDMAP_SUCCESS) { goto cleanup; } } if (p->nldap_winname_attr != NULL && direction != IDMAP_DIRECTION_W2U) { rc = idmap_init_nldap(p); if (rc != IDMAP_SUCCESS) { goto cleanup; } rc = strings2cred(&p->nsc, user, passwd, auth); if (rc != IDMAP_SUCCESS) { goto cleanup; } } cleanup: if (rc == IDMAP_SUCCESS) { *adh = p; return (IDMAP_SUCCESS); } /* There was an error: */ idmap_fini_namemaps(*adh); return (rc); } static char * dns2dn(const char *dns, const char *prefix) { int num_lvl = 1; char *buf; const char *it, *new_it; for (it = dns; it != NULL; it = strchr(it, '.')) { it ++; num_lvl ++; } buf = (char *)malloc(strlen(prefix) + strlen(dns) + 4 * num_lvl); (void) strcpy(buf, prefix); it = dns; for (;;) { new_it = strchr(it, '.'); (void) strcat(buf, "DC="); if (new_it == NULL) { (void) strcat(buf, it); break; } else { (void) strncat(buf, it, new_it - it); (void) strcat(buf, ","); } it = new_it + 1; } return (buf); } static idmap_stat extract_attribute(idmap_nm_handle_t *p, LDAPMessage *entry, char *name, char **value) { char **values = NULL; idmap_stat rc = IDMAP_SUCCESS; /* No value means it is not requested */ if (value == NULL) return (IDMAP_SUCCESS); values = ldap_get_values(p->ad, entry, name); if (values == NULL || values[0] == NULL) *value = NULL; else { *value = strdup(values[0]); if (*value == NULL) rc = IDMAP_ERR_MEMORY; } ldap_value_free(values); return (rc); } /* Split winname to its name and domain part */ static idmap_stat split_fqwn(char *fqwn, char **name, char **domain) { char *at; *name = NULL; *domain = NULL; at = strchr(fqwn, '@'); if (at == NULL) { at = strchr(fqwn, '\\'); } if (at == NULL) { /* There is no domain - leave domain NULL */ *name = strdup(fqwn); if (*name == NULL) goto errout; return (IDMAP_SUCCESS); } *domain = strdup(at+1); if (*domain == NULL) goto errout; *name = (char *)malloc(at - fqwn + 1); if (*name == NULL) goto errout; (void) strlcpy(*name, fqwn, at - fqwn + 1); if (*at == '\\') { char *it = *name; *name = *domain; *domain = it; } return (IDMAP_SUCCESS); errout: free(*name); *name = NULL; free(*domain); *domain = NULL; return (IDMAP_ERR_MEMORY); } static idmap_stat unixname2dn(idmap_nm_handle_t *p, char *unixname, int is_user, char **dn, char **winname, char **windomain) { idmap_stat rc = IDMAP_SUCCESS; int rc_ns; char filter[255]; static const char *attribs[3]; ns_ldap_result_t *res; ns_ldap_error_t *errorp = NULL; char **attrs; attribs[0] = p->nldap_winname_attr; attribs[1] = "dn"; attribs[2] = NULL; (void) snprintf(filter, sizeof (filter), is_user ? "uid=%s" : "cn=%s", unixname); rc_ns = __ns_ldap_list(is_user ? "passwd" : "group", filter, NULL, attribs, NULL, 0, &res, &errorp, NULL, NULL); if (rc_ns == NS_LDAP_NOTFOUND) { namemap_log(is_user ? gettext("User %s not found.") : gettext("Group %s not found."), unixname); return (IDMAP_ERR_NOTFOUND); } else if (rc_ns != NS_LDAP_SUCCESS) { char *msg = "Cause unidentified"; if (errorp != NULL) { (void) __ns_ldap_err2str(errorp->status, &msg); } namemap_log(gettext("Ldap list failed (%s)."), msg); return (IDMAP_ERR_ARG); } if (res == NULL) { namemap_log(gettext("User %s not found"), unixname); return (IDMAP_ERR_ARG); } if (winname != NULL && windomain != NULL) { attrs = __ns_ldap_getAttr(&res->entry[0], p->nldap_winname_attr); if (attrs != NULL && attrs[0] != NULL) { rc = split_fqwn(attrs[0], winname, windomain); } else { *winname = *windomain = NULL; } } if (dn != NULL) { attrs = __ns_ldap_getAttr(&res->entry[0], "dn"); if (attrs == NULL || attrs[0] == NULL) { namemap_log(gettext("dn for %s not found"), unixname); return (IDMAP_ERR_ARG); } *dn = strdup(attrs[0]); } return (rc); } #define FILTER "(sAMAccountName=%s)" /* Puts the values of attributes to unixuser and unixgroup, unless NULL */ static idmap_stat winname2dn(idmap_nm_handle_t *p, char *winname, int *is_wuser, char **dn, char **unixuser, char **unixgroup) { idmap_stat rc = IDMAP_SUCCESS; char *base; char *filter; int flen; char *attribs[4]; int i; LDAPMessage *results = NULL; LDAPMessage *entry; int ldap_rc; /* Query: */ base = dns2dn(p->windomain, ""); if (base == NULL) { return (IDMAP_ERR_MEMORY); } i = 0; attribs[i++] = "objectClass"; if (unixuser != NULL) attribs[i++] = p->ad_unixuser_attr; if (unixgroup != NULL) attribs[i++] = p->ad_unixgroup_attr; attribs[i] = NULL; flen = snprintf(NULL, 0, FILTER, winname) + 1; if ((filter = (char *)malloc(flen)) == NULL) { free(base); return (IDMAP_ERR_MEMORY); } (void) snprintf(filter, flen, FILTER, winname); ldap_rc = ldap_search_s(p->ad, base, LDAP_SCOPE_SUBTREE, filter, attribs, 0, &results); free(base); free(filter); if (ldap_rc != LDAP_SUCCESS) { namemap_log( gettext("Ldap query to server %s port %d failed. (%s)"), p->ad_host, p->ad_port, ldap_err2string(ldap_rc)); (void) ldap_msgfree(results); return (IDMAP_ERR_OTHER); } for (entry = ldap_first_entry(p->ad, results), *dn = NULL; entry != NULL; entry = ldap_next_entry(p->ad, entry)) { char **values = NULL; int i = 0; values = ldap_get_values(p->ad, entry, "objectClass"); if (values == NULL) { (void) ldap_msgfree(results); return (IDMAP_ERR_MEMORY); } for (i = 0; i < ldap_count_values(values); i++) { /* * is_wuser can be IDMAP_UNKNOWN, in that case we accept * both User/Group */ if (*is_wuser != IDMAP_NO && strcasecmp(values[i], "User") == 0 || *is_wuser != IDMAP_YES && strcasecmp(values[i], "Group") == 0) { *dn = ldap_get_dn(p->ad, entry); if (*dn == NULL) { ldap_value_free(values); (void) ldap_msgfree(results); return (IDMAP_ERR_MEMORY); } *is_wuser = strcasecmp(values[i], "User") == 0 ? IDMAP_YES : IDMAP_NO; break; } } ldap_value_free(values); if (*dn != NULL) break; } if (*dn == NULL) { namemap_log( *is_wuser == IDMAP_YES ? gettext("User %s@%s not found") : *is_wuser == IDMAP_NO ? gettext("Group %s@%s not found") : gettext("%s@%s not found"), winname, p->windomain); return (IDMAP_ERR_NOTFOUND); } if (unixuser != NULL) rc = extract_attribute(p, entry, p->ad_unixuser_attr, unixuser); if (rc == IDMAP_SUCCESS && unixgroup != NULL) rc = extract_attribute(p, entry, p->ad_unixgroup_attr, unixgroup); (void) ldap_msgfree(results); return (rc); } /* set the given attribute to the given value. If value is NULL, unset it */ static idmap_stat idmap_ad_set(idmap_nm_handle_t *p, char *dn, char *attr, char *value) { idmap_stat rc = IDMAP_SUCCESS; int ldap_rc; char *new_values[2] = {NULL, NULL}; LDAPMod *mods[2] = {NULL, NULL}; mods[0] = (LDAPMod *)calloc(1, sizeof (LDAPMod)); mods[0]->mod_type = strdup(attr); if (value != NULL) { mods[0]->mod_op = LDAP_MOD_REPLACE; new_values[0] = strdup(value); mods[0]->mod_values = new_values; } else { mods[0]->mod_op = LDAP_MOD_DELETE; mods[0]->mod_values = NULL; } ldap_rc = ldap_modify_s(p->ad, dn, mods); if (ldap_rc != LDAP_SUCCESS) { namemap_log( gettext("Ldap modify of %s, attribute %s failed. (%s)"), dn, attr, ldap_err2string(ldap_rc)); rc = IDMAP_ERR_INTERNAL; } ldap_mods_free(mods, 0); return (rc); } /* * This function takes the p argument just for the beauty of the symmetry * with idmap_ad_set (and for future enhancements). */ static idmap_stat /* LINTED E_FUNC_ARG_UNUSED */ idmap_nldap_set(idmap_nm_handle_t *p, ns_cred_t *nsc, char *dn, char *attr, char *value, bool_t is_new, int is_user) { int ldaprc; ns_ldap_error_t *errorp = NULL; ns_ldap_attr_t *attrs[2]; attrs[0] = (ns_ldap_attr_t *)malloc(sizeof (ns_ldap_attr_t)); if (attrs == NULL) return (IDMAP_ERR_MEMORY); attrs[0]->attrname = attr; if (value != NULL) { char **newattr = (char **)calloc(2, sizeof (char *)); if (newattr == NULL) { free(attrs[0]); return (IDMAP_ERR_MEMORY); } newattr[0] = value; newattr[1] = NULL; attrs[0]->attrvalue = newattr; attrs[0]->value_count = 1; } else { attrs[0]->attrvalue = NULL; attrs[0]->value_count = 0; } attrs[1] = NULL; if (value == NULL) { ldaprc = __ns_ldap_delAttr( is_user == IDMAP_YES ? "passwd": "group", dn, (const ns_ldap_attr_t * const *)attrs, nsc, 0, &errorp); } else if (is_new) ldaprc = __ns_ldap_addAttr( is_user == IDMAP_YES ? "passwd": "group", dn, (const ns_ldap_attr_t * const *)attrs, nsc, 0, &errorp); else ldaprc = __ns_ldap_repAttr( is_user == IDMAP_YES ? "passwd": "group", dn, (const ns_ldap_attr_t * const *)attrs, nsc, 0, &errorp); if (ldaprc != NS_LDAP_SUCCESS) { char *msg = "Cause unidentified"; if (errorp != NULL) { (void) __ns_ldap_err2str(errorp->status, &msg); } namemap_log( gettext("__ns_ldap_addAttr/rep/delAttr failed (%s)"), msg); return (IDMAP_ERR_ARG); } return (IDMAP_SUCCESS); } idmap_stat idmap_set_namemap(idmap_nm_handle_t *p, char *winname, char *unixname, int is_user, int is_wuser, int direction) { idmap_stat rc = IDMAP_SUCCESS; char *dn = NULL; char *oldwinname = NULL; char *oldwindomain = NULL; if (direction == IDMAP_DIRECTION_W2U) { if (!p->is_ad) { rc = IDMAP_ERR_ARG; namemap_log( gettext("AD namemaps aren't set up.")); goto cleanup; } rc = winname2dn(p, winname, &is_wuser, &dn, NULL, NULL); if (rc != IDMAP_SUCCESS) goto cleanup; rc = idmap_ad_set(p, dn, is_user ? p->ad_unixuser_attr : p->ad_unixgroup_attr, unixname); if (rc != IDMAP_SUCCESS) goto cleanup; } if (direction == IDMAP_DIRECTION_U2W) { char *fullname; if (!p->is_nldap) { rc = IDMAP_ERR_ARG; namemap_log( gettext("Native ldap namemaps aren't set up.")); goto cleanup; } rc = unixname2dn(p, unixname, is_user, &dn, &oldwinname, &oldwindomain); if (rc != IDMAP_SUCCESS) goto cleanup; if (p->windomain == NULL) { fullname = strdup(winname); if (fullname == NULL) { rc = IDMAP_ERR_MEMORY; goto cleanup; } } else { fullname = malloc(strlen(winname) + strlen(p->windomain) + 2); if (fullname == NULL) { rc = IDMAP_ERR_MEMORY; goto cleanup; } (void) snprintf(fullname, strlen(winname) + strlen(p->windomain) + 2, "%s\\%s", p->windomain, winname); } rc = idmap_nldap_set(p, &p->nsc, dn, p->nldap_winname_attr, fullname, oldwinname == NULL ? TRUE : FALSE, is_user); free(fullname); free(oldwindomain); free(oldwinname); if (rc != IDMAP_SUCCESS) goto cleanup; } cleanup: if (dn != NULL) free(dn); if (oldwindomain != NULL) free(oldwindomain); if (oldwinname != NULL) free(oldwinname); return (rc); } idmap_stat idmap_unset_namemap(idmap_nm_handle_t *p, char *winname, char *unixname, int is_user, int is_wuser, int direction) { idmap_stat rc = IDMAP_SUCCESS; char *dn = NULL; char *oldwinname = NULL; char *oldwindomain = NULL; if (direction == IDMAP_DIRECTION_W2U) { if (!p->is_ad) { rc = IDMAP_ERR_ARG; namemap_log( gettext("AD namemaps aren't set up.")); goto cleanup; } rc = winname2dn(p, winname, &is_wuser, &dn, NULL, NULL); if (rc != IDMAP_SUCCESS) goto cleanup; rc = idmap_ad_set(p, dn, is_user ? p->ad_unixuser_attr : p->ad_unixgroup_attr, unixname); if (rc != IDMAP_SUCCESS) goto cleanup; } else { /* direction == IDMAP_DIRECTION_U2W */ if (!p->is_nldap) { rc = IDMAP_ERR_ARG; namemap_log( gettext("Native ldap namemaps aren't set up.")); goto cleanup; } rc = unixname2dn(p, unixname, is_user, &dn, NULL, NULL); if (rc != IDMAP_SUCCESS) goto cleanup; rc = idmap_nldap_set(p, &p->nsc, dn, p->nldap_winname_attr, NULL, TRUE, is_user); if (rc != IDMAP_SUCCESS) goto cleanup; } cleanup: if (oldwindomain != NULL) free(oldwindomain); if (oldwinname != NULL) free(oldwinname); if (dn != NULL) free(dn); return (rc); } idmap_stat idmap_get_namemap(idmap_nm_handle_t *p, int *is_source_ad, char **winname, char **windomain, int *is_wuser, char **unixuser, char **unixgroup) { idmap_stat rc = IDMAP_SUCCESS; char *dn = NULL; *is_source_ad = IDMAP_UNKNOWN; if (*winname != NULL) { *is_source_ad = IDMAP_YES; if (p->is_ad == FALSE) { rc = IDMAP_ERR_ARG; namemap_log( gettext("AD namemaps are not active.")); goto cleanup; /* In future maybe resolve winname and try nldap? */ } rc = winname2dn(p, *winname, is_wuser, &dn, unixuser, unixgroup); if (rc != IDMAP_SUCCESS) { namemap_log( gettext("Winname %s@%s not found in AD."), *winname, p->windomain); } } else if (*unixuser != NULL || *unixgroup != NULL) { char *unixname; int is_user; *is_source_ad = IDMAP_NO; if (p->is_nldap == FALSE) { rc = IDMAP_ERR_ARG; namemap_log( gettext("Native ldap namemaps aren't active.")); goto cleanup; /* In future maybe resolve unixname and try AD? */ } if (*unixuser != NULL) { is_user = IDMAP_YES; unixname = *unixuser; } else if (*unixgroup != NULL) { is_user = IDMAP_NO; unixname = *unixgroup; } rc = unixname2dn(p, unixname, is_user, NULL, winname, windomain); if (rc != IDMAP_SUCCESS) { namemap_log( gettext("%s %s not found in native ldap."), is_user == IDMAP_YES ? "UNIX user" : "UNIX group", unixname); goto cleanup; } } else { rc = IDMAP_ERR_ARG; goto cleanup; } cleanup: return (rc); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Additional API for Identity Mapping Service */ #ifndef NAMEMAPS_H #define NAMEMAPS_H #ifdef __cplusplus extern "C" { #endif /* * Directory based name map API */ typedef struct idmap_nm_handle idmap_nm_handle_t; /* Set namemap */ extern idmap_stat idmap_set_namemap(idmap_nm_handle_t *, char *, char *, int, int, int); /* Unset namemap */ extern idmap_stat idmap_unset_namemap(idmap_nm_handle_t *, char *, char *, int, int, int); extern idmap_stat idmap_get_namemap(idmap_nm_handle_t *p, int *, char **, char **, int *, char **, char **); extern void idmap_fini_namemaps(idmap_nm_handle_t *); extern idmap_stat idmap_init_namemaps(idmap_nm_handle_t **, char *, char *, char *, char *, int); #ifdef __cplusplus } #endif #endif /* NAMEMAPS_H */ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2014 Nexenta Systems, Inc. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2022-2023 RackTop Systems, Inc. PROG = idmapd MANIFEST = idmap.xml SERVEROBJS = \ adspriv_impl.o \ directory_provider_builtin.o \ directory_provider_nsswitch.o \ directory_provider_ad.o \ directory_server.o \ adutils.o \ dbutils.o \ idmap_config.o \ idmapd.o \ init.o \ idmap_lsa.o \ krb5_lookup.o \ nldaputils.o \ server.o \ wksids.o GENOBJS = \ adspriv_srv.o \ rpc_svc.o SERVERSRCS = $(SERVEROBJS:%.o=%.c) GENSRCS = $(GENOBJS:%.o=%.c) OBJS = $(SERVEROBJS) $(GENOBJS) SRCS = $(SERVERSRCS) POFILES = $(OBJS:%.o=%.po) all : TARGET = all install : TARGET = install clean : TARGET = clean clobber : TARGET = clobber include ../../Makefile.cmd include ../../Makefile.ctf CERRWARN += -Wno-type-limits CERRWARN += -Wno-switch CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off TEXT_DOMAIN = SUNW_OST_OSLIB XGETTEXT = $(GNUXGETTEXT) XGETFLAGS = --foreign-user --strict -n -E --width=72 \ --omit-header --keyword=directoryError:2 \ --language=C --force-po CSTD = $(CSTD_GNU99) POFILE = $(PROG)_all.po RPC_MSGOUT_OPT = -DRPC_MSGOUT=idmap_rpc_msgout ROOTMANIFESTDIR = $(ROOTSVCSYSTEM) $(ROOTMANIFEST) : FILEMODE= 444 RPCSVC= ../../../uts/common/rpcsvc ADS_CMN=../../../lib/libads/common INCS += -I. -I../../../lib/libidmap/common \ -I../../../lib/libsldap/common \ -I../../../lib/libadutils/common \ -I $(ADS_CMN) \ -I$(RPCSVC) \ -I../../../lib/smbsrv/libsmb/common # Should not have to do this, but the Kerberos includes are a mess. INCS += -I $(ROOT)/usr/include/kerberosv5 $(OBJS) : CPPFLAGS += $(INCS) -D_REENTRANT $(POFILE) : CPPFLAGS += $(INCS) CFLAGS += $(CCVERBOSE) $(NOT_RELEASE_BUILD)CPPFLAGS += -DIDMAPD_DEBUG LDLIBS += \ -lsqlite-sys \ -lsecdb \ -lsocket \ -lnsl \ -lidmap \ -lscf \ -lsldap \ -lldap \ -luuid \ -ladutils \ -lads \ -lumem \ -lnvpair \ -luutil \ -L $(ROOT)/usr/lib/smbsrv \ -lsmb rpc_svc.o : CFLAGS += $(RPC_MSGOUT_OPT) LDFLAGS += -R /usr/lib/smbsrv DIRMODE = 0755 FILEMODE = 0555 .KEEP_STATE: .PARALLEL: $(OBJS) all: $(PROG) $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) # Hammerhead: some .po files may not exist (pre-generated rpcgen stubs # don't have translatable strings). Only cat files that exist. $(POFILE): $(RM) $(POFILE) for f in $(POFILES); do [ -f $$f ] && cat $$f || true; done > $(POFILE) install: all $(ROOTLIBPROG) $(ROOTMANIFEST) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) @# Hammerhead: do not delete pre-generated $(GENSRCS) RPCGENFLAGS = -CMN adspriv_srv.o : adspriv_srv.c # Hammerhead: pre-generated (rpcgen + GNU cpp truncation bug) # adspriv_srv.c and rpc_svc.c are now committed source files. rpc_svc.o : rpc_svc.c include ../../Makefile.targ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "idmapd.h" #include "libadutils.h" #include "dsgetdc.h" #include "ads_priv.h" void adspriv_program_1(struct svc_req *, register SVCXPRT *); SVCXPRT *dcl_xprt = NULL; void init_dc_locator(void) { int connmaxrec = 32 * 1024; dcl_xprt = svc_door_create(adspriv_program_1, ADSPRIV_PROGRAM, ADSPRIV_V1, connmaxrec); if (dcl_xprt == NULL) { syslog(LOG_ERR, "unable to create door RPC service"); return; } if (!svc_control(dcl_xprt, SVCSET_CONNMAXREC, &connmaxrec)) { syslog(LOG_ERR, "unable to limit RPC request size"); } } void fini_dc_locator(void) { if (dcl_xprt != NULL) svc_destroy(dcl_xprt); } /* * Functions called by the (generated) adspriv_srv.c */ /* ARGSUSED */ bool_t adspriv_null_1_svc(void *result, struct svc_req *rqstp) { return (TRUE); } /* ARGSUSED */ bool_t adspriv_forcerediscovery_1_svc( DsForceRediscoveryArgs args, int *res, struct svc_req *sreq) { /* Ignoring args for now. */ idmap_cfg_force_rediscovery(); *res = 0; return (TRUE); } /* ARGSUSED */ bool_t adspriv_getdcname_1_svc( DsGetDcNameArgs args, DsGetDcNameRes *res, struct svc_req *sreq) { uuid_t uuid; adspriv_dcinfo *dci; idmap_pg_config_t *pgcfg; ad_disc_ds_t *ds; char *s; /* Init */ (void) memset(res, 0, sizeof (*res)); res->status = 0; dci = &res->DsGetDcNameRes_u.res0; if (args.Flags & DS_FORCE_REDISCOVERY) idmap_cfg_force_rediscovery(); /* * We normally should wait if discovery is running. * Sort of mis-using the background flag as a way to * skip the wait, until we really do background disc. */ if ((args.Flags & DS_BACKGROUND_ONLY) == 0) { timespec_t tv = { 15, 0 }; int rc = 0; int waited = 0; (void) mutex_lock(&_idmapdstate.addisc_lk); if (_idmapdstate.addisc_st != 0) idmapdlog(LOG_DEBUG, "getdcname wait begin"); while (_idmapdstate.addisc_st != 0) { waited++; rc = cond_reltimedwait(&_idmapdstate.addisc_cv, &_idmapdstate.addisc_lk, &tv); if (rc == ETIME) break; } (void) mutex_unlock(&_idmapdstate.addisc_lk); if (rc == ETIME) { /* Caller will replace this with DC not found. */ idmapdlog(LOG_ERR, "getdcname timeout"); res->status = NT_STATUS_CANT_WAIT; return (TRUE); } if (waited) { idmapdlog(LOG_DEBUG, "getdcname wait done"); } } RDLOCK_CONFIG(); pgcfg = &_idmapdstate.cfg->pgcfg; if (pgcfg->domain_name == NULL) { res->status = NT_STATUS_INVALID_SERVER_STATE; goto out; } if (args.DomainName != NULL && args.DomainName[0] != '\0' && 0 != strcasecmp(args.DomainName, pgcfg->domain_name)) { /* * They asked for a specific domain not our primary, * which is not supported (and not needed). */ res->status = NT_STATUS_NO_SUCH_DOMAIN; goto out; } if ((ds = pgcfg->domain_controller) == NULL) { res->status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; goto out; } dci->dci_DcName = strdup(ds->host); if (dci->dci_DcName == NULL) { res->status = NT_STATUS_NO_MEMORY; goto out; } dci->dci_DcAddr = calloc(1, INET6_ADDRSTRLEN); if (dci->dci_DcAddr == NULL) { res->status = NT_STATUS_NO_MEMORY; goto out; } if (ad_disc_getnameinfo(dci->dci_DcAddr, INET6_ADDRSTRLEN, &ds->addr) == 0) dci->dci_AddrType = DS_INET_ADDRESS; if ((s = pgcfg->domain_guid) != NULL && 0 == uuid_parse(s, uuid)) { (void) memcpy(dci->dci_guid, uuid, sizeof (uuid)); } if ((s = pgcfg->domain_name) != NULL) { dci->dci_DomainName = strdup(s); if (dci->dci_DomainName == NULL) { res->status = NT_STATUS_NO_MEMORY; goto out; } } if ((s = pgcfg->forest_name) != NULL) { dci->dci_DnsForestName = strdup(s); if (dci->dci_DnsForestName == NULL) { res->status = NT_STATUS_NO_MEMORY; goto out; } } dci->dci_Flags = ds->flags; dci->dci_DcSiteName = strdup(ds->site); if (dci->dci_DcSiteName == NULL) { res->status = NT_STATUS_NO_MEMORY; goto out; } if ((s = pgcfg->site_name) != NULL) { dci->dci_ClientSiteName = strdup(s); if (dci->dci_ClientSiteName == NULL) { res->status = NT_STATUS_NO_MEMORY; goto out; } } /* Address in binary form too. */ (void) memcpy(&dci->dci_sockaddr, &ds->addr, ADSPRIV_SOCKADDR_LEN); out: UNLOCK_CONFIG(); if (res->status != 0) { /* Caller will free only if status == 0 */ xdr_free(xdr_adspriv_dcinfo, (char *)dci); } return (TRUE); } /* ARGSUSED */ int adspriv_program_1_freeresult(SVCXPRT *xprt, xdrproc_t fun, caddr_t res) { xdr_free(fun, res); return (TRUE); } /* * Please do not edit this file. * It was generated using rpcgen. */ #include "ads_priv.h" #include #include /* getenv, exit */ #include #include /* for pmap_unset */ #include /* strcmp */ #include /* setsid */ #include #include #include #include /* rlimit */ #include #ifndef SIG_PF #define SIG_PF void(*)(int) #endif #ifdef DEBUG #define RPC_SVC_FG #endif #define _RPCSVC_CLOSEDOWN 120 /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* * from ads_priv.x * Active Directory Services (ADS) Private interface between * libads and the ADS deamon. (RPC over doors) */ /* * Server side stubs for the ADS API */ extern int _rpcpmstart; /* Started by a port monitor ? */ /* States a server can be in wrt request */ #define _IDLE 0 #define _SERVED 1 /* LINTED static unused if no main */ static int _rpcsvcstate = _IDLE; /* Set when a request is serviced */ static int _rpcsvccount = 0; /* Number of requests being serviced */ extern mutex_t _svcstate_lock; /* lock for _rpcsvcstate, _rpcsvccount */ #if defined(RPC_MSGOUT) extern void RPC_MSGOUT(const char *, ...); #else /* defined(RPC_MSGOUT) */ static void RPC_MSGOUT(const char *fmt, char *msg) { #ifdef RPC_SVC_FG if (_rpcpmstart) syslog(LOG_ERR, fmt, msg); else { (void) fprintf(stderr, fmt, msg); (void) putc('\n', stderr); } #else syslog(LOG_ERR, fmt, msg); #endif } #endif /* defined(RPC_MSGOUT) */ /* ARGSUSED */ int _adspriv_null_1( void *argp, void *result, struct svc_req *rqstp) { return (adspriv_null_1_svc(result, rqstp)); } int _adspriv_forcerediscovery_1( DsForceRediscoveryArgs *argp, int *result, struct svc_req *rqstp) { return (adspriv_forcerediscovery_1_svc(*argp, result, rqstp)); } int _adspriv_getdcname_1( DsGetDcNameArgs *argp, DsGetDcNameRes *result, struct svc_req *rqstp) { return (adspriv_getdcname_1_svc(*argp, result, rqstp)); } void adspriv_program_1(struct svc_req *rqstp, register SVCXPRT *transp) { union { DsForceRediscoveryArgs adspriv_forcerediscovery_1_arg; DsGetDcNameArgs adspriv_getdcname_1_arg; } argument; union { int adspriv_forcerediscovery_1_res; DsGetDcNameRes adspriv_getdcname_1_res; } result; bool_t retval; xdrproc_t _xdr_argument, _xdr_result; bool_t (*local)(char *, void *, struct svc_req *); (void) mutex_lock(&_svcstate_lock); _rpcsvccount++; (void) mutex_unlock(&_svcstate_lock); switch (rqstp->rq_proc) { case ADSPRIV_NULL: _xdr_argument = (xdrproc_t) xdr_void; _xdr_result = (xdrproc_t) xdr_void; local = (bool_t (*) (char *, void *, struct svc_req *)) _adspriv_null_1; break; case ADSPRIV_ForceRediscovery: _xdr_argument = (xdrproc_t) xdr_DsForceRediscoveryArgs; _xdr_result = (xdrproc_t) xdr_int; local = (bool_t (*) (char *, void *, struct svc_req *)) _adspriv_forcerediscovery_1; break; case ADSPRIV_GetDcName: _xdr_argument = (xdrproc_t) xdr_DsGetDcNameArgs; _xdr_result = (xdrproc_t) xdr_DsGetDcNameRes; local = (bool_t (*) (char *, void *, struct svc_req *)) _adspriv_getdcname_1; break; default: svcerr_noproc(transp); (void) mutex_lock(&_svcstate_lock); _rpcsvccount--; _rpcsvcstate = _SERVED; (void) mutex_unlock(&_svcstate_lock); return; /* CSTYLED */ } (void) memset((char *)&argument, 0, sizeof (argument)); if (!svc_getargs(transp, _xdr_argument, (caddr_t)&argument)) { svcerr_decode(transp); (void) mutex_lock(&_svcstate_lock); _rpcsvccount--; _rpcsvcstate = _SERVED; (void) mutex_unlock(&_svcstate_lock); return; /* CSTYLED */ } retval = (bool_t)(*local)((char *)&argument, (void *)&result, rqstp); if (_xdr_result && retval > 0 && !svc_sendreply(transp, _xdr_result, (char *)&result)) { svcerr_systemerr(transp); } if (!svc_freeargs(transp, _xdr_argument, (caddr_t)&argument)) { RPC_MSGOUT("%s", "unable to free arguments"); exit(1); } if (_xdr_result != NULL) { if (!adspriv_program_1_freeresult(transp, _xdr_result, (caddr_t)&result)) RPC_MSGOUT("%s", "unable to free results"); } (void) mutex_lock(&_svcstate_lock); _rpcsvccount--; _rpcsvcstate = _SERVED; (void) mutex_unlock(&_svcstate_lock); return; /* CSTYLED */ } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* * Processes name2sid & sid2name batched lookups for a given user or * computer from an AD Directory server using GSSAPI authentication */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libadutils.h" #include "nldaputils.h" #include "idmapd.h" /* Attribute names and filter format strings */ #define SAN "sAMAccountName" #define OBJSID "objectSid" #define OBJCLASS "objectClass" #define UIDNUMBER "uidNumber" #define GIDNUMBER "gidNumber" #define UIDNUMBERFILTER "(&(objectclass=user)(uidNumber=%u))" #define GIDNUMBERFILTER "(&(objectclass=group)(gidNumber=%u))" #define SANFILTER "(sAMAccountName=%s)" #define OBJSIDFILTER "(|(objectSid=%s)(sIDHistory=%s))" void idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc, int qid, void *argp); /* * A place to put the results of a batched (async) query * * There is one of these for every query added to a batch object * (idmap_query_state, see below). */ typedef struct idmap_q { /* * data used for validating search result entries for name->SID * lookups */ char *ecanonname; /* expected canon name */ char *edomain; /* expected domain name */ idmap_id_type esidtype; /* expected SID type */ /* results */ char **canonname; /* actual canon name */ char **domain; /* name of domain of object */ char **sid; /* stringified SID */ rid_t *rid; /* RID */ idmap_id_type *sid_type; /* user or group SID? */ char **unixname; /* unixname for name mapping */ char **dn; /* DN of entry */ char **attr; /* Attr for name mapping */ char **value; /* value for name mapping */ posix_id_t *pid; /* Posix ID found via IDMU */ idmap_retcode *rc; adutils_rc ad_rc; adutils_result_t *result; /* * The LDAP search entry result is placed here to be processed * when the search done result is received. */ LDAPMessage *search_res; /* The LDAP search result */ } idmap_q_t; /* Batch context structure; typedef is in header file */ struct idmap_query_state { adutils_query_state_t *qs; int qsize; /* Queue size */ uint32_t qcount; /* Number of queued requests */ const char *ad_unixuser_attr; const char *ad_unixgroup_attr; int directory_based_mapping; /* enum */ char *default_domain; idmap_q_t queries[1]; /* array of query results */ }; static pthread_t reaperid = 0; /* * Keep connection management simple for now, extend or replace later * with updated libsldap code. */ #define ADREAPERSLEEP 60 /* * Idle connection reaping side of connection management * * Every minute wake up and look for connections that have been idle for * five minutes or more and close them. */ /*ARGSUSED*/ static void * adreaper(void *arg __unused) { timespec_t ts; ts.tv_sec = ADREAPERSLEEP; ts.tv_nsec = 0; for (;;) { /* * nanosleep(3C) is thread-safe (no SIGALRM) and more * portable than usleep(3C) */ (void) nanosleep(&ts, NULL); adutils_reap_idle_connections(); } return (NULL); } /* * Take ad_host_config_t information, create a ad_host_t, * populate it and add it to the list of hosts. */ int idmap_add_ds(adutils_ad_t *ad, const char *host, int port) { int ret = -1; if (adutils_add_ds(ad, host, port) == ADUTILS_SUCCESS) ret = 0; /* Start reaper if it doesn't exist */ if (ret == 0 && reaperid == 0) (void) pthread_create(&reaperid, NULL, adreaper, NULL); return (ret); } static idmap_retcode map_adrc2idmaprc(adutils_rc adrc) { switch (adrc) { case ADUTILS_SUCCESS: return (IDMAP_SUCCESS); case ADUTILS_ERR_NOTFOUND: return (IDMAP_ERR_NOTFOUND); case ADUTILS_ERR_MEMORY: return (IDMAP_ERR_MEMORY); case ADUTILS_ERR_DOMAIN: return (IDMAP_ERR_DOMAIN); case ADUTILS_ERR_OTHER: return (IDMAP_ERR_OTHER); case ADUTILS_ERR_RETRIABLE_NET_ERR: return (IDMAP_ERR_RETRIABLE_NET_ERR); default: return (IDMAP_ERR_INTERNAL); } /* NOTREACHED */ } idmap_retcode idmap_lookup_batch_start(adutils_ad_t *ad, int nqueries, int directory_based_mapping, const char *default_domain, idmap_query_state_t **state) { idmap_query_state_t *new_state; adutils_rc rc; *state = NULL; assert(ad != NULL); new_state = calloc(1, sizeof (idmap_query_state_t) + (nqueries - 1) * sizeof (idmap_q_t)); if (new_state == NULL) return (IDMAP_ERR_MEMORY); if ((rc = adutils_lookup_batch_start(ad, nqueries, idmap_ldap_res_search_cb, new_state, &new_state->qs)) != ADUTILS_SUCCESS) { idmap_lookup_release_batch(&new_state); return (map_adrc2idmaprc(rc)); } new_state->default_domain = strdup(default_domain); if (new_state->default_domain == NULL) { idmap_lookup_release_batch(&new_state); return (IDMAP_ERR_MEMORY); } new_state->directory_based_mapping = directory_based_mapping; new_state->qsize = nqueries; *state = new_state; return (IDMAP_SUCCESS); } /* * Set unixuser_attr and unixgroup_attr for AD-based name mapping */ void idmap_lookup_batch_set_unixattr(idmap_query_state_t *state, const char *unixuser_attr, const char *unixgroup_attr) { state->ad_unixuser_attr = unixuser_attr; state->ad_unixgroup_attr = unixgroup_attr; } /* * Take parsed attribute values from a search result entry and check if * it is the result that was desired and, if so, set the result fields * of the given idmap_q_t. * * Except for dn and attr, all strings are consumed, either by transferring * them over into the request results (where the caller will eventually free * them) or by freeing them here. Note that this aligns with the "const" * declarations below. */ static void idmap_setqresults( idmap_q_t *q, char *san, const char *dn, const char *attr, char *value, char *sid, rid_t rid, int sid_type, char *unixname, posix_id_t pid) { char *domain; int err1; assert(dn != NULL); if ((domain = adutils_dn2dns(dn)) == NULL) goto out; if (q->ecanonname != NULL && san != NULL) { /* Check that this is the canonname that we were looking for */ if (u8_strcmp(q->ecanonname, san, 0, U8_STRCMP_CI_LOWER, /* no normalization, for now */ U8_UNICODE_LATEST, &err1) != 0 || err1 != 0) goto out; } if (q->edomain != NULL) { /* Check that this is the domain that we were looking for */ if (!domain_eq(q->edomain, domain)) goto out; } /* Copy the DN and attr and value */ if (q->dn != NULL) *q->dn = strdup(dn); if (q->attr != NULL && attr != NULL) *q->attr = strdup(attr); if (q->value != NULL && value != NULL) { *q->value = value; value = NULL; } /* Set results */ if (q->sid) { *q->sid = sid; sid = NULL; } if (q->rid) *q->rid = rid; if (q->sid_type) *q->sid_type = sid_type; if (q->unixname) { *q->unixname = unixname; unixname = NULL; } if (q->domain != NULL) { *q->domain = domain; domain = NULL; } if (q->canonname != NULL) { /* * The caller may be replacing the given winname by its * canonical name and therefore free any old name before * overwriting the field by the canonical name. */ free(*q->canonname); *q->canonname = san; san = NULL; } if (q->pid != NULL && pid != IDMAP_SENTINEL_PID) { *q->pid = pid; } q->ad_rc = ADUTILS_SUCCESS; out: /* Free unused attribute values */ free(san); free(sid); free(domain); free(unixname); free(value); } #define BVAL_CASEEQ(bv, str) \ (((*(bv))->bv_len == (sizeof (str) - 1)) && \ strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0) /* * Extract the class of the result entry. Returns 1 on success, 0 on * failure. */ static int idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type) { BerValue **cbval; *sid_type = IDMAP_SID; if (bvalues == NULL) return (0); /* * We consider Computer to be a subclass of User, so we can just * ignore Computer entries and pay attention to the accompanying * User entries. */ for (cbval = bvalues; *cbval != NULL; cbval++) { if (BVAL_CASEEQ(cbval, "group")) { *sid_type = IDMAP_GSID; break; } else if (BVAL_CASEEQ(cbval, "user")) { *sid_type = IDMAP_USID; break; } /* * "else if (*sid_type = IDMAP_USID)" then this is a * new sub-class of user -- what to do with it?? */ } return (1); } /* * Handle a given search result entry */ static void idmap_extract_object(idmap_query_state_t *state, idmap_q_t *q, LDAPMessage *res, LDAP *ld) { BerValue **bvalues; const char *attr = NULL; char *value = NULL; char *unix_name = NULL; char *dn; char *san = NULL; char *sid = NULL; rid_t rid = 0; int sid_type; int ok; posix_id_t pid = IDMAP_SENTINEL_PID; assert(q->rc != NULL); assert(q->domain == NULL || *q->domain == NULL); if ((dn = ldap_get_dn(ld, res)) == NULL) return; bvalues = ldap_get_values_len(ld, res, OBJCLASS); if (bvalues == NULL) { /* * Didn't find objectclass. Something's wrong with our * AD data. */ idmapdlog(LOG_ERR, "%s has no %s", dn, OBJCLASS); goto out; } ok = idmap_bv_objclass2sidtype(bvalues, &sid_type); ldap_value_free_len(bvalues); if (!ok) { /* * Didn't understand objectclass. Something's wrong with our * AD data. */ idmapdlog(LOG_ERR, "%s has unexpected %s", dn, OBJCLASS); goto out; } if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU && q->pid != NULL) { if (sid_type == IDMAP_USID) attr = UIDNUMBER; else if (sid_type == IDMAP_GSID) attr = GIDNUMBER; if (attr != NULL) { bvalues = ldap_get_values_len(ld, res, attr); if (bvalues != NULL) { value = adutils_bv_str(bvalues[0]); if (!adutils_bv_uint(bvalues[0], &pid)) { idmapdlog(LOG_ERR, "%s has Invalid %s value \"%s\"", dn, attr, value); } ldap_value_free_len(bvalues); } } } if (state->directory_based_mapping == DIRECTORY_MAPPING_NAME && q->unixname != NULL) { /* * If the caller has requested unixname then determine the * AD attribute name that will have the unixname, and retrieve * its value. */ idmap_id_type esidtype; /* * Determine the target type. * * If the caller specified one, use that. Otherwise, give the * same type that as we found for the Windows user. */ esidtype = q->esidtype; if (esidtype == IDMAP_SID) esidtype = sid_type; if (esidtype == IDMAP_USID) attr = state->ad_unixuser_attr; else if (esidtype == IDMAP_GSID) attr = state->ad_unixgroup_attr; if (attr != NULL) { bvalues = ldap_get_values_len(ld, res, attr); if (bvalues != NULL) { unix_name = adutils_bv_str(bvalues[0]); ldap_value_free_len(bvalues); value = strdup(unix_name); } } } bvalues = ldap_get_values_len(ld, res, SAN); if (bvalues != NULL) { san = adutils_bv_str(bvalues[0]); ldap_value_free_len(bvalues); } if (q->sid != NULL) { bvalues = ldap_get_values_len(ld, res, OBJSID); if (bvalues != NULL) { sid = adutils_bv_objsid2sidstr(bvalues[0], &rid); ldap_value_free_len(bvalues); } } idmap_setqresults(q, san, dn, attr, value, sid, rid, sid_type, unix_name, pid); out: ldap_memfree(dn); } void idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc, int qid, void *argp) { idmap_query_state_t *state = (idmap_query_state_t *)argp; idmap_q_t *q = &(state->queries[qid]); switch (rc) { case LDAP_RES_SEARCH_RESULT: if (q->search_res != NULL) { idmap_extract_object(state, q, q->search_res, ld); (void) ldap_msgfree(q->search_res); q->search_res = NULL; } else q->ad_rc = ADUTILS_ERR_NOTFOUND; break; case LDAP_RES_SEARCH_ENTRY: if (q->search_res == NULL) { q->search_res = *res; *res = NULL; } break; default: break; } } static void idmap_cleanup_batch(idmap_query_state_t *batch) { int i; for (i = 0; i < batch->qcount; i++) { if (batch->queries[i].ecanonname != NULL) free(batch->queries[i].ecanonname); batch->queries[i].ecanonname = NULL; if (batch->queries[i].edomain != NULL) free(batch->queries[i].edomain); batch->queries[i].edomain = NULL; } } /* * This routine frees the idmap_query_state_t structure */ void idmap_lookup_release_batch(idmap_query_state_t **state) { if (state == NULL || *state == NULL) return; adutils_lookup_batch_release(&(*state)->qs); idmap_cleanup_batch(*state); free((*state)->default_domain); free(*state); *state = NULL; } idmap_retcode idmap_lookup_batch_end(idmap_query_state_t **state) { adutils_rc ad_rc; int i; idmap_query_state_t *id_qs = *state; ad_rc = adutils_lookup_batch_end(&id_qs->qs); /* * Map adutils rc to idmap_retcode in each * query because consumers in dbutils.c * expects idmap_retcode. */ for (i = 0; i < id_qs->qcount; i++) { *id_qs->queries[i].rc = map_adrc2idmaprc(id_qs->queries[i].ad_rc); } idmap_lookup_release_batch(state); return (map_adrc2idmaprc(ad_rc)); } /* * Send one prepared search, queue up msgid, process what results are * available */ static idmap_retcode idmap_batch_add1(idmap_query_state_t *state, const char *filter, char *ecanonname, char *edomain, idmap_id_type esidtype, char **dn, char **attr, char **value, char **canonname, char **dname, char **sid, rid_t *rid, idmap_id_type *sid_type, char **unixname, posix_id_t *pid, idmap_retcode *rc) { adutils_rc ad_rc; int qid, i; idmap_q_t *q; char *attrs[20]; /* Plenty */ qid = atomic_inc_32_nv(&state->qcount) - 1; q = &(state->queries[qid]); assert(qid < state->qsize); /* * Remember the expected canonname, domainname and unix type * so we can check the results * against it */ q->ecanonname = ecanonname; q->edomain = edomain; q->esidtype = esidtype; /* Remember where to put the results */ q->canonname = canonname; q->sid = sid; q->domain = dname; q->rid = rid; q->sid_type = sid_type; q->rc = rc; q->unixname = unixname; q->dn = dn; q->attr = attr; q->value = value; q->pid = pid; /* Add attributes that are not always needed */ i = 0; attrs[i++] = SAN; attrs[i++] = OBJSID; attrs[i++] = OBJCLASS; if (unixname != NULL) { /* Add unixuser/unixgroup attribute names to the attrs list */ if (esidtype != IDMAP_GSID && state->ad_unixuser_attr != NULL) attrs[i++] = (char *)state->ad_unixuser_attr; if (esidtype != IDMAP_USID && state->ad_unixgroup_attr != NULL) attrs[i++] = (char *)state->ad_unixgroup_attr; } if (pid != NULL) { if (esidtype != IDMAP_GSID) attrs[i++] = UIDNUMBER; if (esidtype != IDMAP_USID) attrs[i++] = GIDNUMBER; } attrs[i] = NULL; /* * Provide sane defaults for the results in case we never hear * back from the DS before closing the connection. * * In particular we default the result to indicate a retriable * error. The first complete matching result entry will cause * this to be set to IDMAP_SUCCESS, and the end of the results * for this search will cause this to indicate "not found" if no * result entries arrived or no complete ones matched the lookup * we were doing. */ *rc = IDMAP_ERR_RETRIABLE_NET_ERR; if (sid_type != NULL) *sid_type = IDMAP_SID; if (sid != NULL) *sid = NULL; if (dname != NULL) *dname = NULL; if (rid != NULL) *rid = 0; if (dn != NULL) *dn = NULL; if (attr != NULL) *attr = NULL; if (value != NULL) *value = NULL; /* * Don't set *canonname to NULL because it may be pointing to the * given winname. Later on if we get a canonical name from AD the * old name if any will be freed before assigning the new name. */ /* * Invoke the mother of all APIs i.e. the adutils API */ ad_rc = adutils_lookup_batch_add(state->qs, filter, (const char **)attrs, edomain, &q->result, &q->ad_rc); return (map_adrc2idmaprc(ad_rc)); } idmap_retcode idmap_name2sid_batch_add1(idmap_query_state_t *state, const char *name, const char *dname, idmap_id_type esidtype, char **dn, char **attr, char **value, char **canonname, char **sid, rid_t *rid, idmap_id_type *sid_type, char **unixname, posix_id_t *pid, idmap_retcode *rc) { idmap_retcode retcode; char *filter, *s_name; char *ecanonname, *edomain; /* expected canonname */ /* * Strategy: search the global catalog for user/group by * sAMAccountName = user/groupname with "" as the base DN and by * userPrincipalName = user/groupname@domain. The result * entries will be checked to conform to the name and domain * name given here. The DN, sAMAccountName, userPrincipalName, * objectSid and objectClass of the result entries are all we * need to figure out which entries match the lookup, the SID of * the user/group and whether it is a user or a group. */ if ((ecanonname = strdup(name)) == NULL) return (IDMAP_ERR_MEMORY); if (dname == NULL || *dname == '\0') { /* 'name' not qualified and dname not given */ dname = state->default_domain; edomain = strdup(dname); if (edomain == NULL) { free(ecanonname); return (IDMAP_ERR_MEMORY); } } else { if ((edomain = strdup(dname)) == NULL) { free(ecanonname); return (IDMAP_ERR_MEMORY); } } if (!adutils_lookup_check_domain(state->qs, dname)) { free(ecanonname); free(edomain); return (IDMAP_ERR_DOMAIN_NOTFOUND); } s_name = sanitize_for_ldap_filter(name); if (s_name == NULL) { free(ecanonname); free(edomain); return (IDMAP_ERR_MEMORY); } /* Assemble filter */ (void) asprintf(&filter, SANFILTER, s_name); if (s_name != name) free(s_name); if (filter == NULL) { free(ecanonname); free(edomain); return (IDMAP_ERR_MEMORY); } retcode = idmap_batch_add1(state, filter, ecanonname, edomain, esidtype, dn, attr, value, canonname, NULL, sid, rid, sid_type, unixname, pid, rc); free(filter); return (retcode); } idmap_retcode idmap_sid2name_batch_add1(idmap_query_state_t *state, const char *sid, const rid_t *rid, idmap_id_type esidtype, char **dn, char **attr, char **value, char **name, char **dname, idmap_id_type *sid_type, char **unixname, posix_id_t *pid, idmap_retcode *rc) { idmap_retcode retcode; int ret; char *filter; char cbinsid[ADUTILS_MAXHEXBINSID + 1]; /* * Strategy: search [the global catalog] for user/group by * objectSid = SID with empty base DN. The DN, sAMAccountName * and objectClass of the result are all we need to figure out * the name of the SID and whether it is a user, a group or a * computer. */ if (!adutils_lookup_check_sid_prefix(state->qs, sid)) return (IDMAP_ERR_DOMAIN_NOTFOUND); ret = adutils_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid)); if (ret != 0) return (IDMAP_ERR_SID); /* Assemble filter */ (void) asprintf(&filter, OBJSIDFILTER, cbinsid, cbinsid); if (filter == NULL) return (IDMAP_ERR_MEMORY); retcode = idmap_batch_add1(state, filter, NULL, NULL, esidtype, dn, attr, value, name, dname, NULL, NULL, sid_type, unixname, pid, rc); free(filter); return (retcode); } idmap_retcode idmap_unixname2sid_batch_add1(idmap_query_state_t *state, const char *unixname, int is_user, int is_wuser, char **dn, char **attr, char **value, char **sid, rid_t *rid, char **name, char **dname, idmap_id_type *sid_type, idmap_retcode *rc) { idmap_retcode retcode; char *filter, *s_unixname; const char *attrname; /* Get unixuser or unixgroup AD attribute name */ attrname = (is_user) ? state->ad_unixuser_attr : state->ad_unixgroup_attr; if (attrname == NULL) return (IDMAP_ERR_NOTFOUND); s_unixname = sanitize_for_ldap_filter(unixname); if (s_unixname == NULL) return (IDMAP_ERR_MEMORY); /* Assemble filter */ (void) asprintf(&filter, "(&(objectclass=%s)(%s=%s))", is_wuser ? "user" : "group", attrname, s_unixname); if (s_unixname != unixname) free(s_unixname); if (filter == NULL) { return (IDMAP_ERR_MEMORY); } retcode = idmap_batch_add1(state, filter, NULL, NULL, IDMAP_POSIXID, dn, NULL, NULL, name, dname, sid, rid, sid_type, NULL, NULL, rc); if (retcode == IDMAP_SUCCESS && attr != NULL) { if ((*attr = strdup(attrname)) == NULL) retcode = IDMAP_ERR_MEMORY; } if (retcode == IDMAP_SUCCESS && value != NULL) { if ((*value = strdup(unixname)) == NULL) retcode = IDMAP_ERR_MEMORY; } free(filter); return (retcode); } idmap_retcode idmap_pid2sid_batch_add1(idmap_query_state_t *state, posix_id_t pid, int is_user, char **dn, char **attr, char **value, char **sid, rid_t *rid, char **name, char **dname, idmap_id_type *sid_type, idmap_retcode *rc) { idmap_retcode retcode; char *filter; const char *attrname; /* Assemble filter */ if (is_user) { (void) asprintf(&filter, UIDNUMBERFILTER, pid); attrname = UIDNUMBER; } else { (void) asprintf(&filter, GIDNUMBERFILTER, pid); attrname = GIDNUMBER; } if (filter == NULL) return (IDMAP_ERR_MEMORY); retcode = idmap_batch_add1(state, filter, NULL, NULL, IDMAP_POSIXID, dn, NULL, NULL, name, dname, sid, rid, sid_type, NULL, NULL, rc); if (retcode == IDMAP_SUCCESS && attr != NULL) { if ((*attr = strdup(attrname)) == NULL) retcode = IDMAP_ERR_MEMORY; } if (retcode == IDMAP_SUCCESS && value != NULL) { (void) asprintf(value, "%u", pid); if (*value == NULL) retcode = IDMAP_ERR_MEMORY; } free(filter); return (retcode); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _ADUTILS_H #define _ADUTILS_H #ifdef __cplusplus extern "C" { #endif /* * Processes name2sid & sid2name lookups for a given user or computer * from an AD Difrectory server using GSSAPI authentication */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libadutils.h" #include /* * idmapd interfaces stolen? from other idmapd code? */ typedef uint32_t rid_t; typedef uid_t posix_id_t; typedef struct idmap_query_state idmap_query_state_t; int idmap_add_ds(adutils_ad_t *ad, const char *host, int port); /* * Batch lookups * * Start a batch, add queries to the batch one by one (the output * pointers should all differ, so that a query's results don't clobber * any other's), end the batch to wait for replies for all outstanding * queries. The output parameters of each query are initialized to NULL * or -1 as appropriate. * * LDAP searches are sent one by one without waiting (i.e., blocking) * for replies. Replies are handled as soon as they are available. * Missing replies are waited for only when idmap_lookup_batch_end() is * called. * * If an add1 function returns != 0 then abort the batch by calling * idmap_lookup_batch_end(), but note that some queries may have been * answered, so check the result code of each query. */ /* Start a batch of lookups */ idmap_retcode idmap_lookup_batch_start(adutils_ad_t *ad, int nqueries, int directory_based_mapping, const char *default_domain, idmap_query_state_t **state); /* End a batch and release its idmap_query_state_t object */ idmap_retcode idmap_lookup_batch_end(idmap_query_state_t **state); /* Abandon a batch and release its idmap_query_state_t object */ void idmap_lookup_release_batch(idmap_query_state_t **state); /* * Add a name->SID lookup * * - 'dname' is optional; if NULL or empty string then 'name' has to be * a user/group name qualified wih a domainname (e.g., foo@domain), * else the 'name' must not be qualified and the domainname must be * passed in 'dname'. * * - if 'rid' is NULL then the output SID string will include the last * RID, else it won't and the last RID value will be stored in *rid. * * The caller must free() *sid. */ idmap_retcode idmap_name2sid_batch_add1(idmap_query_state_t *state, const char *name, const char *dname, idmap_id_type esidtype, char **dn, char **attr, char **value, char **canonname, char **sid, rid_t *rid, idmap_id_type *sid_type, char **unixname, posix_id_t *pid, idmap_retcode *rc); /* * Add a SID->name lookup * * - 'rid' is optional; if NULL then 'sid' is expected to have the * user/group RID present, else 'sid' is expected not to have it, and * *rid will be used to qualify the given 'sid' * * - 'dname' is optional; if NULL then the fully qualified user/group * name will be stored in *name, else the domain name will be stored in * *dname and the user/group name will be stored in *name without a * domain qualifier. * * The caller must free() *name and *dname (if present). */ idmap_retcode idmap_sid2name_batch_add1(idmap_query_state_t *state, const char *sid, const rid_t *rid, idmap_id_type esidtype, char **dn, char **attr, char **value, char **name, char **dname, idmap_id_type *sid_type, char **unixname, posix_id_t *pid, idmap_retcode *rc); /* * Add a unixname->SID lookup */ idmap_retcode idmap_unixname2sid_batch_add1(idmap_query_state_t *state, const char *unixname, int is_user, int is_wuser, char **dn, char **attr, char **value, char **sid, rid_t *rid, char **name, char **dname, idmap_id_type *sid_type, idmap_retcode *rc); /* * Add a PID->SID lookup */ idmap_retcode idmap_pid2sid_batch_add1(idmap_query_state_t *state, posix_id_t pid, int is_user, char **dn, char **attr, char **value, char **sid, rid_t *rid, char **name, char **dname, idmap_id_type *sid_type, idmap_retcode *rc); /* * Set unixname attribute names for the batch for AD-based name mapping */ void idmap_lookup_batch_set_unixattr(idmap_query_state_t *state, const char *unixuser_attr, const char *unixgroup_attr); #ifdef __cplusplus } #endif #endif /* _ADUTILS_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. * Copyright 2025 Bill Sommerfeld */ /* * Database related utility routines */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "idmapd.h" #include "adutils.h" #include "string.h" #include "idmap_priv.h" #include "schema.h" #include "nldaputils.h" #include "idmap_lsa.h" static idmap_retcode sql_compile_n_step_once(sqlite *, char *, sqlite_vm **, int *, int, const char ***); static idmap_retcode lookup_localsid2pid(idmap_mapping *, idmap_id_res *); static idmap_retcode lookup_cache_name2sid(sqlite *, const char *, const char *, char **, char **, idmap_rid_t *, idmap_id_type *); #define EMPTY_NAME(name) (*name == 0 || strcmp(name, "\"\"") == 0) #define DO_NOT_ALLOC_NEW_ID_MAPPING(req)\ (req->flag & IDMAP_REQ_FLG_NO_NEW_ID_ALLOC) #define AVOID_NAMESERVICE(req)\ (req->flag & IDMAP_REQ_FLG_NO_NAMESERVICE) #define ALLOW_WK_OR_LOCAL_SIDS_ONLY(req)\ (req->flag & IDMAP_REQ_FLG_WK_OR_LOCAL_SIDS_ONLY) typedef enum init_db_option { FAIL_IF_CORRUPT = 0, REMOVE_IF_CORRUPT = 1 } init_db_option_t; /* * Thread specific data to hold the database handles so that the * databases are not opened and closed for every request. It also * contains the sqlite busy handler structure. */ struct idmap_busy { const char *name; const int *delays; int delay_size; int total; int sec; }; typedef struct idmap_tsd { sqlite *db_db; sqlite *cache_db; struct idmap_busy cache_busy; struct idmap_busy db_busy; } idmap_tsd_t; /* * Flags to indicate how local the directory we're consulting is. * If neither is set, it means the directory belongs to a remote forest. */ #define DOMAIN_IS_LOCAL 0x01 #define FOREST_IS_LOCAL 0x02 static const int cache_delay_table[] = { 1, 2, 5, 10, 15, 20, 25, 30, 35, 40, 50, 50, 60, 70, 80, 90, 100}; static const int db_delay_table[] = { 5, 10, 15, 20, 30, 40, 55, 70, 100}; static pthread_key_t idmap_tsd_key; void idmap_tsd_destroy(void *key) { idmap_tsd_t *tsd = (idmap_tsd_t *)key; if (tsd) { if (tsd->db_db) (void) sqlite_close(tsd->db_db); if (tsd->cache_db) (void) sqlite_close(tsd->cache_db); free(tsd); } } void idmap_init_tsd_key(void) { int rc; rc = pthread_key_create(&idmap_tsd_key, idmap_tsd_destroy); assert(rc == 0); } idmap_tsd_t * idmap_get_tsd(void) { idmap_tsd_t *tsd; if ((tsd = pthread_getspecific(idmap_tsd_key)) == NULL) { /* No thread specific data so create it */ if ((tsd = malloc(sizeof (*tsd))) != NULL) { /* Initialize thread specific data */ (void) memset(tsd, 0, sizeof (*tsd)); /* save the trhread specific data */ if (pthread_setspecific(idmap_tsd_key, tsd) != 0) { /* Can't store key */ free(tsd); tsd = NULL; } } else { tsd = NULL; } } return (tsd); } /* * A simple wrapper around u8_textprep_str() that returns the Unicode * lower-case version of some string. The result must be freed. */ char * tolower_u8(const char *s) { char *res = NULL; char *outs; size_t inlen, outlen, inbytesleft, outbytesleft; int rc, err; /* * u8_textprep_str() does not allocate memory. The input and * output buffers may differ in size (though that would be more * likely when normalization is done). We have to loop over it... * * To improve the chances that we can avoid looping we add 10 * bytes of output buffer room the first go around. */ inlen = inbytesleft = strlen(s); outlen = outbytesleft = inlen + 10; if ((res = malloc(outlen)) == NULL) return (NULL); outs = res; while ((rc = u8_textprep_str((char *)s, &inbytesleft, outs, &outbytesleft, U8_TEXTPREP_TOLOWER, U8_UNICODE_LATEST, &err)) < 0 && err == E2BIG) { if ((res = realloc(res, outlen + inbytesleft)) == NULL) return (NULL); /* adjust input/output buffer pointers */ s += (inlen - inbytesleft); outs = res + outlen - outbytesleft; /* adjust outbytesleft and outlen */ outlen += inbytesleft; outbytesleft += inbytesleft; } if (rc < 0) { free(res); res = NULL; return (NULL); } res[outlen - outbytesleft] = '\0'; return (res); } static int sql_exec_tran_no_cb(sqlite *db, char *sql, const char *dbname, const char *while_doing); /* * Initialize 'dbname' using 'sql' */ static int init_db_instance(const char *dbname, int version, const char *detect_version_sql, char * const *sql, init_db_option_t opt, int *created, int *upgraded) { int rc, curr_version; int tries = 1; int prio = LOG_NOTICE; sqlite *db = NULL; char *errmsg = NULL; *created = 0; *upgraded = 0; if (opt == REMOVE_IF_CORRUPT) tries = 3; rinse_repeat: if (tries == 0) { idmapdlog(LOG_ERR, "Failed to initialize db %s", dbname); return (-1); } if (tries-- == 1) /* Last try, log errors */ prio = LOG_ERR; db = sqlite_open(dbname, 0600, &errmsg); if (db == NULL) { idmapdlog(prio, "Error creating database %s (%s)", dbname, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); if (opt == REMOVE_IF_CORRUPT) (void) unlink(dbname); goto rinse_repeat; } sqlite_busy_timeout(db, 3000); /* Detect current version of schema in the db, if any */ curr_version = 0; if (detect_version_sql != NULL) { char *end, **results; int nrow; #ifdef IDMAPD_DEBUG (void) fprintf(stderr, "Schema version detection SQL: %s\n", detect_version_sql); #endif /* IDMAPD_DEBUG */ rc = sqlite_get_table(db, detect_version_sql, &results, &nrow, NULL, &errmsg); if (rc != SQLITE_OK) { idmapdlog(prio, "Error detecting schema version of db %s (%s)", dbname, errmsg); sqlite_freemem(errmsg); sqlite_free_table(results); sqlite_close(db); return (-1); } if (nrow != 1) { idmapdlog(prio, "Error detecting schema version of db %s", dbname); sqlite_close(db); sqlite_free_table(results); return (-1); } curr_version = strtol(results[1], &end, 10); sqlite_free_table(results); } if (curr_version < 0) { if (opt == REMOVE_IF_CORRUPT) (void) unlink(dbname); goto rinse_repeat; } if (curr_version == version) goto done; /* Install or upgrade schema */ #ifdef IDMAPD_DEBUG (void) fprintf(stderr, "Schema init/upgrade SQL: %s\n", sql[curr_version]); #endif /* IDMAPD_DEBUG */ rc = sql_exec_tran_no_cb(db, sql[curr_version], dbname, (curr_version == 0) ? "installing schema" : "upgrading schema"); if (rc != 0) { idmapdlog(prio, "Error %s schema for db %s", dbname, (curr_version == 0) ? "installing schema" : "upgrading schema"); if (opt == REMOVE_IF_CORRUPT) (void) unlink(dbname); goto rinse_repeat; } *upgraded = (curr_version > 0); *created = (curr_version == 0); done: (void) sqlite_close(db); return (0); } /* * This is the SQLite database busy handler that retries the SQL * operation until it is successful. */ int idmap_sqlite_busy_handler(void *arg, const char *table_name, int count) { struct idmap_busy *busy = arg; int delay; struct timespec rqtp; if (count == 1) { busy->total = 0; busy->sec = 2; } if (busy->total > 1000 * busy->sec) { idmapdlog(LOG_DEBUG, "Thread %d waited %d sec for the %s database", pthread_self(), busy->sec, busy->name); busy->sec++; } if (count <= busy->delay_size) { delay = busy->delays[count-1]; } else { delay = busy->delays[busy->delay_size - 1]; } busy->total += delay; rqtp.tv_sec = 0; rqtp.tv_nsec = MSEC2NSEC(delay); (void) nanosleep(&rqtp, NULL); return (1); } /* * Get the database handle */ idmap_retcode get_db_handle(sqlite **db) { char *errmsg; idmap_tsd_t *tsd; /* * Retrieve the db handle from thread-specific storage * If none exists, open and store in thread-specific storage. */ if ((tsd = idmap_get_tsd()) == NULL) { idmapdlog(LOG_ERR, "Error getting thread specific data for %s", IDMAP_DBNAME); return (IDMAP_ERR_MEMORY); } if (tsd->db_db == NULL) { tsd->db_db = sqlite_open(IDMAP_DBNAME, 0, &errmsg); if (tsd->db_db == NULL) { idmapdlog(LOG_ERR, "Error opening database %s (%s)", IDMAP_DBNAME, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); return (IDMAP_ERR_DB); } tsd->db_busy.name = IDMAP_DBNAME; tsd->db_busy.delays = db_delay_table; tsd->db_busy.delay_size = sizeof (db_delay_table) / sizeof (int); sqlite_busy_handler(tsd->db_db, idmap_sqlite_busy_handler, &tsd->db_busy); } *db = tsd->db_db; return (IDMAP_SUCCESS); } /* * Force next get_db_handle to reopen. * Called after DB errors. */ void kill_db_handle(sqlite *db) { idmap_tsd_t *tsd; sqlite *t; if (db == NULL) return; if ((tsd = idmap_get_tsd()) == NULL) return; if ((t = tsd->db_db) == NULL) return; assert(t == db); tsd->db_db = NULL; (void) sqlite_close(t); } /* * Get the cache handle */ idmap_retcode get_cache_handle(sqlite **cache) { char *errmsg; idmap_tsd_t *tsd; /* * Retrieve the db handle from thread-specific storage * If none exists, open and store in thread-specific storage. */ if ((tsd = idmap_get_tsd()) == NULL) { idmapdlog(LOG_ERR, "Error getting thread specific data for %s", IDMAP_DBNAME); return (IDMAP_ERR_MEMORY); } if (tsd->cache_db == NULL) { tsd->cache_db = sqlite_open(IDMAP_CACHENAME, 0, &errmsg); if (tsd->cache_db == NULL) { idmapdlog(LOG_ERR, "Error opening database %s (%s)", IDMAP_CACHENAME, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); return (IDMAP_ERR_DB); } tsd->cache_busy.name = IDMAP_CACHENAME; tsd->cache_busy.delays = cache_delay_table; tsd->cache_busy.delay_size = sizeof (cache_delay_table) / sizeof (int); sqlite_busy_handler(tsd->cache_db, idmap_sqlite_busy_handler, &tsd->cache_busy); } *cache = tsd->cache_db; return (IDMAP_SUCCESS); } /* * Force next get_cache_handle to reopen. * Called after DB errors. */ void kill_cache_handle(sqlite *db) { idmap_tsd_t *tsd; sqlite *t; if (db == NULL) return; if ((tsd = idmap_get_tsd()) == NULL) return; if ((t = tsd->cache_db) == NULL) return; assert(t == db); tsd->cache_db = NULL; (void) sqlite_close(t); } /* * Initialize cache and db */ int init_dbs(void) { char *sql[4]; int created, upgraded; /* name-based mappings; probably OK to blow away in a pinch(?) */ sql[0] = DB_INSTALL_SQL; sql[1] = DB_UPGRADE_FROM_v1_SQL; sql[2] = NULL; if (init_db_instance(IDMAP_DBNAME, DB_VERSION, DB_VERSION_SQL, sql, FAIL_IF_CORRUPT, &created, &upgraded) < 0) return (-1); /* mappings, name/SID lookup cache + ephemeral IDs; OK to blow away */ sql[0] = CACHE_INSTALL_SQL; sql[1] = CACHE_UPGRADE_FROM_v1_SQL; sql[2] = CACHE_UPGRADE_FROM_v2_SQL; sql[3] = NULL; if (init_db_instance(IDMAP_CACHENAME, CACHE_VERSION, CACHE_VERSION_SQL, sql, REMOVE_IF_CORRUPT, &created, &upgraded) < 0) return (-1); /* * TODO: If cache DB NOT created, get MAX PID for allocids(), eg. * sql = "SELECT MAX(pid) as max_pid FROM idmap_cache;" * sqlite_get_table(db, sql, &results, &nrow, NULL, &errmsg); * * However, the allocids() system call does not currently allow * for this kind of initialization. Until that's dealt with, * use of a persistent idmap cache DB cannot work. */ /* This becomes the "flush" flag for allocids() */ _idmapdstate.new_eph_db = (created || upgraded) ? 1 : 0; return (0); } /* * Finalize databases */ void fini_dbs(void) { } /* * This table is a listing of status codes that will be returned to the * client when a SQL command fails with the corresponding error message. */ static msg_table_t sqlmsgtable[] = { {IDMAP_ERR_U2W_NAMERULE_CONFLICT, "columns unixname, is_user, u2w_order are not unique"}, {IDMAP_ERR_W2U_NAMERULE_CONFLICT, "columns winname, windomain, is_user, is_wuser, w2u_order are not" " unique"}, {IDMAP_ERR_W2U_NAMERULE_CONFLICT, "Conflicting w2u namerules"}, {-1, NULL} }; /* * idmapd's version of string2stat to map SQLite messages to * status codes */ idmap_retcode idmapd_string2stat(const char *msg) { int i; for (i = 0; sqlmsgtable[i].msg; i++) { if (strcasecmp(sqlmsgtable[i].msg, msg) == 0) return (sqlmsgtable[i].retcode); } return (IDMAP_ERR_OTHER); } /* * Executes some SQL in a transaction. * * Returns 0 on success, -1 if it failed but the rollback succeeded, -2 * if the rollback failed. */ static int sql_exec_tran_no_cb(sqlite *db, char *sql, const char *dbname, const char *while_doing) { char *errmsg = NULL; int rc; rc = sqlite_exec(db, "BEGIN TRANSACTION;", NULL, NULL, &errmsg); if (rc != SQLITE_OK) { idmapdlog(LOG_ERR, "Begin transaction failed (%s) " "while %s (%s)", errmsg, while_doing, dbname); sqlite_freemem(errmsg); return (-1); } rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); if (rc != SQLITE_OK) { idmapdlog(LOG_ERR, "Database error (%s) while %s (%s)", errmsg, while_doing, dbname); sqlite_freemem(errmsg); errmsg = NULL; goto rollback; } rc = sqlite_exec(db, "COMMIT TRANSACTION", NULL, NULL, &errmsg); if (rc == SQLITE_OK) { sqlite_freemem(errmsg); return (0); } idmapdlog(LOG_ERR, "Database commit error (%s) while s (%s)", errmsg, while_doing, dbname); sqlite_freemem(errmsg); errmsg = NULL; rollback: rc = sqlite_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, &errmsg); if (rc != SQLITE_OK) { idmapdlog(LOG_ERR, "Rollback failed (%s) while %s (%s)", errmsg, while_doing, dbname); sqlite_freemem(errmsg); return (-2); } sqlite_freemem(errmsg); return (-1); } /* * Execute the given SQL statment without using any callbacks */ idmap_retcode sql_exec_no_cb(sqlite *db, const char *dbname, char *sql) { char *errmsg = NULL; int r; idmap_retcode retcode; r = sqlite_exec(db, sql, NULL, NULL, &errmsg); if (r != SQLITE_OK) { idmapdlog(LOG_ERR, "Database error on %s while executing %s " "(%s)", dbname, sql, CHECK_NULL(errmsg)); switch (r) { case SQLITE_BUSY: case SQLITE_LOCKED: assert(0); retcode = IDMAP_ERR_INTERNAL; break; case SQLITE_NOMEM: retcode = IDMAP_ERR_MEMORY; break; case SQLITE_FULL: retcode = IDMAP_ERR_DB; break; default: retcode = idmapd_string2stat(errmsg); break; } if (errmsg != NULL) sqlite_freemem(errmsg); return (retcode); } return (IDMAP_SUCCESS); } /* * Generate expression that can be used in WHERE statements. * Examples: * * "" "unixuser" "=" "foo" "AND" */ idmap_retcode gen_sql_expr_from_rule(idmap_namerule *rule, char **out) { char *s_windomain = NULL, *s_winname = NULL; char *s_unixname = NULL; char *dir; char *lower_winname; int retcode = IDMAP_SUCCESS; if (out == NULL) return (IDMAP_ERR_ARG); if (!EMPTY_STRING(rule->windomain)) { s_windomain = sqlite_mprintf("AND windomain = %Q ", rule->windomain); if (s_windomain == NULL) { retcode = IDMAP_ERR_MEMORY; goto out; } } if (!EMPTY_STRING(rule->winname)) { if ((lower_winname = tolower_u8(rule->winname)) == NULL) lower_winname = rule->winname; s_winname = sqlite_mprintf( "AND winname = %Q AND is_wuser = %d ", lower_winname, rule->is_wuser ? 1 : 0); if (lower_winname != rule->winname) free(lower_winname); if (s_winname == NULL) { retcode = IDMAP_ERR_MEMORY; goto out; } } if (!EMPTY_STRING(rule->unixname)) { s_unixname = sqlite_mprintf( "AND unixname = %Q AND is_user = %d ", rule->unixname, rule->is_user ? 1 : 0); if (s_unixname == NULL) { retcode = IDMAP_ERR_MEMORY; goto out; } } switch (rule->direction) { case IDMAP_DIRECTION_BI: dir = "AND w2u_order > 0 AND u2w_order > 0"; break; case IDMAP_DIRECTION_W2U: dir = "AND w2u_order > 0" " AND (u2w_order = 0 OR u2w_order ISNULL)"; break; case IDMAP_DIRECTION_U2W: dir = "AND u2w_order > 0" " AND (w2u_order = 0 OR w2u_order ISNULL)"; break; default: dir = ""; break; } *out = sqlite_mprintf("%s %s %s %s", s_windomain ? s_windomain : "", s_winname ? s_winname : "", s_unixname ? s_unixname : "", dir); if (*out == NULL) { retcode = IDMAP_ERR_MEMORY; idmapdlog(LOG_ERR, "Out of memory"); goto out; } out: if (s_windomain != NULL) sqlite_freemem(s_windomain); if (s_winname != NULL) sqlite_freemem(s_winname); if (s_unixname != NULL) sqlite_freemem(s_unixname); return (retcode); } /* * Generate and execute SQL statement for LIST RPC calls */ idmap_retcode process_list_svc_sql(sqlite *db, const char *dbname, char *sql, uint64_t limit, int flag, list_svc_cb cb, void *result) { list_cb_data_t cb_data; char *errmsg = NULL; int r; idmap_retcode retcode = IDMAP_ERR_INTERNAL; (void) memset(&cb_data, 0, sizeof (cb_data)); cb_data.result = result; cb_data.limit = limit; cb_data.flag = flag; r = sqlite_exec(db, sql, cb, &cb_data, &errmsg); assert(r != SQLITE_LOCKED && r != SQLITE_BUSY); switch (r) { case SQLITE_OK: retcode = IDMAP_SUCCESS; break; default: retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "Database error on %s while executing " "%s (%s)", dbname, sql, CHECK_NULL(errmsg)); break; } if (errmsg != NULL) sqlite_freemem(errmsg); return (retcode); } /* * This routine is called by callbacks that process the results of * LIST RPC calls to validate data and to allocate memory for * the result array. */ idmap_retcode validate_list_cb_data(list_cb_data_t *cb_data, int argc, char **argv, int ncol, uchar_t **list, size_t valsize) { size_t nsize; void *tmplist; if (cb_data->limit > 0 && cb_data->next == cb_data->limit) return (IDMAP_NEXT); if (argc < ncol || argv == NULL) { idmapdlog(LOG_ERR, "Invalid data"); return (IDMAP_ERR_INTERNAL); } /* alloc in bulk to reduce number of reallocs */ if (cb_data->next >= cb_data->len) { nsize = (cb_data->len + SIZE_INCR) * valsize; tmplist = realloc(*list, nsize); if (tmplist == NULL) { idmapdlog(LOG_ERR, "Out of memory"); return (IDMAP_ERR_MEMORY); } *list = tmplist; (void) memset(*list + (cb_data->len * valsize), 0, SIZE_INCR * valsize); cb_data->len += SIZE_INCR; } return (IDMAP_SUCCESS); } static idmap_retcode get_namerule_order(char *winname, char *windomain, char *unixname, int direction, int is_diagonal, int *w2u_order, int *u2w_order) { *w2u_order = 0; *u2w_order = 0; /* * Windows to UNIX lookup order: * 1. winname@domain (or winname) to "" * 2. winname@domain (or winname) to unixname * 3. winname@* to "" * 4. winname@* to unixname * 5. *@domain (or *) to * * 6. *@domain (or *) to "" * 7. *@domain (or *) to unixname * 8. *@* to * * 9. *@* to "" * 10. *@* to unixname * * winname is a special case of winname@domain when domain is the * default domain. Similarly * is a special case of *@domain when * domain is the default domain. * * Note that "" has priority over specific names because "" inhibits * mappings and traditionally deny rules always had higher priority. */ if (direction != IDMAP_DIRECTION_U2W) { /* bi-directional or from windows to unix */ if (winname == NULL) return (IDMAP_ERR_W2U_NAMERULE); else if (unixname == NULL) return (IDMAP_ERR_W2U_NAMERULE); else if (EMPTY_NAME(winname)) return (IDMAP_ERR_W2U_NAMERULE); else if (*winname == '*' && windomain && *windomain == '*') { if (*unixname == '*') *w2u_order = 8; else if (EMPTY_NAME(unixname)) *w2u_order = 9; else /* unixname == name */ *w2u_order = 10; } else if (*winname == '*') { if (*unixname == '*') *w2u_order = 5; else if (EMPTY_NAME(unixname)) *w2u_order = 6; else /* name */ *w2u_order = 7; } else if (windomain != NULL && *windomain == '*') { /* winname == name */ if (*unixname == '*') return (IDMAP_ERR_W2U_NAMERULE); else if (EMPTY_NAME(unixname)) *w2u_order = 3; else /* name */ *w2u_order = 4; } else { /* winname == name && windomain == null or name */ if (*unixname == '*') return (IDMAP_ERR_W2U_NAMERULE); else if (EMPTY_NAME(unixname)) *w2u_order = 1; else /* name */ *w2u_order = 2; } } /* * 1. unixname to "", non-diagonal * 2. unixname to winname@domain (or winname), non-diagonal * 3. unixname to "", diagonal * 4. unixname to winname@domain (or winname), diagonal * 5. * to *@domain (or *), non-diagonal * 5. * to *@domain (or *), diagonal * 7. * to "" * 8. * to winname@domain (or winname) * 9. * to "", non-diagonal * 10. * to winname@domain (or winname), diagonal */ if (direction != IDMAP_DIRECTION_W2U) { int diagonal = is_diagonal ? 1 : 0; /* bi-directional or from unix to windows */ if (unixname == NULL || EMPTY_NAME(unixname)) return (IDMAP_ERR_U2W_NAMERULE); else if (winname == NULL) return (IDMAP_ERR_U2W_NAMERULE); else if (windomain != NULL && *windomain == '*') return (IDMAP_ERR_U2W_NAMERULE); else if (*unixname == '*') { if (*winname == '*') *u2w_order = 5 + diagonal; else if (EMPTY_NAME(winname)) *u2w_order = 7 + 2 * diagonal; else *u2w_order = 8 + 2 * diagonal; } else { if (*winname == '*') return (IDMAP_ERR_U2W_NAMERULE); else if (EMPTY_NAME(winname)) *u2w_order = 1 + 2 * diagonal; else *u2w_order = 2 + 2 * diagonal; } } return (IDMAP_SUCCESS); } /* * Generate and execute SQL statement to add name-based mapping rule */ idmap_retcode add_namerule(sqlite *db, idmap_namerule *rule) { char *sql = NULL; idmap_stat retcode; char *dom = NULL; char *name; int w2u_order, u2w_order; char w2ubuf[11], u2wbuf[11]; char *canonname = NULL; char *canondomain = NULL; retcode = get_namerule_order(rule->winname, rule->windomain, rule->unixname, rule->direction, rule->is_user == rule->is_wuser ? 0 : 1, &w2u_order, &u2w_order); if (retcode != IDMAP_SUCCESS) goto out; if (w2u_order) (void) snprintf(w2ubuf, sizeof (w2ubuf), "%d", w2u_order); if (u2w_order) (void) snprintf(u2wbuf, sizeof (u2wbuf), "%d", u2w_order); /* * For the triggers on namerules table to work correctly: * 1) Use NULL instead of 0 for w2u_order and u2w_order * 2) Use "" instead of NULL for "no domain" */ name = rule->winname; dom = rule->windomain; RDLOCK_CONFIG(); if (lookup_wksids_name2sid(name, dom, &canonname, &canondomain, NULL, NULL, NULL) == IDMAP_SUCCESS) { name = canonname; dom = canondomain; } else if (EMPTY_STRING(dom)) { if (_idmapdstate.cfg->pgcfg.default_domain) dom = _idmapdstate.cfg->pgcfg.default_domain; else dom = ""; } sql = sqlite_mprintf("INSERT into namerules " "(is_user, is_wuser, windomain, winname_display, is_nt4, " "unixname, w2u_order, u2w_order) " "VALUES(%d, %d, %Q, %Q, %d, %Q, %q, %q);", rule->is_user ? 1 : 0, rule->is_wuser ? 1 : 0, dom, name, rule->is_nt4 ? 1 : 0, rule->unixname, w2u_order ? w2ubuf : NULL, u2w_order ? u2wbuf : NULL); UNLOCK_CONFIG(); if (sql == NULL) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "Out of memory"); goto out; } retcode = sql_exec_no_cb(db, IDMAP_DBNAME, sql); if (retcode == IDMAP_ERR_OTHER) retcode = IDMAP_ERR_CFG; out: free(canonname); free(canondomain); if (sql != NULL) sqlite_freemem(sql); return (retcode); } /* * Flush name-based mapping rules */ idmap_retcode flush_namerules(sqlite *db) { idmap_stat retcode; retcode = sql_exec_no_cb(db, IDMAP_DBNAME, "DELETE FROM namerules;"); return (retcode); } /* * Generate and execute SQL statement to remove a name-based mapping rule */ idmap_retcode rm_namerule(sqlite *db, idmap_namerule *rule) { char *sql = NULL; idmap_stat retcode; char *expr = NULL; if (rule->direction < 0 && EMPTY_STRING(rule->windomain) && EMPTY_STRING(rule->winname) && EMPTY_STRING(rule->unixname)) return (IDMAP_SUCCESS); retcode = gen_sql_expr_from_rule(rule, &expr); if (retcode != IDMAP_SUCCESS) goto out; sql = sqlite_mprintf("DELETE FROM namerules WHERE 1 %s;", expr); if (sql == NULL) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "Out of memory"); goto out; } retcode = sql_exec_no_cb(db, IDMAP_DBNAME, sql); out: if (expr != NULL) sqlite_freemem(expr); if (sql != NULL) sqlite_freemem(sql); return (retcode); } /* * Compile the given SQL query and step just once. * * Input: * db - db handle * sql - SQL statement * * Output: * vm - virtual SQL machine * ncol - number of columns in the result * values - column values * * Return values: * IDMAP_SUCCESS * IDMAP_ERR_NOTFOUND * IDMAP_ERR_INTERNAL */ static idmap_retcode sql_compile_n_step_once(sqlite *db, char *sql, sqlite_vm **vm, int *ncol, int reqcol, const char ***values) { char *errmsg = NULL; int r; if ((r = sqlite_compile(db, sql, NULL, vm, &errmsg)) != SQLITE_OK) { idmapdlog(LOG_ERR, "Database error during %s (%s)", sql, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); return (IDMAP_ERR_INTERNAL); } r = sqlite_step(*vm, ncol, values, NULL); assert(r != SQLITE_LOCKED && r != SQLITE_BUSY); if (r == SQLITE_ROW) { if (ncol != NULL && *ncol < reqcol) { (void) sqlite_finalize(*vm, NULL); *vm = NULL; return (IDMAP_ERR_INTERNAL); } /* Caller will call finalize after using the results */ return (IDMAP_SUCCESS); } else if (r == SQLITE_DONE) { (void) sqlite_finalize(*vm, NULL); *vm = NULL; return (IDMAP_ERR_NOTFOUND); } (void) sqlite_finalize(*vm, &errmsg); *vm = NULL; idmapdlog(LOG_ERR, "Database error during %s (%s)", sql, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); return (IDMAP_ERR_INTERNAL); } /* * Load config in the state. * * nm_siduid and nm_sidgid fields: * state->nm_siduid represents mode used by sid2uid and uid2sid * requests for directory-based name mappings. Similarly, * state->nm_sidgid represents mode used by sid2gid and gid2sid * requests. * * sid2uid/uid2sid: * none -> directory_based_mapping != DIRECTORY_MAPPING_NAME * AD-mode -> !nldap_winname_attr && ad_unixuser_attr * nldap-mode -> nldap_winname_attr && !ad_unixuser_attr * mixed-mode -> nldap_winname_attr && ad_unixuser_attr * * sid2gid/gid2sid: * none -> directory_based_mapping != DIRECTORY_MAPPING_NAME * AD-mode -> !nldap_winname_attr && ad_unixgroup_attr * nldap-mode -> nldap_winname_attr && !ad_unixgroup_attr * mixed-mode -> nldap_winname_attr && ad_unixgroup_attr */ idmap_retcode load_cfg_in_state(lookup_state_t *state) { state->nm_siduid = IDMAP_NM_NONE; state->nm_sidgid = IDMAP_NM_NONE; RDLOCK_CONFIG(); state->eph_map_unres_sids = 0; if (_idmapdstate.cfg->pgcfg.eph_map_unres_sids) state->eph_map_unres_sids = 1; state->id_cache_timeout = _idmapdstate.cfg->pgcfg.id_cache_timeout; state->name_cache_timeout = _idmapdstate.cfg->pgcfg.name_cache_timeout; state->directory_based_mapping = _idmapdstate.cfg->pgcfg.directory_based_mapping; if (_idmapdstate.cfg->pgcfg.default_domain != NULL) { state->defdom = strdup(_idmapdstate.cfg->pgcfg.default_domain); if (state->defdom == NULL) { UNLOCK_CONFIG(); return (IDMAP_ERR_MEMORY); } } else { UNLOCK_CONFIG(); return (IDMAP_SUCCESS); } if (_idmapdstate.cfg->pgcfg.directory_based_mapping != DIRECTORY_MAPPING_NAME) { UNLOCK_CONFIG(); return (IDMAP_SUCCESS); } if (_idmapdstate.cfg->pgcfg.nldap_winname_attr != NULL) { state->nm_siduid = (_idmapdstate.cfg->pgcfg.ad_unixuser_attr != NULL) ? IDMAP_NM_MIXED : IDMAP_NM_NLDAP; state->nm_sidgid = (_idmapdstate.cfg->pgcfg.ad_unixgroup_attr != NULL) ? IDMAP_NM_MIXED : IDMAP_NM_NLDAP; } else { state->nm_siduid = (_idmapdstate.cfg->pgcfg.ad_unixuser_attr != NULL) ? IDMAP_NM_AD : IDMAP_NM_NONE; state->nm_sidgid = (_idmapdstate.cfg->pgcfg.ad_unixgroup_attr != NULL) ? IDMAP_NM_AD : IDMAP_NM_NONE; } if (_idmapdstate.cfg->pgcfg.ad_unixuser_attr != NULL) { state->ad_unixuser_attr = strdup(_idmapdstate.cfg->pgcfg.ad_unixuser_attr); if (state->ad_unixuser_attr == NULL) { UNLOCK_CONFIG(); return (IDMAP_ERR_MEMORY); } } if (_idmapdstate.cfg->pgcfg.ad_unixgroup_attr != NULL) { state->ad_unixgroup_attr = strdup(_idmapdstate.cfg->pgcfg.ad_unixgroup_attr); if (state->ad_unixgroup_attr == NULL) { UNLOCK_CONFIG(); return (IDMAP_ERR_MEMORY); } } if (_idmapdstate.cfg->pgcfg.nldap_winname_attr != NULL) { state->nldap_winname_attr = strdup(_idmapdstate.cfg->pgcfg.nldap_winname_attr); if (state->nldap_winname_attr == NULL) { UNLOCK_CONFIG(); return (IDMAP_ERR_MEMORY); } } UNLOCK_CONFIG(); return (IDMAP_SUCCESS); } /* * Set the rule with specified values. * All the strings are copied. */ static void idmap_namerule_set(idmap_namerule *rule, const char *windomain, const char *winname, const char *unixname, boolean_t is_user, boolean_t is_wuser, boolean_t is_nt4, int direction) { /* * Only update if they differ because we have to free * and duplicate the strings */ if (rule->windomain == NULL || windomain == NULL || strcmp(rule->windomain, windomain) != 0) { if (rule->windomain != NULL) { free(rule->windomain); rule->windomain = NULL; } if (windomain != NULL) rule->windomain = strdup(windomain); } if (rule->winname == NULL || winname == NULL || strcmp(rule->winname, winname) != 0) { if (rule->winname != NULL) { free(rule->winname); rule->winname = NULL; } if (winname != NULL) rule->winname = strdup(winname); } if (rule->unixname == NULL || unixname == NULL || strcmp(rule->unixname, unixname) != 0) { if (rule->unixname != NULL) { free(rule->unixname); rule->unixname = NULL; } if (unixname != NULL) rule->unixname = strdup(unixname); } rule->is_user = is_user; rule->is_wuser = is_wuser; rule->is_nt4 = is_nt4; rule->direction = direction; } /* * Lookup well-known SIDs table either by winname or by SID. * * If the given winname or SID is a well-known SID then we set is_wksid * variable and then proceed to see if the SID has a hard mapping to * a particular UID/GID (Ex: Creator Owner/Creator Group mapped to * fixed ephemeral ids). The direction flag indicates whether we have * a mapping; UNDEF indicates that we do not. * * If we find a mapping then we return success, except for the * special case of IDMAP_SENTINEL_PID which indicates an inhibited mapping. * * If we find a matching entry, but no mapping, we supply SID, name, and type * information and return "not found". Higher layers will probably * do ephemeral mapping. * * If we do not find a match, we return "not found" and leave the question * to higher layers. */ static idmap_retcode lookup_wksids_sid2pid(idmap_mapping *req, idmap_id_res *res, int *is_wksid) { const wksids_table_t *wksid; *is_wksid = 0; assert(req->id1.idmap_id_u.sid.prefix != NULL || req->id1name != NULL); if (req->id1.idmap_id_u.sid.prefix != NULL) { wksid = find_wksid_by_sid(req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid, res->id.idtype); } else { wksid = find_wksid_by_name(req->id1name, req->id1domain, res->id.idtype); } if (wksid == NULL) return (IDMAP_ERR_NOTFOUND); /* Found matching entry. */ /* Fill in name if it was not already there. */ if (req->id1name == NULL) { req->id1name = strdup(wksid->winname); if (req->id1name == NULL) return (IDMAP_ERR_MEMORY); } /* Fill in SID if it was not already there */ if (req->id1.idmap_id_u.sid.prefix == NULL) { if (wksid->sidprefix != NULL) { req->id1.idmap_id_u.sid.prefix = strdup(wksid->sidprefix); } else { RDLOCK_CONFIG(); req->id1.idmap_id_u.sid.prefix = strdup(_idmapdstate.cfg->pgcfg.machine_sid); UNLOCK_CONFIG(); } if (req->id1.idmap_id_u.sid.prefix == NULL) return (IDMAP_ERR_MEMORY); req->id1.idmap_id_u.sid.rid = wksid->rid; } /* Fill in the canonical domain if not already there */ if (req->id1domain == NULL) { const char *dom; RDLOCK_CONFIG(); if (wksid->domain != NULL) dom = wksid->domain; else dom = _idmapdstate.hostname; req->id1domain = strdup(dom); UNLOCK_CONFIG(); if (req->id1domain == NULL) return (IDMAP_ERR_MEMORY); } *is_wksid = 1; req->direction |= _IDMAP_F_DONT_UPDATE_NAMECACHE; req->id1.idtype = wksid->is_wuser ? IDMAP_USID : IDMAP_GSID; if (res->id.idtype == IDMAP_POSIXID) { res->id.idtype = wksid->is_wuser ? IDMAP_UID : IDMAP_GID; } if (wksid->direction == IDMAP_DIRECTION_UNDEF) { /* * We don't have a mapping * (But note that we may have supplied SID, name, or type * information.) */ return (IDMAP_ERR_NOTFOUND); } /* * We have an explicit mapping. */ if (wksid->pid == IDMAP_SENTINEL_PID) { /* * ... which is that mapping is inhibited. */ return (IDMAP_ERR_NOMAPPING); } switch (res->id.idtype) { case IDMAP_UID: res->id.idmap_id_u.uid = wksid->pid; break; case IDMAP_GID: res->id.idmap_id_u.gid = wksid->pid; break; default: /* IDMAP_POSIXID is eliminated above */ return (IDMAP_ERR_NOTSUPPORTED); } res->direction = wksid->direction; res->info.how.map_type = IDMAP_MAP_TYPE_KNOWN_SID; res->info.src = IDMAP_MAP_SRC_HARD_CODED; return (IDMAP_SUCCESS); } /* * Look for an entry mapping a PID to a SID. * * Note that direction=UNDEF entries do not specify a mapping, * and that IDMAP_SENTINEL_PID entries represent either an inhibited * mapping or an ephemeral mapping. We don't handle either here; * they are filtered out by find_wksid_by_pid. */ static idmap_retcode lookup_wksids_pid2sid(idmap_mapping *req, idmap_id_res *res, int is_user) { const wksids_table_t *wksid; wksid = find_wksid_by_pid(req->id1.idmap_id_u.uid, is_user); if (wksid == NULL) return (IDMAP_ERR_NOTFOUND); if (res->id.idtype == IDMAP_SID) { res->id.idtype = wksid->is_wuser ? IDMAP_USID : IDMAP_GSID; } res->id.idmap_id_u.sid.rid = wksid->rid; if (wksid->sidprefix != NULL) { res->id.idmap_id_u.sid.prefix = strdup(wksid->sidprefix); } else { RDLOCK_CONFIG(); res->id.idmap_id_u.sid.prefix = strdup(_idmapdstate.cfg->pgcfg.machine_sid); UNLOCK_CONFIG(); } if (res->id.idmap_id_u.sid.prefix == NULL) { idmapdlog(LOG_ERR, "Out of memory"); return (IDMAP_ERR_MEMORY); } /* Fill in name if it was not already there. */ if (req->id2name == NULL) { req->id2name = strdup(wksid->winname); if (req->id2name == NULL) return (IDMAP_ERR_MEMORY); } /* Fill in the canonical domain if not already there */ if (req->id2domain == NULL) { const char *dom; RDLOCK_CONFIG(); if (wksid->domain != NULL) dom = wksid->domain; else dom = _idmapdstate.hostname; req->id2domain = strdup(dom); UNLOCK_CONFIG(); if (req->id2domain == NULL) return (IDMAP_ERR_MEMORY); } res->direction = wksid->direction; res->info.how.map_type = IDMAP_MAP_TYPE_KNOWN_SID; res->info.src = IDMAP_MAP_SRC_HARD_CODED; return (IDMAP_SUCCESS); } /* * Look up a name in the wksids list, matching name and, if supplied, domain, * and extract data. * * Given: * name Windows user name * domain Windows domain name (or NULL) * * Return: Error code * * *canonname canonical name (if canonname non-NULL) [1] * *canondomain canonical domain (if canondomain non-NULL) [1] * *sidprefix SID prefix (if sidprefix non-NULL) [1] * *rid RID (if rid non-NULL) [2] * *type Type (if type non-NULL) [2] * * [1] malloc'ed, NULL on error * [2] Undefined on error */ idmap_retcode lookup_wksids_name2sid( const char *name, const char *domain, char **canonname, char **canondomain, char **sidprefix, idmap_rid_t *rid, idmap_id_type *type) { const wksids_table_t *wksid; if (sidprefix != NULL) *sidprefix = NULL; if (canonname != NULL) *canonname = NULL; if (canondomain != NULL) *canondomain = NULL; wksid = find_wksid_by_name(name, domain, IDMAP_POSIXID); if (wksid == NULL) return (IDMAP_ERR_NOTFOUND); if (sidprefix != NULL) { if (wksid->sidprefix != NULL) { *sidprefix = strdup(wksid->sidprefix); } else { RDLOCK_CONFIG(); *sidprefix = strdup( _idmapdstate.cfg->pgcfg.machine_sid); UNLOCK_CONFIG(); } if (*sidprefix == NULL) goto nomem; } if (rid != NULL) *rid = wksid->rid; if (canonname != NULL) { *canonname = strdup(wksid->winname); if (*canonname == NULL) goto nomem; } if (canondomain != NULL) { if (wksid->domain != NULL) { *canondomain = strdup(wksid->domain); } else { RDLOCK_CONFIG(); *canondomain = strdup(_idmapdstate.hostname); UNLOCK_CONFIG(); } if (*canondomain == NULL) goto nomem; } if (type != NULL) *type = (wksid->is_wuser) ? IDMAP_USID : IDMAP_GSID; return (IDMAP_SUCCESS); nomem: idmapdlog(LOG_ERR, "Out of memory"); if (sidprefix != NULL) { free(*sidprefix); *sidprefix = NULL; } if (canonname != NULL) { free(*canonname); *canonname = NULL; } if (canondomain != NULL) { free(*canondomain); *canondomain = NULL; } return (IDMAP_ERR_MEMORY); } static idmap_retcode lookup_cache_sid2pid(sqlite *cache, idmap_mapping *req, idmap_id_res *res) { char *end; char *sql = NULL; const char **values; sqlite_vm *vm = NULL; int ncol, is_user; uid_t pid; time_t curtime, exp; idmap_retcode retcode; char *is_user_string, *lower_name; /* Current time */ errno = 0; if ((curtime = time(NULL)) == (time_t)-1) { idmapdlog(LOG_ERR, "Failed to get current time (%s)", strerror(errno)); retcode = IDMAP_ERR_INTERNAL; goto out; } switch (res->id.idtype) { case IDMAP_UID: is_user_string = "1"; break; case IDMAP_GID: is_user_string = "0"; break; case IDMAP_POSIXID: /* the non-diagonal mapping */ is_user_string = "is_wuser"; break; default: retcode = IDMAP_ERR_NOTSUPPORTED; goto out; } /* SQL to lookup the cache */ if (req->id1.idmap_id_u.sid.prefix != NULL) { sql = sqlite_mprintf("SELECT pid, is_user, expiration, " "unixname, u2w, is_wuser, " "map_type, map_dn, map_attr, map_value, " "map_windomain, map_winname, map_unixname, map_is_nt4 " "FROM idmap_cache WHERE is_user = %s AND " "sidprefix = %Q AND rid = %u AND w2u = 1 AND " "(pid >= 2147483648 OR " "(expiration = 0 OR expiration ISNULL OR " "expiration > %d));", is_user_string, req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid, curtime); } else if (req->id1name != NULL) { if ((lower_name = tolower_u8(req->id1name)) == NULL) lower_name = req->id1name; sql = sqlite_mprintf("SELECT pid, is_user, expiration, " "unixname, u2w, is_wuser, " "map_type, map_dn, map_attr, map_value, " "map_windomain, map_winname, map_unixname, map_is_nt4 " "FROM idmap_cache WHERE is_user = %s AND " "winname = %Q AND windomain = %Q AND w2u = 1 AND " "(pid >= 2147483648 OR " "(expiration = 0 OR expiration ISNULL OR " "expiration > %d));", is_user_string, lower_name, req->id1domain, curtime); if (lower_name != req->id1name) free(lower_name); } else { retcode = IDMAP_ERR_ARG; goto out; } if (sql == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } retcode = sql_compile_n_step_once(cache, sql, &vm, &ncol, 14, &values); sqlite_freemem(sql); if (retcode == IDMAP_ERR_NOTFOUND) { goto out; } else if (retcode == IDMAP_SUCCESS) { /* sanity checks */ if (values[0] == NULL || values[1] == NULL) { retcode = IDMAP_ERR_CACHE; goto out; } pid = strtoul(values[0], &end, 10); is_user = strncmp(values[1], "0", 2) ? 1 : 0; if (is_user) { res->id.idtype = IDMAP_UID; res->id.idmap_id_u.uid = pid; } else { res->id.idtype = IDMAP_GID; res->id.idmap_id_u.gid = pid; } /* * We may have an expired ephemeral mapping. Consider * the expired entry as valid if we are not going to * perform name-based mapping. But do not renew the * expiration. * If we will be doing name-based mapping then store the * ephemeral pid in the result so that we can use it * if we end up doing dynamic mapping again. */ if (!DO_NOT_ALLOC_NEW_ID_MAPPING(req) && !AVOID_NAMESERVICE(req) && IDMAP_ID_IS_EPHEMERAL(pid) && values[2] != NULL) { exp = strtoll(values[2], &end, 10); if (exp && exp <= curtime) { /* Store the ephemeral pid */ res->direction = IDMAP_DIRECTION_BI; req->direction |= is_user ? _IDMAP_F_EXP_EPH_UID : _IDMAP_F_EXP_EPH_GID; retcode = IDMAP_ERR_NOTFOUND; } } } out: if (retcode == IDMAP_SUCCESS) { if (values[4] != NULL) res->direction = (strtol(values[4], &end, 10) == 0)? IDMAP_DIRECTION_W2U:IDMAP_DIRECTION_BI; else res->direction = IDMAP_DIRECTION_W2U; if (values[3] != NULL) { if (req->id2name != NULL) free(req->id2name); req->id2name = strdup(values[3]); if (req->id2name == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; } } req->id1.idtype = strncmp(values[5], "0", 2) ? IDMAP_USID : IDMAP_GSID; if (req->flag & IDMAP_REQ_FLG_MAPPING_INFO) { res->info.src = IDMAP_MAP_SRC_CACHE; res->info.how.map_type = strtoul(values[6], &end, 10); switch (res->info.how.map_type) { case IDMAP_MAP_TYPE_DS_AD: res->info.how.idmap_how_u.ad.dn = strdup(values[7]); res->info.how.idmap_how_u.ad.attr = strdup(values[8]); res->info.how.idmap_how_u.ad.value = strdup(values[9]); break; case IDMAP_MAP_TYPE_DS_NLDAP: res->info.how.idmap_how_u.nldap.dn = strdup(values[7]); res->info.how.idmap_how_u.nldap.attr = strdup(values[8]); res->info.how.idmap_how_u.nldap.value = strdup(values[9]); break; case IDMAP_MAP_TYPE_RULE_BASED: res->info.how.idmap_how_u.rule.windomain = strdup(values[10]); res->info.how.idmap_how_u.rule.winname = strdup(values[11]); res->info.how.idmap_how_u.rule.unixname = strdup(values[12]); res->info.how.idmap_how_u.rule.is_nt4 = strtoul(values[13], &end, 1); res->info.how.idmap_how_u.rule.is_user = is_user; res->info.how.idmap_how_u.rule.is_wuser = strtoul(values[5], &end, 1); break; case IDMAP_MAP_TYPE_EPHEMERAL: break; case IDMAP_MAP_TYPE_LOCAL_SID: break; case IDMAP_MAP_TYPE_KNOWN_SID: break; case IDMAP_MAP_TYPE_IDMU: res->info.how.idmap_how_u.idmu.dn = strdup(values[7]); res->info.how.idmap_how_u.idmu.attr = strdup(values[8]); res->info.how.idmap_how_u.idmu.value = strdup(values[9]); break; default: /* Unknown mapping type */ assert(FALSE); } } } if (vm != NULL) (void) sqlite_finalize(vm, NULL); return (retcode); } /* * Previous versions used two enumerations for representing types. * One of those has largely been eliminated, but was used in the * name cache table and so during an upgrade might still be visible. * In addition, the test suite prepopulates the cache with these values. * * This function translates those old values into the new values. * * This code deliberately does not use symbolic values for the legacy * values. This is the *only* place where they should be used. */ static idmap_id_type xlate_legacy_type(int type) { switch (type) { case -1004: /* _IDMAP_T_USER */ return (IDMAP_USID); case -1005: /* _IDMAP_T_GROUP */ return (IDMAP_GSID); default: return (type); } NOTE(NOTREACHED) } static idmap_retcode lookup_cache_sid2name(sqlite *cache, const char *sidprefix, idmap_rid_t rid, char **canonname, char **canondomain, idmap_id_type *type) { char *end; char *sql = NULL; const char **values; sqlite_vm *vm = NULL; int ncol; time_t curtime; idmap_retcode retcode = IDMAP_SUCCESS; /* Get current time */ errno = 0; if ((curtime = time(NULL)) == (time_t)-1) { idmapdlog(LOG_ERR, "Failed to get current time (%s)", strerror(errno)); retcode = IDMAP_ERR_INTERNAL; goto out; } /* SQL to lookup the cache */ sql = sqlite_mprintf("SELECT canon_name, domain, type " "FROM name_cache WHERE " "sidprefix = %Q AND rid = %u AND " "(expiration = 0 OR expiration ISNULL OR " "expiration > %d);", sidprefix, rid, curtime); if (sql == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } retcode = sql_compile_n_step_once(cache, sql, &vm, &ncol, 3, &values); sqlite_freemem(sql); if (retcode == IDMAP_SUCCESS) { if (type != NULL) { if (values[2] == NULL) { retcode = IDMAP_ERR_CACHE; goto out; } *type = xlate_legacy_type(strtol(values[2], &end, 10)); } if (canonname != NULL && values[0] != NULL) { if ((*canonname = strdup(values[0])) == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } } if (canondomain != NULL && values[1] != NULL) { if ((*canondomain = strdup(values[1])) == NULL) { if (canonname != NULL) { free(*canonname); *canonname = NULL; } idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } } } out: if (vm != NULL) (void) sqlite_finalize(vm, NULL); return (retcode); } /* * Given SID, find winname using name_cache OR * Given winname, find SID using name_cache. * Used when mapping win to unix i.e. req->id1 is windows id and * req->id2 is unix id */ static idmap_retcode lookup_name_cache(sqlite *cache, idmap_mapping *req, idmap_id_res *res) { idmap_id_type type = -1; idmap_retcode retcode; char *sidprefix = NULL; idmap_rid_t rid; char *name = NULL, *domain = NULL; /* Done if we've both sid and winname */ if (req->id1.idmap_id_u.sid.prefix != NULL && req->id1name != NULL) { /* Don't bother TRACE()ing, too boring */ return (IDMAP_SUCCESS); } if (req->id1.idmap_id_u.sid.prefix != NULL) { /* Lookup sid to winname */ retcode = lookup_cache_sid2name(cache, req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid, &name, &domain, &type); } else { /* Lookup winame to sid */ retcode = lookup_cache_name2sid(cache, req->id1name, req->id1domain, &name, &sidprefix, &rid, &type); } if (retcode != IDMAP_SUCCESS) { if (retcode == IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Not found in name cache"); } else { TRACE(req, res, "Name cache lookup error=%d", retcode); } free(name); free(domain); free(sidprefix); return (retcode); } req->id1.idtype = type; req->direction |= _IDMAP_F_DONT_UPDATE_NAMECACHE; /* * If we found canonical names or domain, use them instead of * the existing values. */ if (name != NULL) { free(req->id1name); req->id1name = name; } if (domain != NULL) { free(req->id1domain); req->id1domain = domain; } if (req->id1.idmap_id_u.sid.prefix == NULL) { req->id1.idmap_id_u.sid.prefix = sidprefix; req->id1.idmap_id_u.sid.rid = rid; } TRACE(req, res, "Found in name cache"); return (retcode); } static int ad_lookup_batch_int(lookup_state_t *state, idmap_mapping_batch *batch, idmap_ids_res *result, adutils_ad_t *dir, int how_local, int *num_processed) { idmap_retcode retcode; int i, num_queued, is_wuser, is_user; int next_request; int retries = 0, esidtype; char **unixname; idmap_mapping *req; idmap_id_res *res; idmap_query_state_t *qs = NULL; idmap_how *how; char **dn, **attr, **value; *num_processed = 0; /* * Since req->id2.idtype is unused, we will use it here * to retrieve the value of sid_type. But it needs to be * reset to IDMAP_NONE before we return to prevent xdr * from mis-interpreting req->id2 when it tries to free * the input argument. Other option is to allocate an * array of integers and use it instead for the batched * call. But why un-necessarily allocate memory. That may * be an option if req->id2.idtype cannot be re-used in * future. * * Similarly, we use req->id2.idmap_id_u.uid to return * uidNumber or gidNumber supplied by IDMU, and reset it * back to IDMAP_SENTINEL_PID when we're done. Note that * the query always puts the result in req->id2.idmap_id_u.uid, * not .gid. */ retry: retcode = idmap_lookup_batch_start(dir, state->ad_nqueries, state->directory_based_mapping, state->defdom, &qs); if (retcode != IDMAP_SUCCESS) { if (retcode == IDMAP_ERR_RETRIABLE_NET_ERR && retries++ < ADUTILS_DEF_NUM_RETRIES) goto retry; degrade_svc(1, "failed to create batch for AD lookup"); goto out; } num_queued = 0; restore_svc(); if (how_local & FOREST_IS_LOCAL) { /* * Directory based name mapping is only performed within the * joined forest. We don't trust other "trusted" * forests to provide DS-based name mapping information because * AD's definition of "cross-forest trust" does not encompass * this sort of behavior. */ idmap_lookup_batch_set_unixattr(qs, state->ad_unixuser_attr, state->ad_unixgroup_attr); } for (i = 0; i < batch->idmap_mapping_batch_len; i++) { req = &batch->idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; how = &res->info.how; retcode = IDMAP_SUCCESS; req->id2.idtype = IDMAP_NONE; req->id2.idmap_id_u.uid = IDMAP_SENTINEL_PID; /* Skip if no AD lookup required */ if (!(req->direction & _IDMAP_F_LOOKUP_AD)) continue; /* Skip if we've already tried and gotten a "not found" */ if (req->direction & _IDMAP_F_LOOKUP_OTHER_AD) continue; /* Skip if we've already either succeeded or failed */ if (res->retcode != IDMAP_ERR_RETRIABLE_NET_ERR) continue; if (IS_ID_SID(req->id1)) { /* win2unix request: */ posix_id_t *pid = NULL; unixname = dn = attr = value = NULL; esidtype = IDMAP_SID; if (state->directory_based_mapping == DIRECTORY_MAPPING_NAME && req->id2name == NULL) { if (res->id.idtype == IDMAP_UID && AD_OR_MIXED(state->nm_siduid)) { esidtype = IDMAP_USID; unixname = &req->id2name; } else if (res->id.idtype == IDMAP_GID && AD_OR_MIXED(state->nm_sidgid)) { esidtype = IDMAP_GSID; unixname = &req->id2name; } else if (AD_OR_MIXED(state->nm_siduid) || AD_OR_MIXED(state->nm_sidgid)) { unixname = &req->id2name; } if (unixname != NULL) { /* * Get how info for DS-based name * mapping only if AD or MIXED * mode is enabled. */ idmap_how_clear(&res->info.how); res->info.src = IDMAP_MAP_SRC_NEW; how->map_type = IDMAP_MAP_TYPE_DS_AD; dn = &how->idmap_how_u.ad.dn; attr = &how->idmap_how_u.ad.attr; value = &how->idmap_how_u.ad.value; } } else if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU && (how_local & DOMAIN_IS_LOCAL)) { /* * Ensure that we only do IDMU processing * when querying the domain we've joined. */ pid = &req->id2.idmap_id_u.uid; /* * Get how info for IDMU based mapping. */ idmap_how_clear(&res->info.how); res->info.src = IDMAP_MAP_SRC_NEW; how->map_type = IDMAP_MAP_TYPE_IDMU; dn = &how->idmap_how_u.idmu.dn; attr = &how->idmap_how_u.idmu.attr; value = &how->idmap_how_u.idmu.value; } if (req->id1.idmap_id_u.sid.prefix != NULL) { /* Lookup AD by SID */ retcode = idmap_sid2name_batch_add1( qs, req->id1.idmap_id_u.sid.prefix, &req->id1.idmap_id_u.sid.rid, esidtype, dn, attr, value, (req->id1name == NULL) ? &req->id1name : NULL, (req->id1domain == NULL) ? &req->id1domain : NULL, &req->id2.idtype, unixname, pid, &res->retcode); if (retcode == IDMAP_SUCCESS) num_queued++; } else { /* Lookup AD by winname */ assert(req->id1name != NULL); retcode = idmap_name2sid_batch_add1( qs, req->id1name, req->id1domain, esidtype, dn, attr, value, &req->id1name, &req->id1.idmap_id_u.sid.prefix, &req->id1.idmap_id_u.sid.rid, &req->id2.idtype, unixname, pid, &res->retcode); if (retcode == IDMAP_SUCCESS) num_queued++; } } else if (IS_ID_UID(req->id1) || IS_ID_GID(req->id1)) { /* unix2win request: */ if (res->id.idmap_id_u.sid.prefix != NULL && req->id2name != NULL) { /* Already have SID and winname. done */ res->retcode = IDMAP_SUCCESS; continue; } if (res->id.idmap_id_u.sid.prefix != NULL) { /* * SID but no winname -- lookup AD by * SID to get winname. * how info is not needed here because * we are not retrieving unixname from * AD. */ retcode = idmap_sid2name_batch_add1( qs, res->id.idmap_id_u.sid.prefix, &res->id.idmap_id_u.sid.rid, IDMAP_POSIXID, NULL, NULL, NULL, &req->id2name, &req->id2domain, &req->id2.idtype, NULL, NULL, &res->retcode); if (retcode == IDMAP_SUCCESS) num_queued++; } else if (req->id2name != NULL) { /* * winname but no SID -- lookup AD by * winname to get SID. * how info is not needed here because * we are not retrieving unixname from * AD. */ retcode = idmap_name2sid_batch_add1( qs, req->id2name, req->id2domain, IDMAP_POSIXID, NULL, NULL, NULL, NULL, &res->id.idmap_id_u.sid.prefix, &res->id.idmap_id_u.sid.rid, &req->id2.idtype, NULL, NULL, &res->retcode); if (retcode == IDMAP_SUCCESS) num_queued++; } else if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU && (how_local & DOMAIN_IS_LOCAL)) { assert(req->id1.idmap_id_u.uid != IDMAP_SENTINEL_PID); is_user = IS_ID_UID(req->id1); if (res->id.idtype == IDMAP_USID) is_wuser = 1; else if (res->id.idtype == IDMAP_GSID) is_wuser = 0; else is_wuser = is_user; /* IDMU can't do diagonal mappings */ if (is_user != is_wuser) continue; idmap_how_clear(&res->info.how); res->info.src = IDMAP_MAP_SRC_NEW; how->map_type = IDMAP_MAP_TYPE_IDMU; retcode = idmap_pid2sid_batch_add1( qs, req->id1.idmap_id_u.uid, is_user, &how->idmap_how_u.ad.dn, &how->idmap_how_u.ad.attr, &how->idmap_how_u.ad.value, &res->id.idmap_id_u.sid.prefix, &res->id.idmap_id_u.sid.rid, &req->id2name, &req->id2domain, &req->id2.idtype, &res->retcode); if (retcode == IDMAP_SUCCESS) num_queued++; } else if (req->id1name != NULL) { /* * No SID and no winname but we've unixname. * Lookup AD by unixname to get SID. */ is_user = (IS_ID_UID(req->id1)) ? 1 : 0; if (res->id.idtype == IDMAP_USID) is_wuser = 1; else if (res->id.idtype == IDMAP_GSID) is_wuser = 0; else is_wuser = is_user; idmap_how_clear(&res->info.how); res->info.src = IDMAP_MAP_SRC_NEW; how->map_type = IDMAP_MAP_TYPE_DS_AD; retcode = idmap_unixname2sid_batch_add1( qs, req->id1name, is_user, is_wuser, &how->idmap_how_u.ad.dn, &how->idmap_how_u.ad.attr, &how->idmap_how_u.ad.value, &res->id.idmap_id_u.sid.prefix, &res->id.idmap_id_u.sid.rid, &req->id2name, &req->id2domain, &req->id2.idtype, &res->retcode); if (retcode == IDMAP_SUCCESS) num_queued++; } } if (retcode == IDMAP_ERR_DOMAIN_NOTFOUND) { req->direction |= _IDMAP_F_LOOKUP_OTHER_AD; retcode = IDMAP_SUCCESS; } else if (retcode != IDMAP_SUCCESS) { break; } } /* End of for loop */ if (retcode == IDMAP_SUCCESS) { /* add keeps track if we added an entry to the batch */ if (num_queued > 0) retcode = idmap_lookup_batch_end(&qs); else idmap_lookup_release_batch(&qs); } else { idmap_lookup_release_batch(&qs); num_queued = 0; next_request = i + 1; } if (retcode == IDMAP_ERR_RETRIABLE_NET_ERR && retries++ < ADUTILS_DEF_NUM_RETRIES) goto retry; else if (retcode == IDMAP_ERR_RETRIABLE_NET_ERR) degrade_svc(1, "some AD lookups timed out repeatedly"); if (retcode != IDMAP_SUCCESS) { /* Mark any unproccessed requests for an other AD */ for (i = next_request; i < batch->idmap_mapping_batch_len; i++) { req = &batch->idmap_mapping_batch_val[i]; req->direction |= _IDMAP_F_LOOKUP_OTHER_AD; } } if (retcode != IDMAP_SUCCESS) idmapdlog(LOG_NOTICE, "Failed to batch AD lookup requests"); out: /* * This loop does the following: * 1. Reset _IDMAP_F_LOOKUP_AD flag from the request. * 2. Reset req->id2.idtype to IDMAP_NONE * 3. If batch_start or batch_add failed then set the status * of each request marked for AD lookup to that error. * 4. Evaluate the type of the AD object (i.e. user or group) * and update the idtype in request. */ for (i = 0; i < batch->idmap_mapping_batch_len; i++) { idmap_id_type type; uid_t posix_id; req = &batch->idmap_mapping_batch_val[i]; type = req->id2.idtype; req->id2.idtype = IDMAP_NONE; posix_id = req->id2.idmap_id_u.uid; req->id2.idmap_id_u.uid = IDMAP_SENTINEL_PID; res = &result->ids.ids_val[i]; /* * If it didn't need AD lookup, ignore it. */ if (!(req->direction & _IDMAP_F_LOOKUP_AD)) continue; /* * If we deferred it this time, reset for the next * AD server. */ if (req->direction & _IDMAP_F_LOOKUP_OTHER_AD) { req->direction &= ~_IDMAP_F_LOOKUP_OTHER_AD; continue; } /* Count number processed */ (*num_processed)++; /* Reset AD lookup flag */ req->direction &= ~(_IDMAP_F_LOOKUP_AD); /* * If batch_start or batch_add failed then set the * status of each request marked for AD lookup to * that error. */ if (retcode != IDMAP_SUCCESS) { res->retcode = retcode; continue; } if (res->retcode == IDMAP_ERR_NOTFOUND) { /* Nothing found - remove the preset info */ idmap_how_clear(&res->info.how); } if (IS_ID_SID(req->id1)) { if (res->retcode == IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Not found in AD"); continue; } if (res->retcode != IDMAP_SUCCESS) { TRACE(req, res, "AD lookup error=%d", res->retcode); continue; } /* Evaluate result type */ switch (type) { case IDMAP_USID: if (res->id.idtype == IDMAP_POSIXID) res->id.idtype = IDMAP_UID; /* * We found a user. If we got information * from IDMU and we were expecting a user, * copy the id. */ if (posix_id != IDMAP_SENTINEL_PID && res->id.idtype == IDMAP_UID) { res->id.idmap_id_u.uid = posix_id; res->direction = IDMAP_DIRECTION_BI; res->info.how.map_type = IDMAP_MAP_TYPE_IDMU; res->info.src = IDMAP_MAP_SRC_NEW; } req->id1.idtype = IDMAP_USID; break; case IDMAP_GSID: if (res->id.idtype == IDMAP_POSIXID) res->id.idtype = IDMAP_GID; /* * We found a group. If we got information * from IDMU and we were expecting a group, * copy the id. */ if (posix_id != IDMAP_SENTINEL_PID && res->id.idtype == IDMAP_GID) { res->id.idmap_id_u.gid = posix_id; res->direction = IDMAP_DIRECTION_BI; res->info.how.map_type = IDMAP_MAP_TYPE_IDMU; res->info.src = IDMAP_MAP_SRC_NEW; } req->id1.idtype = IDMAP_GSID; break; default: res->retcode = IDMAP_ERR_SID; break; } TRACE(req, res, "Found in AD"); if (res->retcode == IDMAP_SUCCESS && req->id1name != NULL && (req->id2name == NULL || res->id.idmap_id_u.uid == IDMAP_SENTINEL_PID) && NLDAP_MODE(res->id.idtype, state)) { req->direction |= _IDMAP_F_LOOKUP_NLDAP; state->nldap_nqueries++; } } else if (IS_ID_UID(req->id1) || IS_ID_GID(req->id1)) { if (res->retcode != IDMAP_SUCCESS) { if ((!(IDMAP_FATAL_ERROR(res->retcode))) && res->id.idmap_id_u.sid.prefix == NULL && req->id2name == NULL) { /* * If AD lookup by unixname or pid * failed with non fatal error * then clear the error (ie set * res->retcode to success). * This allows the next pass to * process other mapping * mechanisms for this request. */ if (res->retcode == IDMAP_ERR_NOTFOUND) { /* This is not an error */ res->retcode = IDMAP_SUCCESS; TRACE(req, res, "Not found in AD"); } else { TRACE(req, res, "AD lookup error (ignored)"); res->retcode = IDMAP_SUCCESS; } } else { TRACE(req, res, "AD lookup error"); } continue; } /* Evaluate result type */ switch (type) { case IDMAP_USID: case IDMAP_GSID: if (res->id.idtype == IDMAP_SID) res->id.idtype = type; break; default: res->retcode = IDMAP_ERR_SID; break; } TRACE(req, res, "Found in AD"); } } return (retcode); } /* * Batch AD lookups */ idmap_retcode ad_lookup_batch(lookup_state_t *state, idmap_mapping_batch *batch, idmap_ids_res *result) { idmap_retcode retcode; int i, j; idmap_mapping *req; idmap_id_res *res; int num_queries; int num_processed; if (state->ad_nqueries == 0) return (IDMAP_SUCCESS); for (i = 0; i < batch->idmap_mapping_batch_len; i++) { req = &batch->idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; /* Skip if not marked for AD lookup or already in error. */ if (!(req->direction & _IDMAP_F_LOOKUP_AD) || res->retcode != IDMAP_SUCCESS) continue; /* Init status */ res->retcode = IDMAP_ERR_RETRIABLE_NET_ERR; } RDLOCK_CONFIG(); num_queries = state->ad_nqueries; if (_idmapdstate.num_gcs == 0 && _idmapdstate.num_dcs == 0) { /* Case of no ADs */ retcode = IDMAP_ERR_NO_ACTIVEDIRECTORY; for (i = 0; i < batch->idmap_mapping_batch_len; i++) { req = &batch->idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; if (!(req->direction & _IDMAP_F_LOOKUP_AD)) continue; req->direction &= ~(_IDMAP_F_LOOKUP_AD); res->retcode = IDMAP_ERR_NO_ACTIVEDIRECTORY; } goto out; } if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU) { for (i = 0; i < _idmapdstate.num_dcs && num_queries > 0; i++) { retcode = ad_lookup_batch_int(state, batch, result, _idmapdstate.dcs[i], i == 0 ? DOMAIN_IS_LOCAL|FOREST_IS_LOCAL : 0, &num_processed); num_queries -= num_processed; } } for (i = 0; i < _idmapdstate.num_gcs && num_queries > 0; i++) { retcode = ad_lookup_batch_int(state, batch, result, _idmapdstate.gcs[i], i == 0 ? FOREST_IS_LOCAL : 0, &num_processed); num_queries -= num_processed; } /* * There are no more ADs to try. Return errors for any * remaining requests. */ if (num_queries > 0) { for (j = 0; j < batch->idmap_mapping_batch_len; j++) { req = &batch->idmap_mapping_batch_val[j]; res = &result->ids.ids_val[j]; if (!(req->direction & _IDMAP_F_LOOKUP_AD)) continue; req->direction &= ~(_IDMAP_F_LOOKUP_AD); res->retcode = IDMAP_ERR_DOMAIN_NOTFOUND; } } out: UNLOCK_CONFIG(); /* AD lookups done. Reset state->ad_nqueries and return */ state->ad_nqueries = 0; return (retcode); } /* * Convention when processing win2unix requests: * * Windows identity: * req->id1name = * winname if given otherwise winname found will be placed * here. * req->id1domain = * windomain if given otherwise windomain found will be * placed here. * req->id1.idtype = * Either IDMAP_SID/USID/GSID. If this is IDMAP_SID then it'll * be set to IDMAP_USID/GSID depending upon whether the * given SID is user or group respectively. The user/group-ness * is determined either when looking up well-known SIDs table OR * if the SID is found in namecache OR by ad_lookup_batch(). * req->id1..sid.[prefix, rid] = * SID if given otherwise SID found will be placed here. * * Unix identity: * req->id2name = * unixname found will be placed here. * req->id2domain = * NOT USED * res->id.idtype = * Target type initialized from req->id2.idtype. If * it is IDMAP_POSIXID then actual type (IDMAP_UID/GID) found * will be placed here. * res->id..[uid or gid] = * UID/GID found will be placed here. * * Others: * res->retcode = * Return status for this request will be placed here. * res->direction = * Direction found will be placed here. Direction * meaning whether the resultant mapping is valid * only from win2unix or bi-directional. * req->direction = * INTERNAL USE. Used by idmapd to set various * flags (_IDMAP_F_xxxx) to aid in processing * of the request. * req->id2.idtype = * INTERNAL USE. Initially this is the requested target * type and is used to initialize res->id.idtype. * ad_lookup_batch() uses this field temporarily to store * sid_type obtained by the batched AD lookups and after * use resets it to IDMAP_NONE to prevent xdr from * mis-interpreting the contents of req->id2. * req->id2.idmap_id_u.uid = * INTERNAL USE. If the AD lookup finds IDMU data * (uidNumber or gidNumber, depending on the type of * the entry), it's left here. */ /* * This function does the following: * 1. Lookup well-known SIDs table. * 2. Check if the given SID is a local-SID and if so extract UID/GID from it. * 3. Lookup cache. * 4. Check if the client does not want new mapping to be allocated * in which case this pass is the final pass. * 5. Set AD lookup flag if it determines that the next stage needs * to do AD lookup. */ idmap_retcode sid2pid_first_pass(lookup_state_t *state, idmap_mapping *req, idmap_id_res *res) { idmap_retcode retcode; int wksid; /* Initialize result */ res->id.idtype = req->id2.idtype; res->id.idmap_id_u.uid = IDMAP_SENTINEL_PID; res->direction = IDMAP_DIRECTION_UNDEF; wksid = 0; if (EMPTY_STRING(req->id1.idmap_id_u.sid.prefix)) { /* They have to give us *something* to work with! */ if (req->id1name == NULL) { retcode = IDMAP_ERR_ARG; goto out; } /* sanitize sidprefix */ free(req->id1.idmap_id_u.sid.prefix); req->id1.idmap_id_u.sid.prefix = NULL; /* Allow for a fully-qualified name in the "name" parameter */ if (req->id1domain == NULL) { char *p; p = strchr(req->id1name, '@'); if (p != NULL) { char *q; q = req->id1name; req->id1name = uu_strndup(q, p - req->id1name); req->id1domain = strdup(p+1); free(q); if (req->id1name == NULL || req->id1domain == NULL) { retcode = IDMAP_ERR_MEMORY; goto out; } } } } /* Lookup well-known SIDs table */ retcode = lookup_wksids_sid2pid(req, res, &wksid); if (retcode == IDMAP_SUCCESS) { /* Found a well-known account with a hardwired mapping */ TRACE(req, res, "Hardwired mapping"); goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Well-known account lookup failed, code %d", retcode); goto out; } if (wksid) { /* Found a well-known account, but no mapping */ TRACE(req, res, "Well-known account"); } else { TRACE(req, res, "Not a well-known account"); /* Check if this is a localsid */ retcode = lookup_localsid2pid(req, res); if (retcode == IDMAP_SUCCESS) { TRACE(req, res, "Local SID"); goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Local SID lookup error=%d", retcode); goto out; } TRACE(req, res, "Not a local SID"); if (ALLOW_WK_OR_LOCAL_SIDS_ONLY(req)) { retcode = IDMAP_ERR_NONE_GENERATED; goto out; } } /* * If this is a name-based request and we don't have a domain, * use the default domain. Note that the well-known identity * cases will have supplied a SID prefix already, and that we * don't (yet?) support looking up a local user through a Windows * style name. */ if (req->id1.idmap_id_u.sid.prefix == NULL && req->id1name != NULL && req->id1domain == NULL) { if (state->defdom == NULL) { retcode = IDMAP_ERR_DOMAIN_NOTFOUND; goto out; } req->id1domain = strdup(state->defdom); if (req->id1domain == NULL) { retcode = IDMAP_ERR_MEMORY; goto out; } TRACE(req, res, "Added default domain"); } /* Lookup cache */ retcode = lookup_cache_sid2pid(state->cache, req, res); if (retcode == IDMAP_SUCCESS) { TRACE(req, res, "Found in mapping cache"); goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Mapping cache lookup error=%d", retcode); goto out; } TRACE(req, res, "Not found in mapping cache"); if (DO_NOT_ALLOC_NEW_ID_MAPPING(req) || AVOID_NAMESERVICE(req)) { retcode = IDMAP_ERR_NONE_GENERATED; goto out; } /* * Failed to find non-expired entry in cache. Next step is * to determine if this request needs to be batched for AD lookup. * * At this point we have either sid or winname or both. If we don't * have both then lookup name_cache for the sid or winname * whichever is missing. If not found then this request will be * batched for AD lookup. */ retcode = lookup_name_cache(state->cache, req, res); if (retcode == IDMAP_SUCCESS) { if (res->id.idtype == IDMAP_POSIXID) { if (req->id1.idtype == IDMAP_USID) res->id.idtype = IDMAP_UID; else res->id.idtype = IDMAP_GID; } } else if (retcode != IDMAP_ERR_NOTFOUND) goto out; if (_idmapdstate.cfg->pgcfg.use_lsa && _idmapdstate.cfg->pgcfg.domain_name != NULL) { /* * If we don't have both name and SID, try looking up the * entry with LSA. */ if (req->id1.idmap_id_u.sid.prefix != NULL && req->id1name == NULL) { retcode = lookup_lsa_by_sid( req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid, &req->id1name, &req->id1domain, &req->id1.idtype); if (retcode == IDMAP_SUCCESS) { TRACE(req, res, "Found with LSA"); } else if (retcode == IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Not found with LSA"); } else { TRACE(req, res, "LSA error %d", retcode); goto out; } } else if (req->id1name != NULL && req->id1.idmap_id_u.sid.prefix == NULL) { char *canonname; char *canondomain; retcode = lookup_lsa_by_name( req->id1name, req->id1domain, &req->id1.idmap_id_u.sid.prefix, &req->id1.idmap_id_u.sid.rid, &canonname, &canondomain, &req->id1.idtype); if (retcode == IDMAP_SUCCESS) { free(req->id1name); req->id1name = canonname; free(req->id1domain); req->id1domain = canondomain; TRACE(req, res, "Found with LSA"); } else if (retcode == IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Not found with LSA"); } else { TRACE(req, res, "LSA error %d", retcode); goto out; } } } /* * Set the flag to indicate that we are not done yet so that * subsequent passes considers this request for name-based * mapping and ephemeral mapping. */ state->sid2pid_done = FALSE; req->direction |= _IDMAP_F_NOTDONE; /* * Even if we have both sid and winname, we still may need to batch * this request for AD lookup if we don't have unixname and * directory-based name mapping (AD or mixed) is enabled. * We avoid AD lookup for well-known SIDs because they don't have * regular AD objects. */ if (retcode != IDMAP_SUCCESS || (!wksid && req->id2name == NULL && AD_OR_MIXED_MODE(res->id.idtype, state)) || (!wksid && res->id.idmap_id_u.uid == IDMAP_SENTINEL_PID && state->directory_based_mapping == DIRECTORY_MAPPING_IDMU)) { retcode = IDMAP_SUCCESS; req->direction |= _IDMAP_F_LOOKUP_AD; state->ad_nqueries++; } else if (NLDAP_MODE(res->id.idtype, state)) { req->direction |= _IDMAP_F_LOOKUP_NLDAP; state->nldap_nqueries++; } out: res->retcode = idmap_stat4prot(retcode); /* * If we are done and there was an error then set fallback pid * in the result. */ if (ARE_WE_DONE(req->direction) && res->retcode != IDMAP_SUCCESS) res->id.idmap_id_u.uid = UID_NOBODY; return (retcode); } /* * Generate SID using the following convention * -<1000 + uid> * -<2^31 + gid> */ static idmap_retcode generate_localsid(idmap_mapping *req, idmap_id_res *res, int is_user, int fallback) { free(res->id.idmap_id_u.sid.prefix); res->id.idmap_id_u.sid.prefix = NULL; /* * Diagonal mapping for localSIDs not supported because of the * way we generate localSIDs. */ if (is_user && res->id.idtype == IDMAP_GSID) return (IDMAP_ERR_NOTGROUP); if (!is_user && res->id.idtype == IDMAP_USID) return (IDMAP_ERR_NOTUSER); /* Skip 1000 UIDs */ if (is_user && req->id1.idmap_id_u.uid + LOCALRID_UID_MIN > LOCALRID_UID_MAX) return (IDMAP_ERR_NOMAPPING); RDLOCK_CONFIG(); /* * machine_sid is never NULL because if it is we won't be here. * No need to assert because strdup(NULL) will core anyways. */ res->id.idmap_id_u.sid.prefix = strdup(_idmapdstate.cfg->pgcfg.machine_sid); if (res->id.idmap_id_u.sid.prefix == NULL) { UNLOCK_CONFIG(); idmapdlog(LOG_ERR, "Out of memory"); return (IDMAP_ERR_MEMORY); } UNLOCK_CONFIG(); res->id.idmap_id_u.sid.rid = (is_user) ? req->id1.idmap_id_u.uid + LOCALRID_UID_MIN : req->id1.idmap_id_u.gid + LOCALRID_GID_MIN; res->direction = IDMAP_DIRECTION_BI; if (res->id.idtype == IDMAP_SID) res->id.idtype = is_user ? IDMAP_USID : IDMAP_GSID; if (!fallback) { res->info.how.map_type = IDMAP_MAP_TYPE_LOCAL_SID; res->info.src = IDMAP_MAP_SRC_ALGORITHMIC; } /* * Don't update name_cache because local sids don't have * valid windows names. */ req->direction |= _IDMAP_F_DONT_UPDATE_NAMECACHE; return (IDMAP_SUCCESS); } static idmap_retcode lookup_localsid2pid(idmap_mapping *req, idmap_id_res *res) { char *sidprefix; uint32_t rid; int s; /* * If the sidprefix == localsid then UID = last RID - 1000 or * GID = last RID - 2^31. */ if ((sidprefix = req->id1.idmap_id_u.sid.prefix) == NULL) /* This means we are looking up by winname */ return (IDMAP_ERR_NOTFOUND); rid = req->id1.idmap_id_u.sid.rid; RDLOCK_CONFIG(); s = (_idmapdstate.cfg->pgcfg.machine_sid) ? strcasecmp(sidprefix, _idmapdstate.cfg->pgcfg.machine_sid) : 1; UNLOCK_CONFIG(); /* * If the given sidprefix does not match machine_sid then this is * not a local SID. */ if (s != 0) return (IDMAP_ERR_NOTFOUND); switch (res->id.idtype) { case IDMAP_UID: if (rid < LOCALRID_UID_MIN || rid > LOCALRID_UID_MAX) return (IDMAP_ERR_ARG); res->id.idmap_id_u.uid = rid - LOCALRID_UID_MIN; break; case IDMAP_GID: if (rid < LOCALRID_GID_MIN) return (IDMAP_ERR_ARG); res->id.idmap_id_u.gid = rid - LOCALRID_GID_MIN; break; case IDMAP_POSIXID: if (rid >= LOCALRID_GID_MIN) { res->id.idmap_id_u.gid = rid - LOCALRID_GID_MIN; res->id.idtype = IDMAP_GID; } else if (rid >= LOCALRID_UID_MIN) { res->id.idmap_id_u.uid = rid - LOCALRID_UID_MIN; res->id.idtype = IDMAP_UID; } else { return (IDMAP_ERR_ARG); } break; default: return (IDMAP_ERR_NOTSUPPORTED); } res->info.how.map_type = IDMAP_MAP_TYPE_LOCAL_SID; res->info.src = IDMAP_MAP_SRC_ALGORITHMIC; return (IDMAP_SUCCESS); } /* * Name service lookup by unixname to get pid */ static idmap_retcode ns_lookup_byname(const char *name, const char *lower_name, idmap_id *id) { struct passwd pwd, *pwdp; struct group grp, *grpp; char *buf; static size_t pwdbufsiz = 0; static size_t grpbufsiz = 0; switch (id->idtype) { case IDMAP_UID: if (pwdbufsiz == 0) pwdbufsiz = sysconf(_SC_GETPW_R_SIZE_MAX); buf = alloca(pwdbufsiz); pwdp = getpwnam_r(name, &pwd, buf, pwdbufsiz); if (pwdp == NULL && errno == 0 && lower_name != NULL && name != lower_name && strcmp(name, lower_name) != 0) pwdp = getpwnam_r(lower_name, &pwd, buf, pwdbufsiz); if (pwdp == NULL) { if (errno == 0) return (IDMAP_ERR_NOTFOUND); else return (IDMAP_ERR_INTERNAL); } id->idmap_id_u.uid = pwd.pw_uid; break; case IDMAP_GID: if (grpbufsiz == 0) grpbufsiz = sysconf(_SC_GETGR_R_SIZE_MAX); buf = alloca(grpbufsiz); grpp = getgrnam_r(name, &grp, buf, grpbufsiz); if (grpp == NULL && errno == 0 && lower_name != NULL && name != lower_name && strcmp(name, lower_name) != 0) grpp = getgrnam_r(lower_name, &grp, buf, grpbufsiz); if (grpp == NULL) { if (errno == 0) return (IDMAP_ERR_NOTFOUND); else return (IDMAP_ERR_INTERNAL); } id->idmap_id_u.gid = grp.gr_gid; break; default: return (IDMAP_ERR_ARG); } return (IDMAP_SUCCESS); } /* * Name service lookup by pid to get unixname */ static idmap_retcode ns_lookup_bypid(uid_t pid, int is_user, char **unixname) { struct passwd pwd; struct group grp; char *buf; static size_t pwdbufsiz = 0; static size_t grpbufsiz = 0; if (is_user) { if (pwdbufsiz == 0) pwdbufsiz = sysconf(_SC_GETPW_R_SIZE_MAX); buf = alloca(pwdbufsiz); errno = 0; if (getpwuid_r(pid, &pwd, buf, pwdbufsiz) == NULL) { if (errno == 0) return (IDMAP_ERR_NOTFOUND); else return (IDMAP_ERR_INTERNAL); } *unixname = strdup(pwd.pw_name); } else { if (grpbufsiz == 0) grpbufsiz = sysconf(_SC_GETGR_R_SIZE_MAX); buf = alloca(grpbufsiz); errno = 0; if (getgrgid_r(pid, &grp, buf, grpbufsiz) == NULL) { if (errno == 0) return (IDMAP_ERR_NOTFOUND); else return (IDMAP_ERR_INTERNAL); } *unixname = strdup(grp.gr_name); } if (*unixname == NULL) return (IDMAP_ERR_MEMORY); return (IDMAP_SUCCESS); } /* * Name-based mapping * * Case 1: If no rule matches do ephemeral * * Case 2: If rule matches and unixname is "" then return no mapping. * * Case 3: If rule matches and unixname is specified then lookup name * service using the unixname. If unixname not found then return no mapping. * * Case 4: If rule matches and unixname is * then lookup name service * using winname as the unixname. If unixname not found then process * other rules using the lookup order. If no other rule matches then do * ephemeral. Otherwise, based on the matched rule do Case 2 or 3 or 4. * This allows us to specify a fallback unixname per _domain_ or no mapping * instead of the default behaviour of doing ephemeral mapping. * * Example 1: * *@sfbay == * * If looking up windows users foo@sfbay and foo does not exists in * the name service then foo@sfbay will be mapped to an ephemeral id. * * Example 2: * *@sfbay == * * *@sfbay => guest * If looking up windows users foo@sfbay and foo does not exists in * the name service then foo@sfbay will be mapped to guest. * * Example 3: * *@sfbay == * * *@sfbay => "" * If looking up windows users foo@sfbay and foo does not exists in * the name service then we will return no mapping for foo@sfbay. * */ static idmap_retcode name_based_mapping_sid2pid(lookup_state_t *state, idmap_mapping *req, idmap_id_res *res) { const char *unixname, *windomain; char *sql = NULL, *errmsg = NULL, *lower_winname = NULL; idmap_retcode retcode; char *end, *lower_unixname, *winname; const char **values; sqlite_vm *vm = NULL; int ncol, r, is_user, is_wuser; idmap_namerule *rule = &res->info.how.idmap_how_u.rule; int direction; const char *me = "name_based_mapping_sid2pid"; assert(req->id1name != NULL); /* We have winname */ assert(req->id2name == NULL); /* We don't have unixname */ winname = req->id1name; windomain = req->id1domain; switch (req->id1.idtype) { case IDMAP_USID: is_wuser = 1; break; case IDMAP_GSID: is_wuser = 0; break; default: idmapdlog(LOG_ERR, "%s: Unable to determine if the " "given Windows id is user or group.", me); return (IDMAP_ERR_INTERNAL); } switch (res->id.idtype) { case IDMAP_UID: is_user = 1; break; case IDMAP_GID: is_user = 0; break; case IDMAP_POSIXID: is_user = is_wuser; res->id.idtype = is_user ? IDMAP_UID : IDMAP_GID; break; } if (windomain == NULL) windomain = ""; if ((lower_winname = tolower_u8(winname)) == NULL) lower_winname = winname; /* hope for the best */ sql = sqlite_mprintf( "SELECT unixname, u2w_order, winname_display, windomain, is_nt4 " "FROM namerules WHERE " "w2u_order > 0 AND is_user = %d AND is_wuser = %d AND " "(winname = %Q OR winname = '*') AND " "(windomain = %Q OR windomain = '*') " "ORDER BY w2u_order ASC;", is_user, is_wuser, lower_winname, windomain); if (sql == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } if (sqlite_compile(state->db, sql, NULL, &vm, &errmsg) != SQLITE_OK) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "%s: database error (%s)", me, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); goto out; } for (;;) { r = sqlite_step(vm, &ncol, &values, NULL); assert(r != SQLITE_LOCKED && r != SQLITE_BUSY); if (r == SQLITE_ROW) { if (ncol < 5) { retcode = IDMAP_ERR_INTERNAL; goto out; } TRACE(req, res, "Matching rule: %s@%s -> %s", values[2] == NULL ? "(null)" : values[2], values[3] == NULL ? "(null)" : values[3], values[0] == NULL ? "(null)" : values[0]); if (values[0] == NULL) { retcode = IDMAP_ERR_INTERNAL; goto out; } if (values[1] != NULL) direction = (strtol(values[1], &end, 10) == 0)? IDMAP_DIRECTION_W2U:IDMAP_DIRECTION_BI; else direction = IDMAP_DIRECTION_W2U; if (EMPTY_NAME(values[0])) { TRACE(req, res, "Mapping inhibited"); idmap_namerule_set(rule, values[3], values[2], values[0], is_user, is_wuser, strtol(values[4], &end, 10), direction); retcode = IDMAP_ERR_NOMAPPING; goto out; } if (values[0][0] == '*') { unixname = winname; lower_unixname = lower_winname; } else { unixname = values[0]; lower_unixname = NULL; } retcode = ns_lookup_byname(unixname, lower_unixname, &res->id); if (retcode == IDMAP_SUCCESS) { break; } else if (retcode == IDMAP_ERR_NOTFOUND) { if (values[0][0] == '*') { TRACE(req, res, "%s not found, continuing", unixname); /* Case 4 */ continue; } else { TRACE(req, res, "%s not found, error", unixname); /* Case 3 */ idmap_namerule_set(rule, values[3], values[2], values[0], is_user, is_wuser, strtol(values[4], &end, 10), direction); retcode = IDMAP_ERR_NOMAPPING; } } else { TRACE(req, res, "Looking up %s error=%d", unixname, retcode); } goto out; } else if (r == SQLITE_DONE) { TRACE(req, res, "No matching rule"); retcode = IDMAP_ERR_NOTFOUND; goto out; } else { (void) sqlite_finalize(vm, &errmsg); vm = NULL; idmapdlog(LOG_ERR, "%s: database error (%s)", me, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); retcode = IDMAP_ERR_INTERNAL; goto out; } } /* Found */ if (values[1] != NULL) res->direction = (strtol(values[1], &end, 10) == 0)? IDMAP_DIRECTION_W2U:IDMAP_DIRECTION_BI; else res->direction = IDMAP_DIRECTION_W2U; req->id2name = strdup(unixname); if (req->id2name == NULL) { retcode = IDMAP_ERR_MEMORY; goto out; } TRACE(req, res, "UNIX name found"); idmap_namerule_set(rule, values[3], values[2], values[0], is_user, is_wuser, strtol(values[4], &end, 10), res->direction); out: if (retcode != IDMAP_SUCCESS && retcode != IDMAP_ERR_NOTFOUND && retcode != IDMAP_ERR_NOMAPPING) { TRACE(req, res, "Rule processing error, code=%d", retcode); } if (sql != NULL) sqlite_freemem(sql); if (retcode != IDMAP_ERR_NOTFOUND) { res->info.how.map_type = IDMAP_MAP_TYPE_RULE_BASED; res->info.src = IDMAP_MAP_SRC_NEW; } if (lower_winname != NULL && lower_winname != winname) free(lower_winname); if (vm != NULL) (void) sqlite_finalize(vm, NULL); return (retcode); } static int get_next_eph_uid(uid_t *next_uid) { uid_t uid; gid_t gid; int err; *next_uid = (uid_t)-1; pthread_mutex_lock(&_idmapdstate.id_lock); uid = _idmapdstate.next_uid++; if (uid >= _idmapdstate.limit_uid) { if ((err = allocids(0, 8192, &uid, 0, &gid)) != 0) { pthread_mutex_unlock(&_idmapdstate.id_lock); return (err); } _idmapdstate.limit_uid = uid + 8192; _idmapdstate.next_uid = uid + 1; } pthread_mutex_unlock(&_idmapdstate.id_lock); *next_uid = uid; return (0); } static int get_next_eph_gid(gid_t *next_gid) { uid_t uid; gid_t gid; int err; *next_gid = (uid_t)-1; pthread_mutex_lock(&_idmapdstate.id_lock); gid = _idmapdstate.next_gid++; if (gid >= _idmapdstate.limit_gid) { if ((err = allocids(0, 0, &uid, 8192, &gid)) != 0) { pthread_mutex_unlock(&_idmapdstate.id_lock); return (err); } _idmapdstate.limit_gid = gid + 8192; _idmapdstate.next_gid = gid + 1; } pthread_mutex_unlock(&_idmapdstate.id_lock); *next_gid = gid; return (0); } static int gethash(const char *str, uint32_t num, uint_t htsize) { uint_t hval, i, len; if (str == NULL) return (0); for (len = strlen(str), hval = 0, i = 0; i < len; i++) { hval += str[i]; hval += (hval << 10); hval ^= (hval >> 6); } for (str = (const char *)&num, i = 0; i < sizeof (num); i++) { hval += str[i]; hval += (hval << 10); hval ^= (hval >> 6); } hval += (hval << 3); hval ^= (hval >> 11); hval += (hval << 15); return (hval % htsize); } static int get_from_sid_history(lookup_state_t *state, const char *prefix, uint32_t rid, uid_t *pid) { uint_t next, key; uint_t htsize = state->sid_history_size; idmap_sid *sid; next = gethash(prefix, rid, htsize); while (next != htsize) { key = state->sid_history[next].key; if (key == htsize) return (0); sid = &state->batch->idmap_mapping_batch_val[key].id1. idmap_id_u.sid; if (sid->rid == rid && strcmp(sid->prefix, prefix) == 0) { *pid = state->result->ids.ids_val[key].id. idmap_id_u.uid; return (1); } next = state->sid_history[next].next; } return (0); } static void add_to_sid_history(lookup_state_t *state, const char *prefix, uint32_t rid) { uint_t hash, next; uint_t htsize = state->sid_history_size; hash = next = gethash(prefix, rid, htsize); while (state->sid_history[next].key != htsize) { next++; next %= htsize; } state->sid_history[next].key = state->curpos; if (hash == next) return; state->sid_history[next].next = state->sid_history[hash].next; state->sid_history[hash].next = next; } void cleanup_lookup_state(lookup_state_t *state) { free(state->sid_history); free(state->ad_unixuser_attr); free(state->ad_unixgroup_attr); free(state->nldap_winname_attr); free(state->defdom); } /* ARGSUSED */ static idmap_retcode dynamic_ephemeral_mapping(lookup_state_t *state, idmap_mapping *req, idmap_id_res *res) { uid_t next_pid; res->direction = IDMAP_DIRECTION_BI; if (IDMAP_ID_IS_EPHEMERAL(res->id.idmap_id_u.uid)) { res->info.how.map_type = IDMAP_MAP_TYPE_EPHEMERAL; res->info.src = IDMAP_MAP_SRC_CACHE; return (IDMAP_SUCCESS); } if (state->sid_history != NULL && get_from_sid_history(state, req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid, &next_pid)) { res->id.idmap_id_u.uid = next_pid; res->info.how.map_type = IDMAP_MAP_TYPE_EPHEMERAL; res->info.src = IDMAP_MAP_SRC_NEW; return (IDMAP_SUCCESS); } if (res->id.idtype == IDMAP_UID) { if (get_next_eph_uid(&next_pid) != 0) return (IDMAP_ERR_INTERNAL); res->id.idmap_id_u.uid = next_pid; } else { if (get_next_eph_gid(&next_pid) != 0) return (IDMAP_ERR_INTERNAL); res->id.idmap_id_u.gid = next_pid; } res->info.how.map_type = IDMAP_MAP_TYPE_EPHEMERAL; res->info.src = IDMAP_MAP_SRC_NEW; if (state->sid_history != NULL) add_to_sid_history(state, req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid); return (IDMAP_SUCCESS); } idmap_retcode sid2pid_second_pass(lookup_state_t *state, idmap_mapping *req, idmap_id_res *res) { idmap_retcode retcode; idmap_retcode retcode2; /* Check if second pass is needed */ if (ARE_WE_DONE(req->direction)) return (res->retcode); /* Get status from previous pass */ retcode = res->retcode; if (retcode != IDMAP_SUCCESS && state->eph_map_unres_sids && !EMPTY_STRING(req->id1.idmap_id_u.sid.prefix) && EMPTY_STRING(req->id1name)) { /* * We are asked to map an unresolvable SID to a UID or * GID, but, which? We'll treat all unresolvable SIDs * as users unless the caller specified which of a UID * or GID they want. */ if (req->id1.idtype == IDMAP_SID) req->id1.idtype = IDMAP_USID; if (res->id.idtype == IDMAP_POSIXID) { res->id.idtype = IDMAP_UID; TRACE(req, res, "Assume unresolvable SID is user"); } else if (res->id.idtype == IDMAP_UID) { TRACE(req, res, "Must map unresolvable SID to user"); } else if (res->id.idtype == IDMAP_GID) { TRACE(req, res, "Must map unresolvable SID to group"); } goto do_eph; } if (retcode != IDMAP_SUCCESS) goto out; /* * There are two ways we might get here with a Posix ID: * - It could be from an expired ephemeral cache entry. * - It could be from IDMU. * If it's from IDMU, we need to look up the name, for name-based * requests and the cache. */ if (!IDMAP_ID_IS_EPHEMERAL(res->id.idmap_id_u.uid) && res->id.idmap_id_u.uid != IDMAP_SENTINEL_PID) { if (req->id2name == NULL) { /* * If the lookup fails, go ahead anyway. * The general UNIX rule is that it's OK to * have a UID or GID that isn't in the * name service. */ retcode2 = ns_lookup_bypid(res->id.idmap_id_u.uid, res->id.idtype == IDMAP_UID, &req->id2name); if (IDMAP_ERROR(retcode2)) { TRACE(req, res, "Getting UNIX name, error=%d (ignored)", retcode2); } else { TRACE(req, res, "Found UNIX name"); } } goto out; } /* * If directory-based name mapping is enabled then the unixname * may already have been retrieved from the AD object (AD-mode or * mixed-mode) or from native LDAP object (nldap-mode) -- done. */ if (req->id2name != NULL) { assert(res->id.idtype != IDMAP_POSIXID); if (AD_MODE(res->id.idtype, state)) res->direction = IDMAP_DIRECTION_BI; else if (NLDAP_MODE(res->id.idtype, state)) res->direction = IDMAP_DIRECTION_BI; else if (MIXED_MODE(res->id.idtype, state)) res->direction = IDMAP_DIRECTION_W2U; /* * Special case: (1) If the ad_unixuser_attr and * ad_unixgroup_attr uses the same attribute * name and (2) if this is a diagonal mapping * request and (3) the unixname has been retrieved * from the AD object -- then we ignore it and fallback * to name-based mapping rules and ephemeral mapping * * Example: * Properties: * config/ad_unixuser_attr = "unixname" * config/ad_unixgroup_attr = "unixname" * AD user object: * dn: cn=bob ... * objectclass: user * sam: bob * unixname: bob1234 * AD group object: * dn: cn=winadmins ... * objectclass: group * sam: winadmins * unixname: unixadmins * * In this example whether "unixname" refers to a unixuser * or unixgroup depends upon the AD object. * * $idmap show -c winname:bob gid * AD lookup by "samAccountName=bob" for * "ad_unixgroup_attr (i.e unixname)" for directory-based * mapping would get "bob1234" which is not what we want. * Now why not getgrnam_r("bob1234") and use it if it * is indeed a unixgroup? That's because Unix can have * users and groups with the same name and we clearly * don't know the intention of the admin here. * Therefore we ignore this and fallback to name-based * mapping rules or ephemeral mapping. */ if ((AD_MODE(res->id.idtype, state) || MIXED_MODE(res->id.idtype, state)) && state->ad_unixuser_attr != NULL && state->ad_unixgroup_attr != NULL && strcasecmp(state->ad_unixuser_attr, state->ad_unixgroup_attr) == 0 && ((req->id1.idtype == IDMAP_USID && res->id.idtype == IDMAP_GID) || (req->id1.idtype == IDMAP_GSID && res->id.idtype == IDMAP_UID))) { TRACE(req, res, "Ignoring UNIX name found in AD"); free(req->id2name); req->id2name = NULL; res->id.idmap_id_u.uid = IDMAP_SENTINEL_PID; /* fallback */ } else { if (res->id.idmap_id_u.uid == IDMAP_SENTINEL_PID) { retcode = ns_lookup_byname(req->id2name, NULL, &res->id); if (retcode != IDMAP_SUCCESS) { /* * If ns_lookup_byname() fails that * means the unixname (req->id2name), * which was obtained from the AD * object by directory-based mapping, * is not a valid Unix user/group and * therefore we return the error to the * client instead of doing rule-based * mapping or ephemeral mapping. This * way the client can detect the issue. */ TRACE(req, res, "UNIX lookup error=%d", retcode); goto out; } TRACE(req, res, "UNIX lookup"); } goto out; } } /* Free any mapping info from Directory based mapping */ if (res->info.how.map_type != IDMAP_MAP_TYPE_UNKNOWN) idmap_how_clear(&res->info.how); /* * If we don't have unixname then evaluate local name-based * mapping rules. */ retcode = name_based_mapping_sid2pid(state, req, res); if (retcode == IDMAP_SUCCESS) { TRACE(req, res, "Rule-based mapping"); goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Rule-based mapping error=%d", retcode); goto out; } do_eph: /* If not found, do ephemeral mapping */ retcode = dynamic_ephemeral_mapping(state, req, res); if (retcode == IDMAP_SUCCESS) { TRACE(req, res, "Ephemeral mapping"); goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Ephemeral mapping error=%d", retcode); goto out; } out: res->retcode = idmap_stat4prot(retcode); if (res->retcode != IDMAP_SUCCESS) { req->direction = _IDMAP_F_DONE; res->id.idmap_id_u.uid = UID_NOBODY; } if (!ARE_WE_DONE(req->direction)) state->sid2pid_done = FALSE; return (retcode); } idmap_retcode update_cache_pid2sid(lookup_state_t *state, idmap_mapping *req, idmap_id_res *res) { char *sql = NULL; idmap_retcode retcode; idmap_retcode retcode2; char *map_dn = NULL; char *map_attr = NULL; char *map_value = NULL; char *map_windomain = NULL; char *map_winname = NULL; char *map_unixname = NULL; int map_is_nt4 = FALSE; /* Check if we need to cache anything */ if (ARE_WE_DONE(req->direction)) return (IDMAP_SUCCESS); /* We don't cache negative entries */ if (res->retcode != IDMAP_SUCCESS) return (IDMAP_SUCCESS); assert(res->direction != IDMAP_DIRECTION_UNDEF); assert(req->id1.idmap_id_u.uid != IDMAP_SENTINEL_PID); assert(res->id.idtype != IDMAP_SID); /* * If we've gotten to this point and we *still* don't know the * unixname, well, we'd like to have it now for the cache. * * If we truly always need it for the cache, we should probably * look it up once at the beginning, rather than "at need" in * several places as is now done. However, it's not really clear * that we *do* need it in the cache; there's a decent argument * that the cache should contain only SIDs and PIDs, so we'll * leave our options open by doing it "at need" here too. * * If we can't find it... c'est la vie. */ if (req->id1name == NULL) { retcode2 = ns_lookup_bypid(req->id1.idmap_id_u.uid, req->id1.idtype == IDMAP_UID, &req->id1name); if (retcode2 == IDMAP_SUCCESS) TRACE(req, res, "Found UNIX name"); else TRACE(req, res, "Getting UNIX name error=%d", retcode2); } assert(res->info.how.map_type != IDMAP_MAP_TYPE_UNKNOWN); switch (res->info.how.map_type) { case IDMAP_MAP_TYPE_DS_AD: map_dn = res->info.how.idmap_how_u.ad.dn; map_attr = res->info.how.idmap_how_u.ad.attr; map_value = res->info.how.idmap_how_u.ad.value; break; case IDMAP_MAP_TYPE_DS_NLDAP: map_dn = res->info.how.idmap_how_u.nldap.dn; map_attr = res->info.how.idmap_how_u.nldap.attr; map_value = res->info.how.idmap_how_u.nldap.value; break; case IDMAP_MAP_TYPE_RULE_BASED: map_windomain = res->info.how.idmap_how_u.rule.windomain; map_winname = res->info.how.idmap_how_u.rule.winname; map_unixname = res->info.how.idmap_how_u.rule.unixname; map_is_nt4 = res->info.how.idmap_how_u.rule.is_nt4; break; case IDMAP_MAP_TYPE_EPHEMERAL: break; case IDMAP_MAP_TYPE_LOCAL_SID: break; case IDMAP_MAP_TYPE_IDMU: map_dn = res->info.how.idmap_how_u.idmu.dn; map_attr = res->info.how.idmap_how_u.idmu.attr; map_value = res->info.how.idmap_how_u.idmu.value; break; default: /* Don't cache other mapping types */ assert(FALSE); } /* * Using NULL for u2w instead of 0 so that our trigger allows * the same pid to be the destination in multiple entries */ sql = sqlite_mprintf("INSERT OR REPLACE into idmap_cache " "(sidprefix, rid, windomain, canon_winname, pid, unixname, " "is_user, is_wuser, expiration, w2u, u2w, " "map_type, map_dn, map_attr, map_value, map_windomain, " "map_winname, map_unixname, map_is_nt4) " "VALUES(%Q, %u, %Q, %Q, %u, %Q, %d, %d, " "strftime('%%s','now') + %u, %q, 1, " "%d, %Q, %Q, %Q, %Q, %Q, %Q, %d); ", res->id.idmap_id_u.sid.prefix, res->id.idmap_id_u.sid.rid, req->id2domain, req->id2name, req->id1.idmap_id_u.uid, req->id1name, (req->id1.idtype == IDMAP_UID) ? 1 : 0, (res->id.idtype == IDMAP_USID) ? 1 : 0, state->id_cache_timeout, (res->direction == 0) ? "1" : NULL, res->info.how.map_type, map_dn, map_attr, map_value, map_windomain, map_winname, map_unixname, map_is_nt4); if (sql == NULL) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "Out of memory"); goto out; } retcode = sql_exec_no_cb(state->cache, IDMAP_CACHENAME, sql); if (retcode != IDMAP_SUCCESS) goto out; state->pid2sid_done = FALSE; sqlite_freemem(sql); sql = NULL; /* Check if we need to update namecache */ if (req->direction & _IDMAP_F_DONT_UPDATE_NAMECACHE) goto out; if (req->id2name == NULL) goto out; sql = sqlite_mprintf("INSERT OR REPLACE into name_cache " "(sidprefix, rid, canon_name, domain, type, expiration) " "VALUES(%Q, %u, %Q, %Q, %d, strftime('%%s','now') + %u); ", res->id.idmap_id_u.sid.prefix, res->id.idmap_id_u.sid.rid, req->id2name, req->id2domain, res->id.idtype, state->name_cache_timeout); if (sql == NULL) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "Out of memory"); goto out; } retcode = sql_exec_no_cb(state->cache, IDMAP_CACHENAME, sql); out: if (sql != NULL) sqlite_freemem(sql); return (retcode); } idmap_retcode update_cache_sid2pid(lookup_state_t *state, idmap_mapping *req, idmap_id_res *res) { char *sql = NULL; idmap_retcode retcode; int is_eph_user; char *map_dn = NULL; char *map_attr = NULL; char *map_value = NULL; char *map_windomain = NULL; char *map_winname = NULL; char *map_unixname = NULL; int map_is_nt4 = FALSE; /* Check if we need to cache anything */ if (ARE_WE_DONE(req->direction)) return (IDMAP_SUCCESS); /* We don't cache negative entries */ if (res->retcode != IDMAP_SUCCESS) return (IDMAP_SUCCESS); if (req->direction & _IDMAP_F_EXP_EPH_UID) is_eph_user = 1; else if (req->direction & _IDMAP_F_EXP_EPH_GID) is_eph_user = 0; else is_eph_user = -1; if (is_eph_user >= 0 && !IDMAP_ID_IS_EPHEMERAL(res->id.idmap_id_u.uid)) { sql = sqlite_mprintf("UPDATE idmap_cache " "SET w2u = 0 WHERE " "sidprefix = %Q AND rid = %u AND w2u = 1 AND " "pid >= 2147483648 AND is_user = %d;", req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid, is_eph_user); if (sql == NULL) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "Out of memory"); goto out; } retcode = sql_exec_no_cb(state->cache, IDMAP_CACHENAME, sql); if (retcode != IDMAP_SUCCESS) goto out; sqlite_freemem(sql); sql = NULL; } assert(res->direction != IDMAP_DIRECTION_UNDEF); assert(res->id.idmap_id_u.uid != IDMAP_SENTINEL_PID); switch (res->info.how.map_type) { case IDMAP_MAP_TYPE_DS_AD: map_dn = res->info.how.idmap_how_u.ad.dn; map_attr = res->info.how.idmap_how_u.ad.attr; map_value = res->info.how.idmap_how_u.ad.value; break; case IDMAP_MAP_TYPE_DS_NLDAP: map_dn = res->info.how.idmap_how_u.nldap.dn; map_attr = res->info.how.idmap_how_u.ad.attr; map_value = res->info.how.idmap_how_u.nldap.value; break; case IDMAP_MAP_TYPE_RULE_BASED: map_windomain = res->info.how.idmap_how_u.rule.windomain; map_winname = res->info.how.idmap_how_u.rule.winname; map_unixname = res->info.how.idmap_how_u.rule.unixname; map_is_nt4 = res->info.how.idmap_how_u.rule.is_nt4; break; case IDMAP_MAP_TYPE_EPHEMERAL: break; case IDMAP_MAP_TYPE_IDMU: map_dn = res->info.how.idmap_how_u.idmu.dn; map_attr = res->info.how.idmap_how_u.idmu.attr; map_value = res->info.how.idmap_how_u.idmu.value; break; default: /* Don't cache other mapping types */ assert(FALSE); } sql = sqlite_mprintf("INSERT OR REPLACE into idmap_cache " "(sidprefix, rid, windomain, canon_winname, pid, unixname, " "is_user, is_wuser, expiration, w2u, u2w, " "map_type, map_dn, map_attr, map_value, map_windomain, " "map_winname, map_unixname, map_is_nt4) " "VALUES(%Q, %u, %Q, %Q, %u, %Q, %d, %d, " "strftime('%%s','now') + %u, 1, %q, " "%d, %Q, %Q, %Q, %Q, %Q, %Q, %d);", req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid, (req->id1domain != NULL) ? req->id1domain : "", req->id1name, res->id.idmap_id_u.uid, req->id2name, (res->id.idtype == IDMAP_UID) ? 1 : 0, (req->id1.idtype == IDMAP_USID) ? 1 : 0, state->id_cache_timeout, (res->direction == 0) ? "1" : NULL, res->info.how.map_type, map_dn, map_attr, map_value, map_windomain, map_winname, map_unixname, map_is_nt4); if (sql == NULL) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "Out of memory"); goto out; } retcode = sql_exec_no_cb(state->cache, IDMAP_CACHENAME, sql); if (retcode != IDMAP_SUCCESS) goto out; state->sid2pid_done = FALSE; sqlite_freemem(sql); sql = NULL; /* Check if we need to update namecache */ if (req->direction & _IDMAP_F_DONT_UPDATE_NAMECACHE) goto out; if (EMPTY_STRING(req->id1name)) goto out; sql = sqlite_mprintf("INSERT OR REPLACE into name_cache " "(sidprefix, rid, canon_name, domain, type, expiration) " "VALUES(%Q, %u, %Q, %Q, %d, strftime('%%s','now') + %u); ", req->id1.idmap_id_u.sid.prefix, req->id1.idmap_id_u.sid.rid, req->id1name, req->id1domain, req->id1.idtype, state->name_cache_timeout); if (sql == NULL) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "Out of memory"); goto out; } retcode = sql_exec_no_cb(state->cache, IDMAP_CACHENAME, sql); out: if (sql != NULL) sqlite_freemem(sql); return (retcode); } static idmap_retcode lookup_cache_pid2sid(sqlite *cache, idmap_mapping *req, idmap_id_res *res, int is_user) { char *end; char *sql = NULL; const char **values; sqlite_vm *vm = NULL; int ncol; idmap_retcode retcode = IDMAP_SUCCESS; time_t curtime; idmap_id_type idtype; /* Current time */ errno = 0; if ((curtime = time(NULL)) == (time_t)-1) { idmapdlog(LOG_ERR, "Failed to get current time (%s)", strerror(errno)); retcode = IDMAP_ERR_INTERNAL; goto out; } /* SQL to lookup the cache by pid or by unixname */ if (req->id1.idmap_id_u.uid != IDMAP_SENTINEL_PID) { sql = sqlite_mprintf("SELECT sidprefix, rid, " "canon_winname, windomain, w2u, is_wuser, " "map_type, map_dn, map_attr, map_value, map_windomain, " "map_winname, map_unixname, map_is_nt4 " "FROM idmap_cache WHERE " "pid = %u AND u2w = 1 AND is_user = %d AND " "(pid >= 2147483648 OR " "(expiration = 0 OR expiration ISNULL OR " "expiration > %d));", req->id1.idmap_id_u.uid, is_user, curtime); } else if (req->id1name != NULL) { sql = sqlite_mprintf("SELECT sidprefix, rid, " "canon_winname, windomain, w2u, is_wuser, " "map_type, map_dn, map_attr, map_value, map_windomain, " "map_winname, map_unixname, map_is_nt4 " "FROM idmap_cache WHERE " "unixname = %Q AND u2w = 1 AND is_user = %d AND " "(pid >= 2147483648 OR " "(expiration = 0 OR expiration ISNULL OR " "expiration > %d));", req->id1name, is_user, curtime); } else { retcode = IDMAP_ERR_ARG; goto out; } if (sql == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } retcode = sql_compile_n_step_once( cache, sql, &vm, &ncol, 14, &values); sqlite_freemem(sql); if (retcode == IDMAP_ERR_NOTFOUND) goto out; else if (retcode == IDMAP_SUCCESS) { /* sanity checks */ if (values[0] == NULL || values[1] == NULL) { retcode = IDMAP_ERR_CACHE; goto out; } switch (res->id.idtype) { case IDMAP_SID: case IDMAP_USID: case IDMAP_GSID: idtype = strtol(values[5], &end, 10) == 1 ? IDMAP_USID : IDMAP_GSID; if (res->id.idtype == IDMAP_USID && idtype != IDMAP_USID) { retcode = IDMAP_ERR_NOTUSER; goto out; } else if (res->id.idtype == IDMAP_GSID && idtype != IDMAP_GSID) { retcode = IDMAP_ERR_NOTGROUP; goto out; } res->id.idtype = idtype; res->id.idmap_id_u.sid.rid = strtoul(values[1], &end, 10); res->id.idmap_id_u.sid.prefix = strdup(values[0]); if (res->id.idmap_id_u.sid.prefix == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } if (values[4] != NULL) res->direction = (strtol(values[4], &end, 10) == 0)? IDMAP_DIRECTION_U2W:IDMAP_DIRECTION_BI; else res->direction = IDMAP_DIRECTION_U2W; if (values[2] == NULL) break; req->id2name = strdup(values[2]); if (req->id2name == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } if (values[3] == NULL) break; req->id2domain = strdup(values[3]); if (req->id2domain == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } break; default: retcode = IDMAP_ERR_NOTSUPPORTED; break; } if (req->flag & IDMAP_REQ_FLG_MAPPING_INFO) { res->info.src = IDMAP_MAP_SRC_CACHE; res->info.how.map_type = strtoul(values[6], &end, 10); switch (res->info.how.map_type) { case IDMAP_MAP_TYPE_DS_AD: res->info.how.idmap_how_u.ad.dn = strdup(values[7]); res->info.how.idmap_how_u.ad.attr = strdup(values[8]); res->info.how.idmap_how_u.ad.value = strdup(values[9]); break; case IDMAP_MAP_TYPE_DS_NLDAP: res->info.how.idmap_how_u.nldap.dn = strdup(values[7]); res->info.how.idmap_how_u.nldap.attr = strdup(values[8]); res->info.how.idmap_how_u.nldap.value = strdup(values[9]); break; case IDMAP_MAP_TYPE_RULE_BASED: res->info.how.idmap_how_u.rule.windomain = strdup(values[10]); res->info.how.idmap_how_u.rule.winname = strdup(values[11]); res->info.how.idmap_how_u.rule.unixname = strdup(values[12]); res->info.how.idmap_how_u.rule.is_nt4 = strtoul(values[13], &end, 10); res->info.how.idmap_how_u.rule.is_user = is_user; res->info.how.idmap_how_u.rule.is_wuser = strtol(values[5], &end, 10); break; case IDMAP_MAP_TYPE_EPHEMERAL: break; case IDMAP_MAP_TYPE_LOCAL_SID: break; case IDMAP_MAP_TYPE_KNOWN_SID: break; case IDMAP_MAP_TYPE_IDMU: res->info.how.idmap_how_u.idmu.dn = strdup(values[7]); res->info.how.idmap_how_u.idmu.attr = strdup(values[8]); res->info.how.idmap_how_u.idmu.value = strdup(values[9]); break; default: /* Unknown mapping type */ assert(FALSE); } } } out: if (vm != NULL) (void) sqlite_finalize(vm, NULL); return (retcode); } /* * Given: * cache sqlite handle * name Windows user name * domain Windows domain name * * Return: Error code * * *canonname Canonical name (if canonname is non-NULL) [1] * *sidprefix SID prefix [1] * *rid RID * *type Type of name * * [1] malloc'ed, NULL on error */ static idmap_retcode lookup_cache_name2sid( sqlite *cache, const char *name, const char *domain, char **canonname, char **sidprefix, idmap_rid_t *rid, idmap_id_type *type) { char *end, *lower_name; char *sql; const char **values; sqlite_vm *vm = NULL; int ncol; time_t curtime; idmap_retcode retcode; *sidprefix = NULL; if (canonname != NULL) *canonname = NULL; /* Get current time */ errno = 0; if ((curtime = time(NULL)) == (time_t)-1) { idmapdlog(LOG_ERR, "Failed to get current time (%s)", strerror(errno)); retcode = IDMAP_ERR_INTERNAL; goto out; } /* SQL to lookup the cache */ if ((lower_name = tolower_u8(name)) == NULL) lower_name = (char *)name; sql = sqlite_mprintf("SELECT sidprefix, rid, type, canon_name " "FROM name_cache WHERE name = %Q AND domain = %Q AND " "(expiration = 0 OR expiration ISNULL OR " "expiration > %d);", lower_name, domain, curtime); if (lower_name != name) free(lower_name); if (sql == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } retcode = sql_compile_n_step_once(cache, sql, &vm, &ncol, 4, &values); sqlite_freemem(sql); if (retcode != IDMAP_SUCCESS) goto out; if (type != NULL) { if (values[2] == NULL) { retcode = IDMAP_ERR_CACHE; goto out; } *type = xlate_legacy_type(strtol(values[2], &end, 10)); } if (values[0] == NULL || values[1] == NULL) { retcode = IDMAP_ERR_CACHE; goto out; } if (canonname != NULL) { assert(values[3] != NULL); *canonname = strdup(values[3]); if (*canonname == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } } *sidprefix = strdup(values[0]); if (*sidprefix == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } *rid = strtoul(values[1], &end, 10); retcode = IDMAP_SUCCESS; out: if (vm != NULL) (void) sqlite_finalize(vm, NULL); if (retcode != IDMAP_SUCCESS) { free(*sidprefix); *sidprefix = NULL; if (canonname != NULL) { free(*canonname); *canonname = NULL; } } return (retcode); } static idmap_retcode ad_lookup_by_winname(lookup_state_t *state, const char *name, const char *domain, int esidtype, char **dn, char **attr, char **value, char **canonname, char **sidprefix, idmap_rid_t *rid, idmap_id_type *wintype, char **unixname) { int retries; idmap_query_state_t *qs = NULL; idmap_retcode rc, retcode; int i; int found_ad = 0; RDLOCK_CONFIG(); if (_idmapdstate.num_gcs > 0) { for (i = 0; i < _idmapdstate.num_gcs && !found_ad; i++) { retries = 0; retry: retcode = idmap_lookup_batch_start( _idmapdstate.gcs[i], 1, _idmapdstate.cfg->pgcfg.directory_based_mapping, _idmapdstate.cfg->pgcfg.default_domain, &qs); if (retcode != IDMAP_SUCCESS) { if (retcode == IDMAP_ERR_RETRIABLE_NET_ERR && retries++ < ADUTILS_DEF_NUM_RETRIES) goto retry; degrade_svc(1, "failed to create request for " "AD lookup by winname"); UNLOCK_CONFIG(); return (retcode); } restore_svc(); if (state != NULL && i == 0) { /* * Directory based name mapping is only * performed within the joined forest (i == 0). * We don't trust other "trusted" forests to * provide DS-based name mapping information * because AD's definition of "cross-forest * trust" does not encompass this sort of * behavior. */ idmap_lookup_batch_set_unixattr(qs, state->ad_unixuser_attr, state->ad_unixgroup_attr); } retcode = idmap_name2sid_batch_add1(qs, name, domain, esidtype, dn, attr, value, canonname, sidprefix, rid, wintype, unixname, NULL, &rc); if (retcode == IDMAP_ERR_DOMAIN_NOTFOUND) { idmap_lookup_release_batch(&qs); continue; } found_ad = 1; if (retcode != IDMAP_SUCCESS) idmap_lookup_release_batch(&qs); else retcode = idmap_lookup_batch_end(&qs); if (retcode == IDMAP_ERR_RETRIABLE_NET_ERR && retries++ < ADUTILS_DEF_NUM_RETRIES) goto retry; else if (retcode == IDMAP_ERR_RETRIABLE_NET_ERR) degrade_svc(1, "some AD lookups timed out repeatedly"); } } else { /* No AD case */ retcode = IDMAP_ERR_NO_ACTIVEDIRECTORY; } UNLOCK_CONFIG(); if (retcode != IDMAP_SUCCESS) { idmapdlog(LOG_NOTICE, "AD lookup of winname %s@%s failed, error code %d", name == NULL ? "(null)" : name, domain == NULL ? "(null)" : domain, retcode); return (retcode); } return (rc); } /* * Given: * cache sqlite handle to cache * name Windows user name * domain Windows domain name * local_only if true, don't try AD lookups * * Returns: Error code * * *canonname Canonical name (if non-NULL) [1] * *canondomain Canonical domain (if non-NULL) [1] * *sidprefix SID prefix [1] * *rid RID * *req Request (direction is updated) * * [1] malloc'ed, NULL on error */ idmap_retcode lookup_name2sid( sqlite *cache, const char *name, const char *domain, int want_wuser, char **canonname, char **canondomain, char **sidprefix, idmap_rid_t *rid, idmap_id_type *type, idmap_mapping *req, int local_only) { idmap_retcode retcode; *sidprefix = NULL; if (canonname != NULL) *canonname = NULL; if (canondomain != NULL) *canondomain = NULL; /* Lookup well-known SIDs table */ retcode = lookup_wksids_name2sid(name, domain, canonname, canondomain, sidprefix, rid, type); if (retcode == IDMAP_SUCCESS) { req->direction |= _IDMAP_F_DONT_UPDATE_NAMECACHE; goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { return (retcode); } /* Lookup cache */ retcode = lookup_cache_name2sid(cache, name, domain, canonname, sidprefix, rid, type); if (retcode == IDMAP_SUCCESS) { req->direction |= _IDMAP_F_DONT_UPDATE_NAMECACHE; goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { return (retcode); } /* * The caller may be using this function to determine if this * request needs to be marked for AD lookup or not * (i.e. _IDMAP_F_LOOKUP_AD) and therefore may not want this * function to AD lookup now. */ if (local_only) return (retcode); if (_idmapdstate.cfg->pgcfg.use_lsa && _idmapdstate.cfg->pgcfg.domain_name != NULL && name != NULL && *sidprefix == NULL) { retcode = lookup_lsa_by_name(name, domain, sidprefix, rid, canonname, canondomain, type); if (retcode == IDMAP_SUCCESS) goto out; else if (retcode != IDMAP_ERR_NOTFOUND) return (retcode); } /* Lookup AD */ retcode = ad_lookup_by_winname(NULL, name, domain, IDMAP_POSIXID, NULL, NULL, NULL, canonname, sidprefix, rid, type, NULL); if (retcode != IDMAP_SUCCESS) return (retcode); out: /* * Entry found (cache or Windows lookup) */ if (want_wuser == 1 && *type != IDMAP_USID) retcode = IDMAP_ERR_NOTUSER; else if (want_wuser == 0 && *type != IDMAP_GSID) retcode = IDMAP_ERR_NOTGROUP; else if (want_wuser == -1) { /* * Caller wants to know if its user or group * Verify that it's one or the other. */ if (*type != IDMAP_USID && *type != IDMAP_GSID) retcode = IDMAP_ERR_SID; } if (retcode == IDMAP_SUCCESS) { /* * If we were asked for a canonical domain and none * of the searches have provided one, assume it's the * supplied domain. */ if (canondomain != NULL && *canondomain == NULL) { *canondomain = strdup(domain); if (*canondomain == NULL) retcode = IDMAP_ERR_MEMORY; } } if (retcode != IDMAP_SUCCESS) { free(*sidprefix); *sidprefix = NULL; if (canonname != NULL) { free(*canonname); *canonname = NULL; } if (canondomain != NULL) { free(*canondomain); *canondomain = NULL; } } return (retcode); } static idmap_retcode name_based_mapping_pid2sid(lookup_state_t *state, const char *unixname, int is_user, idmap_mapping *req, idmap_id_res *res) { const char *winname, *windomain; char *canonname; char *canondomain; char *sql = NULL, *errmsg = NULL; idmap_retcode retcode; char *end; const char **values; sqlite_vm *vm = NULL; int ncol, r; int want_wuser; const char *me = "name_based_mapping_pid2sid"; idmap_namerule *rule = &res->info.how.idmap_how_u.rule; int direction; assert(unixname != NULL); /* We have unixname */ assert(req->id2name == NULL); /* We don't have winname */ assert(res->id.idmap_id_u.sid.prefix == NULL); /* No SID either */ sql = sqlite_mprintf( "SELECT winname_display, windomain, w2u_order, " "is_wuser, unixname, is_nt4 " "FROM namerules WHERE " "u2w_order > 0 AND is_user = %d AND " "(unixname = %Q OR unixname = '*') " "ORDER BY u2w_order ASC;", is_user, unixname); if (sql == NULL) { idmapdlog(LOG_ERR, "Out of memory"); retcode = IDMAP_ERR_MEMORY; goto out; } if (sqlite_compile(state->db, sql, NULL, &vm, &errmsg) != SQLITE_OK) { retcode = IDMAP_ERR_INTERNAL; idmapdlog(LOG_ERR, "%s: database error (%s)", me, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); goto out; } for (;;) { r = sqlite_step(vm, &ncol, &values, NULL); assert(r != SQLITE_LOCKED && r != SQLITE_BUSY); if (r == SQLITE_ROW) { if (ncol < 6) { retcode = IDMAP_ERR_INTERNAL; goto out; } TRACE(req, res, "Matching rule: %s -> %s@%s", values[4] == NULL ? "(null)" : values[4], values[0] == NULL ? "(null)" : values[0], values[1] == NULL ? "(null)" : values[1]); if (values[0] == NULL) { /* values [1] and [2] can be null */ retcode = IDMAP_ERR_INTERNAL; goto out; } if (values[2] != NULL) direction = (strtol(values[2], &end, 10) == 0)? IDMAP_DIRECTION_U2W:IDMAP_DIRECTION_BI; else direction = IDMAP_DIRECTION_U2W; if (EMPTY_NAME(values[0])) { idmap_namerule_set(rule, values[1], values[0], values[4], is_user, strtol(values[3], &end, 10), strtol(values[5], &end, 10), direction); TRACE(req, res, "Mapping inhibited"); retcode = IDMAP_ERR_NOMAPPING; goto out; } if (values[0][0] == '*') { winname = unixname; } else { winname = values[0]; } want_wuser = res->id.idtype == IDMAP_USID ? 1 : res->id.idtype == IDMAP_GSID ? 0 : -1; if (values[1] != NULL) windomain = values[1]; else if (state->defdom != NULL) { windomain = state->defdom; TRACE(req, res, "Added default domain %s to rule", windomain); } else { idmapdlog(LOG_ERR, "%s: no domain", me); TRACE(req, res, "No domain in rule, and no default domain"); retcode = IDMAP_ERR_DOMAIN_NOTFOUND; goto out; } retcode = lookup_name2sid(state->cache, winname, windomain, want_wuser, &canonname, &canondomain, &res->id.idmap_id_u.sid.prefix, &res->id.idmap_id_u.sid.rid, &res->id.idtype, req, 0); if (retcode == IDMAP_SUCCESS) { break; } else if (retcode == IDMAP_ERR_NOTFOUND) { if (values[0][0] == '*') { TRACE(req, res, "%s@%s not found, continuing", winname, windomain); continue; } else { TRACE(req, res, "%s@%s not found", winname, windomain); retcode = IDMAP_ERR_NOMAPPING; } } else { TRACE(req, res, "Looking up %s@%s error=%d", winname, windomain, retcode); } idmap_namerule_set(rule, values[1], values[0], values[4], is_user, strtol(values[3], &end, 10), strtol(values[5], &end, 10), direction); goto out; } else if (r == SQLITE_DONE) { TRACE(req, res, "No matching rule"); retcode = IDMAP_ERR_NOTFOUND; goto out; } else { (void) sqlite_finalize(vm, &errmsg); vm = NULL; idmapdlog(LOG_ERR, "%s: database error (%s)", me, CHECK_NULL(errmsg)); sqlite_freemem(errmsg); retcode = IDMAP_ERR_INTERNAL; goto out; } } if (values[2] != NULL) res->direction = (strtol(values[2], &end, 10) == 0)? IDMAP_DIRECTION_U2W:IDMAP_DIRECTION_BI; else res->direction = IDMAP_DIRECTION_U2W; req->id2name = canonname; req->id2domain = canondomain; idmap_namerule_set(rule, values[1], values[0], values[4], is_user, strtol(values[3], &end, 10), strtol(values[5], &end, 10), rule->direction); TRACE(req, res, "Windows name found"); out: if (sql != NULL) sqlite_freemem(sql); if (retcode != IDMAP_ERR_NOTFOUND) { res->info.how.map_type = IDMAP_MAP_TYPE_RULE_BASED; res->info.src = IDMAP_MAP_SRC_NEW; } if (vm != NULL) (void) sqlite_finalize(vm, NULL); return (retcode); } /* * Convention when processing unix2win requests: * * Unix identity: * req->id1name = * unixname if given otherwise unixname found will be placed * here. * req->id1domain = * NOT USED * req->id1.idtype = * Given type (IDMAP_UID or IDMAP_GID) * req->id1..[uid or gid] = * UID/GID if given otherwise UID/GID found will be placed here. * * Windows identity: * req->id2name = * winname found will be placed here. * req->id2domain = * windomain found will be placed here. * res->id.idtype = * Target type initialized from req->id2.idtype. If * it is IDMAP_SID then actual type (IDMAP_USID/GSID) found * will be placed here. * req->id..sid.[prefix, rid] = * SID found will be placed here. * * Others: * res->retcode = * Return status for this request will be placed here. * res->direction = * Direction found will be placed here. Direction * meaning whether the resultant mapping is valid * only from unix2win or bi-directional. * req->direction = * INTERNAL USE. Used by idmapd to set various * flags (_IDMAP_F_xxxx) to aid in processing * of the request. * req->id2.idtype = * INTERNAL USE. Initially this is the requested target * type and is used to initialize res->id.idtype. * ad_lookup_batch() uses this field temporarily to store * sid_type obtained by the batched AD lookups and after * use resets it to IDMAP_NONE to prevent xdr from * mis-interpreting the contents of req->id2. * req->id2..[uid or gid or sid] = * NOT USED */ /* * This function does the following: * 1. Lookup well-known SIDs table. * 2. Lookup cache. * 3. Check if the client does not want new mapping to be allocated * in which case this pass is the final pass. * 4. Set AD/NLDAP lookup flags if it determines that the next stage needs * to do AD/NLDAP lookup. */ idmap_retcode pid2sid_first_pass(lookup_state_t *state, idmap_mapping *req, idmap_id_res *res, int is_user) { idmap_retcode retcode; idmap_retcode retcode2; bool_t gen_localsid_on_err = FALSE; /* Initialize result */ res->id.idtype = req->id2.idtype; res->direction = IDMAP_DIRECTION_UNDEF; if (req->id2.idmap_id_u.sid.prefix != NULL) { /* sanitize sidprefix */ free(req->id2.idmap_id_u.sid.prefix); req->id2.idmap_id_u.sid.prefix = NULL; } /* Find pid */ if (req->id1.idmap_id_u.uid == IDMAP_SENTINEL_PID) { if (req->id1name == NULL) { retcode = IDMAP_ERR_ARG; goto out; } retcode = ns_lookup_byname(req->id1name, NULL, &req->id1); if (retcode != IDMAP_SUCCESS) { TRACE(req, res, "Getting UNIX ID error=%d", retcode); retcode = IDMAP_ERR_NOMAPPING; goto out; } TRACE(req, res, "Found UNIX ID"); } /* Lookup in well-known SIDs table */ retcode = lookup_wksids_pid2sid(req, res, is_user); if (retcode == IDMAP_SUCCESS) { TRACE(req, res, "Hardwired mapping"); goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Well-known account lookup error=%d", retcode); goto out; } /* Lookup in cache */ retcode = lookup_cache_pid2sid(state->cache, req, res, is_user); if (retcode == IDMAP_SUCCESS) { TRACE(req, res, "Found in mapping cache"); goto out; } else if (retcode != IDMAP_ERR_NOTFOUND) { TRACE(req, res, "Mapping cache lookup error=%d", retcode); goto out; } TRACE(req, res, "Not found in mapping cache"); /* Ephemeral ids cannot be allocated during pid2sid */ if (IDMAP_ID_IS_EPHEMERAL(req->id1.idmap_id_u.uid)) { retcode = IDMAP_ERR_NOMAPPING; TRACE(req, res, "Shouldn't have an ephemeral ID here"); goto out; } if (DO_NOT_ALLOC_NEW_ID_MAPPING(req)) { retcode = IDMAP_ERR_NONE_GENERATED; goto out; } if (AVOID_NAMESERVICE(req)) { gen_localsid_on_err = TRUE; retcode = IDMAP_ERR_NOMAPPING; goto out; } /* Set flags for the next stage */ if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU) { req->direction |= _IDMAP_F_LOOKUP_AD; state->ad_nqueries++; } else if (AD_MODE(req->id1.idtype, state)) { /* * If AD-based name mapping is enabled then the next stage * will need to lookup AD using unixname to get the * corresponding winname. */ if (req->id1name == NULL) { /* Get unixname if only pid is given. */ retcode = ns_lookup_bypid(req->id1.idmap_id_u.uid, is_user, &req->id1name); if (retcode != IDMAP_SUCCESS) { TRACE(req, res, "Getting UNIX name error=%d", retcode); gen_localsid_on_err = TRUE; goto out; } TRACE(req, res, "Found UNIX name"); } req->direction |= _IDMAP_F_LOOKUP_AD; state->ad_nqueries++; } else if (NLDAP_OR_MIXED_MODE(req->id1.idtype, state)) { /* * If native LDAP or mixed mode is enabled for name mapping * then the next stage will need to lookup native LDAP using * unixname/pid to get the corresponding winname. */ req->direction |= _IDMAP_F_LOOKUP_NLDAP; state->nldap_nqueries++; } /* * Failed to find non-expired entry in cache. Set the flag to * indicate that we are not done yet. */ state->pid2sid_done = FALSE; req->direction |= _IDMAP_F_NOTDONE; retcode = IDMAP_SUCCESS; out: res->retcode = idmap_stat4prot(retcode); if (ARE_WE_DONE(req->direction) && res->retcode != IDMAP_SUCCESS) { if (gen_localsid_on_err == TRUE) { retcode2 = generate_localsid(req, res, is_user, TRUE); if (retcode2 == IDMAP_SUCCESS) TRACE(req, res, "Generate local SID"); else TRACE(req, res, "Generate local SID error=%d", retcode2); } } return (retcode); } idmap_retcode pid2sid_second_pass(lookup_state_t *state, idmap_mapping *req, idmap_id_res *res, int is_user) { bool_t gen_localsid_on_err = TRUE; idmap_retcode retcode = IDMAP_SUCCESS; idmap_retcode retcode2; /* Check if second pass is needed */ if (ARE_WE_DONE(req->direction)) return (res->retcode); /* Get status from previous pass */ retcode = res->retcode; if (retcode != IDMAP_SUCCESS) goto out; /* * If directory-based name mapping is enabled then the winname * may already have been retrieved from the AD object (AD-mode) * or from native LDAP object (nldap-mode or mixed-mode). * Note that if we have winname but no SID then it's an error * because this implies that the Native LDAP entry contains * winname which does not exist and it's better that we return * an error instead of doing rule-based mapping so that the user * can detect the issue and take appropriate action. */ if (req->id2name != NULL) { /* Return notfound if we've winname but no SID. */ if (res->id.idmap_id_u.sid.prefix == NULL) { TRACE(req, res, "Windows name but no SID"); retcode = IDMAP_ERR_NOTFOUND; goto out; } if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU) res->direction = IDMAP_DIRECTION_BI; else if (AD_MODE(req->id1.idtype, state)) res->direction = IDMAP_DIRECTION_BI; else if (NLDAP_MODE(req->id1.idtype, state)) res->direction = IDMAP_DIRECTION_BI; else if (MIXED_MODE(req->id1.idtype, state)) res->direction = IDMAP_DIRECTION_W2U; goto out; } else if (res->id.idmap_id_u.sid.prefix != NULL) { /* * We've SID but no winname. This is fine because * the caller may have only requested SID. */ goto out; } /* Free any mapping info from Directory based mapping */ if (res->info.how.map_type != IDMAP_MAP_TYPE_UNKNOWN) idmap_how_clear(&res->info.how); if (req->id1name == NULL) { /* Get unixname from name service */ retcode = ns_lookup_bypid(req->id1.idmap_id_u.uid, is_user, &req->id1name); if (retcode != IDMAP_SUCCESS) { TRACE(req, res, "Getting UNIX name error=%d", retcode); goto out; } TRACE(req, res, "Found UNIX name"); } else if (req->id1.idmap_id_u.uid == IDMAP_SENTINEL_PID) { /* Get pid from name service */ retcode = ns_lookup_byname(req->id1name, NULL, &req->id1); if (retcode != IDMAP_SUCCESS) { TRACE(req, res, "Getting UNIX ID error=%d", retcode); gen_localsid_on_err = FALSE; goto out; } TRACE(req, res, "Found UNIX ID"); } /* Use unixname to evaluate local name-based mapping rules */ retcode = name_based_mapping_pid2sid(state, req->id1name, is_user, req, res); if (retcode == IDMAP_ERR_NOTFOUND) { retcode = generate_localsid(req, res, is_user, FALSE); if (retcode == IDMAP_SUCCESS) { TRACE(req, res, "Generated local SID"); } else { TRACE(req, res, "Generating local SID error=%d", retcode); } gen_localsid_on_err = FALSE; } out: res->retcode = idmap_stat4prot(retcode); if (res->retcode != IDMAP_SUCCESS) { req->direction = _IDMAP_F_DONE; free(req->id2name); req->id2name = NULL; free(req->id2domain); req->id2domain = NULL; if (gen_localsid_on_err == TRUE) { retcode2 = generate_localsid(req, res, is_user, TRUE); if (retcode2 == IDMAP_SUCCESS) TRACE(req, res, "Generate local SID"); else TRACE(req, res, "Generate local SID error=%d", retcode2); } else { res->id.idtype = is_user ? IDMAP_USID : IDMAP_GSID; } } if (!ARE_WE_DONE(req->direction)) state->pid2sid_done = FALSE; return (retcode); } idmap_retcode idmap_cache_flush(idmap_flush_op op) { idmap_retcode rc; sqlite *cache = NULL; char *sql1; char *sql2; switch (op) { case IDMAP_FLUSH_EXPIRE: sql1 = "UPDATE idmap_cache SET expiration=1 WHERE expiration>0;"; sql2 = "UPDATE name_cache SET expiration=1 WHERE expiration>0;"; break; case IDMAP_FLUSH_DELETE: sql1 = "DELETE FROM idmap_cache;"; sql2 = "DELETE FROM name_cache;"; break; default: return (IDMAP_ERR_INTERNAL); } rc = get_cache_handle(&cache); if (rc != IDMAP_SUCCESS) return (rc); /* * Note that we flush the idmapd cache first, before the kernel * cache. If we did it the other way 'round, a request could come * in after the kernel cache flush and pull a soon-to-be-flushed * idmapd cache entry back into the kernel cache. This way the * worst that will happen is that a new entry will be added to * the kernel cache and then immediately flushed. */ rc = sql_exec_no_cb(cache, IDMAP_CACHENAME, sql1); if (rc == IDMAP_SUCCESS) rc = sql_exec_no_cb(cache, IDMAP_CACHENAME, sql2); if (rc == IDMAP_SUCCESS) (void) __idmap_flush_kcache(); if (rc == IDMAP_ERR_DB) kill_cache_handle(cache); return (rc); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* * Retrieve directory information for Active Directory users. */ #include #include #include #include #include #include #include #include #include #include #include #include "directory.h" #include "directory_private.h" #include "idmapd.h" #include #include "directory_server_impl.h" /* * Information required by the function that handles the callback from LDAP * when responses are received. */ struct cbinfo { const char * const *attrs; int nattrs; directory_entry_rpc *entry; const char *domain; }; static void directory_provider_ad_cb(LDAP *ld, LDAPMessage **ldapres, int rc, int qid, void *argp); static void directory_provider_ad_cb1(LDAP *ld, LDAPMessage *msg, struct cbinfo *cbinfo); static directory_error_t bv_list_dav(directory_values_rpc *lvals, struct berval **bv); static directory_error_t directory_provider_ad_lookup( directory_entry_rpc *pent, const char * const * attrs, int nattrs, const char *domain, const char *filter); static directory_error_t get_domain(LDAP *ld, LDAPMessage *ldapres, char **domain); static directory_error_t directory_provider_ad_utils_error(char *func, int rc); #if defined(DUMP_VALUES) static void dump_bv_list(const char *attr, struct berval **bv); #endif #define MAX_EXTRA_ATTRS 1 /* sAMAccountName */ /* * Add an entry to a NULL-terminated list, if it's not already there. * Assumes that the list has been allocated large enough for all additions, * and prefilled with NULL. */ static void maybe_add_to_list(const char **list, const char *s) { for (; *list != NULL; list++) { if (uu_strcaseeq(*list, s)) return; } *list = s; } /* * Copy a counted attribute list to a NULL-terminated one. * In the process, examine the requested attributes and augment * the list as required to support any synthesized attributes * requested. */ static const char ** copy_and_augment_attr_list(char **req_list, int req_list_len) { const char **new_list; int i; new_list = calloc(req_list_len + MAX_EXTRA_ATTRS + 1, sizeof (*new_list)); if (new_list == NULL) return (NULL); (void) memcpy(new_list, req_list, req_list_len * sizeof (char *)); for (i = 0; i < req_list_len; i++) { const char *a = req_list[i]; /* * Note that you must update MAX_EXTRA_ATTRS above if you * add to this list. */ if (uu_strcaseeq(a, "x-sun-canonicalName")) { maybe_add_to_list(new_list, "sAMAccountName"); continue; } /* None needed for x-sun-provider */ } return (new_list); } /* * Retrieve information by name. * Called indirectly through the Directory_provider_static structure. */ static directory_error_t directory_provider_ad_get( directory_entry_rpc *del, idmap_utf8str_list *ids, char *types, idmap_utf8str_list *attrs) { int i; const char **attrs2; directory_error_t de = NULL; /* * If we don't have any AD servers handy, we can't find anything. * XXX: this should be using our DC, not the GC. */ if (_idmapdstate.num_gcs < 1) { return (NULL); } RDLOCK_CONFIG() /* 6835280 spurious lint error if the strlen is in the declaration */ int len = strlen(_idmapdstate.cfg->pgcfg.default_domain); char default_domain[len + 1]; (void) strcpy(default_domain, _idmapdstate.cfg->pgcfg.default_domain); UNLOCK_CONFIG(); /* * Turn our counted-array argument into a NULL-terminated array. * At the same time, add in any attributes that we need to support * any requested synthesized attributes. */ attrs2 = copy_and_augment_attr_list(attrs->idmap_utf8str_list_val, attrs->idmap_utf8str_list_len); if (attrs2 == NULL) goto nomem; for (i = 0; i < ids->idmap_utf8str_list_len; i++) { char *vw[3]; int type; /* * Extract the type for this particular ID. * Advance to the next type, if it's there, else keep * using this type until we run out of IDs. */ type = *types; if (*(types+1) != '\0') types++; /* * If this entry has already been handled, one way or another, * skip it. */ if (del[i].status != DIRECTORY_NOT_FOUND) continue; char *id = ids->idmap_utf8str_list_val[i]; /* * Allow for expanding every character to \xx, plus some * space for the query syntax. */ int id_len = strlen(id); char filter[1000 + id_len*3]; if (type == DIRECTORY_ID_SID[0]) { /* * Mildly surprisingly, AD appears to allow searching * based on text SIDs. Must be a special case on the * server end. */ ldap_build_filter(filter, sizeof (filter), "(objectSid=%v)", NULL, NULL, NULL, id, NULL); de = directory_provider_ad_lookup(&del[i], attrs2, attrs->idmap_utf8str_list_len, NULL, filter); if (de != NULL) { directory_entry_set_error(&del[i], de); de = NULL; } } else { int id_len = strlen(id); char name[id_len + 1]; char domain[id_len + 1]; split_name(name, domain, id); vw[0] = name; if (uu_streq(domain, "")) { vw[1] = default_domain; } else { vw[1] = domain; } if (type == DIRECTORY_ID_USER[0]) vw[2] = "user"; else if (type == DIRECTORY_ID_GROUP[0]) vw[2] = "group"; else vw[2] = "*"; /* * Try samAccountName. * Note that here we rely on checking the returned * distinguishedName to make sure that we found an * entry from the right domain, because there's no * attribute we can straightforwardly filter for to * match domain. * * Eventually we should perhaps also try * userPrincipalName. */ ldap_build_filter(filter, sizeof (filter), "(&(samAccountName=%v1)(objectClass=%v3))", NULL, NULL, NULL, NULL, vw); de = directory_provider_ad_lookup(&del[i], attrs2, attrs->idmap_utf8str_list_len, vw[1], filter); if (de != NULL) { directory_entry_set_error(&del[i], de); de = NULL; } } } de = NULL; goto out; nomem: de = directory_error("ENOMEM.AD", "Out of memory during AD lookup", NULL); out: free(attrs2); return (de); } /* * Note that attrs is NULL terminated, and that nattrs is the number * of attributes requested by the user... which might be fewer than are * in attrs because of attributes that we need for our own processing. */ static directory_error_t directory_provider_ad_lookup( directory_entry_rpc *pent, const char * const * attrs, int nattrs, const char *domain, const char *filter) { adutils_ad_t *ad; adutils_rc batchrc; struct cbinfo cbinfo; adutils_query_state_t *qs; int rc; /* * NEEDSWORK: Should eventually handle other forests. * NEEDSWORK: Should eventually handle non-GC attributes. */ ad = _idmapdstate.gcs[0]; /* Stash away information for the callback function. */ cbinfo.attrs = attrs; cbinfo.nattrs = nattrs; cbinfo.entry = pent; cbinfo.domain = domain; rc = adutils_lookup_batch_start(ad, 1, directory_provider_ad_cb, &cbinfo, &qs); if (rc != ADUTILS_SUCCESS) { return (directory_provider_ad_utils_error( "adutils_lookup_batch_start", rc)); } rc = adutils_lookup_batch_add(qs, filter, attrs, domain, NULL, &batchrc); if (rc != ADUTILS_SUCCESS) { adutils_lookup_batch_release(&qs); return (directory_provider_ad_utils_error( "adutils_lookup_batch_add", rc)); } rc = adutils_lookup_batch_end(&qs); if (rc != ADUTILS_SUCCESS) { return (directory_provider_ad_utils_error( "adutils_lookup_batch_end", rc)); } if (batchrc != ADUTILS_SUCCESS) { /* * NEEDSWORK: We're consistently getting -9997 here. * What does it mean? */ return (NULL); } return (NULL); } /* * Callback from the LDAP functions when they get responses. * We don't really need (nor want) asynchronous handling, but it's * what libadutils gives us. */ static void directory_provider_ad_cb( LDAP *ld, LDAPMessage **ldapres, int rc, int qid, void *argp) { NOTE(ARGUNUSED(rc, qid)) struct cbinfo *cbinfo = (struct cbinfo *)argp; LDAPMessage *msg = *ldapres; for (msg = ldap_first_entry(ld, msg); msg != NULL; msg = ldap_next_entry(ld, msg)) { directory_provider_ad_cb1(ld, msg, cbinfo); } } /* * Process a single entry returned by an LDAP callback. * Note that this performs a function roughly equivalent to the * directory*Populate() functions in the other providers. * Given an LDAP response, populate the directory entry for return to * the caller. This one differs primarily in that we're working directly * with LDAP, so we don't have to do any attribute translation. */ static void directory_provider_ad_cb1( LDAP *ld, LDAPMessage *msg, struct cbinfo *cbinfo) { int nattrs = cbinfo->nattrs; const char * const *attrs = cbinfo->attrs; directory_entry_rpc *pent = cbinfo->entry; int i; directory_values_rpc *llvals; directory_error_t de; char *domain = NULL; /* * We don't have a way to filter for entries from the right domain * in the LDAP query, so we check for it here. Searches based on * samAccountName might yield results from the wrong domain. */ de = get_domain(ld, msg, &domain); if (de != NULL) goto err; if (cbinfo->domain != NULL && !domain_eq(cbinfo->domain, domain)) goto out; /* * If we've already found a match, error. */ if (pent->status != DIRECTORY_NOT_FOUND) { de = directory_error("Duplicate.AD", "Multiple matching entries found", NULL); goto err; } llvals = calloc(nattrs, sizeof (directory_values_rpc)); if (llvals == NULL) goto nomem; pent->directory_entry_rpc_u.attrs.attrs_val = llvals; pent->directory_entry_rpc_u.attrs.attrs_len = nattrs; pent->status = DIRECTORY_FOUND; for (i = 0; i < nattrs; i++) { struct berval **bv; const char *a = attrs[i]; directory_values_rpc *val = &llvals[i]; bv = ldap_get_values_len(ld, msg, a); #if defined(DUMP_VALUES) dump_bv_list(attrs[i], bv); #endif if (bv != NULL) { de = bv_list_dav(val, bv); ldap_value_free_len(bv); if (de != NULL) goto err; } else if (uu_strcaseeq(a, "x-sun-canonicalName")) { bv = ldap_get_values_len(ld, msg, "sAMAccountName"); if (bv != NULL) { int n = ldap_count_values_len(bv); if (n > 0) { char *tmp; (void) asprintf(&tmp, "%.*s@%s", bv[0]->bv_len, bv[0]->bv_val, domain); if (tmp == NULL) goto nomem; const char *ctmp = tmp; de = str_list_dav(val, &ctmp, 1); free(tmp); if (de != NULL) goto err; } } } else if (uu_strcaseeq(a, "x-sun-provider")) { const char *provider = "LDAP-AD"; de = str_list_dav(val, &provider, 1); } } goto out; nomem: de = directory_error("ENOMEM.users", "No memory allocating return value for user lookup", NULL); err: directory_entry_set_error(pent, de); de = NULL; out: free(domain); } /* * Given a struct berval, populate a directory attribute value (which is a * list of values). * Note that here we populate the DAV with the exact bytes that LDAP returns. * Back over in the client it appends a \0 so that strings are null * terminated. */ static directory_error_t bv_list_dav(directory_values_rpc *lvals, struct berval **bv) { directory_value_rpc *dav; int n; int i; n = ldap_count_values_len(bv); dav = calloc(n, sizeof (directory_value_rpc)); if (dav == NULL) goto nomem; lvals->directory_values_rpc_u.values.values_val = dav; lvals->directory_values_rpc_u.values.values_len = n; lvals->found = TRUE; for (i = 0; i < n; i++) { dav[i].directory_value_rpc_val = uu_memdup(bv[i]->bv_val, bv[i]->bv_len); if (dav[i].directory_value_rpc_val == NULL) goto nomem; dav[i].directory_value_rpc_len = bv[i]->bv_len; } return (NULL); nomem: return (directory_error("ENOMEM.bv_list_dav", "Insufficient memory copying values")); } #if defined(DUMP_VALUES) static void dump_bv_list(const char *attr, struct berval **bv) { int i; if (bv == NULL) { (void) fprintf(stderr, "%s: (empty)\n", attr); return; } for (i = 0; bv[i] != NULL; i++) { (void) fprintf(stderr, "%s[%d] =\n", attr, i); dump(stderr, " ", bv[i]->bv_val, bv[i]->bv_len); } } #endif /* DUMP_VALUES */ /* * Return the domain associated with the specified entry. */ static directory_error_t get_domain( LDAP *ld, LDAPMessage *msg, char **domain) { *domain = NULL; char *dn = ldap_get_dn(ld, msg); if (dn == NULL) { char buf[100]; /* big enough for any int */ char *m; char *s; int err = ldap_get_lderrno(ld, &m, &s); (void) snprintf(buf, sizeof (buf), "%d", err); return directory_error("AD.get_domain.ldap_get_dn", "ldap_get_dn: %1 (%2)\n" "matched: %3\n" "error: %4", ldap_err2string(err), buf, m == NULL ? "(null)" : m, s == NULL ? "(null)" : s, NULL); } *domain = adutils_dn2dns(dn); if (*domain == NULL) { directory_error_t de; de = directory_error("Unknown.get_domain.adutils_dn2dns", "get_domain: Unexpected error from adutils_dn2dns(%1)", dn, NULL); free(dn); return (de); } free(dn); return (NULL); } /* * Given an error report from libadutils, generate a directory_error_t. */ static directory_error_t directory_provider_ad_utils_error(char *func, int rc) { char rcstr[100]; /* plenty for any int */ char code[100]; /* plenty for any int */ (void) snprintf(rcstr, sizeof (rcstr), "%d", rc); (void) snprintf(code, sizeof (code), "ADUTILS.%d", rc); return (directory_error(code, "Error %2 from adutils function %1", func, rcstr, NULL)); } struct directory_provider_static directory_provider_ad = { "AD", directory_provider_ad_get, }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Retrieve directory information for built-in users and groups */ #include #include #include #include #include #include #include #include #include #include "idmapd.h" #include "directory.h" #include "directory_private.h" #include #include "directory_server_impl.h" #include "sidutil.h" static directory_error_t sid_dav(directory_values_rpc *lvals, const wksids_table_t *wksid); static directory_error_t directory_provider_builtin_populate( directory_entry_rpc *pent, const wksids_table_t *wksid, idmap_utf8str_list *attrs); /* * Retrieve information by name. * Called indirectly through the directory_provider_static structure. */ static directory_error_t directory_provider_builtin_get( directory_entry_rpc *del, idmap_utf8str_list *ids, idmap_utf8str types, idmap_utf8str_list *attrs) { int i; for (i = 0; i < ids->idmap_utf8str_list_len; i++) { const wksids_table_t *wksid; directory_error_t de; int type; /* * Extract the type for this particular ID. * Advance to the next type, if it's there, else keep * using this type until we run out of IDs. */ type = *types; if (*(types+1) != '\0') types++; /* * If this entry has already been handled, one way or another, * skip it. */ if (del[i].status != DIRECTORY_NOT_FOUND) continue; char *id = ids->idmap_utf8str_list_val[i]; /* * End-to-end error injection point. * NEEDSWORK: should probably eliminate this for production */ if (uu_streq(id, " DEBUG BUILTIN ERROR ")) { directory_entry_set_error(&del[i], directory_error("Directory_provider_builtin.debug", "Directory_provider_builtin: artificial error", NULL)); continue; } if (type == DIRECTORY_ID_SID[0]) wksid = find_wk_by_sid(id); else { int idmap_id_type; if (type == DIRECTORY_ID_NAME[0]) idmap_id_type = IDMAP_POSIXID; else if (type == DIRECTORY_ID_USER[0]) idmap_id_type = IDMAP_UID; else if (type == DIRECTORY_ID_GROUP[0]) idmap_id_type = IDMAP_GID; else { directory_entry_set_error(&del[i], directory_error("invalid_arg.id_type", "Invalid ID type \"%1\"", types, NULL)); continue; } int id_len = strlen(id); char name[id_len + 1]; char domain[id_len + 1]; split_name(name, domain, id); wksid = find_wksid_by_name(name, domain, idmap_id_type); } if (wksid == NULL) continue; de = directory_provider_builtin_populate(&del[i], wksid, attrs); if (de != NULL) { directory_entry_set_error(&del[i], de); de = NULL; } } return (NULL); } /* * Given a well-known name entry and a list of attributes that were * requested, populate the structure to return to the caller. */ static directory_error_t directory_provider_builtin_populate( directory_entry_rpc *pent, const wksids_table_t *wksid, idmap_utf8str_list *attrs) { int j; directory_values_rpc *llvals; int nattrs; nattrs = attrs->idmap_utf8str_list_len; llvals = calloc(nattrs, sizeof (directory_values_rpc)); if (llvals == NULL) goto nomem; pent->status = DIRECTORY_FOUND; pent->directory_entry_rpc_u.attrs.attrs_val = llvals; pent->directory_entry_rpc_u.attrs.attrs_len = nattrs; for (j = 0; j < nattrs; j++) { directory_values_rpc *val; char *a; directory_error_t de; /* * We're going to refer to these a lot, so make a shorthand * copy. */ a = attrs->idmap_utf8str_list_val[j]; val = &llvals[j]; /* * Start by assuming no errors and that we don't have * the information. */ val->found = FALSE; de = NULL; if (uu_strcaseeq(a, "uid")) { de = str_list_dav(val, &wksid->winname, 1); } else if (uu_strcaseeq(a, "uidNumber")) { if (wksid->pid != IDMAP_SENTINEL_PID && wksid->is_user) { de = uint_list_dav(val, &wksid->pid, 1); } } else if (uu_strcaseeq(a, "gidNumber")) { if (wksid->pid != IDMAP_SENTINEL_PID && !wksid->is_user) { de = uint_list_dav(val, &wksid->pid, 1); } } else if (uu_strcaseeq(a, "displayName") || uu_strcaseeq(a, "cn")) { de = str_list_dav(val, &wksid->winname, 1); } else if (uu_strcaseeq(a, "distinguishedName")) { char *container; if (wksid->domain == NULL) { container = "Users"; } else { container = "Builtin"; } RDLOCK_CONFIG(); char *dn; (void) asprintf(&dn, "CN=%s,CN=%s,DC=%s", wksid->winname, container, _idmapdstate.hostname); UNLOCK_CONFIG(); const char *cdn = dn; de = str_list_dav(val, &cdn, 1); free(dn); } else if (uu_strcaseeq(a, "objectClass")) { if (wksid->is_wuser) { static const char *objectClasses[] = { "top", "person", "organizationalPerson", "user", }; de = str_list_dav(val, objectClasses, UU_NELEM(objectClasses)); } else { static const char *objectClasses[] = { "top", "group", }; de = str_list_dav(val, objectClasses, UU_NELEM(objectClasses)); } } else if (uu_strcaseeq(a, "objectSid")) { de = sid_dav(val, wksid); } else if (uu_strcaseeq(a, "x-sun-canonicalName")) { char *canon; if (wksid->domain == NULL) { RDLOCK_CONFIG(); (void) asprintf(&canon, "%s@%s", wksid->winname, _idmapdstate.hostname); UNLOCK_CONFIG(); } else if (uu_streq(wksid->domain, "")) { canon = strdup(wksid->winname); } else { (void) asprintf(&canon, "%s@%s", wksid->winname, wksid->domain); } if (canon == NULL) goto nomem; const char *ccanon = canon; de = str_list_dav(val, &ccanon, 1); free(canon); } else if (uu_strcaseeq(a, "x-sun-provider")) { const char *provider = "Builtin"; de = str_list_dav(val, &provider, 1); } if (de != NULL) return (de); } return (NULL); nomem: return (directory_error("ENOMEM.users", "No memory allocating return value for user lookup", NULL)); } /* * Given a well-known name structure, generate a binary-format SID. * It's a bit perverse that we must take a text-format SID and turn it into * a binary-format SID, only to have the caller probably turn it back into * text format, but SIDs are carried across LDAP in binary format. */ static directory_error_t sid_dav(directory_values_rpc *lvals, const wksids_table_t *wksid) { char *text_sid; sid_t *sid; directory_error_t de; if (wksid->sidprefix == NULL) { RDLOCK_CONFIG(); (void) asprintf(&text_sid, "%s-%d", _idmapdstate.cfg->pgcfg.machine_sid, wksid->rid); UNLOCK_CONFIG(); } else { (void) asprintf(&text_sid, "%s-%d", wksid->sidprefix, wksid->rid); } if (text_sid == NULL) goto nomem; sid = sid_fromstr(text_sid); free(text_sid); if (sid == NULL) goto nomem; sid_to_le(sid); de = bin_list_dav(lvals, sid, 1, sid_len(sid)); sid_free(sid); return (de); nomem: return (directory_error("ENOMEM.sid_dav", "No memory allocating SID for user lookup", NULL)); } struct directory_provider_static directory_provider_builtin = { "builtin", directory_provider_builtin_get, }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Retrieve directory information for standard UNIX users/groups. * (NB: not just from files, but all nsswitch sources.) */ #include #include #include #include #include #include #include #include #include #include "idmapd.h" #include "directory.h" #include "directory_private.h" #include #include "directory_server_impl.h" #include "sidutil.h" static directory_error_t machine_sid_dav(directory_values_rpc *lvals, unsigned int rid); static directory_error_t directory_provider_nsswitch_populate( directory_entry_rpc *pent, struct passwd *pwd, struct group *grp, idmap_utf8str_list *attrs); /* * Retrieve information by name. * Called indirectly through the directory_provider_static structure. */ static directory_error_t directory_provider_nsswitch_get( directory_entry_rpc *del, idmap_utf8str_list *ids, idmap_utf8str types, idmap_utf8str_list *attrs) { int i; RDLOCK_CONFIG(); /* 6835280 spurious lint error if the strlen is in the declaration */ int host_name_len = strlen(_idmapdstate.hostname); char my_host_name[host_name_len + 1]; (void) strcpy(my_host_name, _idmapdstate.hostname); /* We use len later, so this is not merely a workaround for 6835280 */ int machine_sid_len = strlen(_idmapdstate.cfg->pgcfg.machine_sid); char my_machine_sid[machine_sid_len + 1]; (void) strcpy(my_machine_sid, _idmapdstate.cfg->pgcfg.machine_sid); UNLOCK_CONFIG(); for (i = 0; i < ids->idmap_utf8str_list_len; i++) { struct passwd *pwd = NULL; struct group *grp = NULL; directory_error_t de; int type; /* * Extract the type for this particular ID. * Advance to the next type, if it's there, else keep * using this type until we run out of IDs. */ type = *types; if (*(types+1) != '\0') types++; /* * If this entry has already been handled, one way or another, * skip it. */ if (del[i].status != DIRECTORY_NOT_FOUND) continue; char *id = ids->idmap_utf8str_list_val[i]; if (type == DIRECTORY_ID_SID[0]) { /* * Is it our SID? * Check whether the first part matches, then a "-", * then a single RID. */ if (strncasecmp(id, my_machine_sid, machine_sid_len) != 0) continue; if (id[machine_sid_len] != '-') continue; char *p; uint32_t rid = strtoul(id + machine_sid_len + 1, &p, 10); if (*p != '\0') continue; if (rid < LOCALRID_UID_MIN) { /* Builtin, not handled here */ continue; } if (rid <= LOCALRID_UID_MAX) { /* User */ errno = 0; pwd = getpwuid(rid - LOCALRID_UID_MIN); if (pwd == NULL) { if (errno == 0) /* Not found */ continue; char buf[40]; int err = errno; (void) snprintf(buf, sizeof (buf), "%d", err); directory_entry_set_error(&del[i], directory_error("errno.getpwuid", "getpwuid: %2 (%1)", buf, strerror(err), NULL)); continue; } } else if (rid >= LOCALRID_GID_MIN && rid <= LOCALRID_GID_MAX) { /* Group */ errno = 0; grp = getgrgid(rid - LOCALRID_GID_MIN); if (grp == NULL) { if (errno == 0) /* Not found */ continue; char buf[40]; int err = errno; (void) snprintf(buf, sizeof (buf), "%d", err); directory_entry_set_error(&del[i], directory_error("errno.getgrgid", "getgrgid: %2 (%1)", buf, strerror(err), NULL)); continue; } } else continue; } else { int id_len = strlen(id); char name[id_len + 1]; char domain[id_len + 1]; split_name(name, domain, id); if (domain[0] != '\0') { if (!domain_eq(domain, my_host_name)) continue; } /* * If the caller has requested user or group * information specifically, we only set one of * pwd or grp. * If the caller has requested either type, we try * both in the hopes of getting one. * Note that directory_provider_nsswitch_populate * considers it to be an error if both are set. */ if (type != DIRECTORY_ID_GROUP[0]) { /* prep for not found / error case */ errno = 0; pwd = getpwnam(name); if (pwd == NULL && errno != 0) { char buf[40]; int err = errno; (void) snprintf(buf, sizeof (buf), "%d", err); directory_entry_set_error(&del[i], directory_error("errno.getpwnam", "getpwnam: %2 (%1)", buf, strerror(err), NULL)); continue; } } if (type != DIRECTORY_ID_USER[0]) { /* prep for not found / error case */ errno = 0; grp = getgrnam(name); if (grp == NULL && errno != 0) { char buf[40]; int err = errno; (void) snprintf(buf, sizeof (buf), "%d", err); directory_entry_set_error(&del[i], directory_error("errno.getgrnam", "getgrnam: %2 (%1)", buf, strerror(err), NULL)); continue; } } } /* * Didn't find it, don't populate the structure. * Another provider might populate it. */ if (pwd == NULL && grp == NULL) continue; de = directory_provider_nsswitch_populate(&del[i], pwd, grp, attrs); if (de != NULL) { directory_entry_set_error(&del[i], de); de = NULL; continue; } } return (NULL); } /* * Given a pwd structure or a grp structure, and a list of attributes that * were requested, populate the structure to return to the caller. */ static directory_error_t directory_provider_nsswitch_populate( directory_entry_rpc *pent, struct passwd *pwd, struct group *grp, idmap_utf8str_list *attrs) { int j; directory_values_rpc *llvals; int nattrs; /* * If it wasn't for this case, everything would be a lot simpler. * UNIX allows users and groups with the same name. Windows doesn't. */ if (pwd != NULL && grp != NULL) { return directory_error("Ambiguous.Name", "Ambiguous name, is both a user and a group", NULL); } nattrs = attrs->idmap_utf8str_list_len; llvals = calloc(nattrs, sizeof (directory_values_rpc)); if (llvals == NULL) goto nomem; pent->directory_entry_rpc_u.attrs.attrs_val = llvals; pent->directory_entry_rpc_u.attrs.attrs_len = nattrs; pent->status = DIRECTORY_FOUND; for (j = 0; j < nattrs; j++) { directory_values_rpc *val; char *a; directory_error_t de; /* * We're going to refer to these a lot, so make a shorthand * copy. */ a = attrs->idmap_utf8str_list_val[j]; val = &llvals[j]; /* * Start by assuming no errors and that we don't have * the information */ val->found = FALSE; de = NULL; if (pwd != NULL) { /* * Handle attributes for user entries. */ if (uu_strcaseeq(a, "cn")) { const char *p = pwd->pw_name; de = str_list_dav(val, &p, 1); } else if (uu_strcaseeq(a, "objectClass")) { static const char *objectClasses[] = { "top", "posixAccount", }; de = str_list_dav(val, objectClasses, UU_NELEM(objectClasses)); } else if (uu_strcaseeq(a, "gidNumber")) { de = uint_list_dav(val, &pwd->pw_gid, 1); } else if (uu_strcaseeq(a, "objectSid")) { de = machine_sid_dav(val, pwd->pw_uid + LOCALRID_UID_MIN); } else if (uu_strcaseeq(a, "displayName")) { const char *p = pwd->pw_gecos; de = str_list_dav(val, &p, 1); } else if (uu_strcaseeq(a, "distinguishedName")) { char *dn; RDLOCK_CONFIG(); (void) asprintf(&dn, "uid=%s,ou=people,dc=%s", pwd->pw_name, _idmapdstate.hostname); UNLOCK_CONFIG(); if (dn == NULL) goto nomem; const char *cdn = dn; de = str_list_dav(val, &cdn, 1); free(dn); } else if (uu_strcaseeq(a, "uid")) { const char *p = pwd->pw_name; de = str_list_dav(val, &p, 1); } else if (uu_strcaseeq(a, "uidNumber")) { de = uint_list_dav(val, &pwd->pw_uid, 1); } else if (uu_strcaseeq(a, "gecos")) { const char *p = pwd->pw_gecos; de = str_list_dav(val, &p, 1); } else if (uu_strcaseeq(a, "homeDirectory")) { const char *p = pwd->pw_dir; de = str_list_dav(val, &p, 1); } else if (uu_strcaseeq(a, "loginShell")) { const char *p = pwd->pw_shell; de = str_list_dav(val, &p, 1); } else if (uu_strcaseeq(a, "x-sun-canonicalName")) { char *canon; RDLOCK_CONFIG(); (void) asprintf(&canon, "%s@%s", pwd->pw_name, _idmapdstate.hostname); UNLOCK_CONFIG(); if (canon == NULL) goto nomem; const char *ccanon = canon; de = str_list_dav(val, &ccanon, 1); free(canon); } else if (uu_strcaseeq(a, "x-sun-provider")) { const char *provider = "UNIX-passwd"; de = str_list_dav(val, &provider, 1); } } else if (grp != NULL) { /* * Handle attributes for group entries. */ if (uu_strcaseeq(a, "cn")) { const char *p = grp->gr_name; de = str_list_dav(val, &p, 1); } else if (uu_strcaseeq(a, "objectClass")) { static const char *objectClasses[] = { "top", "posixGroup", }; de = str_list_dav(val, objectClasses, UU_NELEM(objectClasses)); } else if (uu_strcaseeq(a, "gidNumber")) { de = uint_list_dav(val, &grp->gr_gid, 1); } else if (uu_strcaseeq(a, "objectSid")) { de = machine_sid_dav(val, grp->gr_gid + LOCALRID_GID_MIN); } else if (uu_strcaseeq(a, "displayName")) { const char *p = grp->gr_name; de = str_list_dav(val, &p, 1); } else if (uu_strcaseeq(a, "distinguishedName")) { char *dn; RDLOCK_CONFIG(); (void) asprintf(&dn, "cn=%s,ou=group,dc=%s", grp->gr_name, _idmapdstate.hostname); UNLOCK_CONFIG(); if (dn == NULL) goto nomem; const char *cdn = dn; de = str_list_dav(val, &cdn, 1); free(dn); } else if (uu_strcaseeq(a, "memberUid")) { /* * NEEDSWORK: There is probably a non-cast * way to do this, but I don't immediately * see it. */ const char * const *members = (const char * const *)grp->gr_mem; de = str_list_dav(val, members, 0); } else if (uu_strcaseeq(a, "x-sun-canonicalName")) { char *canon; RDLOCK_CONFIG(); (void) asprintf(&canon, "%s@%s", grp->gr_name, _idmapdstate.hostname); UNLOCK_CONFIG(); if (canon == NULL) goto nomem; const char *ccanon = canon; de = str_list_dav(val, &ccanon, 1); free(canon); } else if (uu_strcaseeq(a, "x-sun-provider")) { const char *provider = "UNIX-group"; de = str_list_dav(val, &provider, 1); } } if (de != NULL) return (de); } return (NULL); nomem: return (directory_error("ENOMEM.users", "No memory allocating return value for user lookup", NULL)); } /* * Populate a directory attribute value with a SID based on our machine SID * and the specified RID. * * It's a bit perverse that we must take a text-format SID and turn it into * a binary-format SID, only to have the caller probably turn it back into * text format, but SIDs are carried across LDAP in binary format. */ static directory_error_t machine_sid_dav(directory_values_rpc *lvals, unsigned int rid) { sid_t *sid; directory_error_t de; RDLOCK_CONFIG(); int len = strlen(_idmapdstate.cfg->pgcfg.machine_sid); char buf[len + 100]; /* 100 is enough space for any RID */ (void) snprintf(buf, sizeof (buf), "%s-%u", _idmapdstate.cfg->pgcfg.machine_sid, rid); UNLOCK_CONFIG(); sid = sid_fromstr(buf); if (sid == NULL) goto nomem; sid_to_le(sid); de = bin_list_dav(lvals, sid, 1, sid_len(sid)); sid_free(sid); return (de); nomem: return (directory_error("ENOMEM.machine_sid_dav", "Out of memory allocating return value for lookup", NULL)); } struct directory_provider_static directory_provider_nsswitch = { "files", directory_provider_nsswitch_get, }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Server-side support for directory information lookup functions. */ #include #include #include #include #include #include #include #include #include #include #include #include "idmapd.h" #include "directory.h" #include "directory_private.h" #include #include "directory_library_impl.h" #include "directory_server_impl.h" #include "sized_array.h" /* * Here's a list of all of the modules that provide directory * information. In the fullness of time this should probably be * a plugin-able switch mechanism. * Note that the list is in precedence order. */ extern struct directory_provider_static directory_provider_builtin; extern struct directory_provider_static directory_provider_nsswitch; extern struct directory_provider_static directory_provider_ad; struct directory_provider_static *providers[] = { &directory_provider_builtin, &directory_provider_nsswitch, &directory_provider_ad, }; /* * This is the entry point for all directory lookup service requests. */ bool_t directory_get_common_1_svc( idmap_utf8str_list ids, idmap_utf8str types, idmap_utf8str_list attrs, directory_results_rpc *result, struct svc_req *req) { NOTE(ARGUNUSED(req)) int nids; directory_entry_rpc *entries; directory_error_t de; int i; nids = ids.idmap_utf8str_list_len; entries = (directory_entry_rpc *) calloc(nids, sizeof (directory_entry_rpc)); if (entries == NULL) goto nomem; result->directory_results_rpc_u.entries.entries_val = entries; result->directory_results_rpc_u.entries.entries_len = nids; result->failed = FALSE; for (i = 0; i < nids; i++) { if (strlen(ids.idmap_utf8str_list_val[i]) > IDMAP_MAX_NAME_LEN) { directory_entry_set_error(&entries[i], directory_error("invalid_arg.id.too_long", "Identifier too long", NULL)); } } for (i = 0; i < UU_NELEM(providers); i++) { de = providers[i]->get(entries, &ids, types, &attrs); if (de != NULL) goto err; } return (TRUE); nomem: de = directory_error("ENOMEM.get_common", "Insufficient memory retrieving directory data", NULL); err: xdr_free(xdr_directory_results_rpc, (char *)result); result->failed = TRUE; return ( directory_error_to_rpc(&result->directory_results_rpc_u.err, de)); } /* * Split name into {domain, name}. * Suggest allocating name and domain on the stack, same size as id, * using variable length arrays. */ void split_name(char *name, char *domain, char *id) { char *p; if ((p = strchr(id, '@')) != NULL) { (void) strlcpy(name, id, p - id + 1); (void) strcpy(domain, p + 1); } else if ((p = strchr(id, '\\')) != NULL) { (void) strcpy(name, p + 1); (void) strlcpy(domain, id, p - id + 1); } else { (void) strcpy(name, id); (void) strcpy(domain, ""); } } /* * Given a list of strings, return a set of directory attribute values. * * Mark that the attribute was found. * * Note that the terminating \0 is *not* included in the result, because * that's the way that strings come from LDAP. * (Note also that the client side stuff adds in a terminating \0.) * * Note that on error the array may have been partially populated and will * need to be cleaned up by the caller. This is normally not a problem * because the caller will need to clean up several such arrays. */ directory_error_t str_list_dav(directory_values_rpc *lvals, const char * const *str_list, int n) { directory_value_rpc *dav; int i; if (n == 0) { for (n = 0; str_list[n] != NULL; n++) /* LOOP */; } dav = calloc(n, sizeof (directory_value_rpc)); if (dav == NULL) goto nomem; lvals->directory_values_rpc_u.values.values_val = dav; lvals->directory_values_rpc_u.values.values_len = n; lvals->found = TRUE; for (i = 0; i < n; i++) { int len; len = strlen(str_list[i]); dav[i].directory_value_rpc_val = uu_memdup(str_list[i], len); if (dav[i].directory_value_rpc_val == NULL) goto nomem; dav[i].directory_value_rpc_len = len; } return (NULL); nomem: return (directory_error("ENOMEM.str_list_dav", "Insufficient memory copying values")); } /* * Given a list of unsigned integers, return a set of string directory * attribute values. * * Mark that the attribute was found. * * Note that the terminating \0 is *not* included in the result, because * that's the way that strings come from LDAP. * (Note also that the client side stuff adds in a terminating \0.) * * Note that on error the array may have been partially populated and will * need to be cleaned up by the caller. This is normally not a problem * because the caller will need to clean up several such arrays. */ directory_error_t uint_list_dav(directory_values_rpc *lvals, const unsigned int *array, int n) { directory_value_rpc *dav; int i; dav = calloc(n, sizeof (directory_value_rpc)); if (dav == NULL) goto nomem; lvals->directory_values_rpc_u.values.values_val = dav; lvals->directory_values_rpc_u.values.values_len = n; lvals->found = TRUE; for (i = 0; i < n; i++) { char buf[100]; /* larger than any integer */ int len; (void) snprintf(buf, sizeof (buf), "%u", array[i]); len = strlen(buf); dav[i].directory_value_rpc_val = uu_memdup(buf, len); if (dav[i].directory_value_rpc_val == NULL) goto nomem; dav[i].directory_value_rpc_len = len; } return (NULL); nomem: return (directory_error("ENOMEM.uint_list_dav", "Insufficient memory copying values")); } /* * Given a list of fixed-length binary chunks, return a set of binary * directory attribute values. * * Mark that the attribute was found. * * Note that on error the array may have been partially populated and will * need to be cleaned up by the caller. This is normally not a problem * because the caller will need to clean up several such arrays. */ directory_error_t bin_list_dav(directory_values_rpc *lvals, const void *array, int n, size_t sz) { directory_value_rpc *dav; char *inbuf = (char *)array; int i; dav = calloc(n, sizeof (directory_value_rpc)); if (dav == NULL) goto nomem; lvals->directory_values_rpc_u.values.values_val = dav; lvals->directory_values_rpc_u.values.values_len = n; lvals->found = TRUE; for (i = 0; i < n; i++) { dav[i].directory_value_rpc_val = uu_memdup(inbuf, sz); if (dav[i].directory_value_rpc_val == NULL) goto nomem; dav[i].directory_value_rpc_len = sz; inbuf += sz; } return (NULL); nomem: return (directory_error("ENOMEM.bin_list_dav", "Insufficient memory copying values")); } /* * Set up to return an error on a particular directory entry. * Note that the caller need not (and in fact must not) free * the directory_error_t; it will be freed when the directory entry * list is freed. */ void directory_entry_set_error(directory_entry_rpc *ent, directory_error_t de) { xdr_free(xdr_directory_entry_rpc, (char *)&ent); ent->status = DIRECTORY_ERROR; (void) directory_error_to_rpc(&ent->directory_entry_rpc_u.err, de); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DIRECTORY_SERVER_IMPL_H #define _DIRECTORY_SERVER_IMPL_H /* * Internal implementation details for the server side of directory lookup. */ #ifdef __cplusplus extern "C" { #endif /* * Functions to populate Directory Attribute Value lists. */ directory_error_t str_list_dav(directory_values_rpc *lvals, const char * const *str_list, int n); directory_error_t uint_list_dav(directory_values_rpc *lvals, const unsigned int *uint_list, int n); directory_error_t bin_list_dav(directory_values_rpc *lvals, const void *array, int n, size_t sz); /* * Split a name@domain into name, domain. Recommend allocating the * destination buffers the same size as the input, on the stack, * using variable length arrays. */ void split_name(char *name, char *domain, char *id); /* * Insert a directory_error_t into a directory entry to be returned. * Caller MUST NOT free the directory_error_t. */ void directory_entry_set_error(directory_entry_rpc *ent, directory_error_t de); /* * This is the structure by which a provider supplies its entry points. * The name is not currently used. */ struct directory_provider_static { char *name; directory_error_t (*get)( directory_entry_rpc *ret, idmap_utf8str_list *ids, idmap_utf8str types, idmap_utf8str_list *attrs); }; #ifdef __cplusplus } #endif #endif /* _DIRECTORY_SERVER_IMPL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Nexenta Systems, Inc. All rights reserved. * Copyright 2023 RackTop Systems, Inc. */ /* * Config routines common to idmap(8) and idmapd(8) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "idmapd.h" #include "addisc.h" #define MACHINE_SID_LEN (9 + 3 * 11) #define FMRI_BASE "svc:/system/idmap" #define CONFIG_PG "config" #define DEBUG_PG "debug" #define RECONFIGURE 1 #define POKE_AUTO_DISCOVERY 2 #define KICK_AUTO_DISCOVERY 3 /* * Default cache timeouts. Can override via svccfg * config/id_cache_timeout = count: seconds * config/name_cache_timeout = count: seconds */ #define ID_CACHE_TMO_DEFAULT 86400 #define NAME_CACHE_TMO_DEFAULT 604800 /* * Default maximum time between rediscovery runs. * config/rediscovery_interval = count: seconds */ #define REDISCOVERY_INTERVAL_DEFAULT 3600 /* * Minimum time between rediscovery runs, in case adutils gives us a * really short TTL (which it never should, but be defensive) * (not configurable) seconds. */ #define MIN_REDISCOVERY_INTERVAL 60 /* * Max number of concurrent door calls */ #define MAX_THREADS_DEFAULT 40 /* * Number of failed discovery attempts before we mark the service degraded. */ #define DISCOVERY_RETRY_DEGRADE_CUTOFF 6 /* * Default maximum time between discovery attempts when we don't have a DC. * config/discovery_retry_max_delay = count: seconds */ #define DISCOVERY_RETRY_MAX_DELAY_DEFAULT 30 /* * Initial retry delay when discovery fails. Doubles on every failure. */ #define DISCOVERY_RETRY_INITIAL_DELAY 1 enum event_type { EVENT_NOTHING, /* Woke up for no good reason */ EVENT_TIMEOUT, /* Timeout expired */ EVENT_ROUTING, /* An interesting routing event happened */ EVENT_POKED, /* Requested from degrade_svc() */ EVENT_KICKED, /* Force rediscovery, i.e. DC failed. */ EVENT_REFRESH, /* SMF refresh */ }; static void idmapd_set_krb5_realm(char *); static pthread_t update_thread_handle = 0; static int idmapd_ev_port = -1; static int rt_sock = -1; struct enum_lookup_map directory_mapping_map[] = { { DIRECTORY_MAPPING_NONE, "none" }, { DIRECTORY_MAPPING_NAME, "name" }, { DIRECTORY_MAPPING_IDMU, "idmu" }, { 0, NULL }, }; struct enum_lookup_map trust_dir_map[] = { { 1, "they trust us" }, { 2, "we trust them" }, { 3, "we trust each other" }, { 0, NULL }, }; static int generate_machine_uuid(char **machine_uuid) { uuid_t uu; *machine_uuid = calloc(1, UUID_PRINTABLE_STRING_LENGTH + 1); if (*machine_uuid == NULL) { idmapdlog(LOG_ERR, "Out of memory"); return (-1); } uuid_clear(uu); uuid_generate_time(uu); uuid_unparse(uu, *machine_uuid); return (0); } static int generate_machine_sid(char **machine_sid, char *machine_uuid) { union { uuid_t uu; uint32_t v[4]; } uv; int len; /* * Split the 128-bit machine UUID into three 32-bit values * we'll use as the "sub-authorities" of the machine SID. * The machine_sid will have the form S-1-5-21-J-K-L * (that's four sub-authorities altogether) where: * J = last 4 bytes of node_addr, * K = time_mid, time_hi_and_version * L = time_low * (see struct uuid) */ (void) memset(&uv, 0, sizeof (uv)); (void) uuid_parse(machine_uuid, uv.uu); len = asprintf(machine_sid, "S-1-5-21-%u-%u-%u", uv.v[3], uv.v[0], uv.v[1]); if (len == -1 || *machine_sid == NULL) { idmapdlog(LOG_ERR, "Out of memory"); return (-1); } return (0); } /* In the case of error, exists is set to FALSE anyway */ static int prop_exists(idmap_cfg_handles_t *handles, const char *name, boolean_t *exists) { scf_property_t *scf_prop; *exists = B_FALSE; scf_prop = scf_property_create(handles->main); if (scf_prop == NULL) { idmapdlog(LOG_ERR, "scf_property_create() failed: %s", scf_strerror(scf_error())); return (-1); } if (scf_pg_get_property(handles->config_pg, name, scf_prop) == 0) *exists = B_TRUE; scf_property_destroy(scf_prop); return (0); } static int get_debug(idmap_cfg_handles_t *handles, const char *name) { int64_t i64 = 0; scf_property_t *scf_prop; scf_value_t *value; scf_prop = scf_property_create(handles->main); if (scf_prop == NULL) { idmapdlog(LOG_ERR, "scf_property_create() failed: %s", scf_strerror(scf_error())); abort(); } value = scf_value_create(handles->main); if (value == NULL) { idmapdlog(LOG_ERR, "scf_value_create() failed: %s", scf_strerror(scf_error())); abort(); } if (scf_pg_get_property(handles->debug_pg, name, scf_prop) < 0) { /* this is OK: the property is just undefined */ goto destruction; } if (scf_property_get_value(scf_prop, value) < 0) { /* It is still OK when a property doesn't have any value */ goto destruction; } if (scf_value_get_integer(value, &i64) != 0) { idmapdlog(LOG_ERR, "Can not retrieve %s/%s: %s", DEBUG_PG, name, scf_strerror(scf_error())); abort(); } destruction: scf_value_destroy(value); scf_property_destroy(scf_prop); return ((int)i64); } static int get_val_bool(idmap_cfg_handles_t *handles, const char *name, boolean_t *val, boolean_t default_val) { int rc = 0; scf_property_t *scf_prop; scf_value_t *value; *val = default_val; scf_prop = scf_property_create(handles->main); if (scf_prop == NULL) { idmapdlog(LOG_ERR, "scf_property_create() failed: %s", scf_strerror(scf_error())); return (-1); } value = scf_value_create(handles->main); if (value == NULL) { idmapdlog(LOG_ERR, "scf_value_create() failed: %s", scf_strerror(scf_error())); scf_property_destroy(scf_prop); return (-1); } /* It is OK if the property is undefined */ if (scf_pg_get_property(handles->config_pg, name, scf_prop) < 0) goto destruction; /* It is still OK when a property doesn't have any value */ if (scf_property_get_value(scf_prop, value) < 0) goto destruction; uint8_t b; rc = scf_value_get_boolean(value, &b); if (rc == 0) *val = (boolean_t)b; destruction: scf_value_destroy(value); scf_property_destroy(scf_prop); return (rc); } static int get_val_int(idmap_cfg_handles_t *handles, const char *name, void *val, scf_type_t type) { int rc = 0; scf_property_t *scf_prop; scf_value_t *value; switch (type) { case SCF_TYPE_COUNT: *(uint64_t *)val = 0; break; case SCF_TYPE_INTEGER: *(int64_t *)val = 0; break; default: idmapdlog(LOG_ERR, "Invalid scf integer type (%d)", type); abort(); } scf_prop = scf_property_create(handles->main); if (scf_prop == NULL) { idmapdlog(LOG_ERR, "scf_property_create() failed: %s", scf_strerror(scf_error())); return (-1); } value = scf_value_create(handles->main); if (value == NULL) { idmapdlog(LOG_ERR, "scf_value_create() failed: %s", scf_strerror(scf_error())); scf_property_destroy(scf_prop); return (-1); } if (scf_pg_get_property(handles->config_pg, name, scf_prop) < 0) /* this is OK: the property is just undefined */ goto destruction; if (scf_property_get_value(scf_prop, value) < 0) /* It is still OK when a property doesn't have any value */ goto destruction; switch (type) { case SCF_TYPE_COUNT: rc = scf_value_get_count(value, val); break; case SCF_TYPE_INTEGER: rc = scf_value_get_integer(value, val); break; default: abort(); /* tested above */ /* NOTREACHED */ } if (rc != 0) { idmapdlog(LOG_ERR, "Can not retrieve config/%s: %s", name, scf_strerror(scf_error())); } destruction: scf_value_destroy(value); scf_property_destroy(scf_prop); return (rc); } static char * scf_value2string(const char *name, scf_value_t *value) { static size_t max_val = 0; if (max_val == 0) max_val = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); char buf[max_val + 1]; if (scf_value_get_astring(value, buf, max_val + 1) < 0) { idmapdlog(LOG_ERR, "Can not retrieve config/%s: %s", name, scf_strerror(scf_error())); return (NULL); } char *s = strdup(buf); if (s == NULL) idmapdlog(LOG_ERR, "Out of memory"); return (s); } /* * Load a domain server property. These are multi-value string properties. * We'll later map these to an ad_disc_ds_t, which includes looking up * the name in DNS, so don't do that before startup completes. */ static int get_val_ds(idmap_cfg_handles_t *handles, const char *name, char ***val) { scf_property_t *scf_prop; scf_value_t *value; scf_iter_t *iter; char *host, **servers = NULL; int len, i; int count = 0; int rc = -1; *val = NULL; restart: scf_prop = scf_property_create(handles->main); if (scf_prop == NULL) { idmapdlog(LOG_ERR, "scf_property_create() failed: %s", scf_strerror(scf_error())); return (-1); } value = scf_value_create(handles->main); if (value == NULL) { idmapdlog(LOG_ERR, "scf_value_create() failed: %s", scf_strerror(scf_error())); scf_property_destroy(scf_prop); return (-1); } iter = scf_iter_create(handles->main); if (iter == NULL) { idmapdlog(LOG_ERR, "scf_iter_create() failed: %s", scf_strerror(scf_error())); scf_value_destroy(value); scf_property_destroy(scf_prop); return (-1); } if (scf_pg_get_property(handles->config_pg, name, scf_prop) < 0) { /* this is OK: the property is just undefined */ rc = 0; goto destruction; } if (scf_iter_property_values(iter, scf_prop) < 0) { idmapdlog(LOG_ERR, "scf_iter_property_values(%s) failed: %s", name, scf_strerror(scf_error())); goto destruction; } /* Workaround scf bugs -- can't reset an iteration */ if (count == 0) { while (scf_iter_next_value(iter, value) > 0) count++; if (count == 0) { /* no values */ rc = 0; goto destruction; } scf_value_destroy(value); scf_iter_destroy(iter); scf_property_destroy(scf_prop); goto restart; } if ((servers = calloc(count + 1, sizeof (*servers))) == NULL) { idmapdlog(LOG_ERR, "Out of memory"); goto destruction; } i = 0; while (i < count && scf_iter_next_value(iter, value) > 0) { if ((host = scf_value2string(name, value)) == NULL) continue; /* * Ignore this server if the hostname is too long * or empty (continue without i++) */ len = strlen(host); if (len == 0) { if (DBG(CONFIG, 1)) { idmapdlog(LOG_INFO, "%s host=\"\"", name); } continue; } if (len >= AD_DISC_MAXHOSTNAME) { idmapdlog(LOG_ERR, "Host name too long: %s", host); idmapdlog(LOG_ERR, "ignoring %s value", name); continue; } /* Add a DS to the array. */ servers[i++] = host; } if (i == 0) { if (DBG(CONFIG, 1)) { idmapdlog(LOG_INFO, "%s is empty", name); } free(servers); servers = NULL; } *val = servers; rc = 0; destruction: scf_value_destroy(value); scf_iter_destroy(iter); scf_property_destroy(scf_prop); if (rc < 0) { if (servers) free(servers); *val = NULL; } return (rc); } static int resolve_ds_addr(idmap_cfg_handles_t *handles, const char *name, int defport, char **ds, ad_disc_ds_t **val) { struct addrinfo hints = { .ai_protocol = IPPROTO_TCP, .ai_socktype = SOCK_STREAM }; struct addrinfo *ai; ad_disc_ds_t *servers = NULL; int err, i, num_ds = 0; *val = NULL; if (ds == NULL || ds[0] == NULL) { if (DBG(CONFIG, 1)) idmapdlog(LOG_INFO, "%s is empty", name); return (0); } for (i = 0; ds[i] != NULL; i++) num_ds++; if ((servers = calloc(num_ds + 1, sizeof (*servers))) == NULL) { idmapdlog(LOG_ERR, "Out of memory"); return (-1); } i = 0; while (i < num_ds && *ds != NULL) { char port_str[8]; char *pport; const char *host = *ds++; servers[i].priority = 0; servers[i].weight = 100; servers[i].port = defport; if ((pport = strchr(host, ':')) != NULL) { *pport++ = '\0'; servers[i].port = strtol(pport, (char **)NULL, 10); if (servers[i].port == 0) servers[i].port = defport; } /* * Get the host address. If we can't, then * log an error and skip this host. */ if (DBG(CONFIG, 2)) idmapdlog(LOG_INFO, "%s: lookup %s:%d", name, host, servers[i].port); (void) snprintf(port_str, sizeof (port_str), "%d", servers[i].port); ai = NULL; err = getaddrinfo(host, port_str, &hints, &ai); if (err != 0) { idmapdlog(LOG_ERR, "%s: No address for host: %s (%s); skipping host", name, host, gai_strerror(err)); continue; } (void) strlcpy(servers[i].host, host, sizeof (servers->host)); (void) memcpy(&servers[i].addr, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); /* Added a DS to the array. */ i++; } if (i == 0) { if (DBG(CONFIG, 1)) { idmapdlog(LOG_INFO, "No valid values in %s", name); } free(servers); servers = NULL; } *val = servers; return (0); } static int get_val_astring(idmap_cfg_handles_t *handles, const char *name, char **val) { int rc = 0; scf_property_t *scf_prop; scf_value_t *value; scf_prop = scf_property_create(handles->main); if (scf_prop == NULL) { idmapdlog(LOG_ERR, "scf_property_create() failed: %s", scf_strerror(scf_error())); return (-1); } value = scf_value_create(handles->main); if (value == NULL) { idmapdlog(LOG_ERR, "scf_value_create() failed: %s", scf_strerror(scf_error())); scf_property_destroy(scf_prop); return (-1); } *val = NULL; if (scf_pg_get_property(handles->config_pg, name, scf_prop) < 0) /* this is OK: the property is just undefined */ goto destruction; if (scf_property_get_value(scf_prop, value) < 0) { idmapdlog(LOG_ERR, "scf_property_get_value(%s) failed: %s", name, scf_strerror(scf_error())); rc = -1; goto destruction; } *val = scf_value2string(name, value); if (*val == NULL) rc = -1; destruction: scf_value_destroy(value); scf_property_destroy(scf_prop); if (rc < 0) { if (*val) free(*val); *val = NULL; } return (rc); } static int del_val( idmap_cfg_handles_t *handles, scf_propertygroup_t *pg, const char *name) { int rc = -1; int ret; scf_transaction_t *tx = NULL; scf_transaction_entry_t *ent = NULL; if ((tx = scf_transaction_create(handles->main)) == NULL) { idmapdlog(LOG_ERR, "scf_transaction_create() failed: %s", scf_strerror(scf_error())); goto destruction; } if ((ent = scf_entry_create(handles->main)) == NULL) { idmapdlog(LOG_ERR, "scf_entry_create() failed: %s", scf_strerror(scf_error())); goto destruction; } do { if (scf_pg_update(pg) == -1) { idmapdlog(LOG_ERR, "scf_pg_update(%s) failed: %s", name, scf_strerror(scf_error())); goto destruction; } if (scf_transaction_start(tx, pg) != 0) { idmapdlog(LOG_ERR, "scf_transaction_start(%s) failed: %s", name, scf_strerror(scf_error())); goto destruction; } if (scf_transaction_property_delete(tx, ent, name) != 0) { /* Don't complain if it already doesn't exist. */ if (scf_error() != SCF_ERROR_NOT_FOUND) { idmapdlog(LOG_ERR, "scf_transaction_property_delete() failed:" " %s", scf_strerror(scf_error())); } goto destruction; } ret = scf_transaction_commit(tx); if (ret == 0) scf_transaction_reset(tx); } while (ret == 0); if (ret == -1) { idmapdlog(LOG_ERR, "scf_transaction_commit(%s) failed: %s", name, scf_strerror(scf_error())); goto destruction; } rc = 0; destruction: if (ent != NULL) scf_entry_destroy(ent); if (tx != NULL) scf_transaction_destroy(tx); return (rc); } static int set_val( idmap_cfg_handles_t *handles, scf_propertygroup_t *pg, const char *name, scf_value_t *value) { int rc = -1; int i; scf_property_t *prop = NULL; scf_transaction_t *tx = NULL; scf_transaction_entry_t *ent = NULL; if ((prop = scf_property_create(handles->main)) == NULL || (tx = scf_transaction_create(handles->main)) == NULL || (ent = scf_entry_create(handles->main)) == NULL) { idmapdlog(LOG_ERR, "Unable to set property %s", name, scf_strerror(scf_error())); goto destruction; } for (i = 0; i < MAX_TRIES; i++) { int ret; if (scf_pg_update(pg) == -1) { idmapdlog(LOG_ERR, "scf_pg_update() failed: %s", scf_strerror(scf_error())); goto destruction; } if (scf_transaction_start(tx, pg) == -1) { idmapdlog(LOG_ERR, "scf_transaction_start(%s) failed: %s", name, scf_strerror(scf_error())); goto destruction; } ret = scf_pg_get_property(pg, name, prop); if (ret == SCF_SUCCESS) { if (scf_transaction_property_change_type(tx, ent, name, scf_value_type(value)) < 0) { idmapdlog(LOG_ERR, "scf_transaction_property_change_type(%s)" " failed: %s", name, scf_strerror(scf_error())); goto destruction; } } else if (scf_error() == SCF_ERROR_NOT_FOUND) { if (scf_transaction_property_new(tx, ent, name, scf_value_type(value)) < 0) { idmapdlog(LOG_ERR, "scf_transaction_property_new() failed: %s", scf_strerror(scf_error())); goto destruction; } } else { idmapdlog(LOG_ERR, "scf_pg_get_property(%s) failed: %s", name, scf_strerror(scf_error())); goto destruction; } if (scf_entry_add_value(ent, value) == -1) { idmapdlog(LOG_ERR, "scf_entry_add_value() failed: %s", scf_strerror(scf_error())); goto destruction; } ret = scf_transaction_commit(tx); if (ret == 0) { /* * Property group set in scf_transaction_start() * is not the most recent. Update pg, reset tx and * retry tx. */ idmapdlog(LOG_WARNING, "scf_transaction_commit(%s) failed: %s", name, scf_strerror(scf_error())); scf_transaction_reset(tx); continue; } if (ret != 1) { idmapdlog(LOG_ERR, "scf_transaction_commit(%s) failed: %s", name, scf_strerror(scf_error())); goto destruction; } /* Success! */ rc = 0; break; } destruction: scf_entry_destroy(ent); scf_transaction_destroy(tx); scf_property_destroy(prop); return (rc); } static int set_val_integer( idmap_cfg_handles_t *handles, scf_propertygroup_t *pg, const char *name, int64_t val) { scf_value_t *value = NULL; int rc; if ((value = scf_value_create(handles->main)) == NULL) { idmapdlog(LOG_ERR, "Unable to set property %s", name, scf_strerror(scf_error())); return (-1); } scf_value_set_integer(value, val); rc = set_val(handles, pg, name, value); scf_value_destroy(value); return (rc); } static int set_val_astring( idmap_cfg_handles_t *handles, scf_propertygroup_t *pg, const char *name, const char *val) { scf_value_t *value = NULL; int rc = -1; if ((value = scf_value_create(handles->main)) == NULL) { idmapdlog(LOG_ERR, "Unable to set property %s", name, scf_strerror(scf_error())); goto out; } if (scf_value_set_astring(value, val) == -1) { idmapdlog(LOG_ERR, "scf_value_set_astring() failed: %s", scf_strerror(scf_error())); goto out; } rc = set_val(handles, pg, name, value); out: scf_value_destroy(value); return (rc); } /* * This function updates a boolean value. * If nothing has changed it returns 0 else 1 */ static int update_bool(boolean_t *value, boolean_t *new, char *name) { if (*value == *new) return (0); if (DBG(CONFIG, 1)) { idmapdlog(LOG_INFO, "change %s=%s", name, *new ? "true" : "false"); } *value = *new; return (1); } /* * This function updates a uint64_t value. * If nothing has changed it returns 0 else 1 */ static int update_uint64(uint64_t *value, uint64_t *new, char *name) { if (*value == *new) return (0); if (DBG(CONFIG, 1)) idmapdlog(LOG_INFO, "change %s=%llu", name, *new); *value = *new; return (1); } /* * This function updates a string value. * If nothing has changed it returns 0 else 1 */ static int update_string(char **value, char **new, char *name) { int changed; if (*new == NULL && *value != NULL) changed = 1; else if (*new != NULL && *value == NULL) changed = 1; else if (*new != NULL && *value != NULL && strcmp(*new, *value) != 0) changed = 1; else changed = 0; /* * Note that even if unchanged we can't just return; we must free one * of the values. */ if (DBG(CONFIG, 1) && changed) idmapdlog(LOG_INFO, "change %s=%s", name, CHECK_NULL(*new)); free(*value); *value = *new; *new = NULL; return (changed); } static int update_enum(int *value, int *new, char *name, struct enum_lookup_map *map) { if (*value == *new) return (0); if (DBG(CONFIG, 1)) { idmapdlog(LOG_INFO, "change %s=%s", name, enum_lookup(*new, map)); } *value = *new; return (1); } /* * This function updates a directory service structure. * If nothing has changed it returns 0 else 1 */ static int update_dirs(ad_disc_ds_t **value, ad_disc_ds_t **new, char *name) { if (*value == *new) /* Nothing to do */ return (0); if (*value != NULL && *new != NULL && ad_disc_compare_ds(*value, *new) == 0) { free(*new); *new = NULL; return (0); } if (*value != NULL) free(*value); *value = *new; *new = NULL; if (*value == NULL) { /* We're unsetting this DS property */ if (DBG(CONFIG, 1)) idmapdlog(LOG_INFO, "change %s=", name); return (1); } if (DBG(CONFIG, 1)) { /* List all the new DSs */ char buf[64]; ad_disc_ds_t *ds; for (ds = *value; ds->host[0] != '\0'; ds++) { if (ad_disc_getnameinfo(buf, sizeof (buf), &ds->addr)) (void) strlcpy(buf, "?", sizeof (buf)); idmapdlog(LOG_INFO, "change %s=%s addr=%s port=%d", name, ds->host, buf, ds->port); } } return (1); } /* * This function updates a trusted domains structure. * If nothing has changed it returns 0 else 1 */ static int update_trusted_domains(ad_disc_trusteddomains_t **value, ad_disc_trusteddomains_t **new, char *name) { int i; if (*value == *new) /* Nothing to do */ return (0); if (*value != NULL && *new != NULL && ad_disc_compare_trusteddomains(*value, *new) == 0) { free(*new); *new = NULL; return (0); } if (*value != NULL) free(*value); *value = *new; *new = NULL; if (*value == NULL) { /* We're unsetting this DS property */ if (DBG(CONFIG, 1)) idmapdlog(LOG_INFO, "change %s=", name); return (1); } if (DBG(CONFIG, 1)) { /* List all the new domains */ for (i = 0; (*value)[i].domain[0] != '\0'; i++) { idmapdlog(LOG_INFO, "change %s=%s direction=%s", name, (*value)[i].domain, enum_lookup((*value)[i].direction, trust_dir_map)); } } return (1); } /* * This function updates a domains in a forest structure. * If nothing has changed it returns 0 else 1 */ static int update_domains_in_forest(ad_disc_domainsinforest_t **value, ad_disc_domainsinforest_t **new, char *name) { int i; if (*value == *new) /* Nothing to do */ return (0); if (*value != NULL && *new != NULL && ad_disc_compare_domainsinforest(*value, *new) == 0) { free(*new); *new = NULL; return (0); } if (*value != NULL) free(*value); *value = *new; *new = NULL; if (*value == NULL) { /* We're unsetting this DS property */ if (DBG(CONFIG, 1)) idmapdlog(LOG_INFO, "change %s=", name); return (1); } if (DBG(CONFIG, 1)) { /* List all the new domains */ for (i = 0; (*value)[i].domain[0] != '\0'; i++) { idmapdlog(LOG_INFO, "change %s=%s", name, (*value)[i].domain); } } return (1); } static void free_trusted_forests(idmap_trustedforest_t **value, int *num_values) { int i; for (i = 0; i < *num_values; i++) { free((*value)[i].forest_name); free((*value)[i].global_catalog); free((*value)[i].domains_in_forest); } free(*value); *value = NULL; *num_values = 0; } static int compare_trusteddomainsinforest(ad_disc_domainsinforest_t *df1, ad_disc_domainsinforest_t *df2) { int i, j; int num_df1 = 0; int num_df2 = 0; boolean_t match; for (i = 0; df1[i].domain[0] != '\0'; i++) if (df1[i].trusted) num_df1++; for (j = 0; df2[j].domain[0] != '\0'; j++) if (df2[j].trusted) num_df2++; if (num_df1 != num_df2) return (1); for (i = 0; df1[i].domain[0] != '\0'; i++) { if (df1[i].trusted) { match = B_FALSE; for (j = 0; df2[j].domain[0] != '\0'; j++) { if (df2[j].trusted && domain_eq(df1[i].domain, df2[j].domain) && strcmp(df1[i].sid, df2[j].sid) == 0) { match = B_TRUE; break; } } if (!match) return (1); } } return (0); } /* * This function updates trusted forest structure. * If nothing has changed it returns 0 else 1 */ static int update_trusted_forest(idmap_trustedforest_t **value, int *num_value, idmap_trustedforest_t **new, int *num_new, char *name) { int i, j; boolean_t match; if (*value == *new) /* Nothing to do */ return (0); if (*value != NULL && *new != NULL) { if (*num_value != *num_new) goto not_equal; for (i = 0; i < *num_value; i++) { match = B_FALSE; for (j = 0; j < *num_new; j++) { if (strcmp((*value)[i].forest_name, (*new)[j].forest_name) == 0 && ad_disc_compare_ds( (*value)[i].global_catalog, (*new)[j].global_catalog) == 0 && compare_trusteddomainsinforest( (*value)[i].domains_in_forest, (*new)[j].domains_in_forest) == 0) { match = B_TRUE; break; } } if (!match) goto not_equal; } free_trusted_forests(new, num_new); return (0); } not_equal: if (*value != NULL) free_trusted_forests(value, num_value); *value = *new; *num_value = *num_new; *new = NULL; *num_new = 0; if (*value == NULL) { /* We're unsetting this DS property */ if (DBG(CONFIG, 1)) idmapdlog(LOG_INFO, "change %s=", name); return (1); } if (DBG(CONFIG, 1)) { /* List all the trusted forests */ for (i = 0; i < *num_value; i++) { idmap_trustedforest_t *f = &(*value)[i]; for (j = 0; f->domains_in_forest[j].domain[0] != '\0'; j++) { /* List trusted Domains in the forest. */ if (f->domains_in_forest[j].trusted) idmapdlog(LOG_INFO, "change %s=%s domain=%s", name, f->forest_name, f->domains_in_forest[j].domain); } /* List the hosts */ for (j = 0; f->global_catalog[j].host[0] != '\0'; j++) { idmapdlog(LOG_INFO, "change %s=%s host=%s port=%d", name, f->forest_name, f->global_catalog[j].host, f->global_catalog[j].port); } } } return (1); } const char * enum_lookup(int value, struct enum_lookup_map *map) { for (; map->string != NULL; map++) { if (value == map->value) { return (map->string); } } return ("(invalid)"); } /* * Returns 1 if the PF_ROUTE socket event indicates that we should rescan the * interfaces. * * Shamelessly based on smb_nics_changed() and other PF_ROUTE uses in ON. */ static boolean_t pfroute_event_is_interesting(int rt_sock) { int nbytes; int64_t msg[2048 / 8]; struct rt_msghdr *rtm; boolean_t is_interesting = B_FALSE; for (;;) { if ((nbytes = read(rt_sock, msg, sizeof (msg))) <= 0) break; rtm = (struct rt_msghdr *)msg; if (rtm->rtm_version != RTM_VERSION) continue; if (nbytes < rtm->rtm_msglen) continue; switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: case RTM_IFINFO: is_interesting = B_TRUE; break; default: break; } } return (is_interesting); } /* * Wait for an event, and report what kind of event occurred. * * Note that there are cases where we are awoken but don't care about * the lower-level event. We can't just loop here because we can't * readily calculate how long to sleep the next time. We return * EVENT_NOTHING and let the caller loop. */ static enum event_type wait_for_event(struct timespec *timeoutp) { port_event_t pe; (void) memset(&pe, 0, sizeof (pe)); if (port_get(idmapd_ev_port, &pe, timeoutp) != 0) { switch (errno) { case EINTR: return (EVENT_NOTHING); case ETIME: /* Timeout */ return (EVENT_TIMEOUT); default: /* EBADF, EBADFD, EFAULT, EINVAL (end of time?)? */ idmapdlog(LOG_ERR, "Event port failed: %s", strerror(errno)); exit(1); /* NOTREACHED */ } } switch (pe.portev_source) { case 0: /* * This isn't documented, but seems to be what you get if * the timeout is zero seconds and there are no events * pending. */ return (EVENT_TIMEOUT); case PORT_SOURCE_USER: switch (pe.portev_events) { case RECONFIGURE: return (EVENT_REFRESH); case POKE_AUTO_DISCOVERY: return (EVENT_POKED); case KICK_AUTO_DISCOVERY: return (EVENT_KICKED); } return (EVENT_NOTHING); case PORT_SOURCE_FD: if (pe.portev_object == rt_sock) { /* * PF_ROUTE socket read event: * re-associate fd * handle event */ if (port_associate(idmapd_ev_port, PORT_SOURCE_FD, rt_sock, POLLIN, NULL) != 0) { idmapdlog(LOG_ERR, "Failed to re-associate the " "routing socket with the event port: %s", strerror(errno)); abort(); } /* * The network configuration may still be in flux. * No matter, the resolver will re-transmit and * timeout if need be. */ if (pfroute_event_is_interesting(rt_sock)) { if (DBG(CONFIG, 1)) { idmapdlog(LOG_DEBUG, "Interesting routing event"); } return (EVENT_ROUTING); } else { if (DBG(CONFIG, 2)) { idmapdlog(LOG_DEBUG, "Boring routing event"); } return (EVENT_NOTHING); } } /* Event on an FD other than the routing FD? Ignore it. */ break; } return (EVENT_NOTHING); } void * idmap_cfg_update_thread(void *arg) { NOTE(ARGUNUSED(arg)) idmap_pg_config_t *pgcfg = &_idmapdstate.cfg->pgcfg; const ad_disc_t ad_ctx = _idmapdstate.cfg->handles.ad_ctx; int flags = CFG_DISCOVER; uint_t retry_count = 0; for (;;) { struct timespec timeout; struct timespec *timeoutp; int rc; int ttl, max_ttl; (void) ad_disc_SubnetChanged(ad_ctx); rc = idmap_cfg_load(_idmapdstate.cfg, flags); if (rc < -1) { idmapdlog(LOG_ERR, "Fatal errors while reading " "SMF properties"); exit(1); } else if (rc == -1) { idmapdlog(LOG_WARNING, "Errors re-loading configuration may cause AD " "lookups to fail"); } /* * If we don't know our domain name, we're not in a domain; * don't bother with rediscovery until the next config change. * Avoids hourly noise in workgroup mode. * * If we don't have a DC currently, use a greatly reduced TTL * until we get one. Degrade if that takes too long. */ if (pgcfg->domain_name == NULL) { ttl = -1; /* We don't need a DC if we're no longer in a domain. */ if (retry_count >= DISCOVERY_RETRY_DEGRADE_CUTOFF) restore_svc(); retry_count = 0; } else if (pgcfg->domain_controller == NULL || pgcfg->global_catalog == NULL) { if (retry_count == 0) ttl = DISCOVERY_RETRY_INITIAL_DELAY; else ttl *= 2; if (ttl > pgcfg->discovery_retry_max_delay) ttl = pgcfg->discovery_retry_max_delay; if (++retry_count >= DISCOVERY_RETRY_DEGRADE_CUTOFF) { degrade_svc(B_FALSE, "Too many DC discovery failures"); } } else { ttl = ad_disc_get_TTL(ad_ctx); max_ttl = (int)pgcfg->rediscovery_interval; if (ttl > max_ttl) ttl = max_ttl; if (ttl < MIN_REDISCOVERY_INTERVAL) ttl = MIN_REDISCOVERY_INTERVAL; if (retry_count >= DISCOVERY_RETRY_DEGRADE_CUTOFF) restore_svc(); retry_count = 0; } /* * Wait for an interesting event. Note that we might get * boring events between interesting events. If so, we loop. */ flags = CFG_DISCOVER; for (;;) { if (ttl < 0) { timeoutp = NULL; } else { timeout.tv_sec = ttl; timeout.tv_nsec = 0; timeoutp = &timeout; } if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "_cfg_update_thread waiting"); switch (wait_for_event(timeoutp)) { case EVENT_NOTHING: if (DBG(CONFIG, 2)) idmapdlog(LOG_DEBUG, "Boring event."); continue; case EVENT_REFRESH: if (DBG(CONFIG, 1)) idmapdlog(LOG_INFO, "SMF refresh"); /* * Forget any DC we had previously. */ flags |= CFG_FORGET_DC; break; case EVENT_POKED: if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "poked"); break; case EVENT_KICKED: if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "kicked"); flags |= CFG_FORGET_DC; break; case EVENT_TIMEOUT: if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "TTL expired"); break; case EVENT_ROUTING: /* Already logged to DEBUG */ break; } /* An interesting event! */ break; } } /* * Lint isn't happy with the concept of a function declared to * return something, that doesn't return. Of course, merely adding * the return isn't enough, because it's never reached... */ /*NOTREACHED*/ return (NULL); } int idmap_cfg_start_updates(void) { if ((idmapd_ev_port = port_create()) < 0) { idmapdlog(LOG_ERR, "Failed to create event port: %s", strerror(errno)); return (-1); } if ((rt_sock = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) { idmapdlog(LOG_ERR, "Failed to open routing socket: %s", strerror(errno)); (void) close(idmapd_ev_port); return (-1); } if (fcntl(rt_sock, F_SETFL, O_NDELAY|O_NONBLOCK) < 0) { idmapdlog(LOG_ERR, "Failed to set routing socket flags: %s", strerror(errno)); (void) close(rt_sock); (void) close(idmapd_ev_port); return (-1); } if (port_associate(idmapd_ev_port, PORT_SOURCE_FD, rt_sock, POLLIN, NULL) != 0) { idmapdlog(LOG_ERR, "Failed to associate the routing " "socket with the event port: %s", strerror(errno)); (void) close(rt_sock); (void) close(idmapd_ev_port); return (-1); } if ((errno = pthread_create(&update_thread_handle, NULL, idmap_cfg_update_thread, NULL)) != 0) { idmapdlog(LOG_ERR, "Failed to start update thread: %s", strerror(errno)); (void) port_dissociate(idmapd_ev_port, PORT_SOURCE_FD, rt_sock); (void) close(rt_sock); (void) close(idmapd_ev_port); return (-1); } return (0); } /* * Reject attribute names with invalid characters. */ static int valid_ldap_attr(const char *attr) { for (; *attr; attr++) { if (!isalnum(*attr) && *attr != '-' && *attr != '_' && *attr != '.' && *attr != ';') return (0); } return (1); } static void idmapd_set_debug( idmap_cfg_handles_t *handles, enum idmapd_debug item, const char *name) { int val; if (item < 0 || item > IDMAPD_DEBUG_MAX) return; val = get_debug(handles, name); if (val != _idmapdstate.debug[item]) idmapdlog(LOG_DEBUG, "%s/%s = %d", DEBUG_PG, name, val); _idmapdstate.debug[item] = val; } static void check_smf_debug_mode(idmap_cfg_handles_t *handles) { idmapd_set_debug(handles, IDMAPD_DEBUG_ALL, "all"); idmapd_set_debug(handles, IDMAPD_DEBUG_CONFIG, "config"); idmapd_set_debug(handles, IDMAPD_DEBUG_MAPPING, "mapping"); idmapd_set_debug(handles, IDMAPD_DEBUG_DISC, "discovery"); idmapd_set_debug(handles, IDMAPD_DEBUG_DNS, "dns"); idmapd_set_debug(handles, IDMAPD_DEBUG_LDAP, "ldap"); adutils_set_debug(AD_DEBUG_ALL, _idmapdstate.debug[IDMAPD_DEBUG_ALL]); adutils_set_debug(AD_DEBUG_DISC, _idmapdstate.debug[IDMAPD_DEBUG_DISC]); adutils_set_debug(AD_DEBUG_DNS, _idmapdstate.debug[IDMAPD_DEBUG_DNS]); adutils_set_debug(AD_DEBUG_LDAP, _idmapdstate.debug[IDMAPD_DEBUG_LDAP]); } /* * This is the half of idmap_cfg_load() that loads property values from * SMF (using the config/ property group of the idmap FMRI). * * Return values: 0 -> success, -1 -> failure, -2 -> hard failures * -3 -> hard smf config failures * reading from SMF. */ static int idmap_cfg_load_smf(idmap_cfg_handles_t *handles, idmap_pg_config_t *pgcfg, int * const errors) { int rc; char *s; *errors = 0; if (scf_pg_update(handles->config_pg) < 0) { idmapdlog(LOG_ERR, "scf_pg_update() failed: %s", scf_strerror(scf_error())); return (-2); } if (scf_pg_update(handles->debug_pg) < 0) { idmapdlog(LOG_ERR, "scf_pg_update() failed: %s", scf_strerror(scf_error())); return (-2); } check_smf_debug_mode(handles); rc = get_val_bool(handles, "unresolvable_sid_mapping", &pgcfg->eph_map_unres_sids, B_TRUE); if (rc != 0) (*errors)++; rc = get_val_bool(handles, "use_ads", &pgcfg->use_ads, B_TRUE); if (rc != 0) (*errors)++; rc = get_val_bool(handles, "use_lsa", &pgcfg->use_lsa, B_TRUE); if (rc != 0) (*errors)++; rc = get_val_bool(handles, "disable_cross_forest_trusts", &pgcfg->disable_cross_forest_trusts, B_TRUE); if (rc != 0) (*errors)++; rc = get_val_astring(handles, "directory_based_mapping", &s); if (rc != 0) (*errors)++; else if (s == NULL || strcasecmp(s, "none") == 0) pgcfg->directory_based_mapping = DIRECTORY_MAPPING_NONE; else if (strcasecmp(s, "name") == 0) pgcfg->directory_based_mapping = DIRECTORY_MAPPING_NAME; else if (strcasecmp(s, "idmu") == 0) pgcfg->directory_based_mapping = DIRECTORY_MAPPING_IDMU; else { pgcfg->directory_based_mapping = DIRECTORY_MAPPING_NONE; idmapdlog(LOG_ERR, "config/directory_based_mapping: invalid value \"%s\" ignored", s); (*errors)++; } free(s); rc = get_val_int(handles, "list_size_limit", &pgcfg->list_size_limit, SCF_TYPE_COUNT); if (rc != 0) (*errors)++; rc = get_val_int(handles, "max_threads", &pgcfg->max_threads, SCF_TYPE_COUNT); if (rc != 0) (*errors)++; if (pgcfg->max_threads == 0) pgcfg->max_threads = MAX_THREADS_DEFAULT; if (pgcfg->max_threads > UINT_MAX) pgcfg->max_threads = UINT_MAX; rc = get_val_int(handles, "discovery_retry_max_delay", &pgcfg->discovery_retry_max_delay, SCF_TYPE_COUNT); if (rc != 0) (*errors)++; if (pgcfg->discovery_retry_max_delay == 0) pgcfg->discovery_retry_max_delay = DISCOVERY_RETRY_MAX_DELAY_DEFAULT; rc = get_val_int(handles, "id_cache_timeout", &pgcfg->id_cache_timeout, SCF_TYPE_COUNT); if (rc != 0) (*errors)++; if (pgcfg->id_cache_timeout == 0) pgcfg->id_cache_timeout = ID_CACHE_TMO_DEFAULT; rc = get_val_int(handles, "name_cache_timeout", &pgcfg->name_cache_timeout, SCF_TYPE_COUNT); if (rc != 0) (*errors)++; if (pgcfg->name_cache_timeout == 0) pgcfg->name_cache_timeout = NAME_CACHE_TMO_DEFAULT; rc = get_val_int(handles, "rediscovery_interval", &pgcfg->rediscovery_interval, SCF_TYPE_COUNT); if (rc != 0) (*errors)++; if (pgcfg->rediscovery_interval == 0) pgcfg->rediscovery_interval = REDISCOVERY_INTERVAL_DEFAULT; rc = get_val_astring(handles, "domain_name", &pgcfg->domain_name); if (rc != 0) (*errors)++; else { if (pgcfg->domain_name != NULL && pgcfg->domain_name[0] == '\0') { free(pgcfg->domain_name); pgcfg->domain_name = NULL; } if (pgcfg->domain_name != NULL) pgcfg->domain_name_auto_disc = B_FALSE; (void) ad_disc_set_DomainName(handles->ad_ctx, pgcfg->domain_name); } rc = get_val_astring(handles, "default_domain", &pgcfg->default_domain); if (rc != 0) { /* * SCF failures fetching config/default_domain we treat * as fatal as they may leave ID mapping rules that * match unqualified winnames flapping in the wind. */ return (-2); } if (pgcfg->default_domain == NULL && pgcfg->domain_name != NULL) { pgcfg->default_domain = strdup(pgcfg->domain_name); } rc = get_val_astring(handles, "domain_guid", &s); if (rc != 0) { (*errors)++; } else if (s == NULL || s[0] == '\0') { /* OK, not set. */ free(s); } else { uuid_t u; if (uuid_parse(s, u) != 0) { idmapdlog(LOG_ERR, "config/domain_guid: invalid value \"%s\" ignored", s); free(s); (*errors)++; } else { pgcfg->domain_guid = s; pgcfg->domain_guid_auto_disc = B_FALSE; (void) ad_disc_set_DomainGUID(handles->ad_ctx, u); } } rc = get_val_astring(handles, "machine_uuid", &pgcfg->machine_uuid); if (rc != 0) (*errors)++; if (pgcfg->machine_uuid == NULL) { /* If machine_uuid not configured, generate one */ if (generate_machine_uuid(&pgcfg->machine_uuid) < 0) return (-2); rc = set_val_astring(handles, handles->config_pg, "machine_uuid", pgcfg->machine_uuid); if (rc != 0) (*errors)++; } rc = get_val_astring(handles, "machine_sid", &pgcfg->machine_sid); if (rc != 0) (*errors)++; if (pgcfg->machine_sid == NULL) { /* * If machine_sid not configured, generate one * from the machine UUID. */ if (generate_machine_sid(&pgcfg->machine_sid, pgcfg->machine_uuid) < 0) return (-2); rc = set_val_astring(handles, handles->config_pg, "machine_sid", pgcfg->machine_sid); if (rc != 0) (*errors)++; } rc = get_val_ds(handles, "domain_controller", &pgcfg->cfg_domain_controller); if (rc != 0) (*errors)++; rc = get_val_ds(handles, "preferred_dc", &pgcfg->cfg_preferred_dc); if (rc != 0) (*errors)++; rc = get_val_astring(handles, "forest_name", &pgcfg->forest_name); if (rc != 0) (*errors)++; else { if (pgcfg->forest_name != NULL && pgcfg->forest_name[0] == '\0') { free(pgcfg->forest_name); pgcfg->forest_name = NULL; } if (pgcfg->forest_name != NULL) pgcfg->forest_name_auto_disc = B_FALSE; (void) ad_disc_set_ForestName(handles->ad_ctx, pgcfg->forest_name); } rc = get_val_astring(handles, "site_name", &pgcfg->site_name); if (rc != 0) (*errors)++; else { if (pgcfg->site_name != NULL && pgcfg->site_name[0] == '\0') { free(pgcfg->site_name); pgcfg->site_name = NULL; } if (pgcfg->site_name != NULL) pgcfg->site_name_auto_disc = B_FALSE; (void) ad_disc_set_SiteName(handles->ad_ctx, pgcfg->site_name); } rc = get_val_ds(handles, "global_catalog", &pgcfg->cfg_global_catalog); if (rc != 0) (*errors)++; /* Unless we're doing directory-based name mapping, we're done. */ if (pgcfg->directory_based_mapping != DIRECTORY_MAPPING_NAME) return (0); rc = get_val_astring(handles, "ad_unixuser_attr", &pgcfg->ad_unixuser_attr); if (rc != 0) return (-2); if (pgcfg->ad_unixuser_attr != NULL && !valid_ldap_attr(pgcfg->ad_unixuser_attr)) { idmapdlog(LOG_ERR, "config/ad_unixuser_attr=%s is not a " "valid LDAP attribute name", pgcfg->ad_unixuser_attr); return (-3); } rc = get_val_astring(handles, "ad_unixgroup_attr", &pgcfg->ad_unixgroup_attr); if (rc != 0) return (-2); if (pgcfg->ad_unixgroup_attr != NULL && !valid_ldap_attr(pgcfg->ad_unixgroup_attr)) { idmapdlog(LOG_ERR, "config/ad_unixgroup_attr=%s is not a " "valid LDAP attribute name", pgcfg->ad_unixgroup_attr); return (-3); } rc = get_val_astring(handles, "nldap_winname_attr", &pgcfg->nldap_winname_attr); if (rc != 0) return (-2); if (pgcfg->nldap_winname_attr != NULL && !valid_ldap_attr(pgcfg->nldap_winname_attr)) { idmapdlog(LOG_ERR, "config/nldap_winname_attr=%s is not a " "valid LDAP attribute name", pgcfg->nldap_winname_attr); return (-3); } if (pgcfg->ad_unixuser_attr == NULL && pgcfg->ad_unixgroup_attr == NULL && pgcfg->nldap_winname_attr == NULL) { idmapdlog(LOG_ERR, "If config/directory_based_mapping property is set to " "\"name\" then at least one of the following name mapping " "attributes must be specified. (config/ad_unixuser_attr OR " "config/ad_unixgroup_attr OR config/nldap_winname_attr)"); return (-3); } return (rc); } static void log_if_unable(const void *val, const char *what) { if (val == NULL) { idmapdlog(LOG_DEBUG, "unable to discover %s", what); } } static void discover_trusted_domains(idmap_pg_config_t *pgcfg, ad_disc_t ad_ctx) { ad_disc_t trusted_ctx; int i, j, k, l; char *forestname; int num_trusteddomains; boolean_t new_forest; char *trusteddomain; ad_disc_ds_t *globalcatalog; idmap_trustedforest_t *trustedforests; ad_disc_domainsinforest_t *domainsinforest; pgcfg->trusted_domains = ad_disc_get_TrustedDomains(ad_ctx, NULL); if (pgcfg->forest_name != NULL && pgcfg->trusted_domains != NULL && pgcfg->trusted_domains[0].domain[0] != '\0') { /* * We have trusted domains. We need to go through every * one and find its forest. If it is a new forest we then need * to find its Global Catalog and the domains in the forest */ for (i = 0; pgcfg->trusted_domains[i].domain[0] != '\0'; i++) continue; num_trusteddomains = i; trustedforests = calloc(num_trusteddomains, sizeof (idmap_trustedforest_t)); j = 0; for (i = 0; pgcfg->trusted_domains[i].domain[0] != '\0'; i++) { trusteddomain = pgcfg->trusted_domains[i].domain; trusted_ctx = ad_disc_init(); (void) ad_disc_set_DomainName(trusted_ctx, trusteddomain); forestname = ad_disc_get_ForestName(trusted_ctx, NULL); if (forestname == NULL) { if (DBG(CONFIG, 1)) { idmapdlog(LOG_DEBUG, "unable to discover Forest Name" " for the trusted domain %s", trusteddomain); } ad_disc_fini(trusted_ctx); continue; } if (strcasecmp(forestname, pgcfg->forest_name) == 0) { /* * Ignore the domain as it is part of * the primary forest */ free(forestname); ad_disc_fini(trusted_ctx); continue; } /* Is this a new forest? */ new_forest = B_TRUE; for (k = 0; k < j; k++) { if (strcasecmp(forestname, trustedforests[k].forest_name) == 0) { new_forest = B_FALSE; domainsinforest = trustedforests[k].domains_in_forest; break; } } if (!new_forest) { /* Mark the domain as trusted */ for (l = 0; domainsinforest[l].domain[0] != '\0'; l++) { if (domain_eq(trusteddomain, domainsinforest[l].domain)) { domainsinforest[l].trusted = TRUE; break; } } free(forestname); ad_disc_fini(trusted_ctx); continue; } /* * Get the Global Catalog and the domains in * this new forest. */ globalcatalog = ad_disc_get_GlobalCatalog(trusted_ctx, AD_DISC_PREFER_SITE, NULL); if (globalcatalog == NULL) { if (DBG(CONFIG, 1)) { idmapdlog(LOG_DEBUG, "unable to discover Global Catalog" " for the trusted domain %s", trusteddomain); } free(forestname); ad_disc_fini(trusted_ctx); continue; } domainsinforest = ad_disc_get_DomainsInForest(trusted_ctx, NULL); if (domainsinforest == NULL) { if (DBG(CONFIG, 1)) { idmapdlog(LOG_DEBUG, "unable to discover Domains in the" " Forest for the trusted domain %s", trusteddomain); } free(globalcatalog); free(forestname); ad_disc_fini(trusted_ctx); continue; } trustedforests[j].forest_name = forestname; trustedforests[j].global_catalog = globalcatalog; trustedforests[j].domains_in_forest = domainsinforest; j++; /* Mark the domain as trusted */ for (l = 0; domainsinforest[l].domain[0] != '\0'; l++) { if (domain_eq(trusteddomain, domainsinforest[l].domain)) { domainsinforest[l].trusted = TRUE; break; } } ad_disc_fini(trusted_ctx); } if (j > 0) { pgcfg->num_trusted_forests = j; pgcfg->trusted_forests = trustedforests; } else { free(trustedforests); } } } /* * This is the half of idmap_cfg_load() that auto-discovers values of * discoverable properties that weren't already set via SMF properties. * * idmap_cfg_discover() is called *after* idmap_cfg_load_smf(), so it * needs to be careful not to overwrite any properties set in SMF. */ static void idmap_cfg_discover1(idmap_cfg_handles_t *handles, idmap_pg_config_t *pgcfg) { ad_disc_t ad_ctx = handles->ad_ctx; FILE *status_fp = NULL; time_t t0, t1; t0 = time(NULL); if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "Running domain discovery."); (void) unlink(IDMAP_CACHEDIR "/discovery.log"); status_fp = fopen(IDMAP_CACHEDIR "/discovery.log", "w"); if (status_fp) { (void) fchmod(fileno(status_fp), 0644); ad_disc_set_StatusFP(ad_ctx, status_fp); } if (pgcfg->domain_name == NULL) { idmapdlog(LOG_DEBUG, "No domain name specified."); if (status_fp) (void) fprintf(status_fp, "(no domain name)\n"); goto out; } if (pgcfg->domain_controller == NULL) pgcfg->domain_controller = ad_disc_get_DomainController(ad_ctx, AD_DISC_PREFER_SITE, &pgcfg->domain_controller_auto_disc); if (pgcfg->domain_guid == NULL) { char buf[UUID_PRINTABLE_STRING_LENGTH]; uchar_t *u = ad_disc_get_DomainGUID(ad_ctx, &pgcfg->domain_guid_auto_disc); (void) memset(buf, 0, sizeof (buf)); if (u != NULL) { uuid_unparse(u, buf); pgcfg->domain_guid = strdup(buf); } } if (pgcfg->forest_name == NULL) pgcfg->forest_name = ad_disc_get_ForestName(ad_ctx, &pgcfg->forest_name_auto_disc); if (pgcfg->site_name == NULL) pgcfg->site_name = ad_disc_get_SiteName(ad_ctx, &pgcfg->site_name_auto_disc); if (DBG(CONFIG, 1)) { log_if_unable(pgcfg->domain_name, "Domain Name"); log_if_unable(pgcfg->domain_controller, "Domain Controller"); log_if_unable(pgcfg->domain_guid, "Domain GUID"); log_if_unable(pgcfg->forest_name, "Forest Name"); log_if_unable(pgcfg->site_name, "Site Name"); } out: if (status_fp) { ad_disc_set_StatusFP(ad_ctx, NULL); (void) fclose(status_fp); status_fp = NULL; } if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "Domain discovery done."); /* * Log when this took more than 15 sec. */ t1 = time(NULL); if (t1 > (t0 + 15)) { idmapdlog(LOG_NOTICE, "Domain discovery took %d sec.", (int)(t1 - t0)); idmapdlog(LOG_NOTICE, "Check the DNS configuration."); } } /* * This is the second part of discovery, which can take a while. * We don't want to hold up parties who just want to know what * domain controller we're using (like smbd), so this part runs * after we've updated that info in the "live" config and told * such consumers to go ahead. * * This is a lot like idmap_cfg_discover(), but used LDAP queries * get the forest information from the global catalog servers. * * Note: the previous update_* calls have usually nuked any * useful information from pgcfg before we get here, so we * can only use it store discovery results, not to read. */ static void idmap_cfg_discover2(idmap_cfg_handles_t *handles, idmap_pg_config_t *pgcfg) { ad_disc_t ad_ctx = handles->ad_ctx; FILE *status_fp = NULL; time_t t0, t1; t0 = time(NULL); if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "Running forest discovery."); status_fp = fopen(IDMAP_CACHEDIR "/discovery.log", "a"); if (status_fp) ad_disc_set_StatusFP(ad_ctx, status_fp); if (pgcfg->global_catalog == NULL) pgcfg->global_catalog = ad_disc_get_GlobalCatalog(ad_ctx, AD_DISC_PREFER_SITE, &pgcfg->global_catalog_auto_disc); if (pgcfg->global_catalog != NULL) { pgcfg->domains_in_forest = ad_disc_get_DomainsInForest(ad_ctx, NULL); if (!pgcfg->disable_cross_forest_trusts) discover_trusted_domains(pgcfg, ad_ctx); } if (DBG(CONFIG, 1)) { log_if_unable(pgcfg->global_catalog, "Global Catalog"); log_if_unable(pgcfg->domains_in_forest, "Domains in the Forest"); /* Empty trusted domains list is OK. */ } if (status_fp) { ad_disc_set_StatusFP(ad_ctx, NULL); (void) fclose(status_fp); status_fp = NULL; } if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "Forest discovery done."); /* * Log when this took more than 30 sec. */ t1 = time(NULL); if (t1 > (t0 + 30)) { idmapdlog(LOG_NOTICE, "Forest discovery took %d sec.", (int)(t1 - t0)); idmapdlog(LOG_NOTICE, "Check AD join status."); } } /* * idmap_cfg_load() is called at startup, and periodically via the * update thread when the auto-discovery TTLs expire, as well as part of * the refresh method, to update the current configuration. It always * reads from SMF, but you still have to refresh the service after * changing the config pg in order for the changes to take effect. * * There is one flag: * * - CFG_DISCOVER * * If CFG_DISCOVER is set then idmap_cfg_load() calls * idmap_cfg_discover() to discover, via DNS and LDAP lookups, property * values that weren't set in SMF. * * idmap_cfg_load() will log (to LOG_NOTICE) whether the configuration * changed. * * Return values: 0 -> success, -1 -> failure, -2 -> hard failures * reading from SMF. */ int idmap_cfg_load(idmap_cfg_t *cfg, int flags) { const ad_disc_t ad_ctx = cfg->handles.ad_ctx; int rc = 0; int errors; int changed = 0; bool_t dc_changed = FALSE; bool_t gc_changed = FALSE; idmap_pg_config_t new_pgcfg, *live_pgcfg; if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "Loading configuration."); live_pgcfg = &cfg->pgcfg; (void) memset(&new_pgcfg, 0, sizeof (new_pgcfg)); (void) pthread_mutex_lock(&cfg->handles.mutex); if ((rc = idmap_cfg_load_smf(&cfg->handles, &new_pgcfg, &errors)) < -1) goto err; if (flags & CFG_DISCOVER) { ad_disc_refresh(ad_ctx); /* * Convert domain server configuration items to libadutils * values. This involves DNS, so we want to avoid doing this * during startup, less we risk slow or unresponsive servers * causing startup to timeout. */ rc = resolve_ds_addr(&cfg->handles, "domain_controller", 389, new_pgcfg.cfg_domain_controller, &new_pgcfg.domain_controller); if (rc != 0) errors++; else { (void) ad_disc_set_DomainController(ad_ctx, new_pgcfg.domain_controller); new_pgcfg.domain_controller_auto_disc = B_FALSE; } rc = resolve_ds_addr(&cfg->handles, "preferred_dc", 389, new_pgcfg.cfg_preferred_dc, &new_pgcfg.preferred_dc); if (rc != 0) errors++; else { (void) ad_disc_set_PreferredDC(ad_ctx, new_pgcfg.preferred_dc); new_pgcfg.preferred_dc_auto_disc = B_FALSE; } rc = resolve_ds_addr(&cfg->handles, "global_catalog", 3268, new_pgcfg.cfg_global_catalog, &new_pgcfg.global_catalog); if (rc != 0) errors++; else { (void) ad_disc_set_GlobalCatalog(ad_ctx, new_pgcfg.global_catalog); new_pgcfg.global_catalog_auto_disc = B_FALSE; } /* * Unless we've been asked to forget the current DC, * give preference (in order) to the preferred DC if * configured, or the current DC. These preferences * reduce undesirable DC changes. */ if (flags & CFG_FORGET_DC) { (void) ad_disc_set_PreferredDC(ad_ctx, NULL); } else if (new_pgcfg.preferred_dc != NULL) { (void) ad_disc_set_PreferredDC(ad_ctx, new_pgcfg.preferred_dc); } else if (live_pgcfg->domain_controller != NULL) { (void) ad_disc_set_PreferredDC(ad_ctx, live_pgcfg->domain_controller); } else { (void) ad_disc_set_PreferredDC(ad_ctx, NULL); } /* * We want a way to tell adspriv_getdcname_1_svc() * (and others) that discovery is running and therefore * they may want to wait a bit or return an error... */ (void) mutex_lock(&_idmapdstate.addisc_lk); _idmapdstate.addisc_st |= ADDISC_ST_RUNNING; (void) mutex_unlock(&_idmapdstate.addisc_lk); idmap_cfg_discover1(&cfg->handles, &new_pgcfg); WRLOCK_CONFIG(); (void) mutex_lock(&_idmapdstate.addisc_lk); _idmapdstate.addisc_st = 0; (void) cond_broadcast(&_idmapdstate.addisc_cv); (void) mutex_unlock(&_idmapdstate.addisc_lk); } else { WRLOCK_CONFIG(); } /* Non-discoverable props updated here */ changed += update_uint64(&live_pgcfg->list_size_limit, &new_pgcfg.list_size_limit, "list_size_limit"); changed += update_uint64(&live_pgcfg->max_threads, &new_pgcfg.max_threads, "max_threads"); changed += update_uint64(&live_pgcfg->discovery_retry_max_delay, &new_pgcfg.discovery_retry_max_delay, "discovery_retry_max_delay"); changed += update_uint64(&live_pgcfg->id_cache_timeout, &new_pgcfg.id_cache_timeout, "id_cache_timeout"); changed += update_uint64(&live_pgcfg->name_cache_timeout, &new_pgcfg.name_cache_timeout, "name_cache_timeout"); changed += update_uint64(&live_pgcfg->rediscovery_interval, &new_pgcfg.rediscovery_interval, "rediscovery_interval"); changed += update_string(&live_pgcfg->machine_sid, &new_pgcfg.machine_sid, "machine_sid"); changed += update_bool(&live_pgcfg->eph_map_unres_sids, &new_pgcfg.eph_map_unres_sids, "unresolvable_sid_mapping"); changed += update_bool(&live_pgcfg->use_ads, &new_pgcfg.use_ads, "use_ads"); changed += update_bool(&live_pgcfg->use_lsa, &new_pgcfg.use_lsa, "use_lsa"); changed += update_bool(&live_pgcfg->disable_cross_forest_trusts, &new_pgcfg.disable_cross_forest_trusts, "disable_cross_forest_trusts"); changed += update_enum(&live_pgcfg->directory_based_mapping, &new_pgcfg.directory_based_mapping, "directory_based_mapping", directory_mapping_map); changed += update_string(&live_pgcfg->ad_unixuser_attr, &new_pgcfg.ad_unixuser_attr, "ad_unixuser_attr"); changed += update_string(&live_pgcfg->ad_unixgroup_attr, &new_pgcfg.ad_unixgroup_attr, "ad_unixgroup_attr"); changed += update_string(&live_pgcfg->nldap_winname_attr, &new_pgcfg.nldap_winname_attr, "nldap_winname_attr"); changed += update_string(&live_pgcfg->default_domain, &new_pgcfg.default_domain, "default_domain"); changed += update_dirs(&live_pgcfg->preferred_dc, &new_pgcfg.preferred_dc, "preferred_dc"); /* Props that can be discovered or set in SMF updated here */ if (update_string(&live_pgcfg->domain_name, &new_pgcfg.domain_name, "domain_name")) { changed++; dc_changed = TRUE; gc_changed = TRUE; idmapd_set_krb5_realm(live_pgcfg->domain_name); } live_pgcfg->domain_name_auto_disc = new_pgcfg.domain_name_auto_disc; changed += update_string(&live_pgcfg->domain_guid, &new_pgcfg.domain_guid, "domain_guid"); live_pgcfg->domain_guid_auto_disc = new_pgcfg.domain_guid_auto_disc; if (update_dirs(&live_pgcfg->domain_controller, &new_pgcfg.domain_controller, "domain_controller")) { changed++; dc_changed = TRUE; } live_pgcfg->domain_controller_auto_disc = new_pgcfg.domain_controller_auto_disc; changed += update_string(&live_pgcfg->forest_name, &new_pgcfg.forest_name, "forest_name"); live_pgcfg->forest_name_auto_disc = new_pgcfg.forest_name_auto_disc; changed += update_string(&live_pgcfg->site_name, &new_pgcfg.site_name, "site_name"); live_pgcfg->site_name_auto_disc = new_pgcfg.site_name_auto_disc; /* Note: explicitly ignoring the bare string domain server values */ if (DBG(CONFIG, 1)) { if (changed) idmapdlog(LOG_NOTICE, "Configuration changed"); else idmapdlog(LOG_NOTICE, "Configuration unchanged"); } UNLOCK_CONFIG(); if (dc_changed) { notify_dc_changed(); } /* * Discovery2 can take a while. */ if (flags & CFG_DISCOVER) { if (live_pgcfg->domain_name != NULL && live_pgcfg->forest_name != NULL) idmap_cfg_discover2(&cfg->handles, &new_pgcfg); ad_disc_done(ad_ctx); } WRLOCK_CONFIG(); /* More props that can be discovered or set in SMF */ if (update_dirs(&live_pgcfg->global_catalog, &new_pgcfg.global_catalog, "global_catalog")) { changed++; gc_changed = TRUE; } live_pgcfg->global_catalog_auto_disc = new_pgcfg.global_catalog_auto_disc; /* Props that are only discovered (never in SMF) */ if (update_domains_in_forest(&live_pgcfg->domains_in_forest, &new_pgcfg.domains_in_forest, "domains_in_forest")) { changed++; gc_changed = TRUE; } if (update_trusted_domains(&live_pgcfg->trusted_domains, &new_pgcfg.trusted_domains, "trusted_domains")) { changed++; if (live_pgcfg->trusted_domains != NULL && live_pgcfg->trusted_domains[0].domain[0] != '\0') gc_changed = TRUE; } if (update_trusted_forest(&live_pgcfg->trusted_forests, &live_pgcfg->num_trusted_forests, &new_pgcfg.trusted_forests, &new_pgcfg.num_trusted_forests, "trusted_forest")) { changed++; if (live_pgcfg->trusted_forests != NULL) gc_changed = TRUE; } if (DBG(CONFIG, 1)) { if (changed) idmapdlog(LOG_NOTICE, "Configuration changed"); else idmapdlog(LOG_NOTICE, "Configuration unchanged"); } UNLOCK_CONFIG(); if (dc_changed) reload_dcs(); if (gc_changed) reload_gcs(); idmap_cfg_unload(&new_pgcfg); err: (void) pthread_mutex_unlock(&cfg->handles.mutex); if (rc < -1) return (rc); return ((errors == 0) ? 0 : -1); } /* * Initialize 'cfg'. */ idmap_cfg_t * idmap_cfg_init() { idmap_cfg_handles_t *handles; /* First the smf repository handles: */ idmap_cfg_t *cfg = calloc(1, sizeof (idmap_cfg_t)); if (!cfg) { idmapdlog(LOG_ERR, "Out of memory"); return (NULL); } handles = &cfg->handles; (void) pthread_mutex_init(&handles->mutex, NULL); if (!(handles->main = scf_handle_create(SCF_VERSION))) { idmapdlog(LOG_ERR, "scf_handle_create() failed: %s", scf_strerror(scf_error())); goto error; } if (scf_handle_bind(handles->main) < 0) { idmapdlog(LOG_ERR, "scf_handle_bind() failed: %s", scf_strerror(scf_error())); goto error; } if (!(handles->service = scf_service_create(handles->main)) || !(handles->instance = scf_instance_create(handles->main)) || !(handles->config_pg = scf_pg_create(handles->main)) || !(handles->debug_pg = scf_pg_create(handles->main))) { idmapdlog(LOG_ERR, "scf handle creation failed: %s", scf_strerror(scf_error())); goto error; } if (scf_handle_decode_fmri(handles->main, FMRI_BASE "/:properties/" CONFIG_PG, NULL, /* scope */ handles->service, /* service */ handles->instance, /* instance */ handles->config_pg, /* pg */ NULL, /* prop */ SCF_DECODE_FMRI_EXACT) < 0) { idmapdlog(LOG_ERR, "scf_handle_decode_fmri() failed: %s", scf_strerror(scf_error())); goto error; } if (scf_service_get_pg(handles->service, DEBUG_PG, handles->debug_pg) < 0) { idmapdlog(LOG_ERR, "Property group \"%s\": %s", DEBUG_PG, scf_strerror(scf_error())); goto error; } check_smf_debug_mode(handles); /* Initialize AD Auto Discovery context */ handles->ad_ctx = ad_disc_init(); if (handles->ad_ctx == NULL) goto error; return (cfg); error: (void) idmap_cfg_fini(cfg); return (NULL); } void idmap_cfg_unload(idmap_pg_config_t *pgcfg) { if (pgcfg->default_domain) { free(pgcfg->default_domain); pgcfg->default_domain = NULL; } if (pgcfg->domain_name) { free(pgcfg->domain_name); pgcfg->domain_name = NULL; } if (pgcfg->domain_guid) { free(pgcfg->domain_guid); pgcfg->domain_guid = NULL; } if (pgcfg->machine_sid) { free(pgcfg->machine_sid); pgcfg->machine_sid = NULL; } if (pgcfg->cfg_domain_controller) { char **host = &pgcfg->cfg_domain_controller[0]; while (*host != NULL) free(*host++); free(pgcfg->cfg_domain_controller); pgcfg->cfg_domain_controller = NULL; } if (pgcfg->domain_controller) { free(pgcfg->domain_controller); pgcfg->domain_controller = NULL; } if (pgcfg->cfg_preferred_dc) { char **host = &pgcfg->cfg_preferred_dc[0]; while (*host != NULL) free(*host++); free(pgcfg->cfg_preferred_dc); pgcfg->cfg_preferred_dc = NULL; } if (pgcfg->preferred_dc) { free(pgcfg->preferred_dc); pgcfg->preferred_dc = NULL; } if (pgcfg->forest_name) { free(pgcfg->forest_name); pgcfg->forest_name = NULL; } if (pgcfg->site_name) { free(pgcfg->site_name); pgcfg->site_name = NULL; } if (pgcfg->cfg_global_catalog) { char **host = &pgcfg->cfg_global_catalog[0]; while (*host != NULL) free(*host++); free(pgcfg->cfg_global_catalog); pgcfg->cfg_global_catalog = NULL; } if (pgcfg->global_catalog) { free(pgcfg->global_catalog); pgcfg->global_catalog = NULL; } if (pgcfg->trusted_domains) { free(pgcfg->trusted_domains); pgcfg->trusted_domains = NULL; } if (pgcfg->trusted_forests) free_trusted_forests(&pgcfg->trusted_forests, &pgcfg->num_trusted_forests); if (pgcfg->ad_unixuser_attr) { free(pgcfg->ad_unixuser_attr); pgcfg->ad_unixuser_attr = NULL; } if (pgcfg->ad_unixgroup_attr) { free(pgcfg->ad_unixgroup_attr); pgcfg->ad_unixgroup_attr = NULL; } if (pgcfg->nldap_winname_attr) { free(pgcfg->nldap_winname_attr); pgcfg->nldap_winname_attr = NULL; } } int idmap_cfg_fini(idmap_cfg_t *cfg) { idmap_cfg_handles_t *handles = &cfg->handles; idmap_cfg_unload(&cfg->pgcfg); (void) pthread_mutex_destroy(&handles->mutex); scf_pg_destroy(handles->config_pg); if (handles->debug_pg != NULL) scf_pg_destroy(handles->debug_pg); scf_instance_destroy(handles->instance); scf_service_destroy(handles->service); scf_handle_destroy(handles->main); if (handles->ad_ctx != NULL) ad_disc_fini(handles->ad_ctx); free(cfg); return (0); } void idmap_cfg_poke_updates(void) { int prev_st; if (DBG(CONFIG, 1)) { idmapdlog(LOG_INFO, "idmap_cfg_poke_updates"); } (void) mutex_lock(&_idmapdstate.addisc_lk); prev_st = _idmapdstate.addisc_st; _idmapdstate.addisc_st |= ADDISC_ST_REQUESTED; (void) mutex_unlock(&_idmapdstate.addisc_lk); if (prev_st & ADDISC_ST_REQUESTED) { idmapdlog(LOG_DEBUG, "already poked"); } else { idmapdlog(LOG_DEBUG, "port send poke"); (void) port_send(idmapd_ev_port, POKE_AUTO_DISCOVERY, NULL); } } void idmap_cfg_force_rediscovery(void) { int prev_st; if (DBG(CONFIG, 1)) { idmapdlog(LOG_INFO, "idmap_cfg_force_rediscovery"); } (void) mutex_lock(&_idmapdstate.addisc_lk); prev_st = _idmapdstate.addisc_st; _idmapdstate.addisc_st |= ADDISC_ST_REQUESTED; (void) mutex_unlock(&_idmapdstate.addisc_lk); if (prev_st & ADDISC_ST_REQUESTED) { idmapdlog(LOG_DEBUG, "already kicked"); } else { idmapdlog(LOG_DEBUG, "port send kick"); (void) port_send(idmapd_ev_port, KICK_AUTO_DISCOVERY, NULL); } } /*ARGSUSED*/ void idmap_cfg_hup_handler(int sig) { if (idmapd_ev_port >= 0) (void) port_send(idmapd_ev_port, RECONFIGURE, NULL); } /* * Upgrade the debug flags. * * We're replacing a single debug flag with a fine-grained mechanism that * is also capable of considerably more verbosity. We'll take a stab at * producing roughly the same level of output. */ static int upgrade_debug(idmap_cfg_handles_t *handles) { boolean_t debug_present; const char DEBUG_PROP[] = "debug"; int rc; rc = prop_exists(handles, DEBUG_PROP, &debug_present); if (rc != 0) return (rc); if (!debug_present) return (0); idmapdlog(LOG_INFO, "Upgrading old %s/%s setting to %s/* settings.", CONFIG_PG, DEBUG_PROP, DEBUG_PG); rc = set_val_integer(handles, handles->debug_pg, "config", 1); if (rc != 0) return (rc); rc = set_val_integer(handles, handles->debug_pg, "discovery", 1); if (rc != 0) return (rc); rc = del_val(handles, handles->config_pg, DEBUG_PROP); if (rc != 0) return (rc); return (0); } /* * Upgrade the DS mapping flags. * * If the old ds_name_mapping_enabled flag is present, then * if the new directory_based_mapping value is present, then * if the two are compatible, delete the old and note it * else delete the old and warn * else * set the new based on the old, and note it * delete the old */ static int upgrade_directory_mapping(idmap_cfg_handles_t *handles) { boolean_t legacy_ds_name_mapping_present; const char DS_NAME_MAPPING_ENABLED[] = "ds_name_mapping_enabled"; const char DIRECTORY_BASED_MAPPING[] = "directory_based_mapping"; int rc; rc = prop_exists(handles, DS_NAME_MAPPING_ENABLED, &legacy_ds_name_mapping_present); if (rc != 0) return (rc); if (!legacy_ds_name_mapping_present) return (0); boolean_t legacy_ds_name_mapping_enabled; rc = get_val_bool(handles, DS_NAME_MAPPING_ENABLED, &legacy_ds_name_mapping_enabled, B_FALSE); if (rc != 0) return (rc); char *legacy_mode; char *legacy_bool_string; if (legacy_ds_name_mapping_enabled) { legacy_mode = "name"; legacy_bool_string = "true"; } else { legacy_mode = "none"; legacy_bool_string = "false"; } char *directory_based_mapping; rc = get_val_astring(handles, DIRECTORY_BASED_MAPPING, &directory_based_mapping); if (rc != 0) return (rc); if (directory_based_mapping == NULL) { idmapdlog(LOG_INFO, "Upgrading old %s=%s setting\n" "to %s=%s.", DS_NAME_MAPPING_ENABLED, legacy_bool_string, DIRECTORY_BASED_MAPPING, legacy_mode); rc = set_val_astring(handles, handles->config_pg, DIRECTORY_BASED_MAPPING, legacy_mode); if (rc != 0) return (rc); } else { boolean_t new_name_mapping; if (strcasecmp(directory_based_mapping, "name") == 0) new_name_mapping = B_TRUE; else new_name_mapping = B_FALSE; if (legacy_ds_name_mapping_enabled == new_name_mapping) { idmapdlog(LOG_INFO, "Automatically removing old %s=%s setting\n" "in favor of %s=%s.", DS_NAME_MAPPING_ENABLED, legacy_bool_string, DIRECTORY_BASED_MAPPING, directory_based_mapping); } else { idmapdlog(LOG_WARNING, "Removing conflicting %s=%s setting\n" "in favor of %s=%s.", DS_NAME_MAPPING_ENABLED, legacy_bool_string, DIRECTORY_BASED_MAPPING, directory_based_mapping); } free(directory_based_mapping); } rc = del_val(handles, handles->config_pg, DS_NAME_MAPPING_ENABLED); if (rc != 0) return (rc); return (0); } /* * Do whatever is necessary to upgrade idmap's configuration before * we load it. */ int idmap_cfg_upgrade(idmap_cfg_t *cfg) { int rc; rc = upgrade_directory_mapping(&cfg->handles); if (rc != 0) return (rc); rc = upgrade_debug(&cfg->handles); if (rc != 0) return (rc); return (0); } /* * The LDAP code passes principal names lacking any * realm information, which causes mech_krb5 to do * awful things trying to figure out the realm. * Avoid that by making sure it has a default, * even when krb5.conf is not configured. */ static void idmapd_set_krb5_realm(char *domain) { static char realm[MAXHOSTNAMELEN]; size_t ilen, olen; int err; (void) unlink(IDMAP_CACHEDIR "/ccache"); if (domain == NULL) { (void) unsetenv("KRB5_DEFAULT_REALM"); return; } /* Convert to upper case, in place. */ (void) strlcpy(realm, domain, sizeof (realm)); olen = ilen = strlen(realm); (void) u8_textprep_str(realm, &ilen, realm, &olen, U8_TEXTPREP_TOUPPER, U8_UNICODE_LATEST, &err); (void) setenv("KRB5_DEFAULT_REALM", realm, 1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Nexenta Systems, Inc. All rights reserved. * Copyright 2023 RackTop Systems, Inc. */ #ifndef _IDMAP_CONFIG_H #define _IDMAP_CONFIG_H #include "idmap.h" #include "addisc.h" #include #include #include #ifdef __cplusplus extern "C" { #endif #define MAX_POLICY_SIZE 1023 #define DIRECTORY_MAPPING_NONE 0 #define DIRECTORY_MAPPING_NAME 1 #define DIRECTORY_MAPPING_IDMU 2 struct enum_lookup_map { int value; char *string; }; extern struct enum_lookup_map directory_mapping_map[]; extern const char *enum_lookup(int value, struct enum_lookup_map *map); /* SMF and auto-discovery context handles */ typedef struct idmap_cfg_handles { pthread_mutex_t mutex; scf_handle_t *main; scf_instance_t *instance; scf_service_t *service; scf_propertygroup_t *config_pg; scf_propertygroup_t *debug_pg; ad_disc_t ad_ctx; } idmap_cfg_handles_t; /* * This structure stores AD and AD-related configuration */ typedef struct idmap_trustedforest { char *forest_name; ad_disc_ds_t *global_catalog; /* global catalog hosts */ ad_disc_domainsinforest_t *domains_in_forest; } idmap_trustedforest_t; typedef struct idmap_pg_config { uint64_t list_size_limit; uint64_t max_threads; uint64_t discovery_retry_max_delay; uint64_t id_cache_timeout; uint64_t name_cache_timeout; uint64_t rediscovery_interval; char *machine_uuid; /* machine uuid */ char *machine_sid; /* machine sid */ char *default_domain; /* default domain name */ char *domain_name; /* AD domain name */ boolean_t domain_name_auto_disc; char *domain_guid; /* GUID (string) */ boolean_t domain_guid_auto_disc; char **cfg_domain_controller; ad_disc_ds_t *domain_controller; /* domain controller hosts */ boolean_t domain_controller_auto_disc; char *forest_name; /* forest name */ boolean_t forest_name_auto_disc; char *site_name; /* site name */ boolean_t site_name_auto_disc; char **cfg_global_catalog; ad_disc_ds_t *global_catalog; /* global catalog hosts */ boolean_t global_catalog_auto_disc; ad_disc_domainsinforest_t *domains_in_forest; ad_disc_trusteddomains_t *trusted_domains; /* Trusted Domains */ int num_trusted_forests; idmap_trustedforest_t *trusted_forests; /* Array of trusted forests */ char **cfg_preferred_dc; ad_disc_ds_t *preferred_dc; boolean_t preferred_dc_auto_disc; /* * Following properties are associated with directory-based * name-mappings. */ char *ad_unixuser_attr; char *ad_unixgroup_attr; char *nldap_winname_attr; int directory_based_mapping; /* enum */ boolean_t eph_map_unres_sids; boolean_t use_ads; boolean_t use_lsa; boolean_t disable_cross_forest_trusts; } idmap_pg_config_t; typedef struct idmap_cfg { idmap_pg_config_t pgcfg; /* live AD/ID mapping config */ idmap_cfg_handles_t handles; int initialized; } idmap_cfg_t; extern void idmap_cfg_unload(idmap_pg_config_t *); extern int idmap_cfg_load(idmap_cfg_t *, int); extern idmap_cfg_t *idmap_cfg_init(void); extern int idmap_cfg_fini(idmap_cfg_t *); extern int idmap_cfg_upgrade(idmap_cfg_t *); extern int idmap_cfg_start_updates(void); extern void idmap_cfg_poke_updates(void); extern void idmap_cfg_force_rediscovery(void); extern void idmap_cfg_hup_handler(int); #define CFG_DISCOVER 0x1 /* Run discovery */ #define CFG_FORGET_DC 0x2 /* Forget current DC. */ #define CFG_LOG 0x4 #ifdef __cplusplus } #endif #endif /* _IDMAP_CONFIG_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. */ /* * LSA lookups */ #include #include #include #include "idmapd.h" #include "libsmb.h" idmap_retcode idmap_lsa_xlate_sid_type(const lsa_account_t *acct, idmap_id_type *ret_type) { switch (acct->a_sidtype) { case SidTypeUser: case SidTypeComputer: case SidTypeDomain: case SidTypeDeletedAccount: case SidTypeUnknown: case SidTypeLabel: *ret_type = IDMAP_USID; return (IDMAP_SUCCESS); case SidTypeGroup: case SidTypeAlias: case SidTypeWellKnownGroup: *ret_type = IDMAP_GSID; return (IDMAP_SUCCESS); case SidTypeNull: case SidTypeInvalid: default: idmapdlog(LOG_WARNING, "LSA lookup: bad type %d for %s@%s", acct->a_sidtype, acct->a_name, acct->a_domain); return (IDMAP_ERR_OTHER); } NOTE(NOTREACHED) } /* Given SID, look up name and type */ idmap_retcode lookup_lsa_by_sid( const char *sidprefix, uint32_t rid, char **ret_name, char **ret_domain, idmap_id_type *ret_type) { lsa_account_t acct; char sid[SMB_SID_STRSZ + 1]; idmap_retcode ret; int rc; (void) memset(&acct, 0, sizeof (acct)); *ret_name = NULL; *ret_domain = NULL; (void) snprintf(sid, sizeof (sid), "%s-%u", sidprefix, rid); rc = smb_lookup_lsid(sid, &acct); if (rc != 0) { idmapdlog(LOG_ERR, "Error: SMB lookup SID failed."); idmapdlog(LOG_ERR, "Check SMB service (svc:/network/smb/server)."); idmapdlog(LOG_ERR, "Check connectivity to Active Directory."); ret = IDMAP_ERR_OTHER; goto out; } if (acct.a_status == NT_STATUS_NONE_MAPPED) { ret = IDMAP_ERR_NOTFOUND; goto out; } if (acct.a_status != NT_STATUS_SUCCESS) { idmapdlog(LOG_WARNING, "Warning: SMB lookup SID(%s) failed (0x%x)", sid, acct.a_status); /* Fail soft */ ret = IDMAP_ERR_NOTFOUND; goto out; } ret = idmap_lsa_xlate_sid_type(&acct, ret_type); if (ret != IDMAP_SUCCESS) goto out; *ret_name = strdup(acct.a_name); if (*ret_name == NULL) { ret = IDMAP_ERR_MEMORY; goto out; } *ret_domain = strdup(acct.a_domain); if (*ret_domain == NULL) { ret = IDMAP_ERR_MEMORY; goto out; } ret = IDMAP_SUCCESS; out: if (ret != IDMAP_SUCCESS) { free(*ret_name); *ret_name = NULL; free(*ret_domain); *ret_domain = NULL; } return (ret); } /* Given name and optional domain, look up SID, type, and canonical name */ idmap_retcode lookup_lsa_by_name( const char *name, const char *domain, char **ret_sidprefix, uint32_t *ret_rid, char **ret_name, char **ret_domain, idmap_id_type *ret_type) { lsa_account_t acct; char *namedom = NULL; idmap_retcode ret; int rc; (void) memset(&acct, 0, sizeof (acct)); *ret_sidprefix = NULL; if (ret_name != NULL) *ret_name = NULL; if (ret_domain != NULL) *ret_domain = NULL; if (domain != NULL) (void) asprintf(&namedom, "%s@%s", name, domain); else namedom = strdup(name); if (namedom == NULL) { ret = IDMAP_ERR_MEMORY; goto out; } rc = smb_lookup_lname(namedom, SidTypeUnknown, &acct); if (rc != 0) { idmapdlog(LOG_ERR, "Error: SMB lookup name failed."); idmapdlog(LOG_ERR, "Check SMB service (svc:/network/smb/server)."); idmapdlog(LOG_ERR, "Check connectivity to Active Directory."); ret = IDMAP_ERR_OTHER; goto out; } if (acct.a_status == NT_STATUS_NONE_MAPPED) { ret = IDMAP_ERR_NOTFOUND; goto out; } if (acct.a_status != NT_STATUS_SUCCESS) { idmapdlog(LOG_WARNING, "Warning: SMB lookup name(%s) failed (0x%x)", namedom, acct.a_status); /* Fail soft */ ret = IDMAP_ERR_NOTFOUND; goto out; } rc = smb_sid_splitstr(acct.a_sid, ret_rid); assert(rc == 0); *ret_sidprefix = strdup(acct.a_sid); if (*ret_sidprefix == NULL) { ret = IDMAP_ERR_MEMORY; goto out; } ret = idmap_lsa_xlate_sid_type(&acct, ret_type); if (ret != IDMAP_SUCCESS) goto out; if (ret_name != NULL) { *ret_name = strdup(acct.a_name); if (*ret_name == NULL) { ret = IDMAP_ERR_MEMORY; goto out; } } if (ret_domain != NULL) { *ret_domain = strdup(acct.a_domain); if (*ret_domain == NULL) { ret = IDMAP_ERR_MEMORY; goto out; } } ret = IDMAP_SUCCESS; out: free(namedom); if (ret != IDMAP_SUCCESS) { if (ret_name != NULL) { free(*ret_name); *ret_name = NULL; } if (ret_domain != NULL) { free(*ret_domain); *ret_domain = NULL; } free(*ret_sidprefix); *ret_sidprefix = NULL; } return (ret); } /* * This exists just so we can avoid exposing all of idmapd to libsmb.h. * Like the above functions, it's a door call over to smbd. */ void notify_dc_changed(void) { int rc; rc = smb_notify_dc_changed(); if (rc != 0) { idmapdlog(LOG_WARNING, "Warning: smb_notify_dc_changed, rc=%d", rc); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef IDMAP_LSA_H #define IDMAP_LSA_H /* * LSA lookups */ #ifdef __cplusplus extern "C" { #endif #include /* Given SID, look up name and type */ idmap_retcode lookup_lsa_by_sid(const char *sidprefix, uint32_t rid, char **ret_name, char **ret_domain, idmap_id_type *ret_type); /* Given name and optional domain, look up SID, type, and canonical name */ idmap_retcode lookup_lsa_by_name(const char *name, const char *domain, char **ret_sidprefix, uint32_t *ret_rid, char **ret_name, char **ret_domain, idmap_id_type *ret_type); #ifdef __cplusplus } #endif #endif /* IDMAP_LSA_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Nexenta Systems, Inc. All rights reserved. * Copyright 2023 RackTop Systems, Inc. * Copyright 2025 Bill Sommerfeld */ /* * main() of idmapd(8) */ #include "idmapd.h" #include #include #include /* for pmap_unset */ #include /* strcmp */ #include /* setsid */ #include #include #include #include #include /* rlimit */ #include /* DAEMON_UID and DAEMON_GID */ #include /* privileges */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CBUFSIZ 26 /* ctime(3c) */ static void term_handler(int); static void init_idmapd(); static void fini_idmapd(); /* The DC Locator lives inside idmap (for now). */ extern void init_dc_locator(void); extern void fini_dc_locator(void); idmapd_state_t _idmapdstate; mutex_t _svcstate_lock = ERRORCHECKMUTEX; SVCXPRT *xprt = NULL; static int dfd = -1; /* our door server fildes, for unregistration */ static boolean_t degraded = B_FALSE; static uint32_t num_threads = 0; static pthread_key_t create_threads_key; static uint32_t max_threads = 40; /* * Server door thread start routine. * * Set a TSD value to the door thread. This enables the destructor to * be called when this thread exits. Note that we need a non-NULL * value for this or the TSD destructor is not called. */ /*ARGSUSED*/ static void * idmapd_door_thread_start(void *arg) { static void *value = "NON-NULL TSD"; /* * Disable cancellation to avoid memory leaks from not running * the thread cleanup code. */ (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); (void) pthread_setspecific(create_threads_key, value); (void) door_return(NULL, 0, NULL, 0); /* make lint happy */ return (NULL); } /* * Server door threads creation */ /*ARGSUSED*/ static void idmapd_door_thread_create(door_info_t *dip) { int num; pthread_t thread_id; if ((num = atomic_inc_32_nv(&num_threads)) > max_threads) { atomic_dec_32(&num_threads); idmapdlog(LOG_DEBUG, "thread creation refused - %d threads currently active", num - 1); return; } (void) pthread_create(&thread_id, NULL, idmapd_door_thread_start, NULL); idmapdlog(LOG_DEBUG, "created thread ID %d - %d threads currently active", thread_id, num); } /* * Server door thread cleanup */ /*ARGSUSED*/ static void idmapd_door_thread_cleanup(void *arg) { int num; /* set TSD to NULL so we don't loop infinitely */ (void) pthread_setspecific(create_threads_key, NULL); num = atomic_dec_32_nv(&num_threads); idmapdlog(LOG_DEBUG, "exiting thread ID %d - %d threads currently active", pthread_self(), num); } /* * This is needed for mech_krb5 -- we run as daemon, yes, but we want * mech_krb5 to think we're root so it can get host/nodename.fqdn * tickets for us so we can authenticate to AD as the machine account * that we are. For more details look at the entry point in mech_krb5 * corresponding to gss_init_sec_context(). * * As a side effect of faking our effective UID to mech_krb5 we will use * root's default ccache (/tmp/krb5cc_0). But if that's created by * another process then we won't have access to it: we run as daemon and * keep PRIV_FILE_DAC_READ, which is insufficient to share the ccache * with others. We putenv("KRB5CCNAME=/var/run/idmap/ccache") in main() * to avoid this issue; see main(). * * Someday we'll have gss/mech_krb5 extensions for acquiring initiator * creds with keytabs/raw keys, and someday we'll have extensions to * libsasl to specify creds/name to use on the initiator side, and * someday we'll have extensions to libldap to pass those through to * libsasl. Until then this interposer will have to do. * * Also, we have to tell lint to shut up: it thinks app_krb5_user_uid() * is defined but not used. */ /*LINTLIBRARY*/ uid_t app_krb5_user_uid(void) { return (0); } /*ARGSUSED*/ static void term_handler(int sig) { idmapdlog(LOG_INFO, "Terminating."); fini_dc_locator(); fini_idmapd(); _exit(0); } /*ARGSUSED*/ static void usr1_handler(int sig) { NOTE(ARGUNUSED(sig)) print_idmapdstate(); } static int pipe_fd = -1; static void daemonize_ready(void) { char data = '\0'; /* * wake the parent */ (void) write(pipe_fd, &data, 1); (void) close(pipe_fd); } static int daemonize_start(void) { char data; int status; int devnull; int filedes[2]; pid_t pid; (void) sigset(SIGPIPE, SIG_IGN); devnull = open("/dev/null", O_RDONLY); if (devnull < 0) return (-1); (void) dup2(devnull, 0); (void) dup2(2, 1); /* stderr only */ if (pipe(filedes) < 0) return (-1); if ((pid = fork1()) < 0) return (-1); if (pid != 0) { /* * parent */ (void) close(filedes[1]); if (read(filedes[0], &data, 1) == 1) { /* presume success */ _exit(0); } status = -1; (void) wait4(pid, &status, 0, NULL); if (WIFEXITED(status)) _exit(WEXITSTATUS(status)); else _exit(-1); } /* * child */ pipe_fd = filedes[1]; (void) close(filedes[0]); (void) setsid(); (void) umask(0077); openlog("idmap", LOG_PID, LOG_DAEMON); return (0); } int main(int argc, char **argv) { int c; struct rlimit rl; if (rwlock_init(&_idmapdstate.rwlk_cfg, USYNC_THREAD, NULL) != 0) return (-1); if (mutex_init(&_idmapdstate.addisc_lk, USYNC_THREAD, NULL) != 0) return (-1); if (cond_init(&_idmapdstate.addisc_cv, USYNC_THREAD, NULL) != 0) return (-1); _idmapdstate.daemon_mode = TRUE; while ((c = getopt(argc, argv, "d")) != -1) { switch (c) { case 'd': _idmapdstate.daemon_mode = FALSE; break; default: (void) fprintf(stderr, "Usage: /usr/lib/idmapd [-d]\n"); return (SMF_EXIT_ERR_CONFIG); } } /* set locale and domain for internationalization */ (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); idmap_set_logger(idmapdlog); adutils_set_logger(idmapdlog); if (is_system_labeled() && getzoneid() != GLOBAL_ZONEID) { idmapdlog(LOG_ERR, "with Trusted Extensions idmapd runs only in the " "global zone"); exit(1); } /* * Raise the fd limit to max */ if (getrlimit(RLIMIT_NOFILE, &rl) != 0) { idmapdlog(LOG_ERR, "getrlimit failed"); } else if (rl.rlim_cur < rl.rlim_max) { rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rl) != 0) idmapdlog(LOG_ERR, "Unable to raise RLIMIT_NOFILE to %d", rl.rlim_cur); } if (_idmapdstate.daemon_mode == TRUE) { if (daemonize_start() < 0) { idmapdlog(LOG_ERR, "unable to daemonize"); exit(-1); } } else (void) umask(0077); idmap_init_tsd_key(); init_idmapd(); init_dc_locator(); /* signal handlers that should run only after we're initialized */ (void) sigset(SIGTERM, term_handler); (void) sigset(SIGUSR1, usr1_handler); (void) sigset(SIGHUP, idmap_cfg_hup_handler); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, DAEMON_UID, DAEMON_GID, PRIV_PROC_AUDIT, PRIV_FILE_DAC_READ, (char *)NULL) == -1) { idmapdlog(LOG_ERR, "unable to drop privileges"); exit(1); } __fini_daemon_priv(PRIV_PROC_FORK, PRIV_PROC_EXEC, PRIV_PROC_SESSION, PRIV_FILE_LINK_ANY, PRIV_PROC_INFO, (char *)NULL); if (_idmapdstate.daemon_mode == TRUE) daemonize_ready(); /* With doors RPC this just wastes this thread, oh well */ svc_run(); return (0); } static void init_idmapd() { int error; int connmaxrec = IDMAP_MAX_DOOR_RPC; /* create directories as root and chown to daemon uid */ if (create_directory(IDMAP_DBDIR, DAEMON_UID, DAEMON_GID) < 0) exit(1); if (create_directory(IDMAP_CACHEDIR, DAEMON_UID, DAEMON_GID) < 0) exit(1); /* * Set KRB5CCNAME in the environment. See app_krb5_user_uid() * for more details. We blow away the existing one, if there is * one. */ (void) unlink(IDMAP_CACHEDIR "/ccache"); (void) putenv("KRB5CCNAME=" IDMAP_CACHEDIR "/ccache"); (void) putenv("MS_INTEROP=1"); if (sysinfo(SI_HOSTNAME, _idmapdstate.hostname, sizeof (_idmapdstate.hostname)) == -1) { error = errno; idmapdlog(LOG_ERR, "unable to determine hostname, error: %d", error); exit(1); } if ((error = init_mapping_system()) < 0) { idmapdlog(LOG_ERR, "unable to initialize mapping system"); exit(error < -2 ? SMF_EXIT_ERR_CONFIG : 1); } /* * This means max_threads can't be updated without restarting idmap. */ RDLOCK_CONFIG(); max_threads = _idmapdstate.cfg->pgcfg.max_threads; UNLOCK_CONFIG(); (void) door_server_create(idmapd_door_thread_create); if ((error = pthread_key_create(&create_threads_key, idmapd_door_thread_cleanup)) != 0) { idmapdlog(LOG_ERR, "unable to create threads key (%s)", strerror(error)); goto errout; } xprt = svc_door_create(idmap_prog_1, IDMAP_PROG, IDMAP_V1, connmaxrec); if (xprt == NULL) { idmapdlog(LOG_ERR, "unable to create door RPC service"); goto errout; } if (!svc_control(xprt, SVCSET_CONNMAXREC, &connmaxrec)) { idmapdlog(LOG_ERR, "unable to limit RPC request size"); goto errout; } dfd = xprt->xp_fd; if (dfd == -1) { idmapdlog(LOG_ERR, "unable to register door"); goto errout; } if ((error = __idmap_reg(dfd)) != 0) { idmapdlog(LOG_ERR, "unable to register door (%s)", strerror(errno)); goto errout; } pthread_mutex_init(&_idmapdstate.id_lock, NULL); if ((error = allocids(_idmapdstate.new_eph_db, 8192, &_idmapdstate.next_uid, 8192, &_idmapdstate.next_gid)) != 0) { idmapdlog(LOG_ERR, "unable to allocate ephemeral IDs (%s)", strerror(errno)); _idmapdstate.next_uid = IDMAP_SENTINEL_PID; _idmapdstate.limit_uid = IDMAP_SENTINEL_PID; _idmapdstate.next_gid = IDMAP_SENTINEL_PID; _idmapdstate.limit_gid = IDMAP_SENTINEL_PID; } else { _idmapdstate.limit_uid = _idmapdstate.next_uid + 8192; _idmapdstate.limit_gid = _idmapdstate.next_gid + 8192; } if (DBG(CONFIG, 1)) print_idmapdstate(); return; errout: fini_idmapd(); exit(1); } static void fini_idmapd() { (void) __idmap_unreg(dfd); fini_mapping_system(); if (xprt != NULL) svc_destroy(xprt); } static const char * get_fmri(void) { static char *fmri = NULL; static char buf[60]; char *s; membar_consumer(); s = fmri; if (s != NULL && *s == '\0') return (NULL); else if (s != NULL) return (s); if ((s = getenv("SMF_FMRI")) == NULL || strlen(s) >= sizeof (buf)) buf[0] = '\0'; else (void) strlcpy(buf, s, sizeof (buf)); membar_producer(); fmri = buf; return (get_fmri()); } /* * Wrappers for smf_degrade/restore_instance() * * smf_restore_instance() is too heavy duty to be calling every time we * have a successful AD name<->SID lookup. */ void degrade_svc(int poke_discovery, const char *reason) { const char *fmri; membar_consumer(); if (degraded) return; idmapdlog(LOG_ERR, "Degraded operation (%s).", reason); membar_producer(); degraded = B_TRUE; if ((fmri = get_fmri()) != NULL) (void) smf_degrade_instance(fmri, 0); /* * If the config update thread is in a state where auto-discovery could * be re-tried, then this will make it try it -- a sort of auto-refresh. */ if (poke_discovery) idmap_cfg_poke_updates(); } void restore_svc(void) { const char *fmri; membar_consumer(); if (!degraded) return; if ((fmri = get_fmri()) == NULL) (void) smf_restore_instance(fmri); membar_producer(); degraded = B_FALSE; idmapdlog(LOG_NOTICE, "Normal operation restored"); } /* printflike */ void idmapdlog(int pri, const char *format, ...) { static time_t prev_ts; va_list args; char cbuf[CBUFSIZ]; time_t ts; ts = time(NULL); if (prev_ts != ts) { prev_ts = ts; /* NB: cbuf has \n */ (void) fprintf(stderr, "@ %s", ctime_r(&ts, cbuf, sizeof (cbuf))); } va_start(args, format); (void) vfprintf(stderr, format, args); (void) fprintf(stderr, "\n"); va_end(args); /* * We don't want to fill up the logs with useless messages when * we're degraded, but we still want to log. */ if (degraded) pri = LOG_DEBUG; va_start(args, format); vsyslog(pri, format, args); va_end(args); } static void trace_str(nvlist_t *entry, char *n1, char *n2, char *str) { char name[IDMAP_TRACE_NAME_MAX+1]; /* Max used is only about 11 */ (void) strlcpy(name, n1, sizeof (name)); if (n2 != NULL) (void) strlcat(name, n2, sizeof (name)); (void) nvlist_add_string(entry, name, str); } static void trace_int(nvlist_t *entry, char *n1, char *n2, int64_t i) { char name[IDMAP_TRACE_NAME_MAX+1]; /* Max used is only about 11 */ (void) strlcpy(name, n1, sizeof (name)); if (n2 != NULL) (void) strlcat(name, n2, sizeof (name)); (void) nvlist_add_int64(entry, name, i); } static void trace_sid(nvlist_t *entry, char *n1, char *n2, idmap_sid *sid) { char *str; (void) asprintf(&str, "%s-%u", sid->prefix, sid->rid); if (str == NULL) return; trace_str(entry, n1, n2, str); free(str); } static void trace_id(nvlist_t *entry, char *fromto, idmap_id *id, char *name, char *domain) { trace_int(entry, fromto, IDMAP_TRACE_TYPE, (int64_t)id->idtype); if (IS_ID_SID(*id)) { if (name != NULL) { char *str; (void) asprintf(&str, "%s%s%s", name, domain == NULL ? "" : "@", domain == NULL ? "" : domain); if (str != NULL) { trace_str(entry, fromto, IDMAP_TRACE_NAME, str); free(str); } } if (id->idmap_id_u.sid.prefix != NULL) { trace_sid(entry, fromto, IDMAP_TRACE_SID, &id->idmap_id_u.sid); } } else if (IS_ID_POSIX(*id)) { if (name != NULL) trace_str(entry, fromto, IDMAP_TRACE_NAME, name); if (id->idmap_id_u.uid != IDMAP_SENTINEL_PID) { trace_int(entry, fromto, IDMAP_TRACE_UNIXID, (int64_t)id->idmap_id_u.uid); } } } /* * Record a trace event. TRACE() has already decided whether or not * tracing is required; what we do here is collect the data and send it * to its destination - to the trace log in the response, if * IDMAP_REQ_FLG_TRACE is set, and to the SMF service log, if debug/mapping * is greater than zero. */ int trace(idmap_mapping *req, idmap_id_res *res, char *fmt, ...) { va_list va; char *buf; int err; nvlist_t *entry; assert(req != NULL); assert(res != NULL); err = nvlist_alloc(&entry, NV_UNIQUE_NAME, 0); if (err != 0) { (void) fprintf(stderr, "trace nvlist_alloc(entry): %s\n", strerror(err)); return (0); } trace_id(entry, "from", &req->id1, req->id1name, req->id1domain); trace_id(entry, "to", &res->id, req->id2name, req->id2domain); if (IDMAP_ERROR(res->retcode)) { trace_int(entry, IDMAP_TRACE_ERROR, NULL, (int64_t)res->retcode); } va_start(va, fmt); (void) vasprintf(&buf, fmt, va); va_end(va); if (buf != NULL) { trace_str(entry, IDMAP_TRACE_MESSAGE, NULL, buf); free(buf); } if (DBG(MAPPING, 1)) idmap_trace_print_1(stderr, "", entry); if (req->flag & IDMAP_REQ_FLG_TRACE) { /* Lazily allocate the trace list */ if (res->info.trace == NULL) { err = nvlist_alloc(&res->info.trace, 0, 0); if (err != 0) { res->info.trace = NULL; /* just in case */ (void) fprintf(stderr, "trace nvlist_alloc(trace): %s\n", strerror(err)); nvlist_free(entry); return (0); } } (void) nvlist_add_nvlist(res->info.trace, "", entry); /* Note that entry is copied, so we must still free our copy */ } nvlist_free(entry); return (0); } /* * Enable libumem debugging by default on DEBUG builds. * idmapd uses rpcgen, so we can't use #ifdef DEBUG without causing * undesirable behavior. */ #ifdef IDMAPD_DEBUG const char * _umem_debug_init(void) { return ("default,verbose"); /* $UMEM_DEBUG setting */ } const char * _umem_logging_init(void) { return ("fail,contents"); /* $UMEM_LOGGING setting */ } #endif /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. * Copyright 2025 Bill Sommerfeld */ #ifndef _IDMAPD_H #define _IDMAPD_H #include #include #include #include #include #include #include #include #include #include #include #include #include "adutils.h" #include "idmap_priv.h" #include "idmap_config.h" #include "libadutils.h" #ifdef __cplusplus extern "C" { #endif #define CHECK_NULL(s) (s != NULL ? s : "null") extern mutex_t _svcstate_lock; /* lock for _rpcsvcstate, _rpcsvccount */ typedef enum idmap_namemap_mode { IDMAP_NM_NONE = 0, IDMAP_NM_AD, IDMAP_NM_NLDAP, IDMAP_NM_MIXED } idmap_namemap_mode_t; /* * Debugging output. * * There are some number of areas - configuration, mapping, discovery, et * cetera - and for each area there is a verbosity level controlled through * an SMF property. The default is zero, and "debug/all" provides a master * control allowing you to turn on all debugging output with one setting. * * A typical debugging output sequence would look like * * if (DBG(CONFIG, 2)) { * idmapdlog(LOG_DEBUG, * "some message about config at verbosity 2"); * } */ enum idmapd_debug { IDMAPD_DEBUG_ALL = 0, IDMAPD_DEBUG_CONFIG = 1, IDMAPD_DEBUG_MAPPING = 2, IDMAPD_DEBUG_DISC = 3, IDMAPD_DEBUG_DNS = 4, IDMAPD_DEBUG_LDAP = 5, IDMAPD_DEBUG_MAX = 5 }; #define DBG(type, lev) \ (_idmapdstate.debug[IDMAPD_DEBUG_##type] >= (lev) || \ _idmapdstate.debug[IDMAPD_DEBUG_ALL] >= (lev)) /* * Global state of idmapd daemon. */ typedef struct idmapd_state { rwlock_t rwlk_cfg; /* config lock */ idmap_cfg_t *cfg; /* config */ bool_t daemon_mode; char hostname[MAX_NAME_LEN]; /* my hostname */ pthread_mutex_t id_lock; /* id allocator lock */ uid_t next_uid; gid_t next_gid; uid_t limit_uid; gid_t limit_gid; int new_eph_db; /* was the ephem ID db [re-]created? */ int num_gcs; adutils_ad_t **gcs; int num_dcs; adutils_ad_t **dcs; mutex_t addisc_lk; cond_t addisc_cv; int addisc_st; int debug[IDMAPD_DEBUG_MAX+1]; } idmapd_state_t; extern idmapd_state_t _idmapdstate; #define ADDISC_ST_REQUESTED 1 #define ADDISC_ST_RUNNING 2 #define RDLOCK_CONFIG() \ (void) rw_rdlock(&_idmapdstate.rwlk_cfg); #define WRLOCK_CONFIG() \ (void) rw_wrlock(&_idmapdstate.rwlk_cfg); #define UNLOCK_CONFIG() \ (void) rw_unlock(&_idmapdstate.rwlk_cfg); typedef struct hashentry { uint_t key; uint_t next; } hashentry_t; typedef struct lookup_state { bool_t sid2pid_done; bool_t pid2sid_done; int ad_nqueries; int nldap_nqueries; bool_t eph_map_unres_sids; int directory_based_mapping; /* enum */ uint_t id_cache_timeout; uint_t name_cache_timeout; uint_t curpos; hashentry_t *sid_history; uint_t sid_history_size; idmap_mapping_batch *batch; idmap_ids_res *result; idmap_namemap_mode_t nm_siduid; idmap_namemap_mode_t nm_sidgid; char *ad_unixuser_attr; char *ad_unixgroup_attr; char *nldap_winname_attr; char *defdom; sqlite *cache; sqlite *db; } lookup_state_t; #define NLDAP_OR_MIXED(nm) \ ((nm) == IDMAP_NM_NLDAP || (nm) == IDMAP_NM_MIXED) #define AD_OR_MIXED(nm) \ ((nm) == IDMAP_NM_AD || (nm) == IDMAP_NM_MIXED) #define PID_UID_OR_UNKNOWN(pidtype) \ ((pidtype) == IDMAP_UID || (pidtype) == IDMAP_POSIXID) #define PID_GID_OR_UNKNOWN(pidtype) \ ((pidtype) == IDMAP_GID || (pidtype) == IDMAP_POSIXID) #define NLDAP_OR_MIXED_MODE(pidtype, ls) \ (NLDAP_MODE(pidtype, ls) || MIXED_MODE(pidtype, ls)) #define AD_OR_MIXED_MODE(pidtype, ls)\ (AD_MODE(pidtype, ls) || MIXED_MODE(pidtype, ls)) #define NLDAP_MODE(pidtype, ls) \ ((PID_UID_OR_UNKNOWN(pidtype) && (ls)->nm_siduid == IDMAP_NM_NLDAP) || \ (PID_GID_OR_UNKNOWN(pidtype) && (ls)->nm_sidgid == IDMAP_NM_NLDAP)) #define AD_MODE(pidtype, ls) \ ((PID_UID_OR_UNKNOWN(pidtype) && (ls)->nm_siduid == IDMAP_NM_AD) || \ (PID_GID_OR_UNKNOWN(pidtype) && (ls)->nm_sidgid == IDMAP_NM_AD)) #define MIXED_MODE(pidtype, ls) \ ((PID_UID_OR_UNKNOWN(pidtype) && (ls)->nm_siduid == IDMAP_NM_MIXED) || \ (PID_GID_OR_UNKNOWN(pidtype) && (ls)->nm_sidgid == IDMAP_NM_MIXED)) typedef struct list_cb_data { void *result; uint64_t next; uint64_t len; uint64_t limit; int flag; } list_cb_data_t; typedef struct msg_table { idmap_retcode retcode; const char *msg; } msg_table_t; /* * Data structure to store well-known SIDs and * associated mappings (if any) */ typedef struct wksids_table { const char *sidprefix; uint32_t rid; const char *domain; const char *winname; int is_wuser; posix_id_t pid; int is_user; int direction; } wksids_table_t; #define IDMAPD_SEARCH_TIMEOUT 3 /* seconds */ #define IDMAPD_LDAP_OPEN_TIMEOUT 1 /* secs; initial, w/ exp backoff */ /* * The following flags are used by idmapd while processing a * given mapping request. Note that idmapd uses multiple passes to * process the request and the flags are used to pass information * about the state of the request between these passes. */ /* Initial state. Done. Reset all flags. Remaining passes can be skipped */ #define _IDMAP_F_DONE 0x00000000 /* Set when subsequent passes are required */ #define _IDMAP_F_NOTDONE 0x00000001 /* Don't update name_cache. (e.g. set when winname,SID found in name_cache) */ #define _IDMAP_F_DONT_UPDATE_NAMECACHE 0x00000002 /* Batch this request for AD lookup */ #define _IDMAP_F_LOOKUP_AD 0x00000004 /* Batch this request for nldap directory lookup */ #define _IDMAP_F_LOOKUP_NLDAP 0x00000008 /* * Expired ephemeral mapping found in cache when processing sid2uid request. * Use it if the given SID cannot be mapped by name */ #define _IDMAP_F_EXP_EPH_UID 0x00000010 /* Same as above. Used for sid2gid request */ #define _IDMAP_F_EXP_EPH_GID 0x00000020 /* This request is not valid for the current forest */ #define _IDMAP_F_LOOKUP_OTHER_AD 0x00000040 /* * Check if we are done. If so, subsequent passes can be skipped * when processing a given mapping request. */ #define ARE_WE_DONE(f) ((f & _IDMAP_F_NOTDONE) == 0) #define SIZE_INCR 5 #define MAX_TRIES 5 #define IDMAP_DBDIR "/var/idmap" #define IDMAP_CACHEDIR "/var/run/idmap" #define IDMAP_DBNAME IDMAP_DBDIR "/idmap.db" #define IDMAP_CACHENAME IDMAP_CACHEDIR "/cache.db" #define IS_ID_NONE(id) \ ((id).idtype == IDMAP_NONE) #define IS_ID_SID(id) \ ((id).idtype == IDMAP_SID || \ (id).idtype == IDMAP_USID || \ (id).idtype == IDMAP_GSID) \ #define IS_ID_UID(id) \ ((id).idtype == IDMAP_UID) #define IS_ID_GID(id) \ ((id).idtype == IDMAP_GID) #define IS_ID_POSIX(id) \ ((id).idtype == IDMAP_UID || \ (id).idtype == IDMAP_GID || \ (id).idtype == IDMAP_POSIXID) \ /* * Local RID ranges */ #define LOCALRID_UID_MIN 1000U #define LOCALRID_UID_MAX ((uint32_t)INT32_MAX) #define LOCALRID_GID_MIN (((uint32_t)INT32_MAX) + 1) #define LOCALRID_GID_MAX UINT32_MAX /* * Tracing. * * The tracing mechanism is intended to help the administrator understand * why their mapping configuration is doing what it is. Each interesting * decision point during the mapping process calls TRACE() with the current * request and response and a printf-style message. The message, plus * data from the request and the response, is logged to the service log * (if debug/mapping is greater than zero) or reported to the caller * (if IDMAP_REQ_FLG_TRACE was set in the request. The primary consumer * is the "-V" option to "idmap show". * * TRACING(req) says whether tracing is appropriate for the request, and * is used to determine and record whether any request in a batch requested * tracing, to control whether later code loops over the batch to do tracing * for any of the requests. * * TRACE(req, res, fmt, ...) generates a trace entry if appropriate. */ #define TRACING(req) \ (DBG(MAPPING, 1) || \ ((req)->flag & IDMAP_REQ_FLG_TRACE) != 0) #define TRACE(req, res, ...) \ ((void)(TRACING(req) && trace(req, res, __VA_ARGS__))) extern int trace(idmap_mapping *req, idmap_id_res *res, char *fmt, ...); typedef idmap_retcode (*update_list_res_cb)(void *, const char **, uint64_t); typedef int (*list_svc_cb)(void *, int, char **, char **); extern void idmap_prog_1(struct svc_req *, register SVCXPRT *); extern void idmapdlog(int, const char *, ...); extern int init_mapping_system(void); extern void fini_mapping_system(void); extern void print_idmapdstate(void); extern int create_directory(const char *, uid_t, gid_t); extern int load_config(void); extern void reload_gcs(void); extern void reload_dcs(void); extern void idmap_init_tsd_key(void); extern void degrade_svc(int, const char *); extern void restore_svc(void); extern void notify_dc_changed(void); extern int init_dbs(void); extern void fini_dbs(void); extern idmap_retcode get_db_handle(sqlite **); extern idmap_retcode get_cache_handle(sqlite **); extern void kill_db_handle(sqlite *); extern void kill_cache_handle(sqlite *); extern idmap_retcode sql_exec_no_cb(sqlite *, const char *, char *); extern idmap_retcode add_namerule(sqlite *, idmap_namerule *); extern idmap_retcode rm_namerule(sqlite *, idmap_namerule *); extern idmap_retcode flush_namerules(sqlite *); extern char *tolower_u8(const char *); extern idmap_retcode gen_sql_expr_from_rule(idmap_namerule *, char **); extern idmap_retcode validate_list_cb_data(list_cb_data_t *, int, char **, int, uchar_t **, size_t); extern idmap_retcode process_list_svc_sql(sqlite *, const char *, char *, uint64_t, int, list_svc_cb, void *); extern idmap_retcode sid2pid_first_pass(lookup_state_t *, idmap_mapping *, idmap_id_res *); extern idmap_retcode sid2pid_second_pass(lookup_state_t *, idmap_mapping *, idmap_id_res *); extern idmap_retcode pid2sid_first_pass(lookup_state_t *, idmap_mapping *, idmap_id_res *, int); extern idmap_retcode pid2sid_second_pass(lookup_state_t *, idmap_mapping *, idmap_id_res *, int); extern idmap_retcode update_cache_sid2pid(lookup_state_t *, idmap_mapping *, idmap_id_res *); extern idmap_retcode update_cache_pid2sid(lookup_state_t *, idmap_mapping *, idmap_id_res *); extern idmap_retcode get_u2w_mapping(sqlite *, sqlite *, idmap_mapping *, idmap_mapping *, int); extern idmap_retcode get_w2u_mapping(sqlite *, sqlite *, idmap_mapping *, idmap_mapping *); extern idmap_retcode load_cfg_in_state(lookup_state_t *); extern void cleanup_lookup_state(lookup_state_t *); extern idmap_retcode ad_lookup_batch(lookup_state_t *, idmap_mapping_batch *, idmap_ids_res *); extern idmap_retcode lookup_name2sid(sqlite *, const char *, const char *, int, char **, char **, char **, idmap_rid_t *, idmap_id_type *, idmap_mapping *, int); extern idmap_retcode lookup_wksids_name2sid(const char *, const char *, char **, char **, char **, idmap_rid_t *, idmap_id_type *); extern idmap_retcode idmap_cache_flush(idmap_flush_op); extern const wksids_table_t *find_wksid_by_pid(posix_id_t pid, int is_user); extern const wksids_table_t *find_wksid_by_sid(const char *sid, int rid, idmap_id_type type); extern const wksids_table_t *find_wksid_by_name(const char *name, const char *domain, idmap_id_type type); extern const wksids_table_t *find_wk_by_sid(char *sid); #ifdef __cplusplus } #endif #endif /* _IDMAPD_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* * Initialization routines */ #include "idmapd.h" #include #include #include #include #include #include #include #include #include int init_mapping_system(void) { int rc = 0; if ((rc = load_config()) < 0) return (rc); (void) setegid(DAEMON_GID); (void) seteuid(DAEMON_UID); if (init_dbs() < 0) { rc = -1; fini_mapping_system(); } (void) seteuid(0); (void) setegid(0); return (rc); } void fini_mapping_system(void) { fini_dbs(); } int load_config(void) { int rc; if ((_idmapdstate.cfg = idmap_cfg_init()) == NULL) { degrade_svc(0, "failed to initialize config"); return (-1); } rc = idmap_cfg_upgrade(_idmapdstate.cfg); if (rc != 0) { degrade_svc(0, "fatal error while upgrading configuration"); return (rc); } rc = idmap_cfg_load(_idmapdstate.cfg, 0); if (rc < -1) { /* Total failure */ degrade_svc(0, "fatal error while loading configuration"); return (rc); } if (rc != 0) /* Partial failure */ idmapdlog(LOG_ERR, "Various errors occurred while loading " "the configuration; check the logs"); if ((rc = idmap_cfg_start_updates()) < 0) { /* Total failure */ degrade_svc(0, "could not start config updater"); return (rc); } if (DBG(CONFIG, 1)) idmapdlog(LOG_DEBUG, "Initial configuration loaded"); return (0); } void reload_gcs(void) { int i, j; adutils_ad_t **new_gcs; adutils_ad_t **old_gcs = _idmapdstate.gcs; int new_num_gcs; int old_num_gcs = _idmapdstate.num_gcs; idmap_pg_config_t *pgcfg = &_idmapdstate.cfg->pgcfg; idmap_trustedforest_t *trustfor = pgcfg->trusted_forests; int num_trustfor = pgcfg->num_trusted_forests; ad_disc_domainsinforest_t *domain_in_forest; if (pgcfg->use_ads == B_FALSE || pgcfg->domain_name == NULL) { /* * ADS disabled, or no domain name specified. * Not using adutils. (but still can use lsa) */ new_gcs = NULL; new_num_gcs = 0; goto out; } if (pgcfg->global_catalog == NULL || pgcfg->global_catalog[0].host[0] == '\0') { /* * No GCs. Continue to use the previous AD config in case * that's still good but auto-discovery had a transient failure. * If that stops working we'll go into degraded mode anyways * when it does. */ idmapdlog(LOG_INFO, "Global Catalog servers not configured/discoverable"); return; } new_num_gcs = 1 + num_trustfor; new_gcs = calloc(new_num_gcs, sizeof (adutils_ad_t *)); if (new_gcs == NULL) { degrade_svc(0, "could not allocate AD context array " "(out of memory)"); return; } if (adutils_ad_alloc(&new_gcs[0], NULL, ADUTILS_AD_GLOBAL_CATALOG) != ADUTILS_SUCCESS) { free(new_gcs); degrade_svc(0, "could not initialize AD context " "(out of memory)"); return; } for (i = 0; pgcfg->global_catalog[i].host[0] != '\0'; i++) { if (idmap_add_ds(new_gcs[0], pgcfg->global_catalog[i].host, pgcfg->global_catalog[i].port) != 0) { adutils_ad_free(&new_gcs[0]); free(new_gcs); degrade_svc(0, "could not set AD hosts " "(out of memory)"); return; } } if (pgcfg->domains_in_forest != NULL) { for (i = 0; pgcfg->domains_in_forest[i].domain[0] != '\0'; i++) { if (adutils_add_domain(new_gcs[0], pgcfg->domains_in_forest[i].domain, pgcfg->domains_in_forest[i].sid) != 0) { adutils_ad_free(&new_gcs[0]); free(new_gcs); degrade_svc(0, "could not set AD domains " "(out of memory)"); return; } } } for (i = 0; i < num_trustfor; i++) { if (adutils_ad_alloc(&new_gcs[i + 1], NULL, ADUTILS_AD_GLOBAL_CATALOG) != ADUTILS_SUCCESS) { degrade_svc(0, "could not initialize trusted AD " "context (out of memory)"); new_num_gcs = i + 1; goto out; } for (j = 0; trustfor[i].global_catalog[j].host[0] != '\0'; j++) { if (idmap_add_ds(new_gcs[i + 1], trustfor[i].global_catalog[j].host, trustfor[i].global_catalog[j].port) != 0) { adutils_ad_free(&new_gcs[i + 1]); degrade_svc(0, "could not set trusted " "AD hosts (out of memory)"); new_num_gcs = i + 1; goto out; } } for (j = 0; trustfor[i].domains_in_forest[j].domain[0] != '\0'; j++) { domain_in_forest = &trustfor[i].domains_in_forest[j]; /* Only add domains which are marked */ if (domain_in_forest->trusted) { if (adutils_add_domain(new_gcs[i + 1], domain_in_forest->domain, domain_in_forest->sid) != 0) { adutils_ad_free(&new_gcs[i + 1]); degrade_svc(0, "could not set trusted " "AD domains (out of memory)"); new_num_gcs = i + 1; goto out; } } } } out: _idmapdstate.gcs = new_gcs; _idmapdstate.num_gcs = new_num_gcs; if (old_gcs != NULL) { for (i = 0; i < old_num_gcs; i++) adutils_ad_free(&old_gcs[i]); free(old_gcs); } } /* * NEEDSWORK: This should load entries for domain servers for all known * domains - the joined domain, other domains in the forest, and trusted * domains in other forests. However, we don't yet discover any DCs other * than the DCs for the joined domain. */ void reload_dcs(void) { int i; adutils_ad_t **new_dcs; adutils_ad_t **old_dcs = _idmapdstate.dcs; int new_num_dcs; int old_num_dcs = _idmapdstate.num_dcs; idmap_pg_config_t *pgcfg = &_idmapdstate.cfg->pgcfg; if (pgcfg->use_ads == B_FALSE || pgcfg->domain_name == NULL) { /* * ADS disabled, or no domain name specified. * Not using adutils. (but still can use lsa) */ new_dcs = NULL; new_num_dcs = 0; goto out; } if (pgcfg->domain_controller == NULL || pgcfg->domain_controller[0].host[0] == '\0') { /* * No DCs. Continue to use the previous AD config in case * that's still good but auto-discovery had a transient failure. * If that stops working we'll go into degraded mode anyways * when it does. */ idmapdlog(LOG_INFO, "Domain controller servers not configured/discoverable"); return; } new_num_dcs = 1; new_dcs = calloc(new_num_dcs, sizeof (adutils_ad_t *)); if (new_dcs == NULL) goto nomem; if (adutils_ad_alloc(&new_dcs[0], pgcfg->domain_name, ADUTILS_AD_DATA) != ADUTILS_SUCCESS) goto nomem; for (i = 0; pgcfg->domain_controller[i].host[0] != '\0'; i++) { if (idmap_add_ds(new_dcs[0], pgcfg->domain_controller[i].host, pgcfg->domain_controller[i].port) != 0) goto nomem; } /* * NEEDSWORK: All we need here is to add the domain and SID for * this DC to the list of domains supported by this entry. Isn't * there an easier way to find the SID than to walk through the list * of all of the domains in the forest? */ ad_disc_domainsinforest_t *dif = pgcfg->domains_in_forest; if (dif != NULL) { for (; dif->domain[0] != '\0'; dif++) { if (domain_eq(pgcfg->domain_name, dif->domain)) { if (adutils_add_domain(new_dcs[0], dif->domain, dif->sid) != 0) goto nomem; break; } } } out: _idmapdstate.dcs = new_dcs; _idmapdstate.num_dcs = new_num_dcs; if (old_dcs != NULL) { for (i = 0; i < old_num_dcs; i++) adutils_ad_free(&old_dcs[i]); free(old_dcs); } return; nomem: degrade_svc(0, "out of memory"); if (new_dcs != NULL) { if (new_dcs[0] != NULL) adutils_ad_free(&new_dcs[0]); free(new_dcs); } } void print_idmapdstate(void) { int i, j; idmap_pg_config_t *pgcfg; idmap_trustedforest_t *tf; RDLOCK_CONFIG(); if (_idmapdstate.cfg == NULL) { idmapdlog(LOG_INFO, "Null configuration"); UNLOCK_CONFIG(); return; } pgcfg = &_idmapdstate.cfg->pgcfg; idmapdlog(LOG_DEBUG, "list_size_limit=%llu", pgcfg->list_size_limit); idmapdlog(LOG_DEBUG, "max_threads=%llu", pgcfg->max_threads); idmapdlog(LOG_DEBUG, "default_domain=%s", CHECK_NULL(pgcfg->default_domain)); idmapdlog(LOG_DEBUG, "domain_name=%s", CHECK_NULL(pgcfg->domain_name)); idmapdlog(LOG_DEBUG, "machine_sid=%s", CHECK_NULL(pgcfg->machine_sid)); if (pgcfg->domain_controller == NULL || pgcfg->domain_controller[0].host[0] == '\0') { idmapdlog(LOG_DEBUG, "No domain controllers known"); } else { for (i = 0; pgcfg->domain_controller[i].host[0] != '\0'; i++) idmapdlog(LOG_DEBUG, "domain_controller=%s port=%d", pgcfg->domain_controller[i].host, pgcfg->domain_controller[i].port); } idmapdlog(LOG_DEBUG, "forest_name=%s", CHECK_NULL(pgcfg->forest_name)); idmapdlog(LOG_DEBUG, "site_name=%s", CHECK_NULL(pgcfg->site_name)); if (pgcfg->global_catalog == NULL || pgcfg->global_catalog[0].host[0] == '\0') { idmapdlog(LOG_DEBUG, "No global catalog servers known"); } else { for (i = 0; pgcfg->global_catalog[i].host[0] != '\0'; i++) idmapdlog(LOG_DEBUG, "global_catalog=%s port=%d", pgcfg->global_catalog[i].host, pgcfg->global_catalog[i].port); } if (pgcfg->domains_in_forest == NULL || pgcfg->domains_in_forest[0].domain[0] == '\0') { idmapdlog(LOG_DEBUG, "No domains in forest %s known", CHECK_NULL(pgcfg->forest_name)); } else { for (i = 0; pgcfg->domains_in_forest[i].domain[0] != '\0'; i++) idmapdlog(LOG_DEBUG, "domains in forest %s = %s", CHECK_NULL(pgcfg->forest_name), pgcfg->domains_in_forest[i].domain); } if (pgcfg->trusted_domains == NULL || pgcfg->trusted_domains[0].domain[0] == '\0') { idmapdlog(LOG_DEBUG, "No trusted domains known"); } else { for (i = 0; pgcfg->trusted_domains[i].domain[0] != '\0'; i++) idmapdlog(LOG_DEBUG, "trusted domain = %s", pgcfg->trusted_domains[i].domain); } for (i = 0; i < pgcfg->num_trusted_forests; i++) { tf = &pgcfg->trusted_forests[i]; for (j = 0; tf->global_catalog[j].host[0] != '\0'; j++) idmapdlog(LOG_DEBUG, "trusted forest %s global_catalog=%s port=%d", tf->forest_name, tf->global_catalog[j].host, tf->global_catalog[j].port); for (j = 0; tf->domains_in_forest[j].domain[0] != '\0'; j++) { if (tf->domains_in_forest[j].trusted) { idmapdlog(LOG_DEBUG, "trusted forest %s domain=%s", tf->forest_name, tf->domains_in_forest[j].domain); } } } idmapdlog(LOG_DEBUG, "directory_based_mapping=%s", enum_lookup(pgcfg->directory_based_mapping, directory_mapping_map)); idmapdlog(LOG_DEBUG, "ad_unixuser_attr=%s", CHECK_NULL(pgcfg->ad_unixuser_attr)); idmapdlog(LOG_DEBUG, "ad_unixgroup_attr=%s", CHECK_NULL(pgcfg->ad_unixgroup_attr)); idmapdlog(LOG_DEBUG, "nldap_winname_attr=%s", CHECK_NULL(pgcfg->nldap_winname_attr)); UNLOCK_CONFIG(); } int create_directory(const char *path, uid_t uid, gid_t gid) { int rc; if ((rc = mkdir(path, 0700)) < 0 && errno != EEXIST) { idmapdlog(LOG_ERR, "Error creating directory %s (%s)", path, strerror(errno)); return (-1); } if (lchown(path, uid, gid) < 0) { idmapdlog(LOG_ERR, "Error creating directory %s (%s)", path, strerror(errno)); if (rc == 0) (void) rmdir(path); return (-1); } return (0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2020 Nexenta by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "idmapd.h" #include "libadutils.h" #include "locate_plugin.h" /* osconf.h - sigh */ #define KRB5_DEFAULT_PORT 88 #define DEFAULT_KADM5_PORT 749 #define DEFAULT_KPASSWD_PORT 464 /* * This is an "override plugin" used by libkrb5. See: * lib/gss_mechs/mech_krb5/krb5/os/locate_kdc.c * * The interface is based on: * http://web.mit.edu/~kerberos/krb5-1.12/doc/plugindev/locate.html */ /* * Called by krb5int_locate_server / override_locate_server */ krb5_error_code _krb5_override_service_locator( void *arg0, enum locate_service_type svc, const char *realm, int socktype, int family, int (*cbfunc)(void *, int, struct sockaddr *), void *cbdata) { _NOTE(ARGUNUSED(arg0)) idmap_pg_config_t *pgcfg; ad_disc_ds_t *ds; int rc = KRB5_PLUGIN_NO_HANDLE; short port; /* * Is this a service we want to override? */ switch (svc) { case locate_service_kdc: case locate_service_master_kdc: port = htons(KRB5_DEFAULT_PORT); break; case locate_service_kadmin: port = htons(DEFAULT_KADM5_PORT); break; case locate_service_kpasswd: port = htons(DEFAULT_KPASSWD_PORT); break; case locate_service_krb524: default: return (rc); } RDLOCK_CONFIG(); pgcfg = &_idmapdstate.cfg->pgcfg; /* * Is this a realm we want to override? */ if (pgcfg->domain_name == NULL) goto out; if (0 != strcasecmp(realm, pgcfg->domain_name)) goto out; /* * Yes, this is our domain. Have a DC? */ if ((ds = pgcfg->domain_controller) == NULL) { rc = KRB5_REALM_CANT_RESOLVE; goto out; } if ((ds->flags & DS_KDC_FLAG) == 0) { idmapdlog(LOG_WARNING, "Domain Controller is not a KDC: " "Kerberos auth may be slow"); goto out; } switch (family) { case AF_UNSPEC: break; /* OK */ case AF_INET: case AF_INET6: if (family == ds->addr.ss_family) break; /* OK */ /* else fallthrough */ default: rc = KRB5_ERR_NO_SERVICE; goto out; } /* * Provide the service address we have. */ switch (ds->addr.ss_family) { case AF_INET: { struct sockaddr_in sin; struct sockaddr_in *dsa = (void *)&ds->addr; (void) memset(&sin, 0, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_port = port; (void) memcpy(&sin.sin_addr, &dsa->sin_addr, sizeof (sin.sin_addr)); rc = cbfunc(cbdata, socktype, (struct sockaddr *)&sin); break; } case AF_INET6: { struct sockaddr_in6 sin6; struct sockaddr_in6 *dsa6 = (void *)&ds->addr; (void) memset(&sin6, 0, sizeof (sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = port; (void) memcpy(&sin6.sin6_addr, &dsa6->sin6_addr, sizeof (sin6.sin6_addr)); rc = cbfunc(cbdata, socktype, (struct sockaddr *)&sin6); break; } default: rc = KRB5_ERR_NO_SERVICE; goto out; } /* rc from cbfunc is special. */ if (rc) rc = ENOMEM; out: UNLOCK_CONFIG(); return (rc); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2011 Nexenta Systems, Inc. All rights reserved. */ /* * native LDAP related utility routines */ #include "idmapd.h" #include "idmap_priv.h" #include "ns_sldap.h" #include "nldaputils.h" #include /* * The following are format strings used to construct LDAP search filters * when looking up Native LDAP directory service. The _F_XXX_SSD format * is used by the libsldap API if a corresponding SSD is defined in * Native LDAP configuration. The SSD contains a string that replaces * the first %s in _F_XXX_SSD. If no SSD is defined then the regular * _F_XXX format is used. * * Note that '\\' needs to be represented as "\\5c" in LDAP filters. */ /* Native LDAP lookup using UNIX username */ #define _F_GETPWNAM "(&(objectClass=posixAccount)(uid=%s))" #define _F_GETPWNAM_SSD "(&(%%s)(uid=%s))" /* * Native LDAP user lookup using names of well-known SIDs * Note the use of 1$, 2$ in the format string which basically * allows snprintf to re-use its first two arguments. */ #define _F_GETPWWNAMWK \ "(&(objectClass=posixAccount)(|(%s=%s)(%1$s=BUILTIN\\5c%2$s)))" #define _F_GETPWWNAMWK_SSD "(&(%%s)(|(%s=%s)(%1$s=BUILTIN\\5c%2$s)))" /* Native LDAP user lookup using winname@windomain OR windomain\winname */ #define _F_GETPWWNAMDOM \ "(&(objectClass=posixAccount)(|(%s=%s@%s)(%1$s=%3$s\\5c%2$s)))" #define _F_GETPWWNAMDOM_SSD "(&(%%s)(|(%s=%s@%s)(%1$s=%3$s\\5c%2$s)))" /* Native LDAP lookup using UID */ #define _F_GETPWUID "(&(objectClass=posixAccount)(uidNumber=%u))" #define _F_GETPWUID_SSD "(&(%%s)(uidNumber=%u))" /* Native LDAP lookup using UNIX groupname */ #define _F_GETGRNAM "(&(objectClass=posixGroup)(cn=%s))" #define _F_GETGRNAM_SSD "(&(%%s)(cn=%s))" /* Native LDAP group lookup using names of well-known SIDs */ #define _F_GETGRWNAMWK \ "(&(objectClass=posixGroup)(|(%s=%s)(%1$s=BUILTIN\\5c%2$s)))" #define _F_GETGRWNAMWK_SSD "(&(%%s)(|(%s=%s)(%1$s=BUILTIN\\5c%2$s)))" /* Native LDAP group lookup using winname@windomain OR windomain\winname */ #define _F_GETGRWNAMDOM \ "(&(objectClass=posixGroup)(|(%s=%s@%s)(%1$s=%3$s\\5c%2$s)))" #define _F_GETGRWNAMDOM_SSD "(&(%%s)(|(%s=%s@%s)(%1$s=%3$s\\5c%2$s)))" /* Native LDAP lookup using GID */ #define _F_GETGRGID "(&(objectClass=posixGroup)(gidNumber=%u))" #define _F_GETGRGID_SSD "(&(%%s)(gidNumber=%u))" /* Native LDAP attribute names */ #define UID "uid" #define CN "cn" #define UIDNUMBER "uidnumber" #define GIDNUMBER "gidnumber" #define DN "dn" #define IS_NLDAP_RC_FATAL(x) ((x == NS_LDAP_MEMORY) ? 1 : 0) typedef struct idmap_nldap_q { char **winname; char **windomain; char **unixname; uid_t *pid; char **dn; char **attr; char **value; int is_user; idmap_retcode *rc; int lrc; ns_ldap_result_t *result; ns_ldap_error_t *errorp; char *filter; char *udata; } idmap_nldap_q_t; typedef struct idmap_nldap_query_state { const char *nldap_winname_attr; const char *defdom; int nqueries; int qid; int flag; ns_ldap_list_batch_t *batch; idmap_nldap_q_t queries[1]; } idmap_nldap_query_state_t; /* * This routine has been copied from lib/nsswitch/ldap/common/ldap_utils.c * after removing the debug statements. * * This is a generic filter callback function for merging the filter * from service search descriptor with an existing search filter. This * routine expects userdata to contain a format string with a single %s * in it, and will use the format string with sprintf() to insert the * SSD filter. * * This routine and userdata are passed to the __ns_ldap_list_batch_add() * API. * * Consider an example that uses __ns_ldap_list_batch_add() to lookup * native LDAP directory using a given userid 'xy12345'. In this * example the userdata will contain the filter "(&(%s)(cn=xy1234))". * If a SSD is defined to replace the rfc2307bis specified filter * i.e. (objectClass=posixAccount) by a site-specific filter * say (department=sds) then this routine when called will produce * "(&(department=sds)(uid=xy1234))" as the real search filter. */ static int merge_SSD_filter(const ns_ldap_search_desc_t *desc, char **realfilter, const void *userdata) { int len; char *checker; if (realfilter == NULL) return (NS_LDAP_INVALID_PARAM); *realfilter = NULL; if (desc == NULL || desc->filter == NULL || userdata == NULL) return (NS_LDAP_INVALID_PARAM); /* Parameter check. We only want one %s here, otherwise bail. */ len = 0; /* Reuse 'len' as "Number of %s hits"... */ checker = (char *)userdata; do { checker = strchr(checker, '%'); if (checker != NULL) { if (len > 0 || *(checker + 1) != 's') return (NS_LDAP_INVALID_PARAM); len++; /* Got our %s. */ checker += 2; } else if (len != 1) return (NS_LDAP_INVALID_PARAM); } while (checker != NULL); len = strlen(userdata) + strlen(desc->filter) + 1; *realfilter = (char *)malloc(len); if (*realfilter == NULL) return (NS_LDAP_MEMORY); (void) sprintf(*realfilter, (char *)userdata, desc->filter); return (NS_LDAP_SUCCESS); } static char hex_char(int n) { return ("0123456789abcdef"[n & 0xf]); } /* * If the input string contains special characters that needs to be * escaped before the string can be used in a LDAP filter then this * function will return a new sanitized string. Otherwise this function * returns the input string (This saves us un-necessary memory allocations * especially when processing a batch of requests). The caller must free * the returned string if it isn't the input string. * * The escape mechanism for LDAP filter is described in RFC2254 basically * it's \hh where hh are the two hexadecimal digits representing the ASCII * value of the encoded character (case of hh is not significant). * Example: * -> \2a, ( -> \28, ) -> \29, \ -> \5c, * * outstring = sanitize_for_ldap_filter(instring); * if (outstring == NULL) * Out of memory * else * Use outstring * if (outstring != instring) * free(outstring); * done */ char * sanitize_for_ldap_filter(const char *str) { const char *p; char *q, *s_str = NULL; int n; /* Get a count of special characters */ for (p = str, n = 0; *p; p++) if (*p == '*' || *p == '(' || *p == ')' || *p == '\\' || *p == '%') n++; /* If count is zero then no need to sanitize */ if (n == 0) return ((char *)str); /* Create output buffer that will contain the sanitized value */ s_str = calloc(1, n * 2 + strlen(str) + 1); if (s_str == NULL) return (NULL); for (p = str, q = s_str; *p; p++) { if (*p == '*' || *p == '(' || *p == ')' || *p == '\\' || *p == '%') { *q++ = '\\'; *q++ = hex_char(*p >> 4); *q++ = hex_char(*p & 0xf); } else *q++ = *p; } return (s_str); } /* * Map libsldap status to idmap status */ static idmap_retcode nldaprc2retcode(int rc) { switch (rc) { case NS_LDAP_SUCCESS: case NS_LDAP_SUCCESS_WITH_INFO: return (IDMAP_SUCCESS); case NS_LDAP_NOTFOUND: return (IDMAP_ERR_NOTFOUND); case NS_LDAP_MEMORY: return (IDMAP_ERR_MEMORY); case NS_LDAP_CONFIG: return (IDMAP_ERR_NS_LDAP_CFG); case NS_LDAP_OP_FAILED: return (IDMAP_ERR_NS_LDAP_OP_FAILED); case NS_LDAP_PARTIAL: return (IDMAP_ERR_NS_LDAP_PARTIAL); case NS_LDAP_INTERNAL: return (IDMAP_ERR_INTERNAL); case NS_LDAP_INVALID_PARAM: return (IDMAP_ERR_ARG); default: return (IDMAP_ERR_OTHER); } /*NOTREACHED*/ } /* * Create a batch for native LDAP lookup. */ static idmap_retcode idmap_nldap_lookup_batch_start(int nqueries, idmap_nldap_query_state_t **qs) { idmap_nldap_query_state_t *s; s = calloc(1, sizeof (*s) + (nqueries - 1) * sizeof (idmap_nldap_q_t)); if (s == NULL) return (IDMAP_ERR_MEMORY); if (__ns_ldap_list_batch_start(&s->batch) != NS_LDAP_SUCCESS) { free(s); return (IDMAP_ERR_MEMORY); } s->nqueries = nqueries; s->flag = NS_LDAP_KEEP_CONN; *qs = s; return (IDMAP_SUCCESS); } /* * Add a lookup by winname request to the batch. */ static idmap_retcode idmap_nldap_bywinname_batch_add(idmap_nldap_query_state_t *qs, const char *winname, const char *windomain, int is_user, char **dn, char **attr, char **value, char **unixname, uid_t *pid, idmap_retcode *rc) { idmap_nldap_q_t *q; const char *db, *filter, *udata; int flen, ulen, wksid = 0; char *s_winname, *s_windomain; const char **attrs; const char *pwd_attrs[] = {UID, UIDNUMBER, NULL, NULL}; const char *grp_attrs[] = {CN, GIDNUMBER, NULL, NULL}; s_winname = s_windomain = NULL; q = &(qs->queries[qs->qid++]); q->unixname = unixname; q->pid = pid; q->rc = rc; q->is_user = is_user; q->dn = dn; q->attr = attr; q->value = value; if (is_user) { db = "passwd"; if (lookup_wksids_name2sid(winname, NULL, NULL, NULL, NULL, NULL, NULL) == IDMAP_SUCCESS) { filter = _F_GETPWWNAMWK; udata = _F_GETPWWNAMWK_SSD; wksid = 1; } else if (windomain != NULL) { filter = _F_GETPWWNAMDOM; udata = _F_GETPWWNAMDOM_SSD; } else { *q->rc = IDMAP_ERR_DOMAIN_NOTFOUND; goto errout; } pwd_attrs[2] = qs->nldap_winname_attr; attrs = pwd_attrs; } else { db = "group"; if (lookup_wksids_name2sid(winname, NULL, NULL, NULL, NULL, NULL, NULL) == IDMAP_SUCCESS) { filter = _F_GETGRWNAMWK; udata = _F_GETGRWNAMWK_SSD; wksid = 1; } else if (windomain != NULL) { filter = _F_GETGRWNAMDOM; udata = _F_GETGRWNAMDOM_SSD; } else { *q->rc = IDMAP_ERR_DOMAIN_NOTFOUND; goto errout; } grp_attrs[2] = qs->nldap_winname_attr; attrs = grp_attrs; } /* * Sanitize names. No need to sanitize qs->nldap_winname_attr * because if it contained any of the special characters then * it would have been rejected by the function that reads it * from the SMF config. LDAP attribute names can only contain * letters, digits or hyphens. */ s_winname = sanitize_for_ldap_filter(winname); if (s_winname == NULL) { *q->rc = IDMAP_ERR_MEMORY; goto errout; } /* windomain could be NULL for names of well-known SIDs */ if (windomain != NULL) { s_windomain = sanitize_for_ldap_filter(windomain); if (s_windomain == NULL) { *q->rc = IDMAP_ERR_MEMORY; goto errout; } } /* Construct the filter and udata using snprintf. */ if (wksid) { flen = snprintf(NULL, 0, filter, qs->nldap_winname_attr, s_winname) + 1; ulen = snprintf(NULL, 0, udata, qs->nldap_winname_attr, s_winname) + 1; } else { flen = snprintf(NULL, 0, filter, qs->nldap_winname_attr, s_winname, s_windomain) + 1; ulen = snprintf(NULL, 0, udata, qs->nldap_winname_attr, s_winname, s_windomain) + 1; } q->filter = malloc(flen); if (q->filter == NULL) { *q->rc = IDMAP_ERR_MEMORY; goto errout; } q->udata = malloc(ulen); if (q->udata == NULL) { *q->rc = IDMAP_ERR_MEMORY; goto errout; } if (wksid) { (void) snprintf(q->filter, flen, filter, qs->nldap_winname_attr, s_winname); (void) snprintf(q->udata, ulen, udata, qs->nldap_winname_attr, s_winname); } else { (void) snprintf(q->filter, flen, filter, qs->nldap_winname_attr, s_winname, s_windomain); (void) snprintf(q->udata, ulen, udata, qs->nldap_winname_attr, s_winname, s_windomain); } if (s_winname != winname) free(s_winname); if (s_windomain != windomain) free(s_windomain); q->lrc = __ns_ldap_list_batch_add(qs->batch, db, q->filter, merge_SSD_filter, attrs, NULL, qs->flag, &q->result, &q->errorp, &q->lrc, NULL, q->udata); if (IS_NLDAP_RC_FATAL(q->lrc)) return (nldaprc2retcode(q->lrc)); return (IDMAP_SUCCESS); errout: /* query q and its content will be freed by batch_release */ if (s_winname != winname) free(s_winname); if (s_windomain != windomain) free(s_windomain); return (*q->rc); } /* * Add a lookup by uid/gid request to the batch. */ static idmap_retcode idmap_nldap_bypid_batch_add(idmap_nldap_query_state_t *qs, uid_t pid, int is_user, char **dn, char **attr, char **value, char **winname, char **windomain, char **unixname, idmap_retcode *rc) { idmap_nldap_q_t *q; const char *db, *filter, *udata; int len; const char **attrs; const char *pwd_attrs[] = {UID, NULL, NULL}; const char *grp_attrs[] = {CN, NULL, NULL}; q = &(qs->queries[qs->qid++]); q->winname = winname; q->windomain = windomain; q->unixname = unixname; q->rc = rc; q->is_user = is_user; q->dn = dn; q->attr = attr; q->value = value; if (is_user) { db = "passwd"; filter = _F_GETPWUID; udata = _F_GETPWUID_SSD; pwd_attrs[1] = qs->nldap_winname_attr; attrs = pwd_attrs; } else { db = "group"; filter = _F_GETGRGID; udata = _F_GETGRGID_SSD; grp_attrs[1] = qs->nldap_winname_attr; attrs = grp_attrs; } len = snprintf(NULL, 0, filter, pid) + 1; q->filter = malloc(len); if (q->filter == NULL) { *q->rc = IDMAP_ERR_MEMORY; return (IDMAP_ERR_MEMORY); } (void) snprintf(q->filter, len, filter, pid); len = snprintf(NULL, 0, udata, pid) + 1; q->udata = malloc(len); if (q->udata == NULL) { *q->rc = IDMAP_ERR_MEMORY; return (IDMAP_ERR_MEMORY); } (void) snprintf(q->udata, len, udata, pid); q->lrc = __ns_ldap_list_batch_add(qs->batch, db, q->filter, merge_SSD_filter, attrs, NULL, qs->flag, &q->result, &q->errorp, &q->lrc, NULL, q->udata); if (IS_NLDAP_RC_FATAL(q->lrc)) return (nldaprc2retcode(q->lrc)); return (IDMAP_SUCCESS); } /* * Add a lookup by user/group name request to the batch. */ static idmap_retcode idmap_nldap_byunixname_batch_add(idmap_nldap_query_state_t *qs, const char *unixname, int is_user, char **dn, char **attr, char **value, char **winname, char **windomain, uid_t *pid, idmap_retcode *rc) { idmap_nldap_q_t *q; const char *db, *filter, *udata; int len; char *s_unixname = NULL; const char **attrs; const char *pwd_attrs[] = {UIDNUMBER, NULL, NULL}; const char *grp_attrs[] = {GIDNUMBER, NULL, NULL}; q = &(qs->queries[qs->qid++]); q->winname = winname; q->windomain = windomain; q->pid = pid; q->rc = rc; q->is_user = is_user; q->dn = dn; q->attr = attr; q->value = value; if (is_user) { db = "passwd"; filter = _F_GETPWNAM; udata = _F_GETPWNAM_SSD; pwd_attrs[1] = qs->nldap_winname_attr; attrs = pwd_attrs; } else { db = "group"; filter = _F_GETGRNAM; udata = _F_GETGRNAM_SSD; grp_attrs[1] = qs->nldap_winname_attr; attrs = grp_attrs; } s_unixname = sanitize_for_ldap_filter(unixname); if (s_unixname == NULL) { *q->rc = IDMAP_ERR_MEMORY; return (IDMAP_ERR_MEMORY); } len = snprintf(NULL, 0, filter, s_unixname) + 1; q->filter = malloc(len); if (q->filter == NULL) { if (s_unixname != unixname) free(s_unixname); *q->rc = IDMAP_ERR_MEMORY; return (IDMAP_ERR_MEMORY); } (void) snprintf(q->filter, len, filter, s_unixname); len = snprintf(NULL, 0, udata, s_unixname) + 1; q->udata = malloc(len); if (q->udata == NULL) { if (s_unixname != unixname) free(s_unixname); *q->rc = IDMAP_ERR_MEMORY; return (IDMAP_ERR_MEMORY); } (void) snprintf(q->udata, len, udata, s_unixname); if (s_unixname != unixname) free(s_unixname); q->lrc = __ns_ldap_list_batch_add(qs->batch, db, q->filter, merge_SSD_filter, attrs, NULL, qs->flag, &q->result, &q->errorp, &q->lrc, NULL, q->udata); if (IS_NLDAP_RC_FATAL(q->lrc)) return (nldaprc2retcode(q->lrc)); return (IDMAP_SUCCESS); } /* * Free the batch */ static void idmap_nldap_lookup_batch_release(idmap_nldap_query_state_t *qs) { idmap_nldap_q_t *q; int i; if (qs->batch != NULL) (void) __ns_ldap_list_batch_release(qs->batch); for (i = 0; i < qs->qid; i++) { q = &(qs->queries[i]); free(q->filter); free(q->udata); if (q->errorp != NULL) (void) __ns_ldap_freeError(&q->errorp); if (q->result != NULL) (void) __ns_ldap_freeResult(&q->result); } free(qs); } /* * Process all requests added to the batch and then free the batch. * The results for individual requests will be accessible using the * pointers passed during idmap_nldap_lookup_batch_end. */ static idmap_retcode idmap_nldap_lookup_batch_end(idmap_nldap_query_state_t *qs) { idmap_nldap_q_t *q; int i; ns_ldap_entry_t *entry; char **val, *end, *str, *name, *dom; idmap_retcode rc = IDMAP_SUCCESS; (void) __ns_ldap_list_batch_end(qs->batch); qs->batch = NULL; for (i = 0; i < qs->qid; i++) { q = &(qs->queries[i]); *q->rc = nldaprc2retcode(q->lrc); if (*q->rc != IDMAP_SUCCESS) continue; if (q->result == NULL || !q->result->entries_count || (entry = q->result->entry) == NULL || !entry->attr_count) { *q->rc = IDMAP_ERR_NOTFOUND; continue; } /* Get uid/gid */ if (q->pid != NULL) { val = __ns_ldap_getAttr(entry, (q->is_user) ? UIDNUMBER : GIDNUMBER); if (val != NULL && *val != NULL) *q->pid = strtoul(*val, &end, 10); } /* Get unixname */ if (q->unixname != NULL) { val = __ns_ldap_getAttr(entry, (q->is_user) ? UID : CN); if (val != NULL && *val != NULL) { *q->unixname = strdup(*val); if (*q->unixname == NULL) { rc = *q->rc = IDMAP_ERR_MEMORY; goto out; } } } /* Get DN for how info */ if (q->dn != NULL) { val = __ns_ldap_getAttr(entry, DN); if (val != NULL && *val != NULL) { *q->dn = strdup(*val); if (*q->dn == NULL) { rc = *q->rc = IDMAP_ERR_MEMORY; goto out; } } } /* Get nldap name mapping attr name for how info */ if (q->attr != NULL) { *q->attr = strdup(qs->nldap_winname_attr); if (*q->attr == NULL) { rc = *q->rc = IDMAP_ERR_MEMORY; goto out; } } /* Get nldap name mapping attr value for how info */ val = __ns_ldap_getAttr(entry, qs->nldap_winname_attr); if (val == NULL || *val == NULL) continue; if (q->value != NULL) { *q->value = strdup(*val); if (*q->value == NULL) { rc = *q->rc = IDMAP_ERR_MEMORY; goto out; } } /* Get winname and windomain */ if (q->winname == NULL && q->windomain == NULL) continue; /* * We need to split the value into winname and * windomain. The value could be either in NT4 * style (i.e. dom\name) or AD-style (i.e. name@dom). * We choose the first '\\' if it's in NT4 style and * the last '@' if it's in AD-style for the split. */ name = dom = NULL; if (lookup_wksids_name2sid(*val, NULL, NULL, NULL, NULL, NULL, NULL) == IDMAP_SUCCESS) { name = *val; dom = NULL; } else if ((str = strchr(*val, '\\')) != NULL) { *str = '\0'; name = str + 1; dom = *val; } else if ((str = strrchr(*val, '@')) != NULL) { *str = '\0'; name = *val; dom = str + 1; } else { idmapdlog(LOG_INFO, "Domain-less " "winname (%s) found in Native LDAP", *val); *q->rc = IDMAP_ERR_NS_LDAP_BAD_WINNAME; continue; } if (q->winname != NULL) { *q->winname = strdup(name); if (*q->winname == NULL) { rc = *q->rc = IDMAP_ERR_MEMORY; goto out; } } if (q->windomain != NULL && dom != NULL) { *q->windomain = strdup(dom); if (*q->windomain == NULL) { rc = *q->rc = IDMAP_ERR_MEMORY; goto out; } } } out: (void) idmap_nldap_lookup_batch_release(qs); return (rc); } /* ARGSUSED */ idmap_retcode nldap_lookup_batch(lookup_state_t *state, idmap_mapping_batch *batch, idmap_ids_res *result) { idmap_retcode retcode, rc1; int i, add; idmap_mapping *req; idmap_id_res *res; idmap_nldap_query_state_t *qs = NULL; idmap_how *how; if (state->nldap_nqueries == 0) return (IDMAP_SUCCESS); /* Create nldap lookup batch */ retcode = idmap_nldap_lookup_batch_start(state->nldap_nqueries, &qs); if (retcode != IDMAP_SUCCESS) { idmapdlog(LOG_ERR, "Failed to create batch for native LDAP lookup"); goto out; } qs->nldap_winname_attr = state->nldap_winname_attr; qs->defdom = state->defdom; /* Add requests to the batch */ for (i = 0, add = 0; i < batch->idmap_mapping_batch_len; i++) { req = &batch->idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; retcode = IDMAP_SUCCESS; /* Skip if not marked for nldap lookup */ if (!(req->direction & _IDMAP_F_LOOKUP_NLDAP)) continue; if (IS_ID_SID(req->id1)) { /* win2unix request: */ /* * When processing a win2unix request, nldap lookup * is performed after AD lookup or a successful * name-cache lookup. Therefore we should already * have sid, winname and sidtype. Note that * windomain could be NULL e.g. well-known SIDs. */ assert(req->id1name != NULL && (res->id.idtype == IDMAP_UID || res->id.idtype == IDMAP_GID)); /* Skip if we already have pid and unixname */ if (req->id2name != NULL && res->id.idmap_id_u.uid != IDMAP_SENTINEL_PID) { res->retcode = IDMAP_SUCCESS; continue; } /* Clear leftover value */ free(req->id2name); req->id2name = NULL; /* Lookup nldap by winname to get pid and unixname */ add = 1; idmap_how_clear(&res->info.how); res->info.src = IDMAP_MAP_SRC_NEW; how = &res->info.how; how->map_type = IDMAP_MAP_TYPE_DS_NLDAP; retcode = idmap_nldap_bywinname_batch_add( qs, req->id1name, req->id1domain, (res->id.idtype == IDMAP_UID) ? 1 : 0, &how->idmap_how_u.nldap.dn, &how->idmap_how_u.nldap.attr, &how->idmap_how_u.nldap.value, &req->id2name, &res->id.idmap_id_u.uid, &res->retcode); } else if (IS_ID_UID(req->id1) || IS_ID_GID(req->id1)) { /* unix2win request: */ /* Skip if we already have winname */ if (req->id2name != NULL) { res->retcode = IDMAP_SUCCESS; continue; } /* Clear old value */ free(req->id2domain); req->id2domain = NULL; /* Set how info */ idmap_how_clear(&res->info.how); res->info.src = IDMAP_MAP_SRC_NEW; how = &res->info.how; how->map_type = IDMAP_MAP_TYPE_DS_NLDAP; /* Lookup nldap by pid or unixname to get winname */ if (req->id1.idmap_id_u.uid != IDMAP_SENTINEL_PID) { add = 1; retcode = idmap_nldap_bypid_batch_add( qs, req->id1.idmap_id_u.uid, (req->id1.idtype == IDMAP_UID) ? 1 : 0, &how->idmap_how_u.nldap.dn, &how->idmap_how_u.nldap.attr, &how->idmap_how_u.nldap.value, &req->id2name, &req->id2domain, (req->id1name == NULL) ? &req->id1name : NULL, &res->retcode); } else if (req->id1name != NULL) { add = 1; retcode = idmap_nldap_byunixname_batch_add( qs, req->id1name, (req->id1.idtype == IDMAP_UID) ? 1 : 0, &how->idmap_how_u.nldap.dn, &how->idmap_how_u.nldap.attr, &how->idmap_how_u.nldap.value, &req->id2name, &req->id2domain, &req->id1.idmap_id_u.uid, &res->retcode); } } /* * nldap_batch_add API returns error only on fatal failures * otherwise it returns success and the actual status * is stored in the individual request (res->retcode). * Stop adding requests to this batch on fatal failures * (i.e. if retcode != success) */ if (retcode != IDMAP_SUCCESS) break; } if (!add) idmap_nldap_lookup_batch_release(qs); else if (retcode != IDMAP_SUCCESS) idmap_nldap_lookup_batch_release(qs); else retcode = idmap_nldap_lookup_batch_end(qs); out: for (i = 0; i < batch->idmap_mapping_batch_len; i++) { req = &batch->idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; if (!(req->direction & _IDMAP_F_LOOKUP_NLDAP)) continue; /* Reset nldap flag */ req->direction &= ~(_IDMAP_F_LOOKUP_NLDAP); /* * As noted earlier retcode != success if there were fatal * errors during batch_start and batch_adds. If so then set * the status of each nldap request to that error. */ if (retcode != IDMAP_SUCCESS) { res->retcode = retcode; continue; } if (!add) continue; /* * If we successfully retrieved winname from nldap entry * then lookup winname2sid locally. If not found locally * then mark this request for AD lookup. */ if (res->retcode == IDMAP_SUCCESS && req->id2name != NULL && res->id.idmap_id_u.sid.prefix == NULL && (IS_ID_UID(req->id1) || IS_ID_GID(req->id1))) { rc1 = lookup_name2sid(state->cache, req->id2name, req->id2domain, -1, NULL, NULL, &res->id.idmap_id_u.sid.prefix, &res->id.idmap_id_u.sid.rid, &res->id.idtype, req, 1); if (rc1 == IDMAP_ERR_NOTFOUND) { req->direction |= _IDMAP_F_LOOKUP_AD; state->ad_nqueries++; } else res->retcode = rc1; } /* * Unset non-fatal errors in individual request. This allows * the next pass to process other mapping mechanisms for * this request. */ if (res->retcode != IDMAP_SUCCESS && res->retcode != IDMAP_ERR_NS_LDAP_BAD_WINNAME && !(IDMAP_FATAL_ERROR(res->retcode))) { idmap_how_clear(&res->info.how); res->retcode = IDMAP_SUCCESS; } } state->nldap_nqueries = 0; return (retcode); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NLDAPUTILS_H #define _NLDAPUTILS_H #include #include #include #include #include #include #include #include #include #include "idmapd.h" #include "idmap_config.h" #ifdef __cplusplus extern "C" { #endif extern idmap_retcode nldap_lookup_one(lookup_state_t *, idmap_mapping *, idmap_id_res *); extern idmap_retcode nldap_lookup_batch(lookup_state_t *, idmap_mapping_batch *, idmap_ids_res *); extern char *sanitize_for_ldap_filter(const char *); #ifdef __cplusplus } #endif #endif /* _NLDAPUTILS_H */ /* * Please do not edit this file. * It was generated using rpcgen. */ #include "idmap_prot.h" #include #include /* getenv, exit */ #include #include /* for pmap_unset */ #include /* strcmp */ #include /* setsid */ #include #include #include #include /* rlimit */ #include #ifndef SIG_PF #define SIG_PF void(*)(int) #endif #ifdef DEBUG #define RPC_SVC_FG #endif #define _RPCSVC_CLOSEDOWN 120 #if defined(_KERNEL) #include #else #include #endif extern int _rpcpmstart; /* Started by a port monitor ? */ /* States a server can be in wrt request */ #define _IDLE 0 #define _SERVED 1 /* LINTED static unused if no main */ static int _rpcsvcstate = _IDLE; /* Set when a request is serviced */ static int _rpcsvccount = 0; /* Number of requests being serviced */ extern mutex_t _svcstate_lock; /* lock for _rpcsvcstate, _rpcsvccount */ #if defined(RPC_MSGOUT) extern void RPC_MSGOUT(const char *, ...); #else /* defined(RPC_MSGOUT) */ static void RPC_MSGOUT(const char *fmt, char *msg) { #ifdef RPC_SVC_FG if (_rpcpmstart) syslog(LOG_ERR, fmt, msg); else { (void) fprintf(stderr, fmt, msg); (void) putc('\n', stderr); } #else syslog(LOG_ERR, fmt, msg); #endif } #endif /* defined(RPC_MSGOUT) */ /* ARGSUSED */ int _idmap_null_1( void *argp, void *result, struct svc_req *rqstp) { return (idmap_null_1_svc(result, rqstp)); } int _idmap_get_mapped_ids_1( idmap_mapping_batch *argp, idmap_ids_res *result, struct svc_req *rqstp) { return (idmap_get_mapped_ids_1_svc(*argp, result, rqstp)); } int _idmap_list_mappings_1( idmap_list_mappings_1_argument *argp, idmap_mappings_res *result, struct svc_req *rqstp) { return (idmap_list_mappings_1_svc( argp->lastrowid, argp->limit, argp->flag, result, rqstp)); } int _idmap_list_namerules_1( idmap_list_namerules_1_argument *argp, idmap_namerules_res *result, struct svc_req *rqstp) { return (idmap_list_namerules_1_svc( argp->rule, argp->lastrowid, argp->limit, result, rqstp)); } int _idmap_update_1( idmap_update_batch *argp, idmap_update_res *result, struct svc_req *rqstp) { return (idmap_update_1_svc(*argp, result, rqstp)); } int _idmap_get_mapped_id_by_name_1( idmap_mapping *argp, idmap_mappings_res *result, struct svc_req *rqstp) { return (idmap_get_mapped_id_by_name_1_svc(*argp, result, rqstp)); } int _idmap_get_prop_1( idmap_prop_type *argp, idmap_prop_res *result, struct svc_req *rqstp) { return (idmap_get_prop_1_svc(*argp, result, rqstp)); } int _directory_get_common_1( directory_get_common_1_argument *argp, directory_results_rpc *result, struct svc_req *rqstp) { return (directory_get_common_1_svc( argp->ids, argp->types, argp->attrs, result, rqstp)); } int _idmap_flush_1( idmap_flush_op *argp, idmap_retcode *result, struct svc_req *rqstp) { return (idmap_flush_1_svc(*argp, result, rqstp)); } void idmap_prog_1(struct svc_req *rqstp, register SVCXPRT *transp) { union { idmap_mapping_batch idmap_get_mapped_ids_1_arg; idmap_list_mappings_1_argument idmap_list_mappings_1_arg; idmap_list_namerules_1_argument idmap_list_namerules_1_arg; idmap_update_batch idmap_update_1_arg; idmap_mapping idmap_get_mapped_id_by_name_1_arg; idmap_prop_type idmap_get_prop_1_arg; directory_get_common_1_argument directory_get_common_1_arg; idmap_flush_op idmap_flush_1_arg; } argument; union { idmap_ids_res idmap_get_mapped_ids_1_res; idmap_mappings_res idmap_list_mappings_1_res; idmap_namerules_res idmap_list_namerules_1_res; idmap_update_res idmap_update_1_res; idmap_mappings_res idmap_get_mapped_id_by_name_1_res; idmap_prop_res idmap_get_prop_1_res; directory_results_rpc directory_get_common_1_res; idmap_retcode idmap_flush_1_res; } result; bool_t retval; xdrproc_t _xdr_argument, _xdr_result; bool_t (*local)(char *, void *, struct svc_req *); (void) mutex_lock(&_svcstate_lock); _rpcsvccount++; (void) mutex_unlock(&_svcstate_lock); switch (rqstp->rq_proc) { case IDMAP_NULL: _xdr_argument = (xdrproc_t) xdr_void; _xdr_result = (xdrproc_t) xdr_void; local = (bool_t (*) (char *, void *, struct svc_req *)) _idmap_null_1; break; case IDMAP_GET_MAPPED_IDS: _xdr_argument = (xdrproc_t) xdr_idmap_mapping_batch; _xdr_result = (xdrproc_t) xdr_idmap_ids_res; local = (bool_t (*) (char *, void *, struct svc_req *)) _idmap_get_mapped_ids_1; break; case IDMAP_LIST_MAPPINGS: _xdr_argument = (xdrproc_t) xdr_idmap_list_mappings_1_argument; _xdr_result = (xdrproc_t) xdr_idmap_mappings_res; local = (bool_t (*) (char *, void *, struct svc_req *)) _idmap_list_mappings_1; break; case IDMAP_LIST_NAMERULES: _xdr_argument = (xdrproc_t) xdr_idmap_list_namerules_1_argument; _xdr_result = (xdrproc_t) xdr_idmap_namerules_res; local = (bool_t (*) (char *, void *, struct svc_req *)) _idmap_list_namerules_1; break; case IDMAP_UPDATE: _xdr_argument = (xdrproc_t) xdr_idmap_update_batch; _xdr_result = (xdrproc_t) xdr_idmap_update_res; local = (bool_t (*) (char *, void *, struct svc_req *)) _idmap_update_1; break; case IDMAP_GET_MAPPED_ID_BY_NAME: _xdr_argument = (xdrproc_t) xdr_idmap_mapping; _xdr_result = (xdrproc_t) xdr_idmap_mappings_res; local = (bool_t (*) (char *, void *, struct svc_req *)) _idmap_get_mapped_id_by_name_1; break; case IDMAP_GET_PROP: _xdr_argument = (xdrproc_t) xdr_idmap_prop_type; _xdr_result = (xdrproc_t) xdr_idmap_prop_res; local = (bool_t (*) (char *, void *, struct svc_req *)) _idmap_get_prop_1; break; case DIRECTORY_GET_COMMON: _xdr_argument = (xdrproc_t) xdr_directory_get_common_1_argument; _xdr_result = (xdrproc_t) xdr_directory_results_rpc; local = (bool_t (*) (char *, void *, struct svc_req *)) _directory_get_common_1; break; case IDMAP_FLUSH: _xdr_argument = (xdrproc_t) xdr_idmap_flush_op; _xdr_result = (xdrproc_t) xdr_idmap_retcode; local = (bool_t (*) (char *, void *, struct svc_req *)) _idmap_flush_1; break; default: svcerr_noproc(transp); (void) mutex_lock(&_svcstate_lock); _rpcsvccount--; _rpcsvcstate = _SERVED; (void) mutex_unlock(&_svcstate_lock); return; /* CSTYLED */ } (void) memset((char *)&argument, 0, sizeof (argument)); if (!svc_getargs(transp, _xdr_argument, (caddr_t)&argument)) { svcerr_decode(transp); (void) mutex_lock(&_svcstate_lock); _rpcsvccount--; _rpcsvcstate = _SERVED; (void) mutex_unlock(&_svcstate_lock); return; /* CSTYLED */ } retval = (bool_t)(*local)((char *)&argument, (void *)&result, rqstp); if (_xdr_result && retval > 0 && !svc_sendreply(transp, _xdr_result, (char *)&result)) { svcerr_systemerr(transp); } if (!svc_freeargs(transp, _xdr_argument, (caddr_t)&argument)) { RPC_MSGOUT("%s", "unable to free arguments"); exit(1); } if (_xdr_result != NULL) { if (!idmap_prog_1_freeresult(transp, _xdr_result, (caddr_t)&result)) RPC_MSGOUT("%s", "unable to free results"); } (void) mutex_lock(&_svcstate_lock); _rpcsvccount--; _rpcsvcstate = _SERVED; (void) mutex_unlock(&_svcstate_lock); return; /* CSTYLED */ } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SCHEMA_H #define _SCHEMA_H #ifdef __cplusplus extern "C" { #endif /* * Various macros (constant strings) containing: * * - CREATE TABLE/INDEX/TRIGGER/VIEW SQL * - old versions of schema items that have changed * - SQL to detect the version currently installed in a db * - SQL to upgrade the schema from any older version to the current * - the SQL to install the current version of the schema on a * freshly created db is the SQL used to "upgrade" from "version 0" * * There is one set of such macros for the cache DB (CACHE_*) and * another set for the persistent DB (DB_*). The macros ending in _SQL * are used in arguments to init_db_instance(). * * Schema version detection SQL has the following form: * * SELECT CASE (SELECT count(*) FROM sqlite_master) WHEN 0 THEN 0 ELSE * (CASE (SELECT count(*) FROM sqlite_master WHERE sql = ...) * WHEN THEN 1 ELSE (CASE () WHEN ... THEN 2 * ELSE -1 END) END AS version; * * That is, check that there is no schema else that the current schema * sql matches the original schema, else the next version, ... and * return an integer identifying the schema. Version numbers returned: * * -1 -> unknown schema (shouldn't happen) * 0 -> no schema (brand new DB, install latest schema) * 1 -> original schema (if != latest, then upgrade) * . -> ... (if != latest, then upgrade) * n -> latest schema (nothing to do) * * Upgrade SQL for the cache DB is simple: drop everything, create * latest schema. This means losing ephemeral mappings, so idmapd must * tell the kernel about that in its registration call. * * Upgrade SQL for the persistent DB is simple: drop the indexes, create * temporary tables with the latest schema, insert into those from the * old tables (transforming the data in the process), then drop the old * tables, create the latest schema, restore the data from the temp. * tables and drop the temp tables. * * Complex, but it avoids all sorts of packaging install/upgrade * complexity, requiring reboots on patch. * * Conventions: * - each TABLE/INDEX gets its own macro, and the SQL therein must not * end in a semi-colon (';) * - macros are named * TABLE_* for tables, INDEX_* for indexes, * *_VERSION_SQL for SQL for determining version number, * *_UPGRADE_FROM_v_SQL for SQL for upgrading from some * schema, *_LATEST_SQL for SQL for installing the latest schema. * - some macros nest expansions of other macros * * The latest schema has two columns for Windows user/group name in * tables where there used to be one. One of those columns contains the * name as it came from the user or from AD, the other is set via a * TRIGGER to be the lower-case version of the first, and we always * search (and index) by the latter. This is for case-insensitivity. */ #define TABLE_IDMAP_CACHE_v1 \ "CREATE TABLE idmap_cache (" \ " sidprefix TEXT," \ " rid INTEGER," \ " windomain TEXT," \ " winname TEXT," \ " pid INTEGER," \ " unixname TEXT," \ " is_user INTEGER," \ " w2u INTEGER," \ " u2w INTEGER," \ " expiration INTEGER" \ ")" #define TABLE_IDMAP_CACHE_v2 \ "CREATE TABLE idmap_cache " \ "(" \ " sidprefix TEXT," \ " rid INTEGER," \ " windomain TEXT," \ " canon_winname TEXT," \ " winname TEXT," \ " pid INTEGER," \ " unixname TEXT," \ " is_user INTEGER," \ " is_wuser INTEGER," \ " w2u INTEGER," \ " u2w INTEGER," \ " expiration INTEGER" \ ")" #define TABLE_IDMAP_CACHE \ "CREATE TABLE idmap_cache " \ "(" \ " sidprefix TEXT," \ " rid INTEGER," \ " windomain TEXT," \ " canon_winname TEXT," \ " winname TEXT," \ " pid INTEGER," \ " unixname TEXT," \ " is_user INTEGER," \ " is_wuser INTEGER," \ " w2u INTEGER," \ " u2w INTEGER," \ " map_type INTEGER," \ " map_dn TEXT, "\ " map_attr TEXT, "\ " map_value TEXT, "\ " map_windomain TEXT, "\ " map_winname TEXT, "\ " map_unixname TEXT, "\ " map_is_nt4 INTEGER, "\ " expiration INTEGER" \ ")" #define INDEX_IDMAP_CACHE_SID_W2U_v1 \ "CREATE UNIQUE INDEX idmap_cache_sid_w2u ON idmap_cache" \ " (sidprefix, rid, w2u)" #define INDEX_IDMAP_CACHE_SID_W2U \ "CREATE UNIQUE INDEX idmap_cache_sid_w2u ON idmap_cache" \ " (sidprefix, rid, is_user, w2u)" #define INDEX_IDMAP_CACHE_PID_U2W \ "CREATE UNIQUE INDEX idmap_cache_pid_u2w ON idmap_cache" \ " (pid, is_user, u2w)" #define TRIGGER_IDMAP_CACHE_TOLOWER_INSERT \ "CREATE TRIGGER idmap_cache_tolower_name_insert " \ "AFTER INSERT ON idmap_cache " \ "BEGIN " \ " UPDATE idmap_cache SET winname = lower_utf8(canon_winname)" \ " WHERE rowid = new.rowid;" \ "END" #define TRIGGER_IDMAP_CACHE_TOLOWER_UPDATE \ "CREATE TRIGGER idmap_cache_tolower_name_update " \ "AFTER UPDATE ON idmap_cache " \ "BEGIN " \ " UPDATE idmap_cache SET winname = lower_utf8(canon_winname)" \ " WHERE rowid = new.rowid;" \ "END" #define TABLE_NAME_CACHE \ "CREATE TABLE name_cache (" \ " sidprefix TEXT," \ " rid INTEGER," \ " name TEXT," \ " canon_name TEXT," \ " domain TEXT," \ " type INTEGER," \ " expiration INTEGER" \ ")" #define TABLE_NAME_CACHE_v1 \ "CREATE TABLE name_cache (" \ " sidprefix TEXT," \ " rid INTEGER," \ " name TEXT," \ " domain TEXT," \ " type INTEGER," \ " expiration INTEGER" \ ")" #define TRIGGER_NAME_CACHE_TOLOWER_INSERT \ "CREATE TRIGGER name_cache_tolower_name_insert " \ "AFTER INSERT ON name_cache " \ "BEGIN " \ " UPDATE name_cache SET name = lower_utf8(canon_name)" \ " WHERE rowid = new.rowid;" \ "END" #define TRIGGER_NAME_CACHE_TOLOWER_UPDATE \ "CREATE TRIGGER name_cache_tolower_name_update " \ "AFTER UPDATE ON name_cache " \ "BEGIN " \ " UPDATE name_cache SET name = lower_utf8(canon_name)" \ " WHERE rowid = new.rowid;" \ "END" #define INDEX_NAME_CACHE_SID \ "CREATE UNIQUE INDEX name_cache_sid ON name_cache" \ " (sidprefix, rid)" #define INDEX_NAME_CACHE_NAME \ "CREATE UNIQUE INDEX name_cache_name ON name_cache" \ " (name, domain)" #define CACHE_INSTALL_SQL \ TABLE_IDMAP_CACHE ";" \ INDEX_IDMAP_CACHE_SID_W2U ";" \ INDEX_IDMAP_CACHE_PID_U2W ";" \ TRIGGER_IDMAP_CACHE_TOLOWER_INSERT ";" \ TRIGGER_IDMAP_CACHE_TOLOWER_UPDATE ";" \ TABLE_NAME_CACHE ";" \ INDEX_NAME_CACHE_SID ";" \ INDEX_NAME_CACHE_NAME ";" \ TRIGGER_NAME_CACHE_TOLOWER_INSERT ";" \ TRIGGER_NAME_CACHE_TOLOWER_UPDATE ";" #define CACHE_VERSION_SQL \ "SELECT CASE (SELECT count(*) FROM sqlite_master) WHEN 0 THEN 0 ELSE " \ "(CASE (SELECT count(*) FROM sqlite_master WHERE " \ "sql = '" TABLE_IDMAP_CACHE_v1 "' OR " \ "sql = '" INDEX_IDMAP_CACHE_SID_W2U_v1 "' OR " \ "sql = '" INDEX_IDMAP_CACHE_PID_U2W "' OR " \ "sql = '" TABLE_NAME_CACHE_v1 "' OR " \ "sql = '" INDEX_NAME_CACHE_SID "') " \ "WHEN 5 THEN 1 ELSE " \ "(CASE (SELECT count(*) FROM sqlite_master WHERE " \ "sql = '" TABLE_IDMAP_CACHE_v2"' OR " \ "sql = '" INDEX_IDMAP_CACHE_SID_W2U "' OR " \ "sql = '" INDEX_IDMAP_CACHE_PID_U2W "' OR " \ "sql = '" TRIGGER_IDMAP_CACHE_TOLOWER_INSERT "' OR " \ "sql = '" TRIGGER_IDMAP_CACHE_TOLOWER_UPDATE "' OR " \ "sql = '" TABLE_NAME_CACHE "' OR " \ "sql = '" INDEX_NAME_CACHE_SID "' OR " \ "sql = '" INDEX_NAME_CACHE_NAME "' OR " \ "sql = '" TRIGGER_NAME_CACHE_TOLOWER_INSERT "' OR " \ "sql = '" TRIGGER_NAME_CACHE_TOLOWER_UPDATE "') " \ "WHEN 10 THEN 2 ELSE " \ "(CASE (SELECT count(*) FROM sqlite_master WHERE " \ "sql = '" TABLE_IDMAP_CACHE"' OR " \ "sql = '" INDEX_IDMAP_CACHE_SID_W2U "' OR " \ "sql = '" INDEX_IDMAP_CACHE_PID_U2W "' OR " \ "sql = '" TRIGGER_IDMAP_CACHE_TOLOWER_INSERT "' OR " \ "sql = '" TRIGGER_IDMAP_CACHE_TOLOWER_UPDATE "' OR " \ "sql = '" TABLE_NAME_CACHE "' OR " \ "sql = '" INDEX_NAME_CACHE_SID "' OR " \ "sql = '" INDEX_NAME_CACHE_NAME "' OR " \ "sql = '" TRIGGER_NAME_CACHE_TOLOWER_INSERT "' OR " \ "sql = '" TRIGGER_NAME_CACHE_TOLOWER_UPDATE "') " \ "WHEN 10 THEN 3 ELSE -1 END) END) END) END AS version;" #define CACHE_UPGRADE_FROM_v1_SQL \ "DROP TABLE idmap_cache;" \ "DROP TABLE name_cache;" \ CACHE_INSTALL_SQL #define CACHE_UPGRADE_FROM_v2_SQL \ "DROP TABLE idmap_cache;" \ "DROP TABLE name_cache;" \ CACHE_INSTALL_SQL #define CACHE_VERSION 3 #define TABLE_NAMERULES_v1 \ "CREATE TABLE namerules (" \ " is_user INTEGER NOT NULL," \ " windomain TEXT," \ " winname TEXT NOT NULL," \ " is_nt4 INTEGER NOT NULL," \ " unixname NOT NULL," \ " w2u_order INTEGER," \ " u2w_order INTEGER" \ ")" #define TABLE_NAMERULES_BODY \ "(" \ " is_user INTEGER NOT NULL," \ " is_wuser INTEGER NOT NULL," \ " windomain TEXT," \ " winname_display TEXT NOT NULL," \ " winname TEXT," \ " is_nt4 INTEGER NOT NULL," \ " unixname NOT NULL," \ " w2u_order INTEGER," \ " u2w_order INTEGER" \ ")" #define TABLE_NAMERULES \ "CREATE TABLE namerules " \ TABLE_NAMERULES_BODY #define INDEX_NAMERULES_W2U_v1 \ "CREATE UNIQUE INDEX namerules_w2u ON namerules" \ " (winname, windomain, is_user, w2u_order)" #define INDEX_NAMERULES_W2U \ "CREATE UNIQUE INDEX namerules_w2u ON namerules" \ " (winname, windomain, is_user, is_wuser, w2u_order)" #define INDEX_NAMERULES_U2W \ "CREATE UNIQUE INDEX namerules_u2w ON namerules" \ " (unixname, is_user, u2w_order)" #define TRIGGER_NAMERULES_TOLOWER_BODY \ "BEGIN " \ " UPDATE namerules SET winname = lower_utf8(winname_display)" \ " WHERE rowid = new.rowid;" \ "END" #define TRIGGER_NAMERULES_TOLOWER_INSERT \ "CREATE TRIGGER namerules_tolower_name_insert " \ "AFTER INSERT ON namerules " \ TRIGGER_NAMERULES_TOLOWER_BODY #define TRIGGER_NAMERULES_TOLOWER_UPDATE \ "CREATE TRIGGER namerules_tolower_name_update " \ "AFTER UPDATE ON namerules " \ TRIGGER_NAMERULES_TOLOWER_BODY #define TRIGGER_NAMERULES_UNIQUE_BODY \ " SELECT CASE (SELECT count(*) FROM namerules AS n" \ " WHERE n.unixname = NEW.unixname AND" \ " n.is_user = NEW.is_user AND" \ " (n.winname != lower(NEW.winname_display) OR" \ " n.windomain != NEW.windomain ) AND" \ " n.u2w_order = NEW.u2w_order AND" \ " n.is_wuser != NEW.is_wuser) > 0" \ " WHEN 1 THEN" \ " raise(ROLLBACK, 'Conflicting w2u namerules')"\ " END; " \ "END" #define TRIGGER_NAMERULES_UNIQUE_INSERT \ "CREATE TRIGGER namerules_unique_insert " \ "BEFORE INSERT ON namerules " \ "BEGIN " \ TRIGGER_NAMERULES_UNIQUE_BODY #define TRIGGER_NAMERULES_UNIQUE_UPDATE \ "CREATE TRIGGER namerules_unique_update " \ "BEFORE INSERT ON namerules " \ "BEGIN " \ TRIGGER_NAMERULES_UNIQUE_BODY #define DB_INSTALL_SQL \ TABLE_NAMERULES ";" \ INDEX_NAMERULES_W2U ";" \ INDEX_NAMERULES_U2W ";" \ TRIGGER_NAMERULES_TOLOWER_INSERT ";" \ TRIGGER_NAMERULES_TOLOWER_UPDATE ";" \ TRIGGER_NAMERULES_UNIQUE_INSERT ";" \ TRIGGER_NAMERULES_UNIQUE_UPDATE ";" #define DB_VERSION_SQL \ "SELECT CASE (SELECT count(*) FROM sqlite_master) WHEN 0 THEN 0 ELSE " \ "(CASE (SELECT count(*) FROM sqlite_master WHERE " \ "sql = '" TABLE_NAMERULES_v1 "' OR " \ "sql = '" INDEX_NAMERULES_W2U_v1 "' OR " \ "sql = '" INDEX_NAMERULES_U2W "') " \ "WHEN 3 THEN 1 ELSE "\ "(CASE (SELECT count(*) FROM sqlite_master WHERE " \ "sql = '" TABLE_NAMERULES "' OR " \ "sql = '" INDEX_NAMERULES_W2U "' OR " \ "sql = '" INDEX_NAMERULES_U2W "' OR " \ "sql = '" TRIGGER_NAMERULES_TOLOWER_INSERT "' OR " \ "sql = '" TRIGGER_NAMERULES_TOLOWER_UPDATE "' OR " \ "sql = \"" TRIGGER_NAMERULES_UNIQUE_INSERT "\" OR " \ "sql = \"" TRIGGER_NAMERULES_UNIQUE_UPDATE "\") " \ "WHEN 7 THEN 2 ELSE -1 END) END) END AS version;" /* SQL for upgrading an existing name rules DB. Includes DB_INSTALL_SQL */ #define DB_UPGRADE_FROM_v1_SQL \ "CREATE TABLE namerules_new " TABLE_NAMERULES_BODY ";" \ "INSERT INTO namerules_new SELECT is_user, is_user, windomain, " \ "winname, winname, is_nt4, unixname, w2u_order, u2w_order " \ "FROM namerules;" \ "DROP TABLE namerules;" \ DB_INSTALL_SQL \ "INSERT INTO namerules SELECT * FROM namerules_new;" \ "DROP TABLE namerules_new;" #define DB_VERSION 2 #ifdef __cplusplus } #endif #endif /* _SCHEMA_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2022 RackTop Systems, Inc. */ /* * Service routines */ #include "idmapd.h" #include "idmap_priv.h" #include "nldaputils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _VALIDATE_LIST_CB_DATA(col, val, siz)\ retcode = validate_list_cb_data(cb_data, argc, argv, col,\ (uchar_t **)val, siz);\ if (retcode == IDMAP_NEXT) {\ result->retcode = IDMAP_NEXT;\ return (0);\ } else if (retcode < 0) {\ result->retcode = retcode;\ return (1);\ } #define PROCESS_LIST_SVC_SQL(rcode, db, dbname, sql, limit, flag, cb, res, len)\ rcode = process_list_svc_sql(db, dbname, sql, limit, flag, cb, res);\ if (rcode == IDMAP_ERR_BUSY)\ res->retcode = IDMAP_ERR_BUSY;\ else if (rcode == IDMAP_SUCCESS && len == 0)\ res->retcode = IDMAP_ERR_NOTFOUND; #define STRDUP_OR_FAIL(to, from) \ if ((from) == NULL) \ to = NULL; \ else { \ if ((to = strdup(from)) == NULL) \ return (1); \ } #define STRDUP_CHECK(to, from) \ if ((from) != NULL) { \ to = strdup(from); \ if (to == NULL) { \ result->retcode = IDMAP_ERR_MEMORY; \ goto out; \ } \ } /* ARGSUSED */ bool_t idmap_null_1_svc(void *result, struct svc_req *rqstp) { return (TRUE); } /* * RPC layer allocates empty strings to replace NULL char *. * This utility function frees these empty strings. */ static void sanitize_mapping_request(idmap_mapping *req) { if (EMPTY_STRING(req->id1name)) { free(req->id1name); req->id1name = NULL; } if (EMPTY_STRING(req->id1domain)) { free(req->id1domain); req->id1domain = NULL; } if (EMPTY_STRING(req->id2name)) { free(req->id2name); req->id2name = NULL; } if (EMPTY_STRING(req->id2domain)) { free(req->id2domain); req->id2domain = NULL; } req->direction = _IDMAP_F_DONE; } static int validate_mapped_id_by_name_req(idmap_mapping *req) { int e; if (IS_ID_UID(req->id1) || IS_ID_GID(req->id1)) return (IDMAP_SUCCESS); if (IS_ID_SID(req->id1)) { if (!EMPTY_STRING(req->id1name) && u8_validate(req->id1name, strlen(req->id1name), NULL, U8_VALIDATE_ENTIRE, &e) < 0) return (IDMAP_ERR_BAD_UTF8); if (!EMPTY_STRING(req->id1domain) && u8_validate(req->id1domain, strlen(req->id1domain), NULL, U8_VALIDATE_ENTIRE, &e) < 0) return (IDMAP_ERR_BAD_UTF8); } return (IDMAP_SUCCESS); } static int validate_rule(idmap_namerule *rule) { int e; if (!EMPTY_STRING(rule->winname) && u8_validate(rule->winname, strlen(rule->winname), NULL, U8_VALIDATE_ENTIRE, &e) < 0) return (IDMAP_ERR_BAD_UTF8); if (!EMPTY_STRING(rule->windomain) && u8_validate(rule->windomain, strlen(rule->windomain), NULL, U8_VALIDATE_ENTIRE, &e) < 0) return (IDMAP_ERR_BAD_UTF8); return (IDMAP_SUCCESS); } static bool_t validate_rules(idmap_update_batch *batch) { idmap_update_op *up; int i; for (i = 0; i < batch->idmap_update_batch_len; i++) { up = &(batch->idmap_update_batch_val[i]); if (validate_rule(&(up->idmap_update_op_u.rule)) != IDMAP_SUCCESS) return (IDMAP_ERR_BAD_UTF8); } return (IDMAP_SUCCESS); } /* ARGSUSED */ bool_t idmap_get_mapped_ids_1_svc(idmap_mapping_batch batch, idmap_ids_res *result, struct svc_req *rqstp) { sqlite *cache = NULL, *db = NULL; lookup_state_t state; idmap_retcode retcode; uint_t i; idmap_mapping *req; idmap_id_res *res; boolean_t any_tracing; int rc; /* Init */ (void) memset(result, 0, sizeof (*result)); (void) memset(&state, 0, sizeof (state)); /* Return success if nothing was requested */ if (batch.idmap_mapping_batch_len < 1) goto out; /* Get cache handle */ result->retcode = get_cache_handle(&cache); if (result->retcode != IDMAP_SUCCESS) goto out; state.cache = cache; /* Get db handle */ result->retcode = get_db_handle(&db); if (result->retcode != IDMAP_SUCCESS) goto out; state.db = db; /* Allocate result array */ result->ids.ids_val = calloc(batch.idmap_mapping_batch_len, sizeof (idmap_id_res)); if (result->ids.ids_val == NULL) { idmapdlog(LOG_ERR, "Out of memory"); result->retcode = IDMAP_ERR_MEMORY; goto out; } result->ids.ids_len = batch.idmap_mapping_batch_len; /* Allocate hash table to check for duplicate sids */ state.sid_history = calloc(batch.idmap_mapping_batch_len, sizeof (*state.sid_history)); if (state.sid_history == NULL) { idmapdlog(LOG_ERR, "Out of memory"); result->retcode = IDMAP_ERR_MEMORY; goto out; } state.sid_history_size = batch.idmap_mapping_batch_len; for (i = 0; i < state.sid_history_size; i++) { state.sid_history[i].key = state.sid_history_size; state.sid_history[i].next = state.sid_history_size; } state.batch = &batch; state.result = result; /* Get directory-based name mapping info */ result->retcode = load_cfg_in_state(&state); if (result->retcode != IDMAP_SUCCESS) goto out; /* Init our 'done' flags */ state.sid2pid_done = state.pid2sid_done = TRUE; any_tracing = B_FALSE; /* First stage */ for (i = 0; i < batch.idmap_mapping_batch_len; i++) { req = &batch.idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; if (TRACING(req)) any_tracing = B_TRUE; state.curpos = i; (void) sanitize_mapping_request(req); TRACE(req, res, "Start mapping"); if (IS_ID_SID(req->id1)) { retcode = sid2pid_first_pass( &state, req, res); } else if (IS_ID_UID(req->id1)) { retcode = pid2sid_first_pass( &state, req, res, 1); } else if (IS_ID_GID(req->id1)) { retcode = pid2sid_first_pass( &state, req, res, 0); } else { res->retcode = IDMAP_ERR_IDTYPE; continue; } if (IDMAP_FATAL_ERROR(retcode)) { result->retcode = retcode; goto out; } } /* Check if we are done */ if (state.sid2pid_done == TRUE && state.pid2sid_done == TRUE) goto out; /* * native LDAP lookups: * pid2sid: * - nldap or mixed mode. Lookup nldap by pid or unixname to get * winname. * sid2pid: * - nldap mode. Got winname and sid (either given or found in * name_cache). Lookup nldap by winname to get pid and * unixname. */ if (state.nldap_nqueries) { retcode = nldap_lookup_batch(&state, &batch, result); if (IDMAP_FATAL_ERROR(retcode)) { TRACE(req, res, "Native LDAP lookup error=%d", retcode); result->retcode = retcode; goto out; } if (any_tracing) { for (i = 0; i < batch.idmap_mapping_batch_len; i++) { res = &result->ids.ids_val[i]; req = &batch.idmap_mapping_batch_val[i]; if (IDMAP_ERROR(res->retcode)) { TRACE(req, res, "Native LDAP lookup error=%d", res->retcode); } else { TRACE(req, res, "Native LDAP lookup"); } } } } /* * AD lookups: * pid2sid: * - nldap or mixed mode. Got winname from nldap lookup. * winname2sid could not be resolved locally. Lookup AD * by winname to get sid. * - ad mode. Got unixname. Lookup AD by unixname to get * winname and sid. * sid2pid: * - ad or mixed mode. Lookup AD by sid or winname to get * winname, sid and unixname. * - any mode. Got either sid or winname but not both. Lookup * AD by sid or winname to get winname, sid. */ if (state.ad_nqueries) { retcode = ad_lookup_batch(&state, &batch, result); if (IDMAP_FATAL_ERROR(retcode)) { TRACE(req, res, "AD lookup error=%d", retcode); result->retcode = retcode; goto out; } for (i = 0; i < batch.idmap_mapping_batch_len; i++) { res = &result->ids.ids_val[i]; req = &batch.idmap_mapping_batch_val[i]; if (res->retcode == IDMAP_ERR_DOMAIN_NOTFOUND && req->id1.idmap_id_u.sid.prefix != NULL && req->id1name != NULL) { /* * If AD lookup failed Domain Not Found but * we have a winname and SID, it means that * - LSA succeeded * - it's a request a cross-forest trust * and * - we were looking for directory-based * mapping information. * In that case, we're OK, just go on. * * If this seems more convoluted than it * should be, it is - really, we probably * shouldn't even be attempting AD lookups * in this situation, but that's a more * intricate cleanup that will have to wait * for later. */ res->retcode = IDMAP_SUCCESS; TRACE(req, res, "AD lookup - domain not found (ignored)"); continue; } } } /* * native LDAP lookups: * sid2pid: * - nldap mode. Got winname and sid from AD lookup. Lookup nldap * by winname to get pid and unixname. */ if (state.nldap_nqueries) { retcode = nldap_lookup_batch(&state, &batch, result); if (IDMAP_FATAL_ERROR(retcode)) { TRACE(req, res, "Native LDAP lookup error=%d", retcode); result->retcode = retcode; goto out; } if (any_tracing) { for (i = 0; i < batch.idmap_mapping_batch_len; i++) { res = &result->ids.ids_val[i]; req = &batch.idmap_mapping_batch_val[i]; TRACE(req, res, "Native LDAP lookup"); } } } /* Reset 'done' flags */ state.sid2pid_done = state.pid2sid_done = TRUE; /* Second stage */ for (i = 0; i < batch.idmap_mapping_batch_len; i++) { req = &batch.idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; state.curpos = i; if (IS_ID_SID(req->id1)) { retcode = sid2pid_second_pass( &state, req, res); } else if (IS_ID_UID(req->id1)) { retcode = pid2sid_second_pass( &state, req, res, 1); } else if (IS_ID_GID(req->id1)) { retcode = pid2sid_second_pass( &state, req, res, 0); } else { /* First stage has already set the error */ continue; } if (IDMAP_FATAL_ERROR(retcode)) { result->retcode = retcode; goto out; } } /* Check if we are done */ if (state.sid2pid_done == TRUE && state.pid2sid_done == TRUE) goto out; /* Reset our 'done' flags */ state.sid2pid_done = state.pid2sid_done = TRUE; /* Update cache in a single transaction */ rc = sql_exec_no_cb(cache, IDMAP_CACHENAME, "BEGIN TRANSACTION;"); if (rc != IDMAP_SUCCESS) goto out; for (i = 0; i < batch.idmap_mapping_batch_len; i++) { req = &batch.idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; state.curpos = i; if (IS_ID_SID(req->id1)) { (void) update_cache_sid2pid( &state, req, res); } else if ((IS_ID_UID(req->id1)) || (IS_ID_GID(req->id1))) { (void) update_cache_pid2sid( &state, req, res); } } /* Commit if we have at least one successful update */ if (state.sid2pid_done == FALSE || state.pid2sid_done == FALSE) rc = sql_exec_no_cb(cache, IDMAP_CACHENAME, "COMMIT TRANSACTION;"); else rc = sql_exec_no_cb(cache, IDMAP_CACHENAME, "END TRANSACTION;"); if (rc != IDMAP_SUCCESS) { /* If (eg.) commit fails, make sure to close the TX. */ (void) sql_exec_no_cb(cache, IDMAP_CACHENAME, "ROLLBACK TRANSACTION;"); } out: if (rc == IDMAP_ERR_DB || result->retcode == IDMAP_ERR_DB) kill_cache_handle(cache); cleanup_lookup_state(&state); if (IDMAP_ERROR(result->retcode)) { if (any_tracing) { for (i = 0; i < batch.idmap_mapping_batch_len; i++) { req = &batch.idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; TRACE(req, res, "Failure code %d", result->retcode); } } xdr_free(xdr_idmap_ids_res, (caddr_t)result); result->ids.ids_len = 0; result->ids.ids_val = NULL; } else { if (any_tracing) { for (i = 0; i < batch.idmap_mapping_batch_len; i++) { req = &batch.idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; TRACE(req, res, "Done"); } } } result->retcode = idmap_stat4prot(result->retcode); for (i = 0; i < result->ids.ids_len; i++) { req = &batch.idmap_mapping_batch_val[i]; res = &result->ids.ids_val[i]; if (!(req->flag & IDMAP_REQ_FLG_MAPPING_INFO) && res->retcode == IDMAP_SUCCESS) idmap_how_clear(&res->info.how); } return (TRUE); } /* ARGSUSED */ static int list_mappings_cb(void *parg, int argc, char **argv, char **colnames) { list_cb_data_t *cb_data; char *str; idmap_mappings_res *result; idmap_retcode retcode; int w2u, u2w; char *end; static int validated_column_names = 0; idmap_how *how; cb_data = (list_cb_data_t *)parg; if (!validated_column_names) { assert(strcmp(colnames[0], "rowid") == 0); assert(strcmp(colnames[1], "sidprefix") == 0); assert(strcmp(colnames[2], "rid") == 0); assert(strcmp(colnames[3], "pid") == 0); assert(strcmp(colnames[4], "w2u") == 0); assert(strcmp(colnames[5], "u2w") == 0); assert(strcmp(colnames[6], "windomain") == 0); assert(strcmp(colnames[7], "canon_winname") == 0); assert(strcmp(colnames[8], "unixname") == 0); assert(strcmp(colnames[9], "is_user") == 0); assert(strcmp(colnames[10], "is_wuser") == 0); assert(strcmp(colnames[11], "map_type") == 0); assert(strcmp(colnames[12], "map_dn") == 0); assert(strcmp(colnames[13], "map_attr") == 0); assert(strcmp(colnames[14], "map_value") == 0); assert(strcmp(colnames[15], "map_windomain") == 0); assert(strcmp(colnames[16], "map_winname") == 0); assert(strcmp(colnames[17], "map_unixname") == 0); assert(strcmp(colnames[18], "map_is_nt4") == 0); validated_column_names = 1; } result = (idmap_mappings_res *)cb_data->result; _VALIDATE_LIST_CB_DATA(19, &result->mappings.mappings_val, sizeof (idmap_mapping)); result->mappings.mappings_len++; if ((str = strdup(argv[1])) == NULL) return (1); result->mappings.mappings_val[cb_data->next].id1.idmap_id_u.sid.prefix = str; result->mappings.mappings_val[cb_data->next].id1.idmap_id_u.sid.rid = strtoul(argv[2], &end, 10); result->mappings.mappings_val[cb_data->next].id1.idtype = strtol(argv[10], &end, 10) ? IDMAP_USID : IDMAP_GSID; result->mappings.mappings_val[cb_data->next].id2.idmap_id_u.uid = strtoul(argv[3], &end, 10); result->mappings.mappings_val[cb_data->next].id2.idtype = strtol(argv[9], &end, 10) ? IDMAP_UID : IDMAP_GID; w2u = argv[4] ? strtol(argv[4], &end, 10) : 0; u2w = argv[5] ? strtol(argv[5], &end, 10) : 0; if (w2u > 0 && u2w == 0) result->mappings.mappings_val[cb_data->next].direction = IDMAP_DIRECTION_W2U; else if (w2u == 0 && u2w > 0) result->mappings.mappings_val[cb_data->next].direction = IDMAP_DIRECTION_U2W; else result->mappings.mappings_val[cb_data->next].direction = IDMAP_DIRECTION_BI; STRDUP_OR_FAIL(result->mappings.mappings_val[cb_data->next].id1domain, argv[6]); STRDUP_OR_FAIL(result->mappings.mappings_val[cb_data->next].id1name, argv[7]); STRDUP_OR_FAIL(result->mappings.mappings_val[cb_data->next].id2name, argv[8]); if (cb_data->flag & IDMAP_REQ_FLG_MAPPING_INFO) { how = &result->mappings.mappings_val[cb_data->next].info.how; how->map_type = strtoul(argv[11], &end, 10); switch (how->map_type) { case IDMAP_MAP_TYPE_DS_AD: how->idmap_how_u.ad.dn = strdup(argv[12]); how->idmap_how_u.ad.attr = strdup(argv[13]); how->idmap_how_u.ad.value = strdup(argv[14]); break; case IDMAP_MAP_TYPE_DS_NLDAP: how->idmap_how_u.nldap.dn = strdup(argv[12]); how->idmap_how_u.nldap.attr = strdup(argv[13]); how->idmap_how_u.nldap.value = strdup(argv[14]); break; case IDMAP_MAP_TYPE_RULE_BASED: how->idmap_how_u.rule.windomain = strdup(argv[15]); how->idmap_how_u.rule.winname = strdup(argv[16]); how->idmap_how_u.rule.unixname = strdup(argv[17]); how->idmap_how_u.rule.is_nt4 = strtoul(argv[18], &end, 10); how->idmap_how_u.rule.is_user = strtol(argv[9], &end, 10); how->idmap_how_u.rule.is_wuser = strtol(argv[10], &end, 10); break; case IDMAP_MAP_TYPE_EPHEMERAL: break; case IDMAP_MAP_TYPE_LOCAL_SID: break; case IDMAP_MAP_TYPE_IDMU: how->idmap_how_u.idmu.dn = strdup(argv[12]); how->idmap_how_u.idmu.attr = strdup(argv[13]); how->idmap_how_u.idmu.value = strdup(argv[14]); break; default: /* Unknown mapping type */ assert(FALSE); } } result->lastrowid = strtoll(argv[0], &end, 10); cb_data->next++; result->retcode = IDMAP_SUCCESS; return (0); } /* ARGSUSED */ bool_t idmap_list_mappings_1_svc(int64_t lastrowid, uint64_t limit, int32_t flag, idmap_mappings_res *result, struct svc_req *rqstp) { sqlite *cache = NULL; char lbuf[30], rbuf[30]; uint64_t maxlimit; idmap_retcode retcode; char *sql = NULL; time_t curtime; (void) memset(result, 0, sizeof (*result)); /* Current time */ errno = 0; if ((curtime = time(NULL)) == (time_t)-1) { idmapdlog(LOG_ERR, "Failed to get current time (%s)", strerror(errno)); retcode = IDMAP_ERR_INTERNAL; goto out; } RDLOCK_CONFIG(); maxlimit = _idmapdstate.cfg->pgcfg.list_size_limit; UNLOCK_CONFIG(); /* Get cache handle */ result->retcode = get_cache_handle(&cache); if (result->retcode != IDMAP_SUCCESS) goto out; result->retcode = IDMAP_ERR_INTERNAL; /* Create LIMIT expression. */ if (limit == 0 || (maxlimit > 0 && maxlimit < limit)) limit = maxlimit; if (limit > 0) (void) snprintf(lbuf, sizeof (lbuf), "LIMIT %" PRIu64, limit + 1ULL); else lbuf[0] = '\0'; (void) snprintf(rbuf, sizeof (rbuf), "rowid > %" PRIu64, lastrowid); /* * Combine all the above into a giant SELECT statement that * will return the requested mappings */ sql = sqlite_mprintf("SELECT rowid, sidprefix, rid, pid, w2u, " "u2w, windomain, canon_winname, unixname, is_user, is_wuser, " "map_type, map_dn, map_attr, map_value, map_windomain, " "map_winname, map_unixname, map_is_nt4 " "FROM idmap_cache WHERE %s AND " "(pid >= 2147483648 OR (expiration = 0 OR " "expiration ISNULL OR expiration > %d)) " "%s;", rbuf, curtime, lbuf); if (sql == NULL) { result->retcode = IDMAP_ERR_MEMORY; idmapdlog(LOG_ERR, "Out of memory"); goto out; } /* Execute the SQL statement and update the return buffer */ PROCESS_LIST_SVC_SQL(retcode, cache, IDMAP_CACHENAME, sql, limit, flag, list_mappings_cb, result, result->mappings.mappings_len); if (retcode == IDMAP_ERR_DB) kill_cache_handle(cache); out: if (sql) sqlite_freemem(sql); if (IDMAP_ERROR(result->retcode)) xdr_free(xdr_idmap_mappings_res, (caddr_t)result); result->retcode = idmap_stat4prot(result->retcode); return (TRUE); } /* ARGSUSED */ static int list_namerules_cb(void *parg, int argc, char **argv, char **colnames) { list_cb_data_t *cb_data; idmap_namerules_res *result; idmap_retcode retcode; int w2u_order, u2w_order; char *end; static int validated_column_names = 0; if (!validated_column_names) { assert(strcmp(colnames[0], "rowid") == 0); assert(strcmp(colnames[1], "is_user") == 0); assert(strcmp(colnames[2], "is_wuser") == 0); assert(strcmp(colnames[3], "windomain") == 0); assert(strcmp(colnames[4], "winname_display") == 0); assert(strcmp(colnames[5], "is_nt4") == 0); assert(strcmp(colnames[6], "unixname") == 0); assert(strcmp(colnames[7], "w2u_order") == 0); assert(strcmp(colnames[8], "u2w_order") == 0); validated_column_names = 1; } cb_data = (list_cb_data_t *)parg; result = (idmap_namerules_res *)cb_data->result; _VALIDATE_LIST_CB_DATA(9, &result->rules.rules_val, sizeof (idmap_namerule)); result->rules.rules_len++; result->rules.rules_val[cb_data->next].is_user = strtol(argv[1], &end, 10); result->rules.rules_val[cb_data->next].is_wuser = strtol(argv[2], &end, 10); STRDUP_OR_FAIL(result->rules.rules_val[cb_data->next].windomain, argv[3]); STRDUP_OR_FAIL(result->rules.rules_val[cb_data->next].winname, argv[4]); result->rules.rules_val[cb_data->next].is_nt4 = strtol(argv[5], &end, 10); STRDUP_OR_FAIL(result->rules.rules_val[cb_data->next].unixname, argv[6]); w2u_order = argv[7] ? strtol(argv[7], &end, 10) : 0; u2w_order = argv[8] ? strtol(argv[8], &end, 10) : 0; if (w2u_order > 0 && u2w_order == 0) result->rules.rules_val[cb_data->next].direction = IDMAP_DIRECTION_W2U; else if (w2u_order == 0 && u2w_order > 0) result->rules.rules_val[cb_data->next].direction = IDMAP_DIRECTION_U2W; else result->rules.rules_val[cb_data->next].direction = IDMAP_DIRECTION_BI; result->lastrowid = strtoll(argv[0], &end, 10); cb_data->next++; result->retcode = IDMAP_SUCCESS; return (0); } /* ARGSUSED */ bool_t idmap_list_namerules_1_svc(idmap_namerule rule, uint64_t lastrowid, uint64_t limit, idmap_namerules_res *result, struct svc_req *rqstp) { sqlite *db = NULL; char lbuf[30], rbuf[30]; char *sql = NULL; char *expr = NULL; uint64_t maxlimit; idmap_retcode retcode; (void) memset(result, 0, sizeof (*result)); result->retcode = validate_rule(&rule); if (result->retcode != IDMAP_SUCCESS) goto out; RDLOCK_CONFIG(); maxlimit = _idmapdstate.cfg->pgcfg.list_size_limit; UNLOCK_CONFIG(); /* Get db handle */ result->retcode = get_db_handle(&db); if (result->retcode != IDMAP_SUCCESS) goto out; result->retcode = gen_sql_expr_from_rule(&rule, &expr); if (result->retcode != IDMAP_SUCCESS) goto out; /* Create LIMIT expression. */ if (limit == 0 || (maxlimit > 0 && maxlimit < limit)) limit = maxlimit; if (limit > 0) (void) snprintf(lbuf, sizeof (lbuf), "LIMIT %" PRIu64, limit + 1ULL); else lbuf[0] = '\0'; (void) snprintf(rbuf, sizeof (rbuf), "rowid > %" PRIu64, lastrowid); /* * Combine all the above into a giant SELECT statement that * will return the requested rules */ sql = sqlite_mprintf("SELECT rowid, is_user, is_wuser, windomain, " "winname_display, is_nt4, unixname, w2u_order, u2w_order " "FROM namerules WHERE " " %s %s %s;", rbuf, expr, lbuf); if (sql == NULL) { result->retcode = IDMAP_ERR_MEMORY; idmapdlog(LOG_ERR, "Out of memory"); goto out; } /* Execute the SQL statement and update the return buffer */ PROCESS_LIST_SVC_SQL(retcode, db, IDMAP_DBNAME, sql, limit, 0, list_namerules_cb, result, result->rules.rules_len); if (retcode == IDMAP_ERR_DB) kill_db_handle(db); out: if (expr) sqlite_freemem(expr); if (sql) sqlite_freemem(sql); if (IDMAP_ERROR(result->retcode)) xdr_free(xdr_idmap_namerules_res, (caddr_t)result); result->retcode = idmap_stat4prot(result->retcode); return (TRUE); } #define IDMAP_RULES_AUTH "solaris.admin.idmap.rules" static int verify_rules_auth(struct svc_req *rqstp) { ucred_t *uc = NULL; uid_t uid; char buf[1024]; struct passwd pwd; if (svc_getcallerucred(rqstp->rq_xprt, &uc) != 0) { idmapdlog(LOG_ERR, "svc_getcallerucred failed during " "authorization (%s)", strerror(errno)); return (-1); } uid = ucred_geteuid(uc); if (uid == (uid_t)-1) { idmapdlog(LOG_ERR, "ucred_geteuid failed during " "authorization (%s)", strerror(errno)); ucred_free(uc); return (-1); } if (getpwuid_r(uid, &pwd, buf, sizeof (buf)) == NULL) { idmapdlog(LOG_ERR, "getpwuid_r(%u) failed during " "authorization (%s)", uid, strerror(errno)); ucred_free(uc); return (-1); } if (chkauthattr(IDMAP_RULES_AUTH, pwd.pw_name) != 1) { idmapdlog(LOG_INFO, "%s is not authorized (%s)", pwd.pw_name, IDMAP_RULES_AUTH); ucred_free(uc); return (-1); } ucred_free(uc); return (1); } /* * Meaning of the return values is the following: For retcode == * IDMAP_SUCCESS, everything went OK and error_index is * undefined. Otherwise, error_index >=0 shows the failed batch * element. errro_index == -1 indicates failure at the beginning, * error_index == -2 at the end. */ /* ARGSUSED */ bool_t idmap_update_1_svc(idmap_update_batch batch, idmap_update_res *res, struct svc_req *rqstp) { sqlite *db = NULL; idmap_update_op *up; int i; int trans = FALSE; res->error_index = -1; (void) memset(&res->error_rule, 0, sizeof (res->error_rule)); (void) memset(&res->conflict_rule, 0, sizeof (res->conflict_rule)); if (verify_rules_auth(rqstp) < 0) { res->retcode = IDMAP_ERR_PERMISSION_DENIED; goto out; } if (batch.idmap_update_batch_len == 0 || batch.idmap_update_batch_val == NULL) { res->retcode = IDMAP_SUCCESS; goto out; } res->retcode = validate_rules(&batch); if (res->retcode != IDMAP_SUCCESS) goto out; /* Get db handle */ res->retcode = get_db_handle(&db); if (res->retcode != IDMAP_SUCCESS) goto out; res->retcode = sql_exec_no_cb(db, IDMAP_DBNAME, "BEGIN TRANSACTION;"); if (res->retcode != IDMAP_SUCCESS) goto out; trans = TRUE; for (i = 0; i < batch.idmap_update_batch_len; i++) { up = &batch.idmap_update_batch_val[i]; switch (up->opnum) { case OP_NONE: res->retcode = IDMAP_SUCCESS; break; case OP_ADD_NAMERULE: res->retcode = add_namerule(db, &up->idmap_update_op_u.rule); break; case OP_RM_NAMERULE: res->retcode = rm_namerule(db, &up->idmap_update_op_u.rule); break; case OP_FLUSH_NAMERULES: res->retcode = flush_namerules(db); break; default: res->retcode = IDMAP_ERR_NOTSUPPORTED; break; }; if (res->retcode != IDMAP_SUCCESS) { res->error_index = i; if (up->opnum == OP_ADD_NAMERULE || up->opnum == OP_RM_NAMERULE) { idmap_stat r2 = idmap_namerule_cpy(&res->error_rule, &up->idmap_update_op_u.rule); if (r2 != IDMAP_SUCCESS) res->retcode = r2; } goto out; } } out: if (trans) { if (res->retcode == IDMAP_SUCCESS) { res->retcode = sql_exec_no_cb(db, IDMAP_DBNAME, "COMMIT TRANSACTION;"); if (res->retcode == IDMAP_SUCCESS) { /* * We've updated the rules. Expire the cache * so that existing mappings will be * reconsidered. */ res->retcode = idmap_cache_flush(IDMAP_FLUSH_EXPIRE); } else { res->error_index = -2; } } else (void) sql_exec_no_cb(db, IDMAP_DBNAME, "ROLLBACK TRANSACTION;"); } if (res->retcode == IDMAP_ERR_DB) kill_db_handle(db); res->retcode = idmap_stat4prot(res->retcode); return (TRUE); } static int copy_string(char **to, char *from) { if (EMPTY_STRING(from)) { *to = NULL; } else { *to = strdup(from); if (*to == NULL) { idmapdlog(LOG_ERR, "Out of memory"); return (IDMAP_ERR_MEMORY); } } return (IDMAP_SUCCESS); } static int copy_id(idmap_id *to, idmap_id *from) { (void) memset(to, 0, sizeof (*to)); to->idtype = from->idtype; if (IS_ID_SID(*from)) { idmap_retcode retcode; to->idmap_id_u.sid.rid = from->idmap_id_u.sid.rid; retcode = copy_string(&to->idmap_id_u.sid.prefix, from->idmap_id_u.sid.prefix); return (retcode); } else { to->idmap_id_u.uid = from->idmap_id_u.uid; return (IDMAP_SUCCESS); } } static int copy_mapping(idmap_mapping *mapping, idmap_mapping *request) { idmap_retcode retcode; (void) memset(mapping, 0, sizeof (*mapping)); mapping->flag = request->flag; mapping->direction = _IDMAP_F_DONE; retcode = copy_id(&mapping->id1, &request->id1); if (retcode != IDMAP_SUCCESS) goto errout; retcode = copy_string(&mapping->id1domain, request->id1domain); if (retcode != IDMAP_SUCCESS) goto errout; retcode = copy_string(&mapping->id1name, request->id1name); if (retcode != IDMAP_SUCCESS) goto errout; retcode = copy_id(&mapping->id2, &request->id2); if (retcode != IDMAP_SUCCESS) goto errout; retcode = copy_string(&mapping->id2domain, request->id2domain); if (retcode != IDMAP_SUCCESS) goto errout; retcode = copy_string(&mapping->id2name, request->id2name); if (retcode != IDMAP_SUCCESS) goto errout; return (IDMAP_SUCCESS); errout: if (IS_ID_SID(mapping->id1)) free(mapping->id1.idmap_id_u.sid.prefix); free(mapping->id1domain); free(mapping->id1name); if (IS_ID_SID(mapping->id2)) free(mapping->id2.idmap_id_u.sid.prefix); free(mapping->id2domain); free(mapping->id2name); (void) memset(mapping, 0, sizeof (*mapping)); return (retcode); } /* ARGSUSED */ bool_t idmap_get_mapped_id_by_name_1_svc(idmap_mapping request, idmap_mappings_res *result, struct svc_req *rqstp) { idmap_mapping_batch batch_request; idmap_ids_res batch_result; idmap_mapping *map; /* Clear out things we might want to xdr_free on error */ (void) memset(&batch_result, 0, sizeof (batch_result)); (void) memset(result, 0, sizeof (*result)); result->retcode = validate_mapped_id_by_name_req(&request); if (result->retcode != IDMAP_SUCCESS) goto out; /* * Copy the request. We need to modify it, and * what we have is a shallow copy. Freeing pointers from * our copy will lead to problems, since the RPC framework * has its own copy of those pointers. Besides, we need * a copy to return. */ map = calloc(1, sizeof (idmap_mapping)); if (map == NULL) { idmapdlog(LOG_ERR, "Out of memory"); result->retcode = IDMAP_ERR_MEMORY; goto out; } /* * Set up to return the filled-in mapping structure. * Note that we xdr_free result on error, and that'll take * care of freeing the mapping structure. */ result->mappings.mappings_val = map; result->mappings.mappings_len = 1; result->retcode = copy_mapping(map, &request); if (result->retcode != IDMAP_SUCCESS) goto out; /* Set up for the request to the batch API */ batch_request.idmap_mapping_batch_val = map; batch_request.idmap_mapping_batch_len = 1; /* Do the real work. */ (void) idmap_get_mapped_ids_1_svc(batch_request, &batch_result, rqstp); /* Copy what we need out of the batch response */ if (batch_result.retcode != IDMAP_SUCCESS) { result->retcode = batch_result.retcode; goto out; } result->retcode = copy_id(&map->id2, &batch_result.ids.ids_val[0].id); if (result->retcode != IDMAP_SUCCESS) goto out; map->direction = batch_result.ids.ids_val[0].direction; result->retcode = batch_result.ids.ids_val[0].retcode; idmap_info_mov(&map->info, &batch_result.ids.ids_val[0].info); out: if (IDMAP_FATAL_ERROR(result->retcode)) { xdr_free(xdr_idmap_mappings_res, (caddr_t)result); result->mappings.mappings_len = 0; result->mappings.mappings_val = NULL; } result->retcode = idmap_stat4prot(result->retcode); xdr_free(xdr_idmap_ids_res, (char *)&batch_result); return (TRUE); } /* ARGSUSED */ bool_t idmap_get_prop_1_svc(idmap_prop_type request, idmap_prop_res *result, struct svc_req *rqstp) { idmap_pg_config_t *pgcfg; /* Init */ (void) memset(result, 0, sizeof (*result)); result->retcode = IDMAP_SUCCESS; result->value.prop = request; RDLOCK_CONFIG(); /* Just shortcuts: */ pgcfg = &_idmapdstate.cfg->pgcfg; switch (request) { case PROP_LIST_SIZE_LIMIT: result->value.idmap_prop_val_u.intval = pgcfg->list_size_limit; result->auto_discovered = FALSE; break; case PROP_DEFAULT_DOMAIN: result->auto_discovered = FALSE; STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, pgcfg->default_domain); break; case PROP_DOMAIN_NAME: STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, pgcfg->domain_name); result->auto_discovered = pgcfg->domain_name_auto_disc; break; case PROP_MACHINE_SID: result->auto_discovered = FALSE; STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, pgcfg->machine_sid); break; case PROP_DOMAIN_CONTROLLER: if (pgcfg->domain_controller != NULL) { (void) memcpy(&result->value.idmap_prop_val_u.dsval, pgcfg->domain_controller, sizeof (idmap_ad_disc_ds_t)); } result->auto_discovered = pgcfg->domain_controller_auto_disc; break; case PROP_FOREST_NAME: STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, pgcfg->forest_name); result->auto_discovered = pgcfg->forest_name_auto_disc; break; case PROP_SITE_NAME: STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, pgcfg->site_name); result->auto_discovered = pgcfg->site_name_auto_disc; break; case PROP_GLOBAL_CATALOG: if (pgcfg->global_catalog != NULL) { (void) memcpy(&result->value.idmap_prop_val_u.dsval, pgcfg->global_catalog, sizeof (idmap_ad_disc_ds_t)); } result->auto_discovered = pgcfg->global_catalog_auto_disc; break; case PROP_AD_UNIXUSER_ATTR: STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, pgcfg->ad_unixuser_attr); result->auto_discovered = FALSE; break; case PROP_AD_UNIXGROUP_ATTR: STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, pgcfg->ad_unixgroup_attr); result->auto_discovered = FALSE; break; case PROP_NLDAP_WINNAME_ATTR: STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, pgcfg->nldap_winname_attr); result->auto_discovered = FALSE; break; case PROP_DIRECTORY_BASED_MAPPING: STRDUP_CHECK(result->value.idmap_prop_val_u.utf8val, enum_lookup(pgcfg->directory_based_mapping, directory_mapping_map)); result->auto_discovered = FALSE; break; default: result->retcode = IDMAP_ERR_PROP_UNKNOWN; break; } out: UNLOCK_CONFIG(); if (IDMAP_FATAL_ERROR(result->retcode)) { xdr_free(xdr_idmap_prop_res, (caddr_t)result); result->value.prop = PROP_UNKNOWN; } result->retcode = idmap_stat4prot(result->retcode); return (TRUE); } int idmap_flush_1_svc( idmap_flush_op op, idmap_retcode *result, struct svc_req *rqstp) { NOTE(ARGUNUSED(rqstp)) if (verify_rules_auth(rqstp) < 0) { *result = IDMAP_ERR_PERMISSION_DENIED; return (TRUE); } *result = idmap_cache_flush(op); return (TRUE); } /* ARGSUSED */ int idmap_prog_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result) { xdr_free(xdr_result, result); return (TRUE); } /* * This function is called by rpc_svc.c when it encounters an error. */ NOTE(PRINTFLIKE(1)) void idmap_rpc_msgout(const char *fmt, ...) { va_list va; char buf[1000]; va_start(va, fmt); (void) vsnprintf(buf, sizeof (buf), fmt, va); va_end(va); idmapdlog(LOG_ERR, "idmap RPC: %s", buf); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Information about well-known (builtin) names, and functions to retrieve * information about them. */ #include #include #include #include "idmapd.h" /* * Table for well-known SIDs. * * Background: * * Some of the well-known principals are stored under: * cn=WellKnown Security Principals, cn=Configuration, dc= * They belong to objectClass "foreignSecurityPrincipal". They don't have * "samAccountName" nor "userPrincipalName" attributes. Their names are * available in "cn" and "name" attributes. Some of these principals have a * second entry under CN=ForeignSecurityPrincipals,dc= and * these duplicate entries have the stringified SID in the "name" and "cn" * attributes instead of the actual name. * * Those of the form S-1-5-32-X are Builtin groups and are stored in the * cn=builtin container (except, Power Users which is not stored in AD) * * These principals are and will remain constant. Therefore doing AD lookups * provides no benefit. Also, using hard-coded table (and thus avoiding AD * lookup) improves performance and avoids additional complexity in the * adutils.c code. Moreover these SIDs can be used when no Active Directory * is available (such as the CIFS server's "workgroup" mode). * * Notes: * 1. Currently we don't support localization of well-known SID names, * unlike Windows. * * 2. Other well-known SIDs i.e. S-1-5-- are not stored * here. AD does have normal user/group objects for these objects and * can be looked up using the existing AD lookup code. * * 3. See comments above lookup_wksids_sid2pid() for more information * on how we lookup the wksids table. * * 4. If this table contains two entries for a particular Windows name, * so as to offer both UID and GID mappings, the preferred mapping (the * one that matches Windows usage) must be listed first. That is the * entry that will be used when the caller specifies IDMAP_POSIXID * ("don't care") as the target. * * Entries here come from KB243330, MS-LSAT, and * http://technet.microsoft.com/en-us/library/cc755854.aspx * http://technet.microsoft.com/en-us/library/cc755925.aspx * http://msdn.microsoft.com/en-us/library/cc980032(PROT.10).aspx */ static wksids_table_t wksids[] = { /* S-1-0 Null Authority */ {"S-1-0", 0, "", "Nobody", 1, IDMAP_SENTINEL_PID, -1, 1}, /* S-1-1 World Authority */ {"S-1-1", 0, "", "Everyone", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-2 Local Authority */ {"S-1-2", 0, "", "Local", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-2", 1, "", "Console Logon", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-3 Creator Authority */ {"S-1-3", 0, "", "Creator Owner", 1, IDMAP_WK_CREATOR_OWNER_UID, 1, 0}, {"S-1-3", 1, "", "Creator Group", 0, IDMAP_WK_CREATOR_GROUP_GID, 0, 0}, {"S-1-3", 2, "", "Creator Owner Server", 1, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-3", 3, "", "Creator Group Server", 0, IDMAP_SENTINEL_PID, -1, 1}, {"S-1-3", 4, "", "Owner Rights", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-4 Non-unique Authority */ /* S-1-5 NT Authority */ {"S-1-5", 1, "", "Dialup", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 2, "", "Network", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 3, "", "Batch", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 4, "", "Interactive", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-5-5-X-Y Logon Session */ {"S-1-5", 6, "", "Service", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 7, "", "Anonymous Logon", 0, GID_NOBODY, 0, 0}, {"S-1-5", 7, "", "Anonymous Logon", 0, UID_NOBODY, 1, 0}, {"S-1-5", 8, "", "Proxy", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 9, "", "Enterprise Domain Controllers", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 10, "", "Self", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 11, "", "Authenticated Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 12, "", "Restricted", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 13, "", "Terminal Server Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 14, "", "Remote Interactive Logon", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 15, "", "This Organization", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 17, "", "IUSR", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 18, "", "Local System", 0, IDMAP_WK_LOCAL_SYSTEM_GID, 0, 0}, {"S-1-5", 19, "", "Local Service", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 20, "", "Network Service", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-5-21- Machine-local definitions */ {NULL, 498, NULL, "Enterprise Read-only Domain Controllers", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 500, NULL, "Administrator", 1, IDMAP_SENTINEL_PID, 1, -1}, {NULL, 501, NULL, "Guest", 1, IDMAP_SENTINEL_PID, 1, -1}, {NULL, 502, NULL, "KRBTGT", 1, IDMAP_SENTINEL_PID, 1, -1}, {NULL, 512, NULL, "Domain Admins", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 513, NULL, "Domain Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 514, NULL, "Domain Guests", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 515, NULL, "Domain Computers", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 516, NULL, "Domain Controllers", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 517, NULL, "Cert Publishers", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 518, NULL, "Schema Admins", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 519, NULL, "Enterprise Admins", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 520, NULL, "Global Policy Creator Owners", 0, IDMAP_SENTINEL_PID, -1, -1}, {NULL, 533, NULL, "RAS and IAS Servers", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-5-32 BUILTIN */ {"S-1-5-32", 544, "BUILTIN", "Administrators", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 545, "BUILTIN", "Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 546, "BUILTIN", "Guests", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 547, "BUILTIN", "Power Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 548, "BUILTIN", "Account Operators", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 549, "BUILTIN", "Server Operators", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 550, "BUILTIN", "Print Operators", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 551, "BUILTIN", "Backup Operators", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 552, "BUILTIN", "Replicator", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 554, "BUILTIN", "Pre-Windows 2000 Compatible Access", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 555, "BUILTIN", "Remote Desktop Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 556, "BUILTIN", "Network Configuration Operators", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 557, "BUILTIN", "Incoming Forest Trust Builders", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 558, "BUILTIN", "Performance Monitor Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 559, "BUILTIN", "Performance Log Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 560, "BUILTIN", "Windows Authorization Access Group", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 561, "BUILTIN", "Terminal Server License Servers", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 562, "BUILTIN", "Distributed COM Users", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 568, "BUILTIN", "IIS_IUSRS", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 569, "BUILTIN", "Cryptographic Operators", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 573, "BUILTIN", "Event Log Readers", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-32", 574, "BUILTIN", "Certificate Service DCOM Access", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5", 33, "", "Write Restricted", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-5-64 NT Authority */ {"S-1-5-64", 10, "", "NTLM Authentication", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-64", 14, "", "SChannel Authentication", 0, IDMAP_SENTINEL_PID, -1, -1}, {"S-1-5-64", 21, "", "Digest Authentication", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-5-80-a-b-c-d NT Service */ {"S-1-5", 1000, "", "Other Organization", 0, IDMAP_SENTINEL_PID, -1, -1}, /* S-1-7 Internet$ */ /* * S-1-16 Mandatory Label * S-1-16-0 Untrusted Mandatory Level * S-1-16-4096 Low Mandatory Level * S-1-16-8192 Medium Mandatory Level * S-1-16-8448 Medium Plus Mandatory Level * S-1-16-12288 High Mandatory Level * S-1-16-16384 System Mandatory Level * S-1-16-20480 Protected Process Mandatory Level */ }; /* * Find a wksid entry for the specified Windows name and domain, of the * specified type. * * Ignore entries intended only for U2W use. */ const wksids_table_t * find_wksid_by_name(const char *name, const char *domain, idmap_id_type type) { int i; RDLOCK_CONFIG(); int len = strlen(_idmapdstate.hostname); char my_host_name[len + 1]; (void) strcpy(my_host_name, _idmapdstate.hostname); UNLOCK_CONFIG(); for (i = 0; i < UU_NELEM(wksids); i++) { /* Check to see if this entry yields the desired type */ switch (type) { case IDMAP_UID: if (wksids[i].is_user == 0) continue; break; case IDMAP_GID: if (wksids[i].is_user == 1) continue; break; case IDMAP_POSIXID: break; default: assert(FALSE); } if (strcasecmp(wksids[i].winname, name) != 0) continue; if (!EMPTY_STRING(domain)) { const char *dom; if (wksids[i].domain != NULL) { dom = wksids[i].domain; } else { dom = my_host_name; } if (strcasecmp(dom, domain) != 0) continue; } /* * We have a Windows name, so ignore entries that are only * usable for mapping UNIX->Windows. (Note: the current * table does not have any such entries.) */ if (wksids[i].direction == IDMAP_DIRECTION_U2W) continue; return (&wksids[i]); } return (NULL); } /* * Find a wksid entry for the specified SID, of the specified type. * * Ignore entries intended only for U2W use. */ const wksids_table_t * find_wksid_by_sid(const char *sid, int rid, idmap_id_type type) { int i; RDLOCK_CONFIG(); int len = strlen(_idmapdstate.cfg->pgcfg.machine_sid); char my_machine_sid[len + 1]; (void) strcpy(my_machine_sid, _idmapdstate.cfg->pgcfg.machine_sid); UNLOCK_CONFIG(); for (i = 0; i < UU_NELEM(wksids); i++) { int sidcmp; /* Check to see if this entry yields the desired type */ switch (type) { case IDMAP_UID: if (wksids[i].is_user == 0) continue; break; case IDMAP_GID: if (wksids[i].is_user == 1) continue; break; case IDMAP_POSIXID: break; default: assert(FALSE); } if (wksids[i].sidprefix != NULL) { sidcmp = strcasecmp(wksids[i].sidprefix, sid); } else { sidcmp = strcasecmp(my_machine_sid, sid); } if (sidcmp != 0) continue; if (wksids[i].rid != rid) continue; /* * We have a SID, so ignore entries that are only usable * for mapping UNIX->Windows. (Note: the current table * does not have any such entries.) */ if (wksids[i].direction == IDMAP_DIRECTION_U2W) continue; return (&wksids[i]); } return (NULL); } /* * Find a wksid entry for the specified pid, of the specified type. * Ignore entries that do not specify U2W mappings. */ const wksids_table_t * find_wksid_by_pid(uid_t pid, int is_user) { int i; if (pid == IDMAP_SENTINEL_PID) return (NULL); for (i = 0; i < UU_NELEM(wksids); i++) { if (wksids[i].pid == pid && wksids[i].is_user == is_user && (wksids[i].direction == IDMAP_DIRECTION_BI || wksids[i].direction == IDMAP_DIRECTION_U2W)) { return (&wksids[i]); } } return (NULL); } /* * It is probably a bug that both this and find_wksid_by_sid exist, * but for now the distinction is primarily that one takes {machinesid,rid} * and the other takes a full SID. */ const wksids_table_t * find_wk_by_sid(char *sid) { int i; RDLOCK_CONFIG(); int len = strlen(_idmapdstate.cfg->pgcfg.machine_sid); char my_machine_sid[len + 1]; (void) strcpy(my_machine_sid, _idmapdstate.cfg->pgcfg.machine_sid); UNLOCK_CONFIG(); for (i = 0; i < UU_NELEM(wksids); i++) { int len; const char *prefix; char *p; unsigned long rid; if (wksids[i].sidprefix == NULL) prefix = my_machine_sid; else prefix = wksids[i].sidprefix; len = strlen(prefix); /* * Check to see whether the SID we're looking for starts * with this prefix, then a -, then a single RID, and it's * the right RID. */ if (strncasecmp(sid, prefix, len) != 0) continue; if (sid[len] != '-') continue; rid = strtoul(sid + len + 1, &p, 10); if (*p != '\0') continue; if (rid != wksids[i].rid) continue; return (&wksids[i]); } return (NULL); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2014 Nexenta Systems, Inc. All rights reserved. # PROG = nltest CLIENTOBJS = nltest.o CLIENTSRCS = $(CLIENTOBJS:%.o=%.c) POFILES = $(CLIENTOBJS:.o=.po) OBJS = $(CLIENTOBJS) SRCS = $(CLIENTSRCS) include ../../Makefile.cmd POFILE = $(PROG)_all.po LDLIBS += -lads -luuid FILEMODE = 0555 INCS += -I. CFLAGS += $(CCVERBOSE) $(OBJS) : CPPFLAGS += $(INCS) -D_REENTRANT $(POFILE) : CPPFLAGS += $(INCS) lint_SRCS : CPPFLAGS += $(INCS) .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(CCGDEBUG) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) $(POFILE): $(POFILES) $(RM) $@ cat $(POFILES) > $@ install: all $(ROOTUSRSBINPROG) clean: $(RM) $(OBJS) lint: lint_SRCS include ../../Makefile.targ #!/bin/sh # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2014 Nexenta Systems, Inc. All rights reserved. # # Helper program to run nltest [ -n "$CODEMGR_WS" ] || { echo "Need a buildenv to set CODEMGR_WS=..." exit 1; } ROOT=${CODEMGR_WS}/proto/root_i386 LD_LIBRARY_PATH=$ROOT/usr/lib:$ROOT/lib export LD_LIBRARY_PATH $ROOT/usr/sbin/nltest "$@" /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright 2021 RackTop Systems, Inc. */ #include #include #include #include #include #include #include #include static void dclist_usage(void); static int cmd_dclist(char *); static void dcname_usage(void); static int cmd_dcname(char *); static void dsgetdc_usage(void); static int cmd_dsgetdc(char *); static void dsgetdcname_usage(void); static int cmd_dsgetdcname(char *); static void kick_usage(void); static int cmd_kick(char *); static void help(void); typedef int cmd_fn_t (char *); typedef void cmd_usage_t (void); static struct commands { const char *name; /* name of subcommand */ cmd_fn_t *fn; /* pointer to subcommand handler function */ cmd_usage_t *usage; /* pointer to subcommand help function */ int optreq; /* does this have a required optval */ } commands[] = { {"dclist", cmd_dclist, dclist_usage, 0}, {"dcname", cmd_dcname, dcname_usage, 0}, {"dsgetdc", cmd_dsgetdc, dsgetdc_usage, 0}, {"dsgetdcname", cmd_dsgetdcname, dsgetdcname_usage, 0}, {"kick", cmd_kick, kick_usage, 0}, {NULL, NULL, NULL, 0} }; /* * lookupcmd */ static struct commands * lookupcmd(const char *name) { struct commands *cmd; for (cmd = commands; cmd->name; cmd++) { if (strcasecmp(cmd->name, name) == 0) return (cmd); } return (NULL); } /* * dclist */ static void dclist_usage(void) { (void) printf(gettext("usage: nltest dclist... \n")); exit(1); } /* ARGSUSED */ static int cmd_dclist(char *optval) { (void) printf("cmd_dclist() \n"); return (0); } /* * dcname */ static void dcname_usage(void) { (void) printf(gettext("usage: nltest dcname... \n")); exit(1); } /* ARGSUSED */ static int cmd_dcname(char *optval) { (void) printf("cmd_dcname() \n"); return (0); } /* * dsgetdc */ static void dsgetdc_usage(void) { (void) printf(gettext("usage: nltest dsgetdc... \n")); exit(1); } /* ARGSUSED */ static int cmd_dsgetdc(char *optval) { (void) printf("cmd_dsgetdc() \n"); return (0); } /* * dsgetdcname */ static void dsgetdcname_usage(void) { (void) printf(gettext("usage: nltest dsgetdcname domainname \n")); exit(1); } static int cmd_dsgetdcname(char *domname) { char uuid_buf[UUID_PRINTABLE_STRING_LENGTH]; int err = 0; char *atype; DOMAIN_CONTROLLER_INFO *dcinfo; if (domname != NULL) (void) printf(" Domain name supplied: %s \n", domname); err = DsGetDcName(NULL, domname, NULL, NULL, 0, &dcinfo); switch (err) { case 0: break; case ERROR_NO_SUCH_DOMAIN: (void) printf("Domain controller not found.\n"); (void) printf("See: /var/run/idmap/discovery.log\n"); exit(1); default: (void) printf("Unexpected error %d\n", err); exit(1); } switch (dcinfo->DomainControllerAddressType) { case DS_INET_ADDRESS: atype = "inet"; break; case DS_NETBIOS_ADDRESS: atype = "netbios"; break; default: atype = "?"; break; } uuid_unparse(dcinfo->DomainGuid, uuid_buf); (void) printf("Data Returned from DsGetDcName() call: \n"); (void) printf(" DC Name: %s \n", dcinfo->DomainControllerName); (void) printf(" DC Addr: %s \n", dcinfo->DomainControllerAddress); (void) printf(" DC Addr Type: %s \n", atype); (void) printf(" Domain Name: %s \n", dcinfo->DomainName); (void) printf(" Domain GUID: %s \n", uuid_buf); (void) printf(" DNS Forest Name: %s \n", dcinfo->DnsForestName); (void) printf(" Flags: 0x%x \n", dcinfo->Flags); (void) printf(" DC Site Name: %s \n", dcinfo->DcSiteName); (void) printf(" Client Site Name: %s \n", dcinfo->ClientSiteName); DsFreeDcInfo(dcinfo); return (0); } /* * kick */ static void kick_usage(void) { (void) printf(gettext("usage: nltest /KICK \n")); exit(1); } static int cmd_kick(char *domname) { int flags = 0; int result; result = _DsForceRediscovery(domname, flags); return (result); } /* * help functions */ static void help(void) { (void) printf("\n"); /* * TODO: We may want to revise this help text. It's basically * a copy-paste from: * http://technet.microsoft.com/en-us/library/cc731935.aspx */ (void) printf(gettext("usage: %s /subcommand\n"), (char *)getexecname()); (void) printf(gettext("where subcommands are:\n" #if 0 /* not yet */ " dclist Lists all domain controllers in the domain.\n" " dcname Lists the PDC or PDC emulator.\n" " dsgetdc Queries DNS server for list of DCs and" " their IP addresses and contacts each DC to check" " for connectivity.\n" #endif " dsgetdcname returns the name of a domain controller in a" " specified domain\n" " help display help on specified subcommand\n" " kick trigger domain controller re-discovery\n" "\n")); exit(1); } int main(int argc, char *argv[]) { struct commands *cmd; int err = 0; char *option_cmd = NULL; char *arg; char *p; char *optname; char *optval = NULL; int i; int optind = 1; /* * Parse options. */ while (optind < argc) { arg = argv[optind]; optname = NULL; optval = NULL; /* Is this an option? */ if (arg[0] == '/') { optname = arg + 1; optind++; /* * May have /optname:value */ if ((p = strchr(optname, ':')) != NULL) { *p++ = '\0'; optval = p; } } else if (arg[0] == '-' && arg[1] == '-') { optname = arg + 2; optind++; /* * May have --optname=value */ if ((p = strchr(optname, '=')) != NULL) { *p++ = '\0'; optval = p; } } else { /* Not an option. Stop parsing. */ break; } /* * Handle each optname (and maybe its optval) * Might put this logic in a table of options. * (including a flag for "optval required", * so that check could be factored out) */ for (cmd = commands; cmd->name; cmd++) { if (!strcasecmp(optname, cmd->name)) { /* cmd->name requires an optval */ if (optval == NULL && optind < argc) optval = argv[optind++]; if (optval == NULL && cmd->optreq > 0) { (void) fprintf(stderr, "%s: option %s requires a value\n", argv[0], optname); return (1); } option_cmd = optname; } } } /* * Handle remaining non-option arguments */ for (i = optind; i < argc; i++) { (void) printf("arg: %s\n", argv[i]); } if (option_cmd == NULL) help(); cmd = lookupcmd(option_cmd); if (cmd == NULL) err = 1; else err = cmd->fn(optval); return (err); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # echo_file usr/src/lib/libidmap/Makefile # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2014 Nexenta Systems, Inc. All rights reserved. # set -o emacs setenv ROOT ${CODEMGR_WS}/proto/root_i386 setenv LD_LIBRARY_PATH ${ROOT}/usr/lib:${ROOT}/lib loadobject -load ${ROOT}/usr/lib/libadutils.so.1 debug ./test-getdc # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2014 Nexenta Systems, Inc. All rights reserved. # PROG = test-getdc OBJS = getdc_main.o SRCS = $(OBJS:%.o=%.c) POFILES = $(OBJS:.o=.po) include ../../Makefile.cmd POFILE = $(PROG)_all.po LDLIBS += -ladutils -lnsl -lumem LDFLAGS += -R'$$ORIGIN/../lib' INCS += -I. -I../../../lib/libadutils/common LINTFLAGS += -xerroff=E_NAME_DEF_NOT_USED2 CFLAGS += $(CCVERBOSE) $(OBJS) : CPPFLAGS += $(INCS) -D_REENTRANT $(POFILE) : CPPFLAGS += $(INCS) lint_SRCS : CPPFLAGS += $(INCS) .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) FRC $(LINK.c) $(CCGDEBUG) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) $(POFILE): $(POFILES) $(RM) $@ cat $(POFILES) > $@ install: all $(ROOTPROG) clean: $(RM) $(OBJS) lint: lint_SRCS include ../../Makefile.targ FRC: /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2019 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include int debug; char *domainname = NULL; char *sitename = NULL; void print_ds(ad_disc_ds_t *); void mylogger(int pri, const char *format, ...); int main(int argc, char *argv[]) { ad_disc_t ad_ctx = NULL; boolean_t autodisc; ad_disc_ds_t *dc, *gc; char *s; int c; while ((c = getopt(argc, argv, "d")) != -1) { switch (c) { case '?': (void) fprintf(stderr, "bad option: -%c\n", optopt); return (1); case 'd': debug++; break; } } if (optind < argc) domainname = argv[optind++]; if (optind < argc) sitename = argv[optind++]; adutils_set_logger(mylogger); adutils_set_debug(AD_DEBUG_ALL, debug); ad_ctx = ad_disc_init(); ad_disc_set_StatusFP(ad_ctx, stdout); if (domainname) (void) ad_disc_set_DomainName(ad_ctx, domainname); if (sitename) (void) ad_disc_set_SiteName(ad_ctx, sitename); ad_disc_refresh(ad_ctx); dc = ad_disc_get_DomainController(ad_ctx, AD_DISC_PREFER_SITE, &autodisc); if (dc == NULL) { (void) printf("getdc failed\n"); return (1); } (void) printf("Found a DC:\n"); print_ds(dc); free(dc); s = ad_disc_get_ForestName(ad_ctx, NULL); (void) printf("Forest: %s\n", s); free(s); s = ad_disc_get_SiteName(ad_ctx, NULL); (void) printf("Site: %s\n", s); free(s); gc = ad_disc_get_GlobalCatalog(ad_ctx, AD_DISC_PREFER_SITE, &autodisc); if (gc != NULL) { (void) printf("Found a GC:\n"); print_ds(gc); free(gc); } ad_disc_done(ad_ctx); ad_disc_fini(ad_ctx); ad_ctx = NULL; return (0); } void print_ds(ad_disc_ds_t *ds) { char buf[64]; for (; ds->host[0] != '\0'; ds++) { const char *p; (void) printf("Name: %s\n", ds->host); (void) printf(" flags: 0x%X\n", ds->flags); if (ds->addr.ss_family == AF_INET) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)&ds->addr; p = inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof (buf)); if (p == NULL) p = "?"; (void) printf(" A %s %d\n", p, ds->port); } if (ds->addr.ss_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)&ds->addr; p = inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof (buf)); if (p == NULL) p = "?"; (void) printf(" AAAA %s %d\n", p, ds->port); } } } /* printflike */ void mylogger(int pri, const char *format, ...) { _NOTE(ARGUNUSED(pri)) va_list args; va_start(args, format); (void) vfprintf(stderr, format, args); (void) fprintf(stderr, "\n"); va_end(args); } /* * This is a unit-test program. Always enable libumem debugging. */ const char * _umem_debug_init(void) { return ("default,verbose"); /* $UMEM_DEBUG setting */ } const char * _umem_logging_init(void) { return ("fail,contents"); /* $UMEM_LOGGING setting */ }