/*
* auth_perl.c:
* Call into a perl subroutine to perform authentication.
*
* This is somewhat based on perl.c in the exim distribution.
*
* Copyright (c) 2001 Chris Lightfoot. All rights reserved.
*
*/
#ifdef HAVE_CONFIG_H
#include "configuration.h"
#endif /* HAVE_CONFIG_H */
#ifdef AUTH_PERL
static const char rcsid[] = "$Id: auth_perl.c,v 1.25 2003/08/25 17:51:25 chris Exp $";
#include <sys/types.h>
#include <pwd.h>
#include <stdlib.h>
#include <syslog.h>
#include <unistd.h>
#include "config.h"
#include "auth_perl.h"
#include "stringmap.h"
#include "util.h"
/* Include files for perl integration. */
#undef PACKAGE /* work around bad perl/autoconf interaction */
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>
static PerlInterpreter *perl_interp;
char *apop_sub, *pass_sub, *onlogin_sub; /* Names of functions we call. */
#ifndef ERRSV
#define ERRSV (GvSV(errgv))
#endif
/* xs_log_print:
* Perl interface to tpop3d's logging. */
XS(xs_log_print)
{
dXSARGS;
char *str;
STRLEN len;
if (items != 1) croak("Usage: TPOP3D::log_print(string)");
str = SvPV(ST(0), len);
log_print(LOG_INFO, "auth_perl: (perl code): %s", str);
}
/* xs_init:
* Start up XS code in perl. */
extern void boot_DynaLoader(CV *cv);
void xs_init(void) {
char *file = __FILE__;
newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
newXS("TPOP3D::log_print", xs_log_print, file);
newXS("TPOP3D::print_log", xs_log_print, file);
}
/* auth_perl_init:
* Initialise the perl interpreter and run its startup code. */
extern stringmap config; /* in main.c */
int auth_perl_init(void) {
dSP;
int argc = 2;
char *argv[3] = {"auth_perl", "/dev/null", NULL};
/* char *argv[4] = {"auth_perl", "-e", "$ENV{TPOP3D_CONTEXT} = 'auth_perl';", NULL}; */
char *startupcode, *s;
SV *sv;
STRLEN len;
/* Obtain perl startup code; this should probably be something like
* "do '/etc/tpop3d.pl';" */
if (!(s = config_get_string("auth-perl-start"))) {
log_print(LOG_ERR, _("auth_perl_init: auth_perl enabled, but no startup code specified"));
return 0;
} else startupcode = s;
if ((s = config_get_string("auth-perl-apop")))
apop_sub = s;
if ((s = config_get_string("auth-perl-pass")))
pass_sub = s;
if ((s = config_get_string("auth-perl-onlogin")))
onlogin_sub = s;
if (!apop_sub && !pass_sub && !onlogin_sub) {
log_print(LOG_ERR, _("auth_perl_init: auth_perl enabled but no subroutines supplied"));
return 0;
}
/* Put a useful string into the environment. */
putenv(xstrdup("TPOP3D_CONTEXT=auth_perl"));
/* Create and start up perl interpreter. */
perl_interp = perl_alloc();
perl_construct(perl_interp);
perl_parse(perl_interp, xs_init, argc, argv, 0);
perl_run(perl_interp);
/* Try to execute the startup code. */
sv = newSVpv(startupcode, 0);
PUSHMARK(SP);
perl_eval_sv(sv, G_SCALAR | G_DISCARD | G_KEEPERR); /* XXX what do the options actually mean? */
SvREFCNT_dec(sv);
if (SvTRUE(ERRSV)) {
log_print(LOG_ERR, _("auth_perl_init: error executing perl start code: %s"), SvPV(ERRSV, len));
perl_destruct(perl_interp);
perl_free(perl_interp);
perl_interp = NULL;
return 0;
}
return 1;
}
/* auth_perl_postfork:
* Post-fork cleanup. */
void auth_perl_postfork(void) {
perl_interp = NULL; /* XXX */
}
/* auth_perl_close:
* Shut down the perl interpreter. */
void auth_perl_close(void) {
if (perl_interp) {
/* There may be code to execute on shutdown. */
char *s;
if ((s = config_get_string("auth-perl-finish"))) {
dSP;
SV *sv;
STRLEN len;
sv = newSVpv(s, 0);
PUSHMARK(SP);
perl_eval_sv(sv, G_SCALAR | G_DISCARD | G_KEEPERR);
SvREFCNT_dec(sv);
if (SvTRUE(ERRSV))
log_print(LOG_ERR, _("auth_perl_close: error executing perl finish code: %s"), SvPV(ERRSV, len));
}
perl_destruct(perl_interp);
perl_free(perl_interp);
perl_interp = NULL;
}
}
/* auth_perl_callfn:
* Calls a perl function, passing the parameters in as a reference to a hash,
* expecting a reference to a hash to be returned; it converts this into a
* stringmap and returns it to the caller. */
stringmap auth_perl_callfn(const char *perlfn, const int nvars, ...) {
dSP;
HV *hash_in, *hash_out;
SV *hashref_in, *hashref_out;
va_list ap;
int i, items;
stringmap s = NULL;
if (!perl_interp) return NULL;
hash_in = newHV();
/* Fill the hash with the passed values. */
va_start(ap, nvars);
for (i = 0; i < nvars; ++i) {
char *key, *val;
SV *sv;
key = va_arg(ap, char*);
val = va_arg(ap, char*);
sv = newSVpv(val, 0);
hv_store(hash_in, key, strlen(key), sv, 0);
}
va_end(ap);
/* Make a reference to the hash. */
hashref_in = newRV_noinc((SV*)hash_in); /* XXX inc/noinc? */
/* Call the function. */
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(hashref_in);
PUTBACK;
items = perl_call_pv((char*)perlfn, G_SCALAR | G_EVAL);
SPAGAIN;
hashref_out = POPs;
PUTBACK;
if (SvTRUE(ERRSV)) {
/* Error. */
STRLEN len;
log_print(LOG_ERR, _("auth_perl_callfn: perl function %s: %s"), perlfn, SvPV(ERRSV, len));
} else if (!SvOK(hashref_out)) {
/* Other sort of error. */
log_print(LOG_ERR, _("auth_perl_callfn: perl function %s: failure return"), perlfn);
} else if (SvTYPE(SvRV(hashref_out)) != SVt_PVHV) {
/* Yet a third sort of error. */
log_print(LOG_ERR, _("auth_perl_callfn: perl function %s: returned value was not a reference to a hash"), perlfn);
} else {
/* Damn and all, it worked! (Maybe.) */
char *key;
I32 len;
SV *val;
hash_out = (HV*)SvRV(hashref_out);
s = stringmap_new();
/* Transfer contents of hash into s. */
hv_iterinit(hash_out);
while ((val = hv_iternextsv(hash_out, &key, &len))) {
STRLEN len2;
stringmap_insert(s, key, item_ptr(xstrdup(SvPV(val, len2))));
}
/* SvREFCNT_dec(hashref_out);*/ /* `Attempt to free unreferenced scalar' */
}
SvREFCNT_dec(hashref_in);
FREETMPS;
LEAVE;
return s;
}
/* auth_perl_new_apop:
* Attempt to authenticate a user using APOP, via a perl subroutine. Much like
* auth_other_new_apop. */
authcontext auth_perl_new_apop(const char *name, const char *local_part, const char *domain, const char *timestamp, const unsigned char *digest, const char *clienthost, const char *serverhost) {
#define MISSING(k) do { log_print(LOG_ERR, _("auth_perl_new_apop: missing key `%s' in response"), (k)); goto fail; } while(0)
#define INVALID(k, v) do { log_print(LOG_ERR, _("auth_perl_new_apop: invalid value `%s' for key `%s' in response"), (v), (k)); goto fail; } while(0)
char digeststr[33];
char *p;
const unsigned char *q;
stringmap S;
item *I;
authcontext a = NULL;
if (!apop_sub)
return NULL;
for (p = digeststr, q = digest; q < digest + 16; p += 2, ++q)
sprintf(p, "%02x", (unsigned int)*q);
if (local_part && domain) {
if (!(S = auth_perl_callfn(apop_sub, 8, "method", "APOP", "user", name, "local_part", local_part, "domain", domain, "timestamp", timestamp, "digest", digeststr, "clienthost", clienthost, "serverhost", serverhost)))
return NULL;
} else if (!(S = auth_perl_callfn(apop_sub, 6, "method", "APOP", "user", name, "timestamp", timestamp, "digest", digeststr, "clienthost", clienthost, "serverhost", serverhost)))
return NULL;
I = stringmap_find(S, "logmsg");
if (I) log_print(LOG_INFO, "auth_perl_new_apop: (perl code): %s", (char*)I->v);
I = stringmap_find(S, "result");
if (!I) MISSING("result");
if (strcmp((char*)I->v, "YES") == 0) {
uid_t uid;
gid_t gid;
struct passwd *pw;
char *mailbox = NULL, *mboxdrv = NULL, *domain = NULL;
I = stringmap_find(S, "uid");
if (!I) MISSING("uid");
else if (!parse_uid(I->v, &uid)) INVALID("uid", (char*)I->v);
pw = getpwuid(uid);
if (!pw) INVALID("uid", (char*)I->v);
I = stringmap_find(S, "gid");
if (!I) MISSING("gid");
else if (!parse_gid(I->v, &gid)) INVALID("gid", (char*)I->v);
I = stringmap_find(S, "mailbox");
if (I) mailbox = (char*)I->v;
I = stringmap_find(S, "mboxtype");
if (I) mboxdrv = (char*)I->v;
I = stringmap_find(S, "domain");
if (I) domain = (char*)I->v;
a = authcontext_new(uid, gid, mboxdrv, mailbox, pw->pw_dir);
} else if (strcmp((char*)I->v, "NO") != 0) INVALID("result", (char*)I->v);
fail:
stringmap_delete_free(S);
return a;
#undef MISSING
#undef INVALID
}
/* auth_perl_new_user_pass:
* Attempt to authenticate a user using USER/PASS, via a perl subroutine. */
authcontext auth_perl_new_user_pass(const char *user, const char *local_part, const char *domain, const char *pass, const char *clienthost, const char *serverhost) {
#define MISSING(k) do { log_print(LOG_ERR, _("auth_perl_new_user_pass: missing key `%s' in response"), (k)); goto fail; } while(0)
#define INVALID(k, v) do { log_print(LOG_ERR, _("auth_perl_new_user_pass: invalid value `%s' for key `%s' in response"), (v), (k)); goto fail; } while(0)
stringmap S;
item *I;
authcontext a = NULL;
if (!pass_sub)
return NULL;
if (local_part && domain) {
if (!(S = auth_perl_callfn(pass_sub, 7, "method", "PASS", "user", user, "local_part", local_part, "domain", domain, "pass", pass, "clienthost", clienthost, "serverhost", serverhost)))
return NULL;
} else if (!(S = auth_perl_callfn(pass_sub, 5, "method", "PASS", "user", user, "pass", pass, "clienthost", clienthost, "serverhost", serverhost)))
return NULL;
if ((I = stringmap_find(S, "logmsg")))
log_print(LOG_INFO, "auth_perl_new_user_pass: (perl code): %s", (char*)I->v);
if (!(I = stringmap_find(S, "result"))) MISSING("result");
if (strcmp((char*)I->v, "YES") == 0) {
uid_t uid;
gid_t gid;
struct passwd *pw;
char *mailbox = NULL, *mboxdrv = NULL, *domain = NULL;
I = stringmap_find(S, "uid");
if (!I) MISSING("uid");
else if (!parse_uid(I->v, &uid)) INVALID("uid", (char*)I->v);
pw = getpwuid(uid);
if (!pw) INVALID("uid", (char*)I->v);
I = stringmap_find(S, "gid");
if (!I) MISSING("gid");
else if (!parse_gid(I->v, &gid)) INVALID("gid", (char*)I->v);
I = stringmap_find(S, "mailbox");
if (I) mailbox = (char*)I->v;
I = stringmap_find(S, "mboxtype");
if (I) mboxdrv = (char*)I->v;
I = stringmap_find(S, "domain");
if (I) domain = (char*)I->v;
a = authcontext_new(uid, gid, mboxdrv, mailbox, pw->pw_dir);
} else if (strcmp((char*)I->v, "NO") != 0) INVALID("result", (char*)I->v);
fail:
stringmap_delete_free(S);
return a;
#undef MISSING
#undef INVALID
}
/* auth_perl_onlogin:
* Pass details of a successful login to a perl subroutine. */
void auth_perl_onlogin(const authcontext A, const char *clienthost, const char *serverhost) {
stringmap S;
item *I;
if (!onlogin_sub || !(S = auth_perl_callfn(onlogin_sub, 6, "method", "ONLOGIN", "user", A->user, "local_part", A->local_part, "domain", A->domain, "clienthost", clienthost, "serverhost", serverhost)))
return;
if ((I = stringmap_find(S, "logmsg")))
log_print(LOG_INFO, "auth_perl_onlogin: (perl code): %s", (char*)I->v);
stringmap_delete_free(S);
}
#endif /* AUTH_PERL */
syntax highlighted by Code2HTML, v. 0.9.1