1 /* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "common.h"
4 #include "llist.h"
5 #include "istream.h"
6 #include "ostream.h"
7 #include "strescape.h"
8 #include "master-service.h"
9 #include "master-interface.h"
10 #include "connect-limit.h"
11 #include "penalty.h"
12 #include "anvil-connection.h"
13 
14 #include <unistd.h>
15 
16 #define MAX_INBUF_SIZE 1024
17 
18 #define ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION 1
19 #define ANVIL_CLIENT_PROTOCOL_MINOR_VERSION 0
20 
21 struct anvil_connection {
22 	struct anvil_connection *prev, *next;
23 
24 	int fd;
25 	struct istream *input;
26 	struct ostream *output;
27 	struct io *io;
28 
29 	bool version_received:1;
30 	bool handshaked:1;
31 	bool master:1;
32 	bool fifo:1;
33 };
34 
35 static struct anvil_connection *anvil_connections = NULL;
36 
37 static const char *const *
anvil_connection_next_line(struct anvil_connection * conn)38 anvil_connection_next_line(struct anvil_connection *conn)
39 {
40 	const char *line;
41 
42 	line = i_stream_next_line(conn->input);
43 	return line == NULL ? NULL : t_strsplit_tabescaped(line);
44 }
45 
46 static int
anvil_connection_request(struct anvil_connection * conn,const char * const * args,const char ** error_r)47 anvil_connection_request(struct anvil_connection *conn,
48 			 const char *const *args, const char **error_r)
49 {
50 	const char *cmd = args[0];
51 	unsigned int value, checksum;
52 	time_t stamp;
53 	pid_t pid;
54 
55 	args++;
56 	if (strcmp(cmd, "CONNECT") == 0) {
57 		if (args[0] == NULL || args[1] == NULL) {
58 			*error_r = "CONNECT: Not enough parameters";
59 			return -1;
60 		}
61 		if (str_to_pid(args[0], &pid) < 0) {
62 			*error_r = "CONNECT: Invalid pid";
63 			return -1;
64 		}
65 		connect_limit_connect(connect_limit, pid, args[1]);
66 	} else if (strcmp(cmd, "DISCONNECT") == 0) {
67 		if (args[0] == NULL || args[1] == NULL) {
68 			*error_r = "DISCONNECT: Not enough parameters";
69 			return -1;
70 		}
71 		if (str_to_pid(args[0], &pid) < 0) {
72 			*error_r = "DISCONNECT: Invalid pid";
73 			return -1;
74 		}
75 		connect_limit_disconnect(connect_limit, pid, args[1]);
76 	} else if (strcmp(cmd, "CONNECT-DUMP") == 0) {
77 		connect_limit_dump(connect_limit, conn->output);
78 	} else if (strcmp(cmd, "KILL") == 0) {
79 		if (args[0] == NULL) {
80 			*error_r = "KILL: Not enough parameters";
81 			return -1;
82 		}
83 		if (!conn->master) {
84 			*error_r = "KILL sent by a non-master connection";
85 			return -1;
86 		}
87 		if (str_to_pid(args[0], &pid) < 0) {
88 			*error_r = "KILL: Invalid pid";
89 			return -1;
90 		}
91 		connect_limit_disconnect_pid(connect_limit, pid);
92 	} else if (strcmp(cmd, "LOOKUP") == 0) {
93 		if (args[0] == NULL) {
94 			*error_r = "LOOKUP: Not enough parameters";
95 			return -1;
96 		}
97 		if (conn->output == NULL) {
98 			*error_r = "LOOKUP on a FIFO, can't send reply";
99 			return -1;
100 		}
101 		value = connect_limit_lookup(connect_limit, args[0]);
102 		o_stream_nsend_str(conn->output,
103 				   t_strdup_printf("%u\n", value));
104 	} else if (strcmp(cmd, "PENALTY-GET") == 0) {
105 		if (args[0] == NULL) {
106 			*error_r = "PENALTY-GET: Not enough parameters";
107 			return -1;
108 		}
109 		value = penalty_get(penalty, args[0], &stamp);
110 		o_stream_nsend_str(conn->output,
111 			t_strdup_printf("%u %s\n", value, dec2str(stamp)));
112 	} else if (strcmp(cmd, "PENALTY-INC") == 0) {
113 		if (args[0] == NULL || args[1] == NULL || args[2] == NULL) {
114 			*error_r = "PENALTY-INC: Not enough parameters";
115 			return -1;
116 		}
117 		if (str_to_uint(args[1], &checksum) < 0 ||
118 		    str_to_uint(args[2], &value) < 0 ||
119 		    value > PENALTY_MAX_VALUE ||
120 		    (value == 0 && checksum != 0)) {
121 			*error_r = "PENALTY-INC: Invalid parameters";
122 			return -1;
123 		}
124 		penalty_inc(penalty, args[0], checksum, value);
125 	} else if (strcmp(cmd, "PENALTY-SET-EXPIRE-SECS") == 0) {
126 		if (args[0] == NULL || str_to_uint(args[0], &value) < 0) {
127 			*error_r = "PENALTY-SET-EXPIRE-SECS: "
128 				"Invalid parameters";
129 			return -1;
130 		}
131 		penalty_set_expire_secs(penalty, value);
132 	} else if (strcmp(cmd, "PENALTY-DUMP") == 0) {
133 		penalty_dump(penalty, conn->output);
134 	} else {
135 		*error_r = t_strconcat("Unknown command: ", cmd, NULL);
136 		return -1;
137 	}
138 	return 0;
139 }
140 
anvil_connection_input(struct anvil_connection * conn)141 static void anvil_connection_input(struct anvil_connection *conn)
142 {
143 	const char *line, *const *args, *error;
144 
145 	switch (i_stream_read(conn->input)) {
146 	case -2:
147 		i_error("BUG: Anvil client connection sent too much data");
148 		anvil_connection_destroy(conn);
149 		return;
150 	case -1:
151 		anvil_connection_destroy(conn);
152 		return;
153 	}
154 
155 	if (!conn->version_received) {
156 		if ((line = i_stream_next_line(conn->input)) == NULL)
157 			return;
158 
159 		if (!version_string_verify(line, "anvil",
160 				ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION)) {
161 			if (anvil_restarted && (conn->master || conn->fifo)) {
162 				/* old pending data. ignore input until we get
163 				   the handshake. */
164 				anvil_connection_input(conn);
165 				return;
166 			}
167 			i_error("Anvil client not compatible with this server "
168 				"(mixed old and new binaries?) %s", line);
169 			anvil_connection_destroy(conn);
170 			return;
171 		}
172 		conn->version_received = TRUE;
173 	}
174 
175 	while ((args = anvil_connection_next_line(conn)) != NULL) {
176 		if (args[0] != NULL) {
177 			if (anvil_connection_request(conn, args, &error) < 0) {
178 				i_error("Anvil client input error: %s", error);
179 				anvil_connection_destroy(conn);
180 				break;
181 			}
182 		}
183 	}
184 }
185 
186 struct anvil_connection *
anvil_connection_create(int fd,bool master,bool fifo)187 anvil_connection_create(int fd, bool master, bool fifo)
188 {
189 	struct anvil_connection *conn;
190 
191 	conn = i_new(struct anvil_connection, 1);
192 	conn->fd = fd;
193 	conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE);
194 	if (!fifo) {
195 		conn->output = o_stream_create_fd(fd, SIZE_MAX);
196 		o_stream_set_no_error_handling(conn->output, TRUE);
197 	}
198 	conn->io = io_add(fd, IO_READ, anvil_connection_input, conn);
199 	conn->master = master;
200 	conn->fifo = fifo;
201 	DLLIST_PREPEND(&anvil_connections, conn);
202 	return conn;
203 }
204 
anvil_connection_destroy(struct anvil_connection * conn)205 void anvil_connection_destroy(struct anvil_connection *conn)
206 {
207 	bool fifo = conn->fifo;
208 
209 	DLLIST_REMOVE(&anvil_connections, conn);
210 
211 	io_remove(&conn->io);
212 	i_stream_destroy(&conn->input);
213 	o_stream_destroy(&conn->output);
214 	if (close(conn->fd) < 0)
215 		i_error("close(anvil conn) failed: %m");
216 	i_free(conn);
217 
218 	if (!fifo)
219 		master_service_client_connection_destroyed(master_service);
220 }
221 
anvil_connections_destroy_all(void)222 void anvil_connections_destroy_all(void)
223 {
224 	while (anvil_connections != NULL)
225 		anvil_connection_destroy(anvil_connections);
226 }
227