# # 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 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2019 Nexenta by DDN, Inc. All rights reserved. # Copyright (c) 2018, Joyent, Inc. # PROG= smbadm SRCS= smbadm.c include ../../Makefile.cmd include ../../Makefile.ctf include ../Makefile.smbsrv.defs OBJS= smbadm.o smbinfo.o LDLIBS += -L$(ROOT)/usr/lib/smbsrv -lsmb -lsmbns \ -lsecdb -lofmt -lcmdutils -lumem LDFLAGS += -R/usr/lib/smbsrv .KEEP_STATE: SMOFF += free all: $(PROG) clean: $(RM) $(OBJS) lint: lint_SRCS $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) include ../../Makefile.targ install: all .WAIT $(ROOTUSRSBINPROG) /* * 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 by DDN, Inc. All rights reserved. * Copyright 2022-2023 RackTop Systems, Inc. */ /* * This module contains smbadm CLI which offers smb configuration * functionalities. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smbadm.h" #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif typedef enum { HELP_ADD_MEMBER, HELP_CREATE, HELP_DELETE, HELP_DEL_MEMBER, HELP_GET, HELP_JOIN, HELP_LIST, HELP_LIST_DOMAINS, HELP_LIST_SESS, HELP_LIST_TREES, HELP_LIST_OFILES, HELP_CLOSE_SESS, HELP_CLOSE_OFILE, HELP_LOOKUP, HELP_RENAME, HELP_SET, HELP_SHOW, HELP_USER_DISABLE, HELP_USER_ENABLE, HELP_USER_DELETE } smbadm_help_t; #define SMBADM_CMDF_NONE 0x00 #define SMBADM_CMDF_USER 0x01 /* needs smb_pwd_init */ #define SMBADM_CMDF_GROUP 0x02 /* needs smb_lgrp_start */ #define SMBADM_CMDF_KMOD 0x04 /* needs smb_kmod_bind */ #define SMBADM_CMDF_TYPEMASK 0x0F typedef enum { SMBADM_GRP_ADDMEMBER = 0, SMBADM_GRP_DELMEMBER, } smbadm_grp_action_t; #define SMBADM_ANSBUFSIZ 64 typedef struct smbadm_cmdinfo { char *name; int (*func)(int, char **); smbadm_help_t usage; uint32_t flags; char *auth; } smbadm_cmdinfo_t; smbadm_cmdinfo_t *curcmd; static char *progname; #define SMBADM_ACTION_AUTH "solaris.smf.manage.smb" #define SMBADM_VALUE_AUTH "solaris.smf.value.smb" #define SMBADM_BASIC_AUTH "solaris.network.hosts.read" static boolean_t smbadm_checkauth(const char *); static void smbadm_usage(boolean_t) __NORETURN; static int smbadm_join_workgroup(const char *, boolean_t); static int smbadm_join_domain(const char *, const char *, const char *, boolean_t); static void smbadm_extract_domain(char *, char **, char **); static void smbadm_update_groups(smbadm_grp_action_t); static int smbadm_join(int, char **); static int smbadm_list(int, char **); static int smbadm_lookup(int, char **); static void smbadm_lookup_name(char *, boolean_t); static void smbadm_lookup_sid(char *, boolean_t); static int smbadm_group_create(int, char **); static int smbadm_group_delete(int, char **); static int smbadm_group_rename(int, char **); static int smbadm_group_show(int, char **); static void smbadm_group_show_name(const char *, const char *); static int smbadm_group_getprop(int, char **); static int smbadm_group_setprop(int, char **); static int smbadm_group_addmember(int, char **); static int smbadm_group_delmember(int, char **); static int smbadm_group_add_del_member(char *, char *, smbadm_grp_action_t); static int smbadm_user_delete(int, char **); static int smbadm_user_disable(int, char **); static int smbadm_user_enable(int, char **); /* Please keep the order consistent with smbadm(8) man page */ static smbadm_cmdinfo_t smbadm_cmdtable[] = { { "create", smbadm_group_create, HELP_CREATE, SMBADM_CMDF_GROUP, SMBADM_ACTION_AUTH }, { "delete", smbadm_group_delete, HELP_DELETE, SMBADM_CMDF_GROUP, SMBADM_ACTION_AUTH }, { "rename", smbadm_group_rename, HELP_RENAME, SMBADM_CMDF_GROUP, SMBADM_ACTION_AUTH }, { "show", smbadm_group_show, HELP_SHOW, SMBADM_CMDF_GROUP, SMBADM_ACTION_AUTH }, { "get", smbadm_group_getprop, HELP_GET, SMBADM_CMDF_GROUP, SMBADM_ACTION_AUTH }, { "set", smbadm_group_setprop, HELP_SET, SMBADM_CMDF_GROUP, SMBADM_ACTION_AUTH }, { "add-member", smbadm_group_addmember, HELP_ADD_MEMBER, SMBADM_CMDF_GROUP, SMBADM_ACTION_AUTH }, { "remove-member", smbadm_group_delmember, HELP_DEL_MEMBER, SMBADM_CMDF_GROUP, SMBADM_ACTION_AUTH }, { "delete-user", smbadm_user_delete, HELP_USER_DELETE, SMBADM_CMDF_USER, SMBADM_ACTION_AUTH }, { "disable-user", smbadm_user_disable, HELP_USER_DISABLE, SMBADM_CMDF_USER, SMBADM_ACTION_AUTH }, { "enable-user", smbadm_user_enable, HELP_USER_ENABLE, SMBADM_CMDF_USER, SMBADM_ACTION_AUTH }, { "join", smbadm_join, HELP_JOIN, SMBADM_CMDF_GROUP, SMBADM_VALUE_AUTH }, /* "list" is now an alias for "list-domains" */ { "list", smbadm_list, HELP_LIST, SMBADM_CMDF_NONE, SMBADM_BASIC_AUTH }, { "list-domains", smbadm_list, HELP_LIST_DOMAINS, SMBADM_CMDF_NONE, SMBADM_BASIC_AUTH }, { "list-sessions", cmd_list_sess, HELP_LIST_SESS, SMBADM_CMDF_KMOD, SMBADM_BASIC_AUTH }, { "list-trees", cmd_list_trees, HELP_LIST_TREES, SMBADM_CMDF_KMOD, SMBADM_BASIC_AUTH }, { "list-ofiles", cmd_list_ofiles, HELP_LIST_OFILES, SMBADM_CMDF_KMOD, SMBADM_BASIC_AUTH }, { "close-session", cmd_close_sess, HELP_CLOSE_SESS, SMBADM_CMDF_KMOD, SMBADM_BASIC_AUTH }, { "close-ofile", cmd_close_ofile, HELP_CLOSE_OFILE, SMBADM_CMDF_KMOD, SMBADM_BASIC_AUTH }, { "lookup", smbadm_lookup, HELP_LOOKUP, SMBADM_CMDF_NONE, SMBADM_BASIC_AUTH }, }; #define SMBADM_NCMD (sizeof (smbadm_cmdtable) / sizeof (smbadm_cmdtable[0])) typedef struct smbadm_prop { char *p_name; char *p_value; } smbadm_prop_t; typedef struct smbadm_prop_handle { char *p_name; char *p_dispvalue; int (*p_setfn)(char *, smbadm_prop_t *); int (*p_getfn)(char *, smbadm_prop_t *); boolean_t (*p_chkfn)(smbadm_prop_t *); } smbadm_prop_handle_t; static boolean_t smbadm_prop_validate(smbadm_prop_t *prop, boolean_t chkval); static int smbadm_prop_parse(char *arg, smbadm_prop_t *prop); static smbadm_prop_handle_t *smbadm_prop_gethandle(char *pname); static boolean_t smbadm_chkprop_priv(smbadm_prop_t *prop); static int smbadm_setprop_tkowner(char *gname, smbadm_prop_t *prop); static int smbadm_getprop_tkowner(char *gname, smbadm_prop_t *prop); static int smbadm_setprop_readfile(char *gname, smbadm_prop_t *prop); static int smbadm_getprop_readfile(char *gname, smbadm_prop_t *prop); static int smbadm_setprop_writefile(char *gname, smbadm_prop_t *prop); static int smbadm_getprop_writefile(char *gname, smbadm_prop_t *prop); static int smbadm_setprop_backup(char *gname, smbadm_prop_t *prop); static int smbadm_getprop_backup(char *gname, smbadm_prop_t *prop); static int smbadm_setprop_restore(char *gname, smbadm_prop_t *prop); static int smbadm_getprop_restore(char *gname, smbadm_prop_t *prop); static int smbadm_setprop_desc(char *gname, smbadm_prop_t *prop); static int smbadm_getprop_desc(char *gname, smbadm_prop_t *prop); static smbadm_prop_handle_t smbadm_ptable[] = { {"backup", "on|off", smbadm_setprop_backup, smbadm_getprop_backup, smbadm_chkprop_priv }, {"restore", "on|off", smbadm_setprop_restore, smbadm_getprop_restore, smbadm_chkprop_priv }, {"take-ownership", "on|off", smbadm_setprop_tkowner, smbadm_getprop_tkowner, smbadm_chkprop_priv }, {"bypass-read", "on|off", smbadm_setprop_readfile, smbadm_getprop_readfile, smbadm_chkprop_priv }, {"bypass-write", "on|off", smbadm_setprop_writefile, smbadm_getprop_writefile, smbadm_chkprop_priv }, {"description", "", smbadm_setprop_desc, smbadm_getprop_desc, NULL }, }; static int smbadm_init(void); static void smbadm_fini(void); static const char *smbadm_pwd_strerror(int error); /* * Number of supported properties */ #define SMBADM_NPROP (sizeof (smbadm_ptable) / sizeof (smbadm_ptable[0])) static void smbadm_cmdusage(FILE *fp, smbadm_cmdinfo_t *cmd) { switch (cmd->usage) { case HELP_ADD_MEMBER: (void) fprintf(fp, gettext("\t%s -m [-m ]... \n"), cmd->name); return; case HELP_CREATE: (void) fprintf(fp, gettext("\t%s [-d ] \n"), cmd->name); return; case HELP_DELETE: (void) fprintf(fp, gettext("\t%s \n"), cmd->name); return; case HELP_USER_DELETE: case HELP_USER_DISABLE: case HELP_USER_ENABLE: (void) fprintf(fp, gettext("\t%s \n"), cmd->name); return; case HELP_GET: (void) fprintf(fp, gettext("\t%s [-p ]... \n"), cmd->name); return; case HELP_JOIN: #if 0 /* Don't document "-p" yet, still needs work (NEX-11960) */ (void) fprintf(fp, gettext("\t%s [-y] -p \n" "\t%s [-y] [-c container] -u \n" "\t%s [-y] -w \n"), cmd->name, cmd->name, cmd->name); #else (void) fprintf(fp, gettext( "\t%s [-y] [-c container] -u \n" "\t%s [-y] -w \n"), cmd->name, cmd->name); #endif return; case HELP_LIST: (void) fprintf(fp, gettext( "\t%s (alias for list-domains)\n"), cmd->name); return; case HELP_LIST_DOMAINS: (void) fprintf(fp, gettext("\t%s\n"), cmd->name); return; case HELP_LIST_SESS: case HELP_LIST_TREES: case HELP_LIST_OFILES: (void) fprintf(fp, gettext( "\t%s [-p] [-o field,...]\n"), cmd->name); return; case HELP_CLOSE_SESS: (void) fprintf(fp, gettext( "\t%s [user_name]\n"), cmd->name); return; case HELP_CLOSE_OFILE: (void) fprintf(fp, gettext("\t%s \n"), cmd->name); return; case HELP_LOOKUP: (void) fprintf(fp, gettext("\t%s [-p] \n"), cmd->name); return; case HELP_DEL_MEMBER: (void) fprintf(fp, gettext("\t%s -m [-m ]... \n"), cmd->name); return; case HELP_RENAME: (void) fprintf(fp, gettext("\t%s \n"), cmd->name); return; case HELP_SET: (void) fprintf(fp, gettext("\t%s -p = " "[-p =]... \n"), cmd->name); return; case HELP_SHOW: (void) fprintf(fp, gettext("\t%s [-mps] []\n"), cmd->name); return; default: break; } abort(); /* NOTREACHED */ } static void smbadm_usage(boolean_t requested) { FILE *fp = requested ? stdout : stderr; boolean_t show_props = B_FALSE; int i; if (curcmd == NULL) { (void) fprintf(fp, gettext("usage: %s ...\n"), progname); for (i = 0; i < SMBADM_NCMD; i++) smbadm_cmdusage(fp, &smbadm_cmdtable[i]); (void) fprintf(fp, gettext("\nFor property list, run %s %s|%s\n"), progname, "get", "set"); exit(requested ? 0 : 2); } (void) fprintf(fp, gettext("usage:\n")); smbadm_cmdusage(fp, curcmd); if (strcmp(curcmd->name, "get") == 0 || strcmp(curcmd->name, "set") == 0) show_props = B_TRUE; if (show_props) { (void) fprintf(fp, gettext("\nThe following properties are supported:\n")); (void) fprintf(fp, "\n\t%-16s %s\n\n", "PROPERTY", "VALUES"); for (i = 0; i < SMBADM_NPROP; i++) { (void) fprintf(fp, "\t%-16s %s\n", smbadm_ptable[i].p_name, smbadm_ptable[i].p_dispvalue); } } exit(requested ? 0 : 2); } /* * smbadm_strcasecmplist * * Find a string 's' within a list of strings. * * Returns the index of the matching string or -1 if there is no match. */ static int smbadm_strcasecmplist(const char *s, ...) { va_list ap; char *p; int ndx; va_start(ap, s); for (ndx = 0; ((p = va_arg(ap, char *)) != NULL); ++ndx) { if (strcasecmp(s, p) == 0) { va_end(ap); return (ndx); } } va_end(ap); return (-1); } /* * smbadm_answer_prompt * * Prompt for the answer to a question. A default response must be * specified, which will be used if the user presses without * answering the question. */ static int smbadm_answer_prompt(const char *prompt, char *answer, const char *dflt) { char buf[SMBADM_ANSBUFSIZ]; char *p; (void) printf(gettext("%s [%s]: "), prompt, dflt); if (fgets(buf, SMBADM_ANSBUFSIZ, stdin) == NULL) return (-1); if ((p = strchr(buf, '\n')) != NULL) *p = '\0'; if (*buf == '\0') (void) strlcpy(answer, dflt, SMBADM_ANSBUFSIZ); else (void) strlcpy(answer, buf, SMBADM_ANSBUFSIZ); return (0); } /* * smbadm_confirm * * Ask a question that requires a yes/no answer. * A default response must be specified. */ static boolean_t smbadm_confirm(const char *prompt, const char *dflt) { char buf[SMBADM_ANSBUFSIZ]; for (;;) { if (smbadm_answer_prompt(prompt, buf, dflt) < 0) return (B_FALSE); if (smbadm_strcasecmplist(buf, "n", "no", 0) >= 0) return (B_FALSE); if (smbadm_strcasecmplist(buf, "y", "yes", 0) >= 0) return (B_TRUE); (void) printf(gettext("Please answer yes or no.\n")); } } static boolean_t smbadm_join_confirm(const char *domain) { (void) printf(gettext("After joining %s the smb service will be " "restarted automatically.\n"), domain); return (smbadm_confirm("Would you like to continue?", "no")); } static void smbadm_restart_service(void) { if (smb_smf_restart_service() != 0) { (void) fprintf(stderr, gettext("Unable to restart smb service. " "Run 'svcs -xv smb/server' for more information.")); } } /* * smbadm_join * * Join a domain or workgroup. * * When joining a domain, we may receive the username, password and * domain name in any of the following combinations. Note that the * password is optional on the command line: if it is not provided, * we will prompt for it later. * * username+password domain * domain\username+password * domain/username+password * username@domain * * We allow domain\name+password or domain/name+password but not * name+password@domain because @ is a valid password character. * * If the username and domain name are passed as separate command * line arguments, we process them directly. Otherwise we separate * them and continue as if they were separate command line arguments. */ static int smbadm_join(int argc, char **argv) { char buf[MAXHOSTNAMELEN * 2]; char *domain = NULL; char *username = NULL; char *container = NULL; uint32_t mode = 0; boolean_t confirm = B_TRUE; int option; while ((option = getopt(argc, argv, "c:pu:wy")) != -1) { if (mode != 0) { (void) fprintf(stderr, gettext( "join options are mutually exclusive\n")); smbadm_usage(B_FALSE); } switch (option) { case 'c': container = optarg; break; case 'p': mode = SMB_SECMODE_DOMAIN; /* leave username = NULL */ break; case 'u': mode = SMB_SECMODE_DOMAIN; username = optarg; break; case 'w': mode = SMB_SECMODE_WORKGRP; break; case 'y': confirm = B_FALSE; break; default: smbadm_usage(B_FALSE); break; } } if (optind < argc) domain = argv[optind]; if (username != NULL && domain == NULL) { /* * The domain was not specified as a separate * argument, check for the combination forms. */ (void) strlcpy(buf, username, sizeof (buf)); smbadm_extract_domain(buf, &username, &domain); } if ((domain == NULL) || (*domain == '\0')) { (void) fprintf(stderr, gettext("missing %s name\n"), (mode == SMB_SECMODE_WORKGRP) ? "workgroup" : "domain"); smbadm_usage(B_FALSE); } if (mode == SMB_SECMODE_WORKGRP) { return (smbadm_join_workgroup(domain, confirm)); } return (smbadm_join_domain(domain, container, username, confirm)); } /* * Workgroups comprise a collection of standalone, independently administered * computers that use a common workgroup name. This is a peer-to-peer model * with no formal membership mechanism. */ static int smbadm_join_workgroup(const char *workgroup, boolean_t confirm) { smb_joininfo_t jdi; smb_joinres_t jdres; uint32_t status; bzero(&jdres, sizeof (jdres)); bzero(&jdi, sizeof (jdi)); jdi.mode = SMB_SECMODE_WORKGRP; (void) strlcpy(jdi.domain_name, workgroup, sizeof (jdi.domain_name)); (void) strtrim(jdi.domain_name, " \t\n"); if (smb_name_validate_workgroup(jdi.domain_name) != ERROR_SUCCESS) { (void) fprintf(stderr, gettext("workgroup name is invalid\n")); smbadm_usage(B_FALSE); } if (confirm && !smbadm_join_confirm(jdi.domain_name)) return (0); smbadm_update_groups(SMBADM_GRP_DELMEMBER); // before "un-join" if ((status = smb_join(&jdi, &jdres)) != NT_STATUS_SUCCESS) { (void) fprintf(stderr, gettext("failed to join %s: %s\n"), jdi.domain_name, xlate_nt_status(status)); return (1); } (void) printf(gettext("Successfully joined %s\n"), jdi.domain_name); smbadm_restart_service(); return (0); } /* * Domains comprise a centrally administered group of computers and accounts * that share a common security and administration policy and database. * Computers must join a domain and become domain members, which requires * an administrator level account name. * * The '+' character is invalid within a username. We allow the password * to be appended to the username using '+' as a scripting convenience. * See details above smbadm_join() */ static int smbadm_join_domain(const char *domain, const char *container, const char *username, boolean_t confirm) { smb_joininfo_t jdi; smb_joinres_t jdres; char *passwd_prompt; char *p; int len, rc; bzero(&jdres, sizeof (jdres)); bzero(&jdi, sizeof (jdi)); jdi.mode = SMB_SECMODE_DOMAIN; (void) strlcpy(jdi.domain_name, domain, sizeof (jdi.domain_name)); (void) strtrim(jdi.domain_name, " \t\n"); if (container != NULL) { if (strlcpy(jdi.container_name, container, sizeof (jdi.container_name)) >= sizeof (jdi.container_name)) { (void) fprintf(stderr, gettext("container name is " "too long\n")); smbadm_usage(B_FALSE); } } if (smb_name_validate_domain(jdi.domain_name) != ERROR_SUCCESS) { (void) fprintf(stderr, gettext("domain name is invalid\n")); smbadm_usage(B_FALSE); } if (confirm && !smbadm_join_confirm(jdi.domain_name)) return (0); /* * Note: username is null for "unsecure join" * (join using a pre-created computer account) * Not implemented. */ if (username == NULL) { (void) fprintf(stderr, gettext("username missing\n")); smbadm_usage(B_FALSE); } /* * Check for "username+password" as described above. */ if ((p = strchr(username, '+')) != NULL) { ++p; len = (int)(p - username); if (len > sizeof (jdi.domain_name)) len = sizeof (jdi.domain_name); (void) strlcpy(jdi.domain_username, username, len); (void) strlcpy(jdi.domain_passwd, p, sizeof (jdi.domain_passwd)); } else { (void) strlcpy(jdi.domain_username, username, sizeof (jdi.domain_username)); } if (smb_name_validate_account(jdi.domain_username) != ERROR_SUCCESS) { (void) fprintf(stderr, gettext("username contains invalid characters\n")); smbadm_usage(B_FALSE); } if (*jdi.domain_passwd == '\0') { if (isatty(fileno(stdin))) { passwd_prompt = gettext("Enter domain password: "); if ((p = getpassphrase(passwd_prompt)) == NULL) { (void) fprintf(stderr, gettext( "missing password\n")); smbadm_usage(B_FALSE); } (void) strlcpy(jdi.domain_passwd, p, sizeof (jdi.domain_passwd)); } else { /* Just read from stdin, no prompt. */ char *pbuf = NULL; size_t pblen = 0; len = getline(&pbuf, &pblen, stdin); /* trim the ending newline if it exists */ if (len > 0 && pbuf[len - 1] == '\n') { pbuf[len - 1] = '\0'; len--; } if (len <= 0) { (void) fprintf(stderr, gettext( "missing password\n")); smbadm_usage(B_FALSE); } (void) strlcpy(jdi.domain_passwd, pbuf, sizeof (jdi.domain_passwd)); } } (void) printf(gettext("Joining %s ... this may take a minute ...\n"), jdi.domain_name); rc = smb_join(&jdi, &jdres); if (rc != 0) { (void) printf(gettext("Cannot call the SMB service. " " (error %d: %s) " "Please check the service status " "(svcs -vx network/smb/server)\n"), rc, strerror(rc)); bzero(&jdi, sizeof (jdi)); return (1); } switch (jdres.status) { case NT_STATUS_SUCCESS: (void) printf(gettext( "Successfully joined domain %s using AD server %s\n"), jdi.domain_name, jdres.dc_name); bzero(&jdi, sizeof (jdi)); smbadm_update_groups(SMBADM_GRP_ADDMEMBER); // after join smbadm_restart_service(); return (0); case NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND: /* See: smb_ads_lookup_msdcs */ (void) fprintf(stderr, gettext( "failed to find any AD servers for domain: %s\n"), jdi.domain_name); goto common; case NT_STATUS_BAD_NETWORK_PATH: /* See: smbrdr_ctx_new / smb_ctx_resolve */ (void) fprintf(stderr, gettext( "failed to resolve address of AD server: %s\n"), jdres.dc_name); goto common; case NT_STATUS_NETWORK_ACCESS_DENIED: /* See: smbrdr_ctx_new / smb_ctx_get_ssn */ (void) fprintf(stderr, gettext( "failed to authenticate with AD server: %s\n"), jdres.dc_name); goto common; case NT_STATUS_BAD_NETWORK_NAME: /* * See: smbrdr_ctx_new / smb_ctx_get_tree * and: ndr_rpc_bind / smb_fh_open */ (void) fprintf(stderr, gettext( "failed connecting to services on AD server: %s\n"), jdres.dc_name); goto common; default: (void) fprintf(stderr, gettext( "failed to join domain %s\n"), jdi.domain_name); if (jdres.dc_name[0] != '\0') { (void) fprintf(stderr, gettext( "using AD server: %s\n"), jdres.dc_name); } /* FALLTHROUGH */ common: if (jdres.join_err != 0) { (void) fprintf(stderr, "%s\n", smb_ads_strerror(jdres.join_err)); } else if (jdres.status != 0) { (void) fprintf(stderr, "(%s)\n", xlate_nt_status(jdres.status)); } (void) fprintf(stderr, gettext("Please refer to the " "service log for more information.\n")); bzero(&jdi, sizeof (jdi)); return (1); } } /* * We want to process the user and domain names as separate strings. * Check for names of the forms below and separate the components as * required. * * name@domain * domain\name * domain/name * * If we encounter any of the forms above in arg, the @, / or \ * separator is replaced by \0 and the username and domain pointers * are changed to point to the appropriate components (in arg). * * If none of the separators are encountered, the username and domain * pointers remain unchanged. */ static void smbadm_extract_domain(char *arg, char **username, char **domain) { char *p; if ((p = strpbrk(arg, "/\\@")) != NULL) { if (*p == '@') { *p = '\0'; ++p; if (strchr(arg, '+') != NULL) return; *domain = p; *username = arg; } else { *p = '\0'; ++p; *username = p; *domain = arg; } } } /* * smbadm_update_groups * Add or remove "Domain Admins@mydomain" to/from the * local administrators group. * * Similar to: smbadm_group_add_del_member */ static void smbadm_update_groups(smbadm_grp_action_t act) { char sidstr[SMB_SID_STRSZ]; char gname[] = "administrators"; // must be writable smb_gsid_t msid; int rc; /* * Compose the (well-known) SID for "Domain Admins" * which is {domain-SID}-512 */ rc = smb_config_getstr(SMB_CI_DOMAIN_SID, sidstr, sizeof (sidstr)); if (rc != 0) { (void) fprintf(stderr, gettext("Update local groups: no domain SID\n")); return; } (void) strlcat(sidstr, "-512", sizeof (sidstr)); msid.gs_type = SidTypeGroup; msid.gs_sid = smb_sid_fromstr(sidstr); if (msid.gs_sid == NULL) { (void) fprintf(stderr, gettext("Update local groups: no memory for SID\n")); return; } switch (act) { case SMBADM_GRP_ADDMEMBER: rc = smb_lgrp_add_member(gname, msid.gs_sid, msid.gs_type); // suppress "already in group" if (rc == SMB_LGRP_MEMBER_IN_GROUP) rc = 0; break; case SMBADM_GRP_DELMEMBER: rc = smb_lgrp_del_member(gname, msid.gs_sid, msid.gs_type); // supress "not in group" if (rc == SMB_LGRP_MEMBER_NOT_IN_GROUP) rc = 0; break; default: rc = SMB_LGRP_INTERNAL_ERROR; break; } smb_sid_free(msid.gs_sid); if (rc != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("Update local groups: can't update DB, %s\n"), smb_lgrp_strerror(rc)); } } /* * smbadm_list * * Displays current security mode and domain/workgroup name. */ /*ARGSUSED*/ static int smbadm_list(int argc, char **argv) { char domain[MAXHOSTNAMELEN]; char fqdn[MAXHOSTNAMELEN]; char srvname[MAXHOSTNAMELEN]; char modename[16]; int rc; smb_inaddr_t srvipaddr; char ipstr[INET6_ADDRSTRLEN]; rc = smb_config_getstr(SMB_CI_SECURITY, modename, sizeof (modename)); if (rc != SMBD_SMF_OK) { (void) fprintf(stderr, gettext("cannot determine the operational mode\n")); return (1); } if (smb_getdomainname(domain, sizeof (domain)) != 0) { (void) fprintf(stderr, gettext("failed to get the %s name\n"), modename); return (1); } if (strcmp(modename, "workgroup") == 0) { (void) printf(gettext("[*] [%s]\n"), domain); return (0); } (void) printf(gettext("[*] [%s]\n"), domain); if ((smb_getfqdomainname(fqdn, sizeof (fqdn)) == 0) && (*fqdn != '\0')) (void) printf(gettext("[*] [%s]\n"), fqdn); if ((smb_get_dcinfo(srvname, MAXHOSTNAMELEN, &srvipaddr) == NT_STATUS_SUCCESS) && (*srvname != '\0') && (!smb_inet_iszero(&srvipaddr))) { (void) smb_inet_ntop(&srvipaddr, ipstr, SMB_IPSTRLEN(srvipaddr.a_family)); (void) printf(gettext("\t[+%s] [%s]\n"), srvname, ipstr); } /* Print the local and domain SID. */ smb_domain_show(); return (0); } /* * smbadm_lookup * * Lookup the SID for a given account (user or group) */ static int smbadm_lookup(int argc, char **argv) { int i; boolean_t parsable = B_FALSE; char option; if (argc < 2) { (void) fprintf(stderr, gettext("missing account name\n")); smbadm_usage(B_FALSE); } while ((option = getopt(argc, argv, "p")) != -1) { switch (option) { case 'p': parsable = B_TRUE; break; default: smbadm_usage(B_FALSE); break; } } for (i = optind; i < argc; i++) { if (strncmp(argv[i], "S-1-", 4) == 0) smbadm_lookup_sid(argv[i], parsable); else smbadm_lookup_name(argv[i], parsable); } return (0); } static void smbadm_lookup_name(char *name, boolean_t parsable) { lsa_account_t acct; int rc; if ((rc = smb_lookup_name(name, SidTypeUnknown, &acct)) != 0) { (void) fprintf(stderr, gettext( "\t\t%s: lookup name failed, rc=%d\n"), name, rc); return; } if (acct.a_status != NT_STATUS_SUCCESS) { (void) fprintf(stderr, gettext("\t\t%s [%s]\n"), name, xlate_nt_status(acct.a_status)); return; } if (parsable) { (void) printf("%s:%u:%s:%s\n", acct.a_sid, acct.a_sidtype, acct.a_name, acct.a_domain); } else { (void) printf("\t%s\n", acct.a_sid); } } static void smbadm_lookup_sid(char *sidstr, boolean_t parsable) { lsa_account_t acct; int rc; if ((rc = smb_lookup_sid(sidstr, &acct)) != 0) { (void) fprintf(stderr, gettext( "\t\t%s: lookup SID failed, rc=%d\n"), sidstr, rc); return; } if (acct.a_status != NT_STATUS_SUCCESS) { (void) fprintf(stderr, gettext("\t\t%s [%s]\n"), sidstr, xlate_nt_status(acct.a_status)); return; } if (parsable) { (void) printf("%s:%u:%s:%s\n", acct.a_sid, acct.a_sidtype, acct.a_name, acct.a_domain); } else { (void) printf("\t%s\\%s\n", acct.a_domain, acct.a_name); } } /* * smbadm_group_create * * Creates a local SMB group */ static int smbadm_group_create(int argc, char **argv) { char *gname = NULL; char *desc = NULL; int option; int status; while ((option = getopt(argc, argv, "d:")) != -1) { switch (option) { case 'd': desc = optarg; break; default: smbadm_usage(B_FALSE); } } gname = argv[optind]; if (optind >= argc || gname == NULL || *gname == '\0') { (void) fprintf(stderr, gettext("missing group name\n")); smbadm_usage(B_FALSE); } status = smb_lgrp_add(gname, desc); if (status != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("failed to create %s (%s)\n"), gname, smb_lgrp_strerror(status)); } else { (void) printf(gettext("%s created\n"), gname); } return (status); } /* * smbadm_group_dump_members * * Dump group members details. */ static void smbadm_group_dump_members(smb_gsid_t *members, int num, boolean_t show_sids) { char sidstr[SMB_SID_STRSZ]; lsa_account_t acct; int i; if (num == 0) { (void) printf(gettext("\tNo members\n")); return; } (void) printf(gettext("\tMembers:\n")); for (i = 0; i < num; i++) { smb_sid_tostr(members[i].gs_sid, sidstr); if (!show_sids && smb_lookup_sid(sidstr, &acct) == 0) { if (acct.a_status == NT_STATUS_SUCCESS) smbadm_group_show_name(acct.a_domain, acct.a_name); else (void) printf(gettext("\t\t%s [%s]\n"), sidstr, xlate_nt_status(acct.a_status)); } else { (void) printf(gettext("\t\t%s\n"), sidstr); } } } static void smbadm_group_show_name(const char *domain, const char *name) { if (strchr(domain, '.') != NULL) (void) printf("\t\t%s@%s\n", name, domain); else (void) printf("\t\t%s\\%s\n", domain, name); } /* * smbadm_group_dump_privs * * Dump group privilege details. */ static void smbadm_group_dump_privs(smb_privset_t *privs) { smb_privinfo_t *pinfo; char *pstatus; int i; (void) printf(gettext("\tPrivileges: \n")); for (i = 0; i < privs->priv_cnt; i++) { pinfo = smb_priv_getbyvalue(privs->priv[i].luid.lo_part); if ((pinfo == NULL) || (pinfo->flags & PF_PRESENTABLE) == 0) continue; switch (privs->priv[i].attrs) { case SE_PRIVILEGE_ENABLED: pstatus = "On"; break; case SE_PRIVILEGE_DISABLED: pstatus = "Off"; break; default: pstatus = "Unknown"; break; } (void) printf(gettext("\t\t%s: %s\n"), pinfo->name, pstatus); } if (privs->priv_cnt == 0) (void) printf(gettext("\t\tNo privileges\n")); } /* * smbadm_group_dump * * Dump group details. */ static void smbadm_group_dump(smb_group_t *grp, boolean_t show_mem, boolean_t show_privs, boolean_t show_sids) { char sidstr[SMB_SID_STRSZ]; (void) printf(gettext("%s (%s)\n"), grp->sg_name, grp->sg_cmnt); smb_sid_tostr(grp->sg_id.gs_sid, sidstr); (void) printf(gettext("\tSID: %s\n"), sidstr); if (show_privs) smbadm_group_dump_privs(grp->sg_privs); if (show_mem) smbadm_group_dump_members(grp->sg_members, grp->sg_nmembers, show_sids); } /* * smbadm_group_show * */ static int smbadm_group_show(int argc, char **argv) { char *gname = NULL; boolean_t show_members = B_FALSE; boolean_t show_privs = B_FALSE; boolean_t show_sids = B_FALSE; int option; int status; smb_group_t grp; smb_giter_t gi; while ((option = getopt(argc, argv, "mps")) != -1) { switch (option) { case 'm': show_members = B_TRUE; break; case 'p': show_privs = B_TRUE; break; case 's': show_sids = B_TRUE; break; default: smbadm_usage(B_FALSE); } } gname = argv[optind]; if (optind >= argc || gname == NULL || *gname == '\0') gname = "*"; if (strcmp(gname, "*")) { status = smb_lgrp_getbyname(gname, &grp); if (status == SMB_LGRP_SUCCESS) { smbadm_group_dump(&grp, show_members, show_privs, show_sids); smb_lgrp_free(&grp); } else { (void) fprintf(stderr, gettext("failed to find %s (%s)\n"), gname, smb_lgrp_strerror(status)); } return (status); } if ((status = smb_lgrp_iteropen(&gi)) != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("failed to list groups (%s)\n"), smb_lgrp_strerror(status)); return (status); } while ((status = smb_lgrp_iterate(&gi, &grp)) == SMB_LGRP_SUCCESS) { smbadm_group_dump(&grp, show_members, show_privs, show_sids); smb_lgrp_free(&grp); } smb_lgrp_iterclose(&gi); if ((status != SMB_LGRP_NO_MORE) || smb_lgrp_itererror(&gi)) { if (status != SMB_LGRP_NO_MORE) smb_syslog(LOG_ERR, "smb_lgrp_iterate: %s", smb_lgrp_strerror(status)); (void) fprintf(stderr, gettext("\nAn error occurred while retrieving group data.\n" "Check the system log for more information.\n")); return (status); } return (0); } /* * smbadm_group_delete */ static int smbadm_group_delete(int argc, char **argv) { char *gname = NULL; int status; gname = argv[optind]; if (optind >= argc || gname == NULL || *gname == '\0') { (void) fprintf(stderr, gettext("missing group name\n")); smbadm_usage(B_FALSE); } status = smb_lgrp_delete(gname); if (status != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("failed to delete %s (%s)\n"), gname, smb_lgrp_strerror(status)); } else { (void) printf(gettext("%s deleted\n"), gname); } return (status); } /* * smbadm_group_rename */ static int smbadm_group_rename(int argc, char **argv) { char *gname = NULL; char *ngname = NULL; int status; gname = argv[optind]; if (optind++ >= argc || gname == NULL || *gname == '\0') { (void) fprintf(stderr, gettext("missing group name\n")); smbadm_usage(B_FALSE); } ngname = argv[optind]; if (optind >= argc || ngname == NULL || *ngname == '\0') { (void) fprintf(stderr, gettext("missing new group name\n")); smbadm_usage(B_FALSE); } status = smb_lgrp_rename(gname, ngname); if (status != SMB_LGRP_SUCCESS) { if (status == SMB_LGRP_EXISTS) (void) fprintf(stderr, gettext("failed to rename '%s' (%s already " "exists)\n"), gname, ngname); else (void) fprintf(stderr, gettext("failed to rename '%s' (%s)\n"), gname, smb_lgrp_strerror(status)); } else { (void) printf(gettext("'%s' renamed to '%s'\n"), gname, ngname); } return (status); } /* * smbadm_group_setprop * * Set the group properties. */ static int smbadm_group_setprop(int argc, char **argv) { char *gname = NULL; smbadm_prop_t props[SMBADM_NPROP]; smbadm_prop_handle_t *phandle; int option; int pcnt = 0; int ret = 0; int p; bzero(props, SMBADM_NPROP * sizeof (smbadm_prop_t)); while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': if (pcnt >= SMBADM_NPROP) { (void) fprintf(stderr, gettext("exceeded number of supported" " properties\n")); smbadm_usage(B_FALSE); } if (smbadm_prop_parse(optarg, &props[pcnt++]) != 0) smbadm_usage(B_FALSE); break; default: smbadm_usage(B_FALSE); } } if (pcnt == 0) { (void) fprintf(stderr, gettext("missing property=value argument\n")); smbadm_usage(B_FALSE); } gname = argv[optind]; if (optind >= argc || gname == NULL || *gname == '\0') { (void) fprintf(stderr, gettext("missing group name\n")); smbadm_usage(B_FALSE); } for (p = 0; p < pcnt; p++) { phandle = smbadm_prop_gethandle(props[p].p_name); if (phandle) { if (phandle->p_setfn(gname, &props[p]) != 0) ret = 1; } } return (ret); } /* * smbadm_group_getprop * * Get the group properties. */ static int smbadm_group_getprop(int argc, char **argv) { char *gname = NULL; smbadm_prop_t props[SMBADM_NPROP]; smbadm_prop_handle_t *phandle; int option; int pcnt = 0; int ret = 0; int p; bzero(props, SMBADM_NPROP * sizeof (smbadm_prop_t)); while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': if (pcnt >= SMBADM_NPROP) { (void) fprintf(stderr, gettext("exceeded number of supported" " properties\n")); smbadm_usage(B_FALSE); } if (smbadm_prop_parse(optarg, &props[pcnt++]) != 0) smbadm_usage(B_FALSE); break; default: smbadm_usage(B_FALSE); } } gname = argv[optind]; if (optind >= argc || gname == NULL || *gname == '\0') { (void) fprintf(stderr, gettext("missing group name\n")); smbadm_usage(B_FALSE); } if (pcnt == 0) { /* * If no property has be specified then get * all the properties. */ pcnt = SMBADM_NPROP; for (p = 0; p < pcnt; p++) props[p].p_name = smbadm_ptable[p].p_name; } for (p = 0; p < pcnt; p++) { phandle = smbadm_prop_gethandle(props[p].p_name); if (phandle) { if (phandle->p_getfn(gname, &props[p]) != 0) ret = 1; } } return (ret); } /* * smbadm_group_addmember * */ static int smbadm_group_addmember(int argc, char **argv) { char *gname = NULL; char **mname; int option; int mcnt = 0; int ret = 0; int i; mname = (char **)malloc(argc * sizeof (char *)); if (mname == NULL) { warn(gettext("failed to add group member")); return (1); } bzero(mname, argc * sizeof (char *)); while ((option = getopt(argc, argv, "m:")) != -1) { switch (option) { case 'm': mname[mcnt++] = optarg; break; default: free(mname); smbadm_usage(B_FALSE); } } if (mcnt == 0) { (void) fprintf(stderr, gettext("missing member name\n")); free(mname); smbadm_usage(B_FALSE); } gname = argv[optind]; if (optind >= argc || gname == NULL || *gname == 0) { (void) fprintf(stderr, gettext("missing group name\n")); free(mname); smbadm_usage(B_FALSE); } for (i = 0; i < mcnt; i++) { if (mname[i] == NULL) continue; ret |= smbadm_group_add_del_member( gname, mname[i], SMBADM_GRP_ADDMEMBER); } free(mname); return (ret); } /* * smbadm_group_delmember */ static int smbadm_group_delmember(int argc, char **argv) { char *gname = NULL; char **mname; int option; int mcnt = 0; int ret = 0; int i; mname = (char **)malloc(argc * sizeof (char *)); if (mname == NULL) { warn(gettext("failed to delete group member")); return (1); } bzero(mname, argc * sizeof (char *)); while ((option = getopt(argc, argv, "m:")) != -1) { switch (option) { case 'm': mname[mcnt++] = optarg; break; default: free(mname); smbadm_usage(B_FALSE); } } if (mcnt == 0) { (void) fprintf(stderr, gettext("missing member name\n")); free(mname); smbadm_usage(B_FALSE); } gname = argv[optind]; if (optind >= argc || gname == NULL || *gname == 0) { (void) fprintf(stderr, gettext("missing group name\n")); free(mname); smbadm_usage(B_FALSE); } for (i = 0; i < mcnt; i++) { ret = 0; if (mname[i] == NULL) continue; ret |= smbadm_group_add_del_member( gname, mname[i], SMBADM_GRP_DELMEMBER); } free(mname); return (ret); } static int smbadm_group_add_del_member(char *gname, char *mname, smbadm_grp_action_t act) { lsa_account_t acct; smb_gsid_t msid; char *sidstr; char *act_str = NULL; int rc; if (strncmp(mname, "S-1-", 4) == 0) { /* * We are given a SID. Just use it. * * We'd like the real account type if we can get it, * but don't want to error out if we can't get it. * Lacking other info, assume it's a group. */ sidstr = mname; rc = smb_lookup_sid(sidstr, &acct); if ((rc != 0) || (acct.a_status != NT_STATUS_SUCCESS)) acct.a_sidtype = SidTypeGroup; } else { rc = smb_lookup_name(mname, SidTypeUnknown, &acct); if ((rc != 0) || (acct.a_status != NT_STATUS_SUCCESS)) { (void) fprintf(stderr, gettext("%s: name lookup failed\n"), mname); return (1); } sidstr = acct.a_sid; } msid.gs_type = acct.a_sidtype; if ((msid.gs_sid = smb_sid_fromstr(sidstr)) == NULL) { (void) fprintf(stderr, gettext("%s: no memory for SID\n"), sidstr); return (1); } switch (act) { case SMBADM_GRP_ADDMEMBER: act_str = gettext("add"); rc = smb_lgrp_add_member(gname, msid.gs_sid, msid.gs_type); break; case SMBADM_GRP_DELMEMBER: act_str = gettext("remove"); rc = smb_lgrp_del_member(gname, msid.gs_sid, msid.gs_type); break; default: rc = SMB_LGRP_INTERNAL_ERROR; break; } smb_sid_free(msid.gs_sid); if (rc != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("failed to %s %s (%s)\n"), act_str, mname, smb_lgrp_strerror(rc)); return (1); } return (0); } static int smbadm_user_delete(int argc, char **argv) { int error; char *user = NULL; user = argv[optind]; if (optind >= argc || user == NULL || *user == '\0') { (void) fprintf(stderr, gettext("missing user name\n")); smbadm_usage(B_FALSE); } error = smb_pwd_setcntl(user, SMB_PWC_DELETE); if (error == SMB_PWE_SUCCESS) (void) printf(gettext("%s has been deleted.\n"), user); else (void) fprintf(stderr, "%s\n", smbadm_pwd_strerror(error)); return (error); } static int smbadm_user_disable(int argc, char **argv) { int error; char *user = NULL; user = argv[optind]; if (optind >= argc || user == NULL || *user == '\0') { (void) fprintf(stderr, gettext("missing user name\n")); smbadm_usage(B_FALSE); } error = smb_pwd_setcntl(user, SMB_PWC_DISABLE); if (error == SMB_PWE_SUCCESS) (void) printf(gettext("%s is disabled.\n"), user); else (void) fprintf(stderr, "%s\n", smbadm_pwd_strerror(error)); return (error); } static int smbadm_user_enable(int argc, char **argv) { int error; char *user = NULL; user = argv[optind]; if (optind >= argc || user == NULL || *user == '\0') { (void) fprintf(stderr, gettext("missing user name\n")); smbadm_usage(B_FALSE); } error = smb_pwd_setcntl(user, SMB_PWC_ENABLE); if (error == SMB_PWE_SUCCESS) (void) printf(gettext("%s is enabled.\n"), user); else (void) fprintf(stderr, "%s\n", smbadm_pwd_strerror(error)); return (error); } int main(int argc, char **argv) { int ret; int i; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); (void) malloc(0); /* satisfy libumem dependency */ progname = basename(argv[0]); if (is_system_labeled()) { (void) fprintf(stderr, gettext("Trusted Extensions not supported\n")); return (1); } if (argc < 2) { (void) fprintf(stderr, gettext("missing command\n")); smbadm_usage(B_FALSE); } /* * Special case "cmd --help/-?" */ if (strcmp(argv[1], "-?") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) smbadm_usage(B_TRUE); for (i = 0; i < SMBADM_NCMD; ++i) { curcmd = &smbadm_cmdtable[i]; if (strcasecmp(argv[1], curcmd->name) == 0) { if (argc > 2) { /* cmd subcmd --help/-? */ if (strcmp(argv[2], "-?") == 0 || strcmp(argv[2], "--help") == 0 || strcmp(argv[2], "-h") == 0) smbadm_usage(B_TRUE); } if (!smbadm_checkauth(curcmd->auth)) { (void) fprintf(stderr, gettext("%s: %s: authorization denied\n"), progname, curcmd->name); return (1); } if ((ret = smbadm_init()) != 0) return (ret); ret = curcmd->func(argc - 1, &argv[1]); smbadm_fini(); return (ret); } } curcmd = NULL; (void) fprintf(stderr, gettext("unknown subcommand (%s)\n"), argv[1]); smbadm_usage(B_FALSE); return (2); } static int smbadm_init(void) { int rc; if ((curcmd->flags & SMBADM_CMDF_KMOD) != 0) { if ((rc = smb_kmod_bind(B_FALSE)) != 0) { (void) fprintf(stderr, gettext("failed to open driver (%s)\n"), strerror(rc)); return (1); } } if ((curcmd->flags & SMBADM_CMDF_GROUP) != 0) { if ((rc = smb_lgrp_start()) != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("failed to initialize (%s)\n"), smb_lgrp_strerror(rc)); return (1); } } if ((curcmd->flags & SMBADM_CMDF_USER) != 0) { smb_pwd_init(B_FALSE); } return (0); } static void smbadm_fini(void) { if ((curcmd->flags & SMBADM_CMDF_KMOD) != 0) { smb_kmod_unbind(); } if ((curcmd->flags & SMBADM_CMDF_GROUP) != 0) { smb_lgrp_stop(); } if ((curcmd->flags & SMBADM_CMDF_USER) != 0) { smb_pwd_fini(); } } static boolean_t smbadm_checkauth(const char *auth) { struct passwd *pw; if ((pw = getpwuid(getuid())) == NULL) return (B_FALSE); if (chkauthattr(auth, pw->pw_name) == 0) return (B_FALSE); return (B_TRUE); } static boolean_t smbadm_prop_validate(smbadm_prop_t *prop, boolean_t chkval) { smbadm_prop_handle_t *pinfo; int i; for (i = 0; i < SMBADM_NPROP; i++) { pinfo = &smbadm_ptable[i]; if (strcmp(pinfo->p_name, prop->p_name) == 0) { if (pinfo->p_chkfn && chkval) return (pinfo->p_chkfn(prop)); return (B_TRUE); } } (void) fprintf(stderr, gettext("unrecognized property '%s'\n"), prop->p_name); return (B_FALSE); } static int smbadm_prop_parse(char *arg, smbadm_prop_t *prop) { boolean_t parse_value; char *equal; if (arg == NULL) return (2); prop->p_name = prop->p_value = NULL; if (strcmp(curcmd->name, "set") == 0) parse_value = B_TRUE; else parse_value = B_FALSE; prop->p_name = arg; if (parse_value) { equal = strchr(arg, '='); if (equal == NULL) return (2); *equal++ = '\0'; prop->p_value = equal; } if (smbadm_prop_validate(prop, parse_value) == B_FALSE) return (2); return (0); } static smbadm_prop_handle_t * smbadm_prop_gethandle(char *pname) { int i; for (i = 0; i < SMBADM_NPROP; i++) if (strcmp(pname, smbadm_ptable[i].p_name) == 0) return (&smbadm_ptable[i]); return (NULL); } static int smbadm_setprop_desc(char *gname, smbadm_prop_t *prop) { int status; status = smb_lgrp_setcmnt(gname, prop->p_value); if (status != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("failed to modify the group description (%s)\n"), smb_lgrp_strerror(status)); return (1); } (void) printf(gettext("%s: description modified\n"), gname); return (0); } static int smbadm_getprop_desc(char *gname, smbadm_prop_t *prop) { char *cmnt = NULL; int status; status = smb_lgrp_getcmnt(gname, &cmnt); if (status != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("failed to get the group description (%s)\n"), smb_lgrp_strerror(status)); return (1); } (void) printf(gettext("\t%s: %s\n"), prop->p_name, cmnt); free(cmnt); return (0); } static int smbadm_group_setpriv(char *gname, uint8_t priv_id, smbadm_prop_t *prop) { boolean_t enable; int status; int ret; if (strcasecmp(prop->p_value, "on") == 0) { (void) printf(gettext("Enabling %s privilege "), prop->p_name); enable = B_TRUE; } else { (void) printf(gettext("Disabling %s privilege "), prop->p_name); enable = B_FALSE; } status = smb_lgrp_setpriv(gname, priv_id, enable); if (status == SMB_LGRP_SUCCESS) { (void) printf(gettext("succeeded\n")); ret = 0; } else { (void) printf(gettext("failed: %s\n"), smb_lgrp_strerror(status)); ret = 1; } return (ret); } static int smbadm_group_getpriv(char *gname, uint8_t priv_id, smbadm_prop_t *prop) { boolean_t enable; int status; status = smb_lgrp_getpriv(gname, priv_id, &enable); if (status != SMB_LGRP_SUCCESS) { (void) fprintf(stderr, gettext("failed to get %s (%s)\n"), prop->p_name, smb_lgrp_strerror(status)); return (1); } (void) printf(gettext("\t%s: %s\n"), prop->p_name, (enable) ? "On" : "Off"); return (0); } static int smbadm_setprop_tkowner(char *gname, smbadm_prop_t *prop) { return (smbadm_group_setpriv(gname, SE_TAKE_OWNERSHIP_LUID, prop)); } static int smbadm_getprop_tkowner(char *gname, smbadm_prop_t *prop) { return (smbadm_group_getpriv(gname, SE_TAKE_OWNERSHIP_LUID, prop)); } static int smbadm_setprop_readfile(char *gname, smbadm_prop_t *prop) { return (smbadm_group_setpriv(gname, SE_READ_FILE_LUID, prop)); } static int smbadm_getprop_readfile(char *gname, smbadm_prop_t *prop) { return (smbadm_group_getpriv(gname, SE_READ_FILE_LUID, prop)); } static int smbadm_setprop_writefile(char *gname, smbadm_prop_t *prop) { return (smbadm_group_setpriv(gname, SE_WRITE_FILE_LUID, prop)); } static int smbadm_getprop_writefile(char *gname, smbadm_prop_t *prop) { return (smbadm_group_getpriv(gname, SE_WRITE_FILE_LUID, prop)); } static int smbadm_setprop_backup(char *gname, smbadm_prop_t *prop) { return (smbadm_group_setpriv(gname, SE_BACKUP_LUID, prop)); } static int smbadm_getprop_backup(char *gname, smbadm_prop_t *prop) { return (smbadm_group_getpriv(gname, SE_BACKUP_LUID, prop)); } static int smbadm_setprop_restore(char *gname, smbadm_prop_t *prop) { return (smbadm_group_setpriv(gname, SE_RESTORE_LUID, prop)); } static int smbadm_getprop_restore(char *gname, smbadm_prop_t *prop) { return (smbadm_group_getpriv(gname, SE_RESTORE_LUID, prop)); } static boolean_t smbadm_chkprop_priv(smbadm_prop_t *prop) { if (prop->p_value == NULL || *prop->p_value == '\0') { (void) fprintf(stderr, gettext("missing value for '%s'\n"), prop->p_name); return (B_FALSE); } if (strcasecmp(prop->p_value, "on") == 0) return (B_TRUE); if (strcasecmp(prop->p_value, "off") == 0) return (B_TRUE); (void) fprintf(stderr, gettext("%s: unrecognized value for '%s' property\n"), prop->p_value, prop->p_name); return (B_FALSE); } static const char * smbadm_pwd_strerror(int error) { switch (error) { case SMB_PWE_SUCCESS: return (gettext("Success.")); case SMB_PWE_USER_UNKNOWN: return (gettext("User does not exist.")); case SMB_PWE_USER_DISABLE: return (gettext("User is disabled.")); case SMB_PWE_CLOSE_FAILED: case SMB_PWE_OPEN_FAILED: case SMB_PWE_WRITE_FAILED: case SMB_PWE_UPDATE_FAILED: return (gettext("Unexpected failure. " "SMB password database unchanged.")); case SMB_PWE_STAT_FAILED: return (gettext("stat of SMB password file failed.")); case SMB_PWE_BUSY: return (gettext("SMB password database busy. " "Try again later.")); case SMB_PWE_DENIED: return (gettext("Operation not permitted.")); case SMB_PWE_SYSTEM_ERROR: return (gettext("System error.")); default: break; } return (gettext("Unknown error code.")); } /* * Enable libumem debugging by default on DEBUG builds. */ #ifdef 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 /* * 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 2023 RackTop Systems, Inc. */ #ifndef _SMBADM_H #define _SMBADM_H /* * Declarations private to the smbadm command */ #ifdef __cplusplus extern "C" { #endif /* Commands in smbinfo.c */ extern int cmd_list_sess(int, char **); extern int cmd_list_trees(int, char **); extern int cmd_list_ofiles(int, char **); extern int cmd_close_sess(int, char **); extern int cmd_close_ofile(int, char **); #ifdef __cplusplus } #endif #endif /* _SMBADM_H */ /* * 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 2022 RackTop Systems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smbadm.h" /* * Share types for shiX_type fields - duplicated from smb.h * Don't want to pull that in, and these are "carved in stone" * (from the SMB protocol definitions) */ #ifndef _SHARE_TYPES_DEFINED_ #define _SHARE_TYPES_DEFINED_ #define STYPE_DISKTREE 0x00000000 #define STYPE_PRINTQ 0x00000001 #define STYPE_DEVICE 0x00000002 #define STYPE_IPC 0x00000003 #define STYPE_MASK 0x0000000F #endif /* _SHARE_TYPES_DEFINED_ */ #define MINS (60U) #define HRS (60 * MINS) #define DAYS (24 * HRS) #define TIME_FMT "%F %T %Z" #define _(x) gettext(x) struct flag_tbl { uint32_t flag; const char *name; }; typedef enum user_field { UF_SESS_ID, UF_DOMAIN, UF_ACCOUNT, UF_USER, UF_UID, UF_WORKSTATION, UF_IP, UF_OS, UF_LOGON_TIME, UF_AGE, UF_NUMOPEN, UF_FLAGS, } user_field_t; typedef enum tree_field { TF_ID, TF_TYPE, TF_NUMOPEN, TF_NUMUSERS, TF_TIME, TF_AGE, TF_USERNAME, TF_SHARE, } tree_field_t; typedef enum netfileinfo_field { NFIF_FID, NFIF_UNIQID, NFIF_PERMS, NFIF_NUMLOCKS, NFIF_PATH, NFIF_USERNAME, } netfileinfo_field_t; static ofmt_handle_t cmd_create_handle(int, char **, const char *, ofmt_field_t *); static boolean_t fmt_user(ofmt_arg_t *, char *, uint_t); static boolean_t fmt_tree(ofmt_arg_t *, char *, uint_t); static boolean_t fmt_netfileinfo(ofmt_arg_t *, char *, uint_t); static void print_str(const char *restrict, char *restrict, uint_t); static void print_u32(uint32_t, char *, uint_t); static void print_age(time_t, char *, uint_t); static void print_time(time_t, const char *, char *, uint_t); static void print_flags(struct flag_tbl *, size_t, uint32_t, char *, uint_t); static void print_perms(struct flag_tbl *, size_t, uint32_t, char *, uint_t); static ofmt_field_t smb_user_fields[] = { { "ID", 4, UF_SESS_ID, fmt_user }, { "DOMAIN", 32, UF_DOMAIN, fmt_user }, { "ACCT", 16, UF_ACCOUNT, fmt_user }, { "USER", 32, UF_USER, fmt_user }, { "UID", 12, UF_UID, fmt_user }, { "COMPUTER", 16, UF_WORKSTATION, fmt_user }, { "IP", 15, UF_IP, fmt_user }, { "OS", 8, UF_OS, fmt_user }, { "LOGON", 24, UF_LOGON_TIME, fmt_user }, { "AGE", 16, UF_AGE, fmt_user }, { "NOPEN", 5, UF_NUMOPEN, fmt_user }, { "FLAGS", 12, UF_FLAGS, fmt_user }, { NULL, 0, 0, NULL } }; static const char default_user_fields[] = "IP,USER,NOPEN,AGE,FLAGS"; struct flag_tbl user_flag_tbl[] = { { SMB_ATF_GUEST, "GUEST" }, { SMB_ATF_ANON, "ANON" }, { SMB_ATF_ADMIN, "ADMIN" }, { SMB_ATF_POWERUSER, "POWERUSER" }, { SMB_ATF_BACKUPOP, "BACKUPOP" }, }; static ofmt_field_t smb_tree_fields[] = { { "ID", 4, TF_ID, fmt_tree }, { "TYPE", 6, TF_TYPE, fmt_tree }, { "NOPEN", 6, TF_NUMOPEN, fmt_tree }, { "NUSER", 6, TF_NUMUSERS, fmt_tree }, { "TIME", 24, TF_TIME, fmt_tree }, { "AGE", 12, TF_AGE, fmt_tree }, { "USER", 32, TF_USERNAME, fmt_tree }, { "SHARE", 16, TF_SHARE, fmt_tree }, { NULL, 0, 0, NULL } }; static const char default_tree_fields[] = "TYPE,SHARE,USER,NOPEN,AGE"; static ofmt_field_t smb_netfileinfo_fields[] = { { "ID", 4, NFIF_FID, fmt_netfileinfo }, { "UNIQID", 8, NFIF_UNIQID, fmt_netfileinfo }, { "PERM", 15, NFIF_PERMS, fmt_netfileinfo }, { "NLOCK", 6, NFIF_NUMLOCKS, fmt_netfileinfo }, { "PATH", 32, NFIF_PATH, fmt_netfileinfo }, { "USER", 16, NFIF_USERNAME, fmt_netfileinfo }, { NULL, 0, 0, NULL } }; static const char default_netfileinfo_fields[] = "UNIQID,PATH,USER,NLOCK,PERM"; /* * Flags are the same as "ls -V" and chmod ACLs: * eg: everyone@:rwxpdDaARWcCos:fd----I:allow * See libsec:acltext.c */ static struct flag_tbl nfi_perm_tbl[] = { { ACE_READ_DATA, "r" }, { ACE_WRITE_DATA, "w" }, { ACE_EXECUTE, "x" }, { ACE_APPEND_DATA, "p" }, { ACE_DELETE, "d" }, { ACE_DELETE_CHILD, "D" }, { ACE_READ_ATTRIBUTES, "a" }, { ACE_WRITE_ATTRIBUTES, "A" }, { ACE_READ_NAMED_ATTRS, "R" }, { ACE_WRITE_NAMED_ATTRS, "W" }, { ACE_READ_ACL, "c" }, { ACE_WRITE_ACL, "C" }, { ACE_WRITE_OWNER, "o" }, { ACE_SYNCHRONIZE, "s" }, }; static int do_enum(smb_svcenum_t *, ofmt_handle_t); static void ofmt_fatal(ofmt_handle_t, ofmt_field_t *, ofmt_status_t) __NORETURN; static void fatal(const char *, ...) __NORETURN; time_t now; boolean_t opt_p; boolean_t opt_x; int cmd_list_sess(int argc, char **argv) { ofmt_handle_t hdl; smb_svcenum_t req = { .se_type = SMB_SVCENUM_TYPE_USER, .se_level = 1, .se_nlimit = UINT32_MAX, }; int rc; hdl = cmd_create_handle(argc, argv, default_user_fields, smb_user_fields); rc = do_enum(&req, hdl); ofmt_close(hdl); return (rc); } int cmd_list_trees(int argc, char **argv) { ofmt_handle_t hdl; smb_svcenum_t req = { .se_type = SMB_SVCENUM_TYPE_TREE, .se_level = 1, .se_nlimit = UINT32_MAX, }; int rc; hdl = cmd_create_handle(argc, argv, default_tree_fields, smb_tree_fields); rc = do_enum(&req, hdl); ofmt_close(hdl); return (rc); } int cmd_list_ofiles(int argc, char **argv) { ofmt_handle_t hdl; smb_svcenum_t req = { .se_type = SMB_SVCENUM_TYPE_FILE, .se_level = 1, .se_nlimit = UINT32_MAX, }; int rc; hdl = cmd_create_handle(argc, argv, default_netfileinfo_fields, smb_netfileinfo_fields); rc = do_enum(&req, hdl); ofmt_close(hdl); return (rc); } static ofmt_handle_t cmd_create_handle(int argc, char **argv, const char *def, ofmt_field_t *templ) { const char *fields = def; ofmt_handle_t hdl; ofmt_status_t status; uint_t flags = 0; int c; while ((c = getopt(argc, argv, "Ho:px")) != -1) { switch (c) { case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': opt_p = B_TRUE; flags |= OFMT_PARSABLE; break; case 'x': opt_x = B_TRUE; break; case '?': /* Note: getopt prints an error for us. */ return (NULL); } } status = ofmt_open(fields, templ, flags, 0, &hdl); if (status != OFMT_SUCCESS) ofmt_fatal(hdl, templ, status); return (hdl); } int cmd_close_ofile(int argc, char **argv) { uint_t errs = 0; if (argc < 2) { warnx(_("Missing file id")); return (2); } for (int i = 1; i < argc; i++) { unsigned long ul; int rc; errno = 0; ul = strtoul(argv[i], NULL, 0); if (errno != 0) { warnx(_("Invalid file id '%s'"), argv[i]); return (2); } #ifdef _LP64 if (ul > UINT32_MAX) { warnx(_("File id %lu too large"), ul); return (2); } #endif /* * See SMB_IOC_FILE_CLOSE (ioc.uniqid) * and smb_server_file_close() */ rc = smb_kmod_file_close((uint32_t)ul); if (rc != 0) { /* * Since the user can specify the fid as a decimal * or hex value, we use the string they gave us so * the value displayed matches what we were given. */ warnc(rc, _("Closing fid %s failed"), argv[i]); errs++; } } if (errs > 0) return (1); return (0); } int cmd_close_sess(int argc, char **argv) { const char *client; const char *user = NULL; int rc; if (argc < 2) { warnx(_("clientname and username missing")); return (2); } client = argv[1]; if (argc > 2) { user = argv[2]; } /* * See SMB_IOC_SESSION_CLOSE (ioc.client, ioc.username) * and smb_server_session_close(). The "client" part * can be EITHER the "workstation" or the IP address, * as shown in the "COMPUTER" and "IP" fields in the * output of "list-sessions". The (optional) "user" * part is as shown in "USER" part of that output. */ rc = smb_kmod_session_close(client, user); if (rc != 0) { rc = 1; } return (rc); } static boolean_t fmt_user(ofmt_arg_t *arg, char *buf, uint_t buflen) { smb_netuserinfo_t *ui = arg->ofmt_cbarg; user_field_t field = (user_field_t)arg->ofmt_id; switch (field) { case UF_SESS_ID: (void) snprintf(buf, buflen, "%" PRIu64, ui->ui_session_id); break; case UF_DOMAIN: print_str(ui->ui_domain, buf, buflen); break; case UF_ACCOUNT: print_str(ui->ui_account, buf, buflen); break; case UF_USER: (void) snprintf(buf, buflen, "%s\\%s", ui->ui_domain, ui->ui_account); break; case UF_UID: VERIFY3U(arg->ofmt_width, <, INT_MAX); (void) snprintf(buf, buflen, "%u", ui->ui_posix_uid); break; case UF_WORKSTATION: print_str(ui->ui_workstation, buf, buflen); break; case UF_IP: (void) smb_inet_ntop(&ui->ui_ipaddr, buf, buflen); break; case UF_OS: /* XXX: Lookup string value */ (void) snprintf(buf, buflen, "%" PRId32, ui->ui_native_os); break; case UF_LOGON_TIME: print_time(ui->ui_logon_time, TIME_FMT, buf, buflen); break; case UF_AGE: print_age(now - ui->ui_logon_time, buf, buflen); break; case UF_NUMOPEN: print_u32(ui->ui_numopens, buf, buflen); break; case UF_FLAGS: print_flags(user_flag_tbl, ARRAY_SIZE(user_flag_tbl), ui->ui_flags, buf, buflen); break; default: fatal("%s: invalid field %d", __func__, field); } return (B_TRUE); } static boolean_t fmt_tree_type(uint32_t type, char *buf, uint_t buflen) { switch (type & STYPE_MASK) { case STYPE_DISKTREE: (void) strlcpy(buf, "DISK", buflen); break; case STYPE_PRINTQ: (void) strlcpy(buf, "PRINTQ", buflen); break; case STYPE_DEVICE: (void) strlcpy(buf, "DEVICE", buflen); break; case STYPE_IPC: (void) strlcpy(buf, "IPC", buflen); break; default: (void) snprintf(buf, buflen, "%" PRIx32, type & STYPE_MASK); break; } return (B_TRUE); } static boolean_t fmt_tree(ofmt_arg_t *arg, char *buf, uint_t buflen) { smb_netconnectinfo_t *nc = arg->ofmt_cbarg; tree_field_t field = (tree_field_t)arg->ofmt_id; switch (field) { case TF_ID: (void) snprintf(buf, buflen, "%" PRIu32, nc->ci_id); break; case TF_TYPE: return (fmt_tree_type(nc->ci_type, buf, buflen)); case TF_NUMOPEN: print_u32(nc->ci_numopens, buf, buflen); break; case TF_NUMUSERS: print_u32(nc->ci_numusers, buf, buflen); break; case TF_TIME: print_time(now - nc->ci_time, TIME_FMT, buf, buflen); break; case TF_AGE: print_age(nc->ci_time, buf, buflen); break; case TF_USERNAME: print_str(nc->ci_username, buf, buflen); break; case TF_SHARE: print_str(nc->ci_share, buf, buflen); break; default: fatal("%s: invalid field %d", __func__, field); } return (B_TRUE); } static boolean_t fmt_netfileinfo(ofmt_arg_t *arg, char *buf, uint_t buflen) { smb_netfileinfo_t *fi = arg->ofmt_cbarg; netfileinfo_field_t field = (netfileinfo_field_t)arg->ofmt_id; switch (field) { case NFIF_FID: (void) snprintf(buf, buflen, "%" PRIu16, fi->fi_fid); break; case NFIF_UNIQID: (void) snprintf(buf, buflen, "%" PRIu32, fi->fi_uniqid); break; case NFIF_PERMS: print_perms(nfi_perm_tbl, ARRAY_SIZE(nfi_perm_tbl), fi->fi_permissions, buf, buflen); break; case NFIF_NUMLOCKS: print_u32(fi->fi_numlocks, buf, buflen); break; case NFIF_PATH: print_str(fi->fi_path, buf, buflen); break; case NFIF_USERNAME: print_str(fi->fi_username, buf, buflen); break; default: fatal("%s: invalid field %d", __func__, field); } return (B_TRUE); } static int do_enum(smb_svcenum_t *req, ofmt_handle_t hdl) { smb_netsvc_t *ns; smb_netsvcitem_t *item; uint32_t n = 0; int rc; if (hdl == NULL) return (2); /* exit (2) -- usage */ now = time(NULL); for (;;) { req->se_nskip = n; ns = smb_kmod_enum_init(req); if (ns == NULL) { warnx(_("SMB enum initialization failure")); return (1); } rc = smb_kmod_enum(ns); if (rc != 0) { /* * When the SMB service is not running, expect ENXIO. */ if (rc == ENXIO) { warnx(_("Kernel SMB server not running")); return (1); } warnc(rc, _("SMB enumeration call failed")); return (1); } if (list_is_empty(&ns->ns_list)) break; for (item = list_head(&ns->ns_list); item != NULL; item = list_next(&ns->ns_list, item)) { ofmt_print(hdl, &item->nsi_un); n++; } smb_kmod_enum_fini(ns); } return (0); } static void print_str(const char *restrict src, char *restrict buf, uint_t buflen) { if (src == NULL) { buf[0] = '\0'; return; } (void) strlcpy(buf, src, buflen); } static void print_u32(uint32_t val, char *buf, uint_t buflen) { const char *fmt = opt_p ? "%" PRIu32 : "%'" PRIu32; (void) snprintf(buf, buflen, fmt, val); } static void print_age(time_t amt, char *buf, uint_t buflen) { uint32_t days = 0, hours = 0, mins = 0; if (opt_p) { (void) snprintf(buf, buflen, "%" PRId64, (int64_t)amt); return; } if (amt >= DAYS) { days = amt / DAYS; amt %= DAYS; } if (amt >= HRS) { hours = amt / HRS; amt %= HRS; } if (amt >= MINS) { mins = amt / MINS; amt %= MINS; } if (days > 0) { int n = snprintf(buf, buflen, "%" PRIu32 " days%s", days, (hours > 0 || mins > 0 || amt > 0) ? ", " : ""); VERIFY3U(buflen, >, n); buf += n; buflen -= n; } (void) snprintf(buf, buflen, "%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, hours, mins, amt); } static void print_time(time_t when, const char *fmt, char *buf, uint_t buflen) { const struct tm *tm; if (opt_p) { (void) snprintf(buf, buflen, "%" PRId64, (int64_t)when); return; } tm = localtime(&when); (void) strftime(buf, buflen - 1, fmt, tm); } static void print_flags(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf, uint_t buflen) { uint_t n = 0; uint_t i; if (opt_x) { (void) snprintf(buf, buflen, "%" PRIx32, val); return; } for (i = 0; i < nent; i++) { if ((val & tbl[i].flag) == 0) continue; if (n > 0) (void) strlcat(buf, ",", buflen); (void) strlcat(buf, tbl[i].name, buflen); n++; } if (n == 0) (void) strlcat(buf, "-", buflen); } static void print_perms(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf, uint_t buflen) { uint_t n = 0; uint_t i; if (opt_x) { (void) snprintf(buf, buflen, "%" PRIx32, val); return; } for (i = 0; i < nent; i++) { if ((val & tbl[i].flag) == 0) { (void) strlcat(buf, "-", buflen); } else { (void) strlcat(buf, tbl[i].name, buflen); } n++; } } __NORETURN static void ofmt_fatal(ofmt_handle_t hdl, ofmt_field_t *templ, ofmt_status_t status) { char buf[OFMT_BUFSIZE]; char *msg = ofmt_strerror(hdl, status, buf, sizeof (buf)); warnx(_("ofmt error: %s"), msg); if (status == OFMT_EBADFIELDS || status == OFMT_ENOFIELDS) { ofmt_field_t *f = templ; fprintf(stderr, _("Valid fields are: ")); while (f->of_name != NULL) { fprintf(stderr, "%s", f->of_name); f++; if (f->of_name != NULL) fprintf(stderr, ","); } fprintf(stderr, "\n"); } exit(EXIT_FAILURE); } __NORETURN static void fatal(const char *msg, ...) { char buf[128]; va_list ap; size_t len; va_start(ap, msg); (void) vsnprintf(buf, sizeof (buf), msg, ap); va_end(ap); len = strlen(buf); upanic(buf, len); }