1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.haxx.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * 'pingpong' is for generic back-and-forth support functions used by FTP,
22 * IMAP, POP3, SMTP and whatever more that likes them.
23 *
24 ***************************************************************************/
25
26#include "curl_setup.h"
27
28#include "urldata.h"
29#include "sendf.h"
30#include "select.h"
31#include "progress.h"
32#include "speedcheck.h"
33#include "pingpong.h"
34#include "multiif.h"
35#include "non-ascii.h"
36#include "vtls/vtls.h"
37
38/* The last 3 #include files should be in this order */
39#include "curl_printf.h"
40#include "curl_memory.h"
41#include "memdebug.h"
42
43#ifdef USE_PINGPONG
44
45/* Returns timeout in ms. 0 or negative number means the timeout has already
46 triggered */
47time_t Curl_pp_state_timeout(struct pingpong *pp, bool disconnecting)
48{
49 struct connectdata *conn = pp->conn;
50 struct Curl_easy *data = conn->data;
51 time_t timeout_ms; /* in milliseconds */
52 long response_time = (data->set.server_response_timeout)?
53 data->set.server_response_timeout: pp->response_time;
54
55 /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine
56 remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is
57 supposed to govern the response for any given server response, not for
58 the time from connect to the given server response. */
59
60 /* Without a requested timeout, we only wait 'response_time' seconds for the
61 full response to arrive before we bail out */
62 timeout_ms = response_time -
63 (time_t)Curl_timediff(Curl_now(), pp->response); /* spent time */
64
65 if(data->set.timeout && !disconnecting) {
66 /* if timeout is requested, find out how much remaining time we have */
67 time_t timeout2_ms = data->set.timeout - /* timeout time */
68 (time_t)Curl_timediff(Curl_now(), conn->now); /* spent time */
69
70 /* pick the lowest number */
71 timeout_ms = CURLMIN(timeout_ms, timeout2_ms);
72 }
73
74 return timeout_ms;
75}
76
77/*
78 * Curl_pp_statemach()
79 */
80CURLcode Curl_pp_statemach(struct pingpong *pp, bool block,
81 bool disconnecting)
82{
83 struct connectdata *conn = pp->conn;
84 curl_socket_t sock = conn->sock[FIRSTSOCKET];
85 int rc;
86 time_t interval_ms;
87 time_t timeout_ms = Curl_pp_state_timeout(pp, disconnecting);
88 struct Curl_easy *data = conn->data;
89 CURLcode result = CURLE_OK;
90
91 if(timeout_ms <= 0) {
92 failf(data, "server response timeout");
93 return CURLE_OPERATION_TIMEDOUT; /* already too little time */
94 }
95
96 if(block) {
97 interval_ms = 1000; /* use 1 second timeout intervals */
98 if(timeout_ms < interval_ms)
99 interval_ms = timeout_ms;
100 }
101 else
102 interval_ms = 0; /* immediate */
103
104 if(Curl_ssl_data_pending(conn, FIRSTSOCKET))
105 rc = 1;
106 else if(Curl_pp_moredata(pp))
107 /* We are receiving and there is data in the cache so just read it */
108 rc = 1;
109 else if(!pp->sendleft && Curl_ssl_data_pending(conn, FIRSTSOCKET))
110 /* We are receiving and there is data ready in the SSL library */
111 rc = 1;
112 else
113 rc = Curl_socket_check(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
114 CURL_SOCKET_BAD,
115 pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
116 interval_ms);
117
118 if(block) {
119 /* if we didn't wait, we don't have to spend time on this now */
120 if(Curl_pgrsUpdate(conn))
121 result = CURLE_ABORTED_BY_CALLBACK;
122 else
123 result = Curl_speedcheck(data, Curl_now());
124
125 if(result)
126 return result;
127 }
128
129 if(rc == -1) {
130 failf(data, "select/poll error");
131 result = CURLE_OUT_OF_MEMORY;
132 }
133 else if(rc)
134 result = pp->statemach_act(conn);
135
136 return result;
137}
138
139/* initialize stuff to prepare for reading a fresh new response */
140void Curl_pp_init(struct pingpong *pp)
141{
142 struct connectdata *conn = pp->conn;
143 pp->nread_resp = 0;
144 pp->linestart_resp = conn->data->state.buffer;
145 pp->pending_resp = TRUE;
146 pp->response = Curl_now(); /* start response time-out now! */
147}
148
149
150
151/***********************************************************************
152 *
153 * Curl_pp_vsendf()
154 *
155 * Send the formatted string as a command to a pingpong server. Note that
156 * the string should not have any CRLF appended, as this function will
157 * append the necessary things itself.
158 *
159 * made to never block
160 */
161CURLcode Curl_pp_vsendf(struct pingpong *pp,
162 const char *fmt,
163 va_list args)
164{
165 ssize_t bytes_written;
166 size_t write_len;
167 char *fmt_crlf;
168 char *s;
169 CURLcode result;
170 struct connectdata *conn = pp->conn;
171 struct Curl_easy *data;
172
173#ifdef HAVE_GSSAPI
174 enum protection_level data_sec;
175#endif
176
177 DEBUGASSERT(pp->sendleft == 0);
178 DEBUGASSERT(pp->sendsize == 0);
179 DEBUGASSERT(pp->sendthis == NULL);
180
181 if(!conn)
182 /* can't send without a connection! */
183 return CURLE_SEND_ERROR;
184
185 data = conn->data;
186
187 fmt_crlf = aprintf("%s\r\n", fmt); /* append a trailing CRLF */
188 if(!fmt_crlf)
189 return CURLE_OUT_OF_MEMORY;
190
191 s = vaprintf(fmt_crlf, args); /* trailing CRLF appended */
192 free(fmt_crlf);
193 if(!s)
194 return CURLE_OUT_OF_MEMORY;
195
196 bytes_written = 0;
197 write_len = strlen(s);
198
199 Curl_pp_init(pp);
200
201 result = Curl_convert_to_network(data, s, write_len);
202 /* Curl_convert_to_network calls failf if unsuccessful */
203 if(result) {
204 free(s);
205 return result;
206 }
207
208#ifdef HAVE_GSSAPI
209 conn->data_prot = PROT_CMD;
210#endif
211 result = Curl_write(conn, conn->sock[FIRSTSOCKET], s, write_len,
212 &bytes_written);
213#ifdef HAVE_GSSAPI
214 data_sec = conn->data_prot;
215 DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
216 conn->data_prot = data_sec;
217#endif
218
219 if(result) {
220 free(s);
221 return result;
222 }
223
224 if(conn->data->set.verbose)
225 Curl_debug(conn->data, CURLINFO_HEADER_OUT, s, (size_t)bytes_written);
226
227 if(bytes_written != (ssize_t)write_len) {
228 /* the whole chunk was not sent, keep it around and adjust sizes */
229 pp->sendthis = s;
230 pp->sendsize = write_len;
231 pp->sendleft = write_len - bytes_written;
232 }
233 else {
234 free(s);
235 pp->sendthis = NULL;
236 pp->sendleft = pp->sendsize = 0;
237 pp->response = Curl_now();
238 }
239
240 return CURLE_OK;
241}
242
243
244/***********************************************************************
245 *
246 * Curl_pp_sendf()
247 *
248 * Send the formatted string as a command to a pingpong server. Note that
249 * the string should not have any CRLF appended, as this function will
250 * append the necessary things itself.
251 *
252 * made to never block
253 */
254CURLcode Curl_pp_sendf(struct pingpong *pp,
255 const char *fmt, ...)
256{
257 CURLcode result;
258 va_list ap;
259 va_start(ap, fmt);
260
261 result = Curl_pp_vsendf(pp, fmt, ap);
262
263 va_end(ap);
264
265 return result;
266}
267
268/*
269 * Curl_pp_readresp()
270 *
271 * Reads a piece of a server response.
272 */
273CURLcode Curl_pp_readresp(curl_socket_t sockfd,
274 struct pingpong *pp,
275 int *code, /* return the server code if done */
276 size_t *size) /* size of the response */
277{
278 ssize_t perline; /* count bytes per line */
279 bool keepon = TRUE;
280 ssize_t gotbytes;
281 char *ptr;
282 struct connectdata *conn = pp->conn;
283 struct Curl_easy *data = conn->data;
284 char * const buf = data->state.buffer;
285 CURLcode result = CURLE_OK;
286
287 *code = 0; /* 0 for errors or not done */
288 *size = 0;
289
290 ptr = buf + pp->nread_resp;
291
292 /* number of bytes in the current line, so far */
293 perline = (ssize_t)(ptr-pp->linestart_resp);
294
295 while((pp->nread_resp < (size_t)data->set.buffer_size) &&
296 (keepon && !result)) {
297
298 if(pp->cache) {
299 /* we had data in the "cache", copy that instead of doing an actual
300 * read
301 *
302 * pp->cache_size is cast to ssize_t here. This should be safe, because
303 * it would have been populated with something of size int to begin
304 * with, even though its datatype may be larger than an int.
305 */
306 if((ptr + pp->cache_size) > (buf + data->set.buffer_size + 1)) {
307 failf(data, "cached response data too big to handle");
308 return CURLE_RECV_ERROR;
309 }
310 memcpy(ptr, pp->cache, pp->cache_size);
311 gotbytes = (ssize_t)pp->cache_size;
312 free(pp->cache); /* free the cache */
313 pp->cache = NULL; /* clear the pointer */
314 pp->cache_size = 0; /* zero the size just in case */
315 }
316 else {
317#ifdef HAVE_GSSAPI
318 enum protection_level prot = conn->data_prot;
319 conn->data_prot = PROT_CLEAR;
320#endif
321 DEBUGASSERT((ptr + data->set.buffer_size - pp->nread_resp) <=
322 (buf + data->set.buffer_size + 1));
323 result = Curl_read(conn, sockfd, ptr,
324 data->set.buffer_size - pp->nread_resp,
325 &gotbytes);
326#ifdef HAVE_GSSAPI
327 DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST);
328 conn->data_prot = prot;
329#endif
330 if(result == CURLE_AGAIN)
331 return CURLE_OK; /* return */
332
333 if(!result && (gotbytes > 0))
334 /* convert from the network encoding */
335 result = Curl_convert_from_network(data, ptr, gotbytes);
336 /* Curl_convert_from_network calls failf if unsuccessful */
337
338 if(result)
339 /* Set outer result variable to this error. */
340 keepon = FALSE;
341 }
342
343 if(!keepon)
344 ;
345 else if(gotbytes <= 0) {
346 keepon = FALSE;
347 result = CURLE_RECV_ERROR;
348 failf(data, "response reading failed");
349 }
350 else {
351 /* we got a whole chunk of data, which can be anything from one
352 * byte to a set of lines and possible just a piece of the last
353 * line */
354 ssize_t i;
355 ssize_t clipamount = 0;
356 bool restart = FALSE;
357
358 data->req.headerbytecount += (long)gotbytes;
359
360 pp->nread_resp += gotbytes;
361 for(i = 0; i < gotbytes; ptr++, i++) {
362 perline++;
363 if(*ptr == '\n') {
364 /* a newline is CRLF in pp-talk, so the CR is ignored as
365 the line isn't really terminated until the LF comes */
366
367 /* output debug output if that is requested */
368#ifdef HAVE_GSSAPI
369 if(!conn->sec_complete)
370#endif
371 if(data->set.verbose)
372 Curl_debug(data, CURLINFO_HEADER_IN,
373 pp->linestart_resp, (size_t)perline);
374
375 /*
376 * We pass all response-lines to the callback function registered
377 * for "headers". The response lines can be seen as a kind of
378 * headers.
379 */
380 result = Curl_client_write(conn, CLIENTWRITE_HEADER,
381 pp->linestart_resp, perline);
382 if(result)
383 return result;
384
385 if(pp->endofresp(conn, pp->linestart_resp, perline, code)) {
386 /* This is the end of the last line, copy the last line to the
387 start of the buffer and zero terminate, for old times sake */
388 size_t n = ptr - pp->linestart_resp;
389 memmove(buf, pp->linestart_resp, n);
390 buf[n] = 0; /* zero terminate */
391 keepon = FALSE;
392 pp->linestart_resp = ptr + 1; /* advance pointer */
393 i++; /* skip this before getting out */
394
395 *size = pp->nread_resp; /* size of the response */
396 pp->nread_resp = 0; /* restart */
397 break;
398 }
399 perline = 0; /* line starts over here */
400 pp->linestart_resp = ptr + 1;
401 }
402 }
403
404 if(!keepon && (i != gotbytes)) {
405 /* We found the end of the response lines, but we didn't parse the
406 full chunk of data we have read from the server. We therefore need
407 to store the rest of the data to be checked on the next invoke as
408 it may actually contain another end of response already! */
409 clipamount = gotbytes - i;
410 restart = TRUE;
411 DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing "
412 "server response left\n",
413 (int)clipamount));
414 }
415 else if(keepon) {
416
417 if((perline == gotbytes) && (gotbytes > data->set.buffer_size/2)) {
418 /* We got an excessive line without newlines and we need to deal
419 with it. We keep the first bytes of the line then we throw
420 away the rest. */
421 infof(data, "Excessive server response line length received, "
422 "%zd bytes. Stripping\n", gotbytes);
423 restart = TRUE;
424
425 /* we keep 40 bytes since all our pingpong protocols are only
426 interested in the first piece */
427 clipamount = 40;
428 }
429 else if(pp->nread_resp > (size_t)data->set.buffer_size/2) {
430 /* We got a large chunk of data and there's potentially still
431 trailing data to take care of, so we put any such part in the
432 "cache", clear the buffer to make space and restart. */
433 clipamount = perline;
434 restart = TRUE;
435 }
436 }
437 else if(i == gotbytes)
438 restart = TRUE;
439
440 if(clipamount) {
441 pp->cache_size = clipamount;
442 pp->cache = malloc(pp->cache_size);
443 if(pp->cache)
444 memcpy(pp->cache, pp->linestart_resp, pp->cache_size);
445 else
446 return CURLE_OUT_OF_MEMORY;
447 }
448 if(restart) {
449 /* now reset a few variables to start over nicely from the start of
450 the big buffer */
451 pp->nread_resp = 0; /* start over from scratch in the buffer */
452 ptr = pp->linestart_resp = buf;
453 perline = 0;
454 }
455
456 } /* there was data */
457
458 } /* while there's buffer left and loop is requested */
459
460 pp->pending_resp = FALSE;
461
462 return result;
463}
464
465int Curl_pp_getsock(struct pingpong *pp,
466 curl_socket_t *socks)
467{
468 struct connectdata *conn = pp->conn;
469 socks[0] = conn->sock[FIRSTSOCKET];
470
471 if(pp->sendleft) {
472 /* write mode */
473 return GETSOCK_WRITESOCK(0);
474 }
475
476 /* read mode */
477 return GETSOCK_READSOCK(0);
478}
479
480CURLcode Curl_pp_flushsend(struct pingpong *pp)
481{
482 /* we have a piece of a command still left to send */
483 struct connectdata *conn = pp->conn;
484 ssize_t written;
485 curl_socket_t sock = conn->sock[FIRSTSOCKET];
486 CURLcode result = Curl_write(conn, sock, pp->sendthis + pp->sendsize -
487 pp->sendleft, pp->sendleft, &written);
488 if(result)
489 return result;
490
491 if(written != (ssize_t)pp->sendleft) {
492 /* only a fraction was sent */
493 pp->sendleft -= written;
494 }
495 else {
496 free(pp->sendthis);
497 pp->sendthis = NULL;
498 pp->sendleft = pp->sendsize = 0;
499 pp->response = Curl_now();
500 }
501 return CURLE_OK;
502}
503
504CURLcode Curl_pp_disconnect(struct pingpong *pp)
505{
506 free(pp->cache);
507 pp->cache = NULL;
508 return CURLE_OK;
509}
510
511bool Curl_pp_moredata(struct pingpong *pp)
512{
513 return (!pp->sendleft && pp->cache && pp->nread_resp < pp->cache_size) ?
514 TRUE : FALSE;
515}
516
517#endif
518