#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/debug.h>
#include <sys/list.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <smbsrv/libsmb.h>
#include <ofmt.h>
#include <libintl.h>
#include <limits.h>
#include <locale.h>
#include <time.h>
#include <upanic.h>
#include "smbadm.h"
#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
#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";
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 '?':
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
rc = smb_kmod_file_close((uint32_t)ul);
if (rc != 0) {
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];
}
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:
(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);
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) {
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);
}