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

  1. /*
  2. * xmpp.c - incredibly cursed XMPP plugin
  3. *
  4. * Copyright (C) 2021 eta <git@eta.st>
  5. *
  6. * This file is part of WeeChat, the extensible chat client.
  7. *
  8. * WeeChat is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * WeeChat is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
  20. */
  21. #define _GNU_SOURCE
  22. #include <stdlib.h>
  23. #include <stdio.h>
  24. #include <string.h>
  25. #include <time.h>
  26. #include <strophe.h>
  27. #include <ecl/ecl.h>
  28. #include "../weechat-plugin.h"
  29. #include "xmpp.h"
  30. /* Interval at which to call the libstrophe event loop */
  31. const long EVENT_LOOP_INTERVAL = 200;
  32. struct t_weechat_plugin *weechat_xmpp_plugin = NULL;
  33. struct t_config_option *opt_jid = NULL;
  34. struct t_config_option *opt_password = NULL;
  35. struct t_config_option *opt_autoconnect = NULL;
  36. xmpp_ctx_t *xmpp_ctx = NULL;
  37. xmpp_conn_t *xmpp_conn = NULL;
  38. WEECHAT_PLUGIN_NAME(XMPP_PLUGIN_NAME);
  39. WEECHAT_PLUGIN_DESCRIPTION(N_("Cursed XMPP plugin"));
  40. WEECHAT_PLUGIN_AUTHOR("eta <git@eta.st>");
  41. WEECHAT_PLUGIN_VERSION(WEECHAT_VERSION);
  42. WEECHAT_PLUGIN_LICENSE(WEECHAT_LICENSE);
  43. WEECHAT_PLUGIN_PRIORITY(8000);
  44. char* ecl_get_string_for(cl_object obj) {
  45. cl_object formatted = NULL;
  46. ECL_HANDLER_CASE_BEGIN(ecl_process_env(), ecl_list1(ECL_T)) {
  47. formatted = cl_princ_to_string(obj);
  48. } ECL_HANDLER_CASE(1, condition) {
  49. } ECL_HANDLER_CASE_END;
  50. if (formatted == NULL) {
  51. return "[error occurred printing Lisp object]";
  52. }
  53. assert(ECL_STRINGP(formatted));
  54. if (!ecl_fits_in_base_string(formatted)) {
  55. return "[Unicode error printing Lisp object]";
  56. }
  57. cl_object base = si_coerce_to_base_string(formatted);
  58. char* ret = base->base_string.self;
  59. return ret;
  60. }
  61. void strophe_log_handler(void *userdata, xmpp_log_level_t level, const char *area, const char *msg) {
  62. char *prefix = "";
  63. if (level >= XMPP_LEVEL_WARN) {
  64. prefix = weechat_prefix("error");
  65. }
  66. weechat_printf(NULL, "%slibstrophe %s: %s", prefix, area, msg);
  67. }
  68. static const xmpp_log_t xmpp_logger = {&strophe_log_handler, NULL};
  69. int
  70. xmpp_finish () {
  71. if (xmpp_conn) {
  72. xmpp_conn_t *old_conn = xmpp_conn;
  73. xmpp_conn = NULL;
  74. xmpp_conn_release(old_conn);
  75. }
  76. return WEECHAT_RC_OK;
  77. }
  78. const char* stringify_error_type (xmpp_error_type_t err) {
  79. if (!err) {
  80. return "(null)";
  81. }
  82. switch (err) {
  83. case XMPP_SE_BAD_FORMAT:
  84. return "bad-format";
  85. case XMPP_SE_BAD_NS_PREFIX:
  86. return "bad-namespace-prefix";
  87. case XMPP_SE_CONFLICT:
  88. return "conflict";
  89. case XMPP_SE_CONN_TIMEOUT:
  90. return "connection-timeout";
  91. case XMPP_SE_HOST_GONE:
  92. return "host-gone";
  93. case XMPP_SE_HOST_UNKNOWN:
  94. return "host-unknown";
  95. case XMPP_SE_IMPROPER_ADDR:
  96. return "improper-addressing";
  97. case XMPP_SE_INTERNAL_SERVER_ERROR:
  98. return "internal-server-error";
  99. case XMPP_SE_INVALID_FROM:
  100. return "invalid-from";
  101. case XMPP_SE_INVALID_ID:
  102. return "invalid-id";
  103. case XMPP_SE_INVALID_NS:
  104. return "invalid-namespace";
  105. case XMPP_SE_INVALID_XML:
  106. return "invalid-xml";
  107. case XMPP_SE_NOT_AUTHORIZED:
  108. return "not-authorized";
  109. case XMPP_SE_POLICY_VIOLATION:
  110. return "policy-violation";
  111. case XMPP_SE_REMOTE_CONN_FAILED:
  112. return "remote-connection-failed";
  113. case XMPP_SE_RESOURCE_CONSTRAINT:
  114. return "resource-constraint";
  115. case XMPP_SE_RESTRICTED_XML:
  116. return "restricted-xml";
  117. case XMPP_SE_SEE_OTHER_HOST:
  118. return "see-other-host";
  119. case XMPP_SE_SYSTEM_SHUTDOWN:
  120. return "system-shutdown";
  121. case XMPP_SE_UNDEFINED_CONDITION:
  122. return "undefined-condition";
  123. case XMPP_SE_UNSUPPORTED_ENCODING:
  124. return "unsupported-encoding";
  125. case XMPP_SE_UNSUPPORTED_STANZA_TYPE:
  126. return "unsupported-stanza-type";
  127. case XMPP_SE_UNSUPPORTED_VERSION:
  128. return "unsupported-version";
  129. case XMPP_SE_XML_NOT_WELL_FORMED:
  130. return "xml-not-well-formed";
  131. default:
  132. return "internal-server-error";
  133. }
  134. }
  135. void
  136. xmpp_conn_cb (xmpp_conn_t *conn, const xmpp_conn_event_t event, const int error, xmpp_stream_error_t *stream_error, void *userdata) {
  137. switch (event) {
  138. case XMPP_CONN_CONNECT:
  139. weechat_printf(NULL, "xmpp: connected to server");
  140. break;
  141. case XMPP_CONN_RAW_CONNECT:
  142. weechat_printf(NULL, "xmpp: raw stream established");
  143. break;
  144. case XMPP_CONN_DISCONNECT:
  145. case XMPP_CONN_FAIL:
  146. if (error) {
  147. char *error_text = strerror(error);
  148. weechat_printf(NULL, "%sxmpp: connection error: %s", weechat_prefix("error"), error_text);
  149. }
  150. else if (stream_error) {
  151. char* error_text = NULL;
  152. char* error_ty = stringify_error_type(stream_error->type);
  153. if (stream_error->text) {
  154. asprintf(&error_text, "%s (%s)", stream_error->text, error_ty);
  155. }
  156. else {
  157. error_text = error_ty;
  158. }
  159. weechat_printf(NULL, "%sxmpp: connection error: %s", weechat_prefix("error"), error_text);
  160. }
  161. weechat_printf(NULL, "%sxmpp: disconnected from server", weechat_prefix("error"));
  162. xmpp_finish();
  163. break;
  164. }
  165. }
  166. int
  167. xmpp_connect () {
  168. if (xmpp_conn) {
  169. weechat_printf(NULL, "%sxmpp: disconnecting existing connection");
  170. xmpp_finish();
  171. }
  172. xmpp_conn = xmpp_conn_new(xmpp_ctx);
  173. if (!xmpp_conn) {
  174. weechat_printf(NULL, "%sxmpp: creating connection failed", weechat_prefix("error"));
  175. return WEECHAT_RC_ERROR;
  176. }
  177. const char *jid = weechat_config_string(opt_jid);
  178. if (!jid || jid == "") {
  179. weechat_printf(NULL, "%sxmpp: no JID configured", weechat_prefix("error"));
  180. return WEECHAT_RC_ERROR;
  181. }
  182. const char *password = weechat_config_string(opt_password);
  183. if (!password || password == "") {
  184. weechat_printf(NULL, "%sxmpp: no password configured", weechat_prefix("error"));
  185. return WEECHAT_RC_ERROR;
  186. }
  187. xmpp_conn_set_jid(xmpp_conn, jid);
  188. xmpp_conn_set_pass(xmpp_conn, password);
  189. weechat_printf(NULL, "xmpp: connecting to server");
  190. xmpp_connect_client(xmpp_conn, NULL, 0, &xmpp_conn_cb, NULL);
  191. return WEECHAT_RC_OK;
  192. }
  193. int
  194. xmpp_command_connect (const void *pointer, void *data,
  195. struct t_gui_buffer *buffer, int argc,
  196. char **argv, char **argv_eol) {
  197. return xmpp_connect();
  198. }
  199. int
  200. xmpp_command_disconnect (const void *pointer, void *data,
  201. struct t_gui_buffer *buffer, int argc,
  202. char **argv, char **argv_eol) {
  203. xmpp_finish();
  204. return WEECHAT_RC_OK;
  205. }
  206. int
  207. xmpp_command_eval (const void *pointer, void *data,
  208. struct t_gui_buffer *buffer, int argc,
  209. char **argv, char **argv_eol) {
  210. if (argc < 1) {
  211. weechat_printf(buffer, "%sxmpp: no form provided", weechat_prefix("error"));
  212. return WEECHAT_RC_ERROR;
  213. }
  214. char *input = argv_eol[1];
  215. cl_env_ptr env = ecl_process_env();
  216. cl_object form = ecl_read_from_cstring_safe(input, ECL_NIL);
  217. if (ecl_eql(form, ECL_NIL)) {
  218. weechat_printf(buffer, "%sxmpp: nil form or error in reader", weechat_prefix("error"));
  219. return WEECHAT_RC_ERROR;
  220. }
  221. cl_object result = ECL_NIL;
  222. char* err = NULL;
  223. ECL_HANDLER_CASE_BEGIN(env, ecl_list1(ECL_T)) {
  224. result = cl_eval(form);
  225. } ECL_HANDLER_CASE(1, condition) {
  226. err = ecl_get_string_for(condition);
  227. } ECL_HANDLER_CASE_END;
  228. if (err) {
  229. weechat_printf(buffer, "%sxmpp: %s", weechat_prefix("error"), err);
  230. return WEECHAT_RC_ERROR;
  231. }
  232. char* disp = ecl_get_string_for(result);
  233. weechat_printf(buffer, "%s", disp);
  234. return WEECHAT_RC_OK;
  235. }
  236. void lisp_load(char *file)
  237. {
  238. ECL_HANDLER_CASE_BEGIN(ecl_process_env(), ecl_list1(ECL_T)) {
  239. cl_load(1, ecls(file));
  240. weechat_printf(NULL, "xmpp: loaded Lisp %s", file);
  241. } ECL_HANDLER_CASE(1, condition) {
  242. char* cond_fmt = ecl_get_string_for(condition);
  243. weechat_printf(NULL, "%sxmpp: could not load Lisp %s: %s", weechat_prefix("error"), file, cond_fmt);
  244. } ECL_HANDLER_CASE_END;
  245. }
  246. void weechat_config_init() {
  247. struct t_config_file *file = weechat_config_new("xmpp", NULL, NULL, NULL);
  248. struct t_config_section *sect_account =
  249. weechat_config_new_section(file, "account", 0, 0,
  250. NULL, NULL, NULL,
  251. NULL, NULL, NULL,
  252. NULL, NULL, NULL,
  253. NULL, NULL, NULL,
  254. NULL, NULL, NULL);
  255. opt_jid =
  256. weechat_config_new_option(file, sect_account, "jid", "string",
  257. "The Jabber ID (JID) of the XMPP account to use.",
  258. NULL, 0, 0, NULL, NULL,
  259. 1, NULL, NULL, NULL,
  260. NULL, NULL, NULL,
  261. NULL, NULL, NULL);
  262. opt_password =
  263. weechat_config_new_option(file, sect_account, "password", "string",
  264. "The password to authenticate to the XMPP server with.",
  265. NULL, 0, 0, NULL, NULL,
  266. 1, NULL, NULL, NULL,
  267. NULL, NULL, NULL,
  268. NULL, NULL, NULL);
  269. opt_autoconnect =
  270. weechat_config_new_option(file, sect_account, "autoconnect", "boolean",
  271. "Whether to connect to XMPP when WeeChat is started.",
  272. NULL, 0, 0, "off", "off",
  273. 0, NULL, NULL, NULL,
  274. NULL, NULL, NULL,
  275. NULL, NULL, NULL);
  276. weechat_config_read(file);
  277. }
  278. int on_event_loop_timer(const void *xmpp_ctx_ptr, void *data, int remaining_calls) {
  279. xmpp_ctx_t *xmpp_ctx = (xmpp_ctx_t *) xmpp_ctx_ptr;
  280. xmpp_run_once(xmpp_ctx, 0);
  281. return WEECHAT_RC_OK;
  282. }
  283. int maybe_autoconnect(const void *data1, void *data, int remaining_calls) {
  284. if (weechat_config_boolean (opt_autoconnect)) {
  285. xmpp_connect();
  286. }
  287. return WEECHAT_RC_OK;
  288. }
  289. cl_object clgc_printf(cl_object buffer, cl_object string) {
  290. struct t_gui_buffer *buf = NULL;
  291. if (buffer != ECL_NIL) {
  292. buf = ecl_foreign_data_pointer_safe(buffer);
  293. }
  294. cl_object base = si_coerce_to_base_string(string);
  295. char* message = base->base_string.self;
  296. weechat_printf(buf, message);
  297. return ECL_NIL;
  298. }
  299. void
  300. init_ecl_functions()
  301. {
  302. cl_object package = ecl_find_package("WEE-IMPL");
  303. assert(package != ECL_NIL);
  304. ecl_def_c_function(_ecl_intern("PRINTF", package), clgc_printf, 2);
  305. }
  306. int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char **argv) {
  307. weechat_plugin = plugin;
  308. weechat_config_init();
  309. xmpp_initialize();
  310. xmpp_ctx = xmpp_ctx_new(NULL, &xmpp_logger);
  311. if (!xmpp_ctx) {
  312. weechat_printf(NULL, "%sxmpp: xmpp_ctx_new failed", weechat_prefix("error"));
  313. return WEECHAT_RC_ERROR;
  314. }
  315. ecl_set_option(ECL_OPT_TRAP_SIGSEGV, FALSE);
  316. ecl_set_option(ECL_OPT_TRAP_SIGFPE, FALSE);
  317. ecl_set_option(ECL_OPT_TRAP_SIGINT, FALSE);
  318. ecl_set_option(ECL_OPT_TRAP_SIGILL, FALSE);
  319. ecl_set_option(ECL_OPT_TRAP_INTERRUPT_SIGNAL, FALSE);
  320. // ecl is really stupid and reads argv[0] to put in ecl_self.
  321. // ...why does this even exist?
  322. char **fake_argv = {"omg-wtf-bbq"};
  323. cl_boot(0, fake_argv);
  324. ecl_init_module(NULL, ecl_support_init);
  325. init_ecl_functions();
  326. cl_env_ptr env = ecl_process_env();
  327. weechat_hook_command("cleval", "Evaluate a Common Lisp form", "[form]", "form: a Common Lisp form", "", &xmpp_command_eval, NULL, NULL);
  328. weechat_hook_command("xconnect", "Connect to the XMPP server", "", "", "", &xmpp_command_connect, NULL, NULL);
  329. weechat_hook_command("xdisconnect", "Disconnect from the XMPP server", "", "", "", &xmpp_command_disconnect, NULL, NULL);
  330. struct t_hook *hook = weechat_hook_timer(EVENT_LOOP_INTERVAL, 0, 0, &on_event_loop_timer, xmpp_ctx, NULL);
  331. if (!hook) {
  332. weechat_printf(NULL, "%sxmpp: event loop hook failed", weechat_prefix("error"));
  333. return WEECHAT_RC_ERROR;
  334. }
  335. struct t_hook *ac_hook = weechat_hook_timer(1, 0, 1, &maybe_autoconnect, NULL, NULL);
  336. if (!ac_hook) {
  337. weechat_printf(NULL, "%sxmpp: autoconnect hook failed", weechat_prefix("error"));
  338. return WEECHAT_RC_ERROR;
  339. }
  340. cl_object package = ecl_find_package("WEEXMPP");
  341. assert(package != ECL_NIL);
  342. cl_object sym = _ecl_intern("INITIALIZE", package);
  343. cl_object result = ECL_NIL;
  344. int failed = 0;
  345. ECL_HANDLER_CASE_BEGIN(env, ecl_list1(ECL_T)) {
  346. cl_object func = cl_symbol_function(sym);
  347. result = cl_funcall(1, func);
  348. } ECL_HANDLER_CASE(1, condition) {
  349. char* err = ecl_get_string_for(condition);
  350. weechat_printf(NULL, "%sxmpp: failed to run Lisp initialize function: %s", weechat_prefix("error"), err);
  351. failed = 1;
  352. } ECL_HANDLER_CASE_END;
  353. if (failed) {
  354. return WEECHAT_RC_ERROR;
  355. }
  356. return WEECHAT_RC_OK;
  357. }
  358. int weechat_plugin_end(struct t_weechat_plugin *plugin) {
  359. xmpp_shutdown();
  360. cl_shutdown();
  361. weechat_printf(NULL, "%sxmpp: plugin can not be unloaded", weechat_prefix("error"));
  362. return WEECHAT_RC_ERROR;
  363. }