zeroconf/testmdnsd.c

425 lines
11 KiB
C

/*
* tinysvcmdns - a tiny MDNS implementation for publishing services
* Copyright (C) 2011 Darell Tan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#ifdef _WIN32
#include <winsock2.h>
#include <in6addr.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#endif
#include "mdns.h"
#include "mdnsd.h"
#define HOSTFILE "/tmp/hosts"
#if 0
#include <sys/stat.h>
#include <fcntl.h>
void
background()
{
int fd;
switch ((int)fork())
{
case -1:
perror("fork");
exit(1);
case 0:
break;
default:
exit(0);
}
#ifdef TIOCNOTTY
if (ioctl(fileno(stdin), TIOCNOTTY) == -1)
perror("detach_console");
#endif
freopen("/dev/null", "r", stdin);
fd = open("/dev/null", O_WRONLY, 0666);
dup2(fd, fileno(stdout));
dup2(fd, fileno(stderr));
close(fd);
setsid();
}
#endif
static void freehostents( struct hostent * h) {
for (struct hostent *p = h; p->h_name; ++p) {
free(p->h_name);
free(p->h_addr_list);
}
free(h);
}
/*
* Add the mapping from hostname to in_addr_ip to the hosts block.
* The return value is the pointer to the next entry after the added entry.
* Case 1. The block is initially empty. Populate it with a loop like
* for (p = hosts,i=0;; p = add_host(p, hostname[i], addr[i],++i);
* Case 2. The block is in use. The added entry replaces the first one with
* h_length == 0.
*/
static struct hostent * add_host(struct hostent * hosts, const char * hostname, struct in_addr ip) {
struct hostent * p;
int newname = 1;
for (p=hosts;p->h_name;++p) {
if (p->h_length == 0 ||
0 == (newname = strcasecmp(p->h_name, hostname)))
break;
}
if (p->h_name) {
if (0 != newname) {
/* reusing an entry with a new name */
free(p->h_name);
p->h_name = strdup(hostname);
}
} else {
/* new entry */
p->h_name = strdup(hostname);
(p+1)->h_name = 0;
}
p->h_aliases = 0;
p->h_addrtype = AF_INET;
p->h_length = sizeof(in_addr_t);
p->h_addr_list = (char **) realloc(p->h_addr_list, sizeof(struct in_addr)+2*sizeof(struct in_addr*));
p->h_addr_list[0] = (char *)(p->h_addr_list + 2);
*(struct in_addr *)(p->h_addr_list[0]) = ip;
p->h_addr_list[1] = NULL;
return ++p;
}
struct hostent * realloc_hosts(struct hostent * p, size_t n) {
struct hostent * new = (struct hostent *) realloc(p,n*sizeof(struct hostent));
if (!new) {
fprintf(stderr, "Failed to allocate hostent: %s\n", strerror(errno));
}
return new;
}
struct hostent * read_hosts(unsigned int extra) {
FILE * hosts = fopen( HOSTFILE, "r");
if (!hosts) {
fprintf(stderr, "Failed to open hosts file: %s\n", strerror(errno));
return NULL;
}
size_t nhost = extra;
while (0 <= fscanf(hosts, "%*[^\n]") && !ferror(hosts)) {
if (0 <= fscanf(hosts,"%*c"))
++nhost;
}
if (ferror(hosts) || -1 == fseek(hosts, 0L, SEEK_SET)) {
fprintf(stderr, "Failed to read hosts file: %s\n", strerror(errno));
fclose(hosts);
return NULL;
}
struct hostent * hostents = (struct hostent *) realloc_hosts(NULL,++nhost);
if (!hostents) {
fclose(hosts);
return NULL;
} else {
memset(hostents,0,nhost*sizeof(struct hostent));
}
char ip[256];
char name[256];
char fmt[32];
/* a format for [the beginning of] a /etc/hosts line */
int n = 0;
if (snprintf( fmt, sizeof fmt - 1, "%%%zus %%%zus%%n%n", sizeof ip - 1, sizeof name - 1, &n) != n) {
fprintf(stderr, "Format length %d inadequate\n", n);
}
/* and for further hostnames */
char * nfmt = strchr(fmt,' ')+1;
char * line = NULL;
size_t linesz = 0;
size_t nleft = nhost;
hostents->h_name = 0;
for (struct hostent * p = hostents;;) {
int ret = getline( &line, &linesz, hosts);
int pos = 0;
if (ret == -1) {
if (!feof(hosts)) {
fprintf(stderr, "Failure reading hosts file: %s\n", strerror(errno));
freehostents(hostents);
hostents = NULL;
}
break;
}
/* skip initial space and any comment line */
ret = sscanf( line, " %n", &pos );
if (0 <= ret) {
int n = 0;
ret = sscanf( line+pos, "#%*[^\n]%n", &n);
if (n > 0) continue;
}
if (0 <= ret) {
int n = 0;
ret = sscanf( line+pos, fmt, ip, name, &n);
// printf( "Read: %.255s\t%.32s\t%d\n", name, ip, ret);
pos += n;
}
if (ret == 2) {
struct in_addr it;
it.s_addr = inet_addr(ip);
do {
char * dot = strrchr(name, '.');
if (dot && 0 == strcasecmp(dot, ".local")) {
struct hostent * pp = add_host(p, name, it);
/* flag as expendable */
p->h_length = 0;
p = pp;
} else {
/* try to push down non-local names */
struct hostent * pp = add_host(hostents, name, it);
/* not pushed down -> appended */
if (pp > p) p = pp;
}
/* running out of space? try for more */
if (--nleft < extra + 1) {
size_t n = 2*nhost;
struct hostent * new = (struct hostent *) realloc_hosts(hostents,n);
if (!new) {
free(line);
fclose(hosts);
free(hostents);
return NULL;
}
/* zero the new space and adjust counts and pointers */
memset(new+nhost,0,(n-nhost)*sizeof(struct hostent));
nleft += n - nhost;
nhost = n;
p += new - hostents;
hostents = new;
}
/* allow for ip name [name ...] */
int n = 0;
ret = sscanf( line+pos, " %n", &n);
pos += n;
/* skip comment */
if (0 <= ret) {
n = 0;
ret = sscanf( line+pos, "#%*[^\n]%n", &n);
if (n > 0) break;
}
if (0 <= ret) {
n = 0;
ret = sscanf( line+pos, nfmt, name, &n);
pos += n;
}
} while (ret == 1);
p->h_name = 0;
} else if (ret >= 0) {
fprintf(stderr, "Unexpected format in hosts file; line was\n%s\n", line);
}
}
free(line);
fclose(hosts);
return hostents;
}
static int write_hosts(const struct hostent * hostents) {
int ret = -1;
FILE * hosts = fopen( HOSTFILE, "w");
if (!hosts) {
fprintf(stderr, "Failed to open hosts file: %s\n", strerror(errno));
return ret;
}
for (const struct hostent * p = hostents;p->h_name;++p) {
/* printf( "writing %.255s\n", p->h_name); */
if (p->h_length > 0) {
for (struct in_addr ** pa = (struct in_addr **)(p->h_addr_list); *pa; ++pa) {
ret = fprintf( hosts, "%s\t%s\n", inet_ntoa(**pa), p->h_name);
}
}
}
fclose(hosts);
return ret;
}
#if 0
static void dump_host(const struct hostent * hostent) {
printf( "Host: %.255s\t%.32s\t%d\n", hostent->h_name,
inet_ntoa(*(struct in_addr *)(hostent->h_addr_list[0])),
hostent->h_length);
}
static void dump_hosts(const struct hostent * hostents) {
for (const struct hostent * p = hostents;p->h_name;++p) {
dump_host(p);
}
}
#endif
static void handler_stop(int sig) {
(void)sig;
mdnsd_stop();
#ifdef WIN32
winsock_close();
#endif
exit(0);
}
volatile static int running;
static void handler_restart(int sig) {
/* a dummy handler so that sleep() will end early */
(void)sig;
if (running != 0)
running = 2;
}
int
main(int argc, char **argv)
{
char hostname[0x100], ifname[0x120], fullname[0x108];
struct in_addr saddr;
if (gethostname(hostname, sizeof(hostname) - 1) == -1)
{
fprintf(stderr, "Couldn't retrieve humax hostname.\n");
return 1;
}
int n = 0;
if (snprintf(fullname, sizeof fullname - 1, "%s.local%n", hostname, &n) != n ||
(n = 0, sprintf(ifname, "Humax Fox T2 (%s) Web Interface%n", hostname, &n) != n)) {
fprintf( stderr, "Format length %d inadeqate\n", n);
}
const char *txt[] = {
"path=/",
NULL
};
running = 0;
signal(SIGINT, handler_stop);
signal(SIGTERM, handler_stop);
#if defined(SIGPIPE)
signal(SIGPIPE, SIG_IGN);
#endif
#if defined(SIGQUIT)
signal(SIGQUIT, handler_stop);
#endif
#if defined(SIGHUP)
signal(SIGHUP, handler_restart);
#endif
for (saddr.s_addr = 0;;sleep(61)) {
struct hostent *hp = gethostbyname(hostname);
char * ip_addr;
if (!hp || hp->h_length != sizeof(saddr.s_addr))
{
fprintf(stderr, "Couldn't retrieve humax IP address.\n");
return 1;
}
if (running != 1 || saddr.s_addr != *(in_addr_t *)(hp->h_addr_list[0])) {
if (running && (0 > mdnsd_stop())) {
fprintf(stderr, "mdnsd_stop() error\n");
return 1;
}
saddr.s_addr = *(in_addr_t *)(hp->h_addr_list[0]);
ip_addr = inet_ntoa(saddr);
printf("Hostname: %s (%s)\n", hostname, ip_addr);
if (mdnsd_start(fullname, ip_addr) < 0) {
fprintf(stderr, "mdnsd_start() error\n");
/* may just have no interface */
continue;
}
running = 1;
/* service must have FQDN */
if (0 <= mdnsd_register_service(
ifname, "_http._tcp.local", 80,
fullname, txt))
printf("Registered name.\n");
}
int numserv = 0;
struct mdns_service_info * svcinfo;
if (0 <= mdnsd_discover_service("_http._tcp.local", 5000, &svcinfo, &numserv)) {
/* allow an extra entry for own host */
struct hostent * hosts = read_hosts(numserv+1);
if (!hosts) {
fprintf(stderr,"null hosts\n");
return 1;
}
//dump_hosts(hosts);
/* recognise own .local hostname */
struct hostent * p = add_host(hosts, fullname, saddr);
for (int i = 0; i < numserv; ++i) {
struct in_addr it;
it.s_addr = svcinfo[i].ipaddr;
printf("Host %d: %s (%s)\n", i, svcinfo[i].hostname, inet_ntoa(it));
p = add_host(p, svcinfo[i].hostname, it);
}
/* p->h_name = 0; */
write_hosts(hosts);
freehostents(hosts);
}
}
if (running && (0 > mdnsd_stop())) {
fprintf(stderr,"mdnsd_stop() error\n");
}
return 0;
}