From 7c37b2a4d64c722ad18ea4d04c5f4c5475134e47 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Tue, 28 Jun 2016 18:26:02 -0700 Subject: [PATCH] Add support for SASL mechanisms handled by scripts This implements the suggested API from #323 with a caveat: currently the SASL mechanisms are tied to IRC and needs a bit of extra work to make them generic enough to move to the regular src/core instead of solely in src/irc/core. This greatly simplifies scripts for each mechanism since this handles the splitting and reassembly of the 400 byte fragments going to and coming from the server. The ECDSA-NIST256P-CHALLENGE mechanism has been reimplemented using this new API and found to work. --- docs/help/in/network.in | 4 +- docs/signals.txt | 3 + src/core/server-setup-rec.h | 2 +- src/irc/core/irc-servers-setup.c | 29 ++-- src/irc/core/irc-servers.c | 2 +- src/irc/core/irc-servers.h | 2 +- src/irc/core/irc-session.c | 5 +- src/irc/core/sasl.c | 265 +++++++++++++++++++++++-------- src/irc/core/sasl.h | 10 +- src/perl/get-signals.pl | 1 + src/perl/irc/Irc.xs | 11 ++ src/perl/irc/Server.xs | 22 +++ src/perl/irc/module.h | 1 + src/perl/perl-signals.c | 19 ++- 14 files changed, 283 insertions(+), 93 deletions(-) diff --git a/docs/help/in/network.in b/docs/help/in/network.in index ba08ef69..1908d4ea 100644 --- a/docs/help/in/network.in +++ b/docs/help/in/network.in @@ -34,8 +34,8 @@ -cmdmax: Specifies the maximum number of commands to perform before starting the internal flood protection. -sasl_mechanism Specifies the mechanism to use for the SASL authentication. - At the moment irssi only supports the 'plain' and the - 'external' mechanisms. + Irssi supports the 'plain' and 'external' mechanisms by + default and other mechanisms through scripts. -sasl_username Specifies the username to use during the SASL authentication. -sasl_password Specifies the password to use during the SASL authentication. diff --git a/docs/signals.txt b/docs/signals.txt index 8b71fd95..159268be 100644 --- a/docs/signals.txt +++ b/docs/signals.txt @@ -137,6 +137,9 @@ irc-cap.c "server cap end", SERVER_REC sasl.c + "server sasl init ", SERVER_REC + "server sasl step ", SERVER_REC, GBytes * + "server sasl abort ", SERVER_REC "server sasl failure", SERVER_REC, char *reason "server sasl success", SERVER_REC diff --git a/src/core/server-setup-rec.h b/src/core/server-setup-rec.h index 2c9614c7..184d38d1 100644 --- a/src/core/server-setup-rec.h +++ b/src/core/server-setup-rec.h @@ -8,7 +8,7 @@ char *address; int port; char *password; -int sasl_mechanism; +char *sasl_mechanism; char *sasl_password; char *ssl_cert; diff --git a/src/irc/core/irc-servers-setup.c b/src/irc/core/irc-servers-setup.c index f425b587..d5908c2a 100644 --- a/src/irc/core/irc-servers-setup.c +++ b/src/irc/core/irc-servers-setup.c @@ -88,26 +88,23 @@ static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn, conn->max_query_chans = ircnet->max_query_chans; /* Validate the SASL parameters filled by sig_chatnet_read() or cmd_network_add */ - conn->sasl_mechanism = SASL_MECHANISM_NONE; + g_free_and_null(conn->sasl_mechanism); if (ircnet->sasl_mechanism != NULL) { - if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "plain")) { - /* The PLAIN method needs both the username and the password */ - if (ircnet->sasl_username != NULL && *ircnet->sasl_username && - ircnet->sasl_password != NULL && *ircnet->sasl_password) { - conn->sasl_mechanism = SASL_MECHANISM_PLAIN; - conn->sasl_username = ircnet->sasl_username; - conn->sasl_password = ircnet->sasl_password; - } else - g_warning("The fields sasl_username and sasl_password are either missing or empty"); + conn->sasl_mechanism = g_strdup(ircnet->sasl_mechanism); + if (ircnet->sasl_username != NULL && *ircnet->sasl_username) { + g_free_and_null(conn->sasl_username); + conn->sasl_username = g_strdup(ircnet->sasl_username); } - else if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "external")) { - conn->sasl_mechanism = SASL_MECHANISM_EXTERNAL; - conn->sasl_username = NULL; - conn->sasl_password = NULL; + if (ircnet->sasl_password != NULL && *ircnet->sasl_password) { + g_free_and_null(conn->sasl_password); + conn->sasl_password = g_strdup(ircnet->sasl_password); } - else - g_warning("Unsupported SASL mechanism \"%s\" selected", ircnet->sasl_mechanism); + + /* The PLAIN method needs both the username and the password */ + if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "plain") && + (conn->sasl_username == NULL || conn->sasl_password == NULL)) + g_warning("The fields sasl_username and sasl_password are either missing or empty"); } } diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index feb7cee8..9976cd0b 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -230,7 +230,7 @@ static void server_init(IRC_SERVER_REC *server) g_free(cmd); } - if (conn->sasl_mechanism != SASL_MECHANISM_NONE) + if (conn->sasl_mechanism != NULL) cap_toggle(server, "sasl", TRUE); cap_toggle(server, "multi-prefix", TRUE); diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h index 41c4b9c2..bbb0dd00 100644 --- a/src/irc/core/irc-servers.h +++ b/src/irc/core/irc-servers.h @@ -27,7 +27,7 @@ struct _IRC_SERVER_CONNECT_REC { char *usermode; char *alternate_nick; - int sasl_mechanism; + char *sasl_mechanism; char *sasl_username; char *sasl_password; diff --git a/src/irc/core/irc-session.c b/src/irc/core/irc-session.c index 18e8e5c7..f7c17d2a 100644 --- a/src/irc/core/irc-session.c +++ b/src/irc/core/irc-session.c @@ -67,7 +67,7 @@ static void sig_session_save_server(IRC_SERVER_REC *server, CONFIG_REC *config, config_node_set_str(config, node, "away_reason", server->away_reason); config_node_set_bool(config, node, "emode_known", server->emode_known); - config_node_set_int(config, node, "sasl_mechanism", server->connrec->sasl_mechanism); + config_node_set_str(config, node, "sasl_mechanism", server->connrec->sasl_mechanism); config_node_set_str(config, node, "sasl_username", server->connrec->sasl_username); config_node_set_str(config, node, "sasl_password", server->connrec->sasl_password); @@ -96,10 +96,11 @@ static void sig_session_restore_server(IRC_SERVER_REC *server, server->emode_known = config_node_get_bool(node, "emode_known", FALSE); server->isupport_sent = config_node_get_bool(node, "isupport_sent", FALSE); - server->connrec->sasl_mechanism = config_node_get_int(node, "sasl_mechanism", SASL_MECHANISM_NONE); /* The fields below might have been filled when loading the chatnet * description from the config and we favor the content that's been saved * in the session file over that. */ + g_free(server->connrec->sasl_mechanism); + server->connrec->sasl_mechanism = g_strdup(config_node_get_str(node, "sasl_mechanism", NULL)); g_free(server->connrec->sasl_username); server->connrec->sasl_username = g_strdup(config_node_get_str(node, "sasl_username", NULL)); g_free(server->connrec->sasl_password); diff --git a/src/irc/core/sasl.c b/src/irc/core/sasl.c index f080ae59..3d140a15 100644 --- a/src/irc/core/sasl.c +++ b/src/irc/core/sasl.c @@ -28,50 +28,131 @@ #define SASL_TIMEOUT (20 * 1000) // ms -static gboolean sasl_timeout(IRC_SERVER_REC *server) +static GSList *sasl_mechanisms; +static GHashTable *sasl_buffers; + +void sasl_mechanism_register(const char *name) +{ + if (gslist_find_string(sasl_mechanisms, name) == NULL) { + sasl_mechanisms = g_slist_append(sasl_mechanisms, g_strdup(name)); + } +} + +void sasl_mechanism_unregister(const char *name) +{ + GSList *pos; + + pos = gslist_find_string(sasl_mechanisms, name); + if (pos != NULL) { + g_free(pos->data); + sasl_mechanisms = g_slist_remove(sasl_mechanisms, pos->data); + } +} + +static gboolean is_registered_mechanism(const char *name) +{ + return gslist_find_string(sasl_mechanisms, name) != NULL; +} + +static void sasl_emit_abort_signal(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + + conn = server->connrec; + + if (is_registered_mechanism(conn->sasl_mechanism)) { + char *str; + + str = g_strconcat("server sasl abort ", conn->sasl_mechanism, NULL); + ascii_strdown(str+18); + if (!signal_emit(str, 1, server)) { + g_warning("No one handled the abort of SASL mechanism %s", + conn->sasl_mechanism); + } + g_free(str); + } +} + +/* + * This is the callback from g_timeout_add. + */ +static gboolean sasl_timeout_callback(IRC_SERVER_REC *server) { /* The authentication timed out, we can't do much beside terminating it */ - irc_send_cmd_now(server, "AUTHENTICATE *"); - cap_finish_negotiation(server); - - server->sasl_timeout = 0; - + sasl_abort(server); signal_emit("server sasl failure", 2, server, "The authentication timed out"); return FALSE; } +static void sasl_timeout_cancel(IRC_SERVER_REC *server) +{ + if (server->sasl_timeout != 0) { + g_source_remove(server->sasl_timeout); + server->sasl_timeout = 0; + } +} + +static void sasl_timeout_start(IRC_SERVER_REC *server) +{ + sasl_timeout_cancel(server); + server->sasl_timeout = g_timeout_add_seconds(SASL_TIMEOUT, (GSourceFunc) sasl_timeout_callback, + server); +} + +void sasl_abort(IRC_SERVER_REC *server) +{ + sasl_timeout_cancel(server); + + irc_send_cmd_now(server, "AUTHENTICATE *"); + cap_finish_negotiation(server); + + sasl_emit_abort_signal(server); +} + static void sasl_start(IRC_SERVER_REC *server, const char *data, const char *from) { IRC_SERVER_CONNECT_REC *conn; conn = server->connrec; - switch (conn->sasl_mechanism) { - case SASL_MECHANISM_PLAIN: - irc_send_cmd_now(server, "AUTHENTICATE PLAIN"); - break; + if (!g_strcmp0(conn->sasl_mechanism, "PLAIN")) { + irc_send_cmd_now(server, "AUTHENTICATE PLAIN"); + sasl_timeout_start(server); + } else if (!g_strcmp0(conn->sasl_mechanism, "external")) { + irc_send_cmd_now(server, "AUTHENTICATE EXTERNAL"); + sasl_timeout_start(server); + } else if (is_registered_mechanism(conn->sasl_mechanism)) { + char *str; - case SASL_MECHANISM_EXTERNAL: - irc_send_cmd_now(server, "AUTHENTICATE EXTERNAL"); - break; + str = g_strconcat("server sasl init ", conn->sasl_mechanism, NULL); + ascii_strdown(str+17); + if (!signal_emit(str, 1, server)) { + g_warning("Nothing handled initialization of SASL mechanism %s", + conn->sasl_mechanism); + } + g_free(str); + + str = g_strconcat("AUTHENTICATE ", conn->sasl_mechanism, NULL); + irc_send_cmd_now(server, str); + g_free(str); + sasl_timeout_start(server); + } else { + g_warning("Unsupported SASL mechanism \"%s\" selected", conn->sasl_mechanism); + sasl_abort(server); } - server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server); } static void sasl_fail(IRC_SERVER_REC *server, const char *data, const char *from) { char *params, *error; - /* Stop any pending timeout, if any */ - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_cancel(server); params = event_get_params(data, 2, NULL, &error); signal_emit("server sasl failure", 2, server, error); + sasl_emit_abort_signal(server); /* Terminate the negotiation */ cap_finish_negotiation(server); @@ -81,12 +162,10 @@ static void sasl_fail(IRC_SERVER_REC *server, const char *data, const char *from static void sasl_already(IRC_SERVER_REC *server, const char *data, const char *from) { - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_cancel(server); signal_emit("server sasl success", 1, server); + sasl_emit_abort_signal(server); /* We're already authenticated, do nothing */ cap_finish_negotiation(server); @@ -94,10 +173,7 @@ static void sasl_already(IRC_SERVER_REC *server, const char *data, const char *f static void sasl_success(IRC_SERVER_REC *server, const char *data, const char *from) { - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_cancel(server); signal_emit("server sasl success", 1, server); @@ -105,52 +181,112 @@ static void sasl_success(IRC_SERVER_REC *server, const char *data, const char *f cap_finish_negotiation(server); } +static void sasl_send_step_if_complete(IRC_SERVER_REC *server, const char *enc_req) +{ + char *buffer; + size_t enc_req_len; + + enc_req_len = strlen(enc_req); + + buffer = g_hash_table_lookup(sasl_buffers, server->tag); + if (buffer != NULL) { + if (!g_strcmp0("+", enc_req)) { + enc_req = buffer; + } else { + enc_req = g_strconcat(buffer, enc_req, NULL); + } + g_hash_table_remove(sasl_buffers, server->tag); + } + + if (enc_req_len == 400) { + g_hash_table_insert(sasl_buffers, server->tag, g_strdup(enc_req)); + } else { + gchar *output; + GBytes *decoded; + IRC_SERVER_CONNECT_REC *conn; + + if (!g_strcmp0("+", enc_req)) { + decoded = g_bytes_new("", 0); + } else { + gsize dec_len; + guchar *tmp; + + tmp = g_base64_decode(enc_req, &dec_len); + decoded = g_bytes_new(tmp, dec_len); + } + + conn = server->connrec; + output = g_strconcat("server sasl step ", conn->sasl_mechanism, NULL); + ascii_strdown(output+17); + + if (!signal_emit(output, 2, server, decoded)) { + g_warning("No one can handle SASL mechanism: %s", + conn->sasl_mechanism); + } + g_free(output); + g_bytes_unref(decoded); + } + + g_free_not_null(buffer); +} + +void sasl_send_response(IRC_SERVER_REC *server, GBytes *response) +{ + char *enc; + const guchar *p; + gsize len; + size_t offset, enc_len; + + p = g_bytes_get_data(response, &len); + enc = g_base64_encode(p, len); + enc_len = strlen(enc); + + offset = 0; + while (offset < enc_len) { + irc_send_cmdv(server, "AUTHENTICATE %400s", + offset == enc_len ? "+" : (enc + offset)); + offset += 400; + } + g_free(enc); +} + static void sasl_step(IRC_SERVER_REC *server, const char *data, const char *from) { IRC_SERVER_CONNECT_REC *conn; GString *req; - char *enc_req; conn = server->connrec; - /* Stop the timer */ - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_cancel(server); - switch (conn->sasl_mechanism) { - case SASL_MECHANISM_PLAIN: - /* At this point we assume that conn->sasl_{username, password} are non-NULL. - * The PLAIN mechanism expects a NULL-separated string composed by the authorization identity, the - * authentication identity and the password. - * The authorization identity field is explicitly set to the user provided username. - * The whole request is then encoded in base64. */ + if (!g_strcmp0(conn->sasl_mechanism, "plain")) { + /* At this point we assume that conn->sasl_{username, password} are non-NULL. + * The PLAIN mechanism expects a NULL-separated string composed by the authorization identity, the + * authentication identity and the password. + * The authorization identity field is explicitly set to the user provided username. + * The whole request is then encoded in base64. */ + GBytes *bytes; - req = g_string_new(NULL); + req = g_string_new(NULL); - g_string_append(req, conn->sasl_username); - g_string_append_c(req, '\0'); - g_string_append(req, conn->sasl_username); - g_string_append_c(req, '\0'); - g_string_append(req, conn->sasl_password); + g_string_append(req, conn->sasl_username); + g_string_append_c(req, '\0'); + g_string_append(req, conn->sasl_username); + g_string_append_c(req, '\0'); + g_string_append(req, conn->sasl_password); - enc_req = g_base64_encode((const guchar *)req->str, req->len); - - irc_send_cmdv(server, "AUTHENTICATE %s", enc_req); - - g_free(enc_req); - g_string_free(req, TRUE); - break; - - case SASL_MECHANISM_EXTERNAL: - /* Empty response */ - irc_send_cmdv(server, "AUTHENTICATE +"); - break; + bytes = g_bytes_new(req->str, req->len); + sasl_send_response(server, bytes); + g_string_free(req, TRUE); + } else if (!g_strcmp0(conn->sasl_mechanism, "external")) { + /* Empty response */ + irc_send_cmdv(server, "AUTHENTICATE +"); + } else { + sasl_send_step_if_complete(server, data); } /* We expect a response within a reasonable time */ - server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server); + sasl_timeout_start(server); } static void sasl_disconnected(IRC_SERVER_REC *server) @@ -161,14 +297,15 @@ static void sasl_disconnected(IRC_SERVER_REC *server) return; } - if (server->sasl_timeout != 0) { - g_source_remove(server->sasl_timeout); - server->sasl_timeout = 0; - } + sasl_timeout_cancel(server); + + sasl_emit_abort_signal(server); } void sasl_init(void) { + sasl_buffers = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + signal_add_first("server cap ack sasl", (SIGNAL_FUNC) sasl_start); signal_add_first("event authenticate", (SIGNAL_FUNC) sasl_step); signal_add_first("event 903", (SIGNAL_FUNC) sasl_success); @@ -182,6 +319,8 @@ void sasl_init(void) void sasl_deinit(void) { + g_hash_table_destroy(sasl_buffers); + signal_remove("server cap ack sasl", (SIGNAL_FUNC) sasl_start); signal_remove("event authenticate", (SIGNAL_FUNC) sasl_step); signal_remove("event 903", (SIGNAL_FUNC) sasl_success); diff --git a/src/irc/core/sasl.h b/src/irc/core/sasl.h index 0693989d..3ab0f67c 100644 --- a/src/irc/core/sasl.h +++ b/src/irc/core/sasl.h @@ -21,12 +21,10 @@ #ifndef __SASL_H #define __SASL_H -enum { - SASL_MECHANISM_NONE = 0, - SASL_MECHANISM_PLAIN, - SASL_MECHANISM_EXTERNAL, - SASL_MECHANISM_MAX -}; +void sasl_mechanism_register(const char *); +void sasl_mechanism_unregister(const char *); +void sasl_send_response(IRC_SERVER_REC *, GBytes *); +void sasl_abort(IRC_SERVER_REC *); void sasl_init(void); void sasl_deinit(void); diff --git a/src/perl/get-signals.pl b/src/perl/get-signals.pl index 529d35b1..39c62d53 100755 --- a/src/perl/get-signals.pl +++ b/src/perl/get-signals.pl @@ -18,6 +18,7 @@ while () { s/GList \* of ([^,]*)/glistptr_\1/g; s/GSList of (\w+)s/gslist_\1/g; + s/GBytes \*/gbytes/g; s/char \*[^,]*/string/g; s/ulong \*[^,]*/ulongptr/g; diff --git a/src/perl/irc/Irc.xs b/src/perl/irc/Irc.xs index 3f8ccc2e..22c43c45 100644 --- a/src/perl/irc/Irc.xs +++ b/src/perl/irc/Irc.xs @@ -7,6 +7,9 @@ static void perl_irc_connect_fill_hash(HV *hv, IRC_SERVER_CONNECT_REC *conn) { perl_connect_fill_hash(hv, (SERVER_CONNECT_REC *) conn); (void) hv_store(hv, "alternate_nick", 14, new_pv(conn->alternate_nick), 0); + + (void) hv_store(hv, "sasl_username", 13, new_pv(conn->sasl_username), 0); + (void) hv_store(hv, "sasl_password", 13, new_pv(conn->sasl_password), 0); } static void perl_irc_server_fill_hash(HV *hv, IRC_SERVER_REC *server) @@ -227,3 +230,11 @@ BOOT: irssi_boot(Irc__Query); irssi_boot(Irc__Server); irssi_boot(Irc__Client); + +void +sasl_mechanism_register(name) + char *name + +void +sasl_mechanism_unregister(name) + char *name diff --git a/src/perl/irc/Server.xs b/src/perl/irc/Server.xs index 0e9ec672..e41c86d1 100644 --- a/src/perl/irc/Server.xs +++ b/src/perl/irc/Server.xs @@ -149,3 +149,25 @@ CODE: OUTPUT: RETVAL +MODULE = Irssi::Irc::Server PACKAGE = Irssi::Irc::Server +PROTOTYPES: ENABLE + +void +send_sasl_response(server, response) + Irssi::Irc::Server server + SV *response +PREINIT: + char *p; + STRLEN len; + GBytes *bytes; +CODE: + p = SvPV(response, len); + bytes = g_bytes_new(p, len); + sasl_send_response(server, bytes); + g_bytes_unref(bytes); + +void +send_sasl_abort(server) + Irssi::Irc::Server server +CODE: + sasl_abort(server); diff --git a/src/perl/irc/module.h b/src/perl/irc/module.h index 91c19c7a..f7e723c8 100644 --- a/src/perl/irc/module.h +++ b/src/perl/irc/module.h @@ -12,6 +12,7 @@ #include "mode-lists.h" #include "netsplit.h" #include "servers-redirect.h" +#include "sasl.h" #include "dcc/dcc.h" #include "dcc/dcc-file.h" diff --git a/src/perl/perl-signals.c b/src/perl/perl-signals.c index 007f7ad5..831ce270 100644 --- a/src/perl/perl-signals.c +++ b/src/perl/perl-signals.c @@ -80,6 +80,7 @@ void perl_signal_args_to_c( unsigned long v_ulong; GSList *v_gslist; GList *v_glist; + GBytes *v_gbytes; } saved_args[SIGNAL_MAX_ARGUMENTS]; void *p[SIGNAL_MAX_ARGUMENTS]; PERL_SIGNAL_ARGS_REC *rec; @@ -101,6 +102,12 @@ void perl_signal_args_to_c( c_arg = NULL; } else if (g_strcmp0(rec->args[n], "string") == 0) { c_arg = SvPV_nolen(arg); + } else if (g_strcmp0(rec->args[n], "gbytes") == 0) { + char *bytes; + STRLEN len; + + bytes = SvPV(arg, len); + c_arg = saved_args[n].v_gbytes = g_bytes_new(bytes, len); } else if (g_strcmp0(rec->args[n], "int") == 0) { c_arg = (void *)SvIV(arg); } else if (g_strcmp0(rec->args[n], "ulongptr") == 0) { @@ -181,7 +188,9 @@ void perl_signal_args_to_c( continue; } - if (g_strcmp0(rec->args[n], "intptr") == 0) { + if (g_strcmp0(rec->args[n], "gbytes") == 0) { + g_bytes_unref(saved_args[n].v_gbytes); + } else if (g_strcmp0(rec->args[n], "intptr") == 0) { SV *t = SvRV(arg); SvIOK_only(t); SvIV_set(t, saved_args[n].v_int); @@ -291,6 +300,14 @@ static void perl_call_signal(PERL_SCRIPT_REC *script, SV *func, /* "simple irssi object" - any struct that has int type; as it's first variable (dcc) */ perlarg = simple_iobject_bless((SERVER_REC *) arg); + } else if (g_strcmp0(rec->args[n], "gbytes") == 0) { + GBytes *tmp; + const char *data; + STRLEN data_len; + + tmp = arg; + data = g_bytes_get_data(tmp, &data_len); + perlarg = newSVpvn(data, data_len); } else { /* blessed object */ perlarg = plain_bless(arg, rec->args[n]);