2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
25 #include "conversation.h"
35 #include "ciphers/sha1hash.h"
36 #include "ciphers/sha256hash.h"
37 #include "ciphers/md5hash.h"
41 #include <stringprep.h>
42 static char idn_buffer
[1024];
46 static gboolean
jabber_nodeprep(char *str
, size_t buflen
)
48 return stringprep_xmpp_nodeprep(str
, buflen
) == STRINGPREP_OK
;
51 static gboolean
jabber_resourceprep(char *str
, size_t buflen
)
53 return stringprep_xmpp_resourceprep(str
, buflen
) == STRINGPREP_OK
;
57 jabber_idn_validate(const char *str
, const char *at
, const char *slash
,
60 const char *node
= NULL
;
61 const char *domain
= NULL
;
62 const char *resource
= NULL
;
69 /* Ensure no parts are > 1023 bytes */
76 domain_len
= slash
- (at
+ 1);
78 resource_len
= null
- (slash
+ 1);
80 domain_len
= null
- (at
+ 1);
86 domain_len
= slash
- str
;
88 resource_len
= null
- (slash
+ 1);
90 domain_len
= null
- str
;
94 if (node
&& node_len
> 1023)
96 if (domain_len
> 1023)
98 if (resource
&& resource_len
> 1023)
101 jid
= g_new0(JabberID
, 1);
104 strncpy(idn_buffer
, node
, node_len
);
105 idn_buffer
[node_len
] = '\0';
107 if (!jabber_nodeprep(idn_buffer
, sizeof(idn_buffer
))) {
113 jid
->node
= g_strdup(idn_buffer
);
116 /* domain *must* be here */
117 strncpy(idn_buffer
, domain
, domain_len
);
118 idn_buffer
[domain_len
] = '\0';
119 if (domain
[0] == '[') { /* IPv6 address */
120 gboolean valid
= FALSE
;
122 if (idn_buffer
[domain_len
- 1] == ']') {
123 idn_buffer
[domain_len
- 1] = '\0';
124 valid
= purple_ipv6_address_is_valid(idn_buffer
+ 1);
133 jid
->domain
= g_strndup(domain
, domain_len
);
136 if (stringprep_nameprep(idn_buffer
, sizeof(idn_buffer
)) != STRINGPREP_OK
) {
142 /* And now ToASCII */
143 if (idna_to_ascii_8z(idn_buffer
, &out
, IDNA_USE_STD3_ASCII_RULES
) != IDNA_SUCCESS
) {
149 /* This *MUST* be freed using 'free', not 'g_free' */
151 jid
->domain
= g_strdup(idn_buffer
);
155 strncpy(idn_buffer
, resource
, resource_len
);
156 idn_buffer
[resource_len
] = '\0';
158 if (!jabber_resourceprep(idn_buffer
, sizeof(idn_buffer
))) {
163 jid
->resource
= g_strdup(idn_buffer
);
172 gboolean
jabber_nodeprep_validate(const char *str
)
183 if(strlen(str
) > 1023)
187 strncpy(idn_buffer
, str
, sizeof(idn_buffer
) - 1);
188 idn_buffer
[sizeof(idn_buffer
) - 1] = '\0';
189 result
= jabber_nodeprep(idn_buffer
, sizeof(idn_buffer
));
194 gunichar ch
= g_utf8_get_char(c
);
195 if(ch
== '\"' || ch
== '&' || ch
== '\'' || ch
== '/' || ch
== ':' ||
196 ch
== '<' || ch
== '>' || ch
== '@' || !g_unichar_isgraph(ch
)) {
199 c
= g_utf8_next_char(c
);
206 gboolean
jabber_domain_validate(const char *str
)
221 /* Check if str is a valid IPv6 identifier */
222 gboolean valid
= FALSE
;
224 if (*(c
+ len
- 1) != ']')
227 /* Ugly, but in-place */
228 *(gchar
*)(c
+ len
- 1) = '\0';
229 valid
= purple_ipv6_address_is_valid(c
+ 1);
230 *(gchar
*)(c
+ len
- 1) = ']';
236 gunichar ch
= g_utf8_get_char(c
);
237 /* The list of characters allowed in domain names is pretty small */
238 if ((ch
<= 0x7F && !( (ch
>= 'a' && ch
<= 'z')
239 || (ch
>= '0' && ch
<= '9')
240 || (ch
>= 'A' && ch
<= 'Z')
242 || ch
== '-' )) || (ch
>= 0x80 && !g_unichar_isgraph(ch
)))
245 c
= g_utf8_next_char(c
);
251 gboolean
jabber_resourceprep_validate(const char *str
)
262 if(strlen(str
) > 1023)
266 strncpy(idn_buffer
, str
, sizeof(idn_buffer
) - 1);
267 idn_buffer
[sizeof(idn_buffer
) - 1] = '\0';
268 result
= jabber_resourceprep(idn_buffer
, sizeof(idn_buffer
));
273 gunichar ch
= g_utf8_get_char(c
);
274 if(!g_unichar_isgraph(ch
) && ch
!= ' ')
277 c
= g_utf8_next_char(c
);
284 char *jabber_saslprep(const char *in
)
289 g_return_val_if_fail(in
!= NULL
, NULL
);
290 g_return_val_if_fail(strlen(in
) <= sizeof(idn_buffer
) - 1, NULL
);
292 strncpy(idn_buffer
, in
, sizeof(idn_buffer
) - 1);
293 idn_buffer
[sizeof(idn_buffer
) - 1] = '\0';
295 if (STRINGPREP_OK
!= stringprep(idn_buffer
, sizeof(idn_buffer
), 0,
296 stringprep_saslprep
)) {
297 memset(idn_buffer
, 0, sizeof(idn_buffer
));
301 out
= g_strdup(idn_buffer
);
302 memset(idn_buffer
, 0, sizeof(idn_buffer
));
305 /* TODO: Something better than disallowing all non-ASCII characters */
306 /* TODO: Is this even correct? */
309 c
= (const guchar
*)in
;
311 if (*c
> 0x7f || /* Non-ASCII characters */
312 *c
== 0x7f || /* ASCII Delete character */
313 (*c
< 0x20 && *c
!= '\t' && *c
!= '\n' && *c
!= '\r'))
314 /* ASCII control characters */
323 jabber_id_new_internal(const char *str
, gboolean allow_terminating_slash
)
325 const char *at
= NULL
;
326 const char *slash
= NULL
;
328 gboolean needs_validation
= FALSE
;
330 gboolean node_is_required
= FALSE
;
341 for (c
= str
; *c
!= '\0'; c
++)
347 /* Multiple @'s in the node/domain portion, not a valid JID! */
351 /* JIDs cannot start with @ */
355 /* JIDs cannot end with @ */
365 /* JIDs cannot start with / */
368 if (c
[1] == '\0' && !allow_terminating_slash
) {
369 /* JIDs cannot end with / */
377 /* characters allowed everywhere */
378 if ((*c
>= 'a' && *c
<= 'z')
379 || (*c
>= '0' && *c
<= '9')
380 || (*c
>= 'A' && *c
<= 'Z')
381 || *c
== '.' || *c
== '-')
387 /* characters allowed only in the resource */
393 /* characters allowed only in the node */
396 * Ok, this character is valid, but only if it's a part
397 * of the node and not the domain. But we don't know
398 * if "c" is a part of the node or the domain until after
399 * we've found the @. So set a flag for now and check
400 * that we found an @ later.
402 node_is_required
= TRUE
;
408 * Hmm, this character is a bit more exotic. Better fall
409 * back to using the more expensive UTF-8 compliant
410 * stringprep functions.
412 needs_validation
= TRUE
;
418 if (node_is_required
&& at
== NULL
)
419 /* Found invalid characters in the domain */
423 if (!needs_validation
) {
424 /* JID is made of only ASCII characters--just lowercase and return */
425 jid
= g_new0(JabberID
, 1);
428 jid
->node
= g_ascii_strdown(str
, at
- str
);
430 jid
->domain
= g_ascii_strdown(at
+ 1, slash
- (at
+ 1));
432 jid
->resource
= g_strdup(slash
+ 1);
434 jid
->domain
= g_ascii_strdown(at
+ 1, -1);
438 jid
->domain
= g_ascii_strdown(str
, slash
- str
);
440 jid
->resource
= g_strdup(slash
+ 1);
442 jid
->domain
= g_ascii_strdown(str
, -1);
449 * If we get here, there are some non-ASCII chars in the string, so
450 * we'll need to validate it, normalize, and finally do a full jabber
451 * nodeprep on the jid.
454 if (!g_utf8_validate(str
, -1, NULL
))
458 return jabber_idn_validate(str
, at
, slash
, c
/* points to the null */);
461 jid
= g_new0(JabberID
, 1);
465 node
= g_utf8_casefold(str
, at
-str
);
467 domain
= g_utf8_casefold(at
+1, slash
-(at
+1));
469 jid
->resource
= g_utf8_normalize(slash
+1, -1, G_NORMALIZE_NFKC
);
471 domain
= g_utf8_casefold(at
+1, -1);
475 domain
= g_utf8_casefold(str
, slash
-str
);
477 jid
->resource
= g_utf8_normalize(slash
+1, -1, G_NORMALIZE_NFKC
);
479 domain
= g_utf8_casefold(str
, -1);
484 jid
->node
= g_utf8_normalize(node
, -1, G_NORMALIZE_NFKC
);
489 jid
->domain
= g_utf8_normalize(domain
, -1, G_NORMALIZE_NFKC
);
493 /* and finally the jabber nodeprep */
494 if(!jabber_nodeprep_validate(jid
->node
) ||
495 !jabber_domain_validate(jid
->domain
) ||
496 !jabber_resourceprep_validate(jid
->resource
)) {
506 jabber_id_free(JabberID
*jid
)
511 g_free(jid
->resource
);
518 jabber_id_equal(const JabberID
*jid1
, const JabberID
*jid2
)
520 if (!jid1
&& !jid2
) {
521 /* Both are null therefore equal */
525 if (!jid1
|| !jid2
) {
526 /* One is null, other is non-null, therefore not equal */
530 return purple_strequal(jid1
->node
, jid2
->node
) &&
531 purple_strequal(jid1
->domain
, jid2
->domain
) &&
532 purple_strequal(jid1
->resource
, jid2
->resource
);
535 char *jabber_get_domain(const char *in
)
537 JabberID
*jid
= jabber_id_new(in
);
543 out
= g_strdup(jid
->domain
);
549 char *jabber_get_resource(const char *in
)
551 JabberID
*jid
= jabber_id_new(in
);
557 out
= g_strdup(jid
->resource
);
564 jabber_id_to_bare_jid(const JabberID
*jid
)
566 JabberID
*result
= g_new0(JabberID
, 1);
568 result
->node
= g_strdup(jid
->node
);
569 result
->domain
= g_strdup(jid
->domain
);
575 jabber_get_bare_jid(const char *in
)
577 JabberID
*jid
= jabber_id_new(in
);
582 out
= jabber_id_get_bare_jid(jid
);
589 jabber_id_get_bare_jid(const JabberID
*jid
)
591 g_return_val_if_fail(jid
!= NULL
, NULL
);
593 return g_strconcat(jid
->node
? jid
->node
: "",
594 jid
->node
? "@" : "",
600 jabber_id_get_full_jid(const JabberID
*jid
)
602 g_return_val_if_fail(jid
!= NULL
, NULL
);
604 return g_strconcat(jid
->node
? jid
->node
: "",
605 jid
->node
? "@" : "",
607 jid
->resource
? "/" : "",
608 jid
->resource
? jid
->resource
: "",
613 jabber_jid_is_domain(const char *jid
)
617 for (c
= jid
; *c
; ++c
) {
618 if (*c
== '@' || *c
== '/')
627 jabber_id_new(const char *str
)
629 return jabber_id_new_internal(str
, FALSE
);
632 const char *jabber_normalize(const PurpleAccount
*account
, const char *in
)
634 PurpleConnection
*gc
= NULL
;
635 JabberStream
*js
= NULL
;
636 static char buf
[3072]; /* maximum legal length of a jabber jid */
640 gc
= purple_account_get_connection(account
);
642 js
= purple_connection_get_protocol_data(gc
);
644 jid
= jabber_id_new_internal(in
, TRUE
);
648 if(js
&& jid
->node
&& jid
->resource
&&
649 jabber_chat_find(js
, jid
->node
, jid
->domain
))
650 g_snprintf(buf
, sizeof(buf
), "%s@%s/%s", jid
->node
, jid
->domain
,
653 g_snprintf(buf
, sizeof(buf
), "%s%s%s", jid
->node
? jid
->node
: "",
654 jid
->node
? "@" : "", jid
->domain
);
662 jabber_is_own_server(JabberStream
*js
, const char *str
)
670 g_return_val_if_fail(*str
!= '\0', FALSE
);
672 jid
= jabber_id_new(str
);
676 equal
= (jid
->node
== NULL
&&
677 g_str_equal(jid
->domain
, js
->user
->domain
) &&
678 jid
->resource
== NULL
);
684 jabber_is_own_account(JabberStream
*js
, const char *str
)
692 g_return_val_if_fail(*str
!= '\0', FALSE
);
694 jid
= jabber_id_new(str
);
698 equal
= (purple_strequal(jid
->node
, js
->user
->node
) &&
699 g_str_equal(jid
->domain
, js
->user
->domain
) &&
700 (jid
->resource
== NULL
||
701 g_str_equal(jid
->resource
, js
->user
->resource
)));
706 static const struct {
707 const char *status_id
; /* link to core */
708 const char *show
; /* The show child's cdata in a presence stanza */
709 const char *readable
; /* readable representation */
710 JabberBuddyState state
;
711 } jabber_statuses
[] = {
712 { "offline", NULL
, N_("Offline"), JABBER_BUDDY_STATE_UNAVAILABLE
},
713 { "available", NULL
, N_("Available"), JABBER_BUDDY_STATE_ONLINE
},
714 { "freeforchat", "chat", N_("Chatty"), JABBER_BUDDY_STATE_CHAT
},
715 { "away", "away", N_("Away"), JABBER_BUDDY_STATE_AWAY
},
716 { "extended_away", "xa", N_("Extended Away"), JABBER_BUDDY_STATE_XA
},
717 { "dnd", "dnd", N_("Do Not Disturb"), JABBER_BUDDY_STATE_DND
},
718 { "error", NULL
, N_("Error"), JABBER_BUDDY_STATE_ERROR
}
722 jabber_buddy_state_get_name(const JabberBuddyState state
)
725 for (i
= 0; i
< G_N_ELEMENTS(jabber_statuses
); ++i
)
726 if (jabber_statuses
[i
].state
== state
)
727 return _(jabber_statuses
[i
].readable
);
733 jabber_buddy_status_id_get_state(const char *id
)
737 return JABBER_BUDDY_STATE_UNKNOWN
;
739 for (i
= 0; i
< G_N_ELEMENTS(jabber_statuses
); ++i
)
740 if (g_str_equal(id
, jabber_statuses
[i
].status_id
))
741 return jabber_statuses
[i
].state
;
743 return JABBER_BUDDY_STATE_UNKNOWN
;
746 JabberBuddyState
jabber_buddy_show_get_state(const char *id
)
750 g_return_val_if_fail(id
!= NULL
, JABBER_BUDDY_STATE_UNKNOWN
);
752 for (i
= 0; i
< G_N_ELEMENTS(jabber_statuses
); ++i
)
753 if (jabber_statuses
[i
].show
&& g_str_equal(id
, jabber_statuses
[i
].show
))
754 return jabber_statuses
[i
].state
;
756 purple_debug_warning("jabber", "Invalid value of presence <show/> "
757 "attribute: %s\n", id
);
758 return JABBER_BUDDY_STATE_UNKNOWN
;
762 jabber_buddy_state_get_show(JabberBuddyState state
)
765 for (i
= 0; i
< G_N_ELEMENTS(jabber_statuses
); ++i
)
766 if (state
== jabber_statuses
[i
].state
)
767 return jabber_statuses
[i
].show
;
773 jabber_buddy_state_get_status_id(JabberBuddyState state
)
776 for (i
= 0; i
< G_N_ELEMENTS(jabber_statuses
); ++i
)
777 if (state
== jabber_statuses
[i
].state
)
778 return jabber_statuses
[i
].status_id
;
784 jabber_calculate_data_hash(gconstpointer data
, size_t len
,
785 const gchar
*hash_algo
)
787 PurpleHash
*hash
= NULL
;
788 static gchar digest
[129]; /* 512 bits hex + \0 */
790 if (g_str_equal(hash_algo
, "sha1"))
791 hash
= purple_sha1_hash_new();
792 else if (g_str_equal(hash_algo
, "sha256"))
793 hash
= purple_sha256_hash_new();
794 else if (g_str_equal(hash_algo
, "md5"))
795 hash
= purple_md5_hash_new();
799 purple_debug_error("jabber", "Unexpected hashing algorithm %s requested\n", hash_algo
);
800 g_return_val_if_reached(NULL
);
804 purple_hash_append(hash
, data
, len
);
805 if (!purple_hash_digest_to_str(hash
, digest
, sizeof(digest
)))
807 purple_debug_error("jabber", "Failed to get digest for %s cipher.\n",
809 g_return_val_if_reached(NULL
);
811 g_object_unref(G_OBJECT(hash
));
813 return g_strdup(digest
);