X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=socket-util.c;h=8141ab09ebcd3fa0c08d4a715c1e873f84c2b262;hp=407c9617b079b1effff71964ae4b9a775c62b2ca;hb=858a9b0a99db40ef22676808c0e19e024467f90c;hpb=42f4e3c4413ad35e3815f25211fee95d775488a7 diff --git a/socket-util.c b/socket-util.c index 407c9617b..8141ab09e 100644 --- a/socket-util.c +++ b/socket-util.c @@ -1,5 +1,24 @@ /*-*- Mode: C; c-basic-offset: 8 -*-*/ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd 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. + + systemd 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 systemd; If not, see . +***/ + #include #include #include @@ -7,12 +26,15 @@ #include #include #include +#include +#include +#include #include "macro.h" #include "util.h" #include "socket-util.h" -int address_parse(Address *a, const char *s) { +int socket_address_parse(SocketAddress *a, const char *s) { int r; char *e, *n; unsigned u; @@ -20,7 +42,8 @@ int address_parse(Address *a, const char *s) { assert(a); assert(s); - memset(a, 0, sizeof(*a)); + zero(*a); + a->type = SOCK_STREAM; if (*s == '[') { /* IPv6 in [x:.....:z]:p notation */ @@ -53,7 +76,6 @@ int address_parse(Address *a, const char *s) { a->sockaddr.in6.sin6_family = AF_INET6; a->sockaddr.in6.sin6_port = htons((uint16_t) u); a->size = sizeof(struct sockaddr_in6); - a->bind_ipv6_only = true; } else if (*s == '/') { /* AF_UNIX socket */ @@ -68,7 +90,7 @@ int address_parse(Address *a, const char *s) { memcpy(a->sockaddr.un.sun_path, s, l); a->size = sizeof(sa_family_t) + l + 1; - } else if (*s == '=') { + } else if (*s == '@') { /* Abstract AF_UNIX socket */ size_t l; @@ -84,28 +106,50 @@ int address_parse(Address *a, const char *s) { if ((e = strchr(s, ':'))) { - /* IPv4 in w.x.y.z:p notation */ + if ((r = safe_atou(e+1, &u)) < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + if (!(n = strndup(s, e-s))) return -ENOMEM; - errno = 0; - if (inet_pton(AF_INET, n, &a->sockaddr.in4.sin_addr) <= 0) { + /* IPv4 in w.x.y.z:p notation? */ + if ((r = inet_pton(AF_INET, n, &a->sockaddr.in4.sin_addr)) < 0) { free(n); - return errno != 0 ? -errno : -EINVAL; + return -errno; } - free(n); + if (r > 0) { + /* Gotcha, it's a traditional IPv4 address */ + free(n); - e++; - if ((r = safe_atou(e, &u)) < 0) - return r; + a->sockaddr.in4.sin_family = AF_INET; + a->sockaddr.in4.sin_port = htons((uint16_t) u); + a->size = sizeof(struct sockaddr_in); + } else { + unsigned idx; - if (u <= 0 || u > 0xFFFF) - return -EINVAL; + if (strlen(n) > IF_NAMESIZE-1) { + free(n); + return -EINVAL; + } - a->sockaddr.in4.sin_family = AF_INET; - a->sockaddr.in4.sin_port = htons((uint16_t) u); - a->size = sizeof(struct sockaddr_in); + /* Uh, our last resort, an interface name */ + idx = if_nametoindex(n); + free(n); + + if (idx == 0) + return -EINVAL; + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_scope_id = idx; + a->sockaddr.in6.sin6_addr = in6addr_any; + a->size = sizeof(struct sockaddr_in6); + + } } else { /* Just a port */ @@ -116,8 +160,8 @@ int address_parse(Address *a, const char *s) { return -EINVAL; a->sockaddr.in6.sin6_family = AF_INET6; - memcpy(&a->sockaddr.in6.sin6_addr, &in6addr_any, INET6_ADDRSTRLEN); a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_addr = in6addr_any; a->size = sizeof(struct sockaddr_in6); } } @@ -125,10 +169,10 @@ int address_parse(Address *a, const char *s) { return 0; } -int address_verify(const Address *a) { +int socket_address_verify(const SocketAddress *a) { assert(a); - switch (address_family(a)) { + switch (socket_address_family(a)) { case AF_INET: if (a->size != sizeof(struct sockaddr_in)) return -EINVAL; @@ -176,15 +220,15 @@ int address_verify(const Address *a) { } } -int address_print(const Address *a, char **p) { +int socket_address_print(const SocketAddress *a, char **p) { int r; assert(a); assert(p); - if ((r = address_verify(a)) < 0) + if ((r = socket_address_verify(a)) < 0) return r; - switch (address_family(a)) { + switch (socket_address_family(a)) { case AF_INET: { char *ret; @@ -237,7 +281,7 @@ int address_print(const Address *a, char **p) { if (!(ret = new(char, sizeof(a->sockaddr.un.sun_path)+1))) return -ENOMEM; - ret[0] = '='; + ret[0] = '@'; memcpy(ret+1, a->sockaddr.un.sun_path+1, sizeof(a->sockaddr.un.sun_path)-1); ret[sizeof(a->sockaddr.un.sun_path)] = 0; @@ -256,35 +300,158 @@ int address_print(const Address *a, char **p) { } } -int address_listen(const Address *a, int backlog) { - int r, fd; +int socket_address_listen( + const SocketAddress *a, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + mode_t directory_mode, + mode_t socket_mode, + int *ret) { + + int r, fd, one; assert(a); + assert(ret); - if ((r = address_verify(a)) < 0) + if ((r = socket_address_verify(a)) < 0) return r; - if ((fd = socket(address_family(a), a->type, 0)) < 0) + if ((fd = socket(socket_address_family(a), a->type | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)) < 0) return -errno; - if (bind(fd, &a->sockaddr.sa, a->size) < 0) { - close_nointr(fd); - return -errno; + if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) { + int flag = only == SOCKET_ADDRESS_IPV6_ONLY; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0) + goto fail; } - if (a->type == SOCK_STREAM) - if (listen(fd, backlog) < 0) { - close_nointr(fd); - return -errno; + if (bind_to_device) + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0) + goto fail; + + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) + goto fail; + + if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) { + mode_t old_mask; + + /* Create parents */ + mkdir_parents(a->sockaddr.un.sun_path, directory_mode); + + /* Enforce the right access mode for the socket*/ + old_mask = umask(~ socket_mode); + + /* Include the original umask in our mask */ + umask(~socket_mode | old_mask); + + r = bind(fd, &a->sockaddr.sa, a->size); + + if (r < 0 && errno == EADDRINUSE) { + /* Unlink and try again */ + unlink(a->sockaddr.un.sun_path); + r = bind(fd, &a->sockaddr.sa, a->size); } - if (address_family(a) == AF_INET6) { - int flag = a->bind_ipv6_only; + umask(old_mask); + } else + r = bind(fd, &a->sockaddr.sa, a->size); + + if (r < 0) + goto fail; + + if (a->type == SOCK_STREAM) + if (listen(fd, backlog) < 0) + goto fail; + + *ret = fd; + return 0; + +fail: + r = -errno; + close_nointr_nofail(fd); + return r; +} + +bool socket_address_can_accept(const SocketAddress *a) { + assert(a); + + return + a->type == SOCK_STREAM || + a->type == SOCK_SEQPACKET; +} + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { + assert(a); + assert(b); + + /* Invalid addresses are unequal to all */ + if (socket_address_verify(a) < 0 || + socket_address_verify(b) < 0) + return false; + + if (a->type != b->type) + return false; + + if (a->size != b->size) + return false; + + if (socket_address_family(a) != socket_address_family(b)) + return false; + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->sockaddr.in4.sin_addr.s_addr != b->sockaddr.in4.sin_addr.s_addr) + return false; + + if (a->sockaddr.in4.sin_port != b->sockaddr.in4.sin_port) + return false; - if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0) { - close_nointr(fd); - return -errno; + break; + + case AF_INET6: + if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0) + return false; + + if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port) + return false; + + break; + + case AF_UNIX: + + if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0)) + return false; + + if (a->sockaddr.un.sun_path[0]) { + if (strncmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, sizeof(a->sockaddr.un.sun_path)) != 0) + return false; + } else { + if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, sizeof(a->sockaddr.un.sun_path)) != 0) + return false; } + + break; + + default: + /* Cannot compare, so we assume the addresses are different */ + return false; } - return 0; + return true; +} + +bool socket_address_is(const SocketAddress *a, const char *s) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse(&b, s) < 0) + return false; + + return socket_address_equal(a, &b); + }