fix: Fix XMPP stream parsing for multiple stanzas
- Add special handling for <stream:stream> (unclosed root element) - Feed virtual <wrapper> element to allow multiple root stanzas - Support non-<query/> IQ payloads like <ping/> (XEP-0199) - Relax stanza matching: allow extra attrs/children in received - Add legacy auth feature in stream:features - Use larger read buffer (1024 bytes) for efficiency - Clear curr_string buffer after each stanza
This commit is contained in:
@@ -48,7 +48,7 @@
|
||||
#define XML_START "<?xml version=\"1.0\"?>"
|
||||
|
||||
#define STREAM_RESP "<stream:stream from=\"localhost\" id=\"stream1\" xml:lang=\"en\" version=\"1.0\" xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\">"
|
||||
#define FEATURES "<stream:features></stream:features>"
|
||||
#define FEATURES "<stream:features><auth xmlns='http://jabber.org/features/iq-auth'/></stream:features>"
|
||||
|
||||
#define STREAM_END "</stream:stream>"
|
||||
|
||||
@@ -97,7 +97,7 @@ write_stream(const char * const stream)
|
||||
int
|
||||
read_stream(void)
|
||||
{
|
||||
char buf[2];
|
||||
char buf[1024];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
GString *stream = g_string_new("");
|
||||
|
||||
@@ -120,7 +120,7 @@ read_stream(void)
|
||||
send_queue = NULL;
|
||||
pthread_mutex_unlock(&send_queue_lock);
|
||||
|
||||
int read_size = recv(client->sock, buf, 1, 0);
|
||||
int read_size = recv(client->sock, buf, sizeof(buf) - 1, 0);
|
||||
|
||||
// client disconnect
|
||||
if (read_size == 0) {
|
||||
@@ -133,6 +133,11 @@ read_stream(void)
|
||||
if (read_size == -1) {
|
||||
// got nothing, sleep and try again
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
static int eagain_count = 0;
|
||||
eagain_count++;
|
||||
if (eagain_count % 1000 == 0) {
|
||||
fflush(stderr);
|
||||
}
|
||||
errno = 0;
|
||||
usleep(1000 * 5);
|
||||
continue;
|
||||
@@ -145,8 +150,9 @@ read_stream(void)
|
||||
}
|
||||
}
|
||||
|
||||
// success, feed parser with byte
|
||||
parser_feed(buf, 1);
|
||||
// success, feed parser with received data
|
||||
buf[read_size] = '\0'; // null-terminate
|
||||
parser_feed(buf, read_size);
|
||||
g_string_append_len(stream, buf, read_size);
|
||||
if (g_str_has_suffix(stream->str, STREAM_END)) {
|
||||
log_println(STBBR_LOGINFO, "RECV: </stream:stream>");
|
||||
@@ -168,6 +174,8 @@ stream_start_callback(void)
|
||||
write_stream(XML_START);
|
||||
write_stream(STREAM_RESP);
|
||||
write_stream(FEATURES);
|
||||
// DEBUG removed
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -375,6 +383,7 @@ _start_server_cb(void* userdata)
|
||||
prctl(PR_SET_NAME, "stbr");
|
||||
#endif
|
||||
|
||||
// DEBUG removed
|
||||
struct sockaddr_in client_addr;
|
||||
|
||||
// listen socket non blocking
|
||||
@@ -390,6 +399,7 @@ _start_server_cb(void* userdata)
|
||||
int c = sizeof(struct sockaddr_in);
|
||||
int client_socket;
|
||||
errno = 0;
|
||||
// DEBUG removed
|
||||
while ((client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, (socklen_t*)&c)) == -1) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
log_println(STBBR_LOGERROR, "Accept failed: %s", strerror(errno));
|
||||
@@ -407,8 +417,10 @@ _start_server_cb(void* userdata)
|
||||
}
|
||||
|
||||
client = xmppclient_new(client_addr, client_socket);
|
||||
// DEBUG removed
|
||||
parser_init(stream_start_callback, auth_callback, id_callback, query_callback);
|
||||
|
||||
// DEBUG removed
|
||||
read_stream();
|
||||
|
||||
return NULL;
|
||||
|
||||
@@ -103,7 +103,8 @@ stanza_to_string(XMPPStanza *stanza)
|
||||
}
|
||||
|
||||
char *result = stanza_str->str;
|
||||
g_string_free(stanza_str, FALSE);
|
||||
char *unused_gstring_data = g_string_free(stanza_str, FALSE);
|
||||
(void)unused_gstring_data; // Silence warning
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -248,12 +249,18 @@ stanza_get_query_request(XMPPStanza *stanza)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
XMPPStanza *query = stanza_get_child_by_name(stanza, "query");
|
||||
if (!query) {
|
||||
return NULL;
|
||||
XMPPStanza *payload = stanza_get_child_by_name(stanza, "query");
|
||||
if (!payload) {
|
||||
// Support IQ payloads that don't use <query/> (e.g. <ping/>) as long as
|
||||
// there is a single child element to associate with the namespace.
|
||||
if (stanza->children && stanza->children->next == NULL) {
|
||||
payload = stanza->children->data;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char *xmlns = stanza_get_attr(query, "xmlns");
|
||||
const char *xmlns = stanza_get_attr(payload, "xmlns");
|
||||
if (!xmlns) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -153,32 +153,30 @@ _stanzas_equal(XMPPStanza *first, XMPPStanza *second)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// check attribute count
|
||||
if (g_list_length(first->attrs) != g_list_length(second->attrs)) {
|
||||
// check attribute count - now we allow second to have MORE attributes
|
||||
// All attrs from first must be in second, but not vice versa
|
||||
if (g_list_length(first->attrs) > g_list_length(second->attrs)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// check children count
|
||||
if (g_list_length(first->children) != g_list_length(second->children)) {
|
||||
// check children count - now we allow second to have MORE children
|
||||
if (g_list_length(first->children) > g_list_length(second->children)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// check presence of content
|
||||
if (!first->content && second->content) {
|
||||
return -1;
|
||||
}
|
||||
// check presence of content - only if first requires it
|
||||
if (first->content && !second->content) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// check content is exists
|
||||
// check content if first has it
|
||||
if (first->content) {
|
||||
if (g_strcmp0(first->content->str, second->content->str) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// check attributes
|
||||
// check attributes - all from first must be in second
|
||||
if (first->attrs) {
|
||||
GList *first_curr_attr = first->attrs;
|
||||
while (first_curr_attr) {
|
||||
|
||||
@@ -68,9 +68,35 @@ int
|
||||
parser_feed(char *chunk, int len)
|
||||
{
|
||||
g_string_append_len(curr_string, chunk, len);
|
||||
|
||||
// Special handling for XMPP stream opener
|
||||
// Check if we've received a complete <stream:stream> element (ending with '>')
|
||||
// Since XMPP streams have an unclosed root element, we need special detection
|
||||
if (len > 0 && chunk[len-1] == '>') {
|
||||
const char *str = curr_string->str;
|
||||
// Look for <?xml...?><stream:stream...>
|
||||
if (strstr(str, "<stream:stream") != NULL &&
|
||||
!strstr(str, "</stream:stream>")) {
|
||||
// Manually trigger the stream start callback
|
||||
log_println(STBBR_LOGINFO, "RECV: %s", curr_string->str);
|
||||
if (stream_start_cb) {
|
||||
stream_start_cb();
|
||||
}
|
||||
// Reset for next stanzas
|
||||
g_string_free(curr_string, TRUE);
|
||||
curr_string = g_string_new("");
|
||||
parser_close();
|
||||
parser_init(stream_start_cb, auth_cb, id_cb, query_cb);
|
||||
// Feed a virtual root element to allow multiple stanzas
|
||||
XML_Parse(parser, "<wrapper>", 9, 0);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int res = XML_Parse(parser, chunk, len, 0);
|
||||
parser_reset();
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -96,6 +122,12 @@ parser_reset(void)
|
||||
static void
|
||||
_start_element(void *data, const char *element, const char **attributes)
|
||||
{
|
||||
|
||||
// Ignore our virtual wrapper element
|
||||
if (g_strcmp0(element, "wrapper") == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_strcmp0(element, "stream:stream") == 0) {
|
||||
log_println(STBBR_LOGINFO, "RECV: %s", curr_string->str);
|
||||
stream_start_cb();
|
||||
@@ -128,6 +160,10 @@ _end_element(void *data, const char *element)
|
||||
|
||||
log_println(STBBR_LOGINFO, "RECV: %s", curr_string->str);
|
||||
stanzas_add(curr_stanza);
|
||||
|
||||
// Clear the string buffer for the next stanza
|
||||
g_string_truncate(curr_string, 0);
|
||||
|
||||
if (stanza_get_child_by_ns(curr_stanza, "jabber:iq:auth")) {
|
||||
auth_cb(curr_stanza);
|
||||
} else {
|
||||
@@ -141,7 +177,7 @@ _end_element(void *data, const char *element)
|
||||
}
|
||||
}
|
||||
|
||||
do_reset = 1;
|
||||
// Don't reset parser here - allow multiple root elements for XMPP stanzas
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
Reference in New Issue
Block a user