1 /* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "buffer.h"
5 #include "base64.h"
6 #include "ioloop.h"
7 #include "hash.h"
8 #include "llist.h"
9 #include "str-table.h"
10 #include "global-memory.h"
11 #include "stats-settings.h"
12 #include "mail-stats.h"
13 #include "mail-user.h"
14 #include "mail-ip.h"
15 #include "mail-session.h"
16 #include "mail-domain.h"
17 
18 /* If session doesn't receive any updates for this long, assume that the
19    process associated with it has crashed, and forcibly disconnect the
20    session. Must be larger than SESSION_STATS_FORCE_REFRESH_SECS in
21    stats plugin */
22 #define MAIL_SESSION_IDLE_TIMEOUT_MSECS (1000*60*15)
23 /* If stats process crashes/restarts, existing processes keep sending status
24    updates to it, but this process doesn't know their session IDs. If these
25    missing IDs are found within this many seconds of starting the stats process,
26    don't log a warning about them. (On a larger installation this avoids
27    flooding the error log with hundreds of warnings.) */
28 #define SESSION_ID_WARN_HIDE_SECS (60*5)
29 
30 static HASH_TABLE(char *, struct mail_session *) mail_sessions_hash;
31 /* sessions are sorted by their last_update timestamp, oldest first */
32 static struct mail_session *mail_sessions_head, *mail_sessions_tail;
33 static time_t session_id_warn_hide_until;
34 static bool session_id_hide_warned = FALSE;
35 static struct str_table *services;
36 
37 struct mail_session *stable_mail_sessions;
38 
mail_session_memsize(const struct mail_session * session)39 static size_t mail_session_memsize(const struct mail_session *session)
40 {
41 	return sizeof(*session) + strlen(session->id) + 1;
42 }
43 
mail_session_disconnect(struct mail_session * session)44 static void mail_session_disconnect(struct mail_session *session)
45 {
46 	i_assert(!session->disconnected);
47 
48 	mail_user_disconnected(session->user);
49 	if (session->ip != NULL)
50 		mail_ip_disconnected(session->ip);
51 
52 	hash_table_remove(mail_sessions_hash, session->id);
53 	session->disconnected = TRUE;
54 	timeout_remove(&session->to_idle);
55 	mail_session_unref(&session);
56 }
57 
mail_session_idle_timeout(struct mail_session * session)58 static void mail_session_idle_timeout(struct mail_session *session)
59 {
60 	/* user="" service="" pid=0 is used for incoming sessions that were
61 	   received after we detected a stats process crash/restart. there's
62 	   no point in logging anything about them, since they contain no
63 	   useful information. */
64 	if (session->user->name[0] == '\0' && session->service[0] != '\0' &&
65 	    session->pid == 0) {
66 		i_warning("Session %s (user %s, service %s) "
67 			  "appears to have crashed, disconnecting it",
68 			  session->id, session->user->name, session->service);
69 	}
70 	mail_session_disconnect(session);
71 }
72 
mail_session_connect_parse(const char * const * args,const char ** error_r)73 int mail_session_connect_parse(const char *const *args, const char **error_r)
74 {
75 	struct mail_session *session;
76 	const char *session_id;
77 	pid_t pid;
78 	struct ip_addr ip;
79 	unsigned int i;
80 
81 	/* <session id> <username> <service> <pid> [key=value ..] */
82 	if (str_array_length(args) < 4) {
83 		*error_r = "CONNECT: Too few parameters";
84 		return -1;
85 	}
86 	session_id = args[0];
87 	if (str_to_pid(args[3], &pid) < 0) {
88 		*error_r = t_strdup_printf("CONNECT: Invalid pid %s for session ID %s",
89 					   args[3], session_id);
90 		return -1;
91 	}
92 
93 	session = hash_table_lookup(mail_sessions_hash, session_id);
94 	if (session != NULL) {
95 		*error_r = t_strdup_printf(
96 			"CONNECT: Duplicate session ID %s for user %s service %s (old PID %ld, new PID %ld)",
97 			session_id, args[1], args[2], (long)session->pid, (long)pid);
98 		return -1;
99 	}
100 	session = i_malloc(MALLOC_ADD(sizeof(struct mail_session), stats_alloc_size()));
101 	session->stats = (void *)(session + 1);
102 	session->refcount = 1; /* unrefed at disconnect */
103 	session->id = i_strdup(session_id);
104 	session->service = str_table_ref(services, args[2]);
105 	session->pid = pid;
106 	session->last_update = ioloop_timeval;
107 	session->to_idle = timeout_add(MAIL_SESSION_IDLE_TIMEOUT_MSECS,
108 				       mail_session_idle_timeout, session);
109 
110 	session->user = mail_user_login(args[1]);
111 	session->user->num_logins++;
112 	mail_domain_login(session->user->domain);
113 
114 	for (i = 3; args[i] != NULL; i++) {
115 		if (str_begins(args[i], "rip=") &&
116 		    net_addr2ip(args[i] + 4, &ip) == 0)
117 			session->ip = mail_ip_login(&ip);
118 	}
119 
120 	hash_table_insert(mail_sessions_hash, session->id, session);
121 	DLLIST_PREPEND_FULL(&stable_mail_sessions, session,
122 			    stable_prev, stable_next);
123 	DLLIST2_APPEND_FULL(&mail_sessions_head, &mail_sessions_tail, session,
124 			    sorted_prev, sorted_next);
125 	DLLIST_PREPEND_FULL(&session->user->sessions, session,
126 			    user_prev, user_next);
127 	mail_user_ref(session->user);
128 	if (session->ip != NULL) {
129 		DLLIST_PREPEND_FULL(&session->ip->sessions, session,
130 				    ip_prev, ip_next);
131 		mail_ip_ref(session->ip);
132 	}
133 	global_memory_alloc(mail_session_memsize(session));
134 
135 	mail_global_login();
136 	return 0;
137 }
138 
mail_session_ref(struct mail_session * session)139 void mail_session_ref(struct mail_session *session)
140 {
141 	session->refcount++;
142 }
143 
mail_session_unref(struct mail_session ** _session)144 void mail_session_unref(struct mail_session **_session)
145 {
146 	struct mail_session *session = *_session;
147 
148 	i_assert(session->refcount > 0);
149 	session->refcount--;
150 
151 	*_session = NULL;
152 }
153 
mail_session_free(struct mail_session * session)154 static void mail_session_free(struct mail_session *session)
155 {
156 	i_assert(session->refcount == 0);
157 
158 	global_memory_free(mail_session_memsize(session));
159 
160 	timeout_remove(&session->to_idle);
161 	if (!session->disconnected)
162 		hash_table_remove(mail_sessions_hash, session->id);
163 	DLLIST_REMOVE_FULL(&stable_mail_sessions, session,
164 			   stable_prev, stable_next);
165 	DLLIST2_REMOVE_FULL(&mail_sessions_head, &mail_sessions_tail, session,
166 			    sorted_prev, sorted_next);
167 	DLLIST_REMOVE_FULL(&session->user->sessions, session,
168 			   user_prev, user_next);
169 	mail_user_unref(&session->user);
170 	if (session->ip != NULL) {
171 		DLLIST_REMOVE_FULL(&session->ip->sessions, session,
172 				   ip_prev, ip_next);
173 		mail_ip_unref(&session->ip);
174 	}
175 
176 	str_table_unref(services, &session->service);
177 	i_free(session->id);
178 	i_free(session);
179 }
180 
mail_session_id_lost(const char * session_id)181 static void mail_session_id_lost(const char *session_id)
182 {
183 	if (ioloop_time < session_id_warn_hide_until) {
184 		if (session_id_hide_warned)
185 			return;
186 		session_id_hide_warned = TRUE;
187 		i_warning("stats process appears to have crashed/restarted, "
188 			  "hiding missing session ID warnings for %d seconds",
189 			  (int)(session_id_warn_hide_until - ioloop_time));
190 		return;
191 	}
192 	i_warning("Couldn't find session ID: %s", session_id);
193 }
194 
mail_session_lookup(const char * id,struct mail_session ** session_r,const char ** error_r)195 int mail_session_lookup(const char *id, struct mail_session **session_r,
196 			const char **error_r)
197 {
198 	if (id == NULL) {
199 		*error_r = "Too few parameters";
200 		return -1;
201 	}
202 	*session_r = hash_table_lookup(mail_sessions_hash, id);
203 	if (*session_r == NULL) {
204 		mail_session_id_lost(id);
205 		return 0;
206 	}
207 	return 1;
208 }
209 
mail_session_get(const char * id,struct mail_session ** session_r,const char ** error_r)210 int mail_session_get(const char *id, struct mail_session **session_r,
211 		     const char **error_r)
212 {
213 	const char *new_args[5];
214 	int ret;
215 
216 	if ((ret = mail_session_lookup(id, session_r, error_r)) != 0)
217 		return ret;
218 
219 	/* Create a new dummy session to avoid repeated warnings */
220 	new_args[0] = id;
221 	new_args[1] = ""; /* username */
222 	new_args[2] = ""; /* service */
223 	new_args[3] = "0"; /* pid */
224 	new_args[4] = NULL;
225 	if (mail_session_connect_parse(new_args, error_r) < 0)
226 		i_unreached();
227 	if (mail_session_lookup(id, session_r, error_r) != 1)
228 		i_unreached();
229 	return 0;
230 }
231 
mail_session_disconnect_parse(const char * const * args,const char ** error_r)232 int mail_session_disconnect_parse(const char *const *args, const char **error_r)
233 {
234 	struct mail_session *session;
235 	int ret;
236 
237 	/* <session id> */
238 	if ((ret = mail_session_lookup(args[0], &session, error_r)) <= 0)
239 		return ret;
240 
241 	if (!session->disconnected)
242 		mail_session_disconnect(session);
243 	return 0;
244 }
245 
mail_session_refresh(struct mail_session * session,const struct stats * diff_stats)246 void mail_session_refresh(struct mail_session *session,
247 			  const struct stats *diff_stats)
248 {
249 	timeout_reset(session->to_idle);
250 
251 	if (diff_stats != NULL)
252 		stats_add(session->stats, diff_stats);
253 	session->last_update = ioloop_timeval;
254 	DLLIST2_REMOVE_FULL(&mail_sessions_head, &mail_sessions_tail, session,
255 			    sorted_prev, sorted_next);
256 	DLLIST2_APPEND_FULL(&mail_sessions_head, &mail_sessions_tail, session,
257 			    sorted_prev, sorted_next);
258 
259 	mail_user_refresh(session->user, diff_stats);
260 	if (session->ip != NULL)
261 		mail_ip_refresh(session->ip, diff_stats);
262 }
263 
mail_session_update_parse(const char * const * args,const char ** error_r)264 int mail_session_update_parse(const char *const *args, const char **error_r)
265 {
266 	struct mail_session *session;
267 	struct stats *new_stats, *diff_stats;
268 	buffer_t *buf;
269 	const char *error;
270 
271 	/* <session id> <stats> */
272 	if (mail_session_get(args[0], &session, error_r) < 0)
273 		return -1;
274 
275 	buf = t_buffer_create(256);
276 	if (args[1] == NULL ||
277 	    base64_decode(args[1], strlen(args[1]), NULL, buf) < 0) {
278 		*error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: Invalid base64 input",
279 					   session->user->name,
280 					   session->service, session->id);
281 		return -1;
282 	}
283 
284 	new_stats = stats_alloc(pool_datastack_create());
285 	diff_stats = stats_alloc(pool_datastack_create());
286 
287 	if (!stats_import(buf->data, buf->used, session->stats, new_stats, &error)) {
288 		*error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: %s",
289 					   session->user->name,
290 					   session->service, session->id, error);
291 		return -1;
292 	}
293 
294 	if (!stats_diff(session->stats, new_stats, diff_stats, &error)) {
295 		*error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: stats shrank: %s",
296 					   session->user->name,
297 					   session->service, session->id, error);
298 		return -1;
299 	}
300 	mail_session_refresh(session, diff_stats);
301 	return 0;
302 }
303 
mail_sessions_free_memory(void)304 void mail_sessions_free_memory(void)
305 {
306 	unsigned int diff;
307 
308 	while (mail_sessions_head != NULL &&
309 	       mail_sessions_head->refcount == 0) {
310 		i_assert(mail_sessions_head->disconnected);
311 		mail_session_free(mail_sessions_head);
312 
313 		if (global_used_memory < stats_settings->memory_limit ||
314 		    mail_sessions_head == NULL)
315 			break;
316 
317 		diff = ioloop_time - mail_sessions_head->last_update.tv_sec;
318 		if (diff < stats_settings->session_min_time)
319 			break;
320 	}
321 }
322 
mail_sessions_init(void)323 void mail_sessions_init(void)
324 {
325 	session_id_warn_hide_until =
326 		ioloop_time + SESSION_ID_WARN_HIDE_SECS;
327 	hash_table_create(&mail_sessions_hash, default_pool, 0,
328 			  str_hash, strcmp);
329 	services = str_table_init();
330 }
331 
mail_sessions_deinit(void)332 void mail_sessions_deinit(void)
333 {
334 	while (mail_sessions_head != NULL) {
335 		struct mail_session *session = mail_sessions_head;
336 
337 		if (!session->disconnected)
338 			mail_session_unref(&session);
339 		mail_session_free(mail_sessions_head);
340 	}
341 	hash_table_destroy(&mail_sessions_hash);
342 	str_table_deinit(&services);
343 }
344