1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 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.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 * SPDX-License-Identifier: curl
22 *
23 * 'pingpong' is for generic back-and-forth support functions used by FTP,
24 * IMAP, POP3, SMTP and whatever more that likes them.
25 *
26 ***************************************************************************/
27
28 #include "curl_setup.h"
29
30 #include "urldata.h"
31 #include "cfilters.h"
32 #include "sendf.h"
33 #include "select.h"
34 #include "progress.h"
35 #include "speedcheck.h"
36 #include "pingpong.h"
37 #include "multiif.h"
38 #include "vtls/vtls.h"
39 #include "strdup.h"
40
41 /* The last 3 #include files should be in this order */
42 #include "curl_printf.h"
43 #include "curl_memory.h"
44 #include "memdebug.h"
45
46 #ifdef USE_PINGPONG
47
48 /* Returns timeout in ms. 0 or negative number means the timeout has already
49 triggered */
Curl_pp_state_timeout(struct Curl_easy * data,struct pingpong * pp,bool disconnecting)50 timediff_t Curl_pp_state_timeout(struct Curl_easy *data,
51 struct pingpong *pp, bool disconnecting)
52 {
53 struct connectdata *conn = data->conn;
54 timediff_t timeout_ms; /* in milliseconds */
55 timediff_t response_time = (data->set.server_response_timeout) ?
56 data->set.server_response_timeout : pp->response_time;
57
58 /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine
59 remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is
60 supposed to govern the response for any given server response, not for
61 the time from connect to the given server response. */
62
63 /* Without a requested timeout, we only wait 'response_time' seconds for the
64 full response to arrive before we bail out */
65 timeout_ms = response_time -
66 Curl_timediff(Curl_now(), pp->response); /* spent time */
67
68 if(data->set.timeout && !disconnecting) {
69 /* if timeout is requested, find out how much remaining time we have */
70 timediff_t timeout2_ms = data->set.timeout - /* timeout time */
71 Curl_timediff(Curl_now(), conn->now); /* spent time */
72
73 /* pick the lowest number */
74 timeout_ms = CURLMIN(timeout_ms, timeout2_ms);
75 }
76
77 return timeout_ms;
78 }
79
80 /*
81 * Curl_pp_statemach()
82 */
Curl_pp_statemach(struct Curl_easy * data,struct pingpong * pp,bool block,bool disconnecting)83 CURLcode Curl_pp_statemach(struct Curl_easy *data,
84 struct pingpong *pp, bool block,
85 bool disconnecting)
86 {
87 struct connectdata *conn = data->conn;
88 curl_socket_t sock = conn->sock[FIRSTSOCKET];
89 int rc;
90 timediff_t interval_ms;
91 timediff_t timeout_ms = Curl_pp_state_timeout(data, pp, disconnecting);
92 CURLcode result = CURLE_OK;
93
94 if(timeout_ms <= 0) {
95 failf(data, "server response timeout");
96 return CURLE_OPERATION_TIMEDOUT; /* already too little time */
97 }
98
99 if(block) {
100 interval_ms = 1000; /* use 1 second timeout intervals */
101 if(timeout_ms < interval_ms)
102 interval_ms = timeout_ms;
103 }
104 else
105 interval_ms = 0; /* immediate */
106
107 if(Curl_conn_data_pending(data, FIRSTSOCKET))
108 rc = 1;
109 else if(pp->overflow)
110 /* We are receiving and there is data in the cache so just read it */
111 rc = 1;
112 else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET))
113 /* We are receiving and there is data ready in the SSL library */
114 rc = 1;
115 else
116 rc = Curl_socket_check(pp->sendleft ? CURL_SOCKET_BAD : sock, /* reading */
117 CURL_SOCKET_BAD,
118 pp->sendleft ? sock : CURL_SOCKET_BAD, /* writing */
119 interval_ms);
120
121 if(block) {
122 /* if we did not wait, we do not have to spend time on this now */
123 if(Curl_pgrsUpdate(data))
124 result = CURLE_ABORTED_BY_CALLBACK;
125 else
126 result = Curl_speedcheck(data, Curl_now());
127
128 if(result)
129 return result;
130 }
131
132 if(rc == -1) {
133 failf(data, "select/poll error");
134 result = CURLE_OUT_OF_MEMORY;
135 }
136 else if(rc)
137 result = pp->statemachine(data, data->conn);
138
139 return result;
140 }
141
142 /* initialize stuff to prepare for reading a fresh new response */
Curl_pp_init(struct pingpong * pp)143 void Curl_pp_init(struct pingpong *pp)
144 {
145 pp->nread_resp = 0;
146 pp->response = Curl_now(); /* start response time-out now! */
147 pp->pending_resp = TRUE;
148 Curl_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD);
149 Curl_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD);
150 }
151
152 /***********************************************************************
153 *
154 * Curl_pp_vsendf()
155 *
156 * Send the formatted string as a command to a pingpong server. Note that
157 * the string should not have any CRLF appended, as this function will
158 * append the necessary things itself.
159 *
160 * made to never block
161 */
Curl_pp_vsendf(struct Curl_easy * data,struct pingpong * pp,const char * fmt,va_list args)162 CURLcode Curl_pp_vsendf(struct Curl_easy *data,
163 struct pingpong *pp,
164 const char *fmt,
165 va_list args)
166 {
167 size_t bytes_written = 0;
168 size_t write_len;
169 char *s;
170 CURLcode result;
171 struct connectdata *conn = data->conn;
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 /* cannot send without a connection! */
183 return CURLE_SEND_ERROR;
184
185 Curl_dyn_reset(&pp->sendbuf);
186 result = Curl_dyn_vaddf(&pp->sendbuf, fmt, args);
187 if(result)
188 return result;
189
190 /* append CRLF */
191 result = Curl_dyn_addn(&pp->sendbuf, "\r\n", 2);
192 if(result)
193 return result;
194
195 pp->pending_resp = TRUE;
196 write_len = Curl_dyn_len(&pp->sendbuf);
197 s = Curl_dyn_ptr(&pp->sendbuf);
198
199 #ifdef HAVE_GSSAPI
200 conn->data_prot = PROT_CMD;
201 #endif
202 result = Curl_conn_send(data, FIRSTSOCKET, s, write_len, FALSE,
203 &bytes_written);
204 if(result == CURLE_AGAIN) {
205 bytes_written = 0;
206 }
207 else if(result)
208 return result;
209 #ifdef HAVE_GSSAPI
210 data_sec = conn->data_prot;
211 DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
212 conn->data_prot = (unsigned char)data_sec;
213 #endif
214
215 Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written);
216
217 if(bytes_written != write_len) {
218 /* the whole chunk was not sent, keep it around and adjust sizes */
219 pp->sendthis = s;
220 pp->sendsize = write_len;
221 pp->sendleft = write_len - bytes_written;
222 }
223 else {
224 pp->sendthis = NULL;
225 pp->sendleft = pp->sendsize = 0;
226 pp->response = Curl_now();
227 }
228
229 return CURLE_OK;
230 }
231
232
233 /***********************************************************************
234 *
235 * Curl_pp_sendf()
236 *
237 * Send the formatted string as a command to a pingpong server. Note that
238 * the string should not have any CRLF appended, as this function will
239 * append the necessary things itself.
240 *
241 * made to never block
242 */
Curl_pp_sendf(struct Curl_easy * data,struct pingpong * pp,const char * fmt,...)243 CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp,
244 const char *fmt, ...)
245 {
246 CURLcode result;
247 va_list ap;
248 va_start(ap, fmt);
249
250 result = Curl_pp_vsendf(data, pp, fmt, ap);
251
252 va_end(ap);
253
254 return result;
255 }
256
pingpong_read(struct Curl_easy * data,int sockindex,char * buffer,size_t buflen,ssize_t * nread)257 static CURLcode pingpong_read(struct Curl_easy *data,
258 int sockindex,
259 char *buffer,
260 size_t buflen,
261 ssize_t *nread)
262 {
263 CURLcode result;
264 #ifdef HAVE_GSSAPI
265 enum protection_level prot = data->conn->data_prot;
266 data->conn->data_prot = PROT_CLEAR;
267 #endif
268 result = Curl_conn_recv(data, sockindex, buffer, buflen, nread);
269 #ifdef HAVE_GSSAPI
270 DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST);
271 data->conn->data_prot = (unsigned char)prot;
272 #endif
273 return result;
274 }
275
276 /*
277 * Curl_pp_readresp()
278 *
279 * Reads a piece of a server response.
280 */
Curl_pp_readresp(struct Curl_easy * data,int sockindex,struct pingpong * pp,int * code,size_t * size)281 CURLcode Curl_pp_readresp(struct Curl_easy *data,
282 int sockindex,
283 struct pingpong *pp,
284 int *code, /* return the server code if done */
285 size_t *size) /* size of the response */
286 {
287 struct connectdata *conn = data->conn;
288 CURLcode result = CURLE_OK;
289 ssize_t gotbytes;
290 char buffer[900];
291
292 *code = 0; /* 0 for errors or not done */
293 *size = 0;
294
295 do {
296 gotbytes = 0;
297 if(pp->nfinal) {
298 /* a previous call left this many bytes in the beginning of the buffer as
299 that was the final line; now ditch that */
300 size_t full = Curl_dyn_len(&pp->recvbuf);
301
302 /* trim off the "final" leading part */
303 Curl_dyn_tail(&pp->recvbuf, full - pp->nfinal);
304
305 pp->nfinal = 0; /* now gone */
306 }
307 if(!pp->overflow) {
308 result = pingpong_read(data, sockindex, buffer, sizeof(buffer),
309 &gotbytes);
310 if(result == CURLE_AGAIN)
311 return CURLE_OK;
312
313 if(result)
314 return result;
315
316 if(gotbytes <= 0) {
317 failf(data, "response reading failed (errno: %d)", SOCKERRNO);
318 return CURLE_RECV_ERROR;
319 }
320
321 result = Curl_dyn_addn(&pp->recvbuf, buffer, gotbytes);
322 if(result)
323 return result;
324
325 data->req.headerbytecount += (unsigned int)gotbytes;
326
327 pp->nread_resp += gotbytes;
328 }
329
330 do {
331 char *line = Curl_dyn_ptr(&pp->recvbuf);
332 char *nl = memchr(line, '\n', Curl_dyn_len(&pp->recvbuf));
333 if(nl) {
334 /* a newline is CRLF in pp-talk, so the CR is ignored as
335 the line is not really terminated until the LF comes */
336 size_t length = nl - line + 1;
337
338 /* output debug output if that is requested */
339 #ifdef HAVE_GSSAPI
340 if(!conn->sec_complete)
341 #endif
342 Curl_debug(data, CURLINFO_HEADER_IN, line, length);
343
344 /*
345 * Pass all response-lines to the callback function registered for
346 * "headers". The response lines can be seen as a kind of headers.
347 */
348 result = Curl_client_write(data, CLIENTWRITE_INFO, line, length);
349 if(result)
350 return result;
351
352 if(pp->endofresp(data, conn, line, length, code)) {
353 /* When at "end of response", keep the endofresp line first in the
354 buffer since it will be accessed outside (by pingpong
355 parsers). Store the overflow counter to inform about additional
356 data in this buffer after the endofresp line. */
357 pp->nfinal = length;
358 if(Curl_dyn_len(&pp->recvbuf) > length)
359 pp->overflow = Curl_dyn_len(&pp->recvbuf) - length;
360 else
361 pp->overflow = 0;
362 *size = pp->nread_resp; /* size of the response */
363 pp->nread_resp = 0; /* restart */
364 gotbytes = 0; /* force break out of outer loop */
365 break;
366 }
367 if(Curl_dyn_len(&pp->recvbuf) > length)
368 /* keep the remaining piece */
369 Curl_dyn_tail((&pp->recvbuf), Curl_dyn_len(&pp->recvbuf) - length);
370 else
371 Curl_dyn_reset(&pp->recvbuf);
372 }
373 else {
374 /* without a newline, there is no overflow */
375 pp->overflow = 0;
376 break;
377 }
378
379 } while(1); /* while there is buffer left to scan */
380
381 } while(gotbytes == sizeof(buffer));
382
383 pp->pending_resp = FALSE;
384
385 return result;
386 }
387
Curl_pp_getsock(struct Curl_easy * data,struct pingpong * pp,curl_socket_t * socks)388 int Curl_pp_getsock(struct Curl_easy *data,
389 struct pingpong *pp, curl_socket_t *socks)
390 {
391 struct connectdata *conn = data->conn;
392 socks[0] = conn->sock[FIRSTSOCKET];
393
394 if(pp->sendleft) {
395 /* write mode */
396 return GETSOCK_WRITESOCK(0);
397 }
398
399 /* read mode */
400 return GETSOCK_READSOCK(0);
401 }
402
Curl_pp_needs_flush(struct Curl_easy * data,struct pingpong * pp)403 bool Curl_pp_needs_flush(struct Curl_easy *data,
404 struct pingpong *pp)
405 {
406 (void)data;
407 return pp->sendleft > 0;
408 }
409
Curl_pp_flushsend(struct Curl_easy * data,struct pingpong * pp)410 CURLcode Curl_pp_flushsend(struct Curl_easy *data,
411 struct pingpong *pp)
412 {
413 /* we have a piece of a command still left to send */
414 size_t written;
415 CURLcode result;
416
417 if(!Curl_pp_needs_flush(data, pp))
418 return CURLE_OK;
419
420 result = Curl_conn_send(data, FIRSTSOCKET,
421 pp->sendthis + pp->sendsize - pp->sendleft,
422 pp->sendleft, FALSE, &written);
423 if(result == CURLE_AGAIN) {
424 result = CURLE_OK;
425 written = 0;
426 }
427 if(result)
428 return result;
429
430 if(written != pp->sendleft) {
431 /* only a fraction was sent */
432 pp->sendleft -= written;
433 }
434 else {
435 pp->sendthis = NULL;
436 pp->sendleft = pp->sendsize = 0;
437 pp->response = Curl_now();
438 }
439 return CURLE_OK;
440 }
441
Curl_pp_disconnect(struct pingpong * pp)442 CURLcode Curl_pp_disconnect(struct pingpong *pp)
443 {
444 Curl_dyn_free(&pp->sendbuf);
445 Curl_dyn_free(&pp->recvbuf);
446 return CURLE_OK;
447 }
448
Curl_pp_moredata(struct pingpong * pp)449 bool Curl_pp_moredata(struct pingpong *pp)
450 {
451 return (!pp->sendleft && Curl_dyn_len(&pp->recvbuf) > pp->nfinal);
452 }
453
454 #endif
455