xref: /curl/lib/pingpong.c (revision fbf5d507)
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