Hi,

I'm trying to monitor an inbox using IMAP. I'd like to let cURL do the TLS and
authentication magic. The program must deal with connection terminated by
server or disrupted after some time. So I tried to follow man curl_easy_perform:
> You can do any amount of calls to curl_easy_perform(3) while using the same
> easy_handle.

The problem is that although everything seems to work (in version 8.9.1),
valgrind reports massive memory leaks each time the easy handle is re-used by
curl_easy_perform. On the other hand, if interrupted right after first
successful connection, memleak report is clear.

Therefore I wrote simplest possible program to demonstrate the problem and run
it against development version of libcurl. It should login and then logout 10x.
However, talking to a locally running test IMAP server (from cURL test suite):
$ tests/ftpserver.pl --verbose --port 43143 --proto imap --logdir /tmp/imap

... it connects successfully just once. Second time libcurl aborts with:
example: url.c:749: Curl_conn_seems_dead: Assertion `!data->conn' failed.

When using a real-world server with TLS (imaps://...), it also aborts when
trying to re-use the handle, but somewhere else:
example: multi.c:964: Curl_attach_connection: Assertion `!data->conn' failed.

The TLS case differs also by extra "* Expire cleared" message.
Haven't found additional hints in man curl_easy_send / curl_easy_recv.
Changing CURLOPT_MAXCONNECTS doesn't help.
Please review attached source code and let me know what's wrong.
Thanks,
-- 
Aleksander Mazur
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include <sys/select.h>
#include <curl/curl.h>

static int unix_wait(curl_socket_t sockfd, unsigned sending, unsigned timeout)
{
	fd_set fds, *rfds = sending ? NULL : &fds, *wfds = sending ? &fds : NULL;
	struct timeval tv = { timeout, 0 };
	int rv;

	FD_ZERO(&fds);
	FD_SET(sockfd, &fds);
	rv = select(sockfd + 1, rfds, wfds, NULL, &tv);
	return rv > 0 ? 0 /*ready*/ : rv < 0 ? rv /*err*/ : 1 /*timeout*/;
}

static const char command[] = "A013 LOGOUT\r\n";
static char buf[4096];

int main(void)
{
	CURL *curl;
	unsigned loop;

	assert(curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK);
	fprintf(stderr, "using curl %s\n", curl_version_info(0)->version);
	assert((curl = curl_easy_init()));
	assert(curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L) == CURLE_OK);
	assert(curl_easy_setopt(curl, CURLOPT_MAXCONNECTS, 1L) == CURLE_OK);
	assert(curl_easy_setopt(curl, CURLOPT_USERNAME, "test") == CURLE_OK);
	assert(curl_easy_setopt(curl, CURLOPT_PASSWORD, "test") == CURLE_OK);
	assert(curl_easy_setopt(curl, CURLOPT_URL, "imap://localhost:43143") == CURLE_OK);
	assert(curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L) == CURLE_OK);
	for (loop = 1; loop <= 10; loop++) {
		curl_socket_t sockfd;
		size_t done, n;

		printf("loop[%u] perform\n", loop);
		assert(curl_easy_perform(curl) == CURLE_OK);
		assert(curl_easy_getinfo(curl, CURLINFO_ACTIVESOCKET, &sockfd) == CURLE_OK);
		for (done = 0; done < sizeof(command) - 1; done += n) {
			CURLcode res = curl_easy_send(curl, command + done, sizeof(command) - 1 - done, &n);

			if (res == CURLE_OK) {
				fprintf(stderr, "loop[%u] sent %zu\n", loop, n);
				if (!n)
					break;
			} else if (res == CURLE_AGAIN) {
				fprintf(stderr, "loop[%u] wait send\n", loop);
				if (unix_wait(sockfd, 1, 10))
					break;
			} else {
				abort();
			}
		}
		if (done == sizeof(command) - 1) {
			for (done = 0; done < sizeof(buf); done += n) {
				CURLcode res = curl_easy_recv(curl, buf + done, sizeof(buf) - done, &n);

				if (res == CURLE_OK) {
					fprintf(stderr, "loop[%u] recv %zu\n", loop, n);
					if (!n)
						break;
				} else if (res == CURLE_AGAIN) {
					fprintf(stderr, "loop[%u] wait recv\n", loop);
					if (unix_wait(sockfd, 0, 3))
						break;
				} else {
					abort();
				}
			}
			if (done)
				printf("loop[%u] response:%.*s--------\n", loop, (int) done, buf);
		}
	}

	return 0;
}
using curl 8.12.0-DEV
loop[1] perform
* !!! WARNING !!!
* This is a debug build of libcurl, do not use in production.
* STATE: INIT => SETUP handle 0x8421df8; line 2677
* STATE: SETUP => CONNECT handle 0x8421df8; line 2693
* Added connection 0. The cache now contains 1 members
* Host localhost:43143 was resolved.
* IPv4: 127.0.0.1
* STATE: CONNECT => CONNECTING handle 0x8421df8; line 2608
*   Trying 127.0.0.1:43143...
* Connected to localhost (127.0.0.1) port 43143
* STATE: CONNECTING => PROTOCONNECT handle 0x8421df8; line 2731
* IMAP 0x841e2c0 state change from STOP to SERVERGREET
* STATE: PROTOCONNECT => PROTOCONNECTING handle 0x8421df8; line 2756
<         _   _ ____  _     
<     ___| | | |  _ \| |    
<    / __| | | | |_) | |    
<   | (__| |_| |  _ {| |___ 
<    \___|\___/|_| \_\_____|
< * OK curl IMAP server ready to serve
> A001 CAPABILITY
* IMAP 0x841e2c0 state change from SERVERGREET to CAPABILITY
< A001 BAD Command
> A002 LOGIN test test
* IMAP 0x841e2c0 state change from CAPABILITY to LOGIN
< A002 OK LOGIN completed
* IMAP 0x841e2c0 state change from LOGIN to STOP
* STATE: PROTOCONNECTING => DO handle 0x8421df8; line 2777
* STATE: DO => DONE handle 0x8421df8; line 2329
* multi_done[DONE]: status: 0 prem: 0 done: 0
* Connection #0 to host localhost left intact
loop[1] sent 13
loop[1] wait recv
loop[1] recv 36
loop[1] wait recv
loop[1] recv 26
loop[1] wait recv
loop[1] response:* BYE curl IMAP server signing off
A013 OK LOGOUT completed
--------
loop[2] perform
* STATE: INIT => SETUP handle 0x8421df8; line 2677
* STATE: SETUP => CONNECT handle 0x8421df8; line 2693
example: url.c:749: Curl_conn_seems_dead: Assertion `!data->conn' failed.

Program received signal SIGABRT, Aborted.
-- 
Unsubscribe: https://lists.haxx.se/mailman/listinfo/curl-library
Etiquette:   https://curl.se/mail/etiquette.html

Reply via email to