11 Commits

Author SHA1 Message Date
5bb776c432 fix(server): check kill_recv in accept() loop to prevent hang on stop
If server_stop() is called before any client connects, the accept()
loop would spin forever since kill_recv was never checked, causing
pthread_join() to block indefinitely.

Add kill_recv check at the top of the accept() polling loop so the
thread can exit cleanly when server_stop() is called during the
pre-connection phase.
2026-03-12 11:51:56 +03:00
1c36292b1f fix(server): remove invalid recv() call on listen socket
The recv() drain loop on listen_socket was incorrect - listen sockets
don't have data to read. This caused Valgrind to report 'points to
unaddressable byte(s)' errors during functional tests.

Replaced magic number 2 with SHUT_RDWR for clarity.
2026-01-13 17:13:41 +03:00
79fe9dc2b3 fix: use valid buffer in recv() instead of NULL 2025-12-31 09:55:22 +03:00
0353d31355 Fix race condition in log_println (use-after-free)
The check for logready was done BEFORE acquiring the lock, causing a race
condition with log_close(). If log_close() runs between the check and
the lock acquisition, the log file would be closed but log_println would
still try to write to it (use-after-free).

Now the logready/logp check is inside the critical section.
2025-12-25 23:04:13 +03:00
afa4a3a4ea Fix GString memory leak in read_stream function
The 'stream' GString was not freed before returning from read_stream()
in two places:
1. When kill_recv is TRUE at the start of the loop
2. At the end of the function after the loop exits

Added g_string_free(stream, TRUE) before both return statements.
2025-12-25 22:47:10 +03:00
6579e9f565 Fix: Pass valid buffer to recv() instead of NULL
Valgrind reports:
  Syscall param socketcall.recvfrom(buf) points to unaddressable byte(s)
  at recv (recv.c:28)
  by xmppclient_end_session (xmppclient.c:57)
  Address 0x0 is not stack'd, malloc'd or (recently) free'd

The recv() call was passing NULL as buffer, which is invalid.
Now using a local char variable as discard buffer.
2025-12-25 20:42:32 +03:00
7a222fc446 fix: Free XMPPStanza objects in verify_any and verify_last
The XMPPStanza objects created by stanza_parse() in verify_any() and
verify_last() functions were never freed, causing memory leaks.

This fix adds stanza_free(stanza) calls before the return statements
to properly release the allocated stanza resources.
2025-12-24 16:52:40 +03:00
da7b32cbba fix: Resolve memory leak in verify_any and verify_last by destroying GTimer
The GTimer objects created in verify_any() and verify_last() functions
were never freed, causing memory leaks detected by Valgrind.

This fix adds g_timer_destroy(timer) calls after the while loops to
properly release the allocated timer resources.
2025-12-24 16:25:39 +03:00
6faee6177d Merge pull request 'fix: Resolve memory leak in stanza_parse by freeing parse state' (#2) from fix/memory-leak-stanza-parse into master
No functional changes to the parsing behavior
2025-12-24 12:38:15 +00:00
9a41c959ca fix: Resolve memory leak in stanza_parse by freeing parse state 2025-12-24 15:34:26 +03:00
00f47a1598 fix: Fix XMPP stream parsing and add stbbr_for_presence_to helper (#1)
This PR fixes critical issues with XMPP stream parsing and adds a new helper function for MUC testing.

## Changes

### 1. Fix XMPP stream parsing for multiple stanzas
The XML parser was not correctly handling multiple consecutive stanzas in the stream. Fixed depth tracking to properly detect stanza boundaries.

### 2. Fix XMPP stream parsing depth tracking
Added proper depth tracking for nested XML elements to correctly identify when a complete stanza has been received.

### 3. Remove debug logging that interfered with expect tests
Removed verbose debug output that was causing expect-based functional tests to fail due to unexpected output in the stream.

### 4. Add `stbbr_for_presence_to` for MUC join presence matching
New helper function that registers a canned response for presence stanzas based on the `to` attribute. This is essential for testing MUC (Multi-User Chat) join flows where the response must be triggered by presence to a specific room JID.

**API:**
```c
void stbbr_for_presence_to(const char *to, const char *response);

Reviewed-on: jabber.developer2/stabber#1
Co-authored-by: jabber.developer2 <jabber.developer2@jabber.space>
Co-committed-by: jabber.developer2 <jabber.developer2@jabber.space>
2025-12-15 08:45:38 +00:00
5 changed files with 21 additions and 7 deletions

View File

@@ -70,11 +70,14 @@ log_init(stbbr_log_t loglevel)
void
log_println(stbbr_log_t loglevel, const char * const msg, ...)
{
if (!logready || loglevel < minlevel) {
pthread_mutex_lock(&loglock);
// Check logready AFTER acquiring lock to prevent race with log_close
if (!logready || !logp || loglevel < minlevel) {
pthread_mutex_unlock(&loglock);
return;
}
pthread_mutex_lock(&loglock);
va_list arg;
va_start(arg, msg);
GString *fmt_msg = g_string_new(NULL);

View File

@@ -109,6 +109,7 @@ read_stream(void)
while (TRUE) {
if (kill_recv) {
_shutdown();
g_string_free(stream, TRUE);
return 0;
}
@@ -167,6 +168,7 @@ read_stream(void)
memset(buf, 0, sizeof(buf));
}
g_string_free(stream, TRUE);
return 0;
}
@@ -434,8 +436,12 @@ _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 (kill_recv) {
log_println(STBBR_LOGINFO, "Accept loop interrupted by stop request");
_shutdown();
return NULL;
}
if (errno != EAGAIN && errno != EWOULDBLOCK) {
log_println(STBBR_LOGERROR, "Accept failed: %s", strerror(errno));
return NULL;
@@ -475,8 +481,7 @@ _shutdown(void)
parser_close();
shutdown(listen_socket, 2);
while (recv(listen_socket, NULL, 1, 0) > 0) {}
shutdown(listen_socket, SHUT_RDWR);
close(listen_socket);
prime_free_all();

View File

@@ -299,7 +299,9 @@ stanza_parse(char *stanza_text)
XML_Parse(parser, stanza_text, strlen(stanza_text), 0);
XML_ParserFree(parser);
return state->curr_stanza;
XMPPStanza *result = state->curr_stanza;
free(state);
return result;
}
static void

View File

@@ -63,6 +63,7 @@ verify_any(char *stanza_text, gboolean ign_timeout)
usleep(1000 * 50);
elapsed = g_timer_elapsed(timer, NULL);
}
g_timer_destroy(timer);
}
if (result) {
@@ -71,6 +72,7 @@ verify_any(char *stanza_text, gboolean ign_timeout)
log_println(STBBR_LOGINFO, "VERIFY FAIL: %s", stanza_text);
}
stanza_free(stanza);
return result;
}
@@ -94,6 +96,7 @@ verify_last(char *stanza_text)
usleep(1000 * 50);
elapsed = g_timer_elapsed(timer, NULL);
}
g_timer_destroy(timer);
}
if (result) {
@@ -104,5 +107,6 @@ verify_last(char *stanza_text)
stanzas_debug_last();
}
stanza_free(stanza);
return result;
}

View File

@@ -54,7 +54,7 @@ xmppclient_end_session(XMPPClient *client)
if (client->sock) {
shutdown(client->sock, 2);
while (recv(client->sock, NULL, 1, 0) > 0) {}
char discard_buf; while (recv(client->sock, &discard_buf, 1, 0) > 0) {}
close(client->sock);
}
free(client->ip);