#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include "includes.h"
#include "dbutil.h"
#include "session.h"
#include "buffer.h"
#include "signkey.h"
#include "runopts.h"
#include "dbrandom.h"
#include "crypto_desc.h"
#include "libdropbear.h"

typedef struct dropbear_chansess_accept * Net__Dropbear__XS__SessionAccept;
typedef struct AuthState * Net__Dropbear__XS__AuthState;

extern struct dropbear_hooks hooks; /* GLOBAL */

int _get_bool(SV *self, char *method)
{
        int count;
        int result;
        SV *option;

        dTHX;
        dSP;

        ENTER;
        SAVETMPS;

        PUSHMARK(SP);
        XPUSHs(self);
        PUTBACK;
        count = call_method(method, G_SCALAR);
        SPAGAIN;

        if (count != 1)
          croak("Too much result from %s\n", method);

        option = POPs;
        result = SvTRUE(option);

        PUTBACK;
        FREETMPS;
        LEAVE;

        return result;
}

SV* hooks_self;
int hooks_on(const char *hook, AV* args)
{
    int RETVAL;
    int len, i;

    dTHX;
    dSP;

    ENTER;
    SAVETMPS;

    if (hooks_self == NULL)
        hooks_self = &PL_sv_undef;

    PUSHMARK(SP);
    XPUSHs(hooks_self);
    XPUSHs(sv_2mortal(newSVpv(hook, 0)));

    len = av_len(args) + 1;

    for(i = 0; i < len; i++)
    {
        SV ** elem = av_fetch(args, i, 0);
        if ( elem != NULL )
          XPUSHs(*elem);
        else
          XPUSHs(&PL_sv_undef);
    }

    PUTBACK;
    int count = call_method("auto_hook", G_EVAL | G_SCALAR);
    SPAGAIN;

    if (SvTRUE(ERRSV))
    {
        dropbear_log(LOG_DEBUG, "Error calling %s: %s\n", hook, SvPV_nolen(ERRSV));
        RETVAL = LIBDROPBEAR_HOOK_FAILURE;
    }
    else
    {
        RETVAL = POPi;
    }

    PUTBACK;
    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_log(int priority, const char *message)
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    av_push(args, sv_2mortal(newSViv(priority)));
    av_push(args, sv_2mortal(newSVpv(message, 0)));
    int RETVAL = hooks_on("on_log", args);

    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_start()
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    int RETVAL = hooks_on("on_start", args);

    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_username(const char* username)
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    av_push(args, sv_2mortal(newSVpv(username, 0)));
    int RETVAL = hooks_on("on_username", args);

    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_passwd_fill(struct AuthState *auth, const char *username)
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    SV *auth_obj = newSV(0);
    auth_obj = sv_setref_pv(auth_obj, "Net::Dropbear::XS::AuthState", auth);
    av_push(args, sv_2mortal(auth_obj));
    av_push(args, sv_2mortal(newSVpv(username, 0)));
    int RETVAL = hooks_on("on_passwd_fill", args);

    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_shadow_fill(char** crypt_password, const char *pw_name)
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    av_push(args, sv_2mortal(newSVpv("", 0)));
    av_push(args, sv_2mortal(newSVpv(pw_name, 0)));
    int RETVAL = hooks_on("on_shadow_fill", args);

    SV** arg = av_fetch(args, 0, 0);
    if ( arg != NULL )
    {
      *crypt_password = m_strdup(SvPV_nolen(*arg));
    }

    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_crypt_passwd(char** input_passwd, const char *salt, const char *pw_name)
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    av_push(args, sv_2mortal(newSVpv(m_strdup(*input_passwd), 0)));
    av_push(args, sv_2mortal(newSVpv(salt, 0)));
    av_push(args, sv_2mortal(newSVpv(pw_name, 0)));
    int RETVAL = hooks_on("on_crypt_passwd", args);

    SV** arg = av_fetch(args, 0, 0);
    if ( arg != NULL )
    {
      *input_passwd = m_strdup(SvPV_nolen(*arg));
    }

    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_check_pubkey(char** authkeys, const char *pw_name)
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    av_push(args, sv_2mortal(newSVpv("", 0)));
    av_push(args, sv_2mortal(newSVpv(pw_name, 0)));
    int RETVAL = hooks_on("on_check_pubkey", args);

    SV** arg = av_fetch(args, 0, 0);
    if ( arg != NULL )
    {
      *authkeys = m_strdup(SvPV_nolen(*arg));
    }

    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_new_channel(const char* type)
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    av_push(args, sv_2mortal(newSVpv(type, 0)));
    int RETVAL = hooks_on("on_new_channel", args);

    FREETMPS;
    LEAVE;

    return RETVAL;
}

