You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

402 lines
12 KiB

/*
* xmpp.c - incredibly cursed XMPP plugin
*
* Copyright (C) 2021 eta <git@eta.st>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <strophe.h>
#include <ecl/ecl.h>
#include "../weechat-plugin.h"
#include "xmpp.h"
/* Interval at which to call the libstrophe event loop */
const long EVENT_LOOP_INTERVAL = 200;
struct t_weechat_plugin *weechat_xmpp_plugin = NULL;
struct t_config_option *opt_jid = NULL;
struct t_config_option *opt_password = NULL;
struct t_config_option *opt_autoconnect = NULL;
xmpp_ctx_t *xmpp_ctx = NULL;
xmpp_conn_t *xmpp_conn = NULL;
WEECHAT_PLUGIN_NAME(XMPP_PLUGIN_NAME);
WEECHAT_PLUGIN_DESCRIPTION(N_("Cursed XMPP plugin"));
WEECHAT_PLUGIN_AUTHOR("eta <git@eta.st>");
WEECHAT_PLUGIN_VERSION(WEECHAT_VERSION);
WEECHAT_PLUGIN_LICENSE(WEECHAT_LICENSE);
WEECHAT_PLUGIN_PRIORITY(8000);
char* ecl_get_string_for(cl_object obj) {
cl_object formatted = NULL;
ECL_HANDLER_CASE_BEGIN(ecl_process_env(), ecl_list1(ECL_T)) {
formatted = cl_princ_to_string(obj);
} ECL_HANDLER_CASE(1, condition) {
} ECL_HANDLER_CASE_END;
if (formatted == NULL) {
return "[error occurred printing Lisp object]";
}
assert(ECL_STRINGP(formatted));
if (!ecl_fits_in_base_string(formatted)) {
return "[Unicode error printing Lisp object]";
}
cl_object base = si_coerce_to_base_string(formatted);
char* ret = base->base_string.self;
return ret;
}
void strophe_log_handler(void *userdata, xmpp_log_level_t level, const char *area, const char *msg) {
char *prefix = "";
if (level >= XMPP_LEVEL_WARN) {
prefix = weechat_prefix("error");
}
weechat_printf(NULL, "%slibstrophe %s: %s", prefix, area, msg);
}
static const xmpp_log_t xmpp_logger = {&strophe_log_handler, NULL};
int
xmpp_finish () {
if (xmpp_conn) {
xmpp_conn_t *old_conn = xmpp_conn;
xmpp_conn = NULL;
xmpp_conn_release(old_conn);
}
return WEECHAT_RC_OK;
}
const char* stringify_error_type (xmpp_error_type_t err) {
if (!err) {
return "(null)";
}
switch (err) {
case XMPP_SE_BAD_FORMAT:
return "bad-format";
case XMPP_SE_BAD_NS_PREFIX:
return "bad-namespace-prefix";
case XMPP_SE_CONFLICT:
return "conflict";
case XMPP_SE_CONN_TIMEOUT:
return "connection-timeout";
case XMPP_SE_HOST_GONE:
return "host-gone";
case XMPP_SE_HOST_UNKNOWN:
return "host-unknown";
case XMPP_SE_IMPROPER_ADDR:
return "improper-addressing";
case XMPP_SE_INTERNAL_SERVER_ERROR:
return "internal-server-error";
case XMPP_SE_INVALID_FROM:
return "invalid-from";
case XMPP_SE_INVALID_ID:
return "invalid-id";
case XMPP_SE_INVALID_NS:
return "invalid-namespace";
case XMPP_SE_INVALID_XML:
return "invalid-xml";
case XMPP_SE_NOT_AUTHORIZED:
return "not-authorized";
case XMPP_SE_POLICY_VIOLATION:
return "policy-violation";
case XMPP_SE_REMOTE_CONN_FAILED:
return "remote-connection-failed";
case XMPP_SE_RESOURCE_CONSTRAINT:
return "resource-constraint";
case XMPP_SE_RESTRICTED_XML:
return "restricted-xml";
case XMPP_SE_SEE_OTHER_HOST:
return "see-other-host";
case XMPP_SE_SYSTEM_SHUTDOWN:
return "system-shutdown";
case XMPP_SE_UNDEFINED_CONDITION:
return "undefined-condition";
case XMPP_SE_UNSUPPORTED_ENCODING:
return "unsupported-encoding";
case XMPP_SE_UNSUPPORTED_STANZA_TYPE:
return "unsupported-stanza-type";
case XMPP_SE_UNSUPPORTED_VERSION:
return "unsupported-version";
case XMPP_SE_XML_NOT_WELL_FORMED:
return "xml-not-well-formed";
default:
return "internal-server-error";
}
}
void
xmpp_conn_cb (xmpp_conn_t *conn, const xmpp_conn_event_t event, const int error, xmpp_stream_error_t *stream_error, void *userdata) {
switch (event) {
case XMPP_CONN_CONNECT:
weechat_printf(NULL, "xmpp: connected to server");
break;
case XMPP_CONN_RAW_CONNECT:
weechat_printf(NULL, "xmpp: raw stream established");
break;
case XMPP_CONN_DISCONNECT:
case XMPP_CONN_FAIL:
if (error) {
char *error_text = strerror(error);
weechat_printf(NULL, "%sxmpp: connection error: %s", weechat_prefix("error"), error_text);
}
else if (stream_error) {
char* error_text = NULL;
char* error_ty = stringify_error_type(stream_error->type);
if (stream_error->text) {
asprintf(&error_text, "%s (%s)", stream_error->text, error_ty);
}
else {
error_text = error_ty;
}
weechat_printf(NULL, "%sxmpp: connection error: %s", weechat_prefix("error"), error_text);
}
weechat_printf(NULL, "%sxmpp: disconnected from server", weechat_prefix("error"));
xmpp_finish();
break;
}
}
int
xmpp_connect () {
if (xmpp_conn) {
weechat_printf(NULL, "%sxmpp: disconnecting existing connection");
xmpp_finish();
}
xmpp_conn = xmpp_conn_new(xmpp_ctx);
if (!xmpp_conn) {
weechat_printf(NULL, "%sxmpp: creating connection failed", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}
const char *jid = weechat_config_string(opt_jid);
if (!jid || jid == "") {
weechat_printf(NULL, "%sxmpp: no JID configured", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}
const char *password = weechat_config_string(opt_password);
if (!password || password == "") {
weechat_printf(NULL, "%sxmpp: no password configured", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}
xmpp_conn_set_jid(xmpp_conn, jid);
xmpp_conn_set_pass(xmpp_conn, password);
weechat_printf(NULL, "xmpp: connecting to server");
xmpp_connect_client(xmpp_conn, NULL, 0, &xmpp_conn_cb, NULL);
return WEECHAT_RC_OK;
}
int
xmpp_command_connect (const void *pointer, void *data,
struct t_gui_buffer *buffer, int argc,
char **argv, char **argv_eol) {
return xmpp_connect();
}
int
xmpp_command_disconnect (const void *pointer, void *data,
struct t_gui_buffer *buffer, int argc,
char **argv, char **argv_eol) {
xmpp_finish();
return WEECHAT_RC_OK;
}
int
xmpp_command_eval (const void *pointer, void *data,
struct t_gui_buffer *buffer, int argc,
char **argv, char **argv_eol) {
if (argc < 1) {
weechat_printf(buffer, "%sxmpp: no form provided", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}
char *input = argv_eol[1];
cl_env_ptr env = ecl_process_env();
cl_object form = ecl_read_from_cstring_safe(input, ECL_NIL);
if (ecl_eql(form, ECL_NIL)) {
weechat_printf(buffer, "%sxmpp: nil form or error in reader", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}
cl_object result = ECL_NIL;
char* err = NULL;
ECL_HANDLER_CASE_BEGIN(env, ecl_list1(ECL_T)) {
result = cl_eval(form);
} ECL_HANDLER_CASE(1, condition) {
err = ecl_get_string_for(condition);
} ECL_HANDLER_CASE_END;
if (err) {
weechat_printf(buffer, "%sxmpp: %s", weechat_prefix("error"), err);
return WEECHAT_RC_ERROR;
}
char* disp = ecl_get_string_for(result);
weechat_printf(buffer, "%s", disp);
return WEECHAT_RC_OK;
}
void lisp_load(char *file)
{
ECL_HANDLER_CASE_BEGIN(ecl_process_env(), ecl_list1(ECL_T)) {
cl_load(1, ecls(file));
weechat_printf(NULL, "xmpp: loaded Lisp %s", file);
} ECL_HANDLER_CASE(1, condition) {
char* cond_fmt = ecl_get_string_for(condition);
weechat_printf(NULL, "%sxmpp: could not load Lisp %s: %s", weechat_prefix("error"), file, cond_fmt);
} ECL_HANDLER_CASE_END;
}
void weechat_config_init() {
struct t_config_file *file = weechat_config_new("xmpp", NULL, NULL, NULL);
struct t_config_section *sect_account =
weechat_config_new_section(file, "account", 0, 0,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL);
opt_jid =
weechat_config_new_option(file, sect_account, "jid", "string",
"The Jabber ID (JID) of the XMPP account to use.",
NULL, 0, 0, NULL, NULL,
1, NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL);
opt_password =
weechat_config_new_option(file, sect_account, "password", "string",
"The password to authenticate to the XMPP server with.",
NULL, 0, 0, NULL, NULL,
1, NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL);
opt_autoconnect =
weechat_config_new_option(file, sect_account, "autoconnect", "boolean",
"Whether to connect to XMPP when WeeChat is started.",
NULL, 0, 0, "off", "off",
0, NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL);
weechat_config_read(file);
}
int on_event_loop_timer(const void *xmpp_ctx_ptr, void *data, int remaining_calls) {
xmpp_ctx_t *xmpp_ctx = (xmpp_ctx_t *) xmpp_ctx_ptr;
xmpp_run_once(xmpp_ctx, 0);
return WEECHAT_RC_OK;
}
int maybe_autoconnect(const void *data1, void *data, int remaining_calls) {
if (weechat_config_boolean (opt_autoconnect)) {
xmpp_connect();
}
return WEECHAT_RC_OK;
}
cl_object clgc_printf(cl_object buffer, cl_object string) {
struct t_gui_buffer *buf = NULL;
if (buffer != ECL_NIL) {
buf = ecl_foreign_data_pointer_safe(buffer);
}
cl_object base = si_coerce_to_base_string(string);
char* message = base->base_string.self;
weechat_printf(buf, message);
return ECL_NIL;
}
void
init_ecl_functions()
{
cl_object package = ecl_find_package("WEE-IMPL");
assert(package != ECL_NIL);
ecl_def_c_function(_ecl_intern("PRINTF", package), clgc_printf, 2);
}
int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char **argv) {
weechat_plugin = plugin;
weechat_config_init();
xmpp_initialize();
xmpp_ctx = xmpp_ctx_new(NULL, &xmpp_logger);
if (!xmpp_ctx) {
weechat_printf(NULL, "%sxmpp: xmpp_ctx_new failed", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}
ecl_set_option(ECL_OPT_TRAP_SIGSEGV, FALSE);
ecl_set_option(ECL_OPT_TRAP_SIGFPE, FALSE);
ecl_set_option(ECL_OPT_TRAP_SIGINT, FALSE);
ecl_set_option(ECL_OPT_TRAP_SIGILL, FALSE);
ecl_set_option(ECL_OPT_TRAP_INTERRUPT_SIGNAL, FALSE);
// ecl is really stupid and reads argv[0] to put in ecl_self.
// ...why does this even exist?
char **fake_argv = {"omg-wtf-bbq"};
cl_boot(0, fake_argv);
ecl_init_module(NULL, ecl_support_init);
init_ecl_functions();
cl_env_ptr env = ecl_process_env();
weechat_hook_command("cleval", "Evaluate a Common Lisp form", "[form]", "form: a Common Lisp form", "", &xmpp_command_eval, NULL, NULL);
weechat_hook_command("xconnect", "Connect to the XMPP server", "", "", "", &xmpp_command_connect, NULL, NULL);
weechat_hook_command("xdisconnect", "Disconnect from the XMPP server", "", "", "", &xmpp_command_disconnect, NULL, NULL);
struct t_hook *hook = weechat_hook_timer(EVENT_LOOP_INTERVAL, 0, 0, &on_event_loop_timer, xmpp_ctx, NULL);
if (!hook) {
weechat_printf(NULL, "%sxmpp: event loop hook failed", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}
struct t_hook *ac_hook = weechat_hook_timer(1, 0, 1, &maybe_autoconnect, NULL, NULL);
if (!ac_hook) {
weechat_printf(NULL, "%sxmpp: autoconnect hook failed", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}
cl_object package = ecl_find_package("WEEXMPP");
assert(package != ECL_NIL);
cl_object sym = _ecl_intern("INITIALIZE", package);
cl_object result = ECL_NIL;
int failed = 0;
ECL_HANDLER_CASE_BEGIN(env, ecl_list1(ECL_T)) {
cl_object func = cl_symbol_function(sym);
result = cl_funcall(1, func);
} ECL_HANDLER_CASE(1, condition) {
char* err = ecl_get_string_for(condition);
weechat_printf(NULL, "%sxmpp: failed to run Lisp initialize function: %s", weechat_prefix("error"), err);
failed = 1;
} ECL_HANDLER_CASE_END;
if (failed) {
return WEECHAT_RC_ERROR;
}
return WEECHAT_RC_OK;
}
int weechat_plugin_end(struct t_weechat_plugin *plugin) {
xmpp_shutdown();
cl_shutdown();
weechat_printf(NULL, "%sxmpp: plugin can not be unloaded", weechat_prefix("error"));
return WEECHAT_RC_ERROR;
}