/*
 * listener.c:
 * Objects representing addresses on which to listen.
 *
 * Copyright (c) 2001 Chris Lightfoot. All rights reserved.
 *
 */

static const char rcsid[] = "$Id: listener.c,v 1.18 2003/11/05 12:36:45 chris Exp $";

#ifdef HAVE_CONFIG_H
#include "configuration.h"
#endif /* HAVE_CONFIG_H */

#include <fcntl.h>
#include <netdb.h>

#ifdef MASS_HOSTING
#include <regex.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>

#include "listener.h"

#ifdef USE_TLS
#include "tls.h"
#endif

#include "util.h"

/* listener_new:
 * Create a new listener object, listening on the specified address. */
listener listener_new(const struct sockaddr_in *addr, const char *domain
#ifdef MASS_HOSTING
 /* leading comma-- yuk */  , const char *regex
#endif
#ifdef USE_TLS
                            , enum tls_mode mode,
                              const char *certfile, const char *pkeyfile
#endif
                        ) {
    listener L;
    struct hostent *he;
    
    alloc_struct(_listener, L);

    L->sin = *addr;
    L->s = socket(PF_INET, SOCK_STREAM, 0);
    if (L->s == -1) {
        log_print(LOG_ERR, "listener_new: socket: %m");
        goto fail;
    } else {
        int t = 1;
        if (setsockopt(L->s, SOL_SOCKET, SO_REUSEADDR, &t, sizeof(t)) == -1) {
            log_print(LOG_ERR, "listener_new: setsockopt: %m");
            goto fail;
        } else if (fcntl(L->s, F_SETFL, O_NONBLOCK) == -1) {
            log_print(LOG_ERR, "listener_new: fcntl: %m");
            goto fail;
        } else if (bind(L->s, (struct sockaddr*)addr, sizeof(struct sockaddr_in)) == -1) {
            log_print(LOG_ERR, "listener_new: bind(%s:%d): %m", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
            goto fail;
        } else if (listen(L->s, SOMAXCONN) == -1) {
            log_print(LOG_ERR, "listener_new: listen: %m");
            goto fail;
        }
    }

#ifdef MASS_HOSTING
    /* Possibly a regex has been specified for mapping name-of-server to
     * domain. */
    if (regex) {
        int err;
        char errbuf[128] = {0};
        if ((err = regcomp(&L->re, regex, REG_EXTENDED | REG_ICASE))) {
            regerror(err, &L->re, errbuf, sizeof errbuf);
            log_print(LOG_WARNING, "listener_new: %s:%d: /%s/: %s", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), L->re, errbuf);
        } else if (L->re.re_nsub != 1) {
            log_print(LOG_WARNING, _("listener_new: %s:%d: /%s/: regular expression should have exactly one bracketed subexpression"), inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), regex);
            regfree(&L->re);
        } else {
            L->have_re = 1;
            L->regex = xstrdup(regex);
        }
        if (!L->have_re)
            log_print(LOG_WARNING, _("listener_new: %s:%d: cannot derive domain information for this address"), inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
    } else
#endif
    /* Now, we need to find the domain associated with this socket. */
    if (!domain) {
        he = gethostbyaddr((char *)&(addr->sin_addr), sizeof(addr->sin_addr), AF_INET);
        if (!he) {
            log_print(LOG_WARNING, _("listener_new: gethostbyaddr(%s): cannot resolve name"), inet_ntoa(addr->sin_addr));
            log_print(LOG_WARNING, _("listener_new: %s:%d: cannot obtain domain suffix for this address"), inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
        } else {
            /* We need to find out an appropriate domain suffix for the address.
             * FIXME we just take the first address with a "." in it, and use
             * the part after the ".". */
            char **a, *b;
            b = strchr(he->h_name, '.');
            if (b && *(b + 1)) {
                L->domain = xstrdup(b + 1);
            } else 
                for (a = he->h_aliases; *a; ++a) {
                    char *b;
                    fprintf(stderr, "%s\n", *a);
                    if ((b = strchr(*a, '.')) && *(b + 1)) {
                        L->domain = xstrdup(b + 1);
                        break;
                    }
                }

            if (!L->domain)
                log_print(LOG_WARNING, _("listener_new: %s:%d: no suitable domain suffix found for this address"), inet_ntoa(addr->sin_addr), htons(addr->sin_port));
        }
    } else L->domain = xstrdup(domain);

    /* Last try; use the nodename from uname(2). */
    if (!L->domain
#ifdef MASS_HOSTING
            && !L->have_re
#endif
            ) {
        struct utsname u;
        if (uname(&u) == -1) {
            log_print(LOG_WARNING, "listener_new: uname: %m");
            log_print(LOG_WARNING, _("listener_new: %s:%d: using domain suffix `x.invalid'"), inet_ntoa(addr->sin_addr), htons(addr->sin_port));
            L->domain = xstrdup("x.invalid");
        } else {
            log_print(LOG_WARNING, _("listener_new: %s:%d: using fallback domain suffix `%s'"), inet_ntoa(addr->sin_addr), htons(addr->sin_port), u.nodename);
            L->domain = xstrdup(u.nodename);
        }
    }

#ifdef USE_TLS
    /* Should this listener support some sort of TLS? */
    if (mode != none) {
        tls_init();
        L->tls.mode = mode;
        L->tls.ctx = tls_create_context(certfile, pkeyfile);
        if (!L->tls.ctx) {
            if (mode == immediate) {
                log_print(LOG_ERR, _("listener_new: %s:%d: cannot create TLS context for listener; dropping it"), inet_ntoa(addr->sin_addr), htons(addr->sin_port));
                goto fail;
            } else if (mode == stls) {
                log_print(LOG_ERR, _("listener_new: %s:%d: cannot create TLS context; setting TLS mode to `none'"), inet_ntoa(addr->sin_addr), htons(addr->sin_port));
                L->tls.mode = none;
            }
        }
    }
#endif /* USE_TLS */

    return L;

fail:
    listener_delete(L);
    return NULL;
}

/* listener_delete:
 * Delete a listener object, closing the associated socket. */
void listener_delete(listener L) {
    if (!L) return;
    if (L->s != -1) close(L->s); /* Do not shutdown(2). */
    xfree(L->domain);
#ifdef MASS_HOSTING
    if (L->have_re)
        regfree(&L->re);
    xfree(L->regex);
#endif
#ifdef USE_TLS
    if (L->tls.ctx)
        tls_close(L->tls.ctx);
#endif
    xfree(L);
}

#ifdef MASS_HOSTING
/* listener_obtain_domain:
 * Use the regular expression specified for the listener to obtain a domain
 * name from the address to which the given socket is connected. */
char *listener_obtain_domain(listener L, int s) {
    struct sockaddr_in sin;
    size_t l = sizeof sin;
    struct hostent *he;
    regmatch_t match[2];
    int err;

    if (!L->have_re)
        return NULL;

    if (getsockname(s, (struct sockaddr*)&sin, (int*)&l) == -1) {
        /* Shouldn't happen. */
        log_print(LOG_ERR, "listener_obtain_domain: getsockname: %m");
        return NULL;
    }

    if (!(he = gethostbyaddr((char*)&(sin.sin_addr), sizeof(sin.sin_addr), AF_INET))) {
        log_print(LOG_WARNING, _("listener_obtain_domain(%s): cannot resolve name"), inet_ntoa(sin.sin_addr));
        return NULL;
    }

    /* OK, we have a name; we need to run the regular expression against it and
     * check that we get one match exactly. */
    if ((err = regexec(&L->re, he->h_name, 2, match, 0)) == REG_NOMATCH) {
        log_print(LOG_WARNING, _("listener_obtain_domain: /%s/: %s: no regex match"), L->regex, he->h_name);
        return NULL;
    } else if (match[1].rm_so == -1) {
        log_print(LOG_WARNING, _("listener_obtain_domain: /%s/: %s: regex failed to match any subexpression"), L->regex, he->h_name);
        return NULL;
    } else if (match[1].rm_so == match[1].rm_eo) {
        log_print(LOG_WARNING, _("listener_obtain_domain: /%s/: %s: zero-length subexpression"), L->regex, he->h_name);
        return NULL;
    } else {
        char *x;
        int l;
        x = xcalloc((l = match[1].rm_eo - match[1].rm_so) + 1, 1);
        memcpy(x, he->h_name + match[1].rm_so, l);
        return x;
    }
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1