/*
 * rebind: Intercept bind calls and bind to a different port
 * Copyright 2010 Joel Martin
 * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
 *
 * Overload (LD_PRELOAD) bind system call. If REBIND_PORT_OLD and
 * REBIND_PORT_NEW environment variables are set then bind on the new
 * port (of localhost) instead of the old port. 
 *
 * This allows a bridge/proxy (such as websockify) to run on the old port and
 * translate traffic to/from the new port.
 *
 * Usage:
 *     LD_PRELOAD=./rebind.so \
 *     REBIND_PORT_OLD=23 \
 *     REBIND_PORT_NEW=2023 \
 *     program
 */

//#define DO_DEBUG 1

#include <stdio.h>
#include <stdlib.h>

#define __USE_GNU 1  // Pull in RTLD_NEXT
#include <dlfcn.h>

#include <string.h>
#include <netinet/in.h>


#if defined(DO_DEBUG)
#define DEBUG(...) \
    fprintf(stderr, "rebind: "); \
    fprintf(stderr, __VA_ARGS__);
#else
#define DEBUG(...)
#endif


int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
    static void * (*func)();
    int do_move = 0;
    struct sockaddr_in * addr_in = (struct sockaddr_in *)addr;
    struct sockaddr_in addr_tmp;
    socklen_t addrlen_tmp;
    char * PORT_OLD, * PORT_NEW, * end1, * end2;
    int ret, oldport, newport, askport = htons(addr_in->sin_port);
    uint32_t askaddr = htons(addr_in->sin_addr.s_addr);
    if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");

    DEBUG(">> bind(%d, _, %d), askaddr %d, askport %d\n",
          sockfd, addrlen, askaddr, askport);

    /* Determine if we should move this socket */
    if (addr_in->sin_family == AF_INET) {
        // TODO: support IPv6
        PORT_OLD = getenv("REBIND_OLD_PORT");
        PORT_NEW = getenv("REBIND_NEW_PORT");
        if (PORT_OLD && (*PORT_OLD != '\0') &&
            PORT_NEW && (*PORT_NEW != '\0')) {
            oldport = strtol(PORT_OLD, &end1, 10);
            newport = strtol(PORT_NEW, &end2, 10);
            if (oldport && (*end1 == '\0') &&
                newport && (*end2 == '\0') &&
                (oldport == askport)) {
                do_move = 1;
            }
        }
    }

    if (! do_move) {
        /* Just pass everything right through to the real bind */
        ret = (long) func(sockfd, addr, addrlen);
        DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
        return ret;
    }

    DEBUG("binding fd %d on localhost:%d instead of 0x%x:%d\n",
        sockfd, newport, ntohl(addr_in->sin_addr.s_addr), oldport);

    /* Use a temporary location for the new address information */
    addrlen_tmp = sizeof(addr_tmp);
    memcpy(&addr_tmp, addr, addrlen_tmp);

    /* Bind to other port on the loopback instead */
    addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addr_tmp.sin_port = htons(newport);
    ret = (long) func(sockfd, &addr_tmp, addrlen_tmp);

    DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
    return ret;
}