int hooks_on_chansess_command(struct dropbear_chansess_accept *chansess)
{
    dTHX;
    ENTER;
    SAVETMPS;

    AV* args = newAV();

    SV *session_accept = newSV(0);
    session_accept = sv_setref_pv(session_accept, "Net::Dropbear::XS::SessionAccept", chansess);
    av_push(args, sv_2mortal(session_accept));
    int RETVAL = hooks_on("on_chansess_command", args);

    FREETMPS;
    LEAVE;

    return RETVAL;
}

MODULE = Net::Dropbear  PACKAGE = Net::Dropbear::XS

BOOT:
{
    HV *stash = gv_stashpv("Net::Dropbear::XS", 0);

    newCONSTSUB(stash, "HOOK_COMPLETE", newSViv (LIBDROPBEAR_HOOK_COMPLETE));
    newCONSTSUB(stash, "HOOK_CONTINUE", newSViv (LIBDROPBEAR_HOOK_CONTINUE));
    newCONSTSUB(stash, "HOOK_FAILURE",  newSViv (LIBDROPBEAR_HOOK_FAILURE));

    seedrandom();
    crypto_init();
}

void
gen_key(const char* filename, enum signkey_type keytype=DROPBEAR_SIGNKEY_RSA, int bits=2048, int skip_exist=0)
    CODE:
        dropbear_gen_key(keytype, bits, filename, skip_exist);

void
svr_main(CLASS)
        SV *CLASS = NO_INIT
    PROTOTYPE: $
    CODE:
        dropbear_run();
        /* Never Returns */

void
setup_svr_opts(CLASS, options)
        SV *CLASS = NO_INIT
        SV * options
    PROTOTYPE: $$
    CODE:
        dropbear_init();

        /* Always clear the default options */
        svr_opts.ports[0] = 0;
        m_free(svr_opts.addresses[0]);
        svr_opts.portcount = 0;
#if DEBUG_TRACE
        debug_trace             = _get_bool(options, "debug");
#endif
        svr_opts.forkbg         = _get_bool(options, "forkbg");
        opts.usingsyslog        = _get_bool(options, "usingsyslog");
        svr_opts.inetdmode      = _get_bool(options, "inetdmode");
        svr_opts.norootlogin    = _get_bool(options, "norootlogin");
        svr_opts.noauthpass     = _get_bool(options, "noauthpass");
        svr_opts.norootpass     = _get_bool(options, "norootpass");
        svr_opts.allowblankpass = _get_bool(options, "allowblankpass");
        svr_opts.delay_hostkey  = _get_bool(options, "delay_hostkey");
#if DO_MOTD
        svr_opts.domotd = _get_bool(options, "domotd");
#endif
#ifdef ENABLE_SVR_REMOTETCPFWD
        svr_opts.noremotetcp = _get_bool(options, "noremotetcp");
#endif
#ifdef ENABLE_SVR_LOCALTCPFWD
        svr_opts.nolocaltcp = _get_bool(options, "nolocaltcp");
#endif

        hooks_self = options;
        hooks.on_log = hooks_on_log;
        hooks.on_start = hooks_on_start;
        hooks.on_username = hooks_on_username;
        hooks.on_passwd_fill = hooks_on_passwd_fill;
        hooks.on_shadow_fill = hooks_on_shadow_fill;
        hooks.on_crypt_passwd = hooks_on_crypt_passwd;
        hooks.on_check_pubkey = hooks_on_check_pubkey;
        hooks.on_new_channel = hooks_on_new_channel;
        hooks.on_chansess_command = hooks_on_chansess_command;

        hooks._will_run_as_root = 0;
        SV* _will_run_as_root = get_sv("Net::Dropbear::SSHd::_will_run_as_root", FALSE);
        hooks._will_run_as_root = SvTRUE(_will_run_as_root);

        int count, i;
        SSize_t len;
        SV * ref_result;

        ENTER;
        SAVETMPS;

        PUSHMARK(SP);
        XPUSHs(options);
        PUTBACK;
        count = call_method("addrs", G_SCALAR);
        SPAGAIN;

        if (count != 1)
          croak("Too much result from %s\n", "addr");

        ref_result = POPs;

        if (!SvROK(ref_result) || SvTYPE(SvRV(ref_result)) != SVt_PVAV)
          croak("$self->addrs did not return an array");

        PUTBACK;

        AV* addrs = (AV*)SvRV(ref_result);
        len = av_len(addrs);

        for (i = 0; i <= len; i++)
        {
          SV** addr = av_fetch(addrs, i, 0);
          if ( addr != NULL )
          {
            dropbear_add_svr_addr(SvPV_nolen(*addr));
          }
        }

        PUSHMARK(SP);
        XPUSHs(options);
        PUTBACK;
        count = call_method("keys", G_SCALAR);
        SPAGAIN;

        if (count != 1)
          croak("Too much result from %s\n", "keys");

        ref_result = POPs;

        if (!SvROK(ref_result) || SvTYPE(SvRV(ref_result)) != SVt_PVAV)
          croak("$self->addr did not return an array");

        PUTBACK;

        AV* svr_keys = (AV*)SvRV(ref_result);
        len = av_len(svr_keys);

        for (i = 0; i <= len; i++)
        {
          SV** key = av_fetch(svr_keys, i, 0);
          if ( key != NULL )
          {
            dropbear_add_svr_key(SvPV_nolen(*key));
          }
        }

        FREETMPS;
        LEAVE;

