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