From 6b7eccfec4f76b7d9d1f865caf741ff3214b5964 Mon Sep 17 00:00:00 2001
From: Pavlo Lavrenenko <santa.ssh@gmail.com>
Date: Thu, 2 Jun 2016 08:18:51 +0300
Subject: [PATCH 07/33] Disconnect IMAP clients if only few free FDs left (#37)

After network connection to DB server goes down the processing of IMAP session
stalls: DB connection pool becomes exhausted as active connections do not
close till TCP timeout kicks in (true at least for Oracle). While DBMail still
accepts incoming connections it quickly reaches the RLIMIT_NOFILE and becomes
unresponsive. Send BYE response if the number of opened FDs reaches the
RLIMIT_NOFILE value.
---
 src/dbmail.h.in |  5 +++++
 src/dm_misc.c   | 20 ++++++++++++++++++++
 src/dm_misc.h   |  8 ++++++++
 src/imap4.c     | 23 ++++++++++++++++++++++-
 4 files changed, 55 insertions(+), 1 deletion(-)

diff --git src/dbmail.h.in src/dbmail.h.in
index d826dc3..17215ef 100644
--- src/dbmail.h.in
+++ src/dbmail.h.in
@@ -69,12 +69,14 @@
 #include <string.h>
 #include <strings.h>
 #include <sysexits.h>
+#include <dirent.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/mman.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <sys/time.h>
+#include <sys/resource.h>
 #include <sys/wait.h>
 #include <sys/ipc.h>
 #include <sys/shm.h>
@@ -252,6 +254,9 @@
 /* input reading linelimit */
 #define MAX_LINESIZE (64*1024)
 
+/* minumun number of free file descriptors required to run the daemon */
+#define FREE_DF_THRESHOLD 16
+
 /* string length for query */
 #define DEF_QUERYSIZE (32*1024)
 #define DEF_FRAGSIZE 256
diff --git src/dm_misc.c src/dm_misc.c
index e27ef34..e795de1 100644
--- src/dm_misc.c
+++ src/dm_misc.c
@@ -104,6 +104,26 @@ int drop_privileges(char *newuser, char *newgroup)
 	return 0;
 }
 
+int get_opened_fd_count(void)
+{
+	DIR* dir = NULL;
+	struct dirent* entry = NULL;
+	char buf[32];
+	int fd_count = 0;
+
+	snprintf(buf, 32, "/proc/%i/fd/", getpid());
+
+	dir = opendir(buf);
+	if (dir == NULL)
+		return -1;
+
+	while ((entry = readdir(dir)) != NULL)
+		fd_count++;
+	closedir(dir);
+
+	return fd_count - 2; /* exclude '.' and '..' entries */
+}
+
 void create_unique_id(char *target, uint64_t message_idnr)
 {
 	char md5_str[FIELDSIZE];
diff --git src/dm_misc.h src/dm_misc.h
index 9660dfa..b6cf24f 100644
--- src/dm_misc.h
+++ src/dm_misc.h
@@ -45,6 +45,14 @@ void g_string_maybe_shrink(GString *s);
 int drop_privileges(char *newuser, char *newgroup);
 
 /**
+   \brief get the number of opened files (requires /proc mounted)
+   \return
+        - -1 on error
+        - number of opened files
+*/
+int get_opened_fd_count(void);
+
+/**
  * \brief create a unique id for a message (used for pop, stored per message)
  * \param target target string. Length should be UID_SIZE 
  * \param message_idnr message_idnr of message
diff --git src/imap4.c src/imap4.c
index 0532f2e..e523edc 100644
--- src/imap4.c
+++ src/imap4.c
@@ -351,6 +351,12 @@ static void send_greeting(ImapSession *session)
 	dbmail_imap_session_set_state(session, CLIENTSTATE_NON_AUTHENTICATED);
 }
 
+static void disconnect_user(ImapSession *session)
+{
+	imap_session_printf(session, "* BYE [Service unavailable.]\r\n");
+	imap_handle_abort(session);
+}
+
 /*
  * the default timeout callback */
 
@@ -601,6 +607,8 @@ int imap_handle_connection(client_sock *c)
 {
 	ImapSession *session;
 	ClientBase_T *ci;
+	struct rlimit fd_limit;
+	int fd_count;
 
 	ci = client_init(c);
 
@@ -617,7 +625,20 @@ int imap_handle_connection(client_sock *c)
 		Capa_remove(session->capa, "LOGINDISABLED");
 	}
 
-	send_greeting(session);
+	fd_count = get_opened_fd_count();
+	if (fd_count < 0 || getrlimit(RLIMIT_NPROC, &fd_limit) < 0) {
+		TRACE(TRACE_ERR,
+			"[%p] failed to retrieve fd limits, dropping client connection",
+			session);
+		disconnect_user(session);
+	} else if (fd_limit.rlim_cur - fd_count < FREE_DF_THRESHOLD) {
+		TRACE(TRACE_WARNING,
+			"[%p] fd count [%d], fd limit [%d], fd threshold [%d]: dropping client connection",
+			session, fd_count, fd_limit.rlim_cur, FREE_DF_THRESHOLD);
+		disconnect_user(session);
+	} else {
+		send_greeting(session);
+	}
 
 	reset_callbacks(session);
 
-- 
2.10.1 (Apple Git-78)

