1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2013, 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 http://curl.haxx.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 *   'pingpong' is for generic back-and-forth support functions used by FTP,
22 *   IMAP, POP3, SMTP and whatever more that likes them.
23 *
24 ***************************************************************************/
25
26#include "curl_setup.h"
27
28#include "urldata.h"
29#include "sendf.h"
30#include "select.h"
31#include "progress.h"
32#include "speedcheck.h"
33#include "pingpong.h"
34#include "multiif.h"
35#include "non-ascii.h"
36#include "vtls/vtls.h"
37
38#define _MPRINTF_REPLACE /* use our functions only */
39#include <curl/mprintf.h>
40
41#include "curl_memory.h"
42/* The last #include file should be: */
43#include "memdebug.h"
44
45#ifdef USE_PINGPONG
46
47/* Returns timeout in ms. 0 or negative number means the timeout has already
48   triggered */
49long Curl_pp_state_timeout(struct pingpong *pp)
50{
51  struct connectdata *conn = pp->conn;
52  struct SessionHandle *data=conn->data;
53  long timeout_ms; /* in milliseconds */
54  long timeout2_ms; /* in milliseconds */
55  long 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_tvdiff(Curl_tvnow(), pp->response); /* spent time */
67
68  if(data->set.timeout) {
69    /* if timeout is requested, find out how much remaining time we have */
70    timeout2_ms = data->set.timeout - /* timeout time */
71      Curl_tvdiff(Curl_tvnow(), 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 */
83CURLcode Curl_pp_statemach(struct pingpong *pp, bool block)
84{
85  struct connectdata *conn = pp->conn;
86  curl_socket_t sock = conn->sock[FIRSTSOCKET];
87  int rc;
88  long interval_ms;
89  long timeout_ms = Curl_pp_state_timeout(pp);
90  struct SessionHandle *data=conn->data;
91  CURLcode result = CURLE_OK;
92
93  if(timeout_ms <=0 ) {
94    failf(data, "server response timeout");
95    return CURLE_OPERATION_TIMEDOUT; /* already too little time */
96  }
97
98  if(block) {
99    interval_ms = 1000;  /* use 1 second timeout intervals */
100    if(timeout_ms < interval_ms)
101      interval_ms = timeout_ms;
102  }
103  else
104    interval_ms = 0; /* immediate */
105
106  if(Curl_pp_moredata(pp))
107    /* We are receiving and there is data in the cache so just read it */
108    rc = 1;
109  else if(!pp->sendleft && Curl_ssl_data_pending(conn, FIRSTSOCKET))
110    /* We are receiving and there is data ready in the SSL library */
111    rc = 1;
112  else
113    rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
114                           pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
115                           interval_ms);
116
117  if(block) {
118    /* if we didn't wait, we don't have to spend time on this now */
119    if(Curl_pgrsUpdate(conn))
120      result = CURLE_ABORTED_BY_CALLBACK;
121    else
122      result = Curl_speedcheck(data, Curl_tvnow());
123
124    if(result)
125      return result;
126  }
127
128  if(rc == -1) {
129    failf(data, "select/poll error");
130    result = CURLE_OUT_OF_MEMORY;
131  }
132  else if(rc)
133    result = pp->statemach_act(conn);
134
135  return result;
136}
137
138/* initialize stuff to prepare for reading a fresh new response */
139void Curl_pp_init(struct pingpong *pp)
140{
141  struct connectdata *conn = pp->conn;
142  pp->nread_resp = 0;
143  pp->linestart_resp = conn->data->state.buffer;
144  pp->pending_resp = TRUE;
145  pp->response = Curl_tvnow(); /* start response time-out now! */
146}
147
148
149
150/***********************************************************************
151 *
152 * Curl_pp_vsendf()
153 *
154 * Send the formated string as a command to a pingpong server. Note that
155 * the string should not have any CRLF appended, as this function will
156 * append the necessary things itself.
157 *
158 * made to never block
159 */
160CURLcode Curl_pp_vsendf(struct pingpong *pp,
161                        const char *fmt,
162                        va_list args)
163{
164  ssize_t bytes_written;
165  size_t write_len;
166  char *fmt_crlf;
167  char *s;
168  CURLcode error;
169  struct connectdata *conn = pp->conn;
170  struct SessionHandle *data = conn->data;
171
172#ifdef HAVE_GSSAPI
173  enum protection_level data_sec = conn->data_prot;
174#endif
175
176  DEBUGASSERT(pp->sendleft == 0);
177  DEBUGASSERT(pp->sendsize == 0);
178  DEBUGASSERT(pp->sendthis == NULL);
179
180  fmt_crlf = aprintf("%s\r\n", fmt); /* append a trailing CRLF */
181  if(!fmt_crlf)
182    return CURLE_OUT_OF_MEMORY;
183
184  s = vaprintf(fmt_crlf, args); /* trailing CRLF appended */
185  free(fmt_crlf);
186  if(!s)
187    return CURLE_OUT_OF_MEMORY;
188
189  bytes_written = 0;
190  write_len = strlen(s);
191
192  Curl_pp_init(pp);
193
194  error = Curl_convert_to_network(data, s, write_len);
195  /* Curl_convert_to_network calls failf if unsuccessful */
196  if(error) {
197    free(s);
198    return error;
199  }
200
201#ifdef HAVE_GSSAPI
202  conn->data_prot = PROT_CMD;
203#endif
204  error = Curl_write(conn, conn->sock[FIRSTSOCKET], s, write_len,
205                     &bytes_written);
206#ifdef HAVE_GSSAPI
207  DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
208  conn->data_prot = data_sec;
209#endif
210
211  if(error) {
212    free(s);
213    return error;
214  }
215
216  if(conn->data->set.verbose)
217    Curl_debug(conn->data, CURLINFO_HEADER_OUT,
218               s, (size_t)bytes_written, conn);
219
220  if(bytes_written != (ssize_t)write_len) {
221    /* the whole chunk was not sent, keep it around and adjust sizes */
222    pp->sendthis = s;
223    pp->sendsize = write_len;
224    pp->sendleft = write_len - bytes_written;
225  }
226  else {
227    free(s);
228    pp->sendthis = NULL;
229    pp->sendleft = pp->sendsize = 0;
230    pp->response = Curl_tvnow();
231  }
232
233  return CURLE_OK;
234}
235
236
237/***********************************************************************
238 *
239 * Curl_pp_sendf()
240 *
241 * Send the formated string as a command to a pingpong server. Note that
242 * the string should not have any CRLF appended, as this function will
243 * append the necessary things itself.
244 *
245 * made to never block
246 */
247CURLcode Curl_pp_sendf(struct pingpong *pp,
248                       const char *fmt, ...)
249{
250  CURLcode res;
251  va_list ap;
252  va_start(ap, fmt);
253
254  res = Curl_pp_vsendf(pp, fmt, ap);
255
256  va_end(ap);
257
258  return res;
259}
260
261/*
262 * Curl_pp_readresp()
263 *
264 * Reads a piece of a server response.
265 */
266CURLcode Curl_pp_readresp(curl_socket_t sockfd,
267                          struct pingpong *pp,
268                          int *code, /* return the server code if done */
269                          size_t *size) /* size of the response */
270{
271  ssize_t perline; /* count bytes per line */
272  bool keepon=TRUE;
273  ssize_t gotbytes;
274  char *ptr;
275  struct connectdata *conn = pp->conn;
276  struct SessionHandle *data = conn->data;
277  char * const buf = data->state.buffer;
278  CURLcode result = CURLE_OK;
279
280  *code = 0; /* 0 for errors or not done */
281  *size = 0;
282
283  ptr=buf + pp->nread_resp;
284
285  /* number of bytes in the current line, so far */
286  perline = (ssize_t)(ptr-pp->linestart_resp);
287
288  keepon=TRUE;
289
290  while((pp->nread_resp<BUFSIZE) && (keepon && !result)) {
291
292    if(pp->cache) {
293      /* we had data in the "cache", copy that instead of doing an actual
294       * read
295       *
296       * pp->cache_size is cast to ssize_t here.  This should be safe, because
297       * it would have been populated with something of size int to begin
298       * with, even though its datatype may be larger than an int.
299       */
300      DEBUGASSERT((ptr+pp->cache_size) <= (buf+BUFSIZE+1));
301      memcpy(ptr, pp->cache, pp->cache_size);
302      gotbytes = (ssize_t)pp->cache_size;
303      free(pp->cache);    /* free the cache */
304      pp->cache = NULL;   /* clear the pointer */
305      pp->cache_size = 0; /* zero the size just in case */
306    }
307    else {
308      int res;
309#ifdef HAVE_GSSAPI
310      enum protection_level prot = conn->data_prot;
311      conn->data_prot = PROT_CLEAR;
312#endif
313      DEBUGASSERT((ptr+BUFSIZE-pp->nread_resp) <= (buf+BUFSIZE+1));
314      res = Curl_read(conn, sockfd, ptr, BUFSIZE-pp->nread_resp,
315                      &gotbytes);
316#ifdef HAVE_GSSAPI
317      DEBUGASSERT(prot  > PROT_NONE && prot < PROT_LAST);
318      conn->data_prot = prot;
319#endif
320      if(res == CURLE_AGAIN)
321        return CURLE_OK; /* return */
322
323      if((res == CURLE_OK) && (gotbytes > 0))
324        /* convert from the network encoding */
325        res = Curl_convert_from_network(data, ptr, gotbytes);
326      /* Curl_convert_from_network calls failf if unsuccessful */
327
328      if(CURLE_OK != res) {
329        result = (CURLcode)res; /* Set outer result variable to this error. */
330        keepon = FALSE;
331      }
332    }
333
334    if(!keepon)
335      ;
336    else if(gotbytes <= 0) {
337      keepon = FALSE;
338      result = CURLE_RECV_ERROR;
339      failf(data, "response reading failed");
340    }
341    else {
342      /* we got a whole chunk of data, which can be anything from one
343       * byte to a set of lines and possible just a piece of the last
344       * line */
345      ssize_t i;
346      ssize_t clipamount = 0;
347      bool restart = FALSE;
348
349      data->req.headerbytecount += (long)gotbytes;
350
351      pp->nread_resp += gotbytes;
352      for(i = 0; i < gotbytes; ptr++, i++) {
353        perline++;
354        if(*ptr=='\n') {
355          /* a newline is CRLF in pp-talk, so the CR is ignored as
356             the line isn't really terminated until the LF comes */
357
358          /* output debug output if that is requested */
359#ifdef HAVE_GSSAPI
360          if(!conn->sec_complete)
361#endif
362            if(data->set.verbose)
363              Curl_debug(data, CURLINFO_HEADER_IN,
364                         pp->linestart_resp, (size_t)perline, conn);
365
366          /*
367           * We pass all response-lines to the callback function registered
368           * for "headers". The response lines can be seen as a kind of
369           * headers.
370           */
371          result = Curl_client_write(conn, CLIENTWRITE_HEADER,
372                                     pp->linestart_resp, perline);
373          if(result)
374            return result;
375
376          if(pp->endofresp(conn, pp->linestart_resp, perline, code)) {
377            /* This is the end of the last line, copy the last line to the
378               start of the buffer and zero terminate, for old times sake */
379            size_t n = ptr - pp->linestart_resp;
380            memmove(buf, pp->linestart_resp, n);
381            buf[n]=0; /* zero terminate */
382            keepon=FALSE;
383            pp->linestart_resp = ptr+1; /* advance pointer */
384            i++; /* skip this before getting out */
385
386            *size = pp->nread_resp; /* size of the response */
387            pp->nread_resp = 0; /* restart */
388            break;
389          }
390          perline=0; /* line starts over here */
391          pp->linestart_resp = ptr+1;
392        }
393      }
394
395      if(!keepon && (i != gotbytes)) {
396        /* We found the end of the response lines, but we didn't parse the
397           full chunk of data we have read from the server. We therefore need
398           to store the rest of the data to be checked on the next invoke as
399           it may actually contain another end of response already! */
400        clipamount = gotbytes - i;
401        restart = TRUE;
402        DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing "
403                     "server response left\n",
404                     (int)clipamount));
405      }
406      else if(keepon) {
407
408        if((perline == gotbytes) && (gotbytes > BUFSIZE/2)) {
409          /* We got an excessive line without newlines and we need to deal
410             with it. We keep the first bytes of the line then we throw
411             away the rest. */
412          infof(data, "Excessive server response line length received, "
413                "%zd bytes. Stripping\n", gotbytes);
414          restart = TRUE;
415
416          /* we keep 40 bytes since all our pingpong protocols are only
417             interested in the first piece */
418          clipamount = 40;
419        }
420        else if(pp->nread_resp > BUFSIZE/2) {
421          /* We got a large chunk of data and there's potentially still
422             trailing data to take care of, so we put any such part in the
423             "cache", clear the buffer to make space and restart. */
424          clipamount = perline;
425          restart = TRUE;
426        }
427      }
428      else if(i == gotbytes)
429        restart = TRUE;
430
431      if(clipamount) {
432        pp->cache_size = clipamount;
433        pp->cache = malloc(pp->cache_size);
434        if(pp->cache)
435          memcpy(pp->cache, pp->linestart_resp, pp->cache_size);
436        else
437          return CURLE_OUT_OF_MEMORY;
438      }
439      if(restart) {
440        /* now reset a few variables to start over nicely from the start of
441           the big buffer */
442        pp->nread_resp = 0; /* start over from scratch in the buffer */
443        ptr = pp->linestart_resp = buf;
444        perline = 0;
445      }
446
447    } /* there was data */
448
449  } /* while there's buffer left and loop is requested */
450
451  pp->pending_resp = FALSE;
452
453  return result;
454}
455
456int Curl_pp_getsock(struct pingpong *pp,
457                    curl_socket_t *socks,
458                    int numsocks)
459{
460  struct connectdata *conn = pp->conn;
461
462  if(!numsocks)
463    return GETSOCK_BLANK;
464
465  socks[0] = conn->sock[FIRSTSOCKET];
466
467  if(pp->sendleft) {
468    /* write mode */
469    return GETSOCK_WRITESOCK(0);
470  }
471
472  /* read mode */
473  return GETSOCK_READSOCK(0);
474}
475
476CURLcode Curl_pp_flushsend(struct pingpong *pp)
477{
478  /* we have a piece of a command still left to send */
479  struct connectdata *conn = pp->conn;
480  ssize_t written;
481  CURLcode result = CURLE_OK;
482  curl_socket_t sock = conn->sock[FIRSTSOCKET];
483
484  result = Curl_write(conn, sock, pp->sendthis + pp->sendsize -
485                      pp->sendleft, pp->sendleft, &written);
486  if(result)
487    return result;
488
489  if(written != (ssize_t)pp->sendleft) {
490    /* only a fraction was sent */
491    pp->sendleft -= written;
492  }
493  else {
494    free(pp->sendthis);
495    pp->sendthis=NULL;
496    pp->sendleft = pp->sendsize = 0;
497    pp->response = Curl_tvnow();
498  }
499  return CURLE_OK;
500}
501
502CURLcode Curl_pp_disconnect(struct pingpong *pp)
503{
504  if(pp->cache) {
505    free(pp->cache);
506    pp->cache = NULL;
507  }
508  return CURLE_OK;
509}
510
511bool Curl_pp_moredata(struct pingpong *pp)
512{
513  return (!pp->sendleft && pp->cache && pp->nread_resp < pp->cache_size) ?
514         TRUE : FALSE;
515}
516
517#endif
518