#include <sys/cdefs.h>
__RCSID("$NetBSD: server.c,v 1.9 2021/10/29 11:40:23 nia Exp $");
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <ifaddrs.h>
#include "hunt_common.h"
#include "pathnames.h"
#include "hunt_private.h"
#ifdef INTERNET
static struct sockaddr_in *daemons;
static unsigned int numdaemons, maxdaemons;
static struct sockaddr_in *broadcastaddrs;
static int numbroadcastaddrs;
static bool initial = true;
static struct in_addr local_address;
static const char *explicit_host;
static uint16_t port;
void
serverlist_setup(const char *explicit_host_arg, uint16_t port_arg)
{
char local_name[MAXHOSTNAMELEN + 1];
struct hostent *hp;
if (gethostname(local_name, sizeof(local_name)) < 0) {
leavex(1, "Sorry, I have no hostname.");
}
local_name[sizeof(local_name) - 1] = '\0';
if ((hp = gethostbyname(local_name)) == NULL) {
leavex(1, "Can't find myself.");
}
memcpy(&local_address, hp->h_addr, sizeof(local_address));
numdaemons = 0;
maxdaemons = 20;
daemons = NULL;
if (reallocarr(&daemons, maxdaemons, sizeof(daemons[0])) != 0)
leavex(1, "Out of memory.");
if (explicit_host_arg) {
explicit_host = explicit_host_arg;
}
port = port_arg;
}
static void
add_daemon_addr(const struct sockaddr_storage *addr, socklen_t addrlen,
uint16_t port_num)
{
const struct sockaddr_in *sin;
if (addr->ss_family != AF_INET) {
return;
}
assert(addrlen == sizeof(struct sockaddr_in));
sin = (const struct sockaddr_in *)addr;
assert(numdaemons <= maxdaemons);
if (numdaemons == maxdaemons) {
maxdaemons += 20;
if (reallocarr(&daemons, maxdaemons, sizeof(daemons[0])) != 0)
leave(1, "realloc");
}
daemons[numdaemons] = *sin;
daemons[numdaemons].sin_port = port_num;
numdaemons++;
}
static bool
have_daemon_addr(const struct sockaddr_storage *addr, socklen_t addrlen)
{
unsigned j;
const struct sockaddr_in *sin;
if (addr->ss_family != AF_INET) {
return false;
}
assert(addrlen == sizeof(struct sockaddr_in));
sin = (const struct sockaddr_in *)addr;
for (j = 0; j < numdaemons; j++) {
if (sin->sin_addr.s_addr == daemons[j].sin_addr.s_addr) {
return true;
}
}
return false;
}
static void
getbroadcastaddrs(void)
{
unsigned num, i;
struct ifaddrs *ifp, *ip;
broadcastaddrs = NULL;
numbroadcastaddrs = 0;
if (getifaddrs(&ifp) < 0) {
return;
}
num = 0;
for (ip = ifp; ip; ip = ip->ifa_next) {
if ((ip->ifa_addr->sa_family == AF_INET) &&
(ip->ifa_flags & IFF_BROADCAST)) {
num++;
}
}
broadcastaddrs = NULL;
if (reallocarr(&broadcastaddrs, num, sizeof(broadcastaddrs[0])) != 0)
leavex(1, "Out of memory");
i = 0;
for (ip = ifp; ip; ip = ip->ifa_next) {
if ((ip->ifa_addr->sa_family == AF_INET) &&
(ip->ifa_flags & IFF_BROADCAST)) {
memcpy(&broadcastaddrs[i], ip->ifa_broadaddr,
sizeof(broadcastaddrs[i]));
i++;
}
}
assert(i == num);
numbroadcastaddrs = num;
freeifaddrs(ifp);
}
static void
send_messages(int contactsock, unsigned short msg)
{
struct sockaddr_in contactaddr;
struct hostent *hp;
uint16_t wiremsg;
int option;
int i;
memset(&contactaddr, 0, sizeof(contactaddr));
contactaddr.sin_family = SOCK_FAMILY;
contactaddr.sin_port = htons(port);
if (explicit_host != NULL) {
hp = gethostbyname(explicit_host);
if (hp == NULL) {
leavex(1, "%s: Unknown host", explicit_host);
}
memcpy(&contactaddr.sin_addr, hp->h_addr,
sizeof(contactaddr.sin_addr));
wiremsg = htons(msg);
(void) sendto(contactsock, &wiremsg, sizeof(wiremsg), 0,
(struct sockaddr *)&contactaddr,
sizeof(contactaddr));
return;
}
if (!initial) {
contactaddr.sin_addr = Daemon.sin_addr;
assert(msg == C_PLAYER);
wiremsg = htons(msg);
(void) sendto(contactsock, &wiremsg, sizeof(wiremsg), 0,
(struct sockaddr *)&contactaddr, sizeof(contactaddr));
}
if (initial) {
getbroadcastaddrs();
}
#ifdef SO_BROADCAST
option = 1;
if (setsockopt(contactsock, SOL_SOCKET, SO_BROADCAST,
&option, sizeof option) < 0) {
leave(1, "setsockopt broadcast");
}
#endif
wiremsg = htons(msg);
for (i = 0; i < numbroadcastaddrs; i++) {
contactaddr.sin_addr = broadcastaddrs[i].sin_addr;
if (sendto(contactsock, &wiremsg, sizeof(wiremsg), 0,
(struct sockaddr *)&contactaddr,
sizeof(contactaddr)) < 0) {
leave(1, "sendto");
}
}
contactaddr.sin_addr = local_address;
if (sendto(contactsock, &wiremsg, sizeof(wiremsg), 0,
(struct sockaddr *)&contactaddr, sizeof(contactaddr)) < 0) {
leave(1, "sendto");
}
}
static void
get_responses(int contactsock)
{
struct pollfd set[1];
struct sockaddr_storage addr;
socklen_t addrlen;
int r;
uint16_t port_num;
ssize_t portlen;
numdaemons = 0;
errno = 0;
set[0].fd = contactsock;
set[0].events = POLLIN;
for (;;) {
r = poll(set, 1, 1000);
if (r < 0) {
if (errno == EINTR) {
continue;
}
leave(1, "poll");
}
if (r == 0) {
break;
}
addrlen = sizeof(addr);
portlen = recvfrom(contactsock, &port_num, sizeof(port_num), 0,
(struct sockaddr *)&addr, &addrlen);
if (portlen < 0) {
if (errno == EINTR) {
continue;
}
leave(1, "recvfrom");
}
if (portlen == 0) {
leavex(1, "recvfrom: Unexpected EOF");
}
if ((size_t)portlen != sizeof(port_num)) {
continue;
}
if (have_daemon_addr(&addr, addrlen)) {
continue;
}
add_daemon_addr(&addr, addrlen, port_num);
}
initial = false;
}
void
serverlist_query(unsigned short msg)
{
int contactsock;
if (!initial && explicit_host != NULL) {
return;
}
contactsock = socket(SOCK_FAMILY, SOCK_DGRAM, 0);
if (contactsock < 0) {
leave(1, "socket system call failed");
}
send_messages(contactsock, msg);
get_responses(contactsock);
(void) close(contactsock);
}
unsigned
serverlist_num(void)
{
return numdaemons;
}
const struct sockaddr_storage *
serverlist_gethost(unsigned i, socklen_t *len_ret)
{
struct sockaddr_in *ret;
assert(i < numdaemons);
ret = &daemons[i];
*len_ret = sizeof(*ret);
return (struct sockaddr_storage *)ret;
}
unsigned short
serverlist_getresponse(unsigned i)
{
assert(i < numdaemons);
return daemons[i].sin_port;
}
#endif