|  | /*****************************************************************************\ | 
|  | *  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; | 
|  | } |