Mercurial > ~mikael > mcabber > hg
comparison mcabber/mcabber/xmpp.c @ 1668:41c26b7d2890
Install mcabber headers
* Change mcabber headers naming scheme
* Move 'src/' -> 'mcabber/'
* Add missing include <mcabber/config.h>'s
* Create and install clean config.h version in 'include/'
* Move "dirty" config.h version to 'mcabber/'
* Add $(top_srcdir) to compiler include path
* Update modules HOWTO
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Mon, 18 Jan 2010 15:36:19 +0200 |
parents | mcabber/src/xmpp.c@1a4890514eb9 |
children | d1e8fb14ce2d |
comparison
equal
deleted
inserted
replaced
1667:8af0e0ad20ad | 1668:41c26b7d2890 |
---|---|
1 /* | |
2 * xmpp.c -- Jabber protocol handling | |
3 * | |
4 * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de> | |
5 * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net> | |
6 * Parts come from the centericq project: | |
7 * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua> | |
8 * | |
9 * This program is free software; you can redistribute it and/or modify | |
10 * it under the terms of the GNU General Public License as published by | |
11 * the Free Software Foundation; either version 2 of the License, or (at | |
12 * your option) any later version. | |
13 * | |
14 * This program is distributed in the hope that it will be useful, but | |
15 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 * General Public License for more details. | |
18 * | |
19 * You should have received a copy of the GNU General Public License | |
20 * along with this program; if not, write to the Free Software | |
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | |
22 * USA | |
23 */ | |
24 #include <stdlib.h> | |
25 #include <string.h> | |
26 | |
27 #include "xmpp.h" | |
28 #include "xmpp_helper.h" | |
29 #include "xmpp_iq.h" | |
30 #include "xmpp_iqrequest.h" | |
31 #include "xmpp_muc.h" | |
32 #include "xmpp_s10n.h" | |
33 #include "caps.h" | |
34 #include "events.h" | |
35 #include "histolog.h" | |
36 #include "hooks.h" | |
37 #include "otr.h" | |
38 #include "roster.h" | |
39 #include "screen.h" | |
40 #include "settings.h" | |
41 #include "utils.h" | |
42 #include "main.h" | |
43 | |
44 #define RECONNECTION_TIMEOUT 60L | |
45 | |
46 LmConnection* lconnection; | |
47 static guint AutoConnection; | |
48 | |
49 inline void update_last_use(void); | |
50 inline gboolean xmpp_reconnect(); | |
51 | |
52 enum imstatus mystatus = offline; | |
53 static enum imstatus mywantedstatus = available; | |
54 gchar *mystatusmsg; | |
55 | |
56 char imstatus2char[imstatus_size+1] = { | |
57 '_', 'o', 'f', 'd', 'n', 'a', 'i', '\0' | |
58 }; | |
59 | |
60 char *imstatus_showmap[] = { | |
61 "", | |
62 "", | |
63 "chat", | |
64 "dnd", | |
65 "xa", | |
66 "away", | |
67 "" | |
68 }; | |
69 | |
70 LmMessageNode *bookmarks = NULL; | |
71 LmMessageNode *rosternotes = NULL; | |
72 | |
73 static struct IqHandlers | |
74 { | |
75 const gchar *xmlns; | |
76 LmHandleMessageFunction handler; | |
77 } iq_handlers[] = { | |
78 {NS_PING, &handle_iq_ping}, | |
79 {NS_VERSION, &handle_iq_version}, | |
80 {NS_TIME, &handle_iq_time}, | |
81 {NS_ROSTER, &handle_iq_roster}, | |
82 {NS_XMPP_TIME, &handle_iq_time202}, | |
83 {NS_LAST, &handle_iq_last}, | |
84 {NS_DISCO_INFO, &handle_iq_disco_info}, | |
85 {NS_DISCO_ITEMS,&handle_iq_disco_items}, | |
86 {NS_COMMANDS, &handle_iq_commands}, | |
87 {NS_VCARD, &handle_iq_vcard}, | |
88 {NULL, NULL} | |
89 }; | |
90 | |
91 void update_last_use(void) | |
92 { | |
93 iqlast = time(NULL); | |
94 } | |
95 | |
96 // Note: the caller should check the jid is correct | |
97 void xmpp_addbuddy(const char *bjid, const char *name, const char *group) | |
98 { | |
99 LmMessageNode *query, *y; | |
100 LmMessage *iq; | |
101 char *cleanjid; | |
102 | |
103 if (!lm_connection_is_authenticated(lconnection)) return; | |
104 | |
105 cleanjid = jidtodisp(bjid); // Stripping resource, just in case... | |
106 | |
107 // We don't check if the jabber user already exists in the roster, | |
108 // because it allows to re-ask for notification. | |
109 | |
110 iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, | |
111 LM_MESSAGE_SUB_TYPE_SET); | |
112 query = lm_message_node_add_child(iq->node, "query", NULL); | |
113 lm_message_node_set_attribute(query, "xmlns", NS_ROSTER); | |
114 y = lm_message_node_add_child(query, "item", NULL); | |
115 lm_message_node_set_attribute(y, "jid", cleanjid); | |
116 | |
117 if (name) | |
118 lm_message_node_set_attribute(y, "name", name); | |
119 | |
120 if (group) | |
121 lm_message_node_add_child(y, "group", group); | |
122 | |
123 lm_connection_send(lconnection, iq, NULL); | |
124 lm_message_unref(iq); | |
125 | |
126 xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_SUBSCRIBE); | |
127 | |
128 roster_add_user(cleanjid, name, group, ROSTER_TYPE_USER, sub_pending, -1); | |
129 g_free(cleanjid); | |
130 buddylist_build(); | |
131 | |
132 update_roster = TRUE; | |
133 } | |
134 | |
135 void xmpp_updatebuddy(const char *bjid, const char *name, const char *group) | |
136 { | |
137 LmMessage *iq; | |
138 LmMessageNode *x; | |
139 char *cleanjid; | |
140 | |
141 if (!lm_connection_is_authenticated(lconnection)) return; | |
142 | |
143 // XXX We should check name's and group's correctness | |
144 | |
145 cleanjid = jidtodisp(bjid); // Stripping resource, just in case... | |
146 | |
147 iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, | |
148 LM_MESSAGE_SUB_TYPE_SET); | |
149 x = lm_message_node_add_child(iq->node, "query", NULL); | |
150 lm_message_node_set_attribute(x, "xmlns", NS_ROSTER); | |
151 x = lm_message_node_add_child(x, "item", NULL); | |
152 lm_message_node_set_attributes(x, | |
153 "jid", cleanjid, | |
154 "name", name, | |
155 NULL); | |
156 | |
157 if (group) | |
158 lm_message_node_add_child(x, "group", group); | |
159 | |
160 lm_connection_send(lconnection, iq, NULL); | |
161 lm_message_unref(iq); | |
162 g_free(cleanjid); | |
163 } | |
164 | |
165 void xmpp_delbuddy(const char *bjid) | |
166 { | |
167 LmMessageNode *y, *z; | |
168 LmMessage *iq; | |
169 char *cleanjid; | |
170 | |
171 if (!lm_connection_is_authenticated(lconnection)) return; | |
172 | |
173 cleanjid = jidtodisp(bjid); // Stripping resource, just in case... | |
174 | |
175 // If the current buddy is an agent, unsubscribe from it | |
176 if (roster_gettype(cleanjid) == ROSTER_TYPE_AGENT) { | |
177 scr_LogPrint(LPRINT_LOGNORM, "Unregistering from the %s agent", cleanjid); | |
178 | |
179 iq = lm_message_new_with_sub_type(cleanjid, LM_MESSAGE_TYPE_IQ, | |
180 LM_MESSAGE_SUB_TYPE_SET); | |
181 y = lm_message_node_add_child(iq->node, "query", NULL); | |
182 lm_message_node_set_attribute(y, "xmlns", NS_REGISTER); | |
183 lm_message_node_add_child(y, "remove", NULL); | |
184 lm_connection_send(lconnection, iq, NULL); | |
185 lm_message_unref(iq); | |
186 } | |
187 | |
188 // Cancel the subscriptions | |
189 xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); //cancel "from" | |
190 xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE); //cancel "to" | |
191 | |
192 // Ask for removal from roster | |
193 iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, | |
194 LM_MESSAGE_SUB_TYPE_SET); | |
195 | |
196 y = lm_message_node_add_child(iq->node, "query", NULL); | |
197 lm_message_node_set_attribute(y, "xmlns", NS_ROSTER); | |
198 z = lm_message_node_add_child(y, "item", NULL); | |
199 lm_message_node_set_attributes(z, | |
200 "jid", cleanjid, | |
201 "subscription", "remove", | |
202 NULL); | |
203 lm_connection_send(lconnection, iq, NULL); | |
204 lm_message_unref(iq); | |
205 | |
206 roster_del_user(cleanjid); | |
207 g_free(cleanjid); | |
208 buddylist_build(); | |
209 | |
210 update_roster = TRUE; | |
211 } | |
212 | |
213 void xmpp_request(const char *fjid, enum iqreq_type reqtype) | |
214 { | |
215 GSList *resources, *p_res; | |
216 GSList *roster_elt; | |
217 const char *strreqtype, *xmlns; | |
218 | |
219 if (reqtype == iqreq_version) { | |
220 xmlns = NS_VERSION; | |
221 strreqtype = "version"; | |
222 } else if (reqtype == iqreq_time) { | |
223 xmlns = NS_TIME; | |
224 strreqtype = "time"; | |
225 } else if (reqtype == iqreq_last) { | |
226 xmlns = NS_LAST; | |
227 strreqtype = "last"; | |
228 } else if (reqtype == iqreq_vcard) { | |
229 xmlns = NS_VCARD; | |
230 strreqtype = "vCard"; | |
231 // Special case | |
232 } else | |
233 return; | |
234 | |
235 if (strchr(fjid, JID_RESOURCE_SEPARATOR)) { | |
236 // This is a full JID | |
237 xmpp_iq_request(fjid, xmlns); | |
238 scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); | |
239 return; | |
240 } | |
241 | |
242 // The resource has not been specified | |
243 roster_elt = roster_find(fjid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM); | |
244 if (!roster_elt) { | |
245 scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid); | |
246 xmpp_iq_request(fjid, xmlns); // Let's send a request anyway... | |
247 scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); | |
248 return; | |
249 } | |
250 | |
251 // Send a request to each resource | |
252 resources = buddy_getresources(roster_elt->data); | |
253 if (!resources) { | |
254 scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid); | |
255 xmpp_iq_request(fjid, xmlns); // Let's send a request anyway... | |
256 scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); | |
257 } | |
258 for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { | |
259 gchar *fulljid; | |
260 fulljid = g_strdup_printf("%s/%s", fjid, (char*)p_res->data); | |
261 xmpp_iq_request(fulljid, xmlns); | |
262 scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fulljid); | |
263 g_free(fulljid); | |
264 g_free(p_res->data); | |
265 } | |
266 g_slist_free(resources); | |
267 } | |
268 | |
269 static LmHandlerResult cb_xep184(LmMessageHandler *h, LmConnection *c, | |
270 LmMessage *m, gpointer user_data) | |
271 { | |
272 char *from = jidtodisp(lm_message_get_from(m)); | |
273 scr_RemoveReceiptFlag(from, h); | |
274 g_free(from); | |
275 return LM_HANDLER_RESULT_REMOVE_MESSAGE; | |
276 } | |
277 | |
278 // xmpp_send_msg(jid, text, type, subject, | |
279 // otrinject, *encrypted, type_overwrite) | |
280 // When encrypted is not NULL, the function set *encrypted to 1 if the | |
281 // message has been PGP-encrypted. If encryption enforcement is set and | |
282 // encryption fails, *encrypted is set to -1. | |
283 void xmpp_send_msg(const char *fjid, const char *text, int type, | |
284 const char *subject, gboolean otrinject, gint *encrypted, | |
285 LmMessageSubType type_overwrite, gpointer *xep184) | |
286 { | |
287 LmMessage *x; | |
288 LmMessageSubType subtype; | |
289 #ifdef HAVE_LIBOTR | |
290 int otr_msg = 0; | |
291 #endif | |
292 #if defined HAVE_GPGME || defined JEP0022 || defined JEP0085 | |
293 char *rname, *barejid; | |
294 GSList *sl_buddy; | |
295 #endif | |
296 #if defined JEP0022 || defined JEP0085 | |
297 LmMessageNode *event; | |
298 guint use_jep85 = 0; | |
299 struct jep0085 *jep85 = NULL; | |
300 #endif | |
301 gchar *enc = NULL; | |
302 | |
303 if (encrypted) | |
304 *encrypted = 0; | |
305 | |
306 if (!lm_connection_is_authenticated(lconnection)) | |
307 return; | |
308 | |
309 if (!text && type == ROSTER_TYPE_USER) | |
310 return; | |
311 | |
312 if (type_overwrite != LM_MESSAGE_SUB_TYPE_NOT_SET) | |
313 subtype = type_overwrite; | |
314 else { | |
315 if (type == ROSTER_TYPE_ROOM) | |
316 subtype = LM_MESSAGE_SUB_TYPE_GROUPCHAT; | |
317 else | |
318 subtype = LM_MESSAGE_SUB_TYPE_CHAT; | |
319 } | |
320 | |
321 #if defined HAVE_GPGME || defined HAVE_LIBOTR || \ | |
322 defined JEP0022 || defined JEP0085 | |
323 rname = strchr(fjid, JID_RESOURCE_SEPARATOR); | |
324 barejid = jidtodisp(fjid); | |
325 sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); | |
326 | |
327 // If we can get a resource name, we use it. Else we use NULL, | |
328 // which hopefully will give us the most likely resource. | |
329 if (rname) | |
330 rname++; | |
331 | |
332 #ifdef HAVE_LIBOTR | |
333 if (otr_enabled() && !otrinject) { | |
334 if (type == ROSTER_TYPE_USER) { | |
335 otr_msg = otr_send((char **)&text, barejid); | |
336 if (!text) { | |
337 g_free(barejid); | |
338 if (encrypted) | |
339 *encrypted = -1; | |
340 return; | |
341 } | |
342 } | |
343 if (otr_msg && encrypted) | |
344 *encrypted = ENCRYPTED_OTR; | |
345 } | |
346 #endif | |
347 | |
348 #ifdef HAVE_GPGME | |
349 if (type == ROSTER_TYPE_USER && sl_buddy && gpg_enabled()) { | |
350 if (!settings_pgp_getdisabled(barejid)) { // not disabled for this contact? | |
351 guint force; | |
352 struct pgp_data *res_pgpdata; | |
353 force = settings_pgp_getforce(barejid); | |
354 res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname); | |
355 if (force || (res_pgpdata && res_pgpdata->sign_keyid)) { | |
356 /* Remote client has PGP support (we have a signature) | |
357 * OR encryption is enforced (force = TRUE). | |
358 * If the contact has a specific KeyId, we'll use it; | |
359 * if not, we'll use the key used for the signature. | |
360 * Both keys should match, in theory (cf. XEP-0027). */ | |
361 const char *key; | |
362 key = settings_pgp_getkeyid(barejid); | |
363 if (!key && res_pgpdata) | |
364 key = res_pgpdata->sign_keyid; | |
365 if (key) | |
366 enc = gpg_encrypt(text, key); | |
367 if (!enc && force) { | |
368 if (encrypted) | |
369 *encrypted = -1; | |
370 g_free(barejid); | |
371 return; | |
372 } | |
373 } | |
374 } | |
375 } | |
376 #endif // HAVE_GPGME | |
377 | |
378 g_free(barejid); | |
379 #endif // HAVE_GPGME || defined JEP0022 || defined JEP0085 | |
380 | |
381 x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, subtype); | |
382 lm_message_node_add_child(x->node, "body", | |
383 enc ? "This message is PGP-encrypted." : text); | |
384 | |
385 if (subject) | |
386 lm_message_node_add_child(x->node, "subject", subject); | |
387 | |
388 if (enc) { | |
389 LmMessageNode *y; | |
390 y = lm_message_node_add_child(x->node, "x", enc); | |
391 lm_message_node_set_attribute(y, "xmlns", NS_ENCRYPTED); | |
392 if (encrypted) | |
393 *encrypted = ENCRYPTED_PGP; | |
394 g_free(enc); | |
395 } | |
396 | |
397 //XEP-0184: Message Receipts | |
398 if (sl_buddy && rname && xep184 && | |
399 caps_has_feature(buddy_resource_getcaps(sl_buddy->data, rname), | |
400 NS_RECEIPTS)) { | |
401 lm_message_node_set_attribute | |
402 (lm_message_node_add_child(x->node, "request", NULL), | |
403 "xmlns", NS_RECEIPTS); | |
404 *xep184 = lm_message_handler_new(cb_xep184, NULL, NULL); | |
405 } | |
406 | |
407 #if defined JEP0022 || defined JEP0085 | |
408 // If typing notifications are disabled, we can skip all this stuff... | |
409 if (chatstates_disabled || type == ROSTER_TYPE_ROOM) | |
410 goto xmpp_send_msg_no_chatstates; | |
411 | |
412 if (sl_buddy) | |
413 jep85 = buddy_resource_jep85(sl_buddy->data, rname); | |
414 #endif | |
415 | |
416 #ifdef JEP0085 | |
417 /* JEP-0085 5.1 | |
418 * "Until receiving a reply to the initial content message (or a standalone | |
419 * notification) from the Contact, the User MUST NOT send subsequent chat | |
420 * state notifications to the Contact." | |
421 * In our implementation support is initially "unknown", then it's "probed" | |
422 * and can become "ok". | |
423 */ | |
424 if (jep85 && (jep85->support == CHATSTATES_SUPPORT_OK || | |
425 jep85->support == CHATSTATES_SUPPORT_UNKNOWN)) { | |
426 event = lm_message_node_add_child(x->node, "active", NULL); | |
427 lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES); | |
428 if (jep85->support == CHATSTATES_SUPPORT_UNKNOWN) | |
429 jep85->support = CHATSTATES_SUPPORT_PROBED; | |
430 else | |
431 use_jep85 = 1; | |
432 jep85->last_state_sent = ROSTER_EVENT_ACTIVE; | |
433 } | |
434 #endif | |
435 #ifdef JEP0022 | |
436 /* JEP-22 | |
437 * If the Contact supports JEP-0085, we do not use JEP-0022. | |
438 * If not, we try to fall back to JEP-0022. | |
439 */ | |
440 if (!use_jep85) { | |
441 struct jep0022 *jep22 = NULL; | |
442 event = lm_message_node_add_child(x->node, "x", NULL); | |
443 lm_message_node_set_attribute(event, "xmlns", NS_EVENT); | |
444 lm_message_node_add_child(event, "composing", NULL); | |
445 | |
446 if (sl_buddy) | |
447 jep22 = buddy_resource_jep22(sl_buddy->data, rname); | |
448 if (jep22) | |
449 jep22->last_state_sent = ROSTER_EVENT_ACTIVE; | |
450 | |
451 // An id is mandatory when using JEP-0022. | |
452 if (text || subject) { | |
453 const gchar *msgid = lm_message_get_id(x); | |
454 // Let's update last_msgid_sent | |
455 if (jep22) { | |
456 g_free(jep22->last_msgid_sent); | |
457 jep22->last_msgid_sent = g_strdup(msgid); | |
458 } | |
459 } | |
460 } | |
461 #endif | |
462 | |
463 xmpp_send_msg_no_chatstates: | |
464 if (mystatus != invisible) | |
465 update_last_use(); | |
466 if (xep184 && *xep184) { | |
467 lm_connection_send_with_reply(lconnection, x, *xep184, NULL); | |
468 lm_message_handler_unref(*xep184); | |
469 } else | |
470 lm_connection_send(lconnection, x, NULL); | |
471 lm_message_unref(x); | |
472 } | |
473 | |
474 #ifdef JEP0085 | |
475 // xmpp_send_jep85_chatstate() | |
476 // Send a JEP-85 chatstate. | |
477 static void xmpp_send_jep85_chatstate(const char *bjid, const char *resname, | |
478 guint state) | |
479 { | |
480 LmMessage *m; | |
481 LmMessageNode *event; | |
482 GSList *sl_buddy; | |
483 const char *chattag; | |
484 char *rjid, *fjid = NULL; | |
485 struct jep0085 *jep85 = NULL; | |
486 | |
487 if (!lm_connection_is_authenticated(lconnection)) return; | |
488 | |
489 sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER); | |
490 | |
491 // If we have a resource name, we use it. Else we use NULL, | |
492 // which hopefully will give us the most likely resource. | |
493 if (sl_buddy) | |
494 jep85 = buddy_resource_jep85(sl_buddy->data, resname); | |
495 | |
496 if (!jep85 || (jep85->support != CHATSTATES_SUPPORT_OK)) | |
497 return; | |
498 | |
499 if (state == jep85->last_state_sent) | |
500 return; | |
501 | |
502 if (state == ROSTER_EVENT_ACTIVE) | |
503 chattag = "active"; | |
504 else if (state == ROSTER_EVENT_COMPOSING) | |
505 chattag = "composing"; | |
506 else if (state == ROSTER_EVENT_PAUSED) | |
507 chattag = "paused"; | |
508 else { | |
509 scr_LogPrint(LPRINT_LOGNORM, "Error: unsupported JEP-85 state (%d)", state); | |
510 return; | |
511 } | |
512 | |
513 jep85->last_state_sent = state; | |
514 | |
515 if (resname) | |
516 fjid = g_strdup_printf("%s/%s", bjid, resname); | |
517 | |
518 rjid = resname ? fjid : (char*)bjid; | |
519 m = lm_message_new_with_sub_type(rjid, LM_MESSAGE_TYPE_MESSAGE, | |
520 LM_MESSAGE_SUB_TYPE_CHAT); | |
521 | |
522 event = lm_message_node_add_child(m->node, chattag, NULL); | |
523 lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES); | |
524 | |
525 lm_connection_send(lconnection, m, NULL); | |
526 lm_message_unref(m); | |
527 | |
528 g_free(fjid); | |
529 } | |
530 #endif | |
531 | |
532 #ifdef JEP0022 | |
533 // xmpp_send_jep22_event() | |
534 // Send a JEP-22 message event (delivered, composing...). | |
535 static void xmpp_send_jep22_event(const char *fjid, guint type) | |
536 { | |
537 LmMessage *x; | |
538 LmMessageNode *event; | |
539 const char *msgid; | |
540 char *rname, *barejid; | |
541 GSList *sl_buddy; | |
542 struct jep0022 *jep22 = NULL; | |
543 guint jep22_state; | |
544 | |
545 if (!lm_connection_is_authenticated(lconnection)) return; | |
546 | |
547 rname = strchr(fjid, JID_RESOURCE_SEPARATOR); | |
548 barejid = jidtodisp(fjid); | |
549 sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); | |
550 g_free(barejid); | |
551 | |
552 // If we can get a resource name, we use it. Else we use NULL, | |
553 // which hopefully will give us the most likely resource. | |
554 if (rname) | |
555 rname++; | |
556 if (sl_buddy) | |
557 jep22 = buddy_resource_jep22(sl_buddy->data, rname); | |
558 | |
559 if (!jep22) | |
560 return; // XXX Maybe we could try harder (other resources?) | |
561 | |
562 msgid = jep22->last_msgid_rcvd; | |
563 | |
564 // For composing events (composing, active, inactive, paused...), | |
565 // JEP22 only has 2 states; we'll use composing and active. | |
566 if (type == ROSTER_EVENT_COMPOSING) | |
567 jep22_state = ROSTER_EVENT_COMPOSING; | |
568 else if (type == ROSTER_EVENT_ACTIVE || | |
569 type == ROSTER_EVENT_PAUSED) | |
570 jep22_state = ROSTER_EVENT_ACTIVE; | |
571 else | |
572 jep22_state = 0; // ROSTER_EVENT_NONE | |
573 | |
574 if (jep22_state) { | |
575 // Do not re-send a same event | |
576 if (jep22_state == jep22->last_state_sent) | |
577 return; | |
578 jep22->last_state_sent = jep22_state; | |
579 } | |
580 | |
581 x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, | |
582 LM_MESSAGE_SUB_TYPE_CHAT); | |
583 | |
584 event = lm_message_node_add_child(x->node, "x", NULL); | |
585 lm_message_node_set_attribute(event, "xmlns", NS_EVENT); | |
586 if (type == ROSTER_EVENT_DELIVERED) | |
587 lm_message_node_add_child(event, "delivered", NULL); | |
588 else if (type == ROSTER_EVENT_COMPOSING) | |
589 lm_message_node_add_child(event, "composing", NULL); | |
590 lm_message_node_add_child(event, "id", msgid); | |
591 | |
592 lm_connection_send(lconnection, x, NULL); | |
593 lm_message_unref(x); | |
594 } | |
595 #endif | |
596 | |
597 // xmpp_send_chatstate(buddy, state) | |
598 // Send a chatstate or event (JEP-22/85) according to the buddy's capabilities. | |
599 // The message is sent to one of the resources with the highest priority. | |
600 #if defined JEP0022 || defined JEP0085 | |
601 void xmpp_send_chatstate(gpointer buddy, guint chatstate) | |
602 { | |
603 const char *bjid; | |
604 #ifdef JEP0085 | |
605 GSList *resources, *p_res, *p_next; | |
606 struct jep0085 *jep85 = NULL; | |
607 #endif | |
608 #ifdef JEP0022 | |
609 struct jep0022 *jep22; | |
610 #endif | |
611 | |
612 bjid = buddy_getjid(buddy); | |
613 if (!bjid) return; | |
614 | |
615 #ifdef JEP0085 | |
616 /* Send the chatstate to the last resource (which should have the highest | |
617 priority). | |
618 If chatstate is "active", send an "active" state to all resources | |
619 which do not curently have this state. | |
620 */ | |
621 resources = buddy_getresources(buddy); | |
622 for (p_res = resources ; p_res ; p_res = p_next) { | |
623 p_next = g_slist_next(p_res); | |
624 jep85 = buddy_resource_jep85(buddy, p_res->data); | |
625 if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK) { | |
626 // If p_next is NULL, this is the highest (prio) resource, i.e. | |
627 // the one we are probably writing to. | |
628 if (!p_next || (jep85->last_state_sent != ROSTER_EVENT_ACTIVE && | |
629 chatstate == ROSTER_EVENT_ACTIVE)) | |
630 xmpp_send_jep85_chatstate(bjid, p_res->data, chatstate); | |
631 } | |
632 g_free(p_res->data); | |
633 } | |
634 g_slist_free(resources); | |
635 // If the last resource had chatstates support when can return now, | |
636 // we don't want to send a JEP22 event. | |
637 if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK) | |
638 return; | |
639 #endif | |
640 #ifdef JEP0022 | |
641 jep22 = buddy_resource_jep22(buddy, NULL); | |
642 if (jep22 && jep22->support == CHATSTATES_SUPPORT_OK) { | |
643 xmpp_send_jep22_event(bjid, chatstate); | |
644 } | |
645 #endif | |
646 } | |
647 #endif | |
648 | |
649 | |
650 // chatstates_reset_probed(fulljid) | |
651 // If the JEP has been probed for this contact, set it back to unknown so | |
652 // that we probe it again. The parameter must be a full jid (w/ resource). | |
653 #if defined JEP0022 || defined JEP0085 | |
654 static void chatstates_reset_probed(const char *fulljid) | |
655 { | |
656 char *rname, *barejid; | |
657 GSList *sl_buddy; | |
658 struct jep0085 *jep85; | |
659 struct jep0022 *jep22; | |
660 | |
661 rname = strchr(fulljid, JID_RESOURCE_SEPARATOR); | |
662 if (!rname++) | |
663 return; | |
664 | |
665 barejid = jidtodisp(fulljid); | |
666 sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); | |
667 g_free(barejid); | |
668 | |
669 if (!sl_buddy) | |
670 return; | |
671 | |
672 jep85 = buddy_resource_jep85(sl_buddy->data, rname); | |
673 jep22 = buddy_resource_jep22(sl_buddy->data, rname); | |
674 | |
675 if (jep85 && jep85->support == CHATSTATES_SUPPORT_PROBED) | |
676 jep85->support = CHATSTATES_SUPPORT_UNKNOWN; | |
677 if (jep22 && jep22->support == CHATSTATES_SUPPORT_PROBED) | |
678 jep22->support = CHATSTATES_SUPPORT_UNKNOWN; | |
679 } | |
680 #endif | |
681 | |
682 #ifdef HAVE_GPGME | |
683 // keys_mismatch(key, expectedkey) | |
684 // Return TRUE if both keys are non-null and "expectedkey" doesn't match | |
685 // the end of "key". | |
686 // If one of the keys is null, return FALSE. | |
687 // If expectedkey is less than 8 bytes long, return TRUE. | |
688 // | |
689 // Example: keys_mismatch("C9940A9BB0B92210", "B0B92210") will return FALSE. | |
690 static bool keys_mismatch(const char *key, const char *expectedkey) | |
691 { | |
692 int lk, lek; | |
693 | |
694 if (!expectedkey || !key) | |
695 return FALSE; | |
696 | |
697 lk = strlen(key); | |
698 lek = strlen(expectedkey); | |
699 | |
700 // If the expectedkey is less than 8 bytes long, this is probably a | |
701 // user mistake so we consider it's a mismatch. | |
702 if (lek < 8) | |
703 return TRUE; | |
704 | |
705 if (lek < lk) | |
706 key += lk - lek; | |
707 | |
708 return strcasecmp(key, expectedkey); | |
709 } | |
710 #endif | |
711 | |
712 // check_signature(barejid, resourcename, xmldata, text) | |
713 // Verify the signature (in xmldata) of "text" for the contact | |
714 // barejid/resourcename. | |
715 // xmldata is the 'jabber:x:signed' stanza. | |
716 // If the key id is found, the contact's PGP data are updated. | |
717 static void check_signature(const char *barejid, const char *rname, | |
718 LmMessageNode *node, const char *text) | |
719 { | |
720 #ifdef HAVE_GPGME | |
721 const char *p, *key; | |
722 GSList *sl_buddy; | |
723 struct pgp_data *res_pgpdata; | |
724 gpgme_sigsum_t sigsum; | |
725 | |
726 // All parameters must be valid | |
727 if (!(node && barejid && rname && text)) | |
728 return; | |
729 | |
730 if (!gpg_enabled()) | |
731 return; | |
732 | |
733 // Get the resource PGP data structure | |
734 sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); | |
735 if (!sl_buddy) | |
736 return; | |
737 res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname); | |
738 if (!res_pgpdata) | |
739 return; | |
740 | |
741 if (!node->name || strcmp(node->name, "x")) //XXX: probably useless | |
742 return; // We expect "<x xmlns='jabber:x:signed'>" | |
743 | |
744 // Get signature | |
745 p = lm_message_node_get_value(node); | |
746 if (!p) | |
747 return; | |
748 | |
749 key = gpg_verify(p, text, &sigsum); | |
750 if (key) { | |
751 const char *expectedkey; | |
752 char *buf; | |
753 g_free(res_pgpdata->sign_keyid); | |
754 res_pgpdata->sign_keyid = (char *)key; | |
755 res_pgpdata->last_sigsum = sigsum; | |
756 if (sigsum & GPGME_SIGSUM_RED) { | |
757 buf = g_strdup_printf("Bad signature from <%s/%s>", barejid, rname); | |
758 scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0); | |
759 scr_LogPrint(LPRINT_LOGNORM, "%s", buf); | |
760 g_free(buf); | |
761 } | |
762 // Verify that the key id is the one we expect. | |
763 expectedkey = settings_pgp_getkeyid(barejid); | |
764 if (keys_mismatch(key, expectedkey)) { | |
765 buf = g_strdup_printf("Warning: The KeyId from <%s/%s> doesn't match " | |
766 "the key you set up", barejid, rname); | |
767 scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0); | |
768 scr_LogPrint(LPRINT_LOGNORM, "%s", buf); | |
769 g_free(buf); | |
770 } | |
771 } | |
772 #endif | |
773 } | |
774 | |
775 static LmSSLResponse ssl_cb(LmSSL *ssl, LmSSLStatus status, gpointer ud) | |
776 { | |
777 scr_LogPrint(LPRINT_LOGNORM, "SSL status:%d", status); | |
778 | |
779 switch (status) { | |
780 case LM_SSL_STATUS_NO_CERT_FOUND: | |
781 scr_LogPrint(LPRINT_LOGNORM, "No certificate found!"); | |
782 break; | |
783 case LM_SSL_STATUS_UNTRUSTED_CERT: | |
784 scr_LogPrint(LPRINT_LOGNORM, "Certificate is not trusted!"); | |
785 break; | |
786 case LM_SSL_STATUS_CERT_EXPIRED: | |
787 scr_LogPrint(LPRINT_LOGNORM, "Certificate has expired!"); | |
788 break; | |
789 case LM_SSL_STATUS_CERT_NOT_ACTIVATED: | |
790 scr_LogPrint(LPRINT_LOGNORM, "Certificate has not been activated!"); | |
791 break; | |
792 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH: | |
793 scr_LogPrint(LPRINT_LOGNORM, | |
794 "Certificate hostname does not match expected hostname!"); | |
795 break; | |
796 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH: { | |
797 char fpr[49]; | |
798 fingerprint_to_hex((const unsigned char*)lm_ssl_get_fingerprint(ssl), | |
799 fpr); | |
800 scr_LogPrint(LPRINT_LOGNORM, | |
801 "Certificate fingerprint does not match expected fingerprint!"); | |
802 scr_LogPrint(LPRINT_LOGNORM, "Remote fingerprint: %s", fpr); | |
803 | |
804 scr_LogPrint(LPRINT_LOGNORM, "Expected fingerprint: %s", | |
805 settings_opt_get("ssl_fingerprint")); | |
806 | |
807 return LM_SSL_RESPONSE_STOP; | |
808 break; | |
809 } | |
810 case LM_SSL_STATUS_GENERIC_ERROR: | |
811 scr_LogPrint(LPRINT_LOGNORM, "Generic SSL error!"); | |
812 break; | |
813 } | |
814 | |
815 if (settings_opt_get_int("ssl_ignore_checks")) | |
816 return LM_SSL_RESPONSE_CONTINUE; | |
817 return LM_SSL_RESPONSE_STOP; | |
818 } | |
819 | |
820 static void connection_auth_cb(LmConnection *connection, gboolean success, | |
821 gpointer user_data) | |
822 { | |
823 if (success) { | |
824 LmMessage *m; | |
825 | |
826 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, | |
827 LM_MESSAGE_SUB_TYPE_AVAILABLE); | |
828 lm_connection_send(connection, m, NULL); | |
829 | |
830 lm_message_unref(m); | |
831 xmpp_setprevstatus(); | |
832 xmpp_iq_request(NULL, NS_ROSTER); | |
833 xmpp_request_storage("storage:bookmarks"); | |
834 xmpp_request_storage("storage:rosternotes"); | |
835 | |
836 AutoConnection = TRUE; | |
837 } else | |
838 scr_LogPrint(LPRINT_LOGNORM, "Authentication failed"); | |
839 } | |
840 | |
841 gboolean xmpp_reconnect() | |
842 { | |
843 if (!lm_connection_is_authenticated(lconnection)) | |
844 xmpp_connect(); | |
845 return FALSE; | |
846 } | |
847 | |
848 static void _try_to_reconnect(void) | |
849 { | |
850 if (AutoConnection) | |
851 g_timeout_add_seconds(RECONNECTION_TIMEOUT, xmpp_reconnect, NULL); | |
852 } | |
853 | |
854 static void connection_open_cb(LmConnection *connection, gboolean success, | |
855 gpointer user_data) | |
856 { | |
857 GError *error = NULL; | |
858 | |
859 if (success) { | |
860 const char *password, *resource; | |
861 char *username; | |
862 username = jid_get_username(settings_opt_get("jid")); | |
863 password = settings_opt_get("password"); | |
864 resource = strchr(lm_connection_get_jid(connection), | |
865 JID_RESOURCE_SEPARATOR); | |
866 if (resource) | |
867 resource++; | |
868 | |
869 if (!lm_connection_authenticate(lconnection, username, password, resource, | |
870 connection_auth_cb, NULL, FALSE, &error)) { | |
871 scr_LogPrint(LPRINT_LOGNORM, "Failed to authenticate: %s\n", | |
872 error->message); | |
873 g_error_free (error); | |
874 _try_to_reconnect(); | |
875 } | |
876 g_free(username); | |
877 } else { | |
878 scr_LogPrint(LPRINT_LOGNORM, "There was an error while connecting."); | |
879 _try_to_reconnect(); | |
880 } | |
881 } | |
882 | |
883 static void connection_close_cb(LmConnection *connection, | |
884 LmDisconnectReason reason, | |
885 gpointer user_data) | |
886 { | |
887 const char *str; | |
888 | |
889 switch (reason) { | |
890 case LM_DISCONNECT_REASON_OK: | |
891 str = "LM_DISCONNECT_REASON_OK"; | |
892 break; | |
893 case LM_DISCONNECT_REASON_PING_TIME_OUT: | |
894 str = "LM_DISCONNECT_REASON_PING_TIME_OUT"; | |
895 break; | |
896 case LM_DISCONNECT_REASON_HUP: | |
897 str = "LM_DISCONNECT_REASON_HUP"; | |
898 break; | |
899 case LM_DISCONNECT_REASON_ERROR: | |
900 str = "LM_DISCONNECT_REASON_ERROR"; | |
901 break; | |
902 case LM_DISCONNECT_REASON_UNKNOWN: | |
903 default: | |
904 str = "LM_DISCONNECT_REASON_UNKNOWN"; | |
905 break; | |
906 } | |
907 | |
908 if (reason != LM_DISCONNECT_REASON_OK) | |
909 _try_to_reconnect(); | |
910 | |
911 // Free bookmarks | |
912 if (bookmarks) | |
913 lm_message_node_unref(bookmarks); | |
914 bookmarks = NULL; | |
915 // Free roster | |
916 roster_free(); | |
917 if (rosternotes) | |
918 lm_message_node_unref(rosternotes); | |
919 rosternotes = NULL; | |
920 // Update display | |
921 update_roster = TRUE; | |
922 scr_UpdateBuddyWindow(); | |
923 | |
924 scr_LogPrint(LPRINT_NORMAL, "Disconnected, reason:%d->'%s'\n", reason, str); | |
925 } | |
926 | |
927 static void handle_state_events(const char *from, LmMessageNode *node) | |
928 { | |
929 #if defined JEP0022 || defined JEP0085 | |
930 LmMessageNode *state_ns = NULL; | |
931 const char *body; | |
932 char *rname, *bjid; | |
933 GSList *sl_buddy; | |
934 guint events; | |
935 struct jep0022 *jep22 = NULL; | |
936 struct jep0085 *jep85 = NULL; | |
937 enum { | |
938 JEP_none, | |
939 JEP_85, | |
940 JEP_22 | |
941 } which_jep = JEP_none; | |
942 | |
943 rname = strchr(from, JID_RESOURCE_SEPARATOR); | |
944 if (rname) | |
945 ++rname; | |
946 else | |
947 rname = (char *)from + strlen(from); | |
948 bjid = jidtodisp(from); | |
949 sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER); | |
950 g_free(bjid); | |
951 | |
952 /* XXX Actually that's wrong, since it filters out server "offline" | |
953 messages (for JEP-0022). This JEP is (almost) deprecated so | |
954 we don't really care. */ | |
955 if (!sl_buddy) { | |
956 return; | |
957 } | |
958 | |
959 /* Let's see chich JEP the contact uses. If possible, we'll use | |
960 JEP-85, if not we'll look for JEP-22 support. */ | |
961 events = buddy_resource_getevents(sl_buddy->data, rname); | |
962 | |
963 jep85 = buddy_resource_jep85(sl_buddy->data, rname); | |
964 if (jep85) { | |
965 state_ns = lm_message_node_find_xmlns(node, NS_CHATSTATES); | |
966 if (state_ns) | |
967 which_jep = JEP_85; | |
968 } | |
969 | |
970 if (which_jep != JEP_85) { /* Fall back to JEP-0022 */ | |
971 jep22 = buddy_resource_jep22(sl_buddy->data, rname); | |
972 if (jep22) { | |
973 state_ns = lm_message_node_find_xmlns(node, NS_EVENT); | |
974 if (state_ns) | |
975 which_jep = JEP_22; | |
976 } | |
977 } | |
978 | |
979 if (!which_jep) { /* Sender does not use chat states */ | |
980 return; | |
981 } | |
982 | |
983 body = lm_message_node_get_child_value(node, "body"); | |
984 | |
985 if (which_jep == JEP_85) { /* JEP-0085 */ | |
986 jep85->support = CHATSTATES_SUPPORT_OK; | |
987 | |
988 if (!strcmp(state_ns->name, "composing")) { | |
989 jep85->last_state_rcvd = ROSTER_EVENT_COMPOSING; | |
990 } else if (!strcmp(state_ns->name, "active")) { | |
991 jep85->last_state_rcvd = ROSTER_EVENT_ACTIVE; | |
992 } else if (!strcmp(state_ns->name, "paused")) { | |
993 jep85->last_state_rcvd = ROSTER_EVENT_PAUSED; | |
994 } else if (!strcmp(state_ns->name, "inactive")) { | |
995 jep85->last_state_rcvd = ROSTER_EVENT_INACTIVE; | |
996 } else if (!strcmp(state_ns->name, "gone")) { | |
997 jep85->last_state_rcvd = ROSTER_EVENT_GONE; | |
998 } | |
999 events = jep85->last_state_rcvd; | |
1000 } else { /* JEP-0022 */ | |
1001 #ifdef JEP0022 | |
1002 const char *msgid; | |
1003 jep22->support = CHATSTATES_SUPPORT_OK; | |
1004 jep22->last_state_rcvd = ROSTER_EVENT_NONE; | |
1005 | |
1006 msgid = lm_message_node_get_attribute(node, "id"); | |
1007 | |
1008 if (lm_message_node_get_child(state_ns, "composing")) { | |
1009 // Clear composing if the message contains a body | |
1010 if (body) | |
1011 events &= ~ROSTER_EVENT_COMPOSING; | |
1012 else | |
1013 events |= ROSTER_EVENT_COMPOSING; | |
1014 jep22->last_state_rcvd |= ROSTER_EVENT_COMPOSING; | |
1015 | |
1016 } else { | |
1017 events &= ~ROSTER_EVENT_COMPOSING; | |
1018 } | |
1019 | |
1020 // Cache the message id | |
1021 g_free(jep22->last_msgid_rcvd); | |
1022 if (msgid) | |
1023 jep22->last_msgid_rcvd = g_strdup(msgid); | |
1024 else | |
1025 jep22->last_msgid_rcvd = NULL; | |
1026 | |
1027 if (lm_message_node_get_child(state_ns, "delivered")) { | |
1028 jep22->last_state_rcvd |= ROSTER_EVENT_DELIVERED; | |
1029 | |
1030 // Do we have to send back an ACK? | |
1031 if (body) | |
1032 xmpp_send_jep22_event(from, ROSTER_EVENT_DELIVERED); | |
1033 } | |
1034 #endif | |
1035 } | |
1036 | |
1037 buddy_resource_setevents(sl_buddy->data, rname, events); | |
1038 | |
1039 update_roster = TRUE; | |
1040 #endif | |
1041 } | |
1042 | |
1043 static void gotmessage(LmMessageSubType type, const char *from, | |
1044 const char *body, const char *enc, const char *subject, | |
1045 time_t timestamp, LmMessageNode *node_signed) | |
1046 { | |
1047 char *bjid; | |
1048 const char *rname, *s; | |
1049 char *decrypted_pgp = NULL; | |
1050 char *decrypted_otr = NULL; | |
1051 int otr_msg = 0, free_msg = 0; | |
1052 | |
1053 bjid = jidtodisp(from); | |
1054 | |
1055 rname = strchr(from, JID_RESOURCE_SEPARATOR); | |
1056 if (rname) rname++; | |
1057 | |
1058 #ifdef HAVE_GPGME | |
1059 if (enc && gpg_enabled()) { | |
1060 decrypted_pgp = gpg_decrypt(enc); | |
1061 if (decrypted_pgp) { | |
1062 body = decrypted_pgp; | |
1063 } | |
1064 } | |
1065 // Check signature of an unencrypted message | |
1066 if (node_signed && gpg_enabled()) | |
1067 check_signature(bjid, rname, node_signed, decrypted_pgp); | |
1068 #endif | |
1069 | |
1070 #ifdef HAVE_LIBOTR | |
1071 if (otr_enabled()) { | |
1072 decrypted_otr = (char*)body; | |
1073 otr_msg = otr_receive(&decrypted_otr, bjid, &free_msg); | |
1074 if (!decrypted_otr) { | |
1075 goto gotmessage_return; | |
1076 } | |
1077 body = decrypted_otr; | |
1078 } | |
1079 #endif | |
1080 | |
1081 // Check for unexpected groupchat messages | |
1082 // If we receive a groupchat message from a room we're not a member of, | |
1083 // this is probably a server issue and the best we can do is to send | |
1084 // a type unavailable. | |
1085 if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT && !roster_getnickname(bjid)) { | |
1086 // It shouldn't happen, probably a server issue | |
1087 GSList *room_elt; | |
1088 char *mbuf; | |
1089 | |
1090 mbuf = g_strdup_printf("Unexpected groupchat packet!"); | |
1091 scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); | |
1092 scr_WriteIncomingMessage(bjid, mbuf, 0, HBB_PREFIX_INFO, 0); | |
1093 g_free(mbuf); | |
1094 | |
1095 // Send back an unavailable packet | |
1096 xmpp_setstatus(offline, bjid, "", TRUE); | |
1097 | |
1098 // MUC | |
1099 // Make sure this is a room (it can be a conversion user->room) | |
1100 room_elt = roster_find(bjid, jidsearch, 0); | |
1101 if (!room_elt) { | |
1102 roster_add_user(bjid, NULL, NULL, ROSTER_TYPE_ROOM, sub_none, -1); | |
1103 } else { | |
1104 buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); | |
1105 } | |
1106 | |
1107 buddylist_build(); | |
1108 scr_DrawRoster(); | |
1109 goto gotmessage_return; | |
1110 } | |
1111 | |
1112 // We don't call the message_in hook if 'block_unsubscribed' is true and | |
1113 // this is a regular message from an unsubscribed user. | |
1114 // System messages (from our server) are allowed. | |
1115 if ((!settings_opt_get_int("block_unsubscribed") || | |
1116 (roster_getsubscription(bjid) & sub_from) || | |
1117 (type == LM_MESSAGE_SUB_TYPE_CHAT)) || | |
1118 ((s = settings_opt_get("server")) != NULL && !strcasecmp(bjid, s))) { | |
1119 gchar *fullbody = NULL; | |
1120 guint encrypted; | |
1121 | |
1122 if (decrypted_pgp) | |
1123 encrypted = ENCRYPTED_PGP; | |
1124 else if (otr_msg) | |
1125 encrypted = ENCRYPTED_OTR; | |
1126 else | |
1127 encrypted = 0; | |
1128 | |
1129 if (subject) { | |
1130 if (body) | |
1131 fullbody = g_strdup_printf("[%s]\n%s", subject, body); | |
1132 else | |
1133 fullbody = g_strdup_printf("[%s]\n", subject); | |
1134 body = fullbody; | |
1135 } | |
1136 hk_message_in(bjid, rname, timestamp, body, type, encrypted); | |
1137 g_free(fullbody); | |
1138 } else { | |
1139 scr_LogPrint(LPRINT_LOGNORM, "Blocked a message from <%s>", bjid); | |
1140 } | |
1141 | |
1142 gotmessage_return: | |
1143 // Clean up and exit | |
1144 g_free(bjid); | |
1145 g_free(decrypted_pgp); | |
1146 if (free_msg) | |
1147 g_free(decrypted_otr); | |
1148 } | |
1149 | |
1150 | |
1151 static LmHandlerResult handle_messages(LmMessageHandler *handler, | |
1152 LmConnection *connection, | |
1153 LmMessage *m, gpointer user_data) | |
1154 { | |
1155 const char *p, *from=lm_message_get_from(m); | |
1156 char *r, *s; | |
1157 LmMessageNode *x; | |
1158 const char *body = NULL; | |
1159 const char *enc = NULL; | |
1160 const char *subject = NULL; | |
1161 time_t timestamp = 0L; | |
1162 LmMessageSubType mstype; | |
1163 | |
1164 mstype = lm_message_get_sub_type(m); | |
1165 | |
1166 body = lm_message_node_get_child_value(m->node, "body"); | |
1167 | |
1168 x = lm_message_node_find_xmlns(m->node, NS_ENCRYPTED); | |
1169 if (x && (p = lm_message_node_get_value(x)) != NULL) | |
1170 enc = p; | |
1171 | |
1172 p = lm_message_node_get_child_value(m->node, "subject"); | |
1173 if (p != NULL) { | |
1174 if (mstype != LM_MESSAGE_SUB_TYPE_GROUPCHAT) { | |
1175 // Chat message | |
1176 subject = p; | |
1177 } else { // Room topic | |
1178 GSList *roombuddy; | |
1179 gchar *mbuf; | |
1180 const gchar *subj = p; | |
1181 // Get the room (s) and the nickname (r) | |
1182 s = g_strdup(lm_message_get_from(m)); | |
1183 r = strchr(s, JID_RESOURCE_SEPARATOR); | |
1184 if (r) *r++ = 0; | |
1185 else r = s; | |
1186 // Set the new topic | |
1187 roombuddy = roster_find(s, jidsearch, 0); | |
1188 if (roombuddy) | |
1189 buddy_settopic(roombuddy->data, subj); | |
1190 // Display inside the room window | |
1191 if (r == s) { | |
1192 // No specific resource (this is certainly history) | |
1193 mbuf = g_strdup_printf("The topic has been set to: %s", subj); | |
1194 } else { | |
1195 mbuf = g_strdup_printf("%s has set the topic to: %s", r, subj); | |
1196 } | |
1197 scr_WriteIncomingMessage(s, mbuf, 0, | |
1198 HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); | |
1199 if (settings_opt_get_int("log_muc_conf")) | |
1200 hlog_write_message(s, 0, -1, mbuf); | |
1201 g_free(s); | |
1202 g_free(mbuf); | |
1203 // The topic is displayed in the chat status line, so refresh now. | |
1204 scr_UpdateChatStatus(TRUE); | |
1205 } | |
1206 } | |
1207 | |
1208 // Timestamp? | |
1209 timestamp = lm_message_node_get_timestamp(m->node); | |
1210 | |
1211 if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { | |
1212 x = lm_message_node_get_child(m->node, "error"); | |
1213 display_server_error(x); | |
1214 #if defined JEP0022 || defined JEP0085 | |
1215 // If the JEP85/22 support is probed, set it back to unknown so that | |
1216 // we probe it again. | |
1217 chatstates_reset_probed(from); | |
1218 #endif | |
1219 } else { | |
1220 handle_state_events(from, m->node); | |
1221 } | |
1222 if (from && (body || subject)) | |
1223 gotmessage(mstype, from, body, enc, subject, timestamp, | |
1224 lm_message_node_find_xmlns(m->node, NS_SIGNED)); | |
1225 //report received message if message receipt was requested | |
1226 if (lm_message_node_get_child(m->node, "request")) { | |
1227 LmMessage *rcvd = lm_message_new(from, LM_MESSAGE_TYPE_MESSAGE); | |
1228 lm_message_node_set_attribute(rcvd->node, "id", lm_message_get_id(m)); | |
1229 lm_message_node_set_attribute | |
1230 (lm_message_node_add_child(rcvd->node, "received", NULL), | |
1231 "xmlns", NS_RECEIPTS); | |
1232 lm_connection_send(connection, rcvd, NULL); | |
1233 lm_message_unref(rcvd); | |
1234 } | |
1235 | |
1236 if (from) { | |
1237 x = lm_message_node_find_xmlns(m->node, | |
1238 "http://jabber.org/protocol/muc#user"); | |
1239 if (x && !strcmp(x->name, "x")) | |
1240 got_muc_message(from, x); | |
1241 } | |
1242 | |
1243 return LM_HANDLER_RESULT_REMOVE_MESSAGE; | |
1244 } | |
1245 | |
1246 static LmHandlerResult cb_caps(LmMessageHandler *h, LmConnection *c, | |
1247 LmMessage *m, gpointer user_data) | |
1248 { | |
1249 char *ver = user_data; | |
1250 LmMessageSubType mstype = lm_message_get_sub_type(m); | |
1251 | |
1252 caps_add(ver); | |
1253 if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { | |
1254 display_server_error(lm_message_node_get_child(m->node, "error")); | |
1255 } else if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) { | |
1256 LmMessageNode *info; | |
1257 LmMessageNode *query = lm_message_node_get_child(m->node, "query"); | |
1258 | |
1259 info = lm_message_node_get_child(query, "identity"); | |
1260 if (info) | |
1261 caps_set_identity(ver, lm_message_node_get_attribute(info, "category"), | |
1262 lm_message_node_get_attribute(info, "name"), | |
1263 lm_message_node_get_attribute(info, "type")); | |
1264 info = lm_message_node_get_child(query, "feature"); | |
1265 while (info) { | |
1266 if (!g_strcmp0(info->name, "feature")) | |
1267 caps_add_feature(ver, lm_message_node_get_attribute(info, "var")); | |
1268 info = info->next; | |
1269 } | |
1270 } | |
1271 g_free(ver); | |
1272 return LM_HANDLER_RESULT_REMOVE_MESSAGE; | |
1273 } | |
1274 | |
1275 static LmHandlerResult handle_presence(LmMessageHandler *handler, | |
1276 LmConnection *connection, | |
1277 LmMessage *m, gpointer user_data) | |
1278 { | |
1279 char *r; | |
1280 const char *from, *rname, *p=NULL, *ustmsg=NULL; | |
1281 enum imstatus ust; | |
1282 char bpprio; | |
1283 time_t timestamp = 0L; | |
1284 LmMessageNode *muc_packet, *caps; | |
1285 LmMessageSubType mstype; | |
1286 | |
1287 // Check for MUC presence packet | |
1288 muc_packet = lm_message_node_find_xmlns | |
1289 (m->node, "http://jabber.org/protocol/muc#user"); | |
1290 | |
1291 from = lm_message_get_from(m); | |
1292 | |
1293 rname = strchr(from, JID_RESOURCE_SEPARATOR); | |
1294 if (rname) rname++; | |
1295 | |
1296 if (settings_opt_get_int("ignore_self_presence")) { | |
1297 const char *self_fjid = lm_connection_get_jid(connection); | |
1298 if (self_fjid && !strcasecmp(self_fjid, from)) { | |
1299 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Ignoring self presence | |
1300 } | |
1301 } | |
1302 | |
1303 r = jidtodisp(from); | |
1304 mstype = lm_message_get_sub_type(m); | |
1305 | |
1306 if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { | |
1307 LmMessageNode *x; | |
1308 scr_LogPrint(LPRINT_LOGNORM, "Error presence packet from <%s>", r); | |
1309 x = lm_message_node_find_child(m->node, "error"); | |
1310 display_server_error(x); | |
1311 // Let's check it isn't a nickname conflict. | |
1312 // XXX Note: We should handle the <conflict/> string condition. | |
1313 if ((p = lm_message_node_get_attribute(x, "code")) != NULL) { | |
1314 if (atoi(p) == 409) { | |
1315 // 409 = conflict (nickname is in use or registered by another user) | |
1316 // If we are not inside this room, we should reset the nickname | |
1317 GSList *room_elt = roster_find(r, jidsearch, 0); | |
1318 if (room_elt && !buddy_getinsideroom(room_elt->data)) | |
1319 buddy_setnickname(room_elt->data, NULL); | |
1320 } | |
1321 } | |
1322 | |
1323 g_free(r); | |
1324 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; | |
1325 } | |
1326 | |
1327 p = lm_message_node_get_child_value(m->node, "priority"); | |
1328 if (p && *p) bpprio = (gchar)atoi(p); | |
1329 else bpprio = 0; | |
1330 | |
1331 ust = available; | |
1332 | |
1333 p = lm_message_node_get_child_value(m->node, "show"); | |
1334 if (p) { | |
1335 if (!strcmp(p, "away")) ust = away; | |
1336 else if (!strcmp(p, "dnd")) ust = dontdisturb; | |
1337 else if (!strcmp(p, "xa")) ust = notavail; | |
1338 else if (!strcmp(p, "chat")) ust = freeforchat; | |
1339 } | |
1340 | |
1341 if (mstype == LM_MESSAGE_SUB_TYPE_UNAVAILABLE) | |
1342 ust = offline; | |
1343 | |
1344 ustmsg = lm_message_node_get_child_value(m->node, "status"); | |
1345 | |
1346 // Timestamp? | |
1347 timestamp = lm_message_node_get_timestamp(m->node); | |
1348 | |
1349 if (muc_packet) { | |
1350 // This is a MUC presence message | |
1351 handle_muc_presence(from, muc_packet, r, rname, | |
1352 ust, ustmsg, timestamp, bpprio); | |
1353 } else { | |
1354 // Not a MUC message, so this is a regular buddy... | |
1355 // Call hk_statuschange() if status has changed or if the | |
1356 // status message is different | |
1357 const char *msg; | |
1358 msg = roster_getstatusmsg(r, rname); | |
1359 if ((ust != roster_getstatus(r, rname)) || | |
1360 (!ustmsg && msg && msg[0]) || (ustmsg && (!msg || strcmp(ustmsg, msg)))) | |
1361 hk_statuschange(r, rname, bpprio, timestamp, ust, ustmsg); | |
1362 // Presence signature processing | |
1363 if (!ustmsg) | |
1364 ustmsg = ""; // Some clients omit the <status/> element :-( | |
1365 check_signature(r, rname, lm_message_node_find_xmlns(m->node, NS_SIGNED), | |
1366 ustmsg); | |
1367 } | |
1368 | |
1369 // XEP-0115 Entity Capabilities | |
1370 caps = lm_message_node_find_xmlns(m->node, NS_CAPS); | |
1371 if (caps && ust != offline) { | |
1372 const char *ver = lm_message_node_get_attribute(caps, "ver"); | |
1373 GSList *sl_buddy = NULL; | |
1374 if (rname) | |
1375 sl_buddy = roster_find(r, jidsearch, ROSTER_TYPE_USER); | |
1376 // Only cache the caps if the user is on the roster | |
1377 if (sl_buddy && buddy_getonserverflag(sl_buddy->data)) { | |
1378 buddy_resource_setcaps(sl_buddy->data, rname, ver); | |
1379 | |
1380 if (!caps_has_hash(ver)) { | |
1381 char *node; | |
1382 LmMessageHandler *handler; | |
1383 LmMessage *iq = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ, | |
1384 LM_MESSAGE_SUB_TYPE_GET); | |
1385 node = g_strdup_printf("%s#%s", | |
1386 lm_message_node_get_attribute(caps, "node"), | |
1387 ver); | |
1388 lm_message_node_set_attributes | |
1389 (lm_message_node_add_child(iq->node, "query", NULL), | |
1390 "xmlns", NS_DISCO_INFO, | |
1391 "node", node, | |
1392 NULL); | |
1393 g_free(node); | |
1394 handler = lm_message_handler_new(cb_caps, g_strdup(ver), NULL); | |
1395 lm_connection_send_with_reply(connection, iq, handler, NULL); | |
1396 lm_message_unref(iq); | |
1397 lm_message_handler_unref(handler); | |
1398 } | |
1399 } | |
1400 } | |
1401 | |
1402 g_free(r); | |
1403 return LM_HANDLER_RESULT_REMOVE_MESSAGE; | |
1404 } | |
1405 | |
1406 | |
1407 static LmHandlerResult handle_iq(LmMessageHandler *handler, | |
1408 LmConnection *connection, | |
1409 LmMessage *m, gpointer user_data) | |
1410 { | |
1411 int i; | |
1412 guint dbgflg; | |
1413 const char *xmlns = NULL; | |
1414 LmMessageNode *x; | |
1415 LmMessageSubType mstype = lm_message_get_sub_type(m); | |
1416 | |
1417 if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { | |
1418 display_server_error(lm_message_node_get_child(m->node, "error")); | |
1419 return LM_HANDLER_RESULT_REMOVE_MESSAGE; | |
1420 } | |
1421 | |
1422 for (x = m->node->children; x; x=x->next) { | |
1423 xmlns = lm_message_node_get_attribute(x, "xmlns"); | |
1424 if (xmlns) | |
1425 for (i=0; iq_handlers[i].xmlns; ++i) | |
1426 if (!strcmp(iq_handlers[i].xmlns, xmlns)) | |
1427 return iq_handlers[i].handler(NULL, connection, m, user_data); | |
1428 xmlns = NULL; | |
1429 } | |
1430 | |
1431 if ((mstype == LM_MESSAGE_SUB_TYPE_SET) || | |
1432 (mstype == LM_MESSAGE_SUB_TYPE_GET)) | |
1433 send_iq_error(connection, m, XMPP_ERROR_NOT_IMPLEMENTED); | |
1434 | |
1435 if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) | |
1436 dbgflg = LPRINT_DEBUG; | |
1437 else | |
1438 dbgflg = LPRINT_NORMAL|LPRINT_DEBUG; | |
1439 | |
1440 scr_LogPrint(dbgflg, "Unhandled IQ: %s", lm_message_node_to_string(m->node)); | |
1441 return LM_HANDLER_RESULT_REMOVE_MESSAGE; | |
1442 } | |
1443 | |
1444 static LmHandlerResult handle_s10n(LmMessageHandler *handler, | |
1445 LmConnection *connection, | |
1446 LmMessage *m, gpointer user_data) | |
1447 { | |
1448 char *r; | |
1449 char *buf; | |
1450 int newbuddy; | |
1451 const char *from = lm_message_get_from(m); | |
1452 LmMessageSubType mstype; | |
1453 | |
1454 r = jidtodisp(from); | |
1455 | |
1456 newbuddy = !roster_find(r, jidsearch, 0); | |
1457 mstype = lm_message_get_sub_type(m); | |
1458 | |
1459 if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBE) { | |
1460 /* The sender wishes to subscribe to our presence */ | |
1461 const char *msg; | |
1462 eviqs *evn; | |
1463 | |
1464 msg = lm_message_node_get_child_value(m->node, "status"); | |
1465 | |
1466 buf = g_strdup_printf("<%s> wants to subscribe to your presence updates", | |
1467 from); | |
1468 scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); | |
1469 scr_LogPrint(LPRINT_LOGNORM, "%s", buf); | |
1470 g_free(buf); | |
1471 | |
1472 if (msg) { | |
1473 buf = g_strdup_printf("<%s> said: %s", from, msg); | |
1474 scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); | |
1475 replace_nl_with_dots(buf); | |
1476 scr_LogPrint(LPRINT_LOGNORM, "%s", buf); | |
1477 g_free(buf); | |
1478 } | |
1479 | |
1480 // Create a new event item | |
1481 evn = evs_new(EVS_TYPE_SUBSCRIPTION, EVS_MAX_TIMEOUT); | |
1482 if (evn) { | |
1483 evn->callback = &evscallback_subscription; | |
1484 evn->data = g_strdup(r); | |
1485 evn->desc = g_strdup_printf("<%s> wants to subscribe to your " | |
1486 "presence updates", r); | |
1487 buf = g_strdup_printf("Please use /event %s accept|reject", evn->id); | |
1488 } else { | |
1489 buf = g_strdup_printf("Unable to create a new event!"); | |
1490 } | |
1491 scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); | |
1492 scr_LogPrint(LPRINT_LOGNORM, "%s", buf); | |
1493 g_free(buf); | |
1494 } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE) { | |
1495 /* The sender is unsubscribing from our presence */ | |
1496 xmpp_send_s10n(from, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); | |
1497 buf = g_strdup_printf("<%s> is unsubscribing from your " | |
1498 "presence updates", from); | |
1499 scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); | |
1500 scr_LogPrint(LPRINT_LOGNORM, "%s", buf); | |
1501 g_free(buf); | |
1502 } else if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBED) { | |
1503 /* The sender has allowed us to receive their presence */ | |
1504 buf = g_strdup_printf("<%s> has allowed you to receive their " | |
1505 "presence updates", from); | |
1506 scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); | |
1507 scr_LogPrint(LPRINT_LOGNORM, "%s", buf); | |
1508 g_free(buf); | |
1509 } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED) { | |
1510 /* The subscription request has been denied or a previously-granted | |
1511 subscription has been cancelled */ | |
1512 roster_unsubscribed(from); | |
1513 update_roster = TRUE; | |
1514 buf = g_strdup_printf("<%s> has cancelled your subscription to " | |
1515 "their presence updates", from); | |
1516 scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); | |
1517 scr_LogPrint(LPRINT_LOGNORM, "%s", buf); | |
1518 g_free(buf); | |
1519 } else { | |
1520 g_free(r); | |
1521 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; | |
1522 } | |
1523 | |
1524 if (newbuddy) | |
1525 update_roster = TRUE; | |
1526 g_free(r); | |
1527 return LM_HANDLER_RESULT_REMOVE_MESSAGE; | |
1528 } | |
1529 | |
1530 //TODO: Use the enum of loudmouth, when it's included in the header... | |
1531 typedef enum { | |
1532 LM_LOG_LEVEL_VERBOSE = 1 << (G_LOG_LEVEL_USER_SHIFT), | |
1533 LM_LOG_LEVEL_NET = 1 << (G_LOG_LEVEL_USER_SHIFT + 1), | |
1534 LM_LOG_LEVEL_PARSER = 1 << (G_LOG_LEVEL_USER_SHIFT + 2), | |
1535 LM_LOG_LEVEL_SSL = 1 << (G_LOG_LEVEL_USER_SHIFT + 3), | |
1536 LM_LOG_LEVEL_SASL = 1 << (G_LOG_LEVEL_USER_SHIFT + 4), | |
1537 LM_LOG_LEVEL_ALL = (LM_LOG_LEVEL_NET | | |
1538 LM_LOG_LEVEL_VERBOSE | | |
1539 LM_LOG_LEVEL_PARSER | | |
1540 LM_LOG_LEVEL_SSL | | |
1541 LM_LOG_LEVEL_SASL) | |
1542 } LmLogLevelFlags; | |
1543 | |
1544 static void lm_debug_handler (const gchar *log_domain, | |
1545 GLogLevelFlags log_level, | |
1546 const gchar *message, | |
1547 gpointer user_data) | |
1548 { | |
1549 if (message && *message) { | |
1550 char *msg; | |
1551 int mcabber_loglevel = settings_opt_get_int("tracelog_level"); | |
1552 | |
1553 if (mcabber_loglevel < 2) | |
1554 return; | |
1555 | |
1556 if (message[0] == '\n') | |
1557 msg = g_strdup(&message[1]); | |
1558 else | |
1559 msg = g_strdup(message); | |
1560 | |
1561 if (msg[strlen(msg)-1] == '\n') | |
1562 msg[strlen(msg)-1] = '\0'; | |
1563 | |
1564 if (log_level & LM_LOG_LEVEL_VERBOSE) { | |
1565 scr_LogPrint(LPRINT_DEBUG, "LM-VERBOSE: %s", msg); | |
1566 } | |
1567 if (log_level & LM_LOG_LEVEL_NET) { | |
1568 if (mcabber_loglevel > 2) | |
1569 scr_LogPrint(LPRINT_DEBUG, "LM-NET: %s", msg); | |
1570 } else if (log_level & LM_LOG_LEVEL_PARSER) { | |
1571 if (mcabber_loglevel > 3) | |
1572 scr_LogPrint(LPRINT_DEBUG, "LM-PARSER: %s", msg); | |
1573 } else if (log_level & LM_LOG_LEVEL_SASL) { | |
1574 scr_LogPrint(LPRINT_DEBUG, "LM-SASL: %s", msg); | |
1575 } else if (log_level & LM_LOG_LEVEL_SSL) { | |
1576 scr_LogPrint(LPRINT_DEBUG, "LM-SSL: %s", msg); | |
1577 } | |
1578 g_free(msg); | |
1579 } | |
1580 } | |
1581 | |
1582 | |
1583 void xmpp_connect(void) | |
1584 { | |
1585 const char *userjid, *password, *resource, *servername, *ssl_fpr; | |
1586 char *dynresource = NULL; | |
1587 char fpr[16]; | |
1588 const char *proxy_host; | |
1589 const char *resource_prefix = PACKAGE_NAME; | |
1590 char *fjid; | |
1591 int ssl, tls; | |
1592 LmSSL *lssl; | |
1593 unsigned int port; | |
1594 unsigned int ping; | |
1595 LmMessageHandler *handler; | |
1596 GError *error = NULL; | |
1597 | |
1598 if (lconnection && lm_connection_is_open(lconnection)) | |
1599 xmpp_disconnect(); | |
1600 | |
1601 servername = settings_opt_get("server"); | |
1602 userjid = settings_opt_get("jid"); | |
1603 password = settings_opt_get("password"); | |
1604 resource = settings_opt_get("resource"); | |
1605 proxy_host = settings_opt_get("proxy_host"); | |
1606 ssl_fpr = settings_opt_get("ssl_fingerprint"); | |
1607 | |
1608 if (!userjid) { | |
1609 scr_LogPrint(LPRINT_LOGNORM, "Your JID has not been specified!"); | |
1610 return; | |
1611 } | |
1612 if (!password) { | |
1613 scr_LogPrint(LPRINT_LOGNORM, "Your password has not been specified!"); | |
1614 return; | |
1615 } | |
1616 | |
1617 lconnection = lm_connection_new_with_context(NULL, main_context); | |
1618 | |
1619 g_log_set_handler("LM", LM_LOG_LEVEL_ALL, lm_debug_handler, NULL); | |
1620 | |
1621 ping = 40; | |
1622 if (settings_opt_get("pinginterval")) | |
1623 ping = (unsigned int) settings_opt_get_int("pinginterval"); | |
1624 lm_connection_set_keep_alive_rate(lconnection, ping); | |
1625 scr_LogPrint(LPRINT_DEBUG, "Ping interval established: %d secs", ping); | |
1626 | |
1627 lm_connection_set_disconnect_function(lconnection, connection_close_cb, | |
1628 NULL, NULL); | |
1629 | |
1630 handler = lm_message_handler_new(handle_messages, NULL, NULL); | |
1631 lm_connection_register_message_handler(lconnection, handler, | |
1632 LM_MESSAGE_TYPE_MESSAGE, | |
1633 LM_HANDLER_PRIORITY_NORMAL); | |
1634 lm_message_handler_unref(handler); | |
1635 | |
1636 handler = lm_message_handler_new(handle_iq, NULL, NULL); | |
1637 lm_connection_register_message_handler(lconnection, handler, | |
1638 LM_MESSAGE_TYPE_IQ, | |
1639 LM_HANDLER_PRIORITY_NORMAL); | |
1640 lm_message_handler_unref(handler); | |
1641 | |
1642 handler = lm_message_handler_new(handle_presence, NULL, NULL); | |
1643 lm_connection_register_message_handler(lconnection, handler, | |
1644 LM_MESSAGE_TYPE_PRESENCE, | |
1645 LM_HANDLER_PRIORITY_LAST); | |
1646 lm_message_handler_unref(handler); | |
1647 | |
1648 handler = lm_message_handler_new(handle_s10n, NULL, NULL); | |
1649 lm_connection_register_message_handler(lconnection, handler, | |
1650 LM_MESSAGE_TYPE_PRESENCE, | |
1651 LM_HANDLER_PRIORITY_NORMAL); | |
1652 lm_message_handler_unref(handler); | |
1653 | |
1654 /* Connect to server */ | |
1655 scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Connecting to server: %s", | |
1656 servername ? servername : "..."); | |
1657 if (!resource) | |
1658 resource = resource_prefix; | |
1659 | |
1660 if (!settings_opt_get("disable_random_resource")) { | |
1661 #if HAVE_ARC4RANDOM | |
1662 dynresource = g_strdup_printf("%s.%08x", resource, arc4random()); | |
1663 #else | |
1664 unsigned int tab[2]; | |
1665 srand(time(NULL)); | |
1666 tab[0] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0))); | |
1667 tab[1] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0))); | |
1668 dynresource = g_strdup_printf("%s.%04x%04x", resource, tab[0], tab[1]); | |
1669 #endif | |
1670 resource = dynresource; | |
1671 } | |
1672 | |
1673 port = (unsigned int) settings_opt_get_int("port"); | |
1674 | |
1675 if (port) | |
1676 scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using port %d", port); | |
1677 scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " resource %s", resource); | |
1678 | |
1679 if (proxy_host) { | |
1680 int proxy_port = settings_opt_get_int("proxy_port"); | |
1681 if (proxy_port <= 0 || proxy_port > 65535) { | |
1682 scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Invalid proxy port: %d", | |
1683 proxy_port); | |
1684 } else { | |
1685 const char *proxy_user, *proxy_pass; | |
1686 LmProxy *lproxy; | |
1687 proxy_user = settings_opt_get("proxy_user"); | |
1688 proxy_pass = settings_opt_get("proxy_pass"); | |
1689 // Proxy initialization | |
1690 lproxy = lm_proxy_new_with_server(LM_PROXY_TYPE_HTTP, | |
1691 proxy_host, proxy_port); | |
1692 lm_proxy_set_username(lproxy, proxy_user); | |
1693 lm_proxy_set_password(lproxy, proxy_pass); | |
1694 lm_connection_set_proxy(lconnection, lproxy); | |
1695 lm_proxy_unref(lproxy); | |
1696 scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using proxy %s:%d", | |
1697 proxy_host, proxy_port); | |
1698 } | |
1699 } | |
1700 | |
1701 fjid = compose_jid(userjid, servername, resource); | |
1702 lm_connection_set_jid(lconnection, fjid); | |
1703 if (servername) | |
1704 lm_connection_set_server(lconnection, servername); | |
1705 #if defined(HAVE_LIBOTR) | |
1706 otr_init(fjid); | |
1707 #endif | |
1708 g_free(fjid); | |
1709 g_free(dynresource); | |
1710 | |
1711 ssl = settings_opt_get_int("ssl"); | |
1712 tls = settings_opt_get_int("tls"); | |
1713 | |
1714 if (!lm_ssl_is_supported()) { | |
1715 if (ssl || tls) { | |
1716 scr_LogPrint(LPRINT_LOGNORM, "** Error: SSL is NOT available, " | |
1717 "please recompile loudmouth with SSL enabled."); | |
1718 return; | |
1719 } | |
1720 } | |
1721 | |
1722 if (ssl && tls) { | |
1723 scr_LogPrint(LPRINT_LOGNORM, "You can only set ssl or tls, not both."); | |
1724 return; | |
1725 } | |
1726 | |
1727 if (!port) | |
1728 port = (ssl ? LM_CONNECTION_DEFAULT_PORT_SSL : LM_CONNECTION_DEFAULT_PORT); | |
1729 lm_connection_set_port(lconnection, port); | |
1730 | |
1731 if (ssl_fpr && (!hex_to_fingerprint(ssl_fpr, fpr))) { | |
1732 scr_LogPrint(LPRINT_LOGNORM, "** Plese set the fingerprint in the format " | |
1733 "97:5C:00:3F:1D:77:45:25:E2:C5:70:EC:83:C8:87:EE"); | |
1734 return; | |
1735 } | |
1736 | |
1737 lssl = lm_ssl_new((ssl_fpr ? fpr : NULL), ssl_cb, NULL, NULL); | |
1738 if (lssl) { | |
1739 lm_ssl_use_starttls(lssl, !ssl, tls); | |
1740 lm_connection_set_ssl(lconnection, lssl); | |
1741 lm_ssl_unref(lssl); | |
1742 } else if (ssl || tls) { | |
1743 scr_LogPrint(LPRINT_LOGNORM, "** Error: Couldn't create SSL struct."); | |
1744 return; | |
1745 } | |
1746 | |
1747 if (!lm_connection_open(lconnection, connection_open_cb, | |
1748 NULL, FALSE, &error)) { | |
1749 _try_to_reconnect(); | |
1750 scr_LogPrint(LPRINT_LOGNORM, "Failed to open: %s\n", error->message); | |
1751 g_error_free (error); | |
1752 } | |
1753 } | |
1754 | |
1755 // insert_entity_capabilities(presence_stanza) | |
1756 // Entity Capabilities (XEP-0115) | |
1757 static void insert_entity_capabilities(LmMessageNode *x, enum imstatus status) | |
1758 { | |
1759 LmMessageNode *y; | |
1760 const char *ver = entity_version(status); | |
1761 | |
1762 y = lm_message_node_add_child(x, "c", NULL); | |
1763 lm_message_node_set_attribute(y, "xmlns", NS_CAPS); | |
1764 lm_message_node_set_attribute(y, "hash", "sha-1"); | |
1765 lm_message_node_set_attribute(y, "node", MCABBER_CAPS_NODE); | |
1766 lm_message_node_set_attribute(y, "ver", ver); | |
1767 } | |
1768 | |
1769 void xmpp_disconnect(void) | |
1770 { | |
1771 if (!lconnection || !lm_connection_is_authenticated(lconnection)) | |
1772 return; | |
1773 | |
1774 // Launch pre-disconnect internal hook | |
1775 hook_execute_internal("hook-pre-disconnect"); | |
1776 // Announce it to everyone else | |
1777 xmpp_setstatus(offline, NULL, "", FALSE); | |
1778 lm_connection_close(lconnection, NULL); | |
1779 } | |
1780 | |
1781 void xmpp_setstatus(enum imstatus st, const char *recipient, const char *msg, | |
1782 int do_not_sign) | |
1783 { | |
1784 LmMessage *m; | |
1785 | |
1786 if (msg) { | |
1787 // The status message has been specified. We'll use it, unless it is | |
1788 // "-" which is a special case (option meaning "no status message"). | |
1789 if (!strcmp(msg, "-")) | |
1790 msg = ""; | |
1791 } else { | |
1792 // No status message specified; we'll use: | |
1793 // a) the default status message (if provided by the user); | |
1794 // b) the current status message; | |
1795 // c) no status message (i.e. an empty one). | |
1796 msg = settings_get_status_msg(st); | |
1797 if (!msg) { | |
1798 if (mystatusmsg) | |
1799 msg = mystatusmsg; | |
1800 else | |
1801 msg = ""; | |
1802 } | |
1803 } | |
1804 | |
1805 // Only send the packet if we're online. | |
1806 // (But we want to update internal status even when disconnected, | |
1807 // in order to avoid some problems during network failures) | |
1808 if (lm_connection_is_authenticated(lconnection)) { | |
1809 const char *s_msg = (st != invisible ? msg : NULL); | |
1810 m = lm_message_new_presence(st, recipient, s_msg); | |
1811 insert_entity_capabilities(m->node, st); // Entity Capabilities (XEP-0115) | |
1812 #ifdef HAVE_GPGME | |
1813 if (!do_not_sign && gpg_enabled()) { | |
1814 char *signature; | |
1815 signature = gpg_sign(s_msg ? s_msg : ""); | |
1816 if (signature) { | |
1817 LmMessageNode *y; | |
1818 y = lm_message_node_add_child(m->node, "x", signature); | |
1819 lm_message_node_set_attribute(y, "xmlns", NS_SIGNED); | |
1820 g_free(signature); | |
1821 } | |
1822 } | |
1823 #endif | |
1824 lm_connection_send(lconnection, m, NULL); | |
1825 lm_message_unref(m); | |
1826 } | |
1827 | |
1828 // If we didn't change our _global_ status, we are done | |
1829 if (recipient) return; | |
1830 | |
1831 if (lm_connection_is_authenticated(lconnection)) { | |
1832 // Send presence to chatrooms | |
1833 if (st != invisible) { | |
1834 struct T_presence room_presence; | |
1835 room_presence.st = st; | |
1836 room_presence.msg = msg; | |
1837 foreach_buddy(ROSTER_TYPE_ROOM, &roompresence, &room_presence); | |
1838 } | |
1839 | |
1840 // We'll have to update the roster if we switch to/from offline because | |
1841 // we don't know the presences of buddies when offline... | |
1842 if (mystatus == offline || st == offline) | |
1843 update_roster = TRUE; | |
1844 | |
1845 hk_mystatuschange(0, mystatus, st, (st != invisible ? msg : "")); | |
1846 mystatus = st; | |
1847 } | |
1848 | |
1849 if (st) | |
1850 mywantedstatus = st; | |
1851 | |
1852 if (msg != mystatusmsg) { | |
1853 g_free(mystatusmsg); | |
1854 if (*msg) | |
1855 mystatusmsg = g_strdup(msg); | |
1856 else | |
1857 mystatusmsg = NULL; | |
1858 } | |
1859 | |
1860 if (!Autoaway) | |
1861 update_last_use(); | |
1862 | |
1863 // Update status line | |
1864 scr_UpdateMainStatus(TRUE); | |
1865 } | |
1866 | |
1867 | |
1868 enum imstatus xmpp_getstatus(void) | |
1869 { | |
1870 return mystatus; | |
1871 } | |
1872 | |
1873 const char *xmpp_getstatusmsg(void) | |
1874 { | |
1875 return mystatusmsg; | |
1876 } | |
1877 | |
1878 // xmpp_setprevstatus() | |
1879 // Set previous status. This wrapper function is used after a disconnection. | |
1880 void xmpp_setprevstatus(void) | |
1881 { | |
1882 xmpp_setstatus(mywantedstatus, NULL, mystatusmsg, FALSE); | |
1883 } | |
1884 | |
1885 // send_storage(store) | |
1886 // Send the node "store" to update the server. | |
1887 // Note: the sender should check we're online. | |
1888 void send_storage(LmMessageNode *store) | |
1889 { | |
1890 LmMessage *iq; | |
1891 LmMessageNode *query; | |
1892 | |
1893 if (!rosternotes) return; | |
1894 | |
1895 iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, | |
1896 LM_MESSAGE_SUB_TYPE_SET); | |
1897 query = lm_message_node_add_child(iq->node, "query", NULL); | |
1898 lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE); | |
1899 lm_message_node_insert_childnode(query, store); | |
1900 | |
1901 lm_connection_send(lconnection, iq, NULL); | |
1902 lm_message_unref(iq); | |
1903 } | |
1904 | |
1905 | |
1906 // xmpp_is_bookmarked(roomjid) | |
1907 // Return TRUE if there's a bookmark for the given jid. | |
1908 guint xmpp_is_bookmarked(const char *bjid) | |
1909 { | |
1910 LmMessageNode *x; | |
1911 | |
1912 if (!bookmarks) | |
1913 return FALSE; | |
1914 | |
1915 // Walk through the storage bookmark tags | |
1916 for (x = bookmarks->children ; x; x = x->next) { | |
1917 // If the node is a conference item, check the jid. | |
1918 if (x->name && !strcmp(x->name, "conference")) { | |
1919 const char *fjid = lm_message_node_get_attribute(x, "jid"); | |
1920 if (fjid && !strcasecmp(bjid, fjid)) | |
1921 return TRUE; | |
1922 } | |
1923 } | |
1924 return FALSE; | |
1925 } | |
1926 | |
1927 // xmpp_get_bookmark_nick(roomjid) | |
1928 // Return the room nickname if it is present in a bookmark. | |
1929 const char *xmpp_get_bookmark_nick(const char *bjid) | |
1930 { | |
1931 LmMessageNode *x; | |
1932 | |
1933 if (!bookmarks || !bjid) | |
1934 return NULL; | |
1935 | |
1936 // Walk through the storage bookmark tags | |
1937 for (x = bookmarks->children ; x; x = x->next) { | |
1938 // If the node is a conference item, check the jid. | |
1939 if (x->name && !strcmp(x->name, "conference")) { | |
1940 const char *fjid = lm_message_node_get_attribute(x, "jid"); | |
1941 if (fjid && !strcasecmp(bjid, fjid)) | |
1942 return lm_message_node_get_child_value(x, "nick"); | |
1943 } | |
1944 } | |
1945 return NULL; | |
1946 } | |
1947 | |
1948 | |
1949 // xmpp_get_all_storage_bookmarks() | |
1950 // Return a GSList with all storage bookmarks. | |
1951 // The caller should g_free the list (not the MUC jids). | |
1952 GSList *xmpp_get_all_storage_bookmarks(void) | |
1953 { | |
1954 LmMessageNode *x; | |
1955 GSList *sl_bookmarks = NULL; | |
1956 | |
1957 // If we have no bookmarks, probably the server doesn't support them. | |
1958 if (!bookmarks) | |
1959 return NULL; | |
1960 | |
1961 // Walk through the storage bookmark tags | |
1962 for (x = bookmarks->children ; x; x = x->next) { | |
1963 // If the node is a conference item, let's add the note to our list. | |
1964 if (x->name && !strcmp(x->name, "conference")) { | |
1965 struct bookmark *bm_elt; | |
1966 const char *autojoin, *name, *nick; | |
1967 const char *fjid = lm_message_node_get_attribute(x, "jid"); | |
1968 if (!fjid) | |
1969 continue; | |
1970 bm_elt = g_new0(struct bookmark, 1); | |
1971 bm_elt->roomjid = g_strdup(fjid); | |
1972 autojoin = lm_message_node_get_attribute(x, "autojoin"); | |
1973 nick = lm_message_node_get_attribute(x, "nick"); | |
1974 name = lm_message_node_get_attribute(x, "name"); | |
1975 if (autojoin && !strcmp(autojoin, "1")) | |
1976 bm_elt->autojoin = 1; | |
1977 if (nick) | |
1978 bm_elt->nick = g_strdup(nick); | |
1979 if (name) | |
1980 bm_elt->name = g_strdup(name); | |
1981 sl_bookmarks = g_slist_append(sl_bookmarks, bm_elt); | |
1982 } | |
1983 } | |
1984 return sl_bookmarks; | |
1985 } | |
1986 | |
1987 // xmpp_set_storage_bookmark(roomid, name, nick, passwd, autojoin, | |
1988 // printstatus, autowhois) | |
1989 // Update the private storage bookmarks: add a conference room. | |
1990 // If name is nil, we remove the bookmark. | |
1991 void xmpp_set_storage_bookmark(const char *roomid, const char *name, | |
1992 const char *nick, const char *passwd, | |
1993 int autojoin, enum room_printstatus pstatus, | |
1994 enum room_autowhois awhois) | |
1995 { | |
1996 LmMessageNode *x; | |
1997 bool changed = FALSE; | |
1998 | |
1999 if (!roomid) | |
2000 return; | |
2001 | |
2002 // If we have no bookmarks, probably the server doesn't support them. | |
2003 if (!bookmarks) { | |
2004 scr_LogPrint(LPRINT_NORMAL, | |
2005 "Sorry, your server doesn't seem to support private storage."); | |
2006 return; | |
2007 } | |
2008 | |
2009 // Walk through the storage tags | |
2010 for (x = bookmarks->children ; x; x = x->next) { | |
2011 // If the current node is a conference item, see if we have to replace it. | |
2012 if (x->name && !strcmp(x->name, "conference")) { | |
2013 const char *fjid = lm_message_node_get_attribute(x, "jid"); | |
2014 if (!fjid) | |
2015 continue; | |
2016 if (!strcmp(fjid, roomid)) { | |
2017 // We've found a bookmark for this room. Let's hide it and we'll | |
2018 // create a new one. | |
2019 lm_message_node_hide(x); | |
2020 changed = TRUE; | |
2021 if (!name) | |
2022 scr_LogPrint(LPRINT_LOGNORM, "Deleting bookmark..."); | |
2023 } | |
2024 } | |
2025 } | |
2026 | |
2027 // Let's create a node/bookmark for this roomid, if the name is not NULL. | |
2028 if (name) { | |
2029 x = lm_message_node_add_child(bookmarks, "conference", NULL); | |
2030 lm_message_node_set_attributes(x, | |
2031 "jid", roomid, | |
2032 "name", name, | |
2033 "autojoin", autojoin ? "1" : "0", | |
2034 NULL); | |
2035 if (nick) | |
2036 lm_message_node_add_child(x, "nick", nick); | |
2037 if (passwd) | |
2038 lm_message_node_add_child(x, "password", passwd); | |
2039 if (pstatus) | |
2040 lm_message_node_add_child(x, "print_status", strprintstatus[pstatus]); | |
2041 if (awhois) | |
2042 lm_message_node_set_attributes(x, "autowhois", | |
2043 (awhois == autowhois_on) ? "1" : "0", | |
2044 NULL); | |
2045 changed = TRUE; | |
2046 scr_LogPrint(LPRINT_LOGNORM, "Updating bookmarks..."); | |
2047 } | |
2048 | |
2049 if (!changed) | |
2050 return; | |
2051 | |
2052 if (lm_connection_is_authenticated(lconnection)) | |
2053 send_storage(bookmarks); | |
2054 else | |
2055 scr_LogPrint(LPRINT_LOGNORM, | |
2056 "Warning: you're not connected to the server."); | |
2057 } | |
2058 | |
2059 static struct annotation *parse_storage_rosternote(LmMessageNode *notenode) | |
2060 { | |
2061 const char *p; | |
2062 struct annotation *note = g_new0(struct annotation, 1); | |
2063 p = lm_message_node_get_attribute(notenode, "cdate"); | |
2064 if (p) | |
2065 note->cdate = from_iso8601(p, 1); | |
2066 p = lm_message_node_get_attribute(notenode, "mdate"); | |
2067 if (p) | |
2068 note->mdate = from_iso8601(p, 1); | |
2069 note->text = g_strdup(lm_message_node_get_value(notenode)); | |
2070 note->jid = g_strdup(lm_message_node_get_attribute(notenode, "jid")); | |
2071 return note; | |
2072 } | |
2073 | |
2074 // xmpp_get_all_storage_rosternotes() | |
2075 // Return a GSList with all storage annotations. | |
2076 // The caller should g_free the list and its contents. | |
2077 GSList *xmpp_get_all_storage_rosternotes(void) | |
2078 { | |
2079 LmMessageNode *x; | |
2080 GSList *sl_notes = NULL; | |
2081 | |
2082 // If we have no rosternotes, probably the server doesn't support them. | |
2083 if (!rosternotes) | |
2084 return NULL; | |
2085 | |
2086 // Walk through the storage rosternotes tags | |
2087 for (x = rosternotes->children ; x; x = x->next) { | |
2088 struct annotation *note; | |
2089 | |
2090 // We want a note item | |
2091 if (!x->name || strcmp(x->name, "note")) | |
2092 continue; | |
2093 // Just in case, check the jid... | |
2094 if (!lm_message_node_get_attribute(x, "jid")) | |
2095 continue; | |
2096 // Ok, let's add the note to our list | |
2097 note = parse_storage_rosternote(x); | |
2098 sl_notes = g_slist_append(sl_notes, note); | |
2099 } | |
2100 return sl_notes; | |
2101 } | |
2102 | |
2103 // xmpp_get_storage_rosternotes(barejid, silent) | |
2104 // Return the annotation associated with this jid. | |
2105 // If silent is TRUE, no warning is displayed when rosternotes is disabled | |
2106 // The caller should g_free the string and structure after use. | |
2107 struct annotation *xmpp_get_storage_rosternotes(const char *barejid, int silent) | |
2108 { | |
2109 LmMessageNode *x; | |
2110 | |
2111 if (!barejid) | |
2112 return NULL; | |
2113 | |
2114 // If we have no rosternotes, probably the server doesn't support them. | |
2115 if (!rosternotes) { | |
2116 if (!silent) | |
2117 scr_LogPrint(LPRINT_NORMAL, "Sorry, " | |
2118 "your server doesn't seem to support private storage."); | |
2119 return NULL; | |
2120 } | |
2121 | |
2122 // Walk through the storage rosternotes tags | |
2123 for (x = rosternotes->children ; x; x = x->next) { | |
2124 const char *fjid; | |
2125 // We want a note item | |
2126 if (!x->name || strcmp(x->name, "note")) | |
2127 continue; | |
2128 // Just in case, check the jid... | |
2129 fjid = lm_message_node_get_attribute(x, "jid"); | |
2130 if (fjid && !strcmp(fjid, barejid)) // We've found a note for this contact. | |
2131 return parse_storage_rosternote(x); | |
2132 } | |
2133 return NULL; // No note found | |
2134 } | |
2135 | |
2136 // xmpp_set_storage_rosternotes(barejid, note) | |
2137 // Update the private storage rosternotes: add/delete a note. | |
2138 // If note is nil, we remove the existing note. | |
2139 void xmpp_set_storage_rosternotes(const char *barejid, const char *note) | |
2140 { | |
2141 LmMessageNode *x; | |
2142 bool changed = FALSE; | |
2143 const char *cdate = NULL; | |
2144 | |
2145 if (!barejid) | |
2146 return; | |
2147 | |
2148 // If we have no rosternotes, probably the server doesn't support them. | |
2149 if (!rosternotes) { | |
2150 scr_LogPrint(LPRINT_NORMAL, | |
2151 "Sorry, your server doesn't seem to support private storage."); | |
2152 return; | |
2153 } | |
2154 | |
2155 // Walk through the storage tags | |
2156 for (x = rosternotes->children ; x; x = x->next) { | |
2157 // If the current node is a conference item, see if we have to replace it. | |
2158 if (x->name && !strcmp(x->name, "note")) { | |
2159 const char *fjid = lm_message_node_get_attribute(x, "jid"); | |
2160 if (!fjid) | |
2161 continue; | |
2162 if (!strcmp(fjid, barejid)) { | |
2163 // We've found a note for this jid. Let's hide it and we'll | |
2164 // create a new one. | |
2165 cdate = lm_message_node_get_attribute(x, "cdate"); | |
2166 lm_message_node_hide(x); | |
2167 changed = TRUE; | |
2168 break; | |
2169 } | |
2170 } | |
2171 } | |
2172 | |
2173 // Let's create a node for this jid, if the note is not NULL. | |
2174 if (note) { | |
2175 char mdate[20]; | |
2176 time_t now; | |
2177 time(&now); | |
2178 to_iso8601(mdate, now); | |
2179 if (!cdate) | |
2180 cdate = mdate; | |
2181 x = lm_message_node_add_child(rosternotes, "note", note); | |
2182 lm_message_node_set_attributes(x, | |
2183 "jid", barejid, | |
2184 "cdate", cdate, | |
2185 "mdate", mdate, | |
2186 NULL); | |
2187 changed = TRUE; | |
2188 } | |
2189 | |
2190 if (!changed) | |
2191 return; | |
2192 | |
2193 if (lm_connection_is_authenticated(lconnection)) | |
2194 send_storage(rosternotes); | |
2195 else | |
2196 scr_LogPrint(LPRINT_LOGNORM, | |
2197 "Warning: you're not connected to the server."); | |
2198 } | |
2199 | |
2200 /* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ |