#include <cstdio>
#include <cerrno>
#include <cstdarg>
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <fcntl.h>
#include <netinet/in.h>
#include <pwd.h>
#include <string>
#include <syslog.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>

/* This client does not care about the remote host name,
 * because a local port can not be reserved twice at the same time.
 */

using namespace std;

#include "sizes.h"

#ifdef __GNUC__
#  define FORMAT(p,a,b) __attribute__((format(p,a,b)))
#else
#  define FORMAT(p,a,b) /* none */
#endif

static unsigned localport=0;
static unsigned remoteport=0;
static FILE *ipm, *ipc, *tcp;

static string RespType;
static string AddInfo;

enum loglevel_t { ll_none=0, ll_quiet, ll_normal, ll_verbose, ll_debug, ll_verbosedebug };
static int ll2sysloglevel[] = { LOG_CRIT, LOG_ERR, LOG_NOTICE, LOG_DEBUG, LOG_DEBUG, LOG_DEBUG };

static int loglevel = ll_normal;

static void msyslog(enum loglevel_t ll, const char *msg, ...) FORMAT(printf, 2, 3);
static void msyslog(enum loglevel_t ll, const char *msg, ...)
{
    char buf[2048];
    va_list ap;
    if (loglevel < ll) return;
    va_start(ap, msg);
    vsnprintf(buf, sizeof(buf), msg, ap);
    va_end(ap);
    syslog(ll2sysloglevel[ll], "%s", buf);
}

static int lgetchar()
{
    char c;
    int rv = EOF;
    if(read(0, &c, 1) > 0)rv = c;
    //int rv = getchar();
    if (isprint(rv))
        msyslog(ll_verbosedebug, "read char %i (%c)", rv, rv);
    else
        msyslog(ll_verbosedebug, "read char %i", rv);
    return rv;
}

