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