MODULE = Net::Dropbear  PACKAGE = Net::Dropbear::XS::AuthState

BOOT:
{
    HV *stash = gv_stashpv("Net::Dropbear::XS::AuthState", 0);

    newCONSTSUB(stash, "AUTH_TYPE_NONE",      newSViv (AUTH_TYPE_NONE));
    newCONSTSUB(stash, "AUTH_TYPE_PUBKEY",    newSViv (AUTH_TYPE_PUBKEY));
    newCONSTSUB(stash, "AUTH_TYPE_PASSWORD",  newSViv (AUTH_TYPE_PASSWORD));
    newCONSTSUB(stash, "AUTH_TYPE_INTERACT",  newSViv (AUTH_TYPE_INTERACT));
}

uid_t
pw_uid(THIS, __value = NO_INIT)
        Net::Dropbear::XS::AuthState THIS
        uid_t __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->pw_uid = __value;
        RETVAL = THIS->pw_uid;
    OUTPUT:
        RETVAL
 
gid_t
pw_gid(THIS, __value = NO_INIT)
        Net::Dropbear::XS::AuthState THIS
        gid_t __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->pw_gid = __value;
        RETVAL = THIS->pw_gid;
    OUTPUT:
        RETVAL
 
char *
pw_dir(THIS, __value = NO_INIT)
        Net::Dropbear::XS::AuthState THIS
        char * __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->pw_dir = m_strdup(__value);
        RETVAL = THIS->pw_dir;
    OUTPUT:
        RETVAL
 
char *
pw_shell(THIS, __value = NO_INIT)
        Net::Dropbear::XS::AuthState THIS
        char * __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->pw_shell = m_strdup(__value);
        RETVAL = THIS->pw_shell;
    OUTPUT:
        RETVAL
 
char *
pw_name(THIS, __value = NO_INIT)
        Net::Dropbear::XS::AuthState THIS
        char * __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->pw_name = m_strdup(__value);
        RETVAL = THIS->pw_name;
    OUTPUT:
        RETVAL
 
char *
pw_passwd(THIS, __value = NO_INIT)
        Net::Dropbear::XS::AuthState THIS
        char * __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->pw_passwd = m_strdup(__value);
        RETVAL = THIS->pw_passwd;
    OUTPUT:
        RETVAL
 
MODULE = Net::Dropbear  PACKAGE = Net::Dropbear::XS::SessionAccept

int
channel_index(THIS, __value = NO_INIT)
        Net::Dropbear::XS::SessionAccept THIS
        int __value
    PROTOTYPE: $;$
    CODE:
        RETVAL = THIS->channel_index;
    OUTPUT:
        RETVAL
 
char *
cmd(THIS, __value = NO_INIT)
        Net::Dropbear::XS::SessionAccept THIS
        char * __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->cmd = m_strdup(__value);
        RETVAL = (char *)THIS->cmd;
    OUTPUT:
        RETVAL
 
pid_t
pid(THIS, __value = NO_INIT)
        Net::Dropbear::XS::SessionAccept THIS
        pid_t __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->pid = __value;
        RETVAL = THIS->pid;
    OUTPUT:
        RETVAL
 
int
iscmd(THIS, __value = NO_INIT)
        Net::Dropbear::XS::SessionAccept THIS
        int __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->iscmd = __value;
        RETVAL = THIS->iscmd;
    OUTPUT:
        RETVAL
 
int
issubsys(THIS, __value = NO_INIT)
        Net::Dropbear::XS::SessionAccept THIS
        int __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->issubsys = __value;
        RETVAL = THIS->issubsys;
    OUTPUT:
        RETVAL
 
int
writefd(THIS, __value = NO_INIT)
        Net::Dropbear::XS::SessionAccept THIS
        int __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->writefd = __value;
        RETVAL = THIS->writefd;
    OUTPUT:
        RETVAL
 
int
readfd(THIS, __value = NO_INIT)
        Net::Dropbear::XS::SessionAccept THIS
        int __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->readfd = __value;
        RETVAL = THIS->readfd;
    OUTPUT:
        RETVAL
 
int
errfd(THIS, __value = NO_INIT)
        Net::Dropbear::XS::SessionAccept THIS
        int __value
    PROTOTYPE: $;$
    CODE:
        if (items > 1)
            THIS->errfd = __value;
        RETVAL = THIS->errfd;
    OUTPUT:
        RETVAL