diff --git a/config.h b/config.h index 671c0eb88e837098473b7e2bd100f9ab3bf4b27f..42a2a029918a4e7f8afe2144a5af9e636a5f5360 100644 --- a/config.h +++ b/config.h @@ -3,6 +3,8 @@ # define DEBUG_OUTPUT 0 // change to 1 to enable debugging +# define PKCS11PROXY_LISTEN_BACKLOG 128 + #ifdef __MINGW32__ # include <stdint.h> diff --git a/gck-rpc-dispatch.c b/gck-rpc-dispatch.c index 5b7c46154f88a920eadfa32d50d41e43cd88571f..f3a6db64d6379885eb36edfe35b6d9d8352687d8 100644 --- a/gck-rpc-dispatch.c +++ b/gck-rpc-dispatch.c @@ -40,6 +40,8 @@ # include <arpa/inet.h> # include <netinet/in.h> # include <netinet/tcp.h> +# include <sys/types.h> +# include <netdb.h> #endif #include <pthread.h> @@ -2297,6 +2299,150 @@ void gck_rpc_layer_inetd(CK_FUNCTION_LIST_PTR module) run_dispatch_thread(&cs); } +/* + * Parses prefix into two strings (host and port). Port may be a NULL pointer + * if none is specified. Since this code does not decode port in any way, a + * service name works too (but requires other code (like + * _get_listening_socket()) able to resolve service names). + * + * This should work for IPv4 and IPv6 inputs : + * + * 0.0.0.0:2345 + * 0.0.0.0 + * [::]:2345 + * [::] + * [::1]:2345 + * localhost:2345 + * localhost + * + * Returns 0 on failure, and 1 on success. + */ +static int _parse_host_port(const char *prefix, char **host, char **port) +{ + char *p = NULL; + int is_ipv6; + + is_ipv6 = (prefix[0] == '[') ? 1 : 0; + + *host = strdup(prefix + is_ipv6); + *port = NULL; + + if (*host == NULL) { + gck_rpc_warn("out of memory"); + return 0; + } + + if (is_ipv6 && prefix[0] == '[') + p = strchr(*host, ']'); + else + p = strchr(*host, ':'); + + if (p) { + is_ipv6 = (*p == ']'); /* remember if separator was ']' */ + + *p = '\0'; /* replace separator will NULL to terminate *host */ + *port = p + 1; + + if (is_ipv6 && (**port == ':')) + *port = p + 2; + } + + return 1; +} + +/* + * Try to get a listening socket for host and port (host may be either a name or + * an IP address, as string) and port (a string with a service name or a port + * number). + * + * Returns -1 on failure, and the socket fd otherwise. + */ +static int _get_listening_socket(char *host, char *port) +{ + char hoststr[NI_MAXHOST], portstr[NI_MAXSERV]; + struct addrinfo *ai, *first, hints; + int res, sock, one = 1; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = AI_PASSIVE; /* Want addr for bind() */ + hints.ai_family = AF_UNSPEC; /* Either IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Only stream oriented sockets */ + + if ((res = getaddrinfo(host, port, &hints, &ai)) < 0) { + gck_rpc_warn("couldn't resolve host '%.100s' or service '%.100s' : %.100s\n", + host, port, gai_strerror(res)); + return -1; + } + + sock = -1; + first = ai; + + /* Loop through the sockets returned and see if we can find one that accepts + * our options and bind() + */ + while (ai) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + + if (sock >= 0) { + if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + (char *)&one, sizeof (one)) == -1) { + gck_rpc_warn("couldn't set pkcs11 " + "socket protocol options (%.100s %.100s): %.100s", + host, port, strerror (errno)); + goto next; + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *)&one, sizeof(one)) == -1) { + gck_rpc_warn + ("couldn't set pkcs11 socket options (%.100s %.100s): %.100s", + host, port, strerror(errno)); + goto next; + } + + if (bind(sock, ai->ai_addr, ai->ai_addrlen) == 0) + break; + + next: + close(sock); + sock = -1; + } + ai = ai->ai_next; + } + + if (sock < 0) { + gck_rpc_warn("couldn't create pkcs11 socket (%.100s %.100s): %.100s\n", + host, port, strerror(errno)); + sock = -1; + goto out; + } + + if (listen(sock, PKCS11PROXY_LISTEN_BACKLOG) < 0) { + gck_rpc_warn("couldn't listen on pkcs11 socket (%.100s %.100s): %.100s", + host, port, strerror(errno)); + sock = -1; + goto out; + } + + /* Format a string describing the socket we're listening on into pkcs11_socket_path */ + if ((res = getnameinfo(ai->ai_addr, ai->ai_addrlen, + hoststr, sizeof(hoststr), portstr, sizeof(portstr), + NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { + gck_rpc_warn("couldn't call getnameinfo on pkcs11 socket (%.100s %.100s): %.100s", + host, port, gai_strerror(res)); + sock = -1; + goto out; + } + + snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), + (ai->ai_family == AF_INET6) ? "[%s]:%s" : "%s:%s", hoststr, portstr); + + out: + freeaddrinfo(first); + + return sock; +} + int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module) { struct sockaddr_un addr; @@ -2330,60 +2476,26 @@ int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module) #endif if (!strncmp("tcp://", prefix, 6)) { - int one = 1, port; - char *p = NULL; - char *ip; - - ip = strdup(prefix + 6); - if (ip == NULL) { - gck_rpc_warn("out of memory"); - return -1; - } - - p = strchr(ip, ':'); - - if (p) { - *p = '\0'; - port = strtol(p + 1, NULL, 0); - } else - port = 0; - - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - - if (sock < 0) { - gck_rpc_warn("couldn't create pkcs11 socket: %s", - strerror(errno)); - return -1; - } - - if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, - (char *)&one, sizeof (one)) == -1) { - gck_rpc_warn("couldn't set pkcs11 " - "socket options : %s", strerror (errno)); - return -1; - } + /* + * TCP socket + */ + char *host, *port; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (char *)&one, sizeof(one)) == -1) { - gck_rpc_warn - ("couldn't set pkcs11 socket options : %s", - strerror(errno)); + if (! _parse_host_port(prefix + 6, &host, &port)) { + free(host); return -1; } - addr.sun_family = AF_INET; - if (inet_aton(ip, &((struct sockaddr_in *)&addr)->sin_addr) == - 0) { - gck_rpc_warn("bad inet address : %s", ip); + if ((sock = _get_listening_socket(host, port)) == -1) { + free(host); return -1; } - ((struct sockaddr_in *)&addr)->sin_port = htons(port); - snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), - "%s", prefix); - - free(ip); + free(host); } else { + /* + * UNIX domain socket + */ snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), "%s/socket.pkcs11", prefix); @@ -2399,34 +2511,18 @@ int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module) unlink(pkcs11_socket_path); strncpy(addr.sun_path, pkcs11_socket_path, sizeof(addr.sun_path)); - } - - if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - gck_rpc_warn("couldn't bind to pkcs11 socket: %s: %s", - pkcs11_socket_path, strerror(errno)); - return -1; - } - if (listen(sock, 128) < 0) { - gck_rpc_warn("couldn't listen on pkcs11 socket: %s: %s", - pkcs11_socket_path, strerror(errno)); - return -1; - } - - if (!strncmp("tcp://", prefix, 6)) { - struct sockaddr_in sa; - socklen_t sa_len; - - sa_len = sizeof(sa); - - if (getsockname(sock, (struct sockaddr *)&sa, &sa_len) == -1) { - gck_rpc_warn("getsockname failed on pkcs11 socket: %s: %s", + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + gck_rpc_warn("couldn't bind to pkcs11 socket: %s: %s", pkcs11_socket_path, strerror(errno)); return -1; } - snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), - "%s:%d", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port)); + if (listen(sock, PKCS11PROXY_LISTEN_BACKLOG) < 0) { + gck_rpc_warn("couldn't listen on pkcs11 socket: %s: %s", + pkcs11_socket_path, strerror(errno)); + return -1; + } } gck_rpc_log("Listening on: %s\n", pkcs11_socket_path);