| /*****************************************************************************\ |
| * callerid.c - Identify initiator of ssh connections, etc |
| ***************************************************************************** |
| * Copyright (C) 2015, Brigham Young University |
| * Author: Ryan Cox <ryan_cox@byu.edu> |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm 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. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include "config.h" |
| |
| #define _GNU_SOURCE |
| |
| #ifdef __FreeBSD__ |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #endif |
| |
| /* |
| * FIXME: In in6.h, s6_addr32 def is guarded by #ifdef _KERNEL |
| * Is there a portable interface that could be used instead of accessing |
| * structure members directly? |
| */ |
| #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) |
| #define s6_addr32 __u6_addr.__u6_addr32 |
| #endif |
| |
| #include <arpa/inet.h> |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <inttypes.h> |
| #include <libgen.h> |
| #include <limits.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include "slurm/slurm.h" |
| #include "src/common/callerid.h" |
| #include "src/common/log.h" |
| #include "src/common/xstring.h" |
| #include "src/common/xmalloc.h" |
| |
| #ifndef _BSD_SOURCE |
| #define _BSD_SOURCE |
| #endif |
| |
| #ifndef PATH_PROCNET_TCP |
| #define PATH_PROCNET_TCP "/proc/net/tcp" |
| #endif |
| #ifndef PATH_PROCNET_TCP6 |
| #define PATH_PROCNET_TCP6 "/proc/net/tcp6" |
| #endif |
| |
| strong_alias(callerid_get_own_netinfo, slurm_callerid_get_own_netinfo); |
| |
| static int _match_inode(callerid_conn_t *conn_result, ino_t *inode_search, |
| callerid_conn_t *conn_row, ino_t inode_row, int af) |
| { |
| if (*inode_search == inode_row) { |
| memcpy(&conn_result->ip_dst, &conn_row->ip_dst, 16); |
| memcpy(&conn_result->ip_src, &conn_row->ip_src, 16); |
| conn_result->port_src = conn_row->port_src; |
| conn_result->port_dst = conn_row->port_dst; |
| conn_result->af = af; |
| debug3("_match_inode matched"); |
| return SLURM_SUCCESS; |
| } |
| return SLURM_ERROR; |
| } |
| |
| static int _match_conn(callerid_conn_t *conn_search, ino_t *inode_result, |
| callerid_conn_t *conn_row, ino_t inode_row, int af) |
| { |
| int addrbytes = af == AF_INET ? 4 : 16; |
| |
| if (conn_search->port_dst != conn_row->port_dst || |
| conn_search->port_src != conn_row->port_src || |
| memcmp((void*)&conn_search->ip_dst, (void*)&conn_row->ip_dst, |
| addrbytes) !=0 || |
| memcmp((void*)&conn_search->ip_src, (void*)&conn_row->ip_src, |
| addrbytes) !=0 |
| ) |
| return SLURM_ERROR; |
| |
| debug3("_match_conn matched inode %lu", (long unsigned int)inode_row); |
| *inode_result = inode_row; |
| return SLURM_SUCCESS; |
| } |
| |
| /* Note that /proc/net/tcp, etc. can be updated *while reading* but a read on |
| * each row is atomic: http://stackoverflow.com/a/5880485. |
| * |
| * This should be safe but may potentially miss an entry due to the entry |
| * moving up in the file as it's read. |
| */ |
| static int _find_match_in_tcp_file( |
| callerid_conn_t *conn, |
| ino_t *inode, |
| int af, |
| const char *path, |
| int (*match_func)(callerid_conn_t *, |
| ino_t *, callerid_conn_t *, ino_t, int)) |
| { |
| int rc = SLURM_ERROR; |
| FILE *fp; |
| char ip_dst_str[INET6_ADDRSTRLEN+1]; /* +1 for scanf to add \0 */ |
| char ip_src_str[INET6_ADDRSTRLEN+1]; |
| char line[1024]; |
| int addrbytes, i, matches; |
| uint64_t inode_row; |
| callerid_conn_t conn_row; |
| |
| addrbytes = af == AF_INET ? 4 : 16; |
| |
| /* Zero out the IPs. Not strictly necessary but it will look much better |
| * in a debugger since IPv4 only uses 4 out of 16 bytes. */ |
| memset(&conn_row.ip_dst, 0, 16); |
| memset(&conn_row.ip_src, 0, 16); |
| |
| fp = fopen(path, "r"); |
| if (!fp) |
| return rc; |
| |
| while( fgets(line, 1024, fp) != NULL ) { |
| matches = sscanf(line, |
| "%*s %[0-9A-Z]:%x %[0-9A-Z]:%x %*s %*s %*s %*s %*s %*s %"PRIu64"", |
| ip_dst_str, &conn_row.port_dst, ip_src_str, |
| &conn_row.port_src, &inode_row); |
| |
| if (matches == EOF) |
| break; |
| |
| /* Probably the header */ |
| if (!matches) |
| continue; |
| |
| /* Convert to usable forms */ |
| inet_nsap_addr(ip_dst_str, (unsigned char*)&conn_row.ip_dst, |
| addrbytes); |
| inet_nsap_addr(ip_src_str, (unsigned char*)&conn_row.ip_src, |
| addrbytes); |
| |
| /* Convert to network byte order. */ |
| for (i=0; i < (addrbytes>>2); i++) { |
| conn_row.ip_dst.s6_addr32[i] |
| = htonl(conn_row.ip_dst.s6_addr32[i]); |
| conn_row.ip_src.s6_addr32[i] |
| = htonl(conn_row.ip_src.s6_addr32[i]); |
| } |
| |
| /* Check if we matched */ |
| rc = match_func(conn, inode, &conn_row, (ino_t)inode_row, af); |
| if (rc == SLURM_SUCCESS) { |
| char ip_src_str[INET6_ADDRSTRLEN]; |
| char ip_dst_str[INET6_ADDRSTRLEN]; |
| |
| inet_ntop(af, &conn->ip_src, ip_src_str, |
| INET6_ADDRSTRLEN); |
| inet_ntop(af, &conn->ip_dst, ip_dst_str, |
| INET6_ADDRSTRLEN); |
| debug("network_callerid matched %s:%lu => %s:%lu with inode %lu", |
| ip_src_str, (long unsigned int)conn->port_src, |
| ip_dst_str, (long unsigned int)conn->port_dst, |
| (long unsigned int)inode); |
| break; |
| } |
| } |
| |
| fclose(fp); |
| return rc; |
| } |
| |
| |
| /* Search through /proc/$pid/fd/ symlinks for the specified inode |
| * |
| * All errors in this function should be silently ignored. Processes appear and |
| * disappear all the time. It is natural for processes to disappear in between |
| * operations such as readdir, stat, and others. We should detect errors but |
| * not log them. |
| */ |
| static int _find_inode_in_fddir(pid_t pid, ino_t inode) |
| { |
| DIR *dirp; |
| struct dirent *entryp; |
| char dirpath[1024]; |
| char fdpath[PATH_MAX]; |
| int rc = SLURM_ERROR; |
| struct stat statbuf; |
| |
| if (snprintf(dirpath, 1024, "/proc/%d/fd", (pid_t)pid) >= 1024) |
| return SLURM_ERROR; |
| if ((dirp = opendir(dirpath)) == NULL) { |
| return SLURM_ERROR; |
| } |
| |
| while (1) { |
| if (!(entryp = readdir(dirp))) |
| break; |
| /* Ignore . and .. */ |
| else if (!xstrncmp(entryp->d_name, ".", 1)) |
| continue; |
| |
| /* This is a symlink. Follow it to get destination's inode. */ |
| if (snprintf(fdpath, sizeof(fdpath), "%s/%s", dirpath, |
| entryp->d_name) >= sizeof(fdpath)) |
| continue; |
| |
| if (stat(fdpath, &statbuf) != 0) |
| continue; |
| if (statbuf.st_ino == inode) { |
| debug3("_find_inode_in_fddir: found %lu at %s", |
| (long unsigned int)inode, fdpath); |
| rc = SLURM_SUCCESS; |
| break; |
| } |
| } |
| |
| closedir(dirp); |
| return rc; |
| } |
| |
| |
| extern int callerid_find_inode_by_conn(callerid_conn_t conn, ino_t *inode) |
| { |
| int rc; |
| |
| rc = _find_match_in_tcp_file(&conn, inode, AF_INET, PATH_PROCNET_TCP, |
| _match_conn); |
| if (rc == SLURM_SUCCESS) |
| return SLURM_SUCCESS; |
| |
| rc = _find_match_in_tcp_file(&conn, inode, AF_INET6, PATH_PROCNET_TCP6, |
| _match_conn); |
| if (rc == SLURM_SUCCESS) |
| return SLURM_SUCCESS; |
| |
| /* Add new protocols here if needed, such as UDP */ |
| |
| return SLURM_ERROR; |
| } |
| |
| |
| extern int callerid_find_conn_by_inode(callerid_conn_t *conn, ino_t inode) |
| { |
| int rc; |
| |
| rc = _find_match_in_tcp_file(conn, &inode, AF_INET, PATH_PROCNET_TCP, |
| _match_inode); |
| if (rc == SLURM_SUCCESS) |
| return SLURM_SUCCESS; |
| |
| rc = _find_match_in_tcp_file(conn, &inode, AF_INET6, PATH_PROCNET_TCP6, |
| _match_inode); |
| if (rc == SLURM_SUCCESS) |
| return SLURM_SUCCESS; |
| |
| /* Add new protocols here if needed, such as UDP */ |
| |
| return SLURM_ERROR; |
| } |
| |
| |
| /* Read through /proc then read each proc's fd/ directory. |
| * |
| * Most errors in this function should be silently ignored. Processes appear and |
| * disappear all the time. It is natural for processes to disappear in between |
| * operations such as readdir, stat, and others. We should detect errors but |
| * not log them. |
| */ |
| extern int find_pid_by_inode (pid_t *pid_result, ino_t inode) |
| { |
| DIR *dirp; |
| struct dirent *entryp; |
| char *dirpath = "/proc"; |
| int rc = SLURM_ERROR; |
| pid_t pid; |
| |
| if ((dirp = opendir(dirpath)) == NULL) { |
| /* Houston, we have a problem: /proc is inaccessible */ |
| error("find_pid_by_inode: unable to open %s: %m", |
| dirpath); |
| return SLURM_ERROR; |
| } |
| |
| while (1) { |
| if (!(entryp = readdir(dirp))) |
| break; |
| /* We're only looking for /proc/[0-9]* */ |
| else if (!isdigit(entryp->d_name[0])) |
| continue; |
| |
| /* More sanity checks can be performed but there isn't much |
| * point. The fd/ directory will exist inside the directory and |
| * we'll find the specified inode or we won't. Failures are |
| * silent so it won't clutter logs. The above checks are |
| * currently sufficient for Linux. */ |
| |
| pid = (int)atoi(entryp->d_name); |
| rc = _find_inode_in_fddir(pid, inode); |
| if (rc == SLURM_SUCCESS) { |
| *pid_result = pid; |
| break; |
| } |
| } |
| |
| closedir(dirp); |
| return rc; |
| } |
| |
| |
| extern int callerid_get_own_netinfo (callerid_conn_t *conn) |
| { |
| DIR *dirp; |
| struct dirent *entryp; |
| char *dirpath = "/proc/self/fd"; |
| char fdpath[PATH_MAX]; |
| int rc = SLURM_ERROR; |
| struct stat statbuf; |
| |
| if ((dirp = opendir(dirpath)) == NULL) { |
| error("callerid_get_own_netinfo: opendir failed for %s: %m", |
| dirpath); |
| return rc; |
| } |
| |
| while (1) { |
| if (!(entryp = readdir(dirp))) |
| break; |
| /* Ignore . and .. */ |
| else if (!xstrncmp(entryp->d_name, ".", 1)) |
| continue; |
| |
| if (snprintf(fdpath, PATH_MAX, "%s/%s", dirpath, |
| entryp->d_name) >= PATH_MAX) |
| continue; |
| debug3("callerid_get_own_netinfo: checking %s", fdpath); |
| /* This is a symlink. Follow it to get destination's inode. */ |
| if (stat(fdpath, &statbuf) != 0) { |
| debug3("stat failed for %s: %m", fdpath); |
| continue; |
| } |
| |
| /* We are only interested in sockets */ |
| if (S_ISSOCK(statbuf.st_mode)) { |
| debug3("callerid_get_own_netinfo: checking socket %s", |
| fdpath); |
| rc = callerid_find_conn_by_inode(conn, statbuf.st_ino); |
| if (rc == SLURM_SUCCESS) { |
| break; |
| } |
| } |
| } |
| |
| closedir(dirp); |
| return rc; |
| } |