• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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