1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2022 RackTop Systems, Inc. 14 */ 15 16 #include <err.h> 17 #include <errno.h> 18 #include <stdio.h> 19 #include <stdlib.h> 20 #include <string.h> 21 #include <sys/debug.h> 22 #include <sys/list.h> 23 #include <sys/types.h> 24 #include <sys/sysmacros.h> 25 #include <smbsrv/libsmb.h> 26 #include <ofmt.h> 27 #include <libintl.h> 28 #include <limits.h> 29 #include <locale.h> 30 #include <time.h> 31 #include <upanic.h> 32 #include "smbadm.h" 33 34 /* 35 * Share types for shiX_type fields - duplicated from smb.h 36 * Don't want to pull that in, and these are "carved in stone" 37 * (from the SMB protocol definitions) 38 */ 39 #ifndef _SHARE_TYPES_DEFINED_ 40 #define _SHARE_TYPES_DEFINED_ 41 #define STYPE_DISKTREE 0x00000000 42 #define STYPE_PRINTQ 0x00000001 43 #define STYPE_DEVICE 0x00000002 44 #define STYPE_IPC 0x00000003 45 #define STYPE_MASK 0x0000000F 46 #endif /* _SHARE_TYPES_DEFINED_ */ 47 48 #define MINS (60U) 49 #define HRS (60 * MINS) 50 #define DAYS (24 * HRS) 51 #define TIME_FMT "%F %T %Z" 52 53 #define _(x) gettext(x) 54 55 struct flag_tbl { 56 uint32_t flag; 57 const char *name; 58 }; 59 60 typedef enum user_field { 61 UF_SESS_ID, 62 UF_DOMAIN, 63 UF_ACCOUNT, 64 UF_USER, 65 UF_UID, 66 UF_WORKSTATION, 67 UF_IP, 68 UF_OS, 69 UF_LOGON_TIME, 70 UF_AGE, 71 UF_NUMOPEN, 72 UF_FLAGS, 73 } user_field_t; 74 75 typedef enum tree_field { 76 TF_ID, 77 TF_TYPE, 78 TF_NUMOPEN, 79 TF_NUMUSERS, 80 TF_TIME, 81 TF_AGE, 82 TF_USERNAME, 83 TF_SHARE, 84 } tree_field_t; 85 86 typedef enum netfileinfo_field { 87 NFIF_FID, 88 NFIF_UNIQID, 89 NFIF_PERMS, 90 NFIF_NUMLOCKS, 91 NFIF_PATH, 92 NFIF_USERNAME, 93 } netfileinfo_field_t; 94 95 static ofmt_handle_t cmd_create_handle(int, char **, const char *, 96 ofmt_field_t *); 97 98 static boolean_t fmt_user(ofmt_arg_t *, char *, uint_t); 99 static boolean_t fmt_tree(ofmt_arg_t *, char *, uint_t); 100 static boolean_t fmt_netfileinfo(ofmt_arg_t *, char *, uint_t); 101 102 static void print_str(const char *restrict, char *restrict, uint_t); 103 static void print_u32(uint32_t, char *, uint_t); 104 static void print_age(time_t, char *, uint_t); 105 static void print_time(time_t, const char *, char *, uint_t); 106 static void print_flags(struct flag_tbl *, size_t, uint32_t, char *, uint_t); 107 static void print_perms(struct flag_tbl *, size_t, uint32_t, char *, uint_t); 108 109 static ofmt_field_t smb_user_fields[] = { 110 { "ID", 4, UF_SESS_ID, fmt_user }, 111 { "DOMAIN", 32, UF_DOMAIN, fmt_user }, 112 { "ACCT", 16, UF_ACCOUNT, fmt_user }, 113 { "USER", 32, UF_USER, fmt_user }, 114 { "UID", 12, UF_UID, fmt_user }, 115 { "COMPUTER", 16, UF_WORKSTATION, fmt_user }, 116 { "IP", 15, UF_IP, fmt_user }, 117 { "OS", 8, UF_OS, fmt_user }, 118 { "LOGON", 24, UF_LOGON_TIME, fmt_user }, 119 { "AGE", 16, UF_AGE, fmt_user }, 120 { "NOPEN", 5, UF_NUMOPEN, fmt_user }, 121 { "FLAGS", 12, UF_FLAGS, fmt_user }, 122 { NULL, 0, 0, NULL } 123 }; 124 125 static const char default_user_fields[] = "IP,USER,NOPEN,AGE,FLAGS"; 126 127 struct flag_tbl user_flag_tbl[] = { 128 { SMB_ATF_GUEST, "GUEST" }, 129 { SMB_ATF_ANON, "ANON" }, 130 { SMB_ATF_ADMIN, "ADMIN" }, 131 { SMB_ATF_POWERUSER, "POWERUSER" }, 132 { SMB_ATF_BACKUPOP, "BACKUPOP" }, 133 }; 134 135 static ofmt_field_t smb_tree_fields[] = { 136 { "ID", 4, TF_ID, fmt_tree }, 137 { "TYPE", 6, TF_TYPE, fmt_tree }, 138 { "NOPEN", 6, TF_NUMOPEN, fmt_tree }, 139 { "NUSER", 6, TF_NUMUSERS, fmt_tree }, 140 { "TIME", 24, TF_TIME, fmt_tree }, 141 { "AGE", 12, TF_AGE, fmt_tree }, 142 { "USER", 32, TF_USERNAME, fmt_tree }, 143 { "SHARE", 16, TF_SHARE, fmt_tree }, 144 { NULL, 0, 0, NULL } 145 }; 146 147 static const char default_tree_fields[] = "TYPE,SHARE,USER,NOPEN,AGE"; 148 149 static ofmt_field_t smb_netfileinfo_fields[] = { 150 { "ID", 4, NFIF_FID, fmt_netfileinfo }, 151 { "UNIQID", 8, NFIF_UNIQID, fmt_netfileinfo }, 152 { "PERM", 15, NFIF_PERMS, fmt_netfileinfo }, 153 { "NLOCK", 6, NFIF_NUMLOCKS, fmt_netfileinfo }, 154 { "PATH", 32, NFIF_PATH, fmt_netfileinfo }, 155 { "USER", 16, NFIF_USERNAME, fmt_netfileinfo }, 156 { NULL, 0, 0, NULL } 157 }; 158 159 static const char default_netfileinfo_fields[] = "UNIQID,PATH,USER,NLOCK,PERM"; 160 161 /* 162 * Flags are the same as "ls -V" and chmod ACLs: 163 * eg: everyone@:rwxpdDaARWcCos:fd----I:allow 164 * See libsec:acltext.c 165 */ 166 static struct flag_tbl nfi_perm_tbl[] = { 167 { ACE_READ_DATA, "r" }, 168 { ACE_WRITE_DATA, "w" }, 169 { ACE_EXECUTE, "x" }, 170 { ACE_APPEND_DATA, "p" }, 171 { ACE_DELETE, "d" }, 172 { ACE_DELETE_CHILD, "D" }, 173 { ACE_READ_ATTRIBUTES, "a" }, 174 { ACE_WRITE_ATTRIBUTES, "A" }, 175 { ACE_READ_NAMED_ATTRS, "R" }, 176 { ACE_WRITE_NAMED_ATTRS, "W" }, 177 { ACE_READ_ACL, "c" }, 178 { ACE_WRITE_ACL, "C" }, 179 { ACE_WRITE_OWNER, "o" }, 180 { ACE_SYNCHRONIZE, "s" }, 181 }; 182 183 static int do_enum(smb_svcenum_t *, ofmt_handle_t); 184 static void ofmt_fatal(ofmt_handle_t, ofmt_field_t *, ofmt_status_t) 185 __NORETURN; 186 static void fatal(const char *, ...) __NORETURN; 187 188 time_t now; 189 boolean_t opt_p; 190 boolean_t opt_x; 191 192 int 193 cmd_list_sess(int argc, char **argv) 194 { 195 ofmt_handle_t hdl; 196 smb_svcenum_t req = { 197 .se_type = SMB_SVCENUM_TYPE_USER, 198 .se_level = 1, 199 .se_nlimit = UINT32_MAX, 200 }; 201 int rc; 202 203 hdl = cmd_create_handle(argc, argv, default_user_fields, 204 smb_user_fields); 205 rc = do_enum(&req, hdl); 206 ofmt_close(hdl); 207 return (rc); 208 } 209 210 int 211 cmd_list_trees(int argc, char **argv) 212 { 213 ofmt_handle_t hdl; 214 smb_svcenum_t req = { 215 .se_type = SMB_SVCENUM_TYPE_TREE, 216 .se_level = 1, 217 .se_nlimit = UINT32_MAX, 218 }; 219 int rc; 220 221 hdl = cmd_create_handle(argc, argv, default_tree_fields, 222 smb_tree_fields); 223 rc = do_enum(&req, hdl); 224 ofmt_close(hdl); 225 return (rc); 226 } 227 228 int 229 cmd_list_ofiles(int argc, char **argv) 230 { 231 ofmt_handle_t hdl; 232 smb_svcenum_t req = { 233 .se_type = SMB_SVCENUM_TYPE_FILE, 234 .se_level = 1, 235 .se_nlimit = UINT32_MAX, 236 }; 237 int rc; 238 239 hdl = cmd_create_handle(argc, argv, default_netfileinfo_fields, 240 smb_netfileinfo_fields); 241 rc = do_enum(&req, hdl); 242 ofmt_close(hdl); 243 return (rc); 244 } 245 246 static ofmt_handle_t 247 cmd_create_handle(int argc, char **argv, const char *def, ofmt_field_t *templ) 248 { 249 const char *fields = def; 250 ofmt_handle_t hdl; 251 ofmt_status_t status; 252 uint_t flags = 0; 253 int c; 254 255 while ((c = getopt(argc, argv, "Ho:px")) != -1) { 256 switch (c) { 257 case 'H': 258 flags |= OFMT_NOHEADER; 259 break; 260 case 'o': 261 fields = optarg; 262 break; 263 case 'p': 264 opt_p = B_TRUE; 265 flags |= OFMT_PARSABLE; 266 break; 267 case 'x': 268 opt_x = B_TRUE; 269 break; 270 case '?': 271 /* Note: getopt prints an error for us. */ 272 return (NULL); 273 } 274 } 275 276 status = ofmt_open(fields, templ, flags, 0, &hdl); 277 if (status != OFMT_SUCCESS) 278 ofmt_fatal(hdl, templ, status); 279 280 return (hdl); 281 } 282 283 int 284 cmd_close_ofile(int argc, char **argv) 285 { 286 uint_t errs = 0; 287 288 if (argc < 2) { 289 warnx(_("Missing file id")); 290 return (2); 291 } 292 293 for (int i = 1; i < argc; i++) { 294 unsigned long ul; 295 int rc; 296 297 errno = 0; 298 ul = strtoul(argv[i], NULL, 0); 299 if (errno != 0) { 300 warnx(_("Invalid file id '%s'"), argv[i]); 301 return (2); 302 } 303 #ifdef _LP64 304 if (ul > UINT32_MAX) { 305 warnx(_("File id %lu too large"), ul); 306 return (2); 307 } 308 #endif 309 310 /* 311 * See SMB_IOC_FILE_CLOSE (ioc.uniqid) 312 * and smb_server_file_close() 313 */ 314 rc = smb_kmod_file_close((uint32_t)ul); 315 if (rc != 0) { 316 /* 317 * Since the user can specify the fid as a decimal 318 * or hex value, we use the string they gave us so 319 * the value displayed matches what we were given. 320 */ 321 warnc(rc, _("Closing fid %s failed"), argv[i]); 322 errs++; 323 } 324 } 325 326 if (errs > 0) 327 return (1); 328 return (0); 329 } 330 331 int 332 cmd_close_sess(int argc, char **argv) 333 { 334 const char *client; 335 const char *user = NULL; 336 int rc; 337 338 if (argc < 2) { 339 warnx(_("clientname and username missing")); 340 return (2); 341 } 342 client = argv[1]; 343 if (argc > 2) { 344 user = argv[2]; 345 } 346 347 /* 348 * See SMB_IOC_SESSION_CLOSE (ioc.client, ioc.username) 349 * and smb_server_session_close(). The "client" part 350 * can be EITHER the "workstation" or the IP address, 351 * as shown in the "COMPUTER" and "IP" fields in the 352 * output of "list-sessions". The (optional) "user" 353 * part is as shown in "USER" part of that output. 354 */ 355 rc = smb_kmod_session_close(client, user); 356 if (rc != 0) { 357 rc = 1; 358 } 359 return (rc); 360 } 361 362 static boolean_t 363 fmt_user(ofmt_arg_t *arg, char *buf, uint_t buflen) 364 { 365 smb_netuserinfo_t *ui = arg->ofmt_cbarg; 366 user_field_t field = (user_field_t)arg->ofmt_id; 367 368 switch (field) { 369 case UF_SESS_ID: 370 (void) snprintf(buf, buflen, "%" PRIu64, ui->ui_session_id); 371 break; 372 case UF_DOMAIN: 373 print_str(ui->ui_domain, buf, buflen); 374 break; 375 case UF_ACCOUNT: 376 print_str(ui->ui_account, buf, buflen); 377 break; 378 case UF_USER: 379 (void) snprintf(buf, buflen, "%s\\%s", ui->ui_domain, 380 ui->ui_account); 381 break; 382 case UF_UID: 383 VERIFY3U(arg->ofmt_width, <, INT_MAX); 384 (void) snprintf(buf, buflen, "%u", ui->ui_posix_uid); 385 break; 386 case UF_WORKSTATION: 387 print_str(ui->ui_workstation, buf, buflen); 388 break; 389 case UF_IP: 390 (void) smb_inet_ntop(&ui->ui_ipaddr, buf, buflen); 391 break; 392 case UF_OS: 393 /* XXX: Lookup string value */ 394 (void) snprintf(buf, buflen, "%" PRId32, ui->ui_native_os); 395 break; 396 case UF_LOGON_TIME: 397 print_time(ui->ui_logon_time, TIME_FMT, buf, buflen); 398 break; 399 case UF_AGE: 400 print_age(now - ui->ui_logon_time, buf, buflen); 401 break; 402 case UF_NUMOPEN: 403 print_u32(ui->ui_numopens, buf, buflen); 404 break; 405 case UF_FLAGS: 406 print_flags(user_flag_tbl, ARRAY_SIZE(user_flag_tbl), 407 ui->ui_flags, buf, buflen); 408 break; 409 default: 410 fatal("%s: invalid field %d", __func__, field); 411 } 412 413 return (B_TRUE); 414 } 415 416 static boolean_t 417 fmt_tree_type(uint32_t type, char *buf, uint_t buflen) 418 { 419 switch (type & STYPE_MASK) { 420 case STYPE_DISKTREE: 421 (void) strlcpy(buf, "DISK", buflen); 422 break; 423 case STYPE_PRINTQ: 424 (void) strlcpy(buf, "PRINTQ", buflen); 425 break; 426 case STYPE_DEVICE: 427 (void) strlcpy(buf, "DEVICE", buflen); 428 break; 429 case STYPE_IPC: 430 (void) strlcpy(buf, "IPC", buflen); 431 break; 432 default: 433 (void) snprintf(buf, buflen, "%" PRIx32, type & STYPE_MASK); 434 break; 435 } 436 437 return (B_TRUE); 438 } 439 440 static boolean_t 441 fmt_tree(ofmt_arg_t *arg, char *buf, uint_t buflen) 442 { 443 smb_netconnectinfo_t *nc = arg->ofmt_cbarg; 444 tree_field_t field = (tree_field_t)arg->ofmt_id; 445 446 switch (field) { 447 case TF_ID: 448 (void) snprintf(buf, buflen, "%" PRIu32, nc->ci_id); 449 break; 450 case TF_TYPE: 451 return (fmt_tree_type(nc->ci_type, buf, buflen)); 452 case TF_NUMOPEN: 453 print_u32(nc->ci_numopens, buf, buflen); 454 break; 455 case TF_NUMUSERS: 456 print_u32(nc->ci_numusers, buf, buflen); 457 break; 458 case TF_TIME: 459 print_time(now - nc->ci_time, TIME_FMT, buf, buflen); 460 break; 461 case TF_AGE: 462 print_age(nc->ci_time, buf, buflen); 463 break; 464 case TF_USERNAME: 465 print_str(nc->ci_username, buf, buflen); 466 break; 467 case TF_SHARE: 468 print_str(nc->ci_share, buf, buflen); 469 break; 470 default: 471 fatal("%s: invalid field %d", __func__, field); 472 } 473 474 return (B_TRUE); 475 } 476 477 static boolean_t 478 fmt_netfileinfo(ofmt_arg_t *arg, char *buf, uint_t buflen) 479 { 480 smb_netfileinfo_t *fi = arg->ofmt_cbarg; 481 netfileinfo_field_t field = (netfileinfo_field_t)arg->ofmt_id; 482 483 switch (field) { 484 case NFIF_FID: 485 (void) snprintf(buf, buflen, "%" PRIu16, fi->fi_fid); 486 break; 487 case NFIF_UNIQID: 488 (void) snprintf(buf, buflen, "%" PRIu32, fi->fi_uniqid); 489 break; 490 case NFIF_PERMS: 491 print_perms(nfi_perm_tbl, ARRAY_SIZE(nfi_perm_tbl), 492 fi->fi_permissions, buf, buflen); 493 break; 494 case NFIF_NUMLOCKS: 495 print_u32(fi->fi_numlocks, buf, buflen); 496 break; 497 case NFIF_PATH: 498 print_str(fi->fi_path, buf, buflen); 499 break; 500 case NFIF_USERNAME: 501 print_str(fi->fi_username, buf, buflen); 502 break; 503 default: 504 fatal("%s: invalid field %d", __func__, field); 505 } 506 507 return (B_TRUE); 508 } 509 510 static int 511 do_enum(smb_svcenum_t *req, ofmt_handle_t hdl) 512 { 513 smb_netsvc_t *ns; 514 smb_netsvcitem_t *item; 515 uint32_t n = 0; 516 int rc; 517 518 if (hdl == NULL) 519 return (2); /* exit (2) -- usage */ 520 now = time(NULL); 521 522 for (;;) { 523 req->se_nskip = n; 524 525 ns = smb_kmod_enum_init(req); 526 if (ns == NULL) { 527 warnx(_("SMB enum initialization failure")); 528 return (1); 529 } 530 531 rc = smb_kmod_enum(ns); 532 if (rc != 0) { 533 /* 534 * When the SMB service is not running, expect ENXIO. 535 */ 536 if (rc == ENXIO) { 537 warnx(_("Kernel SMB server not running")); 538 return (1); 539 } 540 warnc(rc, _("SMB enumeration call failed")); 541 return (1); 542 } 543 544 if (list_is_empty(&ns->ns_list)) 545 break; 546 547 for (item = list_head(&ns->ns_list); item != NULL; 548 item = list_next(&ns->ns_list, item)) { 549 ofmt_print(hdl, &item->nsi_un); 550 n++; 551 } 552 553 smb_kmod_enum_fini(ns); 554 } 555 return (0); 556 } 557 558 static void 559 print_str(const char *restrict src, char *restrict buf, uint_t buflen) 560 { 561 if (src == NULL) { 562 buf[0] = '\0'; 563 return; 564 } 565 (void) strlcpy(buf, src, buflen); 566 } 567 568 static void 569 print_u32(uint32_t val, char *buf, uint_t buflen) 570 { 571 const char *fmt = opt_p ? "%" PRIu32 : "%'" PRIu32; 572 573 (void) snprintf(buf, buflen, fmt, val); 574 } 575 576 static void 577 print_age(time_t amt, char *buf, uint_t buflen) 578 { 579 uint32_t days = 0, hours = 0, mins = 0; 580 581 if (opt_p) { 582 (void) snprintf(buf, buflen, "%" PRId64, (int64_t)amt); 583 return; 584 } 585 586 if (amt >= DAYS) { 587 days = amt / DAYS; 588 amt %= DAYS; 589 } 590 if (amt >= HRS) { 591 hours = amt / HRS; 592 amt %= HRS; 593 } 594 if (amt >= MINS) { 595 mins = amt / MINS; 596 amt %= MINS; 597 } 598 599 if (days > 0) { 600 int n = snprintf(buf, buflen, "%" PRIu32 " days%s", 601 days, (hours > 0 || mins > 0 || amt > 0) ? ", " : ""); 602 603 VERIFY3U(buflen, >, n); 604 605 buf += n; 606 buflen -= n; 607 } 608 609 (void) snprintf(buf, buflen, "%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, 610 hours, mins, amt); 611 } 612 613 static void 614 print_time(time_t when, const char *fmt, char *buf, uint_t buflen) 615 { 616 const struct tm *tm; 617 618 if (opt_p) { 619 (void) snprintf(buf, buflen, "%" PRId64, (int64_t)when); 620 return; 621 } 622 623 tm = localtime(&when); 624 (void) strftime(buf, buflen - 1, fmt, tm); 625 } 626 627 static void 628 print_flags(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf, 629 uint_t buflen) 630 { 631 uint_t n = 0; 632 uint_t i; 633 634 if (opt_x) { 635 (void) snprintf(buf, buflen, "%" PRIx32, val); 636 return; 637 } 638 639 for (i = 0; i < nent; i++) { 640 if ((val & tbl[i].flag) == 0) 641 continue; 642 if (n > 0) 643 (void) strlcat(buf, ",", buflen); 644 (void) strlcat(buf, tbl[i].name, buflen); 645 n++; 646 } 647 648 if (n == 0) 649 (void) strlcat(buf, "-", buflen); 650 } 651 652 static void 653 print_perms(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf, 654 uint_t buflen) 655 { 656 uint_t n = 0; 657 uint_t i; 658 659 if (opt_x) { 660 (void) snprintf(buf, buflen, "%" PRIx32, val); 661 return; 662 } 663 664 for (i = 0; i < nent; i++) { 665 if ((val & tbl[i].flag) == 0) { 666 (void) strlcat(buf, "-", buflen); 667 } else { 668 (void) strlcat(buf, tbl[i].name, buflen); 669 } 670 n++; 671 } 672 } 673 674 __NORETURN static void 675 ofmt_fatal(ofmt_handle_t hdl, ofmt_field_t *templ, ofmt_status_t status) 676 { 677 char buf[OFMT_BUFSIZE]; 678 char *msg = ofmt_strerror(hdl, status, buf, sizeof (buf)); 679 680 warnx(_("ofmt error: %s"), msg); 681 682 if (status == OFMT_EBADFIELDS || 683 status == OFMT_ENOFIELDS) { 684 ofmt_field_t *f = templ; 685 fprintf(stderr, _("Valid fields are: ")); 686 while (f->of_name != NULL) { 687 fprintf(stderr, "%s", f->of_name); 688 f++; 689 if (f->of_name != NULL) 690 fprintf(stderr, ","); 691 } 692 fprintf(stderr, "\n"); 693 } 694 695 exit(EXIT_FAILURE); 696 } 697 698 __NORETURN static void 699 fatal(const char *msg, ...) 700 { 701 char buf[128]; 702 va_list ap; 703 size_t len; 704 705 va_start(ap, msg); 706 (void) vsnprintf(buf, sizeof (buf), msg, ap); 707 va_end(ap); 708 709 len = strlen(buf); 710 upanic(buf, len); 711 } 712