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