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 didn't wait, we don't 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 ssize_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 /* can't 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_nwrite(data, FIRSTSOCKET, s, write_len, &bytes_written);
203 if(result)
204 return result;
205 #ifdef HAVE_GSSAPI
206 data_sec = conn->data_prot;
207 DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
208 conn->data_prot = (unsigned char)data_sec;
209 #endif
210
211 Curl_debug(data, CURLINFO_HEADER_OUT, s, (size_t)bytes_written);
212
213 if(bytes_written != (ssize_t)write_len) {
214 /* the whole chunk was not sent, keep it around and adjust sizes */
215 pp->sendthis = s;
216 pp->sendsize = write_len;
217 pp->sendleft = write_len - bytes_written;
218 }
219 else {
220 pp->sendthis = NULL;
221 pp->sendleft = pp->sendsize = 0;
222 pp->response = Curl_now();
223 }
224
225 return CURLE_OK;
226 }
227
228
229 /***********************************************************************
230 *
231 * Curl_pp_sendf()
232 *
233 * Send the formatted string as a command to a pingpong server. Note that
234 * the string should not have any CRLF appended, as this function will
235 * append the necessary things itself.
236 *
237 * made to never block
238 */
Curl_pp_sendf(struct Curl_easy * data,struct pingpong * pp,const char * fmt,...)239 CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp,
240 const char *fmt, ...)
241 {
242 CURLcode result;
243 va_list ap;
244 va_start(ap, fmt);
245
246 result = Curl_pp_vsendf(data, pp, fmt, ap);
247
248 va_end(ap);
249
250 return result;
251 }
252
pingpong_read(struct Curl_easy * data,curl_socket_t sockfd,char * buffer,size_t buflen,ssize_t * nread)253 static CURLcode pingpong_read(struct Curl_easy *data,
254 curl_socket_t sockfd,
255 char *buffer,
256 size_t buflen,
257 ssize_t *nread)
258 {
259 CURLcode result;
260 #ifdef HAVE_GSSAPI
261 enum protection_level prot = data->conn->data_prot;
262 data->conn->data_prot = PROT_CLEAR;
263 #endif
264 result = Curl_read(data, sockfd, buffer, buflen, nread);
265 #ifdef HAVE_GSSAPI
266 DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST);
267 data->conn->data_prot = (unsigned char)prot;
268 #endif
269 return result;
270 }
271
272 /*
273 * Curl_pp_readresp()
274 *
275 * Reads a piece of a server response.
276 */
Curl_pp_readresp(struct Curl_easy * data,curl_socket_t sockfd,struct pingpong * pp,int * code,size_t * size)277 CURLcode Curl_pp_readresp(struct Curl_easy *data,
278 curl_socket_t sockfd,
279 struct pingpong *pp,
280 int *code, /* return the server code if done */
281 size_t *size) /* size of the response */
282 {
283 struct connectdata *conn = data->conn;
284 CURLcode result = CURLE_OK;
285
286 *code = 0; /* 0 for errors or not done */
287 *size = 0;
288
289 if(pp->nfinal) {
290 /* a previous call left this many bytes in the beginning of the buffer as
291 that was the final line; now ditch that */
292 size_t full = Curl_dyn_len(&pp->recvbuf);
293
294 /* trim off the "final" leading part */
295 Curl_dyn_tail(&pp->recvbuf, full - pp->nfinal);
296
297 pp->nfinal = 0; /* now gone */
298 }
299 if(!pp->overflow) {
300 ssize_t gotbytes = 0;
301 char buffer[900];
302
303 result = pingpong_read(data, sockfd, buffer, sizeof(buffer), &gotbytes);
304 if(result == CURLE_AGAIN)
305 return CURLE_OK;
306
307 if(result)
308 return result;
309
310 if(gotbytes <= 0) {
311 failf(data, "response reading failed (errno: %d)", SOCKERRNO);
312 return CURLE_RECV_ERROR;
313 }
314
315 result = Curl_dyn_addn(&pp->recvbuf, buffer, gotbytes);
316 if(result)
317 return result;
318
319 data->req.headerbytecount += (unsigned int)gotbytes;
320
321 pp->nread_resp += gotbytes;
322 }
323
324 do {
325 char *line = Curl_dyn_ptr(&pp->recvbuf);
326 char *nl = memchr(line, '\n', Curl_dyn_len(&pp->recvbuf));
327 if(nl) {
328 /* a newline is CRLF in pp-talk, so the CR is ignored as
329 the line isn't really terminated until the LF comes */
330 size_t length = nl - line + 1;
331
332 /* output debug output if that is requested */
333 #ifdef HAVE_GSSAPI
334 if(!conn->sec_complete)
335 #endif
336 Curl_debug(data, CURLINFO_HEADER_IN, line, length);
337
338 /*
339 * Pass all response-lines to the callback function registered for
340 * "headers". The response lines can be seen as a kind of headers.
341 */
342 result = Curl_client_write(data, CLIENTWRITE_INFO, line, length);
343 if(result)
344 return result;
345
346 if(pp->endofresp(data, conn, line, length, code)) {
347 /* When at "end of response", keep the endofresp line first in the
348 buffer since it will be accessed outside (by pingpong
349 parsers). Store the overflow counter to inform about additional
350 data in this buffer after the endofresp line. */
351 pp->nfinal = length;
352 if(Curl_dyn_len(&pp->recvbuf) > length)
353 pp->overflow = Curl_dyn_len(&pp->recvbuf) - length;
354 else
355 pp->overflow = 0;
356 *size = pp->nread_resp; /* size of the response */
357 pp->nread_resp = 0; /* restart */
358 break;
359 }
360 if(Curl_dyn_len(&pp->recvbuf) > length)
361 /* keep the remaining piece */
362 Curl_dyn_tail((&pp->recvbuf), Curl_dyn_len(&pp->recvbuf) - length);
363 else
364 Curl_dyn_reset(&pp->recvbuf);
365 }
366 else {
367 /* without a newline, there is no overflow */
368 pp->overflow = 0;
369 break;
370 }
371
372 } while(1); /* while there's buffer left to scan */
373
374 pp->pending_resp = FALSE;
375
376 return result;
377 }
378
Curl_pp_getsock(struct Curl_easy * data,struct pingpong * pp,curl_socket_t * socks)379 int Curl_pp_getsock(struct Curl_easy *data,
380 struct pingpong *pp, curl_socket_t *socks)
381 {
382 struct connectdata *conn = data->conn;
383 socks[0] = conn->sock[FIRSTSOCKET];
384
385 if(pp->sendleft) {
386 /* write mode */
387 return GETSOCK_WRITESOCK(0);
388 }
389
390 /* read mode */
391 return GETSOCK_READSOCK(0);
392 }
393
Curl_pp_flushsend(struct Curl_easy * data,struct pingpong * pp)394 CURLcode Curl_pp_flushsend(struct Curl_easy *data,
395 struct pingpong *pp)
396 {
397 /* we have a piece of a command still left to send */
398 ssize_t written;
399 CURLcode result = Curl_nwrite(data, FIRSTSOCKET,
400 pp->sendthis + pp->sendsize - pp->sendleft,
401 pp->sendleft, &written);
402 if(result)
403 return result;
404
405 if(written != (ssize_t)pp->sendleft) {
406 /* only a fraction was sent */
407 pp->sendleft -= written;
408 }
409 else {
410 pp->sendthis = NULL;
411 pp->sendleft = pp->sendsize = 0;
412 pp->response = Curl_now();
413 }
414 return CURLE_OK;
415 }
416
Curl_pp_disconnect(struct pingpong * pp)417 CURLcode Curl_pp_disconnect(struct pingpong *pp)
418 {
419 Curl_dyn_free(&pp->sendbuf);
420 Curl_dyn_free(&pp->recvbuf);
421 return CURLE_OK;
422 }
423
Curl_pp_moredata(struct pingpong * pp)424 bool Curl_pp_moredata(struct pingpong *pp)
425 {
426 return (!pp->sendleft && Curl_dyn_len(&pp->recvbuf));
427 }
428
429 #endif
430