#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "patchlevel.h"
#include "ppport.h"
#define HASHGET(rv, k, l) (SV*)*hv_fetch((HV*)SvRV(rv), k, l, 0)
#define ACCOUNT2OTR(rv) HASHGET(rv, "otr", 3)
#define CONTACT2ACCOUNT(rv) HASHGET(rv, "act", 3)
#define CHANNEL2ACCOUNT(rv) CONTACT2ACCOUNT(HASHGET(rv, "cnt", 3))
#define CHANNEL2CONTACT(rv) HASHGET(rv, "cnt", 3)
#define ACCOUNT2CTX(rv) INT2PTR(Protocol__OTR,SvIV((SV*)SvRV(ACCOUNT2OTR(rv))))
#define CHANNEL2CTX(rv) ACCOUNT2CTX(CHANNEL2ACCOUNT(rv))
/* There is a struct name conflict with perl.h */
#define context otr_context
#include <libotr/context.h>
#undef context
/* libotr */
#include <libotr/proto.h>
#include <libotr/privkey.h>
#include <libotr/message.h>
static const struct mmsByProto {
char *protocol;
int mms;
} mmsTable[8] = {
{"prpl-msn", 1409},
{"prpl-icq", 2346},
{"prpl-aim", 2343},
{"prpl-yahoo", 799},
{"prpl-gg", 1999},
{"prpl-irc", 417},
{"prpl-oscar", 2343},
{NULL, 0}
};
static const char *TrustStates[] = {
"Not private",
"Unverified",
"Private",
"Finished"
};
typedef enum {
TRUST_NOT_PRIVATE,
TRUST_UNVERIFIED,
TRUST_PRIVATE,
TRUST_FINISHED
} TrustLevel;
typedef struct {
OtrlUserState userstate;
char * privkeys_file;
char * contacts_file;
char * instance_tags_file;
} OTRctx;
typedef OTRctx * Protocol__OTR;
static void write_fingerprints(OTRctx *ctx);
/* privkey.c - Convert a hex character to a value */
static unsigned int ctoh(unsigned char c)
{
if (c >= '0' && c <= '9') return c-'0';
if (c >= 'a' && c <= 'f') return c-'a'+10;
if (c >= 'A' && c <= 'F') return c-'A'+10;
return 0; /* Unknown hex char */
}
static OtrlPolicy policy_cb(void *opdata, ConnContext *context)
{
OtrlPolicy policy = OTRL_POLICY_DEFAULT;
if (!context) return policy;
SV *channel = (SV *)opdata;
policy = SvUV(HASHGET(channel, "policy", 6));
return policy;
}
static int is_logged_in_cb(void *opdata, const char *accountname,
const char *protocol, const char *recipient)
{
int count;
int is_logged_in = 0;
dSP;
SV *channel = (SV *)opdata;
if ( ! hv_exists((HV*)SvRV(channel), "on_is_contact_logged_in", 23) ) return -1;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_is_contact_logged_in", 23 )));
PUTBACK;
count = call_method( "_ev", G_SCALAR );
if ( count != 1 ) {
croak("on_is_contact_logged_in() callback did not return scalar value");
}
SPAGAIN;
is_logged_in = POPi;
if ( is_logged_in < -1 || is_logged_in > 1 ) {
croak("on_is_contact_logged_in() callback must return: -1, 0 or 1");
}
PUTBACK;
FREETMPS;
LEAVE;
return is_logged_in;
}
static void inject_message_cb(void *opdata, const char *accountname,
const char *protocol, const char *recipient, const char *message)
{
dSP;
SV *channel = (SV *)opdata;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_write", 8 )));
XPUSHs( sv_2mortal( newSVpv( message, 0 )));
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static void send_default_query_msg(SV *channel)
{
char * init_msg;
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
unsigned int channel_policy = SvUV(HASHGET(channel, "policy", 6));
char * contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
char * account_name = SvPV_nolen(HASHGET(account, "name", 4));
char * account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
init_msg = otrl_proto_default_query_msg( account_name, channel_policy);
inject_message_cb(
(void*) channel,
account_name,
account_protocol,
contact_name,
init_msg
);
free(init_msg);
}
static void write_fingerprints(OTRctx *ctx)
{
FILE * fs;
gcry_error_t err;
/* contacts_file */
fs = fopen(ctx->contacts_file,"wb");
if ( ! fs ) return;
err = otrl_privkey_write_fingerprints_FILEp(ctx->userstate, fs);
if (fs) fclose(fs);
if ( err ) {
croak("Cannot update contacts files: %s\n", gcry_strerror(err));
}
}
static void write_fingerprints_cb(void *opdata)
{
SV *channel = (SV *)opdata;
OTRctx * ctx = CHANNEL2CTX(channel);
write_fingerprints(ctx);
}
static void update_context_list_cb(void *opdata)
{
SV *channel = (SV *)opdata;
OTRctx * ctx = CHANNEL2CTX(channel);
write_fingerprints(ctx);
}
static void gone_secure_mark_only_cb(void *opdata, ConnContext *context)
{
SV *channel = (SV *)opdata;
(void)hv_store((HV*)SvRV(channel), "gone_secure", 11, &PL_sv_yes, 0);
}
static void update_channel_sessions(SV *channel, ConnContext *context)
{
if ( context->their_instance ) {
char itoa[20] = {0};
HV *sessions = (HV*)SvRV(HASHGET(channel, "known_sessions", 14));
snprintf(itoa, 20, "%u", context->their_instance);
(void)hv_store(sessions, itoa, strlen(itoa), newSViv(1), 0);
}
}
static void gone_secure_cb(void *opdata, ConnContext *context)
{
dSP;
SV *channel = (SV *)opdata;
update_channel_sessions(channel, context);
if ( ! hv_exists((HV*)SvRV(channel), "on_gone_secure", 14) ) return;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_gone_secure", 14 )));
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static void gone_insecure_cb(void *opdata, ConnContext *context)
{
dSP;
SV *channel = (SV *)opdata;
update_channel_sessions(channel, context);
if ( ! hv_exists((HV*)SvRV(channel), "on_gone_insecure", 16) ) return;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_gone_insecure", 16 )));
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static void still_secure_cb(void *opdata, ConnContext *context, int is_reply)
{
dSP;
SV *channel = (SV *)opdata;
if ( is_reply != 0 ) return;
update_channel_sessions(channel, context);
if ( ! hv_exists((HV*)SvRV(channel), "on_still_secure", 15) ) return;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_still_secure", 15 )));
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static void new_fingerprint_cb(void *opdata, OtrlUserState us,
const char *accountname, const char *protocol, const char *username,
unsigned char fingerprint[20])
{
ConnContext *context;
int seenbefore = 0;
char *hex_fingerprint;
dSP;
SV *channel = (SV *)opdata;
/* just skip */
if ( ! hv_exists((HV*)SvRV(channel), "on_unverified_fingerprint", 25) ) return;
/* Figure out if this is the first fingerprint we've seen for this
* user. */
context = otrl_context_find(us, username, accountname, protocol,
OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
if (context) {
Fingerprint *fp;
for ( fp = context->fingerprint_root.next; fp; fp = fp->next ) {
if (memcmp(fingerprint, fp->fingerprint, 20)) {
/* This is a previously seen fingerprint for this user,
* different from the one we were passed. */
seenbefore = 1;
break;
}
}
}
Newx(hex_fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN, char);
otrl_privkey_hash_to_human(hex_fingerprint, fingerprint);
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_unverified_fingerprint", 25 )));
XPUSHs( sv_2mortal( newSVpvn( hex_fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN - 1)));
Safefree(hex_fingerprint);
if (seenbefore) {
XPUSHs( sv_2mortal( &PL_sv_yes ) );
} else {
XPUSHs( sv_2mortal( &PL_sv_no ) );
}
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static int max_message_size_cb(void *opdata, ConnContext *context)
{
SV *channel = (SV *)opdata;
int max_message_size = SvIV(HASHGET(channel, "max_message_size", 16));
if ( max_message_size > 0 ) {
return max_message_size;
} else {
int i;
HV * account = (HV*)CHANNEL2ACCOUNT(channel);
char *account_protocol;
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
for ( i = 0; mmsTable[i].protocol != NULL; i++ ) {
if ( strEQ(mmsTable[i].protocol, account_protocol) ) {
return mmsTable[i].mms;
}
}
}
/* unlimited */
return 0;
}
static void received_symkey_cb(void *opdata, ConnContext *context,
unsigned int use, const unsigned char *usedata,
size_t usedatalen, const unsigned char *symkey)
{
dSP;
SV *channel = (SV *)opdata;
if ( ! hv_exists((HV*)SvRV(channel), "on_symkey", 9) ) return;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_symkey", 9 )));
XPUSHs( sv_2mortal( newSVpvn( (char*)symkey, OTRL_EXTRAKEY_BYTES )));
XPUSHs( sv_2mortal( newSVuv( use )));
if ( usedata ) {
XPUSHs( sv_2mortal( newSVpvn( (char *)usedata, usedatalen )));
}
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static const char* error_message_cb(void *opdata, ConnContext *context,
OtrlErrorCode err_code)
{
char * err_msg;
int count;
dSP;
SV *channel = (SV *)opdata;
if ( ! hv_exists((HV*)SvRV(channel), "on_error", 8) ) return NULL;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_error", 8 )));
XPUSHs( sv_2mortal( newSViv( err_code )));
PUTBACK;
count = call_method( "_ev", G_SCALAR );
if ( count != 1 ) {
croak("on_error() callback did not return message string");
}
SPAGAIN;
err_msg = savepv( POPp );
PUTBACK;
FREETMPS;
LEAVE;
return err_msg;
}
static void error_message_free_cb(void *opdata, const char *err_msg)
{
if (err_msg) Safefree((char *)err_msg);
}
static void smp_event_cb(SV *channel, OtrlSMPEvent smp_event, unsigned short progress_percent)
{
dSP;
if ( ! hv_exists((HV*)SvRV(channel), "on_smp_event", 12) ) return;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_smp_event", 12 )));
XPUSHs( sv_2mortal( newSViv( smp_event )));
XPUSHs( sv_2mortal( newSViv( progress_percent )));
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static void handle_smp_event_cb(void *, OtrlSMPEvent , ConnContext *, unsigned short , char *);
static void handle_msg_event_cb(void *opdata, OtrlMessageEvent msg_event, ConnContext *context,
const char* message, gcry_error_t err)
{
dSP;
SV *channel = (SV *)opdata;
if ( ! hv_exists((HV*)SvRV(channel), "on_event", 8) ) return;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_event", 8 )));
XPUSHs( sv_2mortal( newSViv( msg_event )));
switch ( msg_event ) {
case OTRL_MSGEVENT_SETUP_ERROR:
XPUSHs( sv_2mortal( newSVpv( gcry_strerror(err), 0 )));
break;
case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
XPUSHs( sv_2mortal( newSVpv( message, 0 )));
break;
default:
break;
}
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static void create_instag_cb(void *opdata, const char *accountname,
const char *protocol)
{
FILE * fs;
SV *channel = (SV *)opdata;
OTRctx * ctx = CHANNEL2CTX(channel);
/* contacts_file */
fs = fopen(ctx->instance_tags_file,"w+b");
if ( ! fs ) return;
otrl_instag_generate_FILEp(ctx->userstate, fs, accountname, protocol);
fclose(fs);
}
static void timer_control_cb(void *, unsigned int);
static void convert_data_cb(void *opdata, ConnContext *context,
OtrlConvertType convert_type, char ** dest, const char *src)
{
char *cb_name;
int count;
dSP;
SV *channel = (SV *)opdata;
if ( convert_type == OTRL_CONVERT_SENDING ) {
if ( hv_exists((HV*)SvRV(channel), "on_before_encrypt", 17) ) {
cb_name = "on_before_encrypt";
} else {
*dest = NULL;
return;
}
} else { /* OTRL_CONVERT_RECEIVING */
if ( hv_exists((HV*)SvRV(channel), "on_after_decrypt", 16) ) {
cb_name = "on_after_decrypt";
} else {
*dest = NULL;
return;
}
}
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( cb_name, strlen(cb_name) )));
XPUSHs( sv_2mortal( newSVpv( src, 0 )));
PUTBACK;
count = call_method( "_ev", G_SCALAR );
if ( count != 1 ) {
croak("%s() callback did not return message string", cb_name);
}
SPAGAIN;
*dest = savepv( POPp );
PUTBACK;
FREETMPS;
LEAVE;
}
static void convert_data_free_cb(void *opdata, ConnContext *context, char *dest)
{
if (dest) Safefree(dest);
}
static OtrlMessageAppOps callbacks = {
policy_cb,
NULL, /* missing privkey is generated when creating account obj */
is_logged_in_cb,
inject_message_cb,
update_context_list_cb,
new_fingerprint_cb,
write_fingerprints_cb,
gone_secure_mark_only_cb /* gone_secure_cb */,
gone_insecure_cb,
still_secure_cb,
max_message_size_cb,
NULL, /* account_name */
NULL, /* account_name_free */
received_symkey_cb,
error_message_cb,
error_message_free_cb,
NULL /* resent_msg_prefix_cb */,
NULL /* resent_msg_prefix_free_cb */,
handle_smp_event_cb,
handle_msg_event_cb,
create_instag_cb,
convert_data_cb,
convert_data_free_cb,
timer_control_cb
};
static void smp_event_popup(SV *channel, ConnContext *context, char *question)
{
int count;
OTRctx * ctx = CHANNEL2CTX(channel);
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_smp", 6 )));
if ( question != NULL ) {
XPUSHs( sv_2mortal( newSVpv( question, 0 )));
}
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
}
static void handle_smp_event_cb(void *opdata, OtrlSMPEvent smp_event, ConnContext *context,
unsigned short progress_percent, char *question)
{
SV *channel = (SV *)opdata;
OTRctx * ctx;
if ( ! context ) return;
if ( ! hv_exists((HV*)SvRV(channel), "on_smp", 6) ) return;
ctx = CHANNEL2CTX(channel);
switch (smp_event)
{
case OTRL_SMPEVENT_NONE :
smp_event_cb(channel, smp_event, progress_percent);
break;
case OTRL_SMPEVENT_ASK_FOR_SECRET :
smp_event_popup( channel, context, NULL);
break;
case OTRL_SMPEVENT_ASK_FOR_ANSWER :
smp_event_popup( channel, context, question);
break;
case OTRL_SMPEVENT_CHEATED :
otrl_message_abort_smp(
ctx->userstate,
&callbacks,
channel,
context
);
/* FALLTHROUGH */
case OTRL_SMPEVENT_IN_PROGRESS :
case OTRL_SMPEVENT_SUCCESS :
case OTRL_SMPEVENT_FAILURE :
case OTRL_SMPEVENT_ABORT :
smp_event_cb(channel, smp_event, progress_percent);
break;
case OTRL_SMPEVENT_ERROR :
otrl_message_abort_smp(
ctx->userstate,
&callbacks,
channel,
context
);
smp_event_cb(channel, smp_event, progress_percent);
break;
}
}
static void timer_control_cb(void *opdata, unsigned int interval)
{
SV *channel = (SV *)opdata;
/* user provided timer function */
if ( hv_exists((HV*)SvRV(channel), "on_timer", 8) ) {
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_timer", 8 )));
XPUSHs( sv_2mortal( newSVuv( interval )));
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
} else if ( interval > 0 ) {
OTRctx * ctx = CHANNEL2CTX(channel);
otrl_message_poll(ctx->userstate, &callbacks, channel);
}
}
/* (From otr-plugin.c) What level of trust do we have in the privacy of this ConnContext? */
TrustLevel otrp_plugin_context_to_trust(ConnContext *context)
{
TrustLevel level = TRUST_NOT_PRIVATE;
if (context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
if (context->active_fingerprint &&
context->active_fingerprint->trust &&
context->active_fingerprint->trust[0] != '\0') {
level = TRUST_PRIVATE;
} else {
level = TRUST_UNVERIFIED;
}
} else if (context && context->msgstate == OTRL_MSGSTATE_FINISHED) {
level = TRUST_FINISHED;
}
return level;
}
MODULE = Protocol::OTR PACKAGE = Protocol::OTR
BOOT:
{
#if (PATCHLEVEL > 4) || ((PATCHLEVEL == 4) && (SUBVERSION >= 70))
HV *pstash = gv_stashpv("Protocol::OTR", 0);
newCONSTSUB(pstash, "POLICY_OPPORTUNISTIC", newSVuv(OTRL_POLICY_OPPORTUNISTIC) );
newCONSTSUB(pstash, "POLICY_ALWAYS", newSVuv(OTRL_POLICY_ALWAYS) );
newCONSTSUB(pstash, "ERRCODE_NONE", newSVuv(OTRL_ERRCODE_NONE) );
newCONSTSUB(pstash, "ERRCODE_ENCRYPTION_ERROR", newSVuv(OTRL_ERRCODE_ENCRYPTION_ERROR) );
newCONSTSUB(pstash, "ERRCODE_MSG_NOT_IN_PRIVATE", newSVuv(OTRL_ERRCODE_MSG_NOT_IN_PRIVATE) );
newCONSTSUB(pstash, "ERRCODE_MSG_UNREADABLE", newSVuv(OTRL_ERRCODE_MSG_UNREADABLE) );
newCONSTSUB(pstash, "ERRCODE_MSG_MALFORMED", newSVuv(OTRL_ERRCODE_MSG_MALFORMED) );
newCONSTSUB(pstash, "MSGEVENT_NONE", newSVuv(OTRL_MSGEVENT_NONE) );
newCONSTSUB(pstash, "MSGEVENT_ENCRYPTION_REQUIRED", newSVuv(OTRL_MSGEVENT_ENCRYPTION_REQUIRED) );
newCONSTSUB(pstash, "MSGEVENT_ENCRYPTION_ERROR", newSVuv(OTRL_MSGEVENT_ENCRYPTION_ERROR) );
newCONSTSUB(pstash, "MSGEVENT_CONNECTION_ENDED", newSVuv(OTRL_MSGEVENT_CONNECTION_ENDED) );
newCONSTSUB(pstash, "MSGEVENT_SETUP_ERROR", newSVuv(OTRL_MSGEVENT_SETUP_ERROR) );
newCONSTSUB(pstash, "MSGEVENT_MSG_REFLECTED", newSVuv(OTRL_MSGEVENT_MSG_REFLECTED) );
newCONSTSUB(pstash, "MSGEVENT_MSG_RESENT", newSVuv(OTRL_MSGEVENT_MSG_RESENT) );
newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_NOT_IN_PRIVATE", newSVuv(OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE) );
newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_UNREADABLE", newSVuv(OTRL_MSGEVENT_RCVDMSG_UNREADABLE) );
newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_MALFORMED", newSVuv(OTRL_MSGEVENT_RCVDMSG_MALFORMED) );
newCONSTSUB(pstash, "MSGEVENT_LOG_HEARTBEAT_RCVD", newSVuv(OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD) );
newCONSTSUB(pstash, "MSGEVENT_LOG_HEARTBEAT_SENT", newSVuv(OTRL_MSGEVENT_LOG_HEARTBEAT_SENT) );
newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_GENERAL_ERR", newSVuv(OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR) );
newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_UNENCRYPTED", newSVuv(OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED) );
newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_UNRECOGNIZED", newSVuv(OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED) );
newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE", newSVuv(OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE) );
newCONSTSUB(pstash, "SMPEVENT_NONE", newSVuv(OTRL_SMPEVENT_NONE) );
newCONSTSUB(pstash, "SMPEVENT_CHEATED", newSVuv(OTRL_SMPEVENT_CHEATED) );
newCONSTSUB(pstash, "SMPEVENT_IN_PROGRESS", newSVuv(OTRL_SMPEVENT_IN_PROGRESS) );
newCONSTSUB(pstash, "SMPEVENT_SUCCESS", newSVuv(OTRL_SMPEVENT_SUCCESS) );
newCONSTSUB(pstash, "SMPEVENT_FAILURE", newSVuv(OTRL_SMPEVENT_FAILURE) );
newCONSTSUB(pstash, "SMPEVENT_ABORT", newSVuv(OTRL_SMPEVENT_ABORT) );
newCONSTSUB(pstash, "SMPEVENT_ERROR", newSVuv(OTRL_SMPEVENT_ERROR) );
newCONSTSUB(pstash, "INSTAG_BEST", newSVuv(OTRL_INSTAG_BEST) );
newCONSTSUB(pstash, "INSTAG_RECENT", newSVuv(OTRL_INSTAG_RECENT) );
newCONSTSUB(pstash, "INSTAG_RECENT_RECEIVED", newSVuv(OTRL_INSTAG_RECENT_RECEIVED) );
newCONSTSUB(pstash, "INSTAG_RECENT_SENT", newSVuv(OTRL_INSTAG_RECENT_SENT) );
#endif
/* Version check should be the very first call because it
makes sure that important subsystems are intialized. */
if (!gcry_check_version (GCRYPT_VERSION))
{
croak("libgcrypt version mismatch\n");
}
/* We don't want to see any warnings, e.g. because we have not yet
parsed program options which might be used to suppress such
warnings. */
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Allocate a pool of 16k secure memory. This make the secure memory
available and also drops privileges where needed. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
/* It is now okay to let Libgcrypt complain when there was/is
a problem with the secure memory. */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
if ( PerlEnv_getenv("PROTOCOL_OTR_ENABLE_QUICK_RANDOM") ) {
gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM, 0);
}
/* Tell Libgcrypt that initialization has completed. */
gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
OTRL_INIT;
}
#if (PATCHLEVEL < 4) || ((PATCHLEVEL == 4) && (SUBVERSION < 70))
PROTOTYPES: ENABLE
unsigned int
POLICY_OPPORTUNISTIC()
CODE:
RETVAL = OTRL_POLICY_OPPORTUNISTIC;
OUTPUT:
RETVAL
unsigned int
POLICY_ALWAYS()
CODE:
RETVAL = OTRL_POLICY_ALWAYS;
OUTPUT:
RETVAL
unsigned int
ERRCODE_NONE()
CODE:
RETVAL = OTRL_ERRCODE_NONE;
OUTPUT:
RETVAL
unsigned int
ERRCODE_ENCRYPTION_ERROR()
CODE:
RETVAL = OTRL_ERRCODE_ENCRYPTION_ERROR;
OUTPUT:
RETVAL
unsigned int
ERRCODE_MSG_NOT_IN_PRIVATE()
CODE:
RETVAL = OTRL_ERRCODE_MSG_NOT_IN_PRIVATE;
OUTPUT:
RETVAL
unsigned int
ERRCODE_MSG_UNREADABLE()
CODE:
RETVAL = OTRL_ERRCODE_MSG_UNREADABLE;
OUTPUT:
RETVAL
unsigned int
ERRCODE_MSG_MALFORMED()
CODE:
RETVAL = OTRL_ERRCODE_MSG_MALFORMED;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_NONE()
CODE:
RETVAL = OTRL_MSGEVENT_NONE;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_ENCRYPTION_REQUIRED()
CODE:
RETVAL = OTRL_MSGEVENT_ENCRYPTION_REQUIRED;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_ENCRYPTION_ERROR()
CODE:
RETVAL = OTRL_MSGEVENT_ENCRYPTION_ERROR;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_CONNECTION_ENDED()
CODE:
RETVAL = OTRL_MSGEVENT_CONNECTION_ENDED;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_SETUP_ERROR()
CODE:
RETVAL = OTRL_MSGEVENT_SETUP_ERROR;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_MSG_REFLECTED()
CODE:
RETVAL = OTRL_MSGEVENT_MSG_REFLECTED;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_MSG_RESENT()
CODE:
RETVAL = OTRL_MSGEVENT_MSG_RESENT;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_RCVDMSG_NOT_IN_PRIVATE()
CODE:
RETVAL = OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_RCVDMSG_UNREADABLE()
CODE:
RETVAL = OTRL_MSGEVENT_RCVDMSG_UNREADABLE;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_RCVDMSG_MALFORMED()
CODE:
RETVAL = OTRL_MSGEVENT_RCVDMSG_MALFORMED;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_LOG_HEARTBEAT_RCVD()
CODE:
RETVAL = OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_LOG_HEARTBEAT_SENT()
CODE:
RETVAL = OTRL_MSGEVENT_LOG_HEARTBEAT_SENT;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_RCVDMSG_GENERAL_ERR()
CODE:
RETVAL = OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_RCVDMSG_UNENCRYPTED()
CODE:
RETVAL = OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_RCVDMSG_UNRECOGNIZED()
CODE:
RETVAL = OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED;
OUTPUT:
RETVAL
unsigned int
MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE()
CODE:
RETVAL = OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE;
OUTPUT:
RETVAL
unsigned int
SMPEVENT_NONE()
CODE:
RETVAL = OTRL_SMPEVENT_NONE;
OUTPUT:
RETVAL
unsigned int
SMPEVENT_CHEATED()
CODE:
RETVAL = OTRL_SMPEVENT_CHEATED;
OUTPUT:
RETVAL
unsigned int
SMPEVENT_IN_PROGRESS()
CODE:
RETVAL = OTRL_SMPEVENT_IN_PROGRESS;
OUTPUT:
RETVAL
unsigned int
SMPEVENT_SUCCESS()
CODE:
RETVAL = OTRL_SMPEVENT_SUCCESS;
OUTPUT:
RETVAL
unsigned int
SMPEVENT_FAILURE()
CODE:
RETVAL = OTRL_SMPEVENT_FAILURE;
OUTPUT:
RETVAL
unsigned int
SMPEVENT_ABORT()
CODE:
RETVAL = OTRL_SMPEVENT_ABORT;
OUTPUT:
RETVAL
unsigned int
SMPEVENT_ERROR()
CODE:
RETVAL = OTRL_SMPEVENT_ERROR;
OUTPUT:
RETVAL
unsigned int
INSTAG_BEST()
CODE:
RETVAL = OTRL_INSTAG_BEST;
OUTPUT:
RETVAL
unsigned int
INSTAG_RECENT()
CODE:
RETVAL = OTRL_INSTAG_RECENT;
OUTPUT:
RETVAL
unsigned int
INSTAG_RECENT_RECEIVED()
CODE:
RETVAL = OTRL_INSTAG_RECENT_RECEIVED;
OUTPUT:
RETVAL
unsigned int
INSTAG_RECENT_SENT()
CODE:
RETVAL = OTRL_INSTAG_RECENT_SENT;
OUTPUT:
RETVAL
PROTOTYPES: DISABLE
#endif
Protocol::OTR
_new(privkeys_file, contacts_file, instance_tags_file)
char * privkeys_file
char * contacts_file
char * instance_tags_file
PROTOTYPE: $$$
PREINIT:
FILE *fs;
gcry_error_t err;
OTRctx * ctx;
SV *obj;
CODE:
{
Newx(ctx, 1, OTRctx);
ctx->privkeys_file = savepv(privkeys_file);
ctx->contacts_file = savepv(contacts_file);
ctx->instance_tags_file = savepv(instance_tags_file);
ctx->userstate = otrl_userstate_create();
/* privkeys_file */
fs = fopen(ctx->privkeys_file,"rb");
err = otrl_privkey_read_FILEp(ctx->userstate, fs);
if (fs) fclose(fs);
if ( err ) {
croak("Cannot read/write privkeys_file: %s", gcry_strerror(err));
}
/* contacts_file */
fs = fopen(ctx->contacts_file,"rb");
err = otrl_privkey_read_fingerprints_FILEp(ctx->userstate, fs, NULL, NULL);
if (fs) fclose(fs);
if ( err ) {
croak("Cannot read/write contacts_file: %s", gcry_strerror(err));
}
/* instance_tags_file */
fs = fopen(ctx->instance_tags_file,"rb");
err = otrl_instag_read_FILEp(ctx->userstate, fs);
if (fs) fclose(fs);
if ( err ) {
croak("Cannot read/write instance_tags_file: %s", gcry_strerror(err));
}
RETVAL = ctx;
}
OUTPUT:
RETVAL
SV *
_accounts(ctx)
Protocol::OTR ctx
PROTOTYPE: $
PREINIT:
OtrlPrivKey *p;
AV * list;
char *fingerprint;
CODE:
{
list = (AV *)sv_2mortal( (SV *)newAV() );
for(p=ctx->userstate->privkey_root; p; p=p->next) {
HV *ac = (HV*)sv_2mortal((SV*)newHV());
(void)hv_store(ac, "name", 4, newSVpv( p->accountname, 0 ), 0);
(void)hv_store(ac, "protocol", 8, newSVpv( p->protocol, 0 ), 0);
Newx(fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN, char);
fingerprint = otrl_privkey_fingerprint(ctx->userstate, fingerprint, p->accountname, p->protocol);
(void)hv_store(ac, "fingerprint", 11, newSVpv( fingerprint, 0 ), 0);
Safefree(fingerprint);
av_push(list, newRV((SV*)ac));
}
RETVAL = newRV_inc((SV*) list);
}
OUTPUT:
RETVAL
SV *
_account(ctx, accountname, protocol)
Protocol::OTR ctx
char * accountname
char * protocol
ALIAS:
_find_account = 1
PROTOTYPE: $$$
PREINIT:
OtrlPrivKey *p;
char *fingerprint;
CODE:
{
p = otrl_privkey_find(ctx->userstate, accountname, protocol);
if ( ! p ) {
if ( ix == 1 ) XSRETURN_UNDEF;
FILE * fs;
gcry_error_t err;
/* privkeys_file */
fs = fopen(ctx->privkeys_file,"w+b");
if ( ! fs ) croak("Could not open %s for updating: %s", ctx->privkeys_file, Strerror(errno));
err = otrl_privkey_generate_FILEp(ctx->userstate, fs, accountname, protocol);
if (fs) fclose(fs);
if (err) {
croak("Cannot generate: %s", gcry_strerror(err));
}
}
Newx(fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN, char);
fingerprint = otrl_privkey_fingerprint(ctx->userstate, fingerprint, accountname, protocol);
RETVAL = newSVpvn(fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN - 1);
Safefree(fingerprint);
}
OUTPUT:
RETVAL
void
DESTROY(self)
Protocol::OTR self
CODE:
{
if ( self->userstate ) {
otrl_userstate_free(self->userstate);
self->userstate = NULL;
}
Safefree(self->privkeys_file);
Safefree(self->contacts_file);
Safefree(self->instance_tags_file);
Safefree(self);
}
MODULE = Protocol::OTR PACKAGE = Protocol::OTR::Account
void
_contact(account, username, sv_fprint, verified)
SV * account
char * username
SV * sv_fprint
SV * verified
PROTOTYPE: $$;$$
PREINIT:
OTRctx *ctx;
unsigned char fingerprint[20] = {0};
int with_fingerprint = 0;
int is_verified = 0;
ConnContext *context;
Fingerprint *fprint;
int context_created = 0;
int fprint_added = 0;
char * account_name;
char * account_protocol;
PPCODE:
{
ctx = ACCOUNT2CTX(account);
if ( SvOK(sv_fprint) ) {
STRLEN flen;
unsigned char *fingerprint_hex = (unsigned char*)SvPV(sv_fprint, flen);
if (
( /* human readable string */
flen == 44
&& fingerprint_hex[8] == ' '
&& fingerprint_hex[17] == ' '
&& fingerprint_hex[26] == ' '
&& fingerprint_hex[35] == ' '
)
||
( /* hex encoded string */
flen == 40
&&
strchr((char*)fingerprint_hex, ' ') == NULL
)
) {
int j, i;
for(j=0, i=0; j<=20; i+=2) {
fingerprint[j++] = (ctoh(fingerprint_hex[i]) << 4) + (ctoh(fingerprint_hex[i+1]));
if (flen == 44 && j % 4 == 0 ) { /* skip spaces */
i++;
}
}
with_fingerprint = 1;
}
else {
croak("Fingerprint has invalid format: %s", fingerprint_hex);
}
if ( SvOK(verified) && SvTRUE(verified) ) {
is_verified = 1;
}
}
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, username, account_name, account_protocol,
OTRL_INSTAG_BEST, 1, &context_created, NULL, NULL );
if ( context ) {
if ( context_created && ! with_fingerprint ) {
//croak("New contact requires fingerprint");
}
if ( with_fingerprint ) {
fprint = otrl_context_find_fingerprint(context, fingerprint, 1, &fprint_added);
otrl_context_set_trust(fprint, is_verified ? "verified" : NULL);
}
if ( context_created || fprint_added ) {
write_fingerprints(ctx);
}
XSRETURN_YES;
}
croak("Could not find or create contact");
}
SV *
_contacts(account)
SV * account
PROTOTYPE: $
PREINIT:
AV * list;
ConnContext *context;
OTRctx *ctx;
char * account_name;
char * account_protocol;
CODE:
{
ctx = ACCOUNT2CTX(account);
list = (AV *)sv_2mortal( (SV *)newAV() );
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
for(context = ctx->userstate->context_root; context; context = context->next) {
/* Fingerprints are only stored in the master contexts */
if (context->their_instance != OTRL_INSTAG_MASTER) continue;
/* Found matching account */
if ( ! strEQ(context->accountname, account_name) ) continue;
if ( ! strEQ(context->protocol, account_protocol) ) continue;
av_push(list, newSVpv( context->username, 0 ));
}
RETVAL = newRV_inc((SV*) list);
}
OUTPUT:
RETVAL
MODULE = Protocol::OTR PACKAGE = Protocol::OTR::Contact
SV *
_fingerprints(contact)
SV * contact
PROTOTYPE: \%
PREINIT:
AV * list;
ConnContext *context;
Fingerprint *fingerprint;
OTRctx *ctx;
char *contact_name;
char *account_name;
char *account_protocol;
CODE:
{
HV *account = (HV*)HASHGET(contact, "act", 3);
ctx = ACCOUNT2CTX(account);
list = (AV *)sv_2mortal( (SV *)newAV() );
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
for(context = ctx->userstate->context_root; context; context = context->next) {
/* Fingerprints are only stored in the master contexts */
if (context->their_instance != OTRL_INSTAG_MASTER) continue;
/* Found matching account */
if ( ! strEQ(context->accountname, account_name) ) continue;
if ( ! strEQ(context->protocol, account_protocol) ) continue;
if ( ! strEQ(context->username, contact_name) ) continue;
/* Don't bother with the first (fingerprintless) entry. */
for (fingerprint = context->fingerprint_root.next; fingerprint; fingerprint = fingerprint->next) {
ConnContext *context_iter;
TrustLevel best_level = TRUST_NOT_PRIVATE;
int used = 0;
HV *fprint = (HV*)sv_2mortal((SV*)newHV());
for (context_iter = context->m_context;
context_iter && context_iter->m_context == context->m_context;
context_iter = context_iter->next) {
TrustLevel this_level = TRUST_NOT_PRIVATE;
if (context_iter->active_fingerprint == fingerprint) {
this_level = otrp_plugin_context_to_trust(context_iter);
used = 1;
if (this_level == TRUST_PRIVATE) {
best_level = TRUST_PRIVATE;
} else if (this_level == TRUST_UNVERIFIED
&& best_level != TRUST_PRIVATE) {
best_level = TRUST_UNVERIFIED;
} else if (this_level == TRUST_FINISHED
&& best_level == TRUST_NOT_PRIVATE) {
best_level = TRUST_FINISHED;
}
}
}
(void)hv_store(fprint, "fingerprint", 11, newSVpvn( (char*)fingerprint->fingerprint, 20), 0);
(void)hv_store(fprint, "is_verified", 11, otrl_context_is_fingerprint_trusted(fingerprint) ? &PL_sv_yes : &PL_sv_no , 0);
(void)hv_store(fprint, "status", 6, newSVpv( used ? TrustStates[best_level] : "Unused", 0 ), 0);
av_push(list, newRV((SV*)fprint));
}
}
RETVAL = newRV_inc((SV*) list);
}
OUTPUT:
RETVAL
void
_active_fingerprint(contact)
SV * contact
PROTOTYPE: \%
PREINIT:
ConnContext *context;
Fingerprint *fingerprint;
OTRctx *ctx;
char *contact_name;
char *account_name;
char *account_protocol;
PPCODE:
{
HV *account = (HV*)HASHGET(contact, "act", 3);
ctx = ACCOUNT2CTX(account);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL );
if (context) {
/* Don't bother with the first (fingerprintless) entry. */
for (fingerprint = context->fingerprint_root.next; fingerprint; fingerprint = fingerprint->next) {
ConnContext *context_iter;
for (context_iter = context->m_context;
context_iter && context_iter->m_context == context->m_context;
context_iter = context_iter->next) {
if (context_iter->active_fingerprint == fingerprint) {
HV *fprint = (HV*)sv_2mortal((SV*)newHV());
TrustLevel this_level = otrp_plugin_context_to_trust(context_iter);
(void)hv_store(fprint, "fingerprint", 11, newSVpvn( (char *)fingerprint->fingerprint, 20), 0);
(void)hv_store(fprint, "is_verified", 11, otrl_context_is_fingerprint_trusted(fingerprint) ? &PL_sv_yes : &PL_sv_no , 0);
(void)hv_store(fprint, "status", 6, newSVpv( TrustStates[this_level], 0 ), 0);
XPUSHs(newRV_inc((SV*) fprint));
XSRETURN(1);
}
}
}
}
XSRETURN_UNDEF;
}
MODULE = Protocol::OTR PACKAGE = Protocol::OTR::Fingerprint
void
hash(fprint)
SV *fprint
PROTOTYPE: $
PREINIT:
char *hex_fingerprint;
unsigned char *fprint_bytes;
PPCODE:
{
fprint_bytes = (unsigned char*)SvPVbyte_nolen(HASHGET(fprint, "fingerprint", 11));
Newx(hex_fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN, char);
otrl_privkey_hash_to_human(hex_fingerprint, fprint_bytes);
XPUSHs( sv_2mortal( newSVpvn( hex_fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN - 1)));
Safefree(hex_fingerprint);
XSRETURN(1);
}
void
set_verified(fprint, verified)
SV *fprint
SV * verified
PROTOTYPE: $$
PREINIT:
OTRctx * ctx;
ConnContext * context;
Fingerprint * fingerprint;
int is_verified;
char *contact_name;
char *account_name;
char *account_protocol;
PPCODE:
{
HV *contact = (HV*)HASHGET(fprint, "cnt", 3);
HV *account = (HV*)HASHGET(contact, "act", 3);
ctx = ACCOUNT2CTX(account);
is_verified = SvOK(verified) && SvTRUE(verified) ? 1 : 0;
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(ctx->userstate, contact_name, account_name, account_protocol,
OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
if ( context ) {
unsigned char *fprint_bytes = (unsigned char*)SvPV_nolen(HASHGET(fprint, "fingerprint", 11));
fingerprint = otrl_context_find_fingerprint(context, fprint_bytes, 0, NULL);
if ( fingerprint ) {
otrl_context_set_trust(fingerprint, verified ? "verified" : "");
(void)hv_store((HV*)SvRV(fprint), "is_verified", 11, is_verified ? &PL_sv_yes : &PL_sv_no , 0);
write_fingerprints(ctx);
is_verified ? XSRETURN_YES : XSRETURN_NO;
}
}
XSRETURN_UNDEF;
}
MODULE = Protocol::OTR PACKAGE = Protocol::OTR::Channel
void
init(channel)
SV * channel
ALIAS:
refresh = 1
PROTOTYPE: $
PPCODE:
{
send_default_query_msg(channel);
XSRETURN_YES;
}
void
create_symkey(channel, use, use_for)
SV * channel
unsigned int use
SV * use_for
PROTOTYPE: $$$
PREINIT:
ConnContext *context;
int context_created = 0;
OTRctx * ctx;
char *contact_name;
char * account_name;
char * account_protocol;
PPCODE:
{
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = CHANNEL2CTX(channel);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
SvUV(HASHGET(channel, "selected_instag", 15)), 0, &context_created, NULL, NULL );
if ( context && ! context_created && context->msgstate == OTRL_MSGSTATE_ENCRYPTED ) {
unsigned char *symkey;
unsigned char *usedata;
STRLEN usedatalen;
gcry_error_t err;
usedata = (unsigned char*)SvPVbyte(use_for, usedatalen);
Newx(symkey, OTRL_EXTRAKEY_BYTES, unsigned char);
err = otrl_message_symkey(ctx->userstate,
&callbacks,
channel /* opdata */,
context,
use,
usedata,
(size_t)usedatalen,
symkey);
XPUSHs( sv_2mortal( newSVpvn( (char*)symkey, OTRL_EXTRAKEY_BYTES )));
Safefree(symkey);
XSRETURN( 1 );
}
XSRETURN_UNDEF;
}
void
finish(channel)
SV * channel
PROTOTYPE: $
PREINIT:
OTRctx * ctx;
char *contact_name;
char *account_name;
char *account_protocol;
PPCODE:
{
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = ACCOUNT2CTX(account);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
(void)hv_store((HV*)SvRV(channel), "gone_secure", 11, &PL_sv_no, 0);
otrl_message_disconnect_all_instances(
ctx->userstate,
&callbacks,
channel,
account_name,
account_protocol,
contact_name
);
XSRETURN_YES;
}
void
write(channel, plain_text)
SV * channel
char * plain_text
PROTOTYPE: $$
PREINIT:
OTRctx * ctx;
char *contact_name;
char *account_name;
char *account_protocol;
PPCODE:
{
ConnContext *context;
int context_created = 0;
gcry_error_t err;
char *newmessage = NULL;
unsigned int selected_instag;
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = ACCOUNT2CTX(account);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
selected_instag = SvUV(HASHGET(channel, "selected_instag", 15));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
selected_instag, 0, &context_created, NULL, NULL );
if ( context && context->msgstate == OTRL_MSGSTATE_FINISHED ) {
handle_msg_event_cb(
(void*) channel,
OTRL_MSGEVENT_CONNECTION_ENDED,
context,
NULL,
err
);
XSRETURN_NO;
}
err = otrl_message_sending(ctx->userstate,
&callbacks,
channel /* opdata */,
account_name, account_protocol, contact_name,
selected_instag,
plain_text,
NULL /* tlvs */,
&newmessage,
OTRL_FRAGMENT_SEND_ALL,
&context,
NULL /* add_app_info */,
NULL /* add_app_info_data */);
if ( err ) {
otrl_message_free(newmessage);
croak("Cannot send: %s", gcry_strerror(err));
}
otrl_message_free(newmessage);
XSRETURN_YES;
}
void
ping(channel)
SV * channel
PROTOTYPE: $
PPCODE:
{
OTRctx * ctx = CHANNEL2CTX(channel);
otrl_message_poll(ctx->userstate, &callbacks, channel);
XSRETURN_YES;
}
void
smp_verify(channel, answer, ...)
SV * channel
SV * answer
PROTOTYPE: $$;$
PPCODE:
{
ConnContext *context;
int context_created = 0;
char * question = NULL;
STRLEN slen;
unsigned char * secret;
OTRctx * ctx;
char *contact_name;
char * account_name;
char * account_protocol;
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = CHANNEL2CTX(channel);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
SvUV(HASHGET(channel, "selected_instag", 15)), 0, &context_created, NULL, NULL );
if ( ! context ) XSRETURN_NO;
secret = (unsigned char*)SvPVbyte(answer, slen);
if ( items == 3 && SvOK(ST(2)) ) {
STRLEN qlen;
question = SvPVbyte(ST(2), qlen);
otrl_message_initiate_smp_q(
ctx->userstate,
&callbacks,
channel,
context,
question,
secret,
slen
);
} else {
otrl_message_initiate_smp(
ctx->userstate,
&callbacks,
channel,
context,
secret,
slen
);
}
XSRETURN_YES;
}
void
smp_respond(channel, response)
SV * channel
unsigned char *response
PROTOTYPE: $$
PPCODE:
{
ConnContext *context;
int context_created = 0;
OTRctx * ctx;
char *contact_name;
char * account_name;
char * account_protocol;
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = CHANNEL2CTX(channel);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
SvUV(HASHGET(channel, "selected_instag", 15)), 0, &context_created, NULL, NULL );
if ( ! context ) XSRETURN_NO;
otrl_message_respond_smp(ctx->userstate, &callbacks, channel,
context, response, strlen((char*)response));
XSRETURN_YES;
}
void
smp_abort(channel)
SV * channel
PROTOTYPE: $
PPCODE:
{
ConnContext *context;
int context_created = 0;
OTRctx * ctx;
char *contact_name;
char * account_name;
char * account_protocol;
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = CHANNEL2CTX(channel);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
SvUV(HASHGET(channel, "selected_instag", 15)), 0, &context_created, NULL, NULL );
if ( ! context ) XSRETURN_NO;
otrl_message_abort_smp(
ctx->userstate,
&callbacks,
channel,
context
);
XSRETURN_YES;
}
void
read(channel, input)
SV * channel
char * input
PROTOTYPE: $$
PPCODE:
{
int res;
char *newmessage = NULL;
OtrlTLV *tlvs = NULL;
OtrlTLV *tlv = NULL;
OtrlMessageType msgtype;
ConnContext *context;
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
OTRctx *ctx = ACCOUNT2CTX(account);
char *contact_name;
char *account_name;
char *account_protocol;
if ( ! input ) {
XSRETURN_NO;
}
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
msgtype = otrl_proto_message_type(input);
res = otrl_message_receiving(ctx->userstate,
&callbacks,
channel /* opdata */,
account_name, account_protocol,
contact_name,
input,
&newmessage,
&tlvs,
&context,
NULL /* add_app_info */,
NULL /* add_app_info_data */);
if ( context ) {
if (newmessage) {
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_read", 7 )));
XPUSHs( sv_2mortal( newSVpv( newmessage, 0 )));
PUTBACK;
otrl_message_free(newmessage);
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
} else {
otrl_message_free(newmessage);
}
}
tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED);
if (tlv) {
if ( hv_exists((HV*)SvRV(channel), "on_gone_insecure", 16) ) {
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs( channel );
XPUSHs( sv_2mortal( newSVpvn( "on_gone_insecure", 16 )));
PUTBACK;
call_method( "_ev", G_DISCARD | G_VOID );
FREETMPS;
LEAVE;
(void)hv_store((HV*)SvRV(channel), "gone_secure", 11, &PL_sv_no, 0);
}
}
otrl_tlv_free(tlvs);
if ( res == 1) {
int gone_secure = SvTRUE(HASHGET(channel, "gone_secure", 11));
/* gone_secure */
if ( gone_secure
&&
( /* our init */
msgtype == OTRL_MSGTYPE_REVEALSIG
||
/* their init */
msgtype == OTRL_MSGTYPE_SIGNATURE
)
&& context->msgstate == OTRL_MSGSTATE_ENCRYPTED
&& context->auth.authstate == OTRL_AUTHSTATE_NONE
) {
gone_secure_cb(
(void*) channel,
context
);
}
XSRETURN_NO;
}
XSRETURN_YES;
}
void
sessions(channel)
SV * channel
PROTOTYPE: $
PREINIT:
HV * sessions;
HE * entry;
I32 count;
PPCODE:
{
sessions = (HV*)SvRV(HASHGET(channel, "known_sessions", 14));
count = hv_iterinit(sessions);
EXTEND(SP, count);
while ( (entry = hv_iternext(sessions)) ) {
I32 klen;
char *key = hv_iterkey( entry, &klen );
XPUSHs(sv_2mortal(newSVpvn(key, klen)));
}
}
void
current_session(channel)
SV * channel
PROTOTYPE: $
PREINIT:
ConnContext *context;
OTRctx * ctx;
char *contact_name;
char * account_name;
char * account_protocol;
PPCODE:
{
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = CHANNEL2CTX(channel);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
SvUV(HASHGET(channel, "selected_instag", 15)), 0, NULL, NULL, NULL );
if ( context ) {
XPUSHs( sv_2mortal( newSVuv(context->their_instance) ) );
XSRETURN(1);
}
XSRETURN_UNDEF;
}
void
select_session(channel, session)
SV * channel
SV * session
PROTOTYPE: $$
PREINIT:
otrl_instag_t instag;
ConnContext *context;
OTRctx * ctx;
char *contact_name;
char *account_name;
char *account_protocol;
PPCODE:
{
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = ACCOUNT2CTX(account);
instag = SvUV(session);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
instag, 0, NULL, NULL, NULL );
if ( context ) {
(void)hv_store((HV*)SvRV(channel), "selected_instag", 6, newSVuv(instag), 0);
send_default_query_msg(channel);
XSRETURN_YES;
}
XSRETURN_NO;
}
void
status(channel)
SV * channel
PROTOTYPE: $
PREINIT:
ConnContext *context;
Fingerprint *fingerprint;
OTRctx *ctx;
char *contact_name;
char *account_name;
char *account_protocol;
PPCODE:
{
HV * contact = (HV*)CHANNEL2CONTACT(channel);
HV * account = (HV*)CONTACT2ACCOUNT(contact);
ctx = ACCOUNT2CTX(account);
contact_name = SvPV_nolen(HASHGET(contact, "name", 4));
account_name = SvPV_nolen(HASHGET(account, "name", 4));
account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8));
context = otrl_context_find(
ctx->userstate, contact_name, account_name, account_protocol,
SvUV(HASHGET(channel, "selected_instag", 15)), 0, NULL, NULL, NULL );
if (context) {
TrustLevel this_level = otrp_plugin_context_to_trust(context);
XPUSHs( newSVpv( TrustStates[this_level], 0 ) );
XSRETURN(1);
}
XSRETURN_UNDEF;
}