1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 *
26 * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
27 */
28
29
30#include <sys/mdb_modapi.h>
31#include <mdb/mdb_ctf.h>
32#include <sys/types.h>
33#include <sys/socket.h>
34
35#include <netsmb/smb_conn.h>
36#include <netsmb/smb_rq.h>
37#include <netsmb/smb_pass.h>
38
39#ifdef _KERNEL
40#define	NSMB_OBJNAME	"nsmb"
41#else
42#define	NSMB_OBJNAME	"libfknsmb.so.1"
43#endif
44
45#define	OPT_VERBOSE	0x0001	/* Be [-v]erbose in dcmd's */
46#define	OPT_RECURSE	0x0002	/* recursive display */
47
48/*
49 * We need to read in a private copy
50 * of every string we want to print out.
51 */
52void
53print_str(uintptr_t addr)
54{
55	char buf[32];
56	int len, mx = sizeof (buf) - 4;
57
58	if ((len = mdb_readstr(buf, sizeof (buf), addr)) <= 0) {
59		mdb_printf(" (%p)", addr);
60	} else {
61		if (len > mx)
62			strcpy(&buf[mx], "...");
63		mdb_printf(" %s", buf);
64	}
65}
66
67
68/*
69 * Walker for smb_connobj_t structures, including
70 * smb_vc_t and smb_share_t which "inherit" from it.
71 * Tricky: Exploit the "inheritance" of smb_connobj_t
72 * with common functions for walk_init, walk_next.
73 */
74typedef struct smb_co_walk_data {
75	uintptr_t	pp;
76	int level;		/* SMBL_SM, SMBL_VC, SMBL_SHARE, ...  */
77	int size;		/* sizeof (union member) */
78	union co_u {
79		smb_connobj_t	co;	/* copy of the list element */
80		smb_vc_t	vc;
81		smb_share_t	ss;
82		smb_fh_t	fh;
83	} u;
84} smb_co_walk_data_t;
85
86/*
87 * Common walk_init for walking structs inherited
88 * from smb_connobj_t (smb_vc_t, smb_share_t)
89 */
90int
91smb_co_walk_init(mdb_walk_state_t *wsp, int level)
92{
93	smb_co_walk_data_t *smbw;
94	size_t psz;
95
96	if (wsp->walk_addr == 0)
97		return (WALK_ERR);
98
99	smbw = mdb_alloc(sizeof (*smbw), UM_SLEEP | UM_GC);
100	wsp->walk_data = smbw;
101
102	/*
103	 * Save the parent pointer for later checks, and
104	 * the level so we know which union member it is.
105	 * Also the size of this union member.
106	 */
107	smbw->pp = wsp->walk_addr;
108	smbw->level = level;
109	switch (level) {
110	case SMBL_SM:
111		smbw->size = sizeof (smbw->u.co);
112		break;
113	case SMBL_VC:
114		smbw->size = sizeof (smbw->u.vc);
115		break;
116	case SMBL_SHARE:
117		smbw->size = sizeof (smbw->u.ss);
118		break;
119	case SMBL_FH:
120		smbw->size = sizeof (smbw->u.fh);
121		break;
122	default:
123		smbw->size = sizeof (smbw->u);
124		break;
125	}
126
127	/*
128	 * Read in the parent object.  Just need the
129	 * invariant part (smb_connobj_t) so we can
130	 * get the list of children below it.
131	 */
132	psz = sizeof (smbw->u.co);
133	if (mdb_vread(&smbw->u.co, psz, smbw->pp) != psz) {
134		mdb_warn("cannot read connobj from %p", smbw->pp);
135		return (WALK_ERR);
136	}
137
138	/*
139	 * Finally, setup to walk the list of children.
140	 */
141	wsp->walk_addr = (uintptr_t)smbw->u.co.co_children.slh_first;
142
143	return (WALK_NEXT);
144}
145
146/*
147 * Walk the (global) VC list.
148 */
149int
150smb_vc_walk_init(mdb_walk_state_t *wsp)
151{
152	GElf_Sym sym;
153
154	if (wsp->walk_addr != 0) {
155		mdb_warn("::walk smb_vc only supports global walks\n");
156		return (WALK_ERR);
157	}
158
159	/* Locate the VC list head. */
160	if (mdb_lookup_by_obj(NSMB_OBJNAME, "smb_vclist", &sym)) {
161		mdb_warn("failed to lookup `smb_vclist'\n");
162		return (WALK_ERR);
163	}
164	wsp->walk_addr = sym.st_value;
165
166	return (smb_co_walk_init(wsp, SMBL_VC));
167}
168
169/*
170 * Walk the share list below some VC.
171 */
172int
173smb_ss_walk_init(mdb_walk_state_t *wsp)
174{
175
176	/*
177	 * Initial walk_addr is address of parent (VC)
178	 */
179	if (wsp->walk_addr == 0) {
180		mdb_warn("::walk smb_ss does not support global walks\n");
181		return (WALK_ERR);
182	}
183
184	return (smb_co_walk_init(wsp, SMBL_SHARE));
185}
186
187/*
188 * Walk the file hande list below some share.
189 */
190int
191smb_fh_walk_init(mdb_walk_state_t *wsp)
192{
193
194	/*
195	 * Initial walk_addr is address of parent (share)
196	 */
197	if (wsp->walk_addr == 0) {
198		mdb_warn("::walk smb_fh does not support global walks\n");
199		return (WALK_ERR);
200	}
201
202	return (smb_co_walk_init(wsp, SMBL_FH));
203}
204
205/*
206 * Common walk_step for walking structs inherited
207 * from smb_connobj_t (smb_vc_t, smb_share_t)
208 */
209int
210smb_co_walk_step(mdb_walk_state_t *wsp)
211{
212	smb_co_walk_data_t *smbw = wsp->walk_data;
213	int status;
214
215	if (wsp->walk_addr == 0)
216		return (WALK_DONE);
217
218	if (mdb_vread(&smbw->u, smbw->size, wsp->walk_addr)
219	    != smbw->size) {
220		mdb_warn("cannot read connobj from %p", wsp->walk_addr);
221		return (WALK_ERR);
222	}
223
224	/* XXX: Sanity check level? parent pointer? */
225
226	status = wsp->walk_callback(wsp->walk_addr, &smbw->u,
227	    wsp->walk_cbdata);
228
229	wsp->walk_addr = (uintptr_t)smbw->u.co.co_next.sle_next;
230
231	return (status);
232}
233
234
235/*
236 * Dcmd (and callback function) to print a summary of
237 * all VCs, and optionally all shares under each VC.
238 */
239
240typedef struct smb_co_cbdata {
241	int flags;		/* OPT_...  */
242	int printed_header;
243	mdb_ctf_id_t ctf_id;
244} smb_co_cbdata_t;
245
246/*
247 * Call-back function for walking a file handle list.
248 */
249/* ARGSUSED */
250int
251smb_fh_cb(uintptr_t addr, const void *data, void *arg)
252{
253	const smb_fh_t *fhp = data;
254	// smb_co_cbdata_t *cbd = arg;
255
256	mdb_inc_indent(2);
257	mdb_printf(" %-p", addr);
258	if (fhp->fh_fid2.fid_volatile != 0) {
259		mdb_printf("\t0x%llx\n",
260		    (long long) fhp->fh_fid2.fid_volatile);
261	} else {
262		mdb_printf("\t0x%x\n", fhp->fh_fid1);
263	}
264
265	mdb_dec_indent(2);
266
267	return (WALK_NEXT);
268}
269
270/*
271 * Call-back function for walking a share list.
272 */
273int
274smb_ss_cb(uintptr_t addr, const void *data, void *arg)
275{
276	const smb_share_t *ssp = data;
277	smb_co_cbdata_t *cbd = arg;
278	uint32_t tid;
279
280	tid = ssp->ss2_tree_id;
281	if (tid == 0)
282		tid = ssp->ss_tid;
283
284	mdb_printf(" %-p\t0x%x\t%s\n", addr, tid, ssp->ss_name);
285
286	if (cbd->flags & OPT_RECURSE) {
287		mdb_inc_indent(2);
288		if (mdb_pwalk("nsmb_fh", smb_fh_cb, cbd, addr) < 0) {
289			mdb_warn("failed to walk 'nsmb_fh'");
290			/* Don't: return (WALK_ERR); */
291		}
292		mdb_dec_indent(2);
293	}
294
295	return (WALK_NEXT);
296}
297
298static const char *
299vcstate_str(smb_co_cbdata_t *cbd, int stval)
300{
301	static const char prefix[] = "SMBIOD_ST_";
302	int prefix_len = sizeof (prefix) - 1;
303	mdb_ctf_id_t vcst_enum;
304	const char *cp;
305
306	/* Got this in smb_vc_dcmd. */
307	vcst_enum = cbd->ctf_id;
308
309	/* Get the name for the enum value. */
310	if ((cp = mdb_ctf_enum_name(vcst_enum, stval)) == NULL)
311		return ("?");
312
313	/* Skip the prefix part. */
314	if (strncmp(cp, prefix, prefix_len) == 0)
315		cp += prefix_len;
316
317	return (cp);
318}
319
320/*
321 * Call-back function for walking the VC list.
322 */
323int
324smb_vc_cb(uintptr_t addr, const void *data, void *arg)
325{
326	const smb_vc_t *vcp = data;
327	smb_co_cbdata_t *cbd = arg;
328
329	if (cbd->printed_header == 0) {
330		cbd->printed_header = 1;
331		mdb_printf("// smb_vc_t  uid  server  \tuser\t\tstate\n");
332	}
333
334	mdb_printf("%-p", addr);
335	mdb_printf(" %7d", vcp->vc_owner);
336
337	switch (vcp->vc_srvaddr.sa.sa_family) {
338	case AF_INET:
339		mdb_printf(" %I", vcp->vc_srvaddr.sin.sin_addr);
340		break;
341	case AF_INET6:
342		mdb_printf(" %N", &vcp->vc_srvaddr.sin6.sin6_addr);
343		break;
344	default:
345		mdb_printf(" %15s", "(bad af)");
346		break;
347	}
348
349	if (vcp->vc_username[0] != '\0')
350		mdb_printf("\t%s", vcp->vc_username);
351	else
352		mdb_printf("\t%s", "(?)");
353
354	if (vcp->vc_domain[0] != '\0')
355		mdb_printf("@%s", vcp->vc_domain);
356
357	mdb_printf("\t%s\n", vcstate_str(cbd, vcp->vc_state));
358
359	if (cbd->flags & OPT_RECURSE) {
360		mdb_inc_indent(2);
361		if (mdb_pwalk("nsmb_ss", smb_ss_cb, cbd, addr) < 0) {
362			mdb_warn("failed to walk 'nsmb_ss'");
363			/* Don't: return (WALK_ERR); */
364		}
365		mdb_dec_indent(2);
366	}
367
368	return (WALK_NEXT);
369}
370
371int
372smb_vc_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
373{
374	smb_co_cbdata_t cbd;
375	smb_vc_t *vcp;
376	size_t vcsz;
377
378	memset(&cbd, 0, sizeof (cbd));
379
380	if (mdb_getopts(argc, argv,
381	    'r', MDB_OPT_SETBITS, OPT_RECURSE, &cbd.flags,
382	    'v', MDB_OPT_SETBITS, OPT_VERBOSE, &cbd.flags,
383	    NULL) != argc) {
384		return (DCMD_USAGE);
385	}
386
387	if (mdb_ctf_lookup_by_name("enum smbiod_state", &cbd.ctf_id) == -1) {
388		mdb_warn("Could not find enum smbiod_state");
389	}
390
391	if (!(flags & DCMD_ADDRSPEC)) {
392		if (mdb_walk("nsmb_vc", smb_vc_cb, &cbd) == -1) {
393			mdb_warn("failed to walk 'nsmb_vc'");
394			return (DCMD_ERR);
395		}
396		return (DCMD_OK);
397	}
398
399	vcsz = sizeof (*vcp);
400	vcp = mdb_alloc(vcsz, UM_SLEEP | UM_GC);
401	if (mdb_vread(vcp, vcsz, addr) != vcsz) {
402		mdb_warn("cannot read VC from %p", addr);
403		return (DCMD_ERR);
404	}
405	smb_vc_cb(addr, vcp, &cbd);
406
407	return (DCMD_OK);
408}
409
410void
411smb_vc_help(void)
412{
413	mdb_printf("Options:\n"
414	    "  -r           recursive display of share lists\n"
415	    "  -v           be verbose when displaying smb_vc\n");
416}
417
418/*
419 * Walker for the request list on a VC,
420 * and dcmd to show a summary.
421 */
422int
423rqlist_walk_init(mdb_walk_state_t *wsp)
424{
425	struct smb_rqhead rqh;
426	uintptr_t addr;
427
428	/*
429	 * Initial walk_addr is the address of the VC.
430	 * Add offsetof(iod_rqlist) to get the rqhead.
431	 */
432	if (wsp->walk_addr == 0) {
433		mdb_warn("::walk smb_ss does not support global walks\n");
434		return (WALK_ERR);
435	}
436	addr = wsp->walk_addr;
437	addr += OFFSETOF(smb_vc_t, iod_rqlist);
438
439	if (mdb_vread(&rqh, sizeof (rqh), addr) == -1) {
440		mdb_warn("failed to read smb_rqhead at %p", addr);
441		return (WALK_ERR);
442	}
443	wsp->walk_addr = (uintptr_t)rqh.tqh_first;
444
445	return (WALK_NEXT);
446}
447
448int
449rqlist_walk_step(mdb_walk_state_t *wsp)
450{
451	smb_rq_t rq;
452	int status;
453
454	if (wsp->walk_addr == 0)
455		return (WALK_DONE);
456
457	if (mdb_vread(&rq, sizeof (rq), wsp->walk_addr) == -1) {
458		mdb_warn("cannot read smb_rq from %p", wsp->walk_addr);
459		return (WALK_ERR);
460	}
461
462	status = wsp->walk_callback(wsp->walk_addr, &rq,
463	    wsp->walk_cbdata);
464
465	wsp->walk_addr = (uintptr_t)rq.sr_link.tqe_next;
466
467	return (status);
468}
469
470typedef struct rqlist_cbdata {
471	int printed_header;
472	int vcflags;
473	uintptr_t uid;		/* optional filtering by UID */
474} rqlist_cbdata_t;
475
476int
477rqlist_cb(uintptr_t addr, const void *data, void *arg)
478{
479	const smb_rq_t *rq = data;
480	rqlist_cbdata_t *cbd = arg;
481
482	if (cbd->printed_header == 0) {
483		cbd->printed_header = 1;
484		mdb_printf("// smb_rq_t MID cmd sr_state sr_flags\n");
485	}
486
487	mdb_printf(" %-p", addr);	/* smb_rq_t */
488	if ((cbd->vcflags & SMBV_SMB2) != 0) {
489		mdb_printf(" x%04llx", (long long)rq->sr2_messageid);
490		mdb_printf(" x%02x", rq->sr2_command);
491	} else {
492		mdb_printf(" x%04x", rq->sr_mid);
493		mdb_printf(" x%02x", rq->sr_cmd);
494	}
495	mdb_printf(" %d", rq->sr_state);
496	mdb_printf(" x%x", rq->sr_flags);
497	mdb_printf("\n");
498
499	return (WALK_NEXT);
500}
501
502/*ARGSUSED*/
503int
504rqlist_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
505{
506	rqlist_cbdata_t cbd;
507	smb_vc_t *vcp;
508	size_t vcsz;
509
510	memset(&cbd, 0, sizeof (cbd));
511
512	/* Need the VC again to get  */
513	vcsz = sizeof (*vcp);
514	vcp = mdb_alloc(vcsz, UM_SLEEP | UM_GC);
515	if (mdb_vread(vcp, vcsz, addr) != vcsz) {
516		mdb_warn("cannot read VC from %p", addr);
517		return (DCMD_ERR);
518	}
519	cbd.vcflags = vcp->vc_flags;
520
521	/*
522	 * Initial walk_addr is address of parent (VC)
523	 */
524	if (!(flags & DCMD_ADDRSPEC)) {
525		mdb_warn("address required\n");
526		return (DCMD_ERR);
527	}
528
529	if (mdb_pwalk("nsmb_rqlist", rqlist_cb, &cbd, addr) == -1) {
530		mdb_warn("failed to walk 'nsmb_rqlist'");
531		return (DCMD_ERR);
532	}
533
534	return (DCMD_OK);
535}
536
537
538/*
539 * AVL walker for the passwords AVL tree,
540 * and dcmd to show a summary.
541 */
542static int
543pwtree_walk_init(mdb_walk_state_t *wsp)
544{
545	GElf_Sym sym;
546
547	if (wsp->walk_addr != 0) {
548		mdb_warn("pwtree walk only supports global walks\n");
549		return (WALK_ERR);
550	}
551
552	if (mdb_lookup_by_obj(NSMB_OBJNAME, "smb_ptd", &sym) == -1) {
553		mdb_warn("failed to find symbol 'smb_ptd'");
554		return (WALK_ERR);
555	}
556
557	wsp->walk_addr = (uintptr_t)sym.st_value;
558
559	if (mdb_layered_walk("avl", wsp) == -1) {
560		mdb_warn("failed to walk 'avl'\n");
561		return (WALK_ERR);
562	}
563
564	return (WALK_NEXT);
565}
566
567static int
568pwtree_walk_step(mdb_walk_state_t *wsp)
569{
570	smb_passid_t	ptnode;
571
572	if (mdb_vread(&ptnode, sizeof (ptnode), wsp->walk_addr) == -1) {
573		mdb_warn("failed to read smb_passid_t at %p", wsp->walk_addr);
574		return (WALK_ERR);
575	}
576
577	return (wsp->walk_callback(wsp->walk_addr, &ptnode, wsp->walk_cbdata));
578}
579
580typedef struct pwtree_cbdata {
581	int printed_header;
582	uid_t uid;		/* optional filtering by UID */
583} pwtree_cbdata_t;
584
585int
586pwtree_cb(uintptr_t addr, const void *data, void *arg)
587{
588	const smb_passid_t *ptn = data;
589	pwtree_cbdata_t *cbd = arg;
590
591	/* Optional filtering by UID. */
592	if (cbd->uid != (uid_t)-1 && cbd->uid != ptn->uid) {
593		return (WALK_NEXT);
594	}
595
596	if (cbd->printed_header == 0) {
597		cbd->printed_header = 1;
598		mdb_printf("// smb_passid_t UID domain user\n");
599	}
600
601	mdb_printf(" %-p", addr);	/* smb_passid_t */
602	mdb_printf(" %d", (uintptr_t)ptn->uid);
603	print_str((uintptr_t)ptn->srvdom);
604	print_str((uintptr_t)ptn->username);
605	mdb_printf("\n");
606
607	return (WALK_NEXT);
608}
609
610/*ARGSUSED*/
611int
612pwtree_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
613{
614	pwtree_cbdata_t cbd;
615	char *uid_str = NULL;
616	char buf[32];
617
618	memset(&cbd, 0, sizeof (cbd));
619
620	if (mdb_getopts(argc, argv,
621	    'u', MDB_OPT_STR, &uid_str, NULL) != argc) {
622		return (DCMD_USAGE);
623	}
624	if (uid_str) {
625		/*
626		 * Want the the default radix to be 10 here.
627		 * If the string has some kind of radix prefix,
628		 * just use that as-is, otherwise prepend "0t".
629		 * Cheating on the "not a digit" test, but
630		 * mdb_strtoull will do a real syntax check.
631		 */
632		if (uid_str[0] == '0' && uid_str[1] > '9') {
633			cbd.uid = (uid_t)mdb_strtoull(uid_str);
634		} else {
635			strcpy(buf, "0t");
636			strlcat(buf, uid_str, sizeof (buf));
637			cbd.uid = (uid_t)mdb_strtoull(buf);
638		}
639	} else
640		cbd.uid = (uid_t)-1;
641
642	if (flags & DCMD_ADDRSPEC) {
643		mdb_warn("address not allowed\n");
644		return (DCMD_ERR);
645	}
646
647	if (mdb_pwalk("nsmb_pwtree", pwtree_cb, &cbd, 0) == -1) {
648		mdb_warn("failed to walk 'nsmb_pwtree'");
649		return (DCMD_ERR);
650	}
651
652	return (DCMD_OK);
653}
654
655void
656pwtree_help(void)
657{
658	mdb_printf("Options:\n"
659	    "  -u uid       show only entries belonging to uid (decimal)\n");
660}
661
662
663static const mdb_dcmd_t dcmds[] = {
664	{ "nsmb_vc", "?[-rv]",
665		"show smb_vc (or list)",
666		smb_vc_dcmd, smb_vc_help },
667	{ "nsmb_rqlist", ":",
668		"show smb_rq list on a VC",
669		rqlist_dcmd, NULL },
670	{ "nsmb_pwtree", "?[-u uid]",
671		"list smb_passid_t (password tree)",
672		pwtree_dcmd, pwtree_help },
673	{NULL}
674};
675
676static const mdb_walker_t walkers[] = {
677	{ "nsmb_vc", "walk nsmb VC list",
678		smb_vc_walk_init, smb_co_walk_step, NULL },
679	{ "nsmb_ss", "walk nsmb share list for some VC",
680		smb_ss_walk_init, smb_co_walk_step, NULL },
681	{ "nsmb_fh", "walk nsmb share list for some VC",
682		smb_fh_walk_init, smb_co_walk_step, NULL },
683	{ "nsmb_rqlist", "walk request list for some VC",
684		rqlist_walk_init, rqlist_walk_step, NULL },
685	{ "nsmb_pwtree", "walk passord AVL tree",
686		pwtree_walk_init, pwtree_walk_step, NULL },
687	{NULL}
688};
689
690static const mdb_modinfo_t modinfo = {
691	MDB_API_VERSION,
692	dcmds,
693	walkers
694};
695
696const mdb_modinfo_t *
697_mdb_init(void)
698{
699	return (&modinfo);
700}
701