static int Done(int code=0)
{
    msyslog(ll_normal, "%u, %u : %s : %s\n", localport, remoteport, RespType.c_str(), AddInfo.c_str());
    printf("%u, %u : %s : %s\r\n", localport, remoteport, RespType.c_str(), AddInfo.c_str());
    if(code)
    {
        closelog();
        exit(code);
    }
    return code;
}
static int Error(const char *ErrorType = "INVALID-PORT")
{
    int code = -1;
    RespType = "ERROR";
    if(!ErrorType)
    {
        ErrorType = "UNKNOWN-ERROR";
        code = errno;
    }
    AddInfo = ErrorType;
    return Done(code);
}
static DWORD byteswap(DWORD n)
{
    return  ((n&0xFF) << 24)
          | ((n&0xFF00) << 8)
          | ((n&0xFF0000) >> 8)
          | ((n&0xFF000000) >> 24);
}
static int Forward(DWORD host, int lport, int rport)
{
    in_addr addr = { byteswap(host) };
    msyslog(ll_debug, "(forward) host = %s, lport = %i, rport = %i",
            inet_ntoa(addr), lport, rport);
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        if (loglevel >= ll_normal) msyslog(ll_quiet, "cannot bind socket"); 
        Error(NULL);
    }
    struct sockaddr_in madr;
    memset(&madr, 0, sizeof madr);
    madr.sin_port   = htons(113);
    madr.sin_family = AF_INET;
    madr.sin_addr.s_addr = byteswap(host);
    if(connect(sock, (struct sockaddr *)&madr, sizeof madr) < 0)
    {
        if (loglevel >= ll_normal) msyslog(ll_quiet, "cannot connect");
        Error(NULL);
    }
    char Buf[256];
    sprintf(Buf, "%d,%d\r\n", rport, lport);
    msyslog(ll_verbose, "Forwarding to host %s the following request: %s\n",
            inet_ntoa(addr), Buf);
    write(sock, Buf, strlen(Buf));
    char c;
    while(read(sock, &c, 1)==1 && c!=':') { }
    for(RespType=""; read(sock, &c, 1)==1 && c != ':'; )if(RespType.size() || c!=' ')if(c != ' ')RespType += c;
    for(AddInfo=""; read(sock, &c, 1)==1 && c != '\n'; )if(AddInfo.size() || c!=' ')if(c != '\r')AddInfo += c;
    return close(sock) ? errno : 0;
}
static int Resolve(void)
{
    FILE *fp;
    char Buf[256];
    if((fp = tcp) != NULL)
    {
        while(fgets(Buf, 255, fp))
        {
            unsigned l, r, uid, state;
            sscanf(Buf, "%*u: %*X:%X %*X:%X %X %*X:%*X %*X:%*X %*X %u", &l, &r, &state, &uid);
            msyslog(ll_debug, " (tcp parse) l = %u, r = %u, state = %u, uid = %u", l, r, state, uid);
        
        //  This was a bad guess:
        //  if(state != IPPROTO_TCP)continue;
        
            if(l==localport && r==remoteport)
            {
                const struct passwd *p = getpwuid(uid);
                RespType = "USERID";
                AddInfo = "UNIX : ";
                if(p)
                    AddInfo += p->pw_name;
                else
                {
                    sprintf(Buf, "%u", uid);
                    AddInfo += Buf;
                }
                return 0;
            }
        }
        fclose(fp);
    }
    if((fp = ipm) != NULL)
    {
        while(fgets(Buf, 255, fp))
        {
            unsigned l, r, masqport;
            DWORD masqhost;
            char proto[5];
            sscanf(Buf, "%s %X:%X %*X:%X %X", proto, &masqhost, &l, &r, &masqport);
            in_addr masqaddr = { byteswap(masqhost) };
            msyslog(ll_debug, " (masq parse) proto = %s, masqhost = %s, l = %u, r = %u, masqport = %u",
                    proto, inet_ntoa(masqaddr), l, r, masqport);
            if(strcmp(proto, "TCP"))continue;
            if(r == remoteport && masqport == localport)
                return Forward(masqhost, r, l);
        }
        fclose(fp);
    }
    /* 2.4.-kernel support by Wesley W. Terpstra <wesley@terpstra.ca> */
    if((fp = ipc) != NULL)
    {
        while(fgets(Buf, 255, fp))
        {
            unsigned l, r, masqport;
            char proto[20];
            char masqhosts[20];
            char* a = strstr(Buf, "src=");
            if(!a)continue;
            char* b = strstr(a+1, "src=");
            if(!b)continue;
            sscanf(Buf, "%19s %*d %*d", proto);
            sscanf(a, "src=%19s %*s sport=%u %*s",
                masqhosts, &l);
            sscanf(b, "%*s %*s sport=%u dport=%u",
                &r, &masqport);
            msyslog(ll_debug, " (conntrack parse) proto = %s, masqhost = %s, l = %u, r = %u, masqport = %u\n",
                proto, masqhosts, l, r, masqport);
            DWORD masqhost = inet_addr(masqhosts);
            if(strcmp(proto, "tcp"))continue;
            if(r == remoteport && masqport == localport)
                return Forward(byteswap(masqhost), r, l);
        }
        fclose(fp);
    }
    return Error("NO-USER");
}
int main(int argc, const char *const *argv)
{
    const char *const a0 = *argv;
    int help=0, error=0;
    
    /* Support for modern 2.4 and 2.6 kernels (root-only proc): */
    
    /* use root priveledges to open protected files */
    ipm = fopen("/proc/net/ip_masquerade", "rt");
    ipc = fopen("/proc/net/ip_conntrack", "rt");
    tcp = fopen("/proc/net/tcp", "rt");
    
    /* drop priveledges */
    setregid(65534, 65534); /* nogroup */
    setreuid(65534, 65534); /* nobody */
    
    loglevel = ll_normal;
    while(--argc > 0)
    {
        const char *s = *++argv;
        if(*s == '-')
            while(*++s)
                switch(*s)
                {
                    case 'q': if(loglevel > ll_none)--loglevel; break;
                    case 'v': if(loglevel < ll_verbosedebug)++loglevel; break;
                    case 'h':
                    case '?': ++help; break;
                    default: ++error; break;
                }
        else if(!strcmp(s, "--help"))
            ++help;
        else
            ++error;
    }
    if(error || help)
    {
        fprintf(stderr,
          "Bisqwit's identd version "VERSION"\n"
          "Copyright (C) 1992,2005 Bisqwit (http://iki.fi/bisqwit/)\n"
          "\n"
          "To be spawned from inetd. Usage:\n"
          "    %s <options>\n"
          "       -q    Quieter (-qq=max)\n"
          "       -v    Verboser (-vvv=max)\n"
          "\n"
          "Read the installation instructions from INSTALL.\n\n", a0);
        return -error;
    }
    openlog(a0, LOG_PID, LOG_DAEMON);
    int l, r, c;

    for(l=0; isdigit(c = lgetchar()); )l = l*10 + (c-'0');
    msyslog(ll_debug, "(request parsing) l= %i", l);
    while(c == ' ')c = lgetchar();
    if(c != ',') { msyslog(ll_debug, "missing comma in request"); Error(); }
    for(r=0; isdigit(c = lgetchar()) || c==' '; )if(c!=' ')r = r*10 + (c-'0');
    msyslog(ll_debug, "(request parsing) r= %i", r);
    while(c == '\r')c = lgetchar();
    if(c != '\n' && c != EOF) { msyslog(ll_debug, "message did not end at \\n"); Error(); }
    
    remoteport = r;
    localport = l;
    
    int rv = Done(Resolve());
    closelog();
    return rv;
}
