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 fprintf(stderr, _("Missing file id\n")); 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 fprintf(stderr, _("Invalid file id '%s'"), argv[i]); 301 return (2); 302 } 303 #ifdef _LP64 304 if (ul > UINT32_MAX) { 305 fprintf(stderr, _("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 warnx(_("Closing fid %s failed: %s"), argv[i], 322 strerror(rc)); 323 errs++; 324 } 325 } 326 327 if (errs > 0) 328 return (1); 329 return (0); 330 } 331 332 int 333 cmd_close_sess(int argc, char **argv) 334 { 335 const char *client; 336 const char *user = NULL; 337 int rc; 338 339 if (argc < 2) { 340 fprintf(stderr, _("clientname and username missing\n")); 341 return (2); 342 } 343 client = argv[1]; 344 if (argc > 2) { 345 user = argv[2]; 346 } 347 348 /* 349 * See SMB_IOC_SESSION_CLOSE (ioc.client, ioc.username) 350 * and smb_server_session_close(). The "client" part 351 * can be EITHER the "workstation" or the IP address, 352 * as shown in the "COMPUTER" and "IP" fields in the 353 * output of "list-sessions". The (optional) "user" 354 * part is as shown in "USER" part of that output. 355 */ 356 rc = smb_kmod_session_close(client, user); 357 if (rc != 0) { 358 rc = 1; 359 } 360 return (rc); 361 } 362 363 static boolean_t 364 fmt_user(ofmt_arg_t *arg, char *buf, uint_t buflen) 365 { 366 smb_netuserinfo_t *ui = arg->ofmt_cbarg; 367 user_field_t field = (user_field_t)arg->ofmt_id; 368 369 switch (field) { 370 case UF_SESS_ID: 371 (void) snprintf(buf, buflen, "%" PRIu64, ui->ui_session_id); 372 break; 373 case UF_DOMAIN: 374 print_str(ui->ui_domain, buf, buflen); 375 break; 376 case UF_ACCOUNT: 377 print_str(ui->ui_account, buf, buflen); 378 break; 379 case UF_USER: 380 (void) snprintf(buf, buflen, "%s\\%s", ui->ui_domain, 381 ui->ui_account); 382 break; 383 case UF_UID: 384 VERIFY3U(arg->ofmt_width, <, INT_MAX); 385 (void) snprintf(buf, buflen, "%u", ui->ui_posix_uid); 386 break; 387 case UF_WORKSTATION: 388 print_str(ui->ui_workstation, buf, buflen); 389 break; 390 case UF_IP: 391 (void) smb_inet_ntop(&ui->ui_ipaddr, buf, buflen); 392 break; 393 case UF_OS: 394 /* XXX: Lookup string value */ 395 (void) snprintf(buf, buflen, "%" PRId32, ui->ui_native_os); 396 break; 397 case UF_LOGON_TIME: 398 print_time(ui->ui_logon_time, TIME_FMT, buf, buflen); 399 break; 400 case UF_AGE: 401 print_age(now - ui->ui_logon_time, buf, buflen); 402 break; 403 case UF_NUMOPEN: 404 print_u32(ui->ui_numopens, buf, buflen); 405 break; 406 case UF_FLAGS: 407 print_flags(user_flag_tbl, ARRAY_SIZE(user_flag_tbl), 408 ui->ui_flags, buf, buflen); 409 break; 410 default: 411 fatal("%s: invalid field %d", __func__, field); 412 } 413 414 return (B_TRUE); 415 } 416 417 static boolean_t 418 fmt_tree_type(uint32_t type, char *buf, uint_t buflen) 419 { 420 switch (type & STYPE_MASK) { 421 case STYPE_DISKTREE: 422 (void) strlcpy(buf, "DISK", buflen); 423 break; 424 case STYPE_PRINTQ: 425 (void) strlcpy(buf, "PRINTQ", buflen); 426 break; 427 case STYPE_DEVICE: 428 (void) strlcpy(buf, "DEVICE", buflen); 429 break; 430 case STYPE_IPC: 431 (void) strlcpy(buf, "IPC", buflen); 432 break; 433 default: 434 (void) snprintf(buf, buflen, "%" PRIx32, type & STYPE_MASK); 435 break; 436 } 437 438 return (B_TRUE); 439 } 440 441 static boolean_t 442 fmt_tree(ofmt_arg_t *arg, char *buf, uint_t buflen) 443 { 444 smb_netconnectinfo_t *nc = arg->ofmt_cbarg; 445 tree_field_t field = (tree_field_t)arg->ofmt_id; 446 447 switch (field) { 448 case TF_ID: 449 (void) snprintf(buf, buflen, "%" PRIu32, nc->ci_id); 450 break; 451 case TF_TYPE: 452 return (fmt_tree_type(nc->ci_type, buf, buflen)); 453 case TF_NUMOPEN: 454 print_u32(nc->ci_numopens, buf, buflen); 455 break; 456 case TF_NUMUSERS: 457 print_u32(nc->ci_numusers, buf, buflen); 458 break; 459 case TF_TIME: 460 print_time(now - nc->ci_time, TIME_FMT, buf, buflen); 461 break; 462 case TF_AGE: 463 print_age(nc->ci_time, buf, buflen); 464 break; 465 case TF_USERNAME: 466 print_str(nc->ci_username, buf, buflen); 467 break; 468 case TF_SHARE: 469 print_str(nc->ci_share, buf, buflen); 470 break; 471 default: 472 fatal("%s: invalid field %d", __func__, field); 473 } 474 475 return (B_TRUE); 476 } 477 478 static boolean_t 479 fmt_netfileinfo(ofmt_arg_t *arg, char *buf, uint_t buflen) 480 { 481 smb_netfileinfo_t *fi = arg->ofmt_cbarg; 482 netfileinfo_field_t field = (netfileinfo_field_t)arg->ofmt_id; 483 484 switch (field) { 485 case NFIF_FID: 486 (void) snprintf(buf, buflen, "%" PRIu16, fi->fi_fid); 487 break; 488 case NFIF_UNIQID: 489 (void) snprintf(buf, buflen, "%" PRIu32, fi->fi_uniqid); 490 break; 491 case NFIF_PERMS: 492 print_perms(nfi_perm_tbl, ARRAY_SIZE(nfi_perm_tbl), 493 fi->fi_permissions, buf, buflen); 494 break; 495 case NFIF_NUMLOCKS: 496 print_u32(fi->fi_numlocks, buf, buflen); 497 break; 498 case NFIF_PATH: 499 print_str(fi->fi_path, buf, buflen); 500 break; 501 case NFIF_USERNAME: 502 print_str(fi->fi_username, buf, buflen); 503 break; 504 default: 505 fatal("%s: invalid field %d", __func__, field); 506 } 507 508 return (B_TRUE); 509 } 510 511 static int 512 do_enum(smb_svcenum_t *req, ofmt_handle_t hdl) 513 { 514 smb_netsvc_t *ns; 515 smb_netsvcitem_t *item; 516 uint32_t n = 0; 517 int rc; 518 519 if (hdl == NULL) 520 return (2); /* exit (2) -- usage */ 521 now = time(NULL); 522 523 for (;;) { 524 req->se_nskip = n; 525 526 ns = smb_kmod_enum_init(req); 527 if (ns == NULL) { 528 fprintf(stderr, _("SMB enum initialization failure")); 529 return (1); 530 } 531 532 rc = smb_kmod_enum(ns); 533 if (rc != 0) { 534 /* 535 * When the SMB service is not running, expect ENXIO. 536 */ 537 if (rc == ENXIO) { 538 fprintf(stderr, 539 _("Kernel SMB server not running")); 540 return (1); 541 } 542 fprintf(stderr, _("SMB enumeration call failed: %s"), 543 strerror(rc)); 544 return (1); 545 } 546 547 if (list_is_empty(&ns->ns_list)) 548 break; 549 550 for (item = list_head(&ns->ns_list); item != NULL; 551 item = list_next(&ns->ns_list, item)) { 552 ofmt_print(hdl, &item->nsi_un); 553 n++; 554 } 555 556 smb_kmod_enum_fini(ns); 557 } 558 return (0); 559 } 560 561 static void 562 print_str(const char *restrict src, char *restrict buf, uint_t buflen) 563 { 564 if (src == NULL) { 565 buf[0] = '\0'; 566 return; 567 } 568 (void) strlcpy(buf, src, buflen); 569 } 570 571 static void 572 print_u32(uint32_t val, char *buf, uint_t buflen) 573 { 574 const char *fmt = opt_p ? "%" PRIu32 : "%'" PRIu32; 575 576 (void) snprintf(buf, buflen, fmt, val); 577 } 578 579 static void 580 print_age(time_t amt, char *buf, uint_t buflen) 581 { 582 uint32_t days = 0, hours = 0, mins = 0; 583 584 if (opt_p) { 585 (void) snprintf(buf, buflen, "%" PRId64, (int64_t)amt); 586 return; 587 } 588 589 if (amt >= DAYS) { 590 days = amt / DAYS; 591 amt %= DAYS; 592 } 593 if (amt >= HRS) { 594 hours = amt / HRS; 595 amt %= HRS; 596 } 597 if (amt >= MINS) { 598 mins = amt / MINS; 599 amt %= MINS; 600 } 601 602 if (days > 0) { 603 int n = snprintf(buf, buflen, "%" PRIu32 " days%s", 604 days, (hours > 0 || mins > 0 || amt > 0) ? ", " : ""); 605 606 VERIFY3U(buflen, >, n); 607 608 buf += n; 609 buflen -= n; 610 } 611 612 (void) snprintf(buf, buflen, "%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, 613 hours, mins, amt); 614 } 615 616 static void 617 print_time(time_t when, const char *fmt, char *buf, uint_t buflen) 618 { 619 const struct tm *tm; 620 621 if (opt_p) { 622 (void) snprintf(buf, buflen, "%" PRId64, (int64_t)when); 623 return; 624 } 625 626 tm = localtime(&when); 627 (void) strftime(buf, buflen - 1, fmt, tm); 628 } 629 630 static void 631 print_flags(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf, 632 uint_t buflen) 633 { 634 uint_t n = 0; 635 uint_t i; 636 637 if (opt_x) { 638 (void) snprintf(buf, buflen, "%" PRIx32, val); 639 return; 640 } 641 642 for (i = 0; i < nent; i++) { 643 if ((val & tbl[i].flag) == 0) 644 continue; 645 if (n > 0) 646 (void) strlcat(buf, ",", buflen); 647 (void) strlcat(buf, tbl[i].name, buflen); 648 n++; 649 } 650 651 if (n == 0) 652 (void) strlcat(buf, "-", buflen); 653 } 654 655 static void 656 print_perms(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf, 657 uint_t buflen) 658 { 659 uint_t n = 0; 660 uint_t i; 661 662 if (opt_x) { 663 (void) snprintf(buf, buflen, "%" PRIx32, val); 664 return; 665 } 666 667 for (i = 0; i < nent; i++) { 668 if ((val & tbl[i].flag) == 0) { 669 (void) strlcat(buf, "-", buflen); 670 } else { 671 (void) strlcat(buf, tbl[i].name, buflen); 672 } 673 n++; 674 } 675 } 676 677 __NORETURN static void 678 ofmt_fatal(ofmt_handle_t hdl, ofmt_field_t *templ, ofmt_status_t status) 679 { 680 char buf[OFMT_BUFSIZE]; 681 char *msg = ofmt_strerror(hdl, status, buf, sizeof (buf)); 682 683 fprintf(stderr, _("ofmt error: %s\n"), msg); 684 685 if (status == OFMT_EBADFIELDS || 686 status == OFMT_ENOFIELDS) { 687 ofmt_field_t *f = templ; 688 fprintf(stderr, _("Valid fields are: ")); 689 while (f->of_name != NULL) { 690 fprintf(stderr, "%s", f->of_name); 691 f++; 692 if (f->of_name != NULL) 693 fprintf(stderr, ","); 694 } 695 fprintf(stderr, "\n"); 696 } 697 698 exit(EXIT_FAILURE); 699 } 700 701 __NORETURN static void 702 fatal(const char *msg, ...) 703 { 704 char buf[128]; 705 va_list ap; 706 size_t len; 707 708 va_start(ap, msg); 709 (void) vsnprintf(buf, sizeof (buf), msg, ap); 710 va_end(ap); 711 712 len = strlen(buf); 713 upanic(buf, len); 714 } 715