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