/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include USAGE("[-a unix-address] [-c config-file] [-p udp-port]"); #define UDP_PORT 42712 #define LOCAL_ADDRESS_PREFIX "@oikobus-" #define LOCAL_ADDRESS_BUFSIZE (sizeof(LOCAL_ADDRESS_PREFIX) + 3u * sizeof(uintmax_t)) #define LOCAL_ADDRESS(UID) "%s%ju", LOCAL_ADDRESS_PREFIX, (uintmax_t)(UID) #define SOCKADDR(ADDR) ((void *)&(ADDR)), ((socklen_t)sizeof(ADDR)) #define ALPHA64 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}" #define MIN_REALM_LEN 1u #define MAX_REALM_LEN 510u #define CHALLENGE_SIZE 64u /* must be within [1 64] (BLAKE2b key bounds) */ #define PROOF_SIZE 64u /* must be within [1 64] (BLAKE2b digest bounds) */ #define CHALLENGE_PEPPER "\xa1\x7d\xcc\x75\x51\xd9\xc2\x2b\x63\xf9\x45\x83\x4b\x4d\xae\x4c" union file; enum file_type { TIMER_FILE, BROADCAST_FILE, NETWORK_FILE, LOCAL_FILE, PEER_FILE, CLIENT_FILE }; #define FILE_COMMON\ enum file_type type;\ int fd;\ void (*on_event)(union file *this, uint32_t epoll_events) struct timer_file { FILE_COMMON; union file *owner; }; struct broadcast_file { FILE_COMMON; struct sockaddr_in addr; struct timer_file timer; int64_t timer_inc; struct timespec alarm_time; }; struct network_file { FILE_COMMON; }; struct local_file { FILE_COMMON; }; struct peer_file { FILE_COMMON; uint64_t id; struct sockaddr_in addr; uint8_t *realms; struct handshake_state { #define HANDSHAKE_A_INIT HANDSHAKE_A_SEND_ID #define HANDSHAKE_B_INIT HANDSHAKE_B_SEND_ID enum handshake_status { HANDSHAKE_A_SEND_ID, HANDSHAKE_A_RECV_ID, HANDSHAKE_A_SEND_REALM, HANDSHAKE_A_RECV_CHALLENGE, HANDSHAKE_A_SEND_PROOF, HANDSHAKE_A_RECV_ACCEPTANCE, HANDSHAKE_B_SEND_ID, HANDSHAKE_B_RECV_ID, HANDSHAKE_B_RECV_REALM, HANDSHAKE_B_SEND_CHALLENGE, HANDSHAKE_B_RECV_PROOF, HANDSHAKE_B_SEND_ACCEPTANCE } status; size_t realm; uint16_t off; uint16_t len; uint16_t head; uint16_t tail; unsigned char buf[512]; unsigned char sbuf[512]; } *handshake_state; }; struct client_file { FILE_COMMON; struct client_file *prev; struct client_file *next; }; union file { struct { FILE_COMMON; }; struct timer_file timer; struct broadcast_file broadcast; struct network_file network; struct local_file local; struct peer_file peer; struct client_file client; }; struct broadcast_message { #define BROADCAST_MESSAGE_SIZE 10 uint64_t id; uint16_t port; }; struct realm { char *name; char *key; }; static struct realm *realms = NULL; static size_t nrealms = 0u; static struct broadcast_message server_identity; static int epoll; static struct epoll_event *events; static int events_size; static struct local_file *lsock; static struct network_file *nsock; static struct broadcast_file *bsock; static struct peer_file **peers; static size_t npeers; static size_t peers_size; static uid_t uid; static struct client_file clients_head; static struct client_file clients_tail; static void remove_peer(struct peer_file *peer) { size_t i; for (i = 0u; i < npeers; i++) { if (peers[i] == peer) { peers[i] = peers[--npeers]; break; } } close(peer->fd); free(peer->handshake_state); free(peer->realms); free(peer); } static struct peer_file * add_peer(int fd, uint64_t id, const struct sockaddr_in *addr, enum handshake_status status, void (*on_event)(union file *, uint32_t)) { struct peer_file *file; struct epoll_event ev; file = emalloc(sizeof(*file)); file->type = PEER_FILE; file->fd = fd; file->id = id; file->addr = *addr; file->on_event = on_event; file->realms = nrealms ? ecalloc((nrealms + 7u) / 8u, sizeof(uint8_t)) : NULL; file->handshake_state = ecalloc(1u, sizeof(*file->handshake_state)); file->handshake_state->status = status; memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN | EPOLLOUT; ev.data.ptr = file; if (epoll_ctl(epoll, EPOLL_CTL_ADD, file->fd, &ev)) eprintf("epoll_ctl EPOLL_CTL_ADD {EPOLLIN|EPOLLOUT}:"); if (npeers == peers_size) peers = ereallocarray(peers, ++peers_size, sizeof(*peers)); peers[npeers++] = file; return file; } static void on_client_event(union file *this, uint32_t epoll_events) { (void) this; (void) epoll_events; /* TODO */ } static void on_peer_event(union file *this, uint32_t epoll_events) { (void) this; (void) epoll_events; /* TODO */ } static unsigned char * handshake_prepare(struct peer_file *this, size_t len) { uint16_t n; if (len > sizeof(this->handshake_state->sbuf) - 2u) abort(); len += 2u; n = (uint16_t)len; this->handshake_state->off = 0u; this->handshake_state->len = n; n = htons(n); memcpy(&this->handshake_state->sbuf[0u], &n, 2u); return &this->handshake_state->sbuf[2u]; } static int handshake_continue(struct peer_file *this) { size_t rem; ssize_t r; while ((rem = this->handshake_state->len - this->handshake_state->off)) { r = send(this->fd, &this->handshake_state->sbuf[this->handshake_state->off], rem, MSG_NOSIGNAL | MSG_DONTWAIT); if (r < 0) { if (errno == EINTR) continue; if (errno == ECONNRESET || errno == EPIPE) return -1; if (errno == EAGAIN) return 0; #if EAGAIN != EWOULDBLOCK if (errno == EWOULDBLOCK) return 0; #endif weprintf("send :"); return -1; } this->handshake_state->off += (uint16_t)r; } this->handshake_state->off = 0u; this->handshake_state->len = 0u; return 1; } static int handshake_send(struct peer_file *this, const void *message, size_t len) { if (!this->handshake_state->len) memcpy(handshake_prepare(this, len), message, len); return handshake_continue(this); } static int handshake_receive(struct peer_file *this) { ssize_t r; uint16_t len = 0u; if (this->handshake_state->head != this->handshake_state->tail) { memmove(&this->handshake_state->buf[0u], &this->handshake_state->buf[this->handshake_state->head], this->handshake_state->tail -= this->handshake_state->head); this->handshake_state->off += this->handshake_state->tail; this->handshake_state->tail = this->handshake_state->head = 0u; } for (;;) { if (len) { goto check_done; } else if (this->handshake_state->off >= 2u) { memcpy(&len, &this->handshake_state->buf[0u], 2u); len = ntohs(len); check_done: if (this->handshake_state->off >= len) break; } r = recv(this->fd, &this->handshake_state->buf[this->handshake_state->off], sizeof(this->handshake_state->buf) - this->handshake_state->off, MSG_DONTWAIT); if (r <= 0) { if (!r) return -1; if (errno == EINTR) continue; if (errno == ECONNRESET || errno == EPIPE) return -1; if (errno == EAGAIN) return 0; #if EAGAIN != EWOULDBLOCK if (errno == EWOULDBLOCK) return 0; #endif weprintf("recv :"); return -1; } this->handshake_state->off += (uint16_t)r; } this->handshake_state->tail = this->handshake_state->off; this->handshake_state->head = this->handshake_state->off = len; return 1; } static void generate_proof(const struct realm *realm, const void *challenge, unsigned char *buf) { unsigned char block[LIBBLAKE_BLAKE2B_BLOCK_SIZE]; struct libblake_blake2b_params params; struct libblake_blake2b_state state; size_t salt_len; size_t key_len = strlen(realm->key); size_t off = 0u; salt_len = strlen(realm->name); salt_len = salt_len < 16u ? salt_len : 16u; memset(¶ms, 0, sizeof(params)); params.digest_len = PROOF_SIZE; params.key_len = CHALLENGE_SIZE; params.fanout = 1u; params.depth = 1u; memcpy(params.salt, realm->name, salt_len); memcpy(params.pepper, CHALLENGE_PEPPER, 16u); libblake_blake2b_init(&state, ¶ms); memcpy(&block[0u], challenge, CHALLENGE_SIZE); memset(&block[CHALLENGE_SIZE], 0, sizeof(block) - CHALLENGE_SIZE); libblake_blake2b_force_update(&state, block, sizeof(block)); off = key_len & ~(sizeof(block) - 1u); if (off == key_len) off -= sizeof(block); if (off) off = libblake_blake2b_force_update(&state, realm->key, off); memcpy(&block[0u], realm->key, key_len - off); libblake_blake2b_digest(&state, block, key_len - off, 0, PROOF_SIZE, buf); } static void peer_handshake(union file *this_, uint32_t epoll_events) { unsigned char proof_buf[PROOF_SIZE]; struct peer_file *this = &this_->peer; struct epoll_event ev; uint16_t len; size_t i; int r; #define SEND(...) CHECKED(handshake_send(this, __VA_ARGS__)) goto wait_write #define RECV() CHECKED(handshake_receive(this)) goto wait_read #define CONT() CHECKED(handshake_continue(this)) goto wait_write #define CHECKED(COMMAND)\ r = (COMMAND);\ if (r < 0) goto remove;\ if (r == 0) (void) epoll_events; switch (this->handshake_state->status) { case HANDSHAKE_A_SEND_ID: SEND(&server_identity.id, 8u); this->handshake_state->status++; /* fall-through */ case HANDSHAKE_A_RECV_ID: RECV(); len = this->handshake_state->off; this->handshake_state->off = 0u; if (len != 10u || memcmp(&this->handshake_state->buf[2u], &this->id, 8u)) goto remove; this->handshake_state->status++; /* fall-through */ case HANDSHAKE_A_SEND_REALM: send_realm: if (this->handshake_state->realm == nrealms) { SEND("", 0u); break; } SEND(realms[this->handshake_state->realm].name, strlen(realms[this->handshake_state->realm].name)); this->handshake_state->status++; /* fall-through */ case HANDSHAKE_A_RECV_CHALLENGE: RECV(); len = this->handshake_state->off; this->handshake_state->off = 0u; if (len == 2u) { this->handshake_state->status = HANDSHAKE_A_SEND_REALM; goto send_realm; } if (len != CHALLENGE_SIZE + 2u) { goto remove; } generate_proof(&realms[this->handshake_state->realm], &this->handshake_state->buf[2u], handshake_prepare(this, PROOF_SIZE)); this->handshake_state->status++; /* fall-through */ case HANDSHAKE_A_SEND_PROOF: CONT(); this->handshake_state->status++; /* fall-through */ case HANDSHAKE_A_RECV_ACCEPTANCE: RECV(); len = this->handshake_state->off; this->handshake_state->off = 0u; if (len != 3u || this->handshake_state->buf[2u] > 1u) goto remove; if (this->handshake_state->buf[2u]) { uint8_t bit = (uint8_t)(1u << (this->handshake_state->realm % 8u)); this->realms[this->handshake_state->realm / 8u] |= bit; } this->handshake_state->realm++; this->handshake_state->status = HANDSHAKE_A_SEND_REALM; goto send_realm; case HANDSHAKE_B_SEND_ID: if (!handshake_send(this, &server_identity.id, 8u)) goto wait_write; this->handshake_state->status++; /* fall-through */ case HANDSHAKE_B_RECV_ID: RECV(); len = this->handshake_state->off; this->handshake_state->off = 0u; if (len != 10u) goto remove; memcpy(&this->id, &this->handshake_state->buf[2u], 8u); if (this->id == server_identity.id) goto remove; for (i = 0u; i < npeers; i++) if (peers[i]->id == this->id && peers[i] != this) goto remove; this->handshake_state->status++; /* fall-through */ case HANDSHAKE_B_RECV_REALM: recv_realm: RECV(); len = this->handshake_state->off; this->handshake_state->off = 0u; if (len <= 2u) { if (len == 2u) break; goto remove; } len = (uint16_t)(len - 2u); for (i = 0u; i < nrealms; i++) { if (memcmp(realms[i].name, &this->handshake_state->buf[2u], len)) continue; if (realms[i].name[len]) continue; break; } this->handshake_state->realm = i; if (this->handshake_state->realm == nrealms) { handshake_prepare(this, 0u); } else { unsigned char *m = handshake_prepare(this, CHALLENGE_SIZE); for (i = 0u; i < CHALLENGE_SIZE; i++) m[i] = (unsigned char)(rand() & 255); } this->handshake_state->status++; /* fall-through */ case HANDSHAKE_B_SEND_CHALLENGE: CONT(); if (this->handshake_state->realm == nrealms) { this->handshake_state->status = HANDSHAKE_B_RECV_REALM; goto recv_realm; } this->handshake_state->status++; /* fall-through */ case HANDSHAKE_B_RECV_PROOF: RECV(); len = this->handshake_state->off; this->handshake_state->off = 0u; if (len != PROOF_SIZE + 2u) goto remove; generate_proof(&realms[this->handshake_state->realm], &this->handshake_state->sbuf[2u], proof_buf); if (!memcmp(&this->handshake_state->buf[2u], proof_buf, PROOF_SIZE)) { uint8_t bit = (uint8_t)(1u << (this->handshake_state->realm % 8u)); this->realms[this->handshake_state->realm / 8u] |= bit; handshake_prepare(this, 1u)[0u] = 1u; /* TODO we should send a realm index instead, so we can * encode reals as indicies rather than names * (index 0 should be reserved for the local-only * realm: a default realm that can only be accessed * on the same machine by the same user) */ } else { handshake_prepare(this, 1u)[0u] = 0u; } this->handshake_state->status++; /* fall-through */ case HANDSHAKE_B_SEND_ACCEPTANCE: CONT(); this->handshake_state->status = HANDSHAKE_B_RECV_REALM; goto recv_realm; default: abort(); } for (i = 0u; i < (nrealms + 7u) / 8u; i++) if (this->realms[i]) goto have_a_realm; remove: remove_peer(this); return; have_a_realm: epoll_events = EPOLLIN; this->on_event = &on_peer_event; /* TODO copy over handshake recv buf */ free(this->handshake_state); this->handshake_state = NULL; config_epoll: memset(&ev, 0, sizeof(ev)); ev.events = epoll_events; ev.data.ptr = this; if (epoll_ctl(epoll, EPOLL_CTL_MOD, this->fd, &ev)) eprintf("epoll_ctl EPOLL_CTL_MOD {%s}:", epoll_events == EPOLLIN ? "EPOLLIN" : "EPOLLOUT"); return; wait_write: epoll_events = EPOLLOUT; goto config_epoll; wait_read: epoll_events = EPOLLIN; goto config_epoll; #undef SEND #undef RECV #undef CONT #undef CHECKED } static void peer_connection_done(union file *this, uint32_t epoll_events) { int err = 0; socklen_t len = (socklen_t)sizeof(err); if (getsockopt(this->fd, SOL_SOCKET, SO_ERROR, &err, &len)) eprintf("getsockopt SOL_SOCKET SO_ERROR"); if (len > (socklen_t)sizeof(err)) abort(); if (err && err != EINTR) { weprintf("connect(async) %s:%u\n", inet_ntoa(this->peer.addr.sin_addr), ntohs(this->peer.addr.sin_port)); remove_peer(&this->peer); return; } this->on_event = &peer_handshake; peer_handshake(this, epoll_events); } static void on_network_socket_event(union file *this, uint32_t epoll_events) { struct sockaddr_in addr; socklen_t addrlen = (socklen_t)sizeof(addr); struct peer_file *file; int fd; (void) epoll_events; retry: fd = accept4(this->fd, (void *)&addr, &addrlen, SOCK_NONBLOCK); if (fd < 0) { switch (errno) { case EINTR: goto retry; case ECONNABORTED: case ECONNRESET: case EPERM: return; default: eprintf("accept4 SOCK_NONBLOCK:"); } } if (addrlen > (socklen_t)sizeof(addr)) { weprintf("TCP socket peer name overlong\n"); close(fd); return; } file = add_peer(fd, 0u, &addr, HANDSHAKE_B_INIT, &peer_handshake); peer_handshake((void *)file, EPOLLOUT); } static void on_broadcast_socket_event(union file *this, uint32_t epoll_events) { struct broadcast_message msg; struct sockaddr_in addr; socklen_t addrlen = (socklen_t)sizeof(addr); ssize_t r; int fd, in_progress; struct peer_file *file; size_t i; (void) epoll_events; retry_recv: r = recvfrom(this->fd, &msg, sizeof(msg), MSG_TRUNC, &addr, &addrlen); if (r < 0) { if (errno == EINTR) goto retry_recv; if (errno == ECONNREFUSED) return; eprintf("recvfrom :"); } if (addrlen > (socklen_t)sizeof(addr)) { weprintf("TCP socket peer name overlong\n"); return; } if (r != BROADCAST_MESSAGE_SIZE || msg.id == server_identity.id) return; for (i = 0u; i < npeers; i++) if (msg.id == peers[i]->id) return; fd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); if (fd < 0) { weprintf("socket PF_INET SOCK_STREAM|SOCK_NONBLOCK 0:"); return; } addr.sin_port = msg.port; retry_connect: if (!connect(fd, (void *)&addr, addrlen)) { in_progress = 0; } else if (errno == EINPROGRESS) { in_progress = 1; } else if (errno == EINTR) { goto retry_connect; } else { weprintf("connect %s:%u\n", inet_ntoa(addr.sin_addr), ntohs(msg.port)); if (errno != ENETUNREACH && errno != ETIMEDOUT && errno != ECONNREFUSED) exit(1); close(fd); return; } file = add_peer(fd, msg.id, &addr, HANDSHAKE_A_INIT, in_progress ? &peer_connection_done : &peer_handshake); if (!in_progress) peer_handshake((void *)file, EPOLLOUT); } static void broadcast_identity(struct broadcast_file *file) { long int rnd; struct itimerspec its; if (sendto(file->fd, &server_identity, BROADCAST_MESSAGE_SIZE, 0, SOCKADDR(file->addr)) != BROADCAST_MESSAGE_SIZE) weprintf("sendto :"); file->alarm_time.tv_sec += file->timer_inc; file->timer_inc *= 2; rnd = rand() & 255; rnd *= 1000000l; file->alarm_time.tv_nsec += rnd; if (file->alarm_time.tv_nsec >= 1000000000l) { file->alarm_time.tv_nsec -= 1000000000l; file->alarm_time.tv_sec += 1; } memset(&its, 0, sizeof(its)); its.it_value = file->alarm_time; if (timerfd_settime(file->timer.fd, 0, &its, NULL)) weprintf("timerfd_settime 0 NULL:"); } static void on_broadcast_timer_event(union file *this, uint32_t epoll_events) { (void) epoll_events; broadcast_identity(&this->timer.owner->broadcast); } static void on_local_socket_event(union file *this, uint32_t epoll_events) { struct ucred cred; socklen_t len = (socklen_t)sizeof(cred); int fd; struct client_file *file; struct epoll_event ev; (void) epoll_events; retry: fd = accept4(this->fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (fd < 0) { switch (errno) { case EINTR: goto retry; case ECONNABORTED: case ECONNRESET: case EPERM: return; default: eprintf("accept4 NULL NULL SOCK_NONBLOCK|SOCK_CLOEXEC:"); } } if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len)) { weprintf("getsockopt SOL_SOCKET SO_PEERCRED:"); close(fd); return; } if (len != (socklen_t)sizeof(cred)) abort(); if (cred.uid != uid) { /* TODO maybe it should be possible to open up realms to specific/all * users so they don't need a separate instance running */ close(fd); return; } file = emalloc(sizeof(*file)); file->type = CLIENT_FILE; file->fd = fd; file->on_event = &on_client_event; file->next = &clients_tail; file->prev = clients_tail.prev; clients_tail.prev->next = file; clients_tail.prev = file; memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.ptr = file; if (epoll_ctl(epoll, EPOLL_CTL_ADD, file->fd, &ev)) eprintf("epoll_ctl EPOLL_CTL_ADD {EPOLLIN}:"); } static struct network_file * create_network_socket(void) { struct sockaddr_in addr; socklen_t addrlen = (socklen_t)sizeof(addr); struct network_file *file; struct epoll_event ev; file = emalloc(sizeof(*file)); file->type = NETWORK_FILE; file->on_event = &on_network_socket_event; file->fd = socket(PF_INET, SOCK_STREAM, 0); if (file->fd < 0) eprintf("socket PF_INET SOCK_STREAM 0:"); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(file->fd, (const void *)&addr, addrlen)) eprintf("bind &{AF_INET, 0.0.0.0:0}:"); if (getsockname(file->fd, &addr, &addrlen)) eprintf("getsockname :"); if (addrlen < (socklen_t)(offsetof(struct sockaddr_in, sin_port) + sizeof(addr.sin_port))) abort(); server_identity.port = addr.sin_port; if (listen(file->fd, SOMAXCONN)) eprintf("listen SOMAXCONN:"); memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.ptr = file; if (epoll_ctl(epoll, EPOLL_CTL_ADD, file->fd, &ev)) eprintf("epoll_ctl EPOLL_CTL_ADD {EPOLLIN}:"); return file; } static struct broadcast_file * create_broadcast_socket(uint16_t port) { struct broadcast_file *file; struct epoll_event ev; file = emalloc(sizeof(*file)); memset(file, 0, sizeof(*file)); file->type = BROADCAST_FILE; file->on_event = &on_broadcast_socket_event; file->timer_inc = 1; file->timer.type = TIMER_FILE; file->timer.owner = (void *)file; file->timer.on_event = &on_broadcast_timer_event; file->fd = socket(PF_INET, SOCK_DGRAM, 0); if (file->fd < 0) eprintf("socket PF_INET SOCK_DGRAM 0:"); /* TODO switch to multicast (for portability) */ if (setsockopt(file->fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, (socklen_t)sizeof(int))) eprintf("setsockopt SOL_SOCKET SO_REUSEADDR &{1}:"); if (setsockopt(file->fd, SOL_SOCKET, SO_BROADCAST, &(int){1}, (socklen_t)sizeof(int))) eprintf("setsockopt SOL_SOCKET SO_BROADCAST &{1}:"); memset(&file->addr, 0, sizeof(file->addr)); file->addr.sin_family = AF_INET; file->addr.sin_port = htons(port); file->addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(file->fd, SOCKADDR(file->addr))) eprintf("bind &{AF_INET, 0.0.0.0:%i}:", port); file->addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); file->timer.fd = timerfd_create(CLOCK_BOOTTIME, 0); if (file->timer.fd < 0) eprintf("timerfd_create CLOCK_BOOTTIME 0:"); memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.ptr = file; if (epoll_ctl(epoll, EPOLL_CTL_ADD, file->fd, &ev)) eprintf("epoll_ctl EPOLL_CTL_ADD {EPOLLIN}:"); ev.data.ptr = &file->timer; if (epoll_ctl(epoll, EPOLL_CTL_ADD, file->timer.fd, &ev)) eprintf("epoll_ctl EPOLL_CTL_ADD {EPOLLIN}:"); return file; } static struct local_file * create_local_socket(const char *address) { struct sockaddr_un addr; socklen_t addrlen = (socklen_t)sizeof(addr); struct local_file *file; struct epoll_event ev; size_t len; int print_sockname = 0; len = strlen(address); if (len > sizeof(addr.sun_path)) eprintf("local address overlong: %s", address); file = emalloc(sizeof(*file)); file->type = LOCAL_FILE; file->on_event = &on_local_socket_event; file->fd = socket(PF_LOCAL, SOCK_SEQPACKET, 0); if (file->fd < 0) eprintf("socket PF_LOCAL SOCK_SEQPACKET 0:"); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; memcpy(addr.sun_path, address, len); if (!len || addr.sun_path[0u] == '@') { addr.sun_path[0u] = '\0'; if (len <= 1u) { for (len = 1u; len < sizeof(addr.sun_path); len++) addr.sun_path[len] = ALPHA64[rand() & 63]; print_sockname = 1; } else { addrlen = (socklen_t)(offsetof(struct sockaddr_un, sun_path) + len); } } if (bind(file->fd, (const void *)&addr, addrlen)) eprintf("bind &{AF_LOCAL, %c%.*s}:", addr.sun_path[0u] ? addr.sun_path[0u] : '@', (int)len - 1, &addr.sun_path[1u]); if (listen(file->fd, SOMAXCONN)) eprintf("listen SOMAXCONN:"); memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.ptr = file; if (epoll_ctl(epoll, EPOLL_CTL_ADD, file->fd, &ev)) eprintf("epoll_ctl EPOLL_CTL_ADD {EPOLLIN}:"); if (print_sockname) { if (printf("@%.*s\n", (int)len - 1, &addr.sun_path[1u]) < 0 || fflush(stdout)) eprintf("printf:"); } return file; } static void init_process(void) { struct timespec ts; unsigned long int auxval; unsigned seed = 0; size_t i; int fd; do { fd = open("/dev/null", O_RDWR); if (fd < 0) eprintf("open /dev/null O_RDWR:"); if (fd > STDERR_FILENO) close(fd); } while (fd < STDERR_FILENO); uid = getuid(); libblake_init(); clients_head.prev = NULL; clients_head.next = &clients_tail; clients_tail.prev = &clients_head; clients_tail.next = NULL; auxval = getauxval(AT_RANDOM); if (auxval) { unsigned char *r = (unsigned char *)(uintptr_t)auxval; for (i = 0u; i < 16u; i++) seed = seed * 131u + r[i]; } if (!clock_gettime(CLOCK_REALTIME, &ts)) { seed ^= (unsigned)ts.tv_sec; seed ^= (unsigned)ts.tv_nsec; } if (!clock_gettime(CLOCK_MONOTONIC, &ts)) { seed ^= (unsigned)ts.tv_sec * (unsigned)2654435761lu; seed ^= (unsigned)ts.tv_nsec; } srand(seed); } static void load_config(const char *config_file, char **local_address, uint16_t *udp_port) { FILE *f; ssize_t r; char *key, *value; char *line = NULL; size_t size = 0u; size_t len, off, i; int fd, old_fd; int udp_set = 0; long int li; unsigned long int lu; char *end, *start; const char *env; char *config_file_free = NULL; size_t config_file_size = 0u; if (!config_file) { #define SET_PREFIX(PREFIX) SET_PREFIXN(PREFIX, strlen(PREFIX)) #define SET_PREFIXN(PREFIX, LEN)\ do {\ if (config_file_size < (LEN) + 32u) {\ config_file_size = (LEN) + 32u;\ config_file_free = erealloc(config_file_free, config_file_size);\ }\ start = stpcpy(config_file_free, (PREFIX));\ } while (0) #define TEST_CONFIG(PATH)\ do {\ if (!access(PATH, F_OK)) {\ config_file = (PATH);\ goto open_file;\ }\ } while (0) env = getenv("OIKOBUS_CONFIG"); if (env && *env) { config_file = env; goto open_file; } env = getenv("XDG_CONFIG_HOME"); if (env && *env) { SET_PREFIX(env); stpcpy(start, "/oikobus/config"); TEST_CONFIG(config_file_free); stpcpy(start, "/oikobus.conf"); TEST_CONFIG(config_file_free); } env = getenv("HOME"); if (env && *env) { SET_PREFIX(env); } else { struct passwd *pw; retry_getpwuid: pw = getpwuid(uid); if (!pw) { if (errno == EINTR) goto retry_getpwuid; eprintf("getpwuid :"); } if (!pw->pw_dir || !*pw->pw_dir) goto no_home; SET_PREFIX(pw->pw_dir); } stpcpy(start, "/.config/oikobus/config"); TEST_CONFIG(config_file_free); stpcpy(start, "/.config/oikobus.conf"); TEST_CONFIG(config_file_free); stpcpy(start, "/.oikobus/config"); TEST_CONFIG(config_file_free); stpcpy(start, "/.oikobus.conf"); TEST_CONFIG(config_file_free); no_home: env = getenv("XDG_CONFIG_DIRS"); if (env && *env) { for (off = 0u; env[off]; off = i) { for (i = off; env[i] && env[i] != ':'; i++); len = i - off; if (env[i]) i++; if (!len) continue; SET_PREFIXN(&env[off], len); stpcpy(start, "/oikobus/config"); TEST_CONFIG(config_file_free); stpcpy(start, "/oikobus.conf"); TEST_CONFIG(config_file_free); } } TEST_CONFIG("/etc/oikobus/config"); TEST_CONFIG("/etc/oikobus.conf"); eprintf("no configuration file found, use -c/dev/null if you don't want to load one"); #undef SET_PREFIXN #undef SET_PREFIX #undef TEST_CONFIG } else { if (!strcmp(config_file, "-")) { config_file = "/dev/stdin"; fd = STDIN_FILENO; goto open_fd; } else if (!strcmp(config_file, "/dev/stdin")) { fd = STDIN_FILENO; goto open_fd; } else if (!strcmp(config_file, "/dev/stdout")) { fd = STDOUT_FILENO; goto open_fd; } else if (!strcmp(config_file, "/dev/stderr")) { fd = STDERR_FILENO; goto open_fd; } if (strstarts(config_file, "/dev/fd/")) { off = sizeof("/dev/fd/") - 1u; } else if (strstarts(config_file, "/proc/self/fd/")) { off = sizeof("/proc/self/fd/") - 1u; } else { goto open_file; } if ('0' > config_file[off] || config_file[off] > '9') goto open_file; errno = 0; li = strtol(&config_file[off], &end, 10); if (errno || li < 0 || li > INT_MAX || *end) goto open_file; fd = (int)li; if (fd <= STDERR_FILENO) { open_fd: fd = dup(old_fd = fd); if (fd < 0) eprintf("dup %i:", old_fd); } f = fdopen(fd, "r"); goto file_opened; } open_file: f = fopen(config_file, "r"); file_opened: if (!f) eprintf("%s:", config_file); while ((r = getdelim(&line, &size, '\n', f)) != -1) { len = (size_t)r; if (len && line[len - 1u] == '\n') line[--len] = '\0'; if (len && line[len - 1u] == '\r') line[--len] = '\0'; off = 0u; while (isspace(line[off])) off++; key = &line[off]; value = NULL; for (i = off; line[i]; i++) { if (line[i] == '#' || line[i] == ';') { if (i == off || isspace(line[i - 1u])) { line[i] = '\0'; len = i; break; } } else if ((line[i] == ':' || line[i] == '=') && !value) { value = &line[i]; } } while (len && isspace(line[len - 1u])) line[--len] = '\0'; if (value) { i = (size_t)(value - key); *value++ = '\0'; while (isspace(*value)) value++; while (i-- && isspace(line[i])) line[i] = '\0'; } if (nrealms) { if (!strcmp(key, "password")) { if (!value) goto value_missing; if (realms[nrealms - 1u].key) goto duplicate_realm_option; realms[nrealms - 1u].key = estrdup(value); } else { eprintf("%s: realm option '%s' not recognised", config_file, key); } } else if (!value && key[0] == '[' && (end = strchr(key, '\0'))[-1] == ']') { key++; *--end = '\0'; len = (size_t)(end - key); if (len < MIN_REALM_LEN || len > MAX_REALM_LEN) eprintf("%s: invalid realm name length: '%s', must " "be between %u and %u bytes (inclusively)", config_file, key, MIN_REALM_LEN, MAX_REALM_LEN); for (i = 0u; i < nrealms; i++) if (!strcmp(key, realms[i].name)) eprintf("%s: duplicate section for realm '%s'", config_file, key); realms = ereallocarray(realms, ++nrealms, sizeof(*realms)); realms[nrealms - 1u].name = estrdup(key); realms[nrealms - 1u].key = NULL; } else { if (!strcmp(key, "udp-port")) { if (!value) goto value_missing; if (udp_set++) goto duplicate_global_option; errno = 0; lu = strtoul(value, &end, 10); if (errno || lu == 0u || lu > UINT16_MAX || *end) eprintf("%s: global option '%s' not recognised requires a " "non-zero, unsigned 16-bit integer", config_file, key); *udp_port = (uint16_t)lu; } else if (!strcmp(key, "unix-address")) { if (!value) goto value_missing; if (*local_address) goto duplicate_global_option; *local_address = estrdup(value); } else { eprintf("%s: global option '%s' not recognised", config_file, key); } } } if (ferror(f) || fclose(f)) eprintf("getline %s:", config_file); free(line); free(config_file_free); return; duplicate_realm_option: eprintf("%s: realm option '%s' configured twice for realm '%s'", config_file, key, realms[nrealms - 1u].name); duplicate_global_option: eprintf("%s: global option '%s' configured twice", config_file, key); value_missing: eprintf("%s: option '%s' requires a value", config_file, key); } static void init_server(const char *config_file, const char *local_address_cmdline, uint16_t udp_port_cmdline) { uint16_t udp_port = UDP_PORT; const char *local_address = NULL; char *local_address_free = NULL; char local_address_buf[LOCAL_ADDRESS_BUFSIZE]; size_t i; load_config(config_file, &local_address_free, &udp_port); local_address = local_address_free; if (udp_port_cmdline) udp_port = udp_port_cmdline; if (local_address_cmdline) local_address = local_address_cmdline; if (!local_address) { sprintf(local_address_buf, LOCAL_ADDRESS(getuid())); local_address = local_address_buf; } for (i = 0u; i < sizeof(server_identity.id); i++) ((unsigned char *)&server_identity.id)[i] = (unsigned char)(rand() & 255); events_size = 32; events = ecalloc((size_t)events_size, sizeof(*events)); epoll = epoll_create1(0); if (epoll < 0) eprintf("create_epoll1 0:"); lsock = create_local_socket(local_address); nsock = create_network_socket(); bsock = create_broadcast_socket(udp_port); free(local_address_free); } static void signal_inited(void) { if (dup2(STDERR_FILENO, STDIN_FILENO) != STDIN_FILENO) eprintf("dup2 STDERR_FILENO STDIN_FILENO:"); if (dup2(STDERR_FILENO, STDOUT_FILENO) != STDOUT_FILENO) eprintf("dup2 STDERR_FILENO STDOUT_FILENO:"); } int main(int argc, char *argv[]) { uint16_t udp_port_cmdline = 0u; const char *config_file = NULL; const char *local_address_cmdline = NULL; char *arg; unsigned long int lu; union file *file; int n; ARGBEGIN { /* TODO add daemonisation options */ /* TODO add realm options */ case 'a': if (local_address_cmdline) usage(); local_address_cmdline = ARG(); if (!*local_address_cmdline) usage(); break; case 'c': if (config_file) usage(); config_file = ARG(); if (!*config_file) usage(); break; case 'p': arg = ARG(); if ('0' > arg[0u] || arg[0u] > '9' || udp_port_cmdline) usage(); errno = 0; lu = strtoul(arg, &arg, 10); if (errno || *arg || lu > UINT16_MAX || !lu) usage(); udp_port_cmdline = (uint16_t)lu; break; default: usage(); } ARGEND; /* -W is reserved for vendor options */ if (argc) usage(); init_process(); init_server(config_file, local_address_cmdline, udp_port_cmdline); broadcast_identity(bsock); signal_inited(); for (;;) { n = epoll_wait(epoll, events, events_size, -1); if (n < 0) { if (errno == EINTR) continue; eprintf("epoll_wait %i -1:", events_size); } while (n--) { file = events[n].data.ptr; (*file->on_event)(file, events[n].events); } } }