#include <dlfcn.h>
#include <errno.h>
#include <netinet/in.h>
-#include <netinet/ip.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#define LOG_LEVEL_DEBUG 3
#define LOG_LEVEL_MASK LOG_LEVEL_DEBUG
-#define LOG_LEVEL_PERROR 42
+#define LOG_LEVEL_PERROR (1 << 10)
+
+#define OPTION_PARSED (1 << 1)
+/* Don't intercept listen(), accept(). */
+#define OPTION_CLIENT_ONLY (1 << 2)
+/* Don't intercept connect(). */
+#define OPTION_SERVER_ONLY (1 << 3)
/* GLOBAL VARIABLES */
struct list {
- int unix_sockfd;
-
+ int orig_sockfd;
int orig_domain;
int orig_type;
+ /* Used by listen(). */
+ struct sockaddr *orig_addr;
+ socklen_t orig_addrlen;
+
struct list *next;
};
static struct list socket_list = {
- .unix_sockfd = -1, /* must not match a valid sockfd */
+ .orig_sockfd = -1, /* must not match a valid sockfd */
};
+static int global_options;
+
/* LOG FUNCTIONS/MACROS */
errno = saved_errno;
perror("");
+ /* perror writes a newline. */
+ } else {
+ fprintf(stderr, "\n");
}
funlockfile(stderr);
/* OTHER FUNCTIONS */
+static void *xmalloc(size_t size) {
+ void *x = malloc(size);
+ if (!x) {
+ DIE("malloc(%zu)", size);
+ }
+ return x;
+}
+
static struct list *find_sockfd(int sockfd) {
struct list *e;
- if (sockfd == socket_list.unix_sockfd) {
+ if (sockfd == socket_list.orig_sockfd) {
return NULL;
}
for (e = &socket_list; e != NULL; e = e->next) {
- if (e->unix_sockfd == sockfd) {
+ if (e->orig_sockfd == sockfd) {
return e;
}
}
static struct list *remove_sockfd(int sockfd) {
struct list *e, *p;
- if (sockfd == socket_list.unix_sockfd) {
+ if (sockfd == socket_list.orig_sockfd) {
return NULL;
}
for (e = &socket_list, p = NULL; e != NULL; p = e, e = e->next) {
- if (e->unix_sockfd == sockfd) {
+ if (e->orig_sockfd == sockfd) {
p->next = e->next;
return e;
}
static const char *get_socket_path(void) {
const char *path = getenv("SOCKET2UNIX_PATH");
if (!path) {
- ERROR("SOCKET2UNIX_PATH environment variable not defined\n");
+ ERROR("SOCKET2UNIX_PATH environment variable not defined");
}
if (path[0] != '/') {
- ERROR("SOCKET2UNIX_PATH '%s' must be an absolute path\n", path);
+ ERROR("SOCKET2UNIX_PATH '%s' must be an absolute path", path);
}
return path;
}
}
return number;
}
+static int get_options(void) {
+ const char *start = getenv("SOCKET2UNIX_OPTIONS");
+ if (!start) {
+ return OPTION_PARSED;
+ }
+
+ int options = OPTION_PARSED;
+
+ const char *end = start + strlen(start);
+ const char *pos, *curend;
+
+ for (pos = start; pos < end; pos = curend + 1) {
+ size_t length;
+
+ curend = strchr(pos, ',');
+ if (curend == NULL) {
+ curend = end;
+ }
+ length = (size_t)(curend - pos);
+
+ if (!strncmp("client_only", pos, length)) {
+ options |= OPTION_CLIENT_ONLY;
+ } else if (!strncmp("server_only", pos, length)) {
+ options |= OPTION_SERVER_ONLY;
+ } else {
+ char option[length + 1];
+ strncpy(option, pos, length);
+ option[length] = '\0';
+ ERROR("unknown option '%s' in SOCKET2UNIX_OPTIONS", option);
+ }
+ }
+
+ if ((options & OPTION_CLIENT_ONLY) && (options & OPTION_SERVER_ONLY)) {
+ ERROR("conflicting options 'client_only', 'server_only' "
+ "in SOCKET2UNIX_OPTIONS");
+ }
+
+ return options;
+}
static const char *af_to_name(int af) {
if (af == AF_UNIX) {
return "AF_INET6";
} else if (af == AF_IPX) {
return "AF_IPX";
+#ifdef AF_NETLINK
} else if (af == AF_NETLINK) {
return "AF_NETLINK";
+#endif
+#ifdef AF_X25
} else if (af == AF_X25) {
return "AF_X25";
+#endif
+#ifdef AF_AX25
} else if (af == AF_AX25) {
return "AF_AX25";
+#endif
+#ifdef AF_ATMPVC
} else if (af == AF_ATMPVC) {
return "AF_ATMPVC";
+#endif
} else if (af == AF_APPLETALK) {
return "AF_APPLETALK";
+#ifdef AF_PACKET
} else if (af == AF_PACKET) {
return "AF_PACKET";
+#endif
} else {
return "AF_UNKNOWN";
}
return "SOCK_RAW";
} else if (sock & SOCK_RDM) {
return "SOCK_RDM";
+#ifdef SOCK_PACKET
} else if (sock & SOCK_PACKET) {
return "SOCK_PACKET";
+#endif
} else {
return "SOCK_UNKNOWN";
}
static const char *level_to_name(int level) {
if (level == SOL_SOCKET) {
return "SOL_SOCKET";
+#ifdef SOL_IP
} else if (level == SOL_IP) {
return "SOL_IP";
+#endif
+#ifdef SOL_IPV6
} else if (level == SOL_IPV6) {
return "SOL_IPV6";
+#endif
} else if (level == IPPROTO_TCP) {
return "IPPROTO_TCP";
} else if (level == IPPROTO_UDP) {
&& addrlen < sizeof(struct sockaddr_in))
|| (addr->sa_family == AF_INET6
&& addrlen < sizeof(struct sockaddr_in6))) {
- WARN("invalid addrlen from program\n");
+ WARN("invalid addrlen from program");
return -1;
}
int port;
if (addr->sa_family == AF_INET) {
af = "v4";
- port = ntohs(((struct sockaddr_in *)addr)->sin_port);
+ port = ntohs(((const struct sockaddr_in *)addr)->sin_port);
} else if (addr->sa_family == AF_INET6) {
af = "v6";
- port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port);
+ port = ntohs(((const struct sockaddr_in6 *)addr)->sin6_port);
} else {
af = "unknown";
port = 0;
- WARN("unknown sa_family '%s' (%d)\n",
+ WARN("unknown sa_family '%s' (%d)",
af_to_name(addr->sa_family), addr->sa_family);
}
return 0;
}
+static int replace_socket(int replaceefd, int replacerfd) {
+ static int (*real_close)(int);
+ LOAD_FUNCTION(real_close, "close");
+
+ /* Replace socket replaceefd with replacerfd. After dup2() both socket fds
+ * point to the same socket (replacerfd). */
+ if (dup2(replacerfd, replaceefd) < 0) {
+ return -1;
+ }
+ /* We don't need replacerfd anymore. The program will use our replacement
+ * and we don't need it for anything else. Use real_close() to prevent
+ * unnecessary debug messages. */
+ real_close(replacerfd);
+ return 0;
+}
+
/* FUNCTIONS OVERWRITTEN BY LD_PRELOAD */
static int (*real_socket)(int, int, int);
LOAD_FUNCTION(real_socket, "socket");
- if (domain == AF_UNIX || domain == AF_LOCAL) {
- return real_socket(domain, type, protocol);
+ /* We return the normal socket because we don't know yet if it's a client
+ * or a listen socket and therefore if we should replace it or not. This
+ * happens in listen() and connect(), see below. */
+
+ int sockfd = real_socket(domain, type, protocol);
+ if (sockfd < 0
+ || domain == AF_UNIX
+ || domain == AF_LOCAL) {
+ return sockfd;
}
- DBG("socket(%s, %s, %d)\n",
+ DBG("socket(%s, %s, %d)",
af_to_name(domain), sock_to_name(type), protocol);
- /* We must return the replacement socket in case the program uses select()
- * or similar on it. */
-
- int unix_sockfd = real_socket(AF_UNIX, type, 0);
- if (unix_sockfd < 0) {
- DIE("bind(): failed to create UNIX socket");
- }
-
- struct list *entry = malloc(sizeof(*entry));
- if (!entry) {
- DIE("socket(): malloc");
- }
+ struct list *entry = xmalloc(sizeof(*entry));
memset(entry, 0, sizeof(*entry));
- entry->unix_sockfd = unix_sockfd;
+ entry->orig_sockfd = sockfd;
entry->orig_domain = domain;
entry->orig_type = type;
entry->next = socket_list.next;
socket_list.next = entry;
- return unix_sockfd;
+ return sockfd;
}
int close(int fd) {
static int (*real_close)(int);
LOAD_FUNCTION(real_close, "close");
- DBG("close(%d)\n", fd);
-
struct list *entry = remove_sockfd(fd);
if (entry == NULL) {
- DBG("close(%d): sockfd not found\n", fd);
+ DBG("close(%d): sockfd not found", fd);
return real_close(fd);
}
- assert(fd == entry->unix_sockfd);
+ assert(fd == entry->orig_sockfd);
+
+ DBG("close(%d)", fd);
+ free(entry->orig_addr);
free(entry);
return real_close(fd);
static int (*real_bind)(int, const struct sockaddr *, socklen_t);
LOAD_FUNCTION(real_bind, "bind");
- DBG("bind(%d, ..)\n", sockfd);
-
if (addr == NULL || addrlen < sizeof(addr->sa_family)
|| addr->sa_family == AF_UNIX
|| addr->sa_family == AF_LOCAL) {
struct list *entry = find_sockfd(sockfd);
if (!entry) {
- DBG("bind(%d, ..): sockfd not found\n", sockfd);
+ DBG("bind(%d, ..): sockfd not found", sockfd);
return real_bind(sockfd, addr, addrlen);
}
- assert(sockfd == entry->unix_sockfd);
- DBG("bind(%d, ..): %s %s\n",
+ assert(sockfd == entry->orig_sockfd);
+ DBG("bind(%d, ..): %s %s",
sockfd,
af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
+ /* Copy struct sockaddr, we need it later in listen(). */
+ entry->orig_addr = xmalloc(addrlen);
+ memcpy(entry->orig_addr, addr, addrlen);
+ entry->orig_addrlen = addrlen;
+
+ return real_bind(sockfd, addr, addrlen);
+}
+
+int listen(int sockfd, int backlog) {
+ static int (*real_listen)(int, int);
+ LOAD_FUNCTION(real_listen, "listen");
+
+ if (!global_options) {
+ global_options = get_options();
+ }
+
+ if (global_options & OPTION_CLIENT_ONLY) {
+ DBG("listen(%d, %d): server hooking disabled", sockfd, backlog);
+ return real_listen(sockfd, backlog);
+ }
+
+ struct list *entry = find_sockfd(sockfd);
+ if (!entry) {
+ DBG("listen(%d, %d): sockfd not found", sockfd, backlog);
+ return real_listen(sockfd, backlog);
+ }
+ assert(sockfd == entry->orig_sockfd);
+ DBG("listen(%d, %d): %s %s",
+ sockfd, backlog,
+ af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
+
+ int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
+ if (unix_sockfd < 0) {
+ DIE("listen(): failed to create UNIX socket");
+ }
+
struct sockaddr_un sockaddr;
- if (set_sockaddr_un(&sockaddr, addr, addrlen) != 0) {
- ERROR("connect(%d, ..) failed\n", sockfd);
+ if (set_sockaddr_un(&sockaddr, entry->orig_addr,
+ entry->orig_addrlen) != 0) {
+ ERROR("listen(%d, ..) failed", sockfd);
}
- DBG("bind(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
+ DBG("listen(%d, ..): using path '%s'", sockfd, sockaddr.sun_path);
int attempts = 0;
while (attempts < 10) {
- if (real_bind(entry->unix_sockfd, (struct sockaddr *)&sockaddr,
- sizeof(sockaddr)) == 0) {
- /* Success. */
- return 0;
+ if (bind(unix_sockfd, (struct sockaddr *)&sockaddr,
+ sizeof(sockaddr)) == 0) {
+ break;
}
if (errno != EADDRINUSE) {
- DIE("bind(%d, ..): failed to bind to '%s'",
+ DIE("listen(%d, ..): failed to bind to '%s'",
sockfd, sockaddr.sun_path);
}
struct stat buf;
if (lstat(sockaddr.sun_path, &buf) != 0) {
/* Looks like a race, better abort. */
- DIE("bind(%d, ..): lstat on UNIX socket '%s' failed",
+ DIE("listen(%d, ..): lstat on UNIX socket '%s' failed",
sockfd, sockaddr.sun_path);
}
if (!S_ISSOCK(buf.st_mode)) {
- ERROR("bind(%d, ..): path '%s' exits and is no socket\n",
+ ERROR("listen(%d, ..): path '%s' exits and is no socket",
sockfd, sockaddr.sun_path);
}
- WARN("bind(%d, ..): unlinking '%s'\n", sockfd, sockaddr.sun_path);
+ WARN("listen(%d, ..): unlinking '%s'", sockfd, sockaddr.sun_path);
if (unlink(sockaddr.sun_path) != 0) {
- DIE("bind(%d, ..): unlink '%s' failed",
+ DIE("listen(%d, ..): unlink '%s' failed",
sockfd, sockaddr.sun_path);
}
attempts++;
}
- ERROR("bind(%d, ..): failed to create UNIX socket file\n", sockfd);
- return -1; /* never reached */
-}
-
-int listen(int sockfd, int backlog) {
- static int (*real_listen)(int, int);
- LOAD_FUNCTION(real_listen, "listen");
-
- DBG("listen(%d, %d)\n", sockfd, backlog);
+ if (attempts == 10) {
+ ERROR("listen(%d, ..): failed to create UNIX socket file", sockfd);
+ }
- struct list *entry = find_sockfd(sockfd);
- if (!entry) {
- DBG("listen(%d, %d): sockfd not found\n", sockfd, backlog);
- return real_listen(sockfd, backlog);
+ /* Replace the original socket of the program with our socket. */
+ if (replace_socket(entry->orig_sockfd, unix_sockfd)) {
+ DIE("listen(): failed to replace socket");
}
- assert(sockfd == entry->unix_sockfd);
- DBG("listen(%d, %d): %s %s\n",
- sockfd, backlog,
- af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
- if (real_listen(entry->unix_sockfd, backlog) != 0) {
+ if (real_listen(entry->orig_sockfd, backlog) != 0) {
DIE("listen(): failed to listen");
}
static int (*real_accept)(int, struct sockaddr *, socklen_t *);
LOAD_FUNCTION(real_accept, "accept");
- DBG("accept(%d, ..)\n", sockfd);
+ if (!global_options) {
+ global_options = get_options();
+ }
+
+ if (global_options & OPTION_CLIENT_ONLY) {
+ DBG("accept(%d, ..): server hooking disabled", sockfd);
+ return real_accept(sockfd, addr, addrlen);
+ }
struct list *entry = find_sockfd(sockfd);
if (!entry) {
- DBG("accept(%d, ..): sockfd not found\n", sockfd);
+ DBG("accept(%d, ..): sockfd not found", sockfd);
return real_accept(sockfd, addr, addrlen);
}
- assert(sockfd == entry->unix_sockfd);
- DBG("accept(%d, ..): %s %s\n",
+ assert(sockfd == entry->orig_sockfd);
+ DBG("accept(%d, ..): %s %s",
sockfd,
af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
struct sockaddr_un sockaddr;
socklen_t size = sizeof(sockaddr);
- int sock = real_accept(entry->unix_sockfd, (struct sockaddr *)&sockaddr,
+ int sock = real_accept(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
&size);
if (sock < 0) {
DIE("accept(%d, ..): failed to accept", sockfd);
if (addr == NULL || addrlen == NULL) {
return sock;
}
- DBG("accept(%d, ..): caller requested sockaddr\n", sockfd);
+ DBG("accept(%d, ..): caller requested sockaddr", sockfd);
if (*addrlen < size) {
WARN("accept(%d, ..): invalid addrlen from program", sockfd);
static int (*real_connect)(int, const struct sockaddr *, socklen_t);
LOAD_FUNCTION(real_connect, "connect");
- DBG("connect(%d, ..)\n", sockfd);
+ if (!global_options) {
+ global_options = get_options();
+ }
+
+ if (global_options & OPTION_SERVER_ONLY) {
+ DBG("connect(%d, ..): client hooking disabled", sockfd);
+ return real_connect(sockfd, addr, addrlen);
+ }
if (addr == NULL || addrlen < sizeof(addr->sa_family)
|| addr->sa_family == AF_UNIX
struct list *entry = find_sockfd(sockfd);
if (!entry) {
- DBG("connect(%d, ..): sockfd not found\n", sockfd);
+ DBG("connect(%d, ..): sockfd not found", sockfd);
return real_connect(sockfd, addr, addrlen);
}
- assert(sockfd == entry->unix_sockfd);
- DBG("connect(%d, ..): %s %s\n",
+ assert(sockfd == entry->orig_sockfd);
+ DBG("connect(%d, ..): %s %s",
sockfd,
af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
+ int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
+ if (unix_sockfd < 0) {
+ DIE("bind(): failed to create UNIX socket");
+ }
+
+ /* Replace the original socket of the program with our socket. */
+ if (replace_socket(entry->orig_sockfd, unix_sockfd)) {
+ DIE("connect(): failed to replace socket");
+ }
+
struct sockaddr_un sockaddr;
if (set_sockaddr_un(&sockaddr, addr, addrlen) != 0) {
- ERROR("connect(%d, ..) failed\n", sockfd);
+ ERROR("connect(%d, ..) failed", sockfd);
}
- DBG("connect(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
+ DBG("connect(%d, ..): using path '%s'", sockfd, sockaddr.sun_path);
- if (real_connect(entry->unix_sockfd, (struct sockaddr *)&sockaddr,
+ if (real_connect(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
sizeof(sockaddr)) != 0) {
DIE("connect(%d, ..): failed to connect", sockfd);
}
static int (*real_getsockname)(int, struct sockaddr *, socklen_t *);
LOAD_FUNCTION(real_getsockname, "getsockname");
- DBG("getsockname(%d, ..)\n", sockfd);
+ DBG("getsockname(%d, ..)", sockfd);
return real_getsockname(sockfd, addr, addrlen);
}
static int (*real_getpeername)(int, struct sockaddr *, socklen_t *);
LOAD_FUNCTION(real_getpeername, "getpeername");
- DBG("getpeername(%d, ..)\n", sockfd);
+ DBG("getpeername(%d, ..)", sockfd);
return real_getpeername(sockfd, addr, addrlen);
}
static int (*real_getsockopt)(int, int, int, void *, socklen_t *);
LOAD_FUNCTION(real_getsockopt, "getsockopt");
- DBG("getsockopt(%d, %d %s, %d, ..)\n",
+ DBG("getsockopt(%d, %d %s, %d, ..)",
sockfd, level, level_to_name(level), optname);
return real_getsockopt(sockfd, level, optname, optval, optlen);
static int (*real_setsockopt)(int, int, int, const void *, socklen_t);
LOAD_FUNCTION(real_setsockopt, "setsockopt");
- DBG("setsockopt(%d, %d %s, %d, ..)\n",
+ DBG("setsockopt(%d, %d %s, %d, ..)",
sockfd, level, level_to_name(level), optname);
return real_setsockopt(sockfd, level, optname, optval, optlen);