/* $NetBSD: fstat.c,v 1.120 2023/11/02 10:31:55 martin Exp $ */
/*-
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
The Regents of the University of California. All rights reserved.");
#endif /* not lint */
static int fsflg, /* show files on same filesystem as file(s) argument */
pflg, /* show files open by a particular pid */
uflg; /* show files open by a particular (effective) user */
static int checkfile; /* true if restricting to particular files or filesystems */
static int nflg; /* (numerical) display f.s. and rdev as dev_t */
static int Aflg; /* prefix with address of file structure */
static int Oflg; /* print offset instead of size */
int vflg; /* display errors in locating kernel data objects etc... */
static fdfile_t **ofiles; /* buffer of pointers to file structures */
static int fstat_maxfiles;
#define ALLOC_OFILES(d) \
if ((d) > fstat_maxfiles) { \
size_t len = (d) * sizeof(fdfile_t *); \
free(ofiles); \
ofiles = malloc(len); \
if (ofiles == NULL) { \
err(1, "malloc(%zu)", len); \
} \
fstat_maxfiles = (d); \
}
int
main(int argc, char **argv)
{
struct passwd *passwd;
struct kinfo_proc2 *p, *plast;
int arg, ch, what;
char *memf, *nlistf;
char buf[_POSIX2_LINE_MAX];
int cnt;
gid_t egid = getegid();
(void)setegid(getgid());
arg = 0;
what = KERN_PROC_ALL;
nlistf = memf = NULL;
while ((ch = getopt(argc, argv, "fnAOp:u:vN:M:")) != -1)
switch((char)ch) {
case 'f':
fsflg = 1;
break;
case 'M':
memf = optarg;
break;
case 'N':
nlistf = optarg;
break;
case 'n':
nflg = 1;
break;
case 'A':
Aflg = 1;
break;
case 'O':
Oflg = 1;
break;
case 'p':
if (pflg++)
usage();
if (!isdigit((unsigned char)*optarg)) {
warnx("-p requires a process id");
usage();
}
what = KERN_PROC_PID;
arg = atoi(optarg);
break;
case 'u':
if (uflg++)
usage();
if (!(passwd = getpwnam(optarg))) {
errx(1, "%s: unknown uid", optarg);
}
what = KERN_PROC_UID;
arg = passwd->pw_uid;
break;
case 'v':
vflg = 1;
break;
case '?':
default:
usage();
}
check_privs();
kdriver_init();
if (*(argv += optind)) {
for (; *argv; ++argv) {
if (getfname(*argv))
checkfile = 1;
}
if (!checkfile) /* file(s) specified, but none accessible */
exit(1);
}
ALLOC_OFILES(256); /* reserve space for file pointers */
if (fsflg && !checkfile) {
/* -f with no files means use wd */
if (getfname(".") == 0)
exit(1);
checkfile = 1;
}
/*
* Discard setgid privileges. If not the running kernel, we toss
* them away totally so that bad guys can't print interesting stuff
* from kernel memory, otherwise switch back to kmem for the
* duration of the kvm_openfiles() call.
*/
if (nlistf != NULL || memf != NULL)
(void)setgid(getgid());
else
(void)setegid(egid);
if (sysctlbyname(expname, &expaddr, &expsize, NULL, 0) == -1)
err(EXIT_FAILURE, "Can't get sysctl `%s'", expname);
if (expaddr == 0)
errx(EXIT_FAILURE, "This program does not work without "
"sysctl `%s' being set", expname);
}
if (!KVM_READ(fp, &fdfile, sizeof(fdfile))) {
dprintf("can't read file %d at %p for pid %d",
i, fp, Pid);
return;
}
if (fdfile.ff_file == NULL) {
dprintf("null ff_file for %d at %p for pid %d",
i, fp, Pid);
return;
}
if (!KVM_READ(fdfile.ff_file, &file, sizeof(file))) {
dprintf("can't read file %d at %p for pid %d",
i, fdfile.ff_file, Pid);
return;
}
if (Aflg && file.f_type != DTYPE_VNODE && checkfile == 0)
(void)printf("%*lx ",
2*(int)(sizeof(void*)), (long)fdfile.ff_file);
switch (file.f_type) {
case DTYPE_VNODE:
vtrans(&file, file.f_data, i, file.f_flag, (long)fdfile.ff_file);
break;
case DTYPE_SOCKET:
socktrans(&file, file.f_data, i);
break;
case DTYPE_PIPE:
if (checkfile == 0)
ptrans(&file, file.f_data, i);
break;
case DTYPE_MISC:
case DTYPE_KQUEUE:
case DTYPE_CRYPTO:
case DTYPE_MQUEUE:
case DTYPE_SEM:
case DTYPE_MEMFD:
if (checkfile == 0)
misctrans(&file, i);
break;
default:
dprintf("unknown file type %d for file %d of pid %d",
file.f_type, i, Pid);
break;
}
}
pbuf[0] = '\0';
/* fill in socket */
if (!KVM_READ(sock, &so, sizeof(struct socket))) {
dprintf("can't read sock at %p", sock);
goto bad;
}
/* fill in protosw entry */
if (!KVM_READ(so.so_proto, &proto, sizeof(struct protosw))) {
dprintf("can't read protosw at %p", so.so_proto);
goto bad;
}
/* fill in domain */
if (!KVM_READ(proto.pr_domain, &dom, sizeof(struct domain))) {
dprintf("can't read domain at %p", proto.pr_domain);
goto bad;
}
if (checkfile && dom.dom_family != AF_LOCAL)
return;
if ((len = kvm_read(kd, (u_long)dom.dom_name, dname,
sizeof(dname) - 1)) != sizeof(dname) -1) {
dprintf("can't read domain name at %p", dom.dom_name);
dname[0] = '\0';
}
else
dname[len] = '\0';
/*
* protocol specific formatting
*
* Try to find interesting things to print. For TCP, the interesting
* thing is the address of the tcpcb, for UDP and others, just the
* inpcb (socket pcb). For UNIX domain, its the address of the socket
* pcb and the address of the connected pcb (if connected). Otherwise
* just print the protocol number and address of the socket itself.
* The idea is not to duplicate netstat, but to make available enough
* information for further analysis.
*/
fbuf[0] = '\0';
lbuf[0] = '\0';
isdgram = false;
switch(dom.dom_family) {
case AF_INET:
getinetproto(pbuf, sizeof(pbuf), proto.pr_protocol);
switch (proto.pr_protocol) {
case IPPROTO_UDP:
isdgram = true;
/* FALLTHROUGH */
case IPPROTO_TCP:
if (so.so_pcb == NULL)
break;
if (kvm_read(kd, (u_long)so.so_pcb, (char *)&in4pcb,
sizeof(in4pcb)) != sizeof(in4pcb)) {
dprintf("can't read in4pcb at %p", so.so_pcb);
goto bad;
}
struct inpcb *inp = (struct inpcb *)&in4pcb;
inet_addrstr(lbuf, sizeof(lbuf), &in4p_laddr(inp),
ntohs(inp->inp_lport), isdgram);
inet_addrstr(fbuf, sizeof(fbuf), &in4p_faddr(inp),
ntohs(inp->inp_fport), isdgram);
break;
default:
break;
}
break;
#ifdef INET6
case AF_INET6:
getinetproto(pbuf, sizeof(pbuf), proto.pr_protocol);
switch (proto.pr_protocol) {
case IPPROTO_UDP:
isdgram = true;
/* FALLTHROUGH */
case IPPROTO_TCP:
if (so.so_pcb == NULL)
break;
if (kvm_read(kd, (u_long)so.so_pcb, (char *)&in6pcb,
sizeof(in6pcb)) != sizeof(in6pcb)) {
dprintf("can't read in6pcb at %p", so.so_pcb);
goto bad;
}
struct inpcb *inp = (struct inpcb *)&in6pcb;
inet6_addrstr(lbuf, sizeof(lbuf), &in6p_laddr(inp),
ntohs(inp->inp_lport), isdgram);
inet6_addrstr(fbuf, sizeof(fbuf), &in6p_faddr(inp),
ntohs(inp->inp_fport), isdgram);
break;
default:
break;
}
break;
#endif
case AF_LOCAL:
/* print address of pcb and connected pcb */
if (so.so_pcb) {
char shoconn[4], *cp;
void *pcb[2];
size_t p = 0;
pcb[0] = so.so_pcb;
cp = shoconn;
if (!(so.so_state & SS_CANTRCVMORE))
*cp++ = '<';
*cp++ = '-';
if (!(so.so_state & SS_CANTSENDMORE))
*cp++ = '>';
*cp = '\0';
again:
if (kvm_read(kd, (u_long)pcb[p], (char *)&unpcb,
sizeof(struct unpcb)) != sizeof(struct unpcb)){
dprintf("can't read unpcb at %p", so.so_pcb);
goto bad;
}
if (checkfile) {
struct vnode vn;
struct filestat fst;
const char *badtype, *filename;
if (unpcb.unp_vnode == NULL)
return;
if (!checkfs(unpcb.unp_vnode, &vn, &fst,
&badtype, &filename))
return;
}
if (unpcb.unp_addr) {
struct sockaddr_un *sun =
malloc(unpcb.unp_addrlen);
if (sun == NULL)
err(1, "malloc(%zu)",
unpcb.unp_addrlen);
if (kvm_read(kd, (u_long)unpcb.unp_addr,
sun, unpcb.unp_addrlen) !=
(ssize_t)unpcb.unp_addrlen) {
dprintf("can't read sun at %p",
unpcb.unp_addr);
free(sun);
} else {
snprintf(fbuf, sizeof(fbuf), " %s %s %s",
shoconn, sun->sun_path,
p == 0 ? "[creat]" : "[using]");
free(sun);
break;
}
}
if (unpcb.unp_conn) {
if (p == 0) {
pcb[++p] = unpcb.unp_conn;
goto again;
} else
snprintf(fbuf, sizeof(fbuf),
" %p %s %p", pcb[0], shoconn,
pcb[1]);
}
}
break;
case AF_APPLETALK:
getatproto(pbuf, sizeof(pbuf), proto.pr_protocol);
if (so.so_pcb) {
if (kvm_read(kd, (u_long)so.so_pcb, (char *)&ddpcb,
sizeof(ddpcb)) != sizeof(ddpcb)){
dprintf("can't read ddpcb at %p", so.so_pcb);
goto bad;
}
at_addrstr(fbuf, sizeof(fbuf), &ddpcb.ddp_fsat);
at_addrstr(lbuf, sizeof(lbuf), &ddpcb.ddp_lsat);
}
break;
default:
/* print protocol number and socket address */
snprintf(fbuf, sizeof(fbuf), " %d %jx", proto.pr_protocol,
(uintmax_t)(uintptr_t)sock);
break;
}
PREFIX(i);
if ((u_short)so.so_type > STYPEMAX)
(void)printf("* %s ?%d", dname, so.so_type);
else
(void)printf("* %s %s", dname, stypename[so.so_type]);