1 | /* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab |
2 | |
3 | This program is free software; you can redistribute it and/or modify |
4 | it under the terms of the GNU General Public License as published by |
5 | the Free Software Foundation; version 2 of the License. |
6 | |
7 | This program is distributed in the hope that it will be useful, |
8 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | GNU General Public License for more details. |
11 | |
12 | You should have received a copy of the GNU General Public License |
13 | along with this program; if not, write to the Free Software |
14 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ |
15 | |
16 | #include "feedback.h" |
17 | |
18 | #ifdef HAVE_NETDB_H |
19 | #include <netdb.h> |
20 | #endif |
21 | |
22 | #ifdef _WIN32 |
23 | #include <ws2tcpip.h> |
24 | #define addrinfo ADDRINFOA |
25 | #endif |
26 | |
27 | namespace feedback { |
28 | |
29 | static const uint FOR_READING= 0; |
30 | static const uint FOR_WRITING= 1; |
31 | |
32 | /** |
33 | implementation of the Url class that sends the data via HTTP POST request. |
34 | |
35 | Both http:// and https:// protocols are supported. |
36 | */ |
37 | class Url_http: public Url { |
38 | protected: |
39 | const LEX_STRING host, port, path; |
40 | bool ssl; |
41 | LEX_STRING proxy_host, proxy_port; |
42 | |
43 | bool use_proxy() |
44 | { |
45 | return proxy_host.length != 0; |
46 | } |
47 | |
48 | Url_http(LEX_STRING &url_arg, LEX_STRING &host_arg, |
49 | LEX_STRING &port_arg, LEX_STRING &path_arg, bool ssl_arg) : |
50 | Url(url_arg), host(host_arg), port(port_arg), path(path_arg), ssl(ssl_arg) |
51 | { |
52 | proxy_host.length= 0; |
53 | } |
54 | ~Url_http() |
55 | { |
56 | my_free(host.str); |
57 | my_free(port.str); |
58 | my_free(path.str); |
59 | set_proxy(0,0); |
60 | } |
61 | |
62 | public: |
63 | int send(const char* data, size_t data_length); |
64 | int set_proxy(const char *proxy, size_t proxy_len) |
65 | { |
66 | if (use_proxy()) |
67 | { |
68 | my_free(proxy_host.str); |
69 | my_free(proxy_port.str); |
70 | } |
71 | |
72 | return parse_proxy_server(proxy, proxy_len, &proxy_host, &proxy_port); |
73 | } |
74 | |
75 | friend Url* http_create(const char *url, size_t url_length); |
76 | }; |
77 | |
78 | /** |
79 | create a Url_http object out of the url, if possible. |
80 | |
81 | @note |
82 | Arbitrary limitations here. |
83 | |
84 | The url must be http[s]://hostname[:port]/path |
85 | No username:password@ or ?script=parameters are supported. |
86 | |
87 | But it's ok. This is not a generic purpose www browser - it only needs to be |
88 | good enough to POST the data to mariadb.org. |
89 | */ |
90 | Url* http_create(const char *url, size_t url_length) |
91 | { |
92 | const char *s; |
93 | LEX_STRING full_url= {const_cast<char*>(url), url_length}; |
94 | LEX_STRING host, port, path; |
95 | bool ssl= false; |
96 | |
97 | if (is_prefix(url, "http://" )) |
98 | s= url + 7; |
99 | #ifdef HAVE_OPENSSL |
100 | else if (is_prefix(url, "https://" )) |
101 | { |
102 | ssl= true; |
103 | s= url + 8; |
104 | } |
105 | #endif |
106 | else |
107 | return NULL; |
108 | |
109 | for (url= s; *s && *s != ':' && *s != '/'; s++) /* no-op */; |
110 | host.str= const_cast<char*>(url); |
111 | host.length= s-url; |
112 | |
113 | if (*s == ':') |
114 | { |
115 | for (url= ++s; *s && *s >= '0' && *s <= '9'; s++) /* no-op */; |
116 | port.str= const_cast<char*>(url); |
117 | port.length= s-url; |
118 | } |
119 | else |
120 | { |
121 | if (ssl) |
122 | { |
123 | port.str= const_cast<char*>("443" ); |
124 | port.length=3; |
125 | } |
126 | else |
127 | { |
128 | port.str= const_cast<char*>("80" ); |
129 | port.length=2; |
130 | } |
131 | } |
132 | |
133 | if (*s == 0) |
134 | { |
135 | path.str= const_cast<char*>("/" ); |
136 | path.length= 1; |
137 | } |
138 | else |
139 | { |
140 | path.str= const_cast<char*>(s); |
141 | path.length= strlen(s); |
142 | } |
143 | if (!host.length || !port.length || path.str[0] != '/') |
144 | return NULL; |
145 | |
146 | host.str= my_strndup(host.str, host.length, MYF(MY_WME)); |
147 | port.str= my_strndup(port.str, port.length, MYF(MY_WME)); |
148 | path.str= my_strndup(path.str, path.length, MYF(MY_WME)); |
149 | |
150 | if (!host.str || !port.str || !path.str) |
151 | { |
152 | my_free(host.str); |
153 | my_free(port.str); |
154 | my_free(path.str); |
155 | return NULL; |
156 | } |
157 | |
158 | return new Url_http(full_url, host, port, path, ssl); |
159 | } |
160 | |
161 | /* do the vio_write and check that all data were sent ok */ |
162 | #define write_check(VIO, DATA, LEN) \ |
163 | (vio_write((VIO), (uchar*)(DATA), (LEN)) != (LEN)) |
164 | |
165 | int Url_http::send(const char* data, size_t data_length) |
166 | { |
167 | my_socket fd= INVALID_SOCKET; |
168 | char buf[1024]; |
169 | size_t len= 0; |
170 | |
171 | addrinfo *addrs, *addr, filter= {0, AF_UNSPEC, SOCK_STREAM, 6, 0, 0, 0, 0}; |
172 | int res= use_proxy() ? |
173 | getaddrinfo(proxy_host.str, proxy_port.str, &filter, &addrs) : |
174 | getaddrinfo(host.str, port.str, &filter, &addrs); |
175 | |
176 | if (res) |
177 | { |
178 | sql_print_error("feedback plugin: getaddrinfo() failed for url '%s': %s" , |
179 | full_url.str, gai_strerror(res)); |
180 | return 1; |
181 | } |
182 | |
183 | for (addr= addrs; addr != NULL; addr= addr->ai_next) |
184 | { |
185 | fd= socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); |
186 | if (fd == INVALID_SOCKET) |
187 | continue; |
188 | |
189 | if (connect(fd, addr->ai_addr, (int) addr->ai_addrlen) == 0) |
190 | break; |
191 | |
192 | closesocket(fd); |
193 | fd= INVALID_SOCKET; |
194 | } |
195 | |
196 | freeaddrinfo(addrs); |
197 | |
198 | if (fd == INVALID_SOCKET) |
199 | { |
200 | sql_print_error("feedback plugin: could not connect for url '%s'" , |
201 | full_url.str); |
202 | return 1; |
203 | } |
204 | |
205 | Vio *vio= vio_new(fd, VIO_TYPE_TCPIP, 0); |
206 | if (!vio) |
207 | { |
208 | sql_print_error("feedback plugin: vio_new failed for url '%s'" , |
209 | full_url.str); |
210 | closesocket(fd); |
211 | return 1; |
212 | } |
213 | |
214 | #ifdef HAVE_OPENSSL |
215 | struct st_VioSSLFd *UNINIT_VAR(ssl_fd); |
216 | if (ssl) |
217 | { |
218 | enum enum_ssl_init_error ssl_init_error= SSL_INITERR_NOERROR; |
219 | ulong ssl_error= 0; |
220 | if (!(ssl_fd= new_VioSSLConnectorFd(0, 0, 0, 0, 0, &ssl_init_error, 0, 0)) || |
221 | sslconnect(ssl_fd, vio, send_timeout, &ssl_error)) |
222 | { |
223 | const char *err; |
224 | if (ssl_init_error != SSL_INITERR_NOERROR) |
225 | err= sslGetErrString(ssl_init_error); |
226 | else |
227 | { |
228 | ERR_error_string_n(ssl_error, buf, sizeof(buf)); |
229 | buf[sizeof(buf)-1]= 0; |
230 | err= buf; |
231 | } |
232 | |
233 | sql_print_error("feedback plugin: ssl failed for url '%s' %s" , |
234 | full_url.str, err); |
235 | if (ssl_fd) |
236 | free_vio_ssl_acceptor_fd(ssl_fd); |
237 | closesocket(fd); |
238 | vio_delete(vio); |
239 | return 1; |
240 | } |
241 | } |
242 | #endif |
243 | |
244 | static const LEX_STRING boundary= |
245 | { C_STRING_WITH_LEN("----------------------------ba4f3696b39f" ) }; |
246 | static const LEX_STRING = |
247 | { C_STRING_WITH_LEN("\r\n" |
248 | "Content-Disposition: form-data; name=\"data\"; filename=\"-\"\r\n" |
249 | "Content-Type: application/octet-stream\r\n\r\n" ) |
250 | }; |
251 | |
252 | len= my_snprintf(buf, sizeof(buf), |
253 | use_proxy() ? "POST http://%s:%s/" : "POST " , |
254 | host.str, port.str); |
255 | |
256 | len+= my_snprintf(buf+len, sizeof(buf)-len, |
257 | "%s HTTP/1.0\r\n" |
258 | "User-Agent: MariaDB User Feedback Plugin\r\n" |
259 | "Host: %s:%s\r\n" |
260 | "Accept: */*\r\n" |
261 | "Content-Length: %u\r\n" |
262 | "Content-Type: multipart/form-data; boundary=%s\r\n" |
263 | "\r\n" , |
264 | path.str, host.str, port.str, |
265 | (uint)(2*boundary.length + header.length + data_length + 4), |
266 | boundary.str + 2); |
267 | |
268 | vio_timeout(vio, FOR_READING, send_timeout); |
269 | vio_timeout(vio, FOR_WRITING, send_timeout); |
270 | res = write_check(vio, buf, len) |
271 | || write_check(vio, boundary.str, boundary.length) |
272 | || write_check(vio, header.str, header.length) |
273 | || write_check(vio, data, data_length) |
274 | || write_check(vio, boundary.str, boundary.length) |
275 | || write_check(vio, "--\r\n" , 4); |
276 | |
277 | if (res) |
278 | sql_print_error("feedback plugin: failed to send report to '%s'" , |
279 | full_url.str); |
280 | else |
281 | { |
282 | sql_print_information("feedback plugin: report to '%s' was sent" , |
283 | full_url.str); |
284 | |
285 | /* |
286 | if the data were send successfully, read the reply. |
287 | Extract the first string between <h1>...</h1> tags |
288 | and put it as a server reply into the error log. |
289 | */ |
290 | len= 0; |
291 | for (;;) |
292 | { |
293 | size_t i= sizeof(buf) - len - 1; |
294 | if (i) |
295 | i= vio_read(vio, (uchar*)buf + len, i); |
296 | if ((int)i <= 0) |
297 | break; |
298 | len+= i; |
299 | } |
300 | if (len) |
301 | { |
302 | char *from; |
303 | |
304 | buf[len]= 0; // safety |
305 | |
306 | if ((from= strstr(buf, "<h1>" ))) |
307 | { |
308 | from+= 4; |
309 | char *to= strstr(from, "</h1>" ); |
310 | if (to) |
311 | *to= 0; |
312 | else |
313 | from= NULL; |
314 | } |
315 | if (from) |
316 | sql_print_information("feedback plugin: server replied '%s'" , from); |
317 | else |
318 | sql_print_warning("feedback plugin: failed to parse server reply" ); |
319 | } |
320 | else |
321 | { |
322 | res= 1; |
323 | sql_print_error("feedback plugin: failed to read server reply" ); |
324 | } |
325 | } |
326 | |
327 | vio_delete(vio); |
328 | |
329 | #ifdef HAVE_OPENSSL |
330 | if (ssl) |
331 | { |
332 | SSL_CTX_free(ssl_fd->ssl_context); |
333 | my_free(ssl_fd); |
334 | } |
335 | #endif |
336 | |
337 | return res; |
338 | } |
339 | |
340 | } // namespace feedback |
341 | |
342 | |