diff options
Diffstat (limited to 'src/fuser.c')
-rw-r--r-- | src/fuser.c | 2237 |
1 files changed, 2237 insertions, 0 deletions
diff --git a/src/fuser.c b/src/fuser.c new file mode 100644 index 0000000..0c14128 --- /dev/null +++ b/src/fuser.c @@ -0,0 +1,2237 @@ +/* + * fuser.c - identify processes using files + * + * Based on fuser.c Copyright (C) 1993-2005 Werner Almesberger and Craig Small + * + * Completely re-written + * Copyright (C) 2005-2024 Craig Small <csmall@dropbear.xyz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/sysmacros.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <pwd.h> +#include <netdb.h> +#include <dirent.h> +#include <unistd.h> +#include <ctype.h> +#include <mntent.h> +#include <signal.h> +#include <getopt.h> +#include <setjmp.h> +#include <limits.h> +/* MAXSYMLINKS is a BSDism. If it doesn't exist, fall back to SYMLINK_MAX, + which is the POSIX name. */ +#ifndef MAXSYMLINKS +#define MAXSYMLINKS SYMLINK_MAX +#endif + +#ifdef ENABLE_NLS +#include <locale.h> +#endif + +#include "fuser.h" +#include "signals.h" +#include "i18n.h" +#include "statx.h" +#include "comm.h" + +//#define DEBUG 1 + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif /* PATH_MAX */ + +#define NAME_FIELD 20 /* space reserved for file name */ +/* Function defines */ +static void add_matched_proc(struct names *name_list, const pid_t pid, + const uid_t uid, const char access); +static void add_special_proc(struct names *name_list, const char ptype, + const uid_t uid, const char *command); +static void check_dir(const pid_t pid, const char *dirname, + struct device_list *dev_head, + struct inode_list *ino_head, const uid_t uid, + const char access, struct unixsocket_list *sockets, + dev_t netdev); +static void check_map(const pid_t pid, const char *filename, + struct device_list *dev_head, + struct inode_list *ino_head, const uid_t uid, + const char access); +static struct stat *get_pidstat(const pid_t pid, const char *filename); +static uid_t getpiduid(const pid_t pid); +static int print_matches(struct names *names_head, const opt_type opts, + const int sig_number); +static int kill_matched_proc(struct procs *pptr, const opt_type opts, + const int sig_number); + +/*int parse_mount(struct names *this_name, struct device_list **dev_list);*/ +static void add_device(struct device_list **dev_list, + struct names *this_name, dev_t device); +void fill_unix_cache(struct unixsocket_list **unixsocket_head); +void clear_unix_cache(struct unixsocket_list **unixsocket_head); +static void atexit_clear_unix_cache(); +static dev_t find_net_dev(void); +static void scan_procs( + struct names *names_head, struct inode_list *ino_head, + struct device_list *dev_head, + struct unixsocket_list *sockets, dev_t netdev); +static void scan_knfsd( + struct names *names_head, struct inode_list *ino_head, + struct device_list *dev_head); +static void scan_mounts( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head); +static void scan_swaps( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head); +#ifdef DEBUG +static void debug_match_lists( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head); +#endif + +static char *expandpath(const char *path); +static struct unixsocket_list *unixsockets = NULL; +static struct names *names_head = NULL, *names_tail = NULL; +static struct ip_connections *tcp_connection_list = NULL; +static struct ip_connections *udp_connection_list = NULL; +#ifdef WITH_IPV6 +static struct ip6_connections *tcp6_connection_list = NULL; +static struct ip6_connections *udp6_connection_list = NULL; +#endif +static struct device_list *match_devices = NULL; +static struct inode_list *match_inodes = NULL; + +static void usage( + const char *errormsg) +{ + if (errormsg != NULL) + fprintf(stderr, "%s\n", errormsg); + + fprintf(stderr, + _ + ("Usage: fuser [-fIMuvw] [-a|-s] [-4|-6] [-c|-m|-n SPACE]\n" + " [-k [-i] [-SIGNAL]] NAME...\n" + " fuser -l\n" + " fuser -V\n" + "Show which processes use the named files, sockets, or filesystems.\n\n" + " -a,--all display unused files too\n" + " -i,--interactive ask before killing (ignored without -k)\n" + " -I,--inode use always inodes to compare files\n" + " -k,--kill kill processes accessing the named file\n" + " -l,--list-signals list available signal names\n" + " -m,--mount show all processes using the named filesystems or\n" + " block device\n" + " -M,--ismountpoint fulfill request only if NAME is a mount point\n" + " -n,--namespace SPACE search in this name space (file, udp, or tcp)\n" + " -s,--silent silent operation\n" + " -SIGNAL send this signal instead of SIGKILL\n" + " -u,--user display user IDs\n" + " -v,--verbose verbose output\n" + " -w,--writeonly kill only processes with write access\n" + " -V,--version display version information\n")); +#ifdef WITH_IPV6 + fprintf(stderr, _(" -4,--ipv4 search IPv4 sockets only\n" + " -6,--ipv6 search IPv6 sockets only\n")); +#endif + fprintf(stderr, _(" udp/tcp names: [local_port][,[rmt_host][,[rmt_port]]]\n\n")); + exit(1); +} + +void print_version() +{ + fprintf(stderr, _("fuser (PSmisc) %s\n"), VERSION); + fprintf(stderr, + _("Copyright (C) 1993-2024 Werner Almesberger and Craig Small\n\n")); + fprintf(stderr, + _("PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "For more information about these matters, see the files named COPYING.\n")); +} + +static void scan_procs( + struct names *names_head, struct inode_list *ino_head, + struct device_list *dev_head, struct unixsocket_list *sockets, + dev_t netdev) +{ + DIR *topproc_dir; + struct dirent *topproc_dent; + struct inode_list *ino_tmp; + struct device_list *dev_tmp; + pid_t pid, my_pid; + uid_t uid; + + if ( (ino_head == NULL) && (dev_head == NULL) ) + return; + + if ((topproc_dir = opendir("/proc")) == NULL) { + fprintf(stderr, _("Cannot open /proc directory: %s\n"), + strerror(errno)); + exit(1); + } + my_pid = getpid(); + while ((topproc_dent = readdir(topproc_dir)) != NULL) { + dev_t cwd_dev, exe_dev, root_dev; + struct stat *cwd_stat = NULL; + struct stat *exe_stat = NULL; + struct stat *root_stat = NULL; + + if (topproc_dent->d_name[0] < '0' || topproc_dent->d_name[0] > '9') /* Not a process */ + continue; + pid = atoi(topproc_dent->d_name); + /* Dont print myself */ + if (pid == my_pid) + continue; + uid = getpiduid(pid); + + cwd_stat = get_pidstat(pid, "cwd"); + exe_stat = get_pidstat(pid, "exe"); + root_stat = get_pidstat(pid, "root"); + cwd_dev = cwd_stat ? cwd_stat->st_dev : 0; + exe_dev = exe_stat ? exe_stat->st_dev : 0; + root_dev = root_stat ? root_stat->st_dev : 0; + + /* Scan the devices */ + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) { + if (exe_dev == dev_tmp->device) + add_matched_proc(dev_tmp->name, pid, uid, + ACCESS_EXE); + if (root_dev == dev_tmp->device) + add_matched_proc(dev_tmp->name, pid, uid, + ACCESS_ROOT); + if (cwd_dev == dev_tmp->device) + add_matched_proc(dev_tmp->name, pid, uid, + ACCESS_CWD); + } + for (ino_tmp = ino_head; ino_tmp != NULL; + ino_tmp = ino_tmp->next) { + if (exe_dev == ino_tmp->device) { + if (!exe_stat) + exe_stat = get_pidstat(pid, "exe"); + if (exe_stat + && exe_stat->st_dev == ino_tmp->device + && exe_stat->st_ino == ino_tmp->inode) + add_matched_proc(ino_tmp->name, pid, + uid, ACCESS_EXE); + } + if (root_dev == ino_tmp->device) { + if (!root_stat) + root_stat = get_pidstat(pid, "root"); + if (root_stat + && root_stat->st_dev == ino_tmp->device + && root_stat->st_ino == ino_tmp->inode) + add_matched_proc(ino_tmp->name, pid, + uid, ACCESS_ROOT); + } + if (cwd_dev == ino_tmp->device) { + if (!cwd_stat) + cwd_stat = get_pidstat(pid, "cwd"); + if (cwd_stat + && cwd_stat->st_dev == ino_tmp->device + && cwd_stat->st_ino == ino_tmp->inode) + add_matched_proc(ino_tmp->name, pid, + uid, ACCESS_CWD); + } + } + if (root_stat) + free(root_stat); + if (cwd_stat) + free(cwd_stat); + if (exe_stat) + free(exe_stat); +#if !defined (__linux__) && !defined (__CYGWIN__) + check_dir(pid, "lib", dev_head, ino_head, uid, ACCESS_MMAP, + sockets, netdev); + check_dir(pid, "mmap", dev_head, ino_head, uid, ACCESS_MMAP, + sockets, netdev); +#endif + check_dir(pid, "fd", dev_head, ino_head, uid, ACCESS_FILE, + sockets, netdev); + check_map(pid, "maps", dev_head, ino_head, uid, ACCESS_MMAP); + + } /* while topproc_dent */ + closedir(topproc_dir); +} + +static void add_inode( + struct inode_list **ino_list, + struct names *this_name, + dev_t device, ino_t inode) +{ + struct inode_list *ino_tmp, *ino_head; + + if ((ino_tmp = + (struct inode_list *)malloc(sizeof(struct inode_list))) == NULL) + return; + ino_head = *ino_list; + ino_tmp->name = this_name; + ino_tmp->device = device; + ino_tmp->inode = inode; + ino_tmp->next = ino_head; + *ino_list = ino_tmp; +} + +static void add_device( + struct device_list **dev_list, + struct names *this_name, + dev_t device) +{ + struct device_list *dev_tmp, *dev_head; +#ifdef DEBUG + fprintf(stderr, "add_device(%s %u\n", this_name->filename, + (unsigned int)device); +#endif /* DEBUG */ + + if ((dev_tmp = + (struct device_list *)malloc(sizeof(struct device_list))) == NULL) + return; + dev_head = *dev_list; + dev_tmp->name = this_name; + dev_tmp->device = device; + dev_tmp->next = dev_head; + *dev_list = dev_tmp; +} + +static void add_ip_conn( + struct ip_connections **ip_list, const char *protocol, + struct names *this_name, const int lcl_port, const int rmt_port, + unsigned long rmt_address) +{ + struct ip_connections *ip_tmp, *ip_head; + + if ((ip_tmp = + (struct ip_connections *)malloc(sizeof(struct ip_connections))) == + NULL) + return; + ip_head = *ip_list; + ip_tmp->name = this_name; + ip_tmp->lcl_port = lcl_port; + ip_tmp->rmt_port = rmt_port; + ip_tmp->rmt_address.s_addr = rmt_address; + ip_tmp->next = ip_head; + + *ip_list = ip_tmp; +} + +#ifdef WITH_IPV6 +static void add_ip6_conn( + struct ip6_connections **ip_list, + const char *protocol, + struct names *this_name, + const int lcl_port, + const int rmt_port, + struct in6_addr rmt_address) +{ + struct ip6_connections *ip_tmp, *ip_head; + + if ((ip_tmp = + (struct ip6_connections *)malloc(sizeof(struct ip6_connections))) + == NULL) + return; + ip_head = *ip_list; + ip_tmp->name = this_name; + ip_tmp->lcl_port = lcl_port; + ip_tmp->rmt_port = rmt_port; + memcpy(&(ip_tmp->rmt_address), &(rmt_address), sizeof(struct in6_addr)); + ip_tmp->next = ip_head; + + *ip_list = ip_tmp; +} +#endif + +/* Adds a normal process only */ +static void add_matched_proc( + struct names *name_list, + const pid_t pid, + const uid_t uid, + const char access) +{ + struct procs *pptr, *last_proc; + char *pathname; + char cmdname[101], *cptr; + int cmdlen; + FILE *fp; + + last_proc = NULL; + for (pptr = name_list->matched_procs; pptr != NULL; pptr = pptr->next) + { + last_proc = pptr; + if (pptr->pid == pid) + { + pptr->access |= access; + return; + } + } + /* Not found */ + if ((pptr = (struct procs *)malloc(sizeof(struct procs))) == NULL) + { + fprintf(stderr, + _("Cannot allocate memory for matched proc: %s\n"), + strerror(errno)); + return; + } + pptr->pid = pid; + pptr->uid = uid; + pptr->access = access; + pptr->proc_type = PTYPE_NORMAL; + pptr->next = NULL; + /* set command name */ + pptr->command = NULL; + + fp = NULL; + pathname = NULL; + if ((asprintf(&pathname, "/proc/%d/stat", pid) > 0) && + ((fp = fopen(pathname, "r")) != NULL) && + (fscanf(fp, "%*d (%100[^)]", cmdname) == 1)) + if ((pptr->command = (char *)malloc(COMM_LEN + 1)) != NULL) + { + cmdlen = 0; + for (cptr = cmdname; cmdlen < COMM_LEN && *cptr; + cptr++) + { + if (isprint(*cptr)) + pptr->command[cmdlen++] = *cptr; + else if (cmdlen < (COMM_LEN - 4)) + cmdlen += + sprintf(&(pptr->command[cmdlen]), + "\\%03o", *cptr); + } + pptr->command[cmdlen] = '\0'; + } + if (last_proc == NULL) + name_list->matched_procs = pptr; + else + last_proc->next = pptr; + if (pathname) + free(pathname); + if (fp) + fclose(fp); +} + +/* Adds a knfsd etc process */ +static void add_special_proc( + struct names *name_list, + const char ptype, + const uid_t uid, + const char *command) +{ + struct procs *pptr; + + for (pptr = name_list->matched_procs; pptr != NULL; pptr = pptr->next) + { + if (pptr->proc_type == ptype) + return; + } + if ((pptr = malloc(sizeof(struct procs))) == NULL) + { + fprintf(stderr, + _("Cannot allocate memory for matched proc: %s\n"), + strerror(errno)); + return; + } + pptr->pid = 0; + pptr->uid = uid; + pptr->access = 0; + pptr->proc_type = ptype; + /* Append the special processes */ + pptr->next = name_list->matched_procs; + name_list->matched_procs = pptr; + /* set command name */ + pptr->command = strdup(command); +} + +int parse_file( + struct names *this_name, + struct inode_list **ino_list, + const opt_type opts) +{ + char *new = expandpath(this_name->filename); + if (new) + { + if (this_name->filename) + free(this_name->filename); + this_name->filename = strdup(new); + } + if (statn(this_name->filename, STATX_INO|STATX_TYPE, &(this_name->st)) != 0 ) + { + if (errno == ENOENT) + fprintf(stderr, + _("Specified filename %s does not exist.\n"), + this_name->filename); + else + fprintf(stderr, _("Cannot stat %s: %s\n"), + this_name->filename, strerror(errno)); + return -1; + } +#ifdef DEBUG + printf("adding file %s %lX %lX\n", this_name->filename, + (unsigned long)this_name->st.st_dev, + (unsigned long)this_name->st.st_ino); +#endif /* DEBUG */ + add_inode(ino_list, this_name, this_name->st.st_dev, + this_name->st.st_ino); + return 0; +} + +int parse_unixsockets( + struct names *this_name, + struct inode_list **ino_list, + struct unixsocket_list *sun_head) +{ + struct unixsocket_list *sun_tmp; + dev_t net_dev; + + net_dev = find_net_dev(); + + for (sun_tmp = sun_head; sun_tmp != NULL; sun_tmp = sun_tmp->next) + { + if (sun_tmp->dev == this_name->st.st_dev + && sun_tmp->inode == this_name->st.st_ino) + { + add_inode(ino_list, this_name, net_dev, + sun_tmp->net_inode); + return 0; + } + } + return 0; +} + +int parse_mounts( + struct names *this_name, + struct device_list **dev_list, + const opt_type opts) +{ + dev_t match_device; + + if (S_ISBLK(this_name->st.st_mode)) + match_device = this_name->st.st_rdev; + else + match_device = this_name->st.st_dev; + add_device(dev_list, this_name, match_device); + return 0; +} + +#ifdef WITH_IPV6 +int parse_inet( + struct names *this_name, + const int ipv4_only, + const int ipv6_only, + struct ip_connections **ip_list, + struct ip6_connections **ip6_list) +#else +int parse_inet( + struct names *this_name, + struct ip_connections **ip_list) +#endif +{ + struct addrinfo *res, *resptr; + struct addrinfo hints; + int errcode; + char *lcl_port_str, *rmt_addr_str, *rmt_port_str, *tmpstr, *tmpstr2; + in_port_t lcl_port; + struct sockaddr_in *sin; +#ifdef WITH_IPV6 + struct sockaddr_in6 *sin6; +#endif + char hostspec[100]; + char *protocol; + int i; + + if ((protocol = strchr(this_name->filename, '/')) == NULL) + return -1; + protocol++; + if (protocol[0] == '\0') + return -1; + for (i = 0; + i < 99 && this_name->filename[i] != '\0' + && this_name->filename[i] != '/'; i++) + hostspec[i] = this_name->filename[i]; + hostspec[i] = '\0'; + + lcl_port_str = rmt_addr_str = rmt_port_str = NULL; + /* Split out the names */ + if ((tmpstr = strchr(hostspec, ',')) == NULL) + { + /* Single option */ + lcl_port_str = strdup(hostspec); + } else { + if (tmpstr == hostspec) + lcl_port_str = NULL; + else { + *tmpstr = '\0'; + lcl_port_str = strdup(hostspec); + } + tmpstr++; + if (*tmpstr != '\0') { + if ((tmpstr2 = strchr(tmpstr, ',')) == NULL) + { + /* Only 2 options */ + rmt_addr_str = tmpstr; + } else { + if (tmpstr2 == tmpstr) + rmt_addr_str = NULL; + else { + rmt_addr_str = tmpstr; + *tmpstr2 = '\0'; + } + tmpstr2++; + if (*tmpstr2 != '\0') + rmt_port_str = tmpstr2; + } + } + } +#ifdef DEBUG + printf("parsed to lp %s rh %s rp %s\n", lcl_port_str, rmt_addr_str, + rmt_port_str); +#endif + + memset(&hints, 0, sizeof(hints)); +#ifdef WITH_IPV6 + if (ipv6_only) + { + hints.ai_family = PF_INET6; + } else if (ipv4_only) + { + hints.ai_family = PF_INET; + } else + hints.ai_family = PF_UNSPEC; +#else + hints.ai_family = PF_INET; +#endif + if (strcmp(protocol, "tcp") == 0) + hints.ai_socktype = SOCK_STREAM; + else + hints.ai_socktype = SOCK_DGRAM; + + if (lcl_port_str == NULL) + { + lcl_port = 0; + } else { + /* Resolve local port first */ + if ((errcode = + getaddrinfo(NULL, lcl_port_str, &hints, &res)) != 0) + { + fprintf(stderr, _("Cannot resolve local port %s: %s\n"), + lcl_port_str, gai_strerror(errcode)); + free(lcl_port_str); + return -1; + } + free(lcl_port_str); + if (res == NULL) + return -1; + switch (res->ai_family) + { + case AF_INET: + lcl_port = + ((struct sockaddr_in *)(res->ai_addr))->sin_port; + break; +#ifdef WITH_IPV6 + case AF_INET6: + lcl_port = + ((struct sockaddr_in6 *)(res->ai_addr))->sin6_port; + break; +#endif + default: + fprintf(stderr, _("Unknown local port AF %d\n"), + res->ai_family); + freeaddrinfo(res); + return -1; + } + freeaddrinfo(res); + } + res = NULL; + if (rmt_addr_str == NULL && rmt_port_str == NULL) + { + add_ip_conn(ip_list, protocol, this_name, ntohs(lcl_port), 0, + INADDR_ANY); +#ifdef WITH_IPV6 + add_ip6_conn(ip6_list, protocol, this_name, ntohs(lcl_port), 0, + in6addr_any); +#endif + return 0; + } else { + /* Resolve remote address and port */ + if (getaddrinfo(rmt_addr_str, rmt_port_str, &hints, &res) == 0) + { + for (resptr = res; resptr != NULL; + resptr = resptr->ai_next) + { + switch (resptr->ai_family) + { + case AF_INET: + sin = (struct sockaddr_in *) + resptr->ai_addr; + if (rmt_addr_str == NULL) + { + add_ip_conn(ip_list, protocol, this_name, + ntohs(lcl_port), ntohs(sin->sin_port), + INADDR_ANY); + } else { + add_ip_conn(ip_list, protocol, this_name, + ntohs(lcl_port), ntohs(sin->sin_port), + sin->sin_addr.s_addr); + } + break; +#ifdef WITH_IPV6 + case AF_INET6: + sin6 = (struct sockaddr_in6 *) resptr->ai_addr; + if (rmt_addr_str == NULL) + { + add_ip6_conn(ip6_list, protocol, this_name, + ntohs(lcl_port), ntohs(sin6->sin6_port), + in6addr_any); + } else { + add_ip6_conn(ip6_list, protocol, this_name, + ntohs(lcl_port), ntohs(sin6->sin6_port), + sin6->sin6_addr); + } + break; +#endif + } + } /*while */ + freeaddrinfo(res); + return 0; + } + } + return 1; +} + +void find_net_sockets( + struct inode_list **ino_list, + struct ip_connections *conn_list, + const char *protocol, + dev_t netdev) +{ + FILE *fp; + char pathname[200], line[BUFSIZ]; + unsigned long loc_port, rmt_port; + unsigned long rmt_addr; + unsigned long long scanned_inode; + ino_t inode; + struct ip_connections *conn_tmp; + + if (snprintf(pathname, 200, "/proc/net/%s", protocol) < 0) + return; + + if ((fp = fopen(pathname, "r")) == NULL) + { + fprintf(stderr, _("Cannot open protocol file \"%s\": %s\n"), + pathname, strerror(errno)); + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + if (sscanf + (line, + "%*u: %*x:%lx %08lx:%lx %*x %*x:%*x %*x:%*x %*x %*d %*d %llu", + &loc_port, &rmt_addr, &rmt_port, &scanned_inode) != 4) + continue; +#ifdef DEBUG + printf("Found IPv4 *:%lu with %s:%lu\n", loc_port, + inet_ntoa(*((struct in_addr *)&rmt_addr)), rmt_port); +#endif /* DEBUG */ + inode = scanned_inode; + for (conn_tmp = conn_list; conn_tmp != NULL; + conn_tmp = conn_tmp->next) + { +#ifdef DEBUG + printf(" Comparing with *.%lu %s:%lu\n", + conn_tmp->lcl_port, + inet_ntoa(conn_tmp->rmt_address), + conn_tmp->rmt_port); +#endif + if ((conn_tmp->lcl_port == 0 + || conn_tmp->lcl_port == loc_port) + && (conn_tmp->rmt_port == 0 + || conn_tmp->rmt_port == rmt_port) + && (conn_tmp->rmt_address.s_addr == INADDR_ANY + || + (memcmp + (&(conn_tmp->rmt_address), &(rmt_addr), + 4) == 0))) + { + /* add inode to list */ +#ifdef DEBUG + printf("Added inode!\n"); +#endif /* DEBUG */ + add_inode(ino_list, conn_tmp->name, netdev, inode); + } + } + + } + fclose(fp); +} + +#ifdef WITH_IPV6 +void find_net6_sockets( + struct inode_list **ino_list, + struct ip6_connections *conn_list, + const char *protocol, + const dev_t netdev) +{ + FILE *fp; + char pathname[200], line[BUFSIZ]; + unsigned long loc_port, rmt_port; + struct in6_addr rmt_addr; + unsigned int tmp_addr[4]; + char rmt_addr6str[INET6_ADDRSTRLEN]; + struct ip6_connections *conn_tmp; + unsigned long long scanned_inode; + ino_t inode; + + if (snprintf(pathname, 200, "/proc/net/%s6", protocol) < 0) + return; + + if ((fp = fopen(pathname, "r")) == NULL) + { +#ifdef DEBUG + printf("Cannot open protocol file \"%s\": %s\n", pathname, + strerror(errno)); +#endif /* DEBUG */ + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + if (sscanf + (line, + "%*u: %*x:%lx %08x%08x%08x%08x:%lx %*x %*x:%*x %*x:%*x %*x %*d %*d %llu", + &loc_port, &(tmp_addr[0]), &(tmp_addr[1]), &(tmp_addr[2]), + &(tmp_addr[3]), &rmt_port, &scanned_inode) != 7) + continue; + inode = scanned_inode; + rmt_addr.s6_addr32[0] = tmp_addr[0]; + rmt_addr.s6_addr32[1] = tmp_addr[1]; + rmt_addr.s6_addr32[2] = tmp_addr[2]; + rmt_addr.s6_addr32[3] = tmp_addr[3]; + inet_ntop(AF_INET6, &rmt_addr, rmt_addr6str, INET6_ADDRSTRLEN); +#ifdef DEBUG + printf("Found IPv6 %ld with %s:%ld\n", loc_port, rmt_addr6str, + rmt_port); +#endif /* DEBUG */ + for (conn_tmp = conn_list; conn_tmp != NULL; + conn_tmp = conn_tmp->next) + { + inet_ntop(AF_INET6, &conn_tmp->rmt_address, + rmt_addr6str, INET6_ADDRSTRLEN); +#ifdef DEBUG + printf(" Comparing with *.%lu %s:%lu ...\n", + conn_tmp->lcl_port, rmt_addr6str, + conn_tmp->rmt_port); +#endif /* DEBUG */ + if ((conn_tmp->lcl_port == 0 + || conn_tmp->lcl_port == loc_port) + && (conn_tmp->rmt_port == 0 + || conn_tmp->rmt_port == rmt_port) + && + (memcmp(&(conn_tmp->rmt_address), &in6addr_any, 16) + == 0 + || + (memcmp(&(conn_tmp->rmt_address), &(rmt_addr), 16) + == 0))) + { + add_inode(ino_list, conn_tmp->name, netdev, inode); + } + } + } + fclose(fp); +} +#endif + +static void read_proc_mounts( + struct mount_list **mnt_list) +{ + FILE *fp; + char line[BUFSIZ]; + char *find_mountp; + char *find_space; + struct mount_list *mnt_tmp; + + if ((fp = fopen(PROC_MOUNTS, "r")) == NULL) + { + fprintf(stderr, "Cannot open %s\n", PROC_MOUNTS); + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + if ((find_mountp = strchr(line, ' ')) == NULL) + continue; + find_mountp++; + if ((find_space = strchr(find_mountp, ' ')) == NULL) + continue; + *find_space = '\0'; + if ((mnt_tmp = malloc(sizeof(struct mount_list))) == NULL) + continue; + if ((mnt_tmp->mountpoint = strdup(find_mountp)) == NULL) + { + free(mnt_tmp); + continue; + } + mnt_tmp->next = *mnt_list; + *mnt_list = mnt_tmp; + } + fclose(fp); +} + +static void free_proc_mounts( + struct mount_list *mnt_list) +{ + struct mount_list *mnt_tmp, *mnt_next; + + mnt_tmp = mnt_list; + while (mnt_tmp != NULL) + { + mnt_next = mnt_tmp->next; + free(mnt_tmp->mountpoint); + free(mnt_tmp); + mnt_tmp = mnt_next; + } +} + +/* + * Free up structures allocated in add_matched_proc and add_special_proc + */ +static void free_matched_procs( + struct procs *matched_procs) +{ + struct procs *procs_tmp, *procs_next; + + procs_tmp = matched_procs; + while (procs_tmp != NULL) + { + procs_next = procs_tmp->next; + if (procs_tmp->command) + free(procs_tmp->command); + free(procs_tmp); + procs_tmp = procs_next; + } +} + +static void free_names() +{ + while(names_head != NULL) + { + struct names *this_name = names_head; + names_head = this_name->next; + free_matched_procs(this_name->matched_procs); + free(this_name->filename); + free(this_name); + } + names_tail = NULL; +} + +/* + * Free up structures allocated in add_ip_conn and add_ip6_conn + */ +static void free_connection( + struct ip_connections **conn) +{ + struct ip_connections *conn_tmp, *conn_next; + + conn_tmp = *conn; + while(conn_tmp != NULL) + { + conn_next = conn_tmp->next; + free(conn_tmp); + conn_tmp = conn_next; + } + *conn = NULL; +} + +#ifdef WITH_IPV6 +static void free_connection6( + struct ip6_connections **conn) +{ + struct ip6_connections *conn_tmp, *conn_next; + + conn_tmp = *conn; + while(conn_tmp != NULL) + { + conn_next = conn_tmp->next; + free(conn_tmp); + conn_tmp = conn_next; + } + *conn = NULL; +} +#endif + +/* + * Free up structures allocated in add_inode + */ +static void free_inodes( + struct inode_list **match_inodes) +{ + struct inode_list *inode_tmp, *inode_next; + + inode_tmp = *match_inodes; + while(inode_tmp != NULL) + { + inode_next = inode_tmp->next; + free(inode_tmp); + inode_tmp = inode_next; + } + *match_inodes = NULL; +} + +/* + * Free up structures allocated in add_device + */ +static void free_devices( + struct device_list **match_devices) +{ + struct device_list *device_tmp, *device_next; + + device_tmp = *match_devices; + while(device_tmp != NULL) + { + device_next = device_tmp->next; + free(device_tmp); + device_tmp = device_next; + } + *match_devices = NULL; +} + +static void atexit_free_lists() +{ + free_connection(&tcp_connection_list); + free_connection(&udp_connection_list); +#ifdef WITH_IPV6 + free_connection6(&tcp6_connection_list); + free_connection6(&udp6_connection_list); +#endif + + free_inodes(&match_inodes); + free_devices(&match_devices); + + free_names(); +} + + +static int is_mountpoint( + struct mount_list **mnt_list, + char *arg) +{ + char *p; + struct mount_list *mnt_tmp; + + if (*arg == '\0') + return 0; + /* Remove trailing slashes. */ + for (p = arg; *p != '\0'; p++) + /*do nothing*/; + while (*(--p) == '/' && p > arg) + *p = '\0'; + + for (mnt_tmp = *mnt_list; mnt_tmp != NULL; mnt_tmp = mnt_tmp->next) + if (!strcmp(mnt_tmp->mountpoint, arg)) + return 1; + return 0; +} + +static void check_mountpoints( + struct mount_list **mnt_list, + struct names **head, + struct names **tail) +{ + struct names *this, *last; + + last = NULL; + for(this = *head; this != NULL; this = this->next) + { + if (this->name_space == NAMESPACE_FILE && + !is_mountpoint(mnt_list, this->filename)) + { + fprintf(stderr, + _("Specified filename %s is not a mountpoint.\n"), + this->filename); + /* Remove from list */ + if (last) + last->next = this->next; + if (*head == this) + *head = this->next; + if (*tail == this) + *tail = last; + } else { + last = this; + } + } +} + +int main(int argc, char *argv[]) +{ + opt_type opts; + int sig_number; +#ifdef WITH_IPV6 + int ipv4_only, ipv6_only; +#endif + unsigned char default_namespace = NAMESPACE_FILE; + + dev_t netdev; + struct names *this_name; + int argc_cnt; + char *current_argv, *option; + char option_buf[3]; + struct option *optr; + char *nsptr; + int skip_argv; + + struct option options[] = { + {"all", 0, NULL, 'a'}, + {"kill", 0, NULL, 'k'}, + {"interactive", 0, NULL, 'i'}, + {"inode", 0, NULL, 'I'}, + {"list-signals", 0, NULL, 'l'}, + {"mount", 0, NULL, 'm'}, + {"ismountpoint", 0, NULL, 'M'}, + {"namespace", 1, NULL, 'n'}, + {"silent", 0, NULL, 's'}, + {"user", 0, NULL, 'u'}, + {"verbose", 0, NULL, 'v'}, + {"writeonly", 0, NULL, 'w'}, + {"version", 0, NULL, 'V'}, +#ifdef WITH_IPV6 + {"ipv4", 0, NULL, '4'}, + {"ipv6", 0, NULL, '6'}, +#endif + {0, 0, 0, 0} + }; + +#ifdef WITH_IPV6 + ipv4_only = ipv6_only = 0; +#endif + this_name = NULL; + opts = 0; + sig_number = SIGKILL; + +#ifdef ENABLE_NLS + /* Set up the i18n */ + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + netdev = find_net_dev(); +#ifndef __CYGWIN__ /* Cygwin doesn't support /proc/net/unix */ + fill_unix_cache(&unixsockets); + atexit(atexit_clear_unix_cache); +#endif + atexit(atexit_free_lists); + + for (argc_cnt = 1; argc_cnt < argc; argc_cnt++) + { + current_argv = argv[argc_cnt]; + if (current_argv[0] == '-') /* its an option */ + { + if (current_argv[1] == '-') /* its a long option */ + { + if (current_argv[2] == '\0') /* -- */ + break; + /* Parse the long options */ + option = option_buf; + for (optr = options; optr->name != NULL; optr++) + { + if (strcmp(current_argv + 2, optr->name) == 0) + { + sprintf(option_buf, "-%c", (char)optr->val); + break; + } + } + if (optr->name == NULL) + { + fprintf(stderr, _("%s: Invalid option %s\n"), + argv[0], current_argv); + usage(NULL); + } + } else { + option = current_argv; + } + skip_argv = 0; + while (*(++option) != '\0' && !skip_argv)/* skips over the - */ + { + switch (*option) + { +#ifdef WITH_IPV6 + case '4': + ipv4_only = 1; + break; + case '6': + ipv6_only = 1; + break; +#endif /* WITH_IPV6 */ + case 'a': + opts |= OPT_ALLFILES; + break; + case 'c': + opts |= OPT_MOUNTS; + break; + case 'f': + /* ignored */ + break; + case 'h': + usage(NULL); + break; + case 'i': + opts |= OPT_INTERACTIVE; + break; + case 'I': + opts |= OPT_ALWAYSSTAT; + break; + case 'k': + opts |= OPT_KILL; + break; + case 'l': + list_signals(); + return 0; + case 'm': + opts |= OPT_MOUNTS; + break; + case 'M': + opts |= OPT_ISMOUNTPOINT; + break; + case 'n': + argc_cnt++; + if (argc_cnt >= argc) + { + usage(_ + ("Namespace option requires an argument.")); + } + skip_argv = 1; + //while(option != '\0') option++; + if (strcmp(argv[argc_cnt], "tcp") == 0) + default_namespace = NAMESPACE_TCP; + else if (strcmp(argv[argc_cnt], "udp") == 0) + default_namespace = NAMESPACE_UDP; + else if (strcmp(argv[argc_cnt], "file") == 0) + default_namespace = NAMESPACE_FILE; + else + usage(_ + ("Invalid namespace name")); + break; + case 's': + opts |= OPT_SILENT; + break; + case 'u': + opts |= OPT_USER; + break; + case 'v': + opts |= OPT_VERBOSE; + break; + case 'w': + opts |= OPT_WRITE; + break; + case 'V': + print_version(); + return 0; + default: + if (isupper(*option) || isdigit(*option)) + { + sig_number = get_signal(current_argv + 1, argv[0]); + skip_argv = 1; + break; + } + fprintf(stderr, "%s: Invalid option %c\n", + argv[0], *option); + usage(NULL); + break; + } /* switch */ + } /* while option */ + continue; + } + +#if defined(HAVE_DECL_SYS_STATX) && HAVE_DECL_SYS_STATX == 1 + if ((opts & OPT_ALWAYSSTAT)) + stat_flags = 0; /* Triggers sync with e.g. remote NFS server even on autofs */ +#endif + /* an option */ + /* Not an option, must be a file specification */ + if ((this_name = malloc(sizeof(struct names))) == NULL) + continue; + this_name->next = NULL; + /* try to find namespace spec */ + this_name->name_space = default_namespace; + if (((nsptr = strchr(current_argv, '/')) != NULL) + && (nsptr != current_argv)) + { + if (strcmp(nsptr + 1, "tcp") == 0) + { + this_name->name_space = NAMESPACE_TCP; + *nsptr = '\0'; + } else if (strcmp(nsptr + 1, "udp") == 0) + { + this_name->name_space = NAMESPACE_UDP; + *nsptr = '\0'; + } else if (strcmp(nsptr + 1, "file") == 0) + { + this_name->name_space = NAMESPACE_FILE; + *nsptr = '\0'; + } + } + this_name->matched_procs = NULL; + if (opts & (OPT_MOUNTS | OPT_ISMOUNTPOINT) + && this_name->name_space != NAMESPACE_FILE) + { + free(this_name); + usage(_ + ("You can only use files with mountpoint options")); + } + switch (this_name->name_space) + { + case NAMESPACE_TCP: + if (asprintf + (&(this_name->filename), "%s/tcp", + current_argv) > 0) + { +#ifdef WITH_IPV6 + parse_inet(this_name, ipv4_only, ipv6_only, + &tcp_connection_list, &tcp6_connection_list); +#else + parse_inet(this_name, &tcp_connection_list); +#endif + } + break; + case NAMESPACE_UDP: + if (asprintf (&(this_name->filename), "%s/udp", current_argv) > 0) + { +#ifdef WITH_IPV6 + parse_inet(this_name, ipv4_only, ipv6_only, + &udp_connection_list, &udp6_connection_list); +#else + parse_inet(this_name, &udp_connection_list); +#endif + } + break; + default: /* FILE */ + this_name->filename = strdup(current_argv); + if (parse_file(this_name, &match_inodes, opts) == 0) + { + if (opts & OPT_MOUNTS) + parse_mounts(this_name, &match_devices, opts); + else + parse_unixsockets(this_name, &match_inodes, unixsockets); + } + break; + } + + if (names_head == NULL) + names_head = this_name; + if (names_tail != NULL) + names_tail->next = this_name; + names_tail = this_name; + } /* for across the argvs */ + if (names_head == NULL) + usage(_("No process specification given")); + + /* Check if -M flag was used and if so check mounts */ + if (opts & OPT_ISMOUNTPOINT) + { + struct mount_list *mounts = NULL; + read_proc_mounts(&mounts); + check_mountpoints(&mounts, &names_head, &names_tail); + free_proc_mounts(mounts); + } + + if (opts & OPT_SILENT) + { + opts &= ~OPT_VERBOSE; + opts &= ~OPT_USER; + if (opts & OPT_ALLFILES) + usage(_ + ("all option cannot be used with silent option.")); + } +#ifdef WITH_IPV6 + if (ipv4_only && ipv6_only) + usage(_ + ("You cannot search for only IPv4 and only IPv6 sockets at the same time")); + if (!ipv6_only) + { +#endif + if (tcp_connection_list != NULL) + find_net_sockets(&match_inodes, tcp_connection_list, "tcp", netdev); + if (udp_connection_list != NULL) + find_net_sockets(&match_inodes, udp_connection_list, "udp", netdev); +#ifdef WITH_IPV6 + } + if (!ipv4_only) + { + if (tcp6_connection_list != NULL) + find_net6_sockets(&match_inodes, tcp6_connection_list, + "tcp", netdev); + if (udp6_connection_list != NULL) + find_net6_sockets(&match_inodes, udp6_connection_list, + "udp", netdev); + } +#endif + free_connection(&tcp_connection_list); + free_connection(&udp_connection_list); +#ifdef WITH_IPV6 + free_connection6(&tcp6_connection_list); + free_connection6(&udp6_connection_list); +#endif +#ifdef DEBUG + debug_match_lists(names_head, match_inodes, match_devices); +#endif + scan_procs(names_head, match_inodes, match_devices, unixsockets, netdev); +#ifndef __CYGWIN__ /* Cygwin doesn't support /proc/net/unix */ + clear_unix_cache(&unixsockets); +#endif + scan_knfsd(names_head, match_inodes, match_devices); + scan_mounts(names_head, match_inodes, match_devices); + scan_swaps(names_head, match_inodes, match_devices); + free_inodes(&match_inodes); + free_devices(&match_devices); + return print_matches(names_head, opts, sig_number); +} + +/* + * returns 0 if match, 1 if no match + */ +static int print_matches( + struct names *names_head, + const opt_type opts, + const int sig_number) +{ + struct names *nptr; + struct procs *pptr; + char head = 0; + char first = 1; + int len = 0; + struct passwd *pwent = NULL; + int have_match = 0; + int have_kill = 0; + int name_has_procs = 0; + + for (nptr = names_head; nptr != NULL; nptr = nptr->next) + { + if (opts & OPT_SILENT) + { + for (pptr = nptr->matched_procs; pptr != NULL; + pptr = pptr->next) + { + if (pptr->proc_type != PTYPE_NORMAL) + continue; + + have_match = 1; + } + } else { /* We're not silent */ + if ((opts & OPT_ALLFILES) == 0) + { + name_has_procs = 0; + if (opts & OPT_VERBOSE) + { + if (nptr->matched_procs) + name_has_procs = 1; + } else { + for (pptr = nptr->matched_procs; + pptr != NULL; pptr = pptr->next) + { + if (pptr->proc_type == PTYPE_NORMAL) + { + name_has_procs = 1; + break; + } + } + } + } + if (name_has_procs == 1 || opts & OPT_ALLFILES) + { + if (head == 0 && opts & OPT_VERBOSE) + { + fprintf(stderr, + _("%*s USER PID ACCESS COMMAND\n"), NAME_FIELD, ""); + head = 1; + } + + fprintf(stderr, "%s:", nptr->filename); + len = strlen(nptr->filename) + 1; + } + + first = 1; + for (pptr = nptr->matched_procs; pptr != NULL; pptr = pptr->next) + { + /* Suppress any special "processes" */ + if (!(opts & OPT_VERBOSE) + && (pptr->proc_type != PTYPE_NORMAL)) + continue; + + have_match = 1; + if (opts & (OPT_VERBOSE | OPT_USER)) + { + if (pwent == NULL || pwent->pw_uid != pptr->uid) + pwent = getpwuid(pptr->uid); + } + if (len > NAME_FIELD && (opts & OPT_VERBOSE)) + { + putc('\n', stderr); + len = 0; + } + if ((opts & OPT_VERBOSE) || first) + while (len++ < NAME_FIELD) + putc(' ', stderr); + if (opts & OPT_VERBOSE) + { + if (pwent == NULL) + fprintf(stderr, " %-8s ", + _("(unknown)")); + else + fprintf(stderr, " %-8s ", + pwent->pw_name); + } + if (pptr->proc_type == PTYPE_NORMAL) + printf(" %5d", pptr->pid); + else + printf("kernel"); + fflush(stdout); + if (opts & OPT_VERBOSE) + { + switch (pptr->proc_type) + { + case PTYPE_KNFSD: + fprintf(stderr, " knfsd "); + break; + case PTYPE_MOUNT: + fprintf(stderr, " mount "); + break; + case PTYPE_SWAP: + fprintf(stderr, " swap "); + break; + default: + fprintf(stderr, " %c%c%c%c%c ", + pptr->access & ACCESS_FILE ? + (pptr->access & ACCESS_FILEWR ? 'F' : 'f') : '.', + pptr->access & ACCESS_ROOT ? 'r' : '.', + pptr->access & ACCESS_CWD ? 'c' : '.', + pptr->access & ACCESS_EXE ? 'e' : '.', + (pptr->access & ACCESS_MMAP) + && !(pptr-> access & ACCESS_EXE) ? 'm' : '.'); + } /* switch */ + } else { + if (pptr->access & ACCESS_ROOT) + putc('r', stderr); + if (pptr->access & ACCESS_CWD) + putc('c', stderr); + if (pptr->access & ACCESS_EXE) + putc('e', stderr); + else if (pptr->access & ACCESS_MMAP) + putc('m', stderr); + } + if (opts & OPT_USER) + { + if (pwent == NULL) + fprintf(stderr, " %-8s ", _("(unknown)")); + else + fprintf(stderr, "(%s)", pwent->pw_name); + } + if (opts & OPT_VERBOSE) + { + if (pptr->command == NULL) + fprintf(stderr, "???\n"); + else + fprintf(stderr, "%s\n", pptr->command); + } + len = 0; + first = 0; + } + if (opts & OPT_VERBOSE) + { + /* put a newline if showing all files and no procs */ + if (nptr->matched_procs == NULL && (opts & OPT_ALLFILES)) + putc('\n', stderr); + } else { + if (name_has_procs || (opts & OPT_ALLFILES)) + putc('\n', stderr); + } + } /* be silent */ + if (opts & OPT_KILL) + have_kill |= kill_matched_proc(nptr->matched_procs, + opts, sig_number); + + } /* next name */ + if (opts & OPT_KILL) + return (have_kill == 1 ? 0 : 1); + else + return (have_match == 1 ? 0 : 1); + +} + +static struct stat *get_pidstat( + const pid_t pid, + const char *filename) +{ + char pathname[PATH_MAX]; + struct stat *st; + + if ((st = (struct stat *)malloc(sizeof(struct stat))) == NULL) + return NULL; + snprintf(pathname, PATH_MAX-1, "/proc/%d/%s", pid, filename); + if (statn(pathname, STATX_UID|STATX_INO|STATX_TYPE, st) != 0) + { + free(st); + return NULL; + } + return st; +} + +static void check_dir( + const pid_t pid, + const char *dirname, + struct device_list *dev_head, + struct inode_list *ino_head, + const uid_t uid, + const char access, + struct unixsocket_list *sockets, + dev_t netdev) +{ + DIR *dirp; + dev_t thedev; + struct dirent *direntry; + struct inode_list *ino_tmp; + struct device_list *dev_tmp; + struct unixsocket_list *sock_tmp; + struct stat st, lst; + char *dirpath; + char filepath[PATH_MAX]; + char real_filepath[PATH_MAX]; + + if (asprintf(&dirpath, "/proc/%d/%s", pid, dirname) < 0) + return; + if ((dirp = opendir(dirpath)) == NULL) + { + free(dirpath); + return; + } + free(dirpath); + + while ((direntry = readdir(dirp)) != NULL) + { + if (direntry->d_name[0] < '0' || direntry->d_name[0] > '9') + continue; + + snprintf(filepath, sizeof filepath - 1, "/proc/%d/%s/%s", + pid, dirname, direntry->d_name); + + if (statn(filepath, STATX_INO, &st) != 0) + { + if (errno != ENOENT && errno != ENOTDIR && errno != EACCES) + { + fprintf(stderr, _("Cannot stat file %s: %s\n"), + filepath, strerror(errno)); + } + } else { + thedev = st.st_dev; + if (thedev == netdev) + { + for (sock_tmp = sockets; sock_tmp != NULL; + sock_tmp = sock_tmp->next) + { + if (sock_tmp->net_inode == st.st_ino) + { + st.st_ino = sock_tmp->inode; + st.st_dev = sock_tmp->dev; + thedev = sock_tmp->dev; + break; + } + } + } + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) + { + if (thedev != dev_tmp->device) + continue; + + /* check the paths match if it is not a block device or socket */ + if (! S_ISBLK(dev_tmp->name->st.st_mode) + && !S_ISSOCK(st.st_mode)) + { + if (readlink(filepath, real_filepath, PATH_MAX-1) < 0) + { + if (strncmp(dev_tmp->name->filename, filepath, + strlen(dev_tmp->name->filename)) != 0) + continue; + } else { + if (strncmp(dev_tmp->name->filename, real_filepath, + strlen(dev_tmp->name->filename)) != 0) + continue; + } + } + if (access == ACCESS_FILE + && (lstat(filepath, &lst) == 0) + && (lst.st_mode & S_IWUSR)) + { + add_matched_proc(dev_tmp->name, pid, uid, + ACCESS_FILEWR | access); + } else { + add_matched_proc(dev_tmp->name, pid, uid, access); + } + } + for (ino_tmp = ino_head; ino_tmp != NULL; ino_tmp = ino_tmp->next) + { + if (thedev != ino_tmp->device) + continue; + if (!st.st_ino && statn(filepath, STATX_INO, &st) != 0) + { + fprintf(stderr, _("Cannot stat file %s: %s\n"), + filepath, strerror(errno)); + continue; + } + if (st.st_ino == ino_tmp->inode) + { + if (access == ACCESS_FILE + && (lstat(filepath, &lst) == 0) + && (lst.st_mode & S_IWUSR)) + { + add_matched_proc(ino_tmp->name, pid, uid, + ACCESS_FILEWR | access); + } else { + add_matched_proc(ino_tmp->name, pid, uid, access); + } + } + } + } + } /* while fd_dent */ + closedir(dirp); +} + +static void check_map( + const pid_t pid, + const char *filename, + struct device_list *dev_head, + struct inode_list *ino_head, + const uid_t uid, + const char access) +{ + char *pathname; + char line[BUFSIZ]; + struct inode_list *ino_tmp; + struct device_list *dev_tmp; + FILE *fp; + unsigned long long tmp_inode; + unsigned int tmp_maj, tmp_min; + dev_t tmp_device; + + if (asprintf(&pathname, "/proc/%d/%s", pid, filename) < 0) + return; + if ((fp = fopen(pathname, "r")) == NULL) + { + free(pathname); + return; + } + free(pathname); + while (fgets(line, BUFSIZ, fp)) + { + if (sscanf(line, "%*s %*s %*s %x:%x %lld", + &tmp_maj, &tmp_min, &tmp_inode) == 3) + { + tmp_device = tmp_maj * 256 + tmp_min; + for (dev_tmp = dev_head; dev_tmp != NULL; dev_tmp = dev_tmp->next) + if (dev_tmp->device == tmp_device) + add_matched_proc(dev_tmp->name, pid, uid, access); + for (ino_tmp = ino_head; ino_tmp != NULL; ino_tmp = ino_tmp->next) + if (ino_tmp->device == tmp_device + && ino_tmp->inode == tmp_inode) + add_matched_proc(ino_tmp->name, pid, uid, access); + } + } + fclose(fp); +} + +static uid_t getpiduid( + const pid_t pid) +{ + char *pathname; + struct stat st; + + if (asprintf(&pathname, "/proc/%d", pid) < 0) + return 0; + if (statn(pathname, STATX_UID, &st) != 0) + { + free(pathname); + return 0; + } + free(pathname); + return st.st_uid; +} + +/* + * fill_unix_cache : Create a list of Unix sockets + * This list is used later for matching purposes + */ +void fill_unix_cache( + struct unixsocket_list **unixsocket_head) +{ + FILE *fp; + char line[BUFSIZ]; + unsigned long long scanned_inode; + struct stat st; + struct unixsocket_list *newsocket; + + if ((fp = fopen("/proc/net/unix", "r")) == NULL) + { + fprintf(stderr, _("Cannot open /proc/net/unix: %s\n"), + strerror(errno)); + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + char *path; + char *scanned_path = NULL; + if (sscanf(line, "%*x: %*x %*x %*x %*x %*d %llu %ms", + &scanned_inode, &scanned_path) != 2) + { + if (scanned_path) + free(scanned_path); + continue; + } + if (scanned_path == NULL) + continue; + path = scanned_path; + if (*scanned_path == '@') + scanned_path++; + if (statn(scanned_path, STATX_INO, &st) < 0) + { + free(path); + continue; + } + if ((newsocket = (struct unixsocket_list *) + malloc(sizeof(struct unixsocket_list))) == NULL) + { + free(path); + continue; + } + newsocket->sun_name = strdup(scanned_path); + newsocket->inode = st.st_ino; + newsocket->dev = st.st_dev; + newsocket->net_inode = scanned_inode; + newsocket->next = *unixsocket_head; + *unixsocket_head = newsocket; + free(path); + } /* while */ + + fclose(fp); +} + +/* + * Free up the list of Unix sockets + */ +void clear_unix_cache( + struct unixsocket_list **unixsocket_head) +{ + while(*unixsocket_head != NULL) + { + struct unixsocket_list *oldsocket = *unixsocket_head; + *unixsocket_head = oldsocket->next; + free(oldsocket->sun_name); + free(oldsocket); + } +} + +static void atexit_clear_unix_cache() +{ + clear_unix_cache(&unixsockets); +} + +#ifdef DEBUG +/* often not used, doesn't need translation */ +static void debug_match_lists( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head) +{ + struct names *nptr; + struct inode_list *iptr; + struct device_list *dptr; + + fprintf(stderr, "Specified Names:\n"); + for (nptr = names_head; nptr != NULL; nptr = nptr->next) + { + fprintf(stderr, "\t%s %c\n", nptr->filename, nptr->name_space); + } + fprintf(stderr, "\nInodes:\n"); + for (iptr = ino_head; iptr != NULL; iptr = iptr->next) + { + fprintf(stderr, " Dev:%0lx Inode:(%0ld) 0x%0lx => %s\n", + (unsigned long)iptr->device, (unsigned long)iptr->inode, + (unsigned long)iptr->inode, iptr->name->filename); + } + fprintf(stderr, "\nDevices:\n"); + for (dptr = dev_head; dptr != NULL; dptr = dptr->next) + { + fprintf(stderr, "\tDev:%0lx\n", (unsigned long)dptr->device); + } +} + +#endif + +/* 0 = no, 1=yes */ +static int ask( + const pid_t pid) +{ + int res; + size_t len = 0; + char *line = NULL; + + fflush(stdout); + while (1) + { + fprintf(stderr, _("Kill process %d ? (y/N) "), pid); + fflush(stderr); + if (getline(&line, &len, stdin) < 0) + return 0; + if (line[0] == '\n') + { + free(line); + return 0; + } + res = rpmatch(line); + if (res >= 0) + { + free(line); + return res; + } + } /* while */ +} + +static int kill_matched_proc( + struct procs *proc_head, + const opt_type opts, + const int sig_number) +{ + struct procs *pptr; + pid_t mypid; + int ret = 0; + + mypid = getpid(); + + for (pptr = proc_head; pptr != NULL; pptr = pptr->next) + { + if (pptr->pid == mypid) + continue; /* dont kill myself */ + if (pptr->proc_type != PTYPE_NORMAL) + continue; + if ((opts & OPT_WRITE) && ((pptr->access & ACCESS_FILEWR) == 0)) + continue; + if ((opts & OPT_INTERACTIVE) && (ask(pptr->pid) == 0)) + continue; + if (kill(pptr->pid, sig_number) < 0) + { + fprintf(stderr, _("Could not kill process %d: %s\n"), + pptr->pid, strerror(errno)); + continue; + } + ret = 1; + } + return ret; +} + +static dev_t find_net_dev(void) +{ + int skt; + struct stat st; + + if ((skt = socket(PF_INET, SOCK_DGRAM, 0)) < 0) + { + fprintf(stderr, _("Cannot open a network socket.\n")); + return -1; + } + if (fstatn(skt, STATX_INO, &st) != 0) + { + fprintf(stderr, _("Cannot find socket's device number.\n")); + close(skt); + return -1; + } + close(skt); + return st.st_dev; +} + +static void scan_knfsd( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head) +{ + struct device_list *dev_tmp; + struct inode_list *ino_tmp; + FILE *fp; + char line[BUFSIZ]; + char *find_space; + struct stat st; + + if ( (ino_head == NULL) && (dev_head == NULL) ) + return; + + + if ((fp = fopen(KNFSD_EXPORTS, "r")) == NULL) + { +#ifdef DEBUG + printf("Cannot open %s\n", KNFSD_EXPORTS); +#endif + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + if (line[0] == '#') + continue; + + if ((find_space = strpbrk(line, " \t")) == NULL) + continue; + + *find_space = '\0'; + if (statn(line, STATX_INO, &st) != 0) + continue; + + /* Scan the devices */ + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) + { + if (st.st_dev == dev_tmp->device) + add_special_proc(dev_tmp->name, PTYPE_KNFSD, 0, line); + } + + for (ino_tmp = ino_head; ino_tmp != NULL; + ino_tmp = ino_tmp->next) + { + if (st.st_dev == ino_tmp->device + && st.st_ino == ino_tmp->inode) + add_special_proc(ino_tmp->name, PTYPE_KNFSD, 0, line); + } + } + fclose(fp); +} + +static void scan_mounts( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head) +{ + struct device_list *dev_tmp; + struct inode_list *ino_tmp; + FILE *fp; + char line[BUFSIZ]; + char *find_mountp; + char *find_space; + struct stat st; + + if ( (ino_head == NULL) && (dev_head == NULL) ) + return; + + + if ((fp = fopen(PROC_MOUNTS, "r")) == NULL) + { + fprintf(stderr, "Cannot open %s\n", PROC_MOUNTS); + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + if ((find_mountp = strchr(line, ' ')) == NULL) + continue; + find_mountp++; + if ((find_space = strchr(find_mountp, ' ')) == NULL) + continue; + *find_space = '\0'; + if (statn(find_mountp, STATX_INO, &st) != 0) + continue; + /* Scan the devices */ + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) + { + if (st.st_dev == dev_tmp->device) + add_special_proc(dev_tmp->name, PTYPE_MOUNT, 0, find_mountp); + } + for (ino_tmp = ino_head; ino_tmp != NULL; + ino_tmp = ino_tmp->next) + { + if (st.st_dev == ino_tmp->device + && st.st_ino == ino_tmp->inode) + add_special_proc(ino_tmp->name, PTYPE_MOUNT, 0, find_mountp); + } + } + fclose(fp); +} + +static void scan_swaps( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head) +{ + struct device_list *dev_tmp; + struct inode_list *ino_tmp; + FILE *fp; + char line[BUFSIZ]; + char *find_space; + struct stat st; + + if ( (ino_head == NULL) && (dev_head == NULL) ) + return; + + if ((fp = fopen(PROC_SWAPS, "r")) == NULL) + { + /*fprintf(stderr, "Cannot open %s\n", PROC_SWAPS); */ + return; + } + /* lines are filename type */ + while (fgets(line, BUFSIZ, fp) != NULL) + { + if ((find_space = strchr(line, ' ')) == NULL) + continue; + *find_space = '\0'; + find_space++; + while (*find_space == ' ') + { + find_space++; + if (*find_space == '\0') + continue; + } + if (statn(line, STATX_INO, &st) != 0) + continue; + + /* Scan the devices */ + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) + { + if (st.st_dev == dev_tmp->device) + add_special_proc(dev_tmp->name, PTYPE_SWAP, 0, line); + } + for (ino_tmp = ino_head; ino_tmp != NULL; + ino_tmp = ino_tmp->next) + { + if (st.st_dev == ino_tmp->device + && st.st_ino == ino_tmp->inode) + add_special_proc(ino_tmp->name, PTYPE_SWAP, 0, line); + } + } + fclose(fp); +} + +/* + * Somehow the realpath(3) glibc function call, nevertheless + * it avoids lstat(2) system calls. + */ +static char real[PATH_MAX + 1]; +char *expandpath( + const char *path) +{ + char tmpbuf[PATH_MAX + 1]; + const char *start, *end; + char *curr, *dest; + int deep = MAXSYMLINKS; + + if (!path || *path == '\0') + return (char *)0; + + curr = &real[0]; + + if (*path != '/') + { + if (!getcwd(curr, PATH_MAX)) + return (char *)0; + dest = strchr(curr, '\0'); + } else { + *curr = '/'; + dest = curr + 1; + } + + for (start = end = path; *start; start = end) + { + while (*start == '/') + ++start; + + for (end = start; *end && *end != '/'; ++end) ; + + if (end - start == 0) + break; + else if (end - start == 1 && start[0] == '.') + { + ; + } else if (end - start == 2 && start[0] == '.' && start[1] == '.') + { + if (dest > curr + 1) + while ((--dest)[-1] != '/') ; + } else { + char lnkbuf[PATH_MAX + 1]; + size_t len; + ssize_t n; + + if (dest[-1] != '/') + *dest++ = '/'; + + if (dest + (end - start) > curr + PATH_MAX) + { + errno = ENAMETOOLONG; + return (char *)0; + } + + dest = mempcpy(dest, start, end - start); + *dest = '\0'; + + if (deep-- < 0) + { + errno = ELOOP; + return (char *)0; + } + + errno = 0; + if ((n = readlink(curr, lnkbuf, PATH_MAX)) < 0) + { + deep = MAXSYMLINKS; + if (errno == EINVAL) + continue; /* Not a symlink */ + return (char *)0; + } + lnkbuf[n] = '\0'; /* Don't be fooled by readlink(2) */ + + len = strlen(end); + if ((n + len) > PATH_MAX) + { + errno = ENAMETOOLONG; + return (char *)0; + } + + memmove(&tmpbuf[n], end, len + 1); + path = end = memcpy(tmpbuf, lnkbuf, n); + + if (lnkbuf[0] == '/') + dest = curr + 1; + else if (dest > curr + 1) + while ((--dest)[-1] != '/') ; + + } + } + + if (dest > curr + 1 && dest[-1] == '/') + --dest; + *dest = '\0'; + + return curr; +} |