X-Git-Url: https://ruderich.org/simon/gitweb/?p=socket2unix%2Fsocket2unix.git;a=blobdiff_plain;f=src%2Fsocket2unix.c;h=7e761c62db253b056e796de9c592763a71ea8aa1;hp=848a40bf68745eecf0fd9dd2bf3416d65ace8e61;hb=a452581cde874113f833e590cd6765413dba81c0;hpb=39b505e0c28b8ee39bef37174cd12b0981b07d20 diff --git a/src/socket2unix.c b/src/socket2unix.c index 848a40b..7e761c6 100644 --- a/src/socket2unix.c +++ b/src/socket2unix.c @@ -22,11 +22,12 @@ /* Necessary for RTLD_NEXT. */ #define _GNU_SOURCE +#include + #include #include #include #include -#include #include #include #include @@ -45,24 +46,35 @@ #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 */ @@ -104,6 +116,9 @@ static void log_helper(int action, const char *file, int line, const char *forma errno = saved_errno; perror(""); + /* perror writes a newline. */ + } else { + fprintf(stderr, "\n"); } funlockfile(stderr); @@ -154,15 +169,23 @@ static void log_(int level, const char *file, int line, const char *format, ...) /* 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; } } @@ -171,12 +194,12 @@ static struct list *find_sockfd(int sockfd) { 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; } @@ -187,17 +210,21 @@ static struct list *remove_sockfd(int sockfd) { 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; } static int get_log_level(void) { const char *level = getenv("SOCKET2UNIX_DEBUG"); if (!level) { +#ifdef DEBUG + return LOG_LEVEL_DEBUG; +#else return LOG_LEVEL_WARNING; +#endif } int number = atoi(level); if (number <= 0 || number > LOG_LEVEL_DEBUG) { @@ -205,6 +232,45 @@ static int get_log_level(void) { } 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) { @@ -217,18 +283,28 @@ static const char *af_to_name(int af) { 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"; } @@ -244,8 +320,10 @@ static const char *sock_to_name(int sock) { 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"; } @@ -254,10 +332,14 @@ static const char *sock_to_name(int sock) { 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) { @@ -275,7 +357,7 @@ static int set_sockaddr_un(struct sockaddr_un *sockaddr, && 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; } @@ -287,14 +369,14 @@ static int set_sockaddr_un(struct sockaddr_un *sockaddr, 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); } @@ -311,6 +393,22 @@ static int set_sockaddr_un(struct sockaddr_un *sockaddr, 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 */ @@ -318,49 +416,46 @@ int socket(int domain, int type, int protocol) { 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); @@ -370,8 +465,6 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 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) { @@ -380,30 +473,66 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 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); } @@ -414,45 +543,34 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 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"); } @@ -463,21 +581,28 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { 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); @@ -486,7 +611,7 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { 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); @@ -508,7 +633,14 @@ int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 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 @@ -518,22 +650,32 @@ int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 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); } @@ -546,7 +688,7 @@ int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { 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); } @@ -555,7 +697,7 @@ int getpeername(int sockfd, struct sockaddr *addr, socklen_t *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); } @@ -564,7 +706,7 @@ int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optl 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); @@ -573,7 +715,7 @@ int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t 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);