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 "llist.h"
8 #include "global-memory.h"
9 #include "stats-settings.h"
10 #include "mail-stats.h"
11 #include "mail-session.h"
12 #include "mail-command.h"
13 
14 #define MAIL_COMMAND_TIMEOUT_SECS (60*15)
15 
16 /* commands are sorted by their last_update timestamp, oldest first */
17 struct mail_command *stable_mail_commands_head;
18 struct mail_command *stable_mail_commands_tail;
19 
mail_command_memsize(const struct mail_command * cmd)20 static size_t mail_command_memsize(const struct mail_command *cmd)
21 {
22 	return sizeof(*cmd) + strlen(cmd->name) + 1 + strlen(cmd->args) + 1;
23 }
24 
25 static struct mail_command *
mail_command_find(struct mail_session * session,unsigned int id)26 mail_command_find(struct mail_session *session, unsigned int id)
27 {
28 	struct mail_command *cmd;
29 
30 	i_assert(id != 0);
31 
32 	if (id > session->highest_cmd_id) {
33 		/* fast path for new commands */
34 		return NULL;
35 	}
36 	for (cmd = session->commands; cmd != NULL; cmd = cmd->session_next) {
37 		if (cmd->id == id)
38 			return cmd;
39 	}
40 	/* expired */
41 	return NULL;
42 }
43 
44 static struct mail_command *
mail_command_add(struct mail_session * session,const char * name,const char * args)45 mail_command_add(struct mail_session *session, const char *name,
46 		 const char *args)
47 {
48 	struct mail_command *cmd;
49 
50 	cmd = i_malloc(MALLOC_ADD(sizeof(struct mail_command), stats_alloc_size()));
51 	cmd->stats = (void *)(cmd + 1);
52 	cmd->refcount = 1; /* unrefed at "done" */
53 	cmd->session = session;
54 	cmd->name = i_strdup(name);
55 	cmd->args = i_strdup(args);
56 	cmd->last_update = ioloop_timeval;
57 
58 	DLLIST2_APPEND_FULL(&stable_mail_commands_head,
59 			    &stable_mail_commands_tail, cmd,
60 			    stable_prev, stable_next);
61 	DLLIST_PREPEND_FULL(&session->commands, cmd,
62 			    session_prev, session_next);
63 	mail_session_ref(cmd->session);
64 	global_memory_alloc(mail_command_memsize(cmd));
65 	return cmd;
66 }
67 
mail_command_free(struct mail_command * cmd)68 static void mail_command_free(struct mail_command *cmd)
69 {
70 	i_assert(cmd->refcount == 0);
71 
72 	global_memory_free(mail_command_memsize(cmd));
73 
74 	DLLIST2_REMOVE_FULL(&stable_mail_commands_head,
75 			    &stable_mail_commands_tail, cmd,
76 			    stable_prev, stable_next);
77 	DLLIST_REMOVE_FULL(&cmd->session->commands, cmd,
78 			   session_prev, session_next);
79 	mail_session_unref(&cmd->session);
80 	i_free(cmd->name);
81 	i_free(cmd->args);
82 	i_free(cmd);
83 }
84 
mail_command_ref(struct mail_command * cmd)85 void mail_command_ref(struct mail_command *cmd)
86 {
87 	cmd->refcount++;
88 }
89 
mail_command_unref(struct mail_command ** _cmd)90 void mail_command_unref(struct mail_command **_cmd)
91 {
92 	struct mail_command *cmd = *_cmd;
93 
94 	i_assert(cmd->refcount > 0);
95 	cmd->refcount--;
96 
97 	*_cmd = NULL;
98 }
99 
mail_command_update_parse(const char * const * args,const char ** error_r)100 int mail_command_update_parse(const char *const *args, const char **error_r)
101 {
102 	struct mail_session *session;
103 	struct mail_command *cmd;
104 	struct stats *new_stats, *diff_stats;
105 	buffer_t *buf;
106 	const char *error;
107 	unsigned int i, cmd_id;
108 	bool done = FALSE, continued = FALSE;
109 
110 	/* <session guid> <cmd id> [d] <name> <args> <stats>
111 	   <session guid> <cmd id> c[d] <stats> */
112 	if (str_array_length(args) < 3) {
113 		*error_r = "UPDATE-CMD: Too few parameters";
114 		return -1;
115 	}
116 	if (mail_session_get(args[0], &session, error_r) < 0)
117 		return -1;
118 
119 	if (str_to_uint(args[1], &cmd_id) < 0 || cmd_id == 0) {
120 		*error_r = "UPDATE-CMD: Invalid command id";
121 		return -1;
122 	}
123 	for (i = 0; args[2][i] != '\0'; i++) {
124 		switch (args[2][i]) {
125 		case 'd':
126 			done = TRUE;
127 			break;
128 		case 'c':
129 			continued = TRUE;
130 			break;
131 		default:
132 			*error_r = "UPDATE-CMD: Invalid flags parameter";
133 			return -1;
134 		}
135 	}
136 
137 	cmd = mail_command_find(session, cmd_id);
138 	if (!continued) {
139 		/* new command */
140 		if (cmd != NULL) {
141 			*error_r = "UPDATE-CMD: Duplicate new command id";
142 			return -1;
143 		}
144 		if (str_array_length(args) < 5) {
145 			*error_r = "UPDATE-CMD: Too few parameters";
146 			return -1;
147 		}
148 		cmd = mail_command_add(session, args[3], args[4]);
149 		cmd->id = cmd_id;
150 
151 		session->highest_cmd_id =
152 			I_MAX(session->highest_cmd_id, cmd_id);
153 		session->num_cmds++;
154 		session->user->num_cmds++;
155 		session->user->domain->num_cmds++;
156 		if (session->ip != NULL)
157 			session->ip->num_cmds++;
158 		mail_global_stats.num_cmds++;
159 		args += 5;
160 	} else {
161 		if (cmd == NULL) {
162 			/* already expired command, ignore */
163 			i_warning("UPDATE-CMD: Already expired");
164 			return 0;
165 		}
166 		args += 3;
167 		cmd->last_update = ioloop_timeval;
168 	}
169 	buf = t_buffer_create(256);
170 	if (args[0] == NULL ||
171 	    base64_decode(args[0], strlen(args[0]), NULL, buf) < 0) {
172 		*error_r = t_strdup_printf("UPDATE-CMD: Invalid base64 input");
173 		return -1;
174 	}
175 
176 	new_stats = stats_alloc(pool_datastack_create());
177 	diff_stats = stats_alloc(pool_datastack_create());
178 
179 	if (!stats_import(buf->data, buf->used, cmd->stats, new_stats, &error)) {
180 		*error_r = t_strdup_printf("UPDATE-CMD: %s", error);
181 		return -1;
182 	}
183 
184 	if (!stats_diff(cmd->stats, new_stats, diff_stats, &error)) {
185 		*error_r = t_strdup_printf("UPDATE-CMD: stats shrank: %s", error);
186 		return -1;
187 	}
188 	stats_add(cmd->stats, diff_stats);
189 
190 	if (done) {
191 		cmd->id = 0;
192 		mail_command_unref(&cmd);
193 	}
194 	mail_session_refresh(session, NULL);
195 	return 0;
196 }
197 
mail_command_is_timed_out(struct mail_command * cmd)198 static bool mail_command_is_timed_out(struct mail_command *cmd)
199 {
200 	/* some commands like IDLE can run forever */
201 	return ioloop_time - cmd->last_update.tv_sec >
202 		MAIL_COMMAND_TIMEOUT_SECS;
203 }
204 
mail_commands_free_memory(void)205 void mail_commands_free_memory(void)
206 {
207 	unsigned int diff;
208 
209 	while (stable_mail_commands_head != NULL) {
210 		struct mail_command *cmd = stable_mail_commands_head;
211 
212 		if (cmd->refcount == 0)
213 			i_assert(cmd->id == 0);
214 		else if (cmd->refcount == 1 &&
215 			 (cmd->session->disconnected ||
216 			  mail_command_is_timed_out(cmd))) {
217 			/* session was probably lost */
218 			mail_command_unref(&cmd);
219 		} else {
220 			break;
221 		}
222 		mail_command_free(stable_mail_commands_head);
223 
224 		if (global_used_memory < stats_settings->memory_limit ||
225 		    stable_mail_commands_head == NULL)
226 			break;
227 
228 		diff = ioloop_time - stable_mail_commands_head->last_update.tv_sec;
229 		if (diff < stats_settings->command_min_time)
230 			break;
231 	}
232 }
233 
mail_commands_init(void)234 void mail_commands_init(void)
235 {
236 }
237 
mail_commands_deinit(void)238 void mail_commands_deinit(void)
239 {
240 	while (stable_mail_commands_head != NULL) {
241 		struct mail_command *cmd = stable_mail_commands_head;
242 
243 		if (cmd->id != 0)
244 			mail_command_unref(&cmd);
245 		mail_command_free(stable_mail_commands_head);
246 	}
247 }
248