1 /*
2 * hooks.c -- Hooks layer
3 *
4 * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or (at
9 * your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <loudmouth/loudmouth.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include "hooks.h"
27 #include "screen.h"
28 #include "roster.h"
29 #include "histolog.h"
30 #include "hbuf.h"
31 #include "settings.h"
32 #include "utils.h"
33 #include "utf8.h"
34 #include "commands.h"
35 #include "main.h"
36
37 #ifdef MODULES_ENABLE
38 #include <glib.h>
39
40 typedef struct {
41 hk_handler_t handler;
42 gint priority;
43 gpointer userdata;
44 guint hid;
45 } hook_list_data_t;
46
47 static GHashTable *hk_handler_hash = NULL;
48
49 // _new_hook_id()
50 // Return a unique Hook Id
_new_hook_id(void)51 static guint _new_hook_id(void)
52 {
53 static guint hidcounter;
54
55 return ++hidcounter;
56 }
57
58 // _new_hook_queue(hookname)
59 // Create a new hash table entry with a GSList pointer for the specified hook
_new_hook_queue(const gchar * hookname)60 static GSList **_new_hook_queue(const gchar *hookname)
61 {
62 GSList **p;
63 // Create the hash table if needed.
64 if (!hk_handler_hash) {
65 hk_handler_hash = g_hash_table_new_full(&g_str_hash, &g_str_equal,
66 &g_free, &g_free);
67 if (!hk_handler_hash) {
68 scr_log_print(LPRINT_LOGNORM, "Couldn't create hook hash table!");
69 return NULL;
70 }
71 }
72
73 // Add a queue for the requested hook
74 p = g_new(GSList*, 1);
75 *p = NULL;
76 g_hash_table_insert(hk_handler_hash, g_strdup(hookname), p);
77
78 return p;
79 }
80
_hk_compare_prio(hook_list_data_t * a,hook_list_data_t * b)81 static gint _hk_compare_prio(hook_list_data_t *a, hook_list_data_t *b)
82 {
83 if (a->priority > b->priority)
84 return 1;
85 return 0;
86 }
87
88 // hk_add_handler(handler, hookname, priority, userdata)
89 // Create a hook handler and a hook hash entry if needed.
90 // Return the handler id.
hk_add_handler(hk_handler_t handler,const gchar * hookname,gint priority,gpointer userdata)91 guint hk_add_handler(hk_handler_t handler, const gchar *hookname,
92 gint priority, gpointer userdata)
93 {
94 GSList **hqueue = NULL;
95 hook_list_data_t *h = g_new(hook_list_data_t, 1);
96
97 h->handler = handler;
98 h->priority = priority;
99 h->userdata = userdata;
100 h->hid = _new_hook_id();
101
102 if (hk_handler_hash)
103 hqueue = g_hash_table_lookup(hk_handler_hash, hookname);
104
105 if (!hqueue)
106 hqueue = _new_hook_queue(hookname);
107
108 if (!hqueue)
109 return 0;
110
111 *hqueue = g_slist_insert_sorted(*hqueue, h, (GCompareFunc)_hk_compare_prio);
112
113 return h->hid;
114 }
115
_hk_queue_search_cb(hook_list_data_t * a,guint * hid)116 static gint _hk_queue_search_cb(hook_list_data_t *a, guint *hid)
117 {
118 if (a->hid == *hid)
119 return 0;
120 return 1;
121 }
122
123 // hk_del_handler(hookname, hook_id)
124 // Remove the handler with specified hook id from the hookname queue.
125 // The hash entry is removed if the queue is empty.
hk_del_handler(const gchar * hookname,guint hid)126 void hk_del_handler(const gchar *hookname, guint hid)
127 {
128 GSList **hqueue;
129 GSList *el;
130
131 if (!hid)
132 return;
133
134 hqueue = g_hash_table_lookup(hk_handler_hash, hookname);
135
136 if (!hqueue) {
137 scr_log_print(LPRINT_LOGNORM, "*ERROR*: Couldn't remove hook handler!");
138 return;
139 }
140
141 el = g_slist_find_custom(*hqueue, &hid,
142 (GCompareFunc)_hk_queue_search_cb);
143 if (el) {
144 g_free(el->data);
145 *hqueue = g_slist_delete_link(*hqueue, el);
146 // Remove hook hash table entry if the hook queue is empty
147 if (!*hqueue)
148 g_hash_table_remove(hk_handler_hash, hookname);
149 }
150 }
151
152 // hk_run_handlers(hookname, args)
153 // Process all hooks for the "hookname" event.
154 // Note that the processing is interrupted as soon as one of the handlers
155 // do not return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS (i.e. 0).
hk_run_handlers(const gchar * hookname,hk_arg_t * args)156 guint hk_run_handlers(const gchar *hookname, hk_arg_t *args)
157 {
158 GSList **hqueue;
159 GSList *h;
160 guint ret = 0;
161
162 if (!hk_handler_hash)
163 return 0;
164
165 hqueue = g_hash_table_lookup(hk_handler_hash, hookname);
166 if (!hqueue)
167 return 0; // Should we use a special code?
168
169 for (h = *hqueue; h; h = g_slist_next(h)) {
170 hook_list_data_t *data = h->data;
171 ret = (data->handler)(hookname, args, data->userdata);
172 if (ret) break;
173 }
174 return ret;
175 }
176 #endif
177
178 static char *extcmd;
179
180 static const char *COMMAND_ME = "/me ";
181
hk_message_in(const char * bjid,const char * resname,time_t timestamp,const char * msg,LmMessageSubType type,guint encrypted,gboolean carbon)182 void hk_message_in(const char *bjid, const char *resname,
183 time_t timestamp, const char *msg, LmMessageSubType type,
184 guint encrypted, gboolean carbon)
185 {
186 int new_guy = FALSE;
187 int is_groupchat = FALSE; // groupchat message
188 int is_room = FALSE; // window is a room window
189 int log_muc_conf = FALSE;
190 int active_window = FALSE;
191 int message_flags = 0;
192 guint rtype = ROSTER_TYPE_USER;
193 char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL;
194 GSList *roster_usr;
195 unsigned mucnicklen = 0;
196 const char *ename = NULL;
197 gboolean attention = FALSE, mucprivmsg = FALSE;
198 gboolean error_msg_subtype = (type == LM_MESSAGE_SUB_TYPE_ERROR);
199 #ifdef MODULES_ENABLE
200 gchar strdelay[32];
201
202 if (timestamp)
203 to_iso8601(strdelay, timestamp);
204 else
205 strdelay[0] = '\0';
206 #endif
207
208 if (encrypted == ENCRYPTED_PGP)
209 message_flags |= HBB_PREFIX_PGPCRYPT;
210 else if (encrypted == ENCRYPTED_OTR)
211 message_flags |= HBB_PREFIX_OTRCRYPT;
212
213 if (carbon)
214 message_flags |= HBB_PREFIX_CARBON;
215
216 if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
217 rtype = ROSTER_TYPE_ROOM;
218 is_groupchat = TRUE;
219 log_muc_conf = settings_opt_get_int("log_muc_conf");
220 if (!resname) {
221 message_flags = HBB_PREFIX_INFO | HBB_PREFIX_NOFLAG;
222 resname = "";
223 wmsg = bmsg = g_strdup_printf("~ %s", msg);
224 } else {
225 wmsg = bmsg = g_strdup_printf("<%s> %s", resname, msg);
226 mucnicklen = strlen(resname) + 2;
227 if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME)))
228 wmsg = mmsg = g_strdup_printf("*%s %s", resname, msg+4);
229 }
230 } else {
231 bmsg = g_strdup(msg);
232 if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
233 gchar *shortid = g_strdup(bjid);
234 if (settings_opt_get_int("buddy_me_fulljid") == FALSE) {
235 gchar *p = strchr(shortid, '@'); // Truncate the jid
236 if (p)
237 *p = '\0';
238 }
239 wmsg = mmsg = g_strdup_printf("*%s %s", shortid, msg+4);
240 g_free(shortid);
241 } else
242 wmsg = (char*) msg;
243 }
244
245 #ifdef MODULES_ENABLE
246 {
247 guint h_result;
248 hk_arg_t args[] = {
249 { "jid", bjid },
250 { "resource", resname },
251 { "message", msg },
252 { "groupchat", is_groupchat ? "true" : "false" },
253 { "delayed", strdelay },
254 { "error", error_msg_subtype ? "true" : "false" },
255 { "carbon", carbon ? "true" : "false" },
256 { NULL, NULL },
257 };
258 h_result = hk_run_handlers(HOOK_PRE_MESSAGE_IN, args);
259 if (h_result == HOOK_HANDLER_RESULT_NO_MORE_HANDLER_DROP_DATA) {
260 scr_LogPrint(LPRINT_DEBUG, "Message dropped (hook result).");
261 g_free(bmsg);
262 g_free(mmsg);
263 return;
264 }
265 }
266 #endif
267
268 // If this user isn't in the roster, we add it
269 roster_usr = roster_find(bjid, jidsearch, 0);
270 if (!roster_usr) {
271 new_guy = TRUE;
272 roster_usr = roster_add_user(bjid, NULL, NULL, rtype, sub_none, -1);
273 if (!roster_usr) { // Shouldn't happen...
274 scr_LogPrint(LPRINT_LOGNORM, "ERROR: unable to add buddy!");
275 g_free(bmsg);
276 g_free(mmsg);
277 return;
278 }
279 } else if (is_groupchat) {
280 // Make sure the type is ROOM
281 buddy_settype(roster_usr->data, ROSTER_TYPE_ROOM);
282 }
283
284 is_room = !!(buddy_gettype(roster_usr->data) & ROSTER_TYPE_ROOM);
285
286 if (is_room) {
287 if (!is_groupchat) {
288 // This is a private message from a room participant
289 g_free(bmsg);
290 if (!resname) {
291 resname = "";
292 wmsg = bmsg = g_strdup(msg);
293 } else {
294 wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", resname, msg);
295 if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
296 g_free(mmsg);
297 wmsg = mmsg = g_strdup_printf("PRIV#*%s %s", resname, msg+4);
298 }
299 mucprivmsg = TRUE;
300 }
301 message_flags |= HBB_PREFIX_HLIGHT;
302 } else {
303 // This is a regular chatroom message.
304 const char *nick = buddy_getnickname(roster_usr->data);
305
306 if (nick) {
307 // Let's see if we are the message sender, in which case we'll
308 // highlight it.
309 if (resname && !strcmp(resname, nick)) {
310 message_flags |= HBB_PREFIX_HLIGHT_OUT;
311 } else {
312 // We're not the sender. Can we see our nick?
313 const char *msgptr = msg;
314 while ((msgptr = strcasestr(msgptr, nick)) != NULL) {
315 const char *leftb, *rightb;
316 // The message contains our nick. Let's check it's not
317 // in the middle of another word (i.e. preceded/followed
318 // immediately by an alphanumeric character or an underscore.
319 rightb = msgptr+strlen(nick);
320 if (msgptr == msg)
321 leftb = NULL;
322 else
323 leftb = prev_char((char*)msgptr, msg);
324 msgptr = next_char((char*)msgptr);
325 // Check left boundary
326 if (leftb && (iswalnum(get_char(leftb)) || get_char(leftb) == '_'))
327 continue;
328 // Check right boundary
329 if (!iswalnum(get_char(rightb)) && get_char(rightb) != '_')
330 attention = TRUE;
331 if (attention && !settings_opt_get_int("muc_disable_nick_hl"))
332 message_flags |= HBB_PREFIX_HLIGHT;
333 }
334 }
335 }
336 }
337 } else if (settings_opt_get_int("roster_autolock_resource")) {
338 buddy_setactiveresource(roster_usr->data, resname);
339 scr_update_chat_status(FALSE);
340 }
341
342 if (error_msg_subtype) {
343 message_flags = HBB_PREFIX_ERR | HBB_PREFIX_IN;
344 scr_LogPrint(LPRINT_LOGNORM, "Error message received from <%s>", bjid);
345 }
346
347 // Note: the hlog_write should not be called first, because in some
348 // cases scr_write_incoming_message() will load the history and we'd
349 // have the message twice...
350 scr_write_incoming_message(bjid, wmsg, timestamp, message_flags, mucnicklen);
351
352 // Set urgent (a.k.a. "attention") flag
353 {
354 guint uip;
355 if (is_groupchat) {
356 if (attention) uip = ROSTER_UI_PRIO_MUC_HL_MESSAGE;
357 else uip = ROSTER_UI_PRIO_MUC_MESSAGE;
358 } else {
359 if (mucprivmsg) uip = ROSTER_UI_PRIO_MUC_PRIV_MESSAGE;
360 else if (attention) uip = ROSTER_UI_PRIO_ATTENTION_MESSAGE;
361 else uip = ROSTER_UI_PRIO_PRIVATE_MESSAGE;
362 }
363 scr_setattentionflag_if_needed(bjid, FALSE, uip, prio_max);
364 }
365
366 // We don't log the modified message, but the original one
367 if (wmsg == mmsg)
368 wmsg = bmsg;
369
370 // - We don't log the message if it is an error message
371 // - We don't log the message if it is a private conf. message
372 // - We don't log the message if it is groupchat message and the log_muc_conf
373 // option is off (and it is not a history line)
374 if (!(message_flags & HBB_PREFIX_ERR) &&
375 (!is_room || (is_groupchat && log_muc_conf && !timestamp)))
376 hlog_write_message(bjid, timestamp, 0, wmsg);
377
378 if (settings_opt_get_int("events_ignore_active_window") &&
379 current_buddy && scr_get_chatmode()) {
380 gpointer bud = BUDDATA(current_buddy);
381 if (bud) {
382 const char *cjid = buddy_getjid(bud);
383 if (cjid && !strcasecmp(cjid, bjid))
384 active_window = TRUE;
385 }
386 }
387
388 if (settings_opt_get_int("eventcmd_use_nickname"))
389 ename = roster_getname(bjid);
390
391 // Display the sender in the log window
392 if ((!is_groupchat) && !(message_flags & HBB_PREFIX_ERR) &&
393 settings_opt_get_int("log_display_sender")) {
394 const char *name = roster_getname(bjid);
395 if (!name) name = "";
396 scr_LogPrint(LPRINT_NORMAL, "Message received from %s <%s/%s>",
397 name, bjid, (resname ? resname : ""));
398 }
399
400 #ifdef MODULES_ENABLE
401 {
402 hk_arg_t args[] = {
403 { "jid", bjid },
404 { "resource", resname },
405 { "message", msg },
406 { "groupchat", is_groupchat ? "true" : "false" },
407 { "attention", attention ? "true" : "false" },
408 { "delayed", strdelay },
409 { "error", error_msg_subtype ? "true" : "false" },
410 { "carbon", carbon ? "true" : "false" },
411 { NULL, NULL },
412 };
413 hk_run_handlers(HOOK_POST_MESSAGE_IN, args);
414 }
415 #endif
416
417 // External command
418 // - We do not call hk_ext_cmd() for history lines in MUC
419 // - We do call hk_ext_cmd() for private messages in a room
420 // - We do call hk_ext_cmd() for messages to the current window
421 if (!active_window && ((is_groupchat && !timestamp) || !is_groupchat))
422 hk_ext_cmd(ename ? ename : bjid, (is_groupchat ? 'G' : 'M'), 'R', wmsg);
423
424 // Beep, if enabled:
425 // - if it's a private message
426 // - if it's a public message and it's highlighted
427 if (settings_opt_get_int("beep_on_message")) {
428 if ((!is_groupchat && !(message_flags & HBB_PREFIX_ERR)) ||
429 (is_groupchat && (message_flags & HBB_PREFIX_HLIGHT)))
430 scr_beep();
431 }
432
433 // We need to update the roster if the sender is unknown or
434 // if the sender is offline/invisible and a filter is set.
435 if (new_guy ||
436 (buddy_getstatus(roster_usr->data, NULL) == offline &&
437 buddylist_isset_filter()))
438 {
439 scr_update_roster();
440 }
441
442 g_free(bmsg);
443 g_free(mmsg);
444 }
445
446 // hk_message_out()
447 // nick should be set for private messages in a chat room, and null for
448 // normal messages.
hk_message_out(const char * bjid,const char * nick,time_t timestamp,const char * msg,guint encrypted,gboolean carbon,gpointer xep184)449 void hk_message_out(const char *bjid, const char *nick,
450 time_t timestamp, const char *msg,
451 guint encrypted, gboolean carbon, gpointer xep184)
452 {
453 char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL;
454 guint message_flags = 0;
455
456 if (nick) {
457 wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", nick, msg);
458 if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
459 const char *mynick = roster_getnickname(bjid);
460 wmsg = mmsg = g_strdup_printf("PRIV#<%s> *%s %s", nick,
461 (mynick ? mynick : "me"), msg+4);
462 }
463 } else {
464 wmsg = (char*)msg;
465 if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
466 char *myid = jid_get_username(settings_opt_get("jid"));
467 if (myid) {
468 wmsg = mmsg = g_strdup_printf("*%s %s", myid, msg+4);
469 g_free(myid);
470 }
471 }
472 }
473
474 // Note: the hlog_write should not be called first, because in some
475 // cases scr_write_outgoing_message() will load the history and we'd
476 // have the message twice...
477 if (encrypted == ENCRYPTED_PGP)
478 message_flags |= HBB_PREFIX_PGPCRYPT;
479 else if (encrypted == ENCRYPTED_OTR)
480 message_flags |= HBB_PREFIX_OTRCRYPT;
481
482 if (carbon)
483 message_flags |= HBB_PREFIX_CARBON | HBB_PREFIX_NOFLAG;
484
485 scr_write_outgoing_message(bjid, wmsg, message_flags, xep184);
486
487 // We don't log private messages
488 if (!nick)
489 hlog_write_message(bjid, timestamp, 1, msg);
490
491 #ifdef MODULES_ENABLE
492 {
493 hk_arg_t args[] = {
494 { "jid", bjid },
495 { "message", wmsg },
496 { NULL, NULL },
497 };
498 hk_run_handlers(HOOK_MESSAGE_OUT, args);
499 // TODO: check (and use) return value
500 }
501 #endif
502
503 // External command
504 hk_ext_cmd(bjid, 'M', 'S', NULL);
505
506 g_free(bmsg);
507 g_free(mmsg);
508 }
509
hk_statuschange(const char * bjid,const char * resname,gchar prio,time_t timestamp,enum imstatus status,const char * status_msg)510 void hk_statuschange(const char *bjid, const char *resname, gchar prio,
511 time_t timestamp, enum imstatus status,
512 const char *status_msg)
513 {
514 int st_in_buf;
515 enum imstatus oldstat;
516 char *bn;
517 char *logsmsg;
518 const char *rn = (resname ? resname : "");
519 const char *ename = NULL;
520
521 if (settings_opt_get_int("eventcmd_use_nickname"))
522 ename = roster_getname(bjid);
523
524 oldstat = roster_getstatus(bjid, resname);
525
526 st_in_buf = settings_opt_get_int("show_status_in_buffer");
527
528 if (settings_opt_get_int("log_display_presence")) {
529 int buddy_format = settings_opt_get_int("buddy_format");
530 bn = NULL;
531 if (buddy_format) {
532 const char *name = roster_getname(bjid);
533 if (name && strcmp(name, bjid)) {
534 if (buddy_format == 1)
535 bn = g_strdup_printf("%s <%s/%s>", name, bjid, rn);
536 else if (buddy_format == 2)
537 bn = g_strdup_printf("%s/%s", name, rn);
538 else if (buddy_format == 3)
539 bn = g_strdup_printf("%s", name);
540 }
541 }
542
543 if (!bn)
544 bn = g_strdup_printf("<%s/%s>", bjid, rn);
545
546 logsmsg = g_strdup(status_msg ? status_msg : "");
547 replace_nl_with_dots(logsmsg);
548
549 scr_LogPrint(LPRINT_LOGNORM, "Buddy status has changed: [%c>%c] %s %s",
550 imstatus2char[oldstat], imstatus2char[status], bn, logsmsg);
551 g_free(logsmsg);
552 g_free(bn);
553 }
554
555 if (st_in_buf == 2 ||
556 (st_in_buf == 1 && (status == offline || oldstat == offline))) {
557 // Write the status change in the buddy's buffer, only if it already exists
558 if (scr_buddy_buffer_exists(bjid)) {
559 bn = g_strdup_printf("Buddy status has changed: [%c>%c] [%s] %s",
560 imstatus2char[oldstat], imstatus2char[status], rn,
561 ((status_msg) ? status_msg : ""));
562 scr_write_incoming_message(bjid, bn, timestamp,
563 HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
564 g_free(bn);
565 }
566 }
567
568 roster_setstatus(bjid, rn, prio, status, status_msg, timestamp,
569 role_none, affil_none, NULL);
570 buddylist_defer_build();
571 scr_update_roster();
572 hlog_write_status(bjid, timestamp, status, status_msg);
573
574 #ifdef MODULES_ENABLE
575 {
576 char os[2] = " \0";
577 char ns[2] = " \0";
578 hk_arg_t args[] = {
579 { "jid", bjid },
580 { "resource", rn },
581 { "old_status", os },
582 { "new_status", ns },
583 { "message", status_msg ? status_msg : "" },
584 { NULL, NULL },
585 };
586 os[0] = imstatus2char[oldstat];
587 ns[0] = imstatus2char[status];
588
589 hk_run_handlers(HOOK_STATUS_CHANGE, args);
590 }
591 #endif
592
593 // External command
594 hk_ext_cmd(ename ? ename : bjid, 'S', imstatus2char[status], NULL);
595 }
596
hk_mystatuschange(time_t timestamp,enum imstatus old_status,enum imstatus new_status,const char * msg)597 void hk_mystatuschange(time_t timestamp, enum imstatus old_status,
598 enum imstatus new_status, const char *msg)
599 {
600 scr_LogPrint(LPRINT_LOGNORM, "Your status has been set: [%c>%c] %s",
601 imstatus2char[old_status], imstatus2char[new_status],
602 (msg ? msg : ""));
603
604 #ifdef MODULES_ENABLE
605 {
606 char ns[2] = " \0";
607 hk_arg_t args[] = {
608 { "new_status", ns },
609 { "message", msg ? msg : "" },
610 { NULL, NULL },
611 };
612 ns[0] = imstatus2char[new_status];
613
614 hk_run_handlers(HOOK_MY_STATUS_CHANGE, args);
615 }
616 #endif
617
618 //hlog_write_status(NULL, 0, status);
619 }
620
hk_postconnect(void)621 void hk_postconnect(void)
622 {
623 const char *hook_command;
624 char *cmdline;
625
626 #ifdef MODULES_ENABLE
627 {
628 hk_arg_t args[] = {
629 { NULL, NULL },
630 };
631 hk_run_handlers(HOOK_POST_CONNECT, args);
632 }
633 #endif
634
635 hook_command = settings_opt_get("hook-post-connect");
636 if (!hook_command)
637 return;
638
639 scr_LogPrint(LPRINT_LOGNORM, "Running hook-post-connect...");
640
641 cmdline = from_utf8(hook_command);
642 process_command(cmdline, TRUE);
643
644 g_free(cmdline);
645 }
646
hk_predisconnect(void)647 void hk_predisconnect(void)
648 {
649 const char *hook_command;
650 char *cmdline;
651
652 #ifdef MODULES_ENABLE
653 {
654 hk_arg_t args[] = {
655 { NULL, NULL },
656 };
657 hk_run_handlers(HOOK_PRE_DISCONNECT, args);
658 }
659 #endif
660
661 hook_command = settings_opt_get("hook-pre-disconnect");
662 if (!hook_command)
663 return;
664
665 scr_LogPrint(LPRINT_LOGNORM, "Running hook-pre-disconnect...");
666
667 cmdline = from_utf8(hook_command);
668 process_command(cmdline, TRUE);
669
670 g_free(cmdline);
671 }
672
hk_unread_list_change(guint unread_count,guint attention_count,guint muc_unread,guint muc_attention)673 void hk_unread_list_change(guint unread_count, guint attention_count,
674 guint muc_unread, guint muc_attention)
675 {
676 // Previous static variables are initialized with an unlikely value
677 static guint prev_unread = 65535;
678 static guint prev_attention = 65535;
679 static guint prev_muc_unread = 65535;
680 static guint prev_muc_attention = 65535;
681 gchar *str_unread;
682
683 // Do not call the handlers if the unread values haven't changed
684 if (unread_count == prev_unread &&
685 attention_count == prev_attention &&
686 muc_unread == prev_muc_unread &&
687 muc_attention == prev_muc_attention)
688 return;
689
690 #ifdef MODULES_ENABLE
691 {
692 str_unread = g_strdup_printf("%u", unread_count);
693 gchar *str_attention = g_strdup_printf("%u", attention_count);
694 gchar *str_muc_unread = g_strdup_printf("%u", muc_unread);
695 gchar *str_muc_attention = g_strdup_printf("%u", muc_attention);
696 hk_arg_t args[] = {
697 { "unread", str_unread }, // All unread
698 { "attention", str_attention }, // Attention (private)
699 { "muc_unread", str_muc_unread }, // MUC unread
700 { "muc_attention", str_muc_attention }, // MUC attention (highlight)
701 { NULL, NULL },
702 };
703 hk_run_handlers(HOOK_UNREAD_LIST_CHANGE, args);
704 g_free(str_unread);
705 g_free(str_attention);
706 g_free(str_muc_unread);
707 g_free(str_muc_attention);
708 }
709 #endif
710
711 prev_unread = unread_count;
712 prev_attention = attention_count;
713 prev_muc_unread = muc_unread;
714 prev_muc_attention = muc_attention;
715
716 /* Call external command */
717 str_unread = g_strdup_printf("%u %u %u %u", unread_count, attention_count,
718 muc_unread, muc_attention);
719 hk_ext_cmd("", 'U', (guchar)MIN(255, unread_count), str_unread);
720 g_free(str_unread);
721 }
722
723 // hk_presence_subscription_request(jid, message)
724 // Return non-zero if mcabber should stop processing the subscription request
hk_subscription(LmMessageSubType mstype,const gchar * bjid,const gchar * msg)725 guint hk_subscription(LmMessageSubType mstype, const gchar *bjid,
726 const gchar *msg)
727 {
728 #ifdef MODULES_ENABLE
729 guint h_result;
730 const char *stype;
731
732 if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBE)
733 stype = "subscribe";
734 else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE)
735 stype = "unsubscribe";
736 else if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBED)
737 stype = "subscribed";
738 else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED)
739 stype = "unsubscribed";
740 else return 0; // Should not happen
741
742 {
743 hk_arg_t args[] = {
744 { "type", stype },
745 { "jid", bjid },
746 { "message", msg ? msg : "" },
747 { NULL, NULL },
748 };
749 h_result = hk_run_handlers(HOOK_SUBSCRIPTION, args);
750 }
751 if (h_result != HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS) {
752 scr_LogPrint(LPRINT_DEBUG, "Subscription message ignored (hook result).");
753 return h_result;
754 }
755 #endif
756 return 0;
757 }
758
759
760 /* External commands */
761
762 // hk_ext_cmd_init()
763 // Initialize external command variable.
764 // Can be called with parameter NULL to reset and free memory.
hk_ext_cmd_init(const char * command)765 void hk_ext_cmd_init(const char *command)
766 {
767 if (extcmd) {
768 g_free(extcmd);
769 extcmd = NULL;
770 }
771 if (command)
772 extcmd = expand_filename(command);
773 }
774
775 // hk_ext_cmd()
776 // Launch an external command (process) for the given event.
777 // For now, data should be NULL.
hk_ext_cmd(const char * bjid,guchar type,guchar info,const char * data)778 void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data)
779 {
780 pid_t pid;
781 const char *arg_type = NULL;
782 const char *arg_info = NULL;
783 const char *arg_data = NULL;
784 char status_str[2];
785 char *datafname = NULL;
786
787 if (!extcmd) return;
788
789 // Prepare arg_* (external command parameters)
790 switch (type) {
791 case 'M': /* Normal message */
792 arg_type = "MSG";
793 if (info == 'R')
794 arg_info = "IN";
795 else if (info == 'S')
796 arg_info = "OUT";
797 break;
798 case 'G': /* Groupchat message */
799 arg_type = "MSG";
800 arg_info = "MUC";
801 break;
802 case 'S': /* Status change */
803 arg_type = "STATUS";
804 if (strchr(imstatus2char, tolower(info))) {
805 status_str[0] = toupper(info);
806 status_str[1] = 0;
807 arg_info = status_str;
808 }
809 break;
810 case 'U': /* Unread buffer count */
811 arg_type = "UNREAD";
812 arg_info = data;
813 break;
814 default:
815 return;
816 }
817
818 if (!arg_type || !arg_info) return;
819
820 if (strchr("MG", type) && data && settings_opt_get_int("event_log_files")) {
821 int fd;
822 const char *prefix;
823 char *prefix_xp = NULL;
824 char *data_locale;
825
826 data_locale = from_utf8(data);
827 prefix = settings_opt_get("event_log_dir");
828 if (prefix)
829 prefix = prefix_xp = expand_filename(prefix);
830 else
831 prefix = ut_get_tmpdir();
832 datafname = g_strdup_printf("%s/mcabber-%d.XXXXXX", prefix, getpid());
833 g_free(prefix_xp);
834
835 // XXX Some old systems may require us to set umask first.
836 fd = mkstemp(datafname);
837 if (fd == -1) {
838 g_free(datafname);
839 datafname = NULL;
840 scr_LogPrint(LPRINT_LOGNORM,
841 "Unable to create temp file for external command.");
842 } else {
843 size_t data_locale_len = strlen(data_locale);
844 ssize_t a = write(fd, data_locale, data_locale_len);
845 ssize_t b = write(fd, "\n", 1);
846 if ((size_t)a != data_locale_len || b != 1) {
847 g_free(datafname);
848 datafname = NULL;
849 scr_LogPrint(LPRINT_LOGNORM,
850 "Unable to write to temp file for external command.");
851 }
852 close(fd);
853 arg_data = datafname;
854 }
855 g_free(data_locale);
856 }
857
858 if ((pid=fork()) == -1) {
859 scr_LogPrint(LPRINT_LOGNORM, "Fork error, cannot launch external command.");
860 g_free(datafname);
861 return;
862 }
863
864 if (pid == 0) { // child
865 // Close standard file descriptors
866 close(STDIN_FILENO);
867 close(STDOUT_FILENO);
868 close(STDERR_FILENO);
869 if (execl(extcmd, extcmd, arg_type, arg_info, bjid, arg_data,
870 (char *)NULL) == -1) {
871 // scr_LogPrint(LPRINT_LOGNORM, "Cannot execute external command.");
872 exit(1);
873 }
874 }
875 g_free(datafname);
876 }
877
878 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
879