/* * 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. */ #pragma ident "%Z%%M% %I% %E% SMI" #include <stdlib.h> #include <strings.h> #include <unistd.h> #include <syslog.h> #include <thread.h> #include <synch.h> #include <grp.h> #include <assert.h> #include <libintl.h> #include <smbsrv/libsmb.h> #include <smb_sqlite.h> /* * Local domain SID (aka machine SID) is not stored in the domain table * therefore the index is 0 */ #define SMB_LGRP_LOCAL_IDX 0 #define SMB_LGRP_BUILTIN_IDX 1 #define SMB_LGRP_DB_NAME "/var/smb/smbgroup.db" #define SMB_LGRP_DB_TIMEOUT 3000 /* in millisecond */ #define SMB_LGRP_DB_VERMAJOR 1 #define SMB_LGRP_DB_VERMINOR 0 #define SMB_LGRP_DB_MAGIC 0x4C475250 /* LGRP */ #define SMB_LGRP_DB_ORD 1 /* open read-only */ #define SMB_LGRP_DB_ORW 2 /* open read/write */ #define SMB_LGRP_DB_ADDMEMBER 1 #define SMB_LGRP_DB_DELMEMBER 2 /* * members column of the groups table is an array of * member structure smb_lgmid_t defined below. * * privs column of the groups table is an array of bytes * where each byte is the id of an enable privilege */ #define SMB_LGRP_DB_SQL \ "CREATE TABLE db_info (" \ " ver_major INTEGER," \ " ver_minor INTEGER," \ " magic INTEGER" \ ");" \ "" \ "CREATE TABLE domains (" \ " dom_idx INTEGER PRIMARY KEY," \ " dom_sid TEXT UNIQUE," \ " dom_cnt INTEGER" \ ");" \ "" \ "CREATE UNIQUE INDEX domsid_idx ON domains (dom_sid);" \ "" \ "CREATE TABLE groups (" \ " name TEXT PRIMARY KEY," \ " sid_idx INTEGER," \ " sid_rid INTEGER," \ " sid_type INTEGER," \ " sid_attrs INTEGER," \ " comment TEXT," \ " n_privs INTEGER," \ " privs BLOB," \ " n_members INTEGER," \ " members BLOB" \ ");" \ "" \ "CREATE INDEX grprid_idx ON groups (sid_rid);" /* * Number of groups table columns */ #define SMB_LGRP_GTBL_NCOL 10 #define SMB_LGRP_GTBL_NAME 0 #define SMB_LGRP_GTBL_SIDIDX 1 #define SMB_LGRP_GTBL_SIDRID 2 #define SMB_LGRP_GTBL_SIDTYP 3 #define SMB_LGRP_GTBL_SIDATR 4 #define SMB_LGRP_GTBL_CMNT 5 #define SMB_LGRP_GTBL_NPRIVS 6 #define SMB_LGRP_GTBL_PRIVS 7 #define SMB_LGRP_GTBL_NMEMBS 8 #define SMB_LGRP_GTBL_MEMBS 9 #define SMB_LGRP_INFO_NONE 0x00 #define SMB_LGRP_INFO_NAME 0x01 #define SMB_LGRP_INFO_CMNT 0x02 #define SMB_LGRP_INFO_SID 0x04 #define SMB_LGRP_INFO_PRIV 0x08 #define SMB_LGRP_INFO_MEMB 0x10 #define SMB_LGRP_INFO_ALL 0x1F #define NULL_MSGCHK(msg) ((msg) ? (msg) : "NULL") /* Member ID */ typedef struct smb_lgmid { uint32_t m_idx; uint32_t m_rid; uint16_t m_type; } smb_lgmid_t; #define SMB_LGRP_MID_HEXSZ 32 /* Member list */ typedef struct smb_lgmlist { uint32_t m_cnt; char *m_ids; } smb_lgmlist_t; /* Privilege ID */ typedef uint8_t smb_lgpid_t; /* Privilege list */ typedef struct smb_lgplist { uint32_t p_cnt; smb_lgpid_t *p_ids; } smb_lgplist_t; static mutex_t smb_lgrp_lsid_mtx; static nt_sid_t *smb_lgrp_lsid; static int smb_lgrp_db_init(void); static sqlite *smb_lgrp_db_open(int); static void smb_lgrp_db_close(sqlite *); static int smb_lgrp_db_setinfo(sqlite *); static boolean_t smb_lgrp_gtbl_exists(sqlite *, char *); static int smb_lgrp_gtbl_lookup(sqlite *, int, smb_group_t *, int, ...); static int smb_lgrp_gtbl_insert(sqlite *, smb_group_t *); static int smb_lgrp_gtbl_update(sqlite *, char *, smb_group_t *, int); static int smb_lgrp_gtbl_delete(sqlite *, char *); static int smb_lgrp_gtbl_update_mlist(sqlite *, char *, smb_gsid_t *, int); static int smb_lgrp_gtbl_update_plist(sqlite *, char *, uint8_t, boolean_t); static int smb_lgrp_gtbl_count(sqlite *, int, int *); static int smb_lgrp_dtbl_insert(sqlite *, char *, uint32_t *); static int smb_lgrp_dtbl_getidx(sqlite *, nt_sid_t *, uint16_t, uint32_t *, uint32_t *); static int smb_lgrp_dtbl_getsid(sqlite *, uint32_t, nt_sid_t **); static int smb_lgrp_mlist_add(smb_lgmlist_t *, smb_lgmid_t *, smb_lgmlist_t *); static int smb_lgrp_mlist_del(smb_lgmlist_t *, smb_lgmid_t *, smb_lgmlist_t *); static int smb_lgrp_plist_add(smb_lgplist_t *, smb_lgpid_t, smb_lgplist_t *); static int smb_lgrp_plist_del(smb_lgplist_t *, smb_lgpid_t, smb_lgplist_t *); static void smb_lgrp_encode_privset(smb_group_t *, smb_lgplist_t *); static int smb_lgrp_decode(smb_group_t *, char **, int, sqlite *); static int smb_lgrp_decode_privset(smb_group_t *, char *, char *); static int smb_lgrp_decode_members(smb_group_t *, char *, char *, sqlite *); static void smb_lgrp_set_default_privs(smb_group_t *); static boolean_t smb_lgrp_chkname(char *); static boolean_t smb_lgrp_chkmember(uint16_t); static int smb_lgrp_getsid(int, uint32_t *, uint16_t, sqlite *, nt_sid_t **); /* * smb_lgrp_add * * Create a local group with the given name and comment. * This new group doesn't have any members and no enabled * privileges. * * No well-known accounts can be added other than Administators, * Backup Operators and Power Users. These built-in groups * won't have any members when created but a set of default * privileges will be enabled for them. */ int smb_lgrp_add(char *gname, char *cmnt) { well_known_account_t *wk_acct; struct group *pxgrp; smb_group_t grp; nt_sid_t *sid = NULL; sqlite *db; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); if (cmnt && (strlen(cmnt) > SMB_LGRP_COMMENT_MAX)) return (SMB_LGRP_INVALID_ARG); bzero(&grp, sizeof (grp)); grp.sg_name = utf8_strlwr(gname); grp.sg_cmnt = cmnt; wk_acct = nt_builtin_lookup(gname); if (wk_acct == NULL) { if ((pxgrp = getgrnam(gname)) == NULL) return (SMB_LGRP_NOT_FOUND); /* * Make sure a local SID can be obtained */ if (smb_idmap_getsid(pxgrp->gr_gid, SMB_IDMAP_GROUP, &sid) != IDMAP_SUCCESS) return (SMB_LGRP_NO_SID); if (!nt_sid_is_indomain(smb_lgrp_lsid, sid)) { free(sid); return (SMB_LGRP_SID_NOTLOCAL); } grp.sg_id.gs_type = SidTypeAlias; grp.sg_domain = SMB_LGRP_LOCAL; grp.sg_rid = pxgrp->gr_gid; } else { if (wk_acct->flags & LGF_HIDDEN) { /* cannot add well-known accounts */ return (SMB_LGRP_WKSID); } grp.sg_id.gs_type = wk_acct->sid_name_use; if ((sid = nt_sid_strtosid(wk_acct->sid)) == NULL) return (SMB_LGRP_NO_MEMORY); (void) nt_sid_get_rid(sid, &grp.sg_rid); free(sid); grp.sg_domain = SMB_LGRP_BUILTIN; grp.sg_privs = smb_privset_new(); smb_lgrp_set_default_privs(&grp); } grp.sg_attr = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_insert(db, &grp); smb_lgrp_db_close(db); smb_privset_free(grp.sg_privs); return (rc); } /* * smb_lgrp_rename * * Renames the given group */ int smb_lgrp_rename(char *gname, char *new_gname) { smb_group_t grp; sqlite *db; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); (void) trim_whitespace(new_gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); if (utf8_strcasecmp(gname, new_gname) == 0) return (SMB_LGRP_SUCCESS); /* Cannot rename well-known groups */ if (nt_builtin_is_wellknown(gname)) return (SMB_LGRP_WKSID); /* Cannot rename to a well-known groups */ if (nt_builtin_is_wellknown(new_gname)) return (SMB_LGRP_WKSID); grp.sg_name = new_gname; db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update(db, gname, &grp, SMB_LGRP_GTBL_NAME); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_delete * * Deletes the specified local group. */ int smb_lgrp_delete(char *gname) { sqlite *db; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); /* Cannot remove a built-in group */ if (nt_builtin_is_wellknown(gname)) return (SMB_LGRP_WKSID); db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_delete(db, gname); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_setcmnt * * Sets the description for the given group */ int smb_lgrp_setcmnt(char *gname, char *cmnt) { smb_group_t grp; sqlite *db; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); if (cmnt && (strlen(cmnt) > SMB_LGRP_COMMENT_MAX)) return (SMB_LGRP_INVALID_ARG); grp.sg_cmnt = cmnt; db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update(db, gname, &grp, SMB_LGRP_GTBL_CMNT); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_getcmnt * * Obtain the description of the specified group */ int smb_lgrp_getcmnt(char *gname, char **cmnt) { smb_group_t grp; sqlite *db; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); if (cmnt == NULL) return (SMB_LGRP_INVALID_ARG); db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_lookup(db, SMB_LGRP_GTBL_NAME, &grp, SMB_LGRP_INFO_CMNT, gname); smb_lgrp_db_close(db); if (rc == SMB_LGRP_SUCCESS) { *cmnt = grp.sg_cmnt; grp.sg_cmnt = NULL; smb_lgrp_free(&grp); } return (rc); } /* * smb_lgrp_setpriv * * Enable/disable the specified privilge for the group */ int smb_lgrp_setpriv(char *gname, uint8_t priv_lid, boolean_t enable) { sqlite *db; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); if ((priv_lid < SE_MIN_LUID) || (priv_lid > SE_MAX_LUID)) return (SMB_LGRP_NO_SUCH_PRIV); db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update_plist(db, gname, priv_lid, enable); smb_lgrp_db_close(db); if (enable) { if (rc == SMB_LGRP_PRIV_HELD) rc = SMB_LGRP_SUCCESS; } else { if (rc == SMB_LGRP_PRIV_NOT_HELD) rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_getpriv * * Obtain the status of the specified privilge for the group */ int smb_lgrp_getpriv(char *gname, uint8_t priv_lid, boolean_t *enable) { sqlite *db; smb_group_t grp; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); if ((priv_lid < SE_MIN_LUID) || (priv_lid > SE_MAX_LUID)) return (SMB_LGRP_NO_SUCH_PRIV); db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_lookup(db, SMB_LGRP_GTBL_NAME, &grp, SMB_LGRP_INFO_PRIV, gname); smb_lgrp_db_close(db); if (rc == SMB_LGRP_SUCCESS) { *enable = (smb_privset_query(grp.sg_privs, priv_lid) == 1); smb_lgrp_free(&grp); } return (rc); } /* * smb_lgrp_add_member * * Add the given account to the specified group as its member. */ int smb_lgrp_add_member(char *gname, nt_sid_t *msid, uint16_t sid_type) { sqlite *db; smb_gsid_t mid; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); if (!nt_sid_is_valid(msid)) return (SMB_LGRP_INVALID_ARG); if (!smb_lgrp_chkmember(sid_type)) return (SMB_LGRP_INVALID_MEMBER); mid.gs_sid = msid; mid.gs_type = sid_type; db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update_mlist(db, gname, &mid, SMB_LGRP_DB_ADDMEMBER); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_del_member * * Delete the specified member from the given group. */ int smb_lgrp_del_member(char *gname, nt_sid_t *msid, uint16_t sid_type) { sqlite *db; smb_gsid_t mid; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); if (!nt_sid_is_valid(msid)) return (SMB_LGRP_INVALID_ARG); mid.gs_sid = msid; mid.gs_type = sid_type; db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update_mlist(db, gname, &mid, SMB_LGRP_DB_DELMEMBER); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_getbyname * * Retrieves the information of the group specified by * the given name. * * Note that this function doesn't allocate the group * structure itself only the fields, so the given grp * pointer has to point to a group structure. * Caller must free the allocated memories for the fields * by calling smb_lgrp_free(). */ int smb_lgrp_getbyname(char *gname, smb_group_t *grp) { sqlite *db; int rc; (void) trim_whitespace(gname); if (!smb_lgrp_chkname(gname)) return (SMB_LGRP_INVALID_NAME); db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_lookup(db, SMB_LGRP_GTBL_NAME, grp, SMB_LGRP_INFO_ALL, gname); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_getbyrid * * Retrieves the information of the group specified by * the given RID and domain type. * * Note that this function doesn't allocate the group * structure itself only the fields, so the given grp * pointer has to point to a group structure. * Caller must free the allocated memories for the fields * by calling smb_lgrp_free(). * * If grp is NULL no information would be returned. The * return value of SMB_LGRP_SUCCESS will indicate that a * group with the given information exists. */ int smb_lgrp_getbyrid(uint32_t rid, smb_gdomain_t domtype, smb_group_t *grp) { smb_group_t tmpgrp; sqlite *db; int infolvl = SMB_LGRP_INFO_ALL; int rc; if (grp == NULL) { grp = &tmpgrp; infolvl = SMB_LGRP_INFO_NONE; } db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_lookup(db, SMB_LGRP_GTBL_SIDRID, grp, infolvl, rid, domtype); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_numbydomain * * Returns the number of groups in the given domain in the * arg 'count' */ int smb_lgrp_numbydomain(smb_gdomain_t dom_type, int *count) { sqlite *db; int dom_idx; int rc; switch (dom_type) { case SMB_LGRP_LOCAL: dom_idx = SMB_LGRP_LOCAL_IDX; break; case SMB_LGRP_BUILTIN: dom_idx = SMB_LGRP_BUILTIN_IDX; break; default: *count = 0; return (SMB_LGRP_INVALID_ARG); } db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_count(db, dom_idx, count); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_numbydomain * * Returns the number of groups which have the given SID * as a member. */ int smb_lgrp_numbymember(nt_sid_t *msid, int *count) { smb_giter_t gi; smb_group_t grp; int rc; *count = 0; rc = smb_lgrp_iteropen(&gi); if (rc != SMB_LGRP_SUCCESS) return (rc); while (smb_lgrp_iterate(&gi, &grp) == SMB_LGRP_SUCCESS) { if (smb_lgrp_is_member(&grp, msid)) (*count)++; smb_lgrp_free(&grp); } smb_lgrp_iterclose(&gi); return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_free * * Frees the allocated memory for the fields of the given * group structure. Note that this function doesn't free * the group itself. */ void smb_lgrp_free(smb_group_t *grp) { int i; if (grp == NULL) return; free(grp->sg_name); free(grp->sg_cmnt); free(grp->sg_id.gs_sid); smb_privset_free(grp->sg_privs); for (i = 0; i < grp->sg_nmembers; i++) free(grp->sg_members[i].gs_sid); free(grp->sg_members); } /* * smb_lgrp_iteropen * * Initializes the given group iterator by opening * the group database and creating a virtual machine * for iteration. */ int smb_lgrp_iteropen(smb_giter_t *iter) { char *sql; char *errmsg = NULL; int rc = SMB_LGRP_SUCCESS; assert(iter); bzero(iter, sizeof (smb_giter_t)); sql = sqlite_mprintf("SELECT * FROM groups"); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); iter->sgi_db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); if (iter->sgi_db == NULL) { sqlite_freemem(sql); return (SMB_LGRP_DBOPEN_FAILED); } rc = sqlite_compile(iter->sgi_db, sql, NULL, &iter->sgi_vm, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to create a VM (%s)", NULL_MSGCHK(errmsg)); rc = SMB_LGRP_DB_ERROR; } return (rc); } /* * smb_lgrp_iterclose * * Closes the given group iterator. */ void smb_lgrp_iterclose(smb_giter_t *iter) { char *errmsg = NULL; int rc; assert(iter); rc = sqlite_finalize(iter->sgi_vm, &errmsg); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to destroy a VM (%s)", NULL_MSGCHK(errmsg)); } smb_lgrp_db_close(iter->sgi_db); } /* * smb_lgrp_iterate * * Iterate through group database * Group information is returned in provided group structure. * * Note that this function doesn't allocate the group * structure itself only the fields, so the given grp * pointer has to point to a group structure. * Caller must free the allocated memories for the fields * by calling smb_lgrp_free(). */ int smb_lgrp_iterate(smb_giter_t *iter, smb_group_t *grp) { const char **values; int ncol; int rc; int i; if (iter->sgi_vm == NULL || iter->sgi_db == NULL) return (SMB_LGRP_INVALID_ARG); bzero(grp, sizeof (smb_group_t)); rc = sqlite_step(iter->sgi_vm, &ncol, &values, NULL); if (rc == SQLITE_DONE) return (SMB_LGRP_NO_MORE); if (rc != SQLITE_ROW) return (SMB_LGRP_DBEXEC_FAILED); if (ncol != SMB_LGRP_GTBL_NCOL) return (SMB_LGRP_DB_ERROR); for (i = 0; i < ncol; i++) { if (values[i] == NULL) return (SMB_LGRP_DB_ERROR); } return (smb_lgrp_decode(grp, (char **)values, SMB_LGRP_INFO_ALL, iter->sgi_db)); } /* * smb_lgrp_is_member * * Check to see if the specified account is a member of * the given group. */ boolean_t smb_lgrp_is_member(smb_group_t *grp, nt_sid_t *sid) { int i; if (grp == NULL || grp->sg_members == NULL || sid == NULL) return (B_FALSE); for (i = 0; i < grp->sg_nmembers; i++) { if (nt_sid_is_equal(grp->sg_members[i].gs_sid, sid)) return (B_TRUE); } return (B_FALSE); } /* * smb_lgrp_strerror * * Returns a text for the given group error code. */ char * smb_lgrp_strerror(int errno) { switch (errno) { case SMB_LGRP_SUCCESS: return (dgettext(TEXT_DOMAIN, "success")); case SMB_LGRP_INVALID_ARG: return (dgettext(TEXT_DOMAIN, "invalid argument")); case SMB_LGRP_INVALID_MEMBER: return (dgettext(TEXT_DOMAIN, "invalid member type")); case SMB_LGRP_INVALID_NAME: return (dgettext(TEXT_DOMAIN, "invalid name")); case SMB_LGRP_NOT_FOUND: return (dgettext(TEXT_DOMAIN, "group not found")); case SMB_LGRP_EXISTS: return (dgettext(TEXT_DOMAIN, "group exists")); case SMB_LGRP_NO_SID: return (dgettext(TEXT_DOMAIN, "cannot obtain a SID")); case SMB_LGRP_NO_LOCAL_SID: return (dgettext(TEXT_DOMAIN, "cannot get the machine SID")); case SMB_LGRP_SID_NOTLOCAL: return (dgettext(TEXT_DOMAIN, "not a local SID")); case SMB_LGRP_WKSID: return (dgettext(TEXT_DOMAIN, "operation not permitted on well-known accounts")); case SMB_LGRP_NO_MEMORY: return (dgettext(TEXT_DOMAIN, "not enough memory")); case SMB_LGRP_DB_ERROR: return (dgettext(TEXT_DOMAIN, "database operation error")); case SMB_LGRP_DBINIT_ERROR: return (dgettext(TEXT_DOMAIN, "database initialization error")); case SMB_LGRP_INTERNAL_ERROR: return (dgettext(TEXT_DOMAIN, "internal error")); case SMB_LGRP_MEMBER_IN_GROUP: return (dgettext(TEXT_DOMAIN, "member already in the group")); case SMB_LGRP_MEMBER_NOT_IN_GROUP: return (dgettext(TEXT_DOMAIN, "not a member")); case SMB_LGRP_NO_SUCH_PRIV: return (dgettext(TEXT_DOMAIN, "no such privilege")); case SMB_LGRP_NO_SUCH_DOMAIN: return (dgettext(TEXT_DOMAIN, "no such domain SID")); case SMB_LGRP_PRIV_HELD: return (dgettext(TEXT_DOMAIN, "already holds the privilege")); case SMB_LGRP_PRIV_NOT_HELD: return (dgettext(TEXT_DOMAIN, "privilege not held")); case SMB_LGRP_BAD_DATA: return (dgettext(TEXT_DOMAIN, "bad data")); case SMB_LGRP_NO_MORE: return (dgettext(TEXT_DOMAIN, "no more groups")); case SMB_LGRP_DBOPEN_FAILED: return (dgettext(TEXT_DOMAIN, "failed openning database")); case SMB_LGRP_DBEXEC_FAILED: return (dgettext(TEXT_DOMAIN, "failed executing database operation")); case SMB_LGRP_DBINIT_FAILED: return (dgettext(TEXT_DOMAIN, "failed initializing database")); case SMB_LGRP_DOMLKP_FAILED: return (dgettext(TEXT_DOMAIN, "failed getting the domain SID")); case SMB_LGRP_DOMINS_FAILED: return (dgettext(TEXT_DOMAIN, "failed inserting the domain SID")); case SMB_LGRP_INSERT_FAILED: return (dgettext(TEXT_DOMAIN, "failed inserting the group")); case SMB_LGRP_DELETE_FAILED: return (dgettext(TEXT_DOMAIN, "failed deleting the group")); case SMB_LGRP_UPDATE_FAILED: return (dgettext(TEXT_DOMAIN, "failed updating the group")); case SMB_LGRP_LOOKUP_FAILED: return (dgettext(TEXT_DOMAIN, "failed looking up the group")); } return (dgettext(TEXT_DOMAIN, "unknown error code")); } /* * smb_lgrp_chkmember * * Determines valid account types for being member of * a local group. * * Currently, we just support users as valid members. */ static boolean_t smb_lgrp_chkmember(uint16_t sid_type) { return (sid_type == SidTypeUser); } /* * smb_lgrp_start * * Initializes the library private global variables. * If the database doesn't exist, it'll create it and adds the * predefined builtin groups. */ int smb_lgrp_start(void) { char *supported_bg[] = {"Administrators", "Backup Operators", "Power Users"}; well_known_account_t *wka; int rc, i, ngrp; char *lsid_str; (void) mutex_init(&smb_lgrp_lsid_mtx, USYNC_THREAD, NULL); (void) mutex_lock(&smb_lgrp_lsid_mtx); lsid_str = smb_config_get_localsid(); if (lsid_str == NULL) { (void) mutex_unlock(&smb_lgrp_lsid_mtx); return (SMB_LGRP_NO_LOCAL_SID); } smb_lgrp_lsid = nt_sid_strtosid(lsid_str); free(lsid_str); if (!nt_sid_is_valid(smb_lgrp_lsid)) { free(smb_lgrp_lsid); smb_lgrp_lsid = NULL; (void) mutex_unlock(&smb_lgrp_lsid_mtx); return (SMB_LGRP_NO_LOCAL_SID); } rc = smb_lgrp_db_init(); if (rc != SMB_LGRP_SUCCESS) { free(smb_lgrp_lsid); smb_lgrp_lsid = NULL; (void) mutex_unlock(&smb_lgrp_lsid_mtx); return (rc); } (void) mutex_unlock(&smb_lgrp_lsid_mtx); ngrp = sizeof (supported_bg) / sizeof (supported_bg[0]); for (i = 0; i < ngrp; i++) { wka = nt_builtin_lookup(supported_bg[i]); if (wka == NULL) continue; rc = smb_lgrp_add(wka->name, wka->desc); if (rc != SMB_LGRP_SUCCESS) syslog(LOG_DEBUG, "failed to add %s", wka->name); } return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_stop * * Unintialize the library global private variables. */ void smb_lgrp_stop(void) { (void) mutex_lock(&smb_lgrp_lsid_mtx); free(smb_lgrp_lsid); smb_lgrp_lsid = NULL; (void) mutex_unlock(&smb_lgrp_lsid_mtx); (void) mutex_destroy(&smb_lgrp_lsid_mtx); } /* * smb_lgrp_db_open * * Opens group database with the given mode. */ static sqlite * smb_lgrp_db_open(int mode) { sqlite *db; char *errmsg = NULL; db = sqlite_open(SMB_LGRP_DB_NAME, mode, &errmsg); if (db == NULL) { syslog(LOG_ERR, "failed to open group database (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); } return (db); } /* * smb_lgrp_db_close * * Closes the given database handle */ static void smb_lgrp_db_close(sqlite *db) { if (db) { sqlite_close(db); } } /* * smb_lgrp_db_init * * Creates the group database based on the defined SQL statement. * It also initializes db_info and domain tables. */ static int smb_lgrp_db_init(void) { int dbrc = SQLITE_OK; int rc = SMB_LGRP_SUCCESS; sqlite *db = NULL; char *errmsg = NULL; db = sqlite_open(SMB_LGRP_DB_NAME, 0600, &errmsg); if (db == NULL) { syslog(LOG_ERR, "failed to create group database (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_DBOPEN_FAILED); } sqlite_busy_timeout(db, SMB_LGRP_DB_TIMEOUT); dbrc = sqlite_exec(db, "BEGIN TRANSACTION;", NULL, NULL, &errmsg); if (dbrc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to begin database transaction (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); sqlite_close(db); return (SMB_LGRP_DBEXEC_FAILED); } switch (sqlite_exec(db, SMB_LGRP_DB_SQL, NULL, NULL, &errmsg)) { case SQLITE_ERROR: /* * This is the normal situation: CREATE probably failed because * tables already exist. It may indicate an error in SQL as well * but we cannot tell. */ sqlite_freemem(errmsg); dbrc = sqlite_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, &errmsg); rc = SMB_LGRP_SUCCESS; break; case SQLITE_OK: dbrc = sqlite_exec(db, "COMMIT TRANSACTION", NULL, NULL, &errmsg); if (dbrc != SQLITE_OK) break; rc = smb_lgrp_dtbl_insert(db, NT_BUILTIN_DOMAIN_SIDSTR, NULL); if (rc == SMB_LGRP_SUCCESS) rc = smb_lgrp_db_setinfo(db); if (rc != SMB_LGRP_SUCCESS) { (void) sqlite_close(db); (void) unlink(SMB_LGRP_DB_NAME); return (rc); } break; default: syslog(LOG_ERR, "failed to initialize group database (%s)", errmsg); sqlite_freemem(errmsg); dbrc = sqlite_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, &errmsg); rc = SMB_LGRP_DBINIT_FAILED; break; } if (dbrc != SQLITE_OK) { /* this is bad - database may be left in a locked state */ syslog(LOG_DEBUG, "failed to close a transaction (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); } (void) sqlite_close(db); return (rc); } /* * smb_lgrp_gtbl_lookup * * This is a flexible lookup function for the group database. * The key type can be specified by the 'key' arg and the actual key * values can be passed after the 'infolvl' arg. 'infolvl' arg specifies * what information items for the specified group is needed. * * Note that the function assumes the given key is unique and only * specifies one or 0 group. The keys that are supported now are * the group name and the group SID * * Note that this function doesn't allocate the group * structure itself only the fields, so the given grp * pointer has to point to a group structure. * Caller must free the allocated memories for the fields * by calling smb_lgrp_free(). */ static int smb_lgrp_gtbl_lookup(sqlite *db, int key, smb_group_t *grp, int infolvl, ...) { char *errmsg = NULL; char *sql; char **result; int nrow, ncol; int rc, dom_idx; smb_group_t kgrp; va_list ap; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); bzero(grp, sizeof (smb_group_t)); va_start(ap, infolvl); switch (key) { case SMB_LGRP_GTBL_NAME: kgrp.sg_name = va_arg(ap, char *); sql = sqlite_mprintf("SELECT * FROM groups WHERE name = '%s'", kgrp.sg_name); break; case SMB_LGRP_GTBL_SIDRID: kgrp.sg_rid = va_arg(ap, uint32_t); kgrp.sg_domain = va_arg(ap, smb_gdomain_t); dom_idx = (kgrp.sg_domain == SMB_LGRP_LOCAL) ? SMB_LGRP_LOCAL_IDX : SMB_LGRP_BUILTIN_IDX; sql = sqlite_mprintf("SELECT * FROM groups" "WHERE (sid_idx = %d) AND (sid_rid = %u)", dom_idx, kgrp.sg_rid); break; default: va_end(ap); return (SMB_LGRP_INVALID_ARG); } va_end(ap); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_LOOKUP_FAILED); } if (nrow == 0) { /* group not found */ sqlite_free_table(result); return (SMB_LGRP_NOT_FOUND); } if (nrow != 1 || ncol != SMB_LGRP_GTBL_NCOL) { sqlite_free_table(result); return (SMB_LGRP_DB_ERROR); } rc = smb_lgrp_decode(grp, &result[SMB_LGRP_GTBL_NCOL], infolvl, db); sqlite_free_table(result); return (rc); } /* * smb_lgrp_gtbl_exists * * Checks to see if the given group exists or not. */ static boolean_t smb_lgrp_gtbl_exists(sqlite *db, char *gname) { char *errmsg = NULL; char *sql; char **result; int nrow, ncol; int rc; if (db == NULL) return (NULL); sql = sqlite_mprintf("SELECT name FROM groups WHERE name = '%s'", gname); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (B_FALSE); } sqlite_free_table(result); return (nrow != 0); } /* * smb_lgrp_gtbl_count * * Counts the number of groups in the domain specified by * 'dom_idx' */ static int smb_lgrp_gtbl_count(sqlite *db, int dom_idx, int *count) { char *errmsg = NULL; char *sql; char **result; int nrow, ncol; int rc; *count = 0; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); sql = sqlite_mprintf("SELECT sid_idx FROM groups WHERE sid_idx = %d", dom_idx); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to count (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_LOOKUP_FAILED); } sqlite_free_table(result); if (ncol > 1) return (SMB_LGRP_DB_ERROR); *count = nrow; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_gtbl_insert * * Insert a record for the given group in the group database. * * NOTE: this function assumes that this group has no members * at this time. */ static int smb_lgrp_gtbl_insert(sqlite *db, smb_group_t *grp) { smb_lgpid_t privs[SE_MAX_LUID + 1]; smb_lgplist_t plist; char *errmsg = NULL; char *sql; int dom_idx; int rc; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); dom_idx = (grp->sg_domain == SMB_LGRP_LOCAL) ? SMB_LGRP_LOCAL_IDX : SMB_LGRP_BUILTIN_IDX; plist.p_cnt = SE_MAX_LUID; plist.p_ids = privs; smb_lgrp_encode_privset(grp, &plist); sql = sqlite_mprintf("INSERT INTO groups" "(name, sid_idx, sid_rid, sid_type, sid_attrs, comment, " "n_privs, privs, n_members, members) " "VALUES('%s', %u, %u, %u, %u, '%q', %u, '%q', %u, '%q')", grp->sg_name, dom_idx, grp->sg_rid, grp->sg_id.gs_type, grp->sg_attr, (grp->sg_cmnt) ? grp->sg_cmnt : "", plist.p_cnt, (char *)plist.p_ids, 0, ""); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to insert %s (%s)", grp->sg_name, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_INSERT_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_gtbl_delete * * Removes the specified group from the database */ static int smb_lgrp_gtbl_delete(sqlite *db, char *gname) { char *errmsg = NULL; char *sql; int rc; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); sql = sqlite_mprintf("DELETE FROM groups WHERE name = '%s'", gname); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to delete %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_DELETE_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_gtbl_update * * Updates the specified group information, the supported items * are group name and comment */ static int smb_lgrp_gtbl_update(sqlite *db, char *gname, smb_group_t *grp, int col_id) { char *errmsg = NULL; char *sql; int rc; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); /* UPDATE doesn't fail if gname doesn't exist */ if (!smb_lgrp_gtbl_exists(db, gname)) return (SMB_LGRP_NOT_FOUND); switch (col_id) { case SMB_LGRP_GTBL_NAME: if (smb_lgrp_gtbl_exists(db, grp->sg_name)) return (SMB_LGRP_EXISTS); sql = sqlite_mprintf("UPDATE groups SET name = '%s' " "WHERE name = '%s'", grp->sg_name, gname); break; case SMB_LGRP_GTBL_CMNT: sql = sqlite_mprintf("UPDATE groups SET comment = '%q' " "WHERE name = '%s'", grp->sg_cmnt, gname); break; default: return (SMB_LGRP_INVALID_ARG); } if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to update %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_UPDATE_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_gtbl_update_mlist * * Adds/removes the specified member from the member list of the * given group */ static int smb_lgrp_gtbl_update_mlist(sqlite *db, char *gname, smb_gsid_t *member, int flags) { smb_lgmlist_t new_members; smb_lgmlist_t members; smb_lgmid_t mid; char *errmsg = NULL; char *sql; char **result; int nrow, ncol; int rc; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); sql = sqlite_mprintf("SELECT n_members, members FROM groups " "WHERE name = '%s'", gname); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_LOOKUP_FAILED); } if (nrow == 0) { /* group not found */ sqlite_free_table(result); return (SMB_LGRP_NOT_FOUND); } if (nrow != 1 || ncol != 2) { sqlite_free_table(result); return (SMB_LGRP_DB_ERROR); } bzero(&mid, sizeof (mid)); mid.m_type = member->gs_type; rc = smb_lgrp_dtbl_getidx(db, member->gs_sid, mid.m_type, &mid.m_idx, &mid.m_rid); if (rc != SMB_LGRP_SUCCESS) { sqlite_free_table(result); return (rc); } members.m_cnt = atoi(result[2]); members.m_ids = result[3]; switch (flags) { case SMB_LGRP_DB_ADDMEMBER: rc = smb_lgrp_mlist_add(&members, &mid, &new_members); break; case SMB_LGRP_DB_DELMEMBER: rc = smb_lgrp_mlist_del(&members, &mid, &new_members); break; default: rc = SMB_LGRP_INVALID_ARG; } sqlite_free_table(result); if (rc != SMB_LGRP_SUCCESS) return (rc); sql = sqlite_mprintf("UPDATE groups SET n_members = %u, members = '%s'" " WHERE name = '%s'", new_members.m_cnt, new_members.m_ids, gname); free(new_members.m_ids); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to update %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_UPDATE_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_gtbl_update_plist * * Adds/removes the specified privilege from the privilege list of the * given group */ static int smb_lgrp_gtbl_update_plist(sqlite *db, char *gname, uint8_t priv_id, boolean_t enable) { char *sql; char *errmsg = NULL; char **result; int nrow, ncol; int rc; smb_lgplist_t privs; smb_lgplist_t new_privs; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); sql = sqlite_mprintf("SELECT n_privs, privs FROM groups " "WHERE name = '%s'", gname); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_LOOKUP_FAILED); } if (nrow == 0) { /* group not found */ sqlite_free_table(result); return (SMB_LGRP_NOT_FOUND); } if (nrow != 1 || ncol != 2) { sqlite_free_table(result); return (SMB_LGRP_DB_ERROR); } privs.p_cnt = atoi(result[2]); privs.p_ids = (smb_lgpid_t *)result[3]; if (enable) rc = smb_lgrp_plist_add(&privs, priv_id, &new_privs); else rc = smb_lgrp_plist_del(&privs, priv_id, &new_privs); sqlite_free_table(result); if (rc != SMB_LGRP_SUCCESS) return (rc); sql = sqlite_mprintf("UPDATE groups SET n_privs = %u, privs = '%q'" " WHERE name = '%s'", new_privs.p_cnt, (char *)new_privs.p_ids, gname); free(new_privs.p_ids); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to update %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_UPDATE_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_dtbl_insert * * Inserts the specified domain SID in the dmain table. * Upon successful insert the index will be returned in * 'dom_idx' arg. */ static int smb_lgrp_dtbl_insert(sqlite *db, char *dom_sid, uint32_t *dom_idx) { char *errmsg = NULL; char *sql; int rc; sql = sqlite_mprintf("INSERT INTO domains (dom_sid, dom_cnt)" " VALUES('%s', 1);", dom_sid); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to insert domain SID (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_DOMINS_FAILED); } if (dom_idx) *dom_idx = sqlite_last_insert_rowid(db); return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_dtbl_getidx * * Searches the domain table for the domain SID of the * given member SID. If it finds the domain SID it'll * return the index and the RID, otherwise it'll insert * it in the domain table as a new SID. */ static int smb_lgrp_dtbl_getidx(sqlite *db, nt_sid_t *sid, uint16_t sid_type, uint32_t *dom_idx, uint32_t *rid) { char sidstr[NT_SID_FMTBUF_SIZE]; nt_sid_t *dom_sid; char **result; int nrow, ncol; char *errmsg = NULL; char *sql; int rc; if (nt_sid_is_indomain(smb_lgrp_lsid, sid)) { /* This is a local SID */ int id_type = (sid_type == SidTypeUser) ? SMB_IDMAP_USER : SMB_IDMAP_GROUP; *dom_idx = SMB_LGRP_LOCAL_IDX; if (smb_idmap_getid(sid, rid, &id_type) != IDMAP_SUCCESS) return (SMB_LGRP_INTERNAL_ERROR); return (SMB_LGRP_SUCCESS); } dom_sid = nt_sid_dup(sid); if (dom_sid == NULL) return (SMB_LGRP_NO_MEMORY); (void) nt_sid_split(dom_sid, rid); nt_sid_format2(dom_sid, sidstr); free(dom_sid); sql = sqlite_mprintf("SELECT dom_idx FROM domains WHERE dom_sid = '%s'", sidstr); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup domain SID (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_DOMLKP_FAILED); } switch (nrow) { case 0: /* new domain SID; insert it into the domains table */ sqlite_free_table(result); return (smb_lgrp_dtbl_insert(db, sidstr, dom_idx)); case 1: *dom_idx = atoi(result[1]); sqlite_free_table(result); return (SMB_LGRP_SUCCESS); } sqlite_free_table(result); return (SMB_LGRP_DB_ERROR); } /* * smb_lgrp_dtbl_getsid * * Searchs the domain table for the given domain index. * Converts the found domain SID to binary format and * returns it in the 'sid' arg. * * Caller must free the returned SID by calling free(). */ static int smb_lgrp_dtbl_getsid(sqlite *db, uint32_t dom_idx, nt_sid_t **sid) { char **result; int nrow, ncol; char *errmsg = NULL; char *sql; int rc; sql = sqlite_mprintf("SELECT dom_sid FROM domains WHERE dom_idx = %u", dom_idx); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup domain index (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_DOMLKP_FAILED); } switch (nrow) { case 0: rc = SMB_LGRP_NO_SUCH_DOMAIN; break; case 1: *sid = nt_sid_strtosid(result[1]); rc = (*sid == NULL) ? SMB_LGRP_INTERNAL_ERROR : SMB_LGRP_SUCCESS; break; default: rc = SMB_LGRP_DB_ERROR; break; } sqlite_free_table(result); return (rc); } /* * smb_lgrp_db_setinfo * * Initializes the db_info table upon database creation. */ static int smb_lgrp_db_setinfo(sqlite *db) { char *errmsg = NULL; char *sql; int rc; sql = sqlite_mprintf("INSERT INTO db_info (ver_major, ver_minor," " magic) VALUES (%d, %d, %u)", SMB_LGRP_DB_VERMAJOR, SMB_LGRP_DB_VERMINOR, SMB_LGRP_DB_MAGIC); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to insert database information (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_DBINIT_ERROR; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_mlist_add * * Adds the given member (newm) to the input member list (in_members) * if it's not already there. The result list will be returned in * out_members. The caller must free the allocated memory for * out_members by calling free(). * * in_members and out_members are hex strings. */ static int smb_lgrp_mlist_add(smb_lgmlist_t *in_members, smb_lgmid_t *newm, smb_lgmlist_t *out_members) { char mid_hex[SMB_LGRP_MID_HEXSZ]; char *in_list; char *out_list; int in_size; int out_size; int mid_hexsz; int i; out_members->m_cnt = 0; out_members->m_ids = NULL; bzero(mid_hex, sizeof (mid_hex)); mid_hexsz = bintohex((const char *)newm, sizeof (smb_lgmid_t), mid_hex, sizeof (mid_hex)); /* * Check to see if this is already a group member */ in_list = in_members->m_ids; for (i = 0; i < in_members->m_cnt; i++) { if (strncmp(in_list, mid_hex, mid_hexsz) == 0) return (SMB_LGRP_MEMBER_IN_GROUP); in_list += mid_hexsz; } in_size = (in_members->m_ids) ? strlen(in_members->m_ids) : 0; out_size = in_size + sizeof (mid_hex) + 1; out_list = malloc(out_size); if (out_list == NULL) return (SMB_LGRP_NO_MEMORY); bzero(out_list, out_size); if (in_members->m_ids) (void) strlcpy(out_list, in_members->m_ids, out_size); (void) strcat(out_list, mid_hex); out_members->m_cnt = in_members->m_cnt + 1; out_members->m_ids = out_list; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_mlist_del * * Removes the given member (msid) from the input member list * (in_members) if it's already there. The result list will b * returned in out_members. The caller must free the allocated * memory for out_members by calling free(). * * in_members and out_members are hex strings. */ static int smb_lgrp_mlist_del(smb_lgmlist_t *in_members, smb_lgmid_t *mid, smb_lgmlist_t *out_members) { char mid_hex[SMB_LGRP_MID_HEXSZ]; char *in_list; char *out_list; int in_size; int out_size; int mid_hexsz; int out_cnt; int i; out_members->m_cnt = 0; out_members->m_ids = NULL; if ((in_members == NULL) || (in_members->m_cnt == 0)) return (SMB_LGRP_MEMBER_NOT_IN_GROUP); in_size = strlen(in_members->m_ids); out_size = in_size + sizeof (mid_hex) + 1; out_list = malloc(out_size); if (out_list == NULL) return (SMB_LGRP_NO_MEMORY); *out_list = '\0'; bzero(mid_hex, sizeof (mid_hex)); mid_hexsz = bintohex((const char *)mid, sizeof (smb_lgmid_t), mid_hex, sizeof (mid_hex)); in_list = in_members->m_ids; for (i = 0, out_cnt = 0; i < in_members->m_cnt; i++) { if (strncmp(in_list, mid_hex, mid_hexsz)) { (void) strncat(out_list, in_list, mid_hexsz); out_cnt++; } in_list += mid_hexsz; } if (out_cnt == in_members->m_cnt) { free(out_list); return (SMB_LGRP_MEMBER_NOT_IN_GROUP); } out_members->m_cnt = out_cnt; out_members->m_ids = out_list; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_plist_add * * Adds the given privilege to the input list (in_privs) * if it's not already there. The result list is returned * in out_privs. The caller must free the allocated memory * for out_privs by calling free(). */ static int smb_lgrp_plist_add(smb_lgplist_t *in_privs, smb_lgpid_t priv_id, smb_lgplist_t *out_privs) { int i, size; smb_lgpid_t *pbuf; out_privs->p_cnt = 0; out_privs->p_ids = NULL; for (i = 0; i < in_privs->p_cnt; i++) { if (in_privs->p_ids[i] == priv_id) return (SMB_LGRP_PRIV_HELD); } size = (in_privs->p_cnt + 1) * sizeof (smb_lgpid_t) + 1; pbuf = malloc(size); if (pbuf == NULL) return (SMB_LGRP_NO_MEMORY); bzero(pbuf, size); bcopy(in_privs->p_ids, pbuf, in_privs->p_cnt * sizeof (smb_lgpid_t)); pbuf[in_privs->p_cnt] = priv_id; out_privs->p_cnt = in_privs->p_cnt + 1; out_privs->p_ids = pbuf; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_plist_del * * Removes the given privilege from the input list (in_privs) * if it's already there. The result list is returned * in out_privs. The caller must free the allocated memory * for out_privs by calling free(). */ static int smb_lgrp_plist_del(smb_lgplist_t *in_privs, smb_lgpid_t priv_id, smb_lgplist_t *out_privs) { int i, size; out_privs->p_cnt = 0; out_privs->p_ids = NULL; if ((in_privs == NULL) || (in_privs->p_cnt == 0)) return (SMB_LGRP_PRIV_NOT_HELD); size = (in_privs->p_cnt - 1) * sizeof (smb_lgpid_t) + 1; out_privs->p_ids = malloc(size); if (out_privs->p_ids == NULL) return (SMB_LGRP_NO_MEMORY); bzero(out_privs->p_ids, size); for (i = 0; i < in_privs->p_cnt; i++) { if (in_privs->p_ids[i] != priv_id) out_privs->p_ids[out_privs->p_cnt++] = in_privs->p_ids[i]; } if (out_privs->p_cnt == in_privs->p_cnt) { free(out_privs->p_ids); out_privs->p_cnt = 0; out_privs->p_ids = NULL; return (SMB_LGRP_PRIV_NOT_HELD); } return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_encode_privset * * Encodes given privilege set into a buffer to be stored in the group * database. Each entry of the encoded buffer contains the privilege ID * of an enable privilege. The returned buffer is null-terminated. */ static void smb_lgrp_encode_privset(smb_group_t *grp, smb_lgplist_t *plist) { smb_privset_t *privs; uint32_t pcnt = plist->p_cnt; int i; bzero(plist->p_ids, sizeof (smb_lgpid_t) * plist->p_cnt); plist->p_cnt = 0; privs = grp->sg_privs; if ((privs == NULL) || (privs->priv_cnt == 0)) return; if (pcnt < privs->priv_cnt) { assert(0); } for (i = 0; i < privs->priv_cnt; i++) { if (privs->priv[i].attrs == SE_PRIVILEGE_ENABLED) { plist->p_ids[plist->p_cnt++] = (uint8_t)privs->priv[i].luid.lo_part; } } } /* * smb_lgrp_decode_privset * * Decodes the privilege information read from group table * (nprivs, privs) into a binray format specified by the * privilege field of smb_group_t */ static int smb_lgrp_decode_privset(smb_group_t *grp, char *nprivs, char *privs) { smb_lgplist_t plist; int i; plist.p_cnt = atoi(nprivs); if (strlen(privs) != plist.p_cnt) return (SMB_LGRP_BAD_DATA); plist.p_ids = (smb_lgpid_t *)privs; grp->sg_privs = smb_privset_new(); if (grp->sg_privs == NULL) return (SMB_LGRP_NO_MEMORY); for (i = 0; i < plist.p_cnt; i++) smb_privset_enable(grp->sg_privs, plist.p_ids[i]); return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_decode_members * * Decodes the members information read from group table * (nmembers, members) into a binray format specified by the * member fields of smb_group_t */ static int smb_lgrp_decode_members(smb_group_t *grp, char *nmembers, char *members, sqlite *db) { smb_lgmid_t *m_ids; smb_lgmid_t *mid; smb_gsid_t *member; int mids_size; int i, rc; grp->sg_nmembers = atoi(nmembers); mids_size = grp->sg_nmembers * sizeof (smb_lgmid_t); m_ids = malloc(mids_size); if (m_ids == NULL) return (SMB_LGRP_NO_MEMORY); grp->sg_members = malloc(grp->sg_nmembers * sizeof (smb_gsid_t)); if (grp->sg_members == NULL) { free(m_ids); return (SMB_LGRP_NO_MEMORY); } (void) hextobin(members, strlen(members), (char *)m_ids, mids_size); mid = m_ids; member = grp->sg_members; for (i = 0; i < grp->sg_nmembers; i++, mid++, member++) { rc = smb_lgrp_getsid(mid->m_idx, &mid->m_rid, mid->m_type, db, &member->gs_sid); if (rc != SMB_LGRP_SUCCESS) { free(m_ids); return (SMB_LGRP_DB_ERROR); } member->gs_type = mid->m_type; } free(m_ids); return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_decode * * Fills out the fields of the given group (grp) based in the * string information read from the group table. infolvl determines * which fields are requested and need to be decoded. * * Allocated memories must be freed by calling smb_lgrp_free() * upon successful return. */ static int smb_lgrp_decode(smb_group_t *grp, char **values, int infolvl, sqlite *db) { uint32_t sid_idx; int rc; if (infolvl == SMB_LGRP_INFO_NONE) return (SMB_LGRP_SUCCESS); if (infolvl & SMB_LGRP_INFO_NAME) { grp->sg_name = strdup(values[SMB_LGRP_GTBL_NAME]); if (grp->sg_name == NULL) return (SMB_LGRP_NO_MEMORY); } if (infolvl & SMB_LGRP_INFO_CMNT) { grp->sg_cmnt = strdup(values[SMB_LGRP_GTBL_CMNT]); if (grp->sg_cmnt == NULL) { smb_lgrp_free(grp); return (SMB_LGRP_NO_MEMORY); } } if (infolvl & SMB_LGRP_INFO_SID) { sid_idx = atoi(values[SMB_LGRP_GTBL_SIDIDX]); grp->sg_rid = atoi(values[SMB_LGRP_GTBL_SIDRID]); grp->sg_attr = atoi(values[SMB_LGRP_GTBL_SIDATR]); grp->sg_id.gs_type = atoi(values[SMB_LGRP_GTBL_SIDTYP]); rc = smb_lgrp_getsid(sid_idx, &grp->sg_rid, grp->sg_id.gs_type, db, &grp->sg_id.gs_sid); if (rc != SMB_LGRP_SUCCESS) { smb_lgrp_free(grp); return (SMB_LGRP_NO_MEMORY); } grp->sg_domain = (sid_idx == SMB_LGRP_LOCAL_IDX) ? SMB_LGRP_LOCAL : SMB_LGRP_BUILTIN; } if (infolvl & SMB_LGRP_INFO_PRIV) { rc = smb_lgrp_decode_privset(grp, values[SMB_LGRP_GTBL_NPRIVS], values[SMB_LGRP_GTBL_PRIVS]); if (rc != SMB_LGRP_SUCCESS) { smb_lgrp_free(grp); return (rc); } } if (infolvl & SMB_LGRP_INFO_MEMB) { rc = smb_lgrp_decode_members(grp, values[SMB_LGRP_GTBL_NMEMBS], values[SMB_LGRP_GTBL_MEMBS], db); if (rc != SMB_LGRP_SUCCESS) { smb_lgrp_free(grp); return (rc); } } return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_chkname * * User account names are limited to 20 characters and group names are * limited to 256 characters. In addition, account names cannot be terminated * by a period and they cannot include commas or any of the following printable * characters: ", /, \, [, ], :, |, <, >, +, =, ;, ?, *. * Names also cannot include characters in the range 1-31, which are * nonprintable. * * Source: MSDN, description of NetLocalGroupAdd function. */ static boolean_t smb_lgrp_chkname(char *name) { static char *invalid_chars = "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017" "\020\021\022\023\024\025\026\027\030\031" "\"/\\[]:|<>+=;,*?"; int len, i; if (name == NULL || *name == '\0') return (B_FALSE); len = strlen(name); if (len > SMB_LGRP_NAME_MAX) return (B_FALSE); if (name[len - 1] == '.') return (B_FALSE); for (i = 0; i < len; i++) if (strchr(invalid_chars, name[i])) return (B_FALSE); (void) utf8_strlwr(name); return (B_TRUE); } /* * smb_lgrp_set_default_privs * * set default privileges for Administrators and Backup Operators */ static void smb_lgrp_set_default_privs(smb_group_t *grp) { if (utf8_strcasecmp(grp->sg_name, "Administrators") == 0) { smb_privset_enable(grp->sg_privs, SE_TAKE_OWNERSHIP_LUID); return; } if (utf8_strcasecmp(grp->sg_name, "Backup Operators") == 0) { smb_privset_enable(grp->sg_privs, SE_BACKUP_LUID); smb_privset_enable(grp->sg_privs, SE_RESTORE_LUID); return; } } /* * smb_lgrp_getsid * * Returns a SID based on the provided information * If dom_idx is 0, it means 'rid' contains a UID/GID and the * returned SID will be a local SID. If dom_idx is not 0 then * the domain SID will be fetched from the domain table. */ static int smb_lgrp_getsid(int dom_idx, uint32_t *rid, uint16_t sid_type, sqlite *db, nt_sid_t **sid) { nt_sid_t *dom_sid = NULL; nt_sid_t *res_sid = NULL; int id_type; int rc; *sid = NULL; if (dom_idx == SMB_LGRP_LOCAL_IDX) { id_type = (sid_type == SidTypeUser) ? SMB_IDMAP_USER : SMB_IDMAP_GROUP; if (smb_idmap_getsid(*rid, id_type, &res_sid) != IDMAP_SUCCESS) return (SMB_LGRP_NO_SID); /* * Make sure the returned SID is local */ if (!nt_sid_is_indomain(smb_lgrp_lsid, res_sid)) { free(res_sid); return (SMB_LGRP_SID_NOTLOCAL); } (void) nt_sid_get_rid(res_sid, rid); *sid = res_sid; return (SMB_LGRP_SUCCESS); } rc = smb_lgrp_dtbl_getsid(db, dom_idx, &dom_sid); if (rc != SMB_LGRP_SUCCESS) return (SMB_LGRP_DB_ERROR); res_sid = nt_sid_splice(dom_sid, *rid); free(dom_sid); if (res_sid == NULL) return (SMB_LGRP_NO_MEMORY); *sid = res_sid; return (SMB_LGRP_SUCCESS); }