pingpong.c (13580B)
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 "connect.h" 33 #include "sendf.h" 34 #include "select.h" 35 #include "progress.h" 36 #include "speedcheck.h" 37 #include "pingpong.h" 38 #include "multiif.h" 39 #include "vtls/vtls.h" 40 #include "strdup.h" 41 42 /* The last 3 #include files should be in this order */ 43 #include "curl_printf.h" 44 #include "curl_memory.h" 45 #include "memdebug.h" 46 47 #ifdef USE_PINGPONG 48 49 /* Returns timeout in ms. 0 or negative number means the timeout has already 50 triggered */ 51 timediff_t Curl_pp_state_timeout(struct Curl_easy *data, 52 struct pingpong *pp, bool disconnecting) 53 { 54 timediff_t timeout_ms; /* in milliseconds */ 55 timediff_t response_time = (data->set.server_response_timeout > 0) ? 56 data->set.server_response_timeout : pp->response_time; 57 struct curltime now = curlx_now(); 58 59 /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine 60 remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is 61 supposed to govern the response for any given server response, not for 62 the time from connect to the given server response. */ 63 64 /* Without a requested timeout, we only wait 'response_time' seconds for the 65 full response to arrive before we bail out */ 66 timeout_ms = response_time - curlx_timediff(now, pp->response); 67 68 if((data->set.timeout > 0) && !disconnecting) { 69 /* if timeout is requested, find out how much overall remains */ 70 timediff_t timeout2_ms = Curl_timeleft(data, &now, FALSE); 71 /* pick the lowest number */ 72 timeout_ms = CURLMIN(timeout_ms, timeout2_ms); 73 } 74 75 if(disconnecting) { 76 timediff_t total_left_ms = Curl_timeleft(data, NULL, FALSE); 77 timeout_ms = CURLMIN(timeout_ms, CURLMAX(total_left_ms, 0)); 78 } 79 80 return timeout_ms; 81 } 82 83 /* 84 * Curl_pp_statemach() 85 */ 86 CURLcode Curl_pp_statemach(struct Curl_easy *data, 87 struct pingpong *pp, bool block, 88 bool disconnecting) 89 { 90 struct connectdata *conn = data->conn; 91 curl_socket_t sock = conn->sock[FIRSTSOCKET]; 92 int rc; 93 timediff_t interval_ms; 94 timediff_t timeout_ms = Curl_pp_state_timeout(data, pp, disconnecting); 95 CURLcode result = CURLE_OK; 96 97 if(timeout_ms <= 0) { 98 failf(data, "server response timeout"); 99 return CURLE_OPERATION_TIMEDOUT; /* already too little time */ 100 } 101 102 DEBUGF(infof(data, "pp_statematch, timeout=%" FMT_TIMEDIFF_T, timeout_ms)); 103 if(block) { 104 interval_ms = 1000; /* use 1 second timeout intervals */ 105 if(timeout_ms < interval_ms) 106 interval_ms = timeout_ms; 107 } 108 else 109 interval_ms = 0; /* immediate */ 110 111 if(Curl_conn_data_pending(data, FIRSTSOCKET)) 112 rc = 1; 113 else if(pp->overflow) 114 /* We are receiving and there is data in the cache so just read it */ 115 rc = 1; 116 else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET)) 117 /* We are receiving and there is data ready in the SSL library */ 118 rc = 1; 119 else { 120 DEBUGF(infof(data, "pp_statematch, select, timeout=%" FMT_TIMEDIFF_T 121 ", sendleft=%zu", 122 timeout_ms, pp->sendleft)); 123 rc = Curl_socket_check(pp->sendleft ? CURL_SOCKET_BAD : sock, /* reading */ 124 CURL_SOCKET_BAD, 125 pp->sendleft ? sock : CURL_SOCKET_BAD, /* writing */ 126 interval_ms); 127 } 128 129 if(block) { 130 /* if we did not wait, we do not have to spend time on this now */ 131 if(Curl_pgrsUpdate(data)) 132 result = CURLE_ABORTED_BY_CALLBACK; 133 else 134 result = Curl_speedcheck(data, curlx_now()); 135 136 if(result) 137 return result; 138 } 139 140 if(rc == -1) { 141 failf(data, "select/poll error"); 142 result = CURLE_OUT_OF_MEMORY; 143 } 144 else if(rc) 145 result = pp->statemachine(data, data->conn); 146 else if(disconnecting) 147 return CURLE_OPERATION_TIMEDOUT; 148 149 return result; 150 } 151 152 /* initialize stuff to prepare for reading a fresh new response */ 153 void Curl_pp_init(struct pingpong *pp) 154 { 155 DEBUGASSERT(!pp->initialised); 156 pp->nread_resp = 0; 157 pp->response = curlx_now(); /* start response time-out now! */ 158 pp->pending_resp = TRUE; 159 curlx_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); 160 curlx_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD); 161 pp->initialised = TRUE; 162 } 163 164 /*********************************************************************** 165 * 166 * Curl_pp_vsendf() 167 * 168 * Send the formatted string as a command to a pingpong server. Note that 169 * the string should not have any CRLF appended, as this function will 170 * append the necessary things itself. 171 * 172 * made to never block 173 */ 174 CURLcode Curl_pp_vsendf(struct Curl_easy *data, 175 struct pingpong *pp, 176 const char *fmt, 177 va_list args) 178 { 179 size_t bytes_written = 0; 180 size_t write_len; 181 char *s; 182 CURLcode result; 183 struct connectdata *conn = data->conn; 184 185 #ifdef HAVE_GSSAPI 186 enum protection_level data_sec; 187 #endif 188 189 DEBUGASSERT(pp->sendleft == 0); 190 DEBUGASSERT(pp->sendsize == 0); 191 DEBUGASSERT(pp->sendthis == NULL); 192 193 if(!conn) 194 /* cannot send without a connection! */ 195 return CURLE_SEND_ERROR; 196 197 curlx_dyn_reset(&pp->sendbuf); 198 result = curlx_dyn_vaddf(&pp->sendbuf, fmt, args); 199 if(result) 200 return result; 201 202 /* append CRLF */ 203 result = curlx_dyn_addn(&pp->sendbuf, "\r\n", 2); 204 if(result) 205 return result; 206 207 pp->pending_resp = TRUE; 208 write_len = curlx_dyn_len(&pp->sendbuf); 209 s = curlx_dyn_ptr(&pp->sendbuf); 210 211 #ifdef HAVE_GSSAPI 212 conn->data_prot = PROT_CMD; 213 #endif 214 result = Curl_conn_send(data, FIRSTSOCKET, s, write_len, FALSE, 215 &bytes_written); 216 if(result == CURLE_AGAIN) { 217 bytes_written = 0; 218 } 219 else if(result) 220 return result; 221 #ifdef HAVE_GSSAPI 222 data_sec = conn->data_prot; 223 DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); 224 conn->data_prot = (unsigned char)data_sec; 225 #endif 226 227 Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written); 228 229 if(bytes_written != write_len) { 230 /* the whole chunk was not sent, keep it around and adjust sizes */ 231 pp->sendthis = s; 232 pp->sendsize = write_len; 233 pp->sendleft = write_len - bytes_written; 234 } 235 else { 236 pp->sendthis = NULL; 237 pp->sendleft = pp->sendsize = 0; 238 pp->response = curlx_now(); 239 } 240 241 return CURLE_OK; 242 } 243 244 245 /*********************************************************************** 246 * 247 * Curl_pp_sendf() 248 * 249 * Send the formatted string as a command to a pingpong server. Note that 250 * the string should not have any CRLF appended, as this function will 251 * append the necessary things itself. 252 * 253 * made to never block 254 */ 255 CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, 256 const char *fmt, ...) 257 { 258 CURLcode result; 259 va_list ap; 260 va_start(ap, fmt); 261 262 result = Curl_pp_vsendf(data, pp, fmt, ap); 263 264 va_end(ap); 265 266 return result; 267 } 268 269 static CURLcode pingpong_read(struct Curl_easy *data, 270 int sockindex, 271 char *buffer, 272 size_t buflen, 273 size_t *nread) 274 { 275 CURLcode result; 276 #ifdef HAVE_GSSAPI 277 enum protection_level prot = data->conn->data_prot; 278 data->conn->data_prot = PROT_CLEAR; 279 #endif 280 result = Curl_conn_recv(data, sockindex, buffer, buflen, nread); 281 #ifdef HAVE_GSSAPI 282 DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); 283 data->conn->data_prot = (unsigned char)prot; 284 #endif 285 return result; 286 } 287 288 /* 289 * Curl_pp_readresp() 290 * 291 * Reads a piece of a server response. 292 */ 293 CURLcode Curl_pp_readresp(struct Curl_easy *data, 294 int sockindex, 295 struct pingpong *pp, 296 int *code, /* return the server code if done */ 297 size_t *size) /* size of the response */ 298 { 299 struct connectdata *conn = data->conn; 300 CURLcode result = CURLE_OK; 301 size_t gotbytes; 302 char buffer[900]; 303 304 *code = 0; /* 0 for errors or not done */ 305 *size = 0; 306 307 do { 308 gotbytes = 0; 309 if(pp->nfinal) { 310 /* a previous call left this many bytes in the beginning of the buffer as 311 that was the final line; now ditch that */ 312 size_t full = curlx_dyn_len(&pp->recvbuf); 313 314 /* trim off the "final" leading part */ 315 curlx_dyn_tail(&pp->recvbuf, full - pp->nfinal); 316 317 pp->nfinal = 0; /* now gone */ 318 } 319 if(!pp->overflow) { 320 result = pingpong_read(data, sockindex, buffer, sizeof(buffer), 321 &gotbytes); 322 if(result == CURLE_AGAIN) 323 return CURLE_OK; 324 325 if(result) 326 return result; 327 328 if(!gotbytes) { 329 failf(data, "response reading failed (errno: %d)", SOCKERRNO); 330 return CURLE_RECV_ERROR; 331 } 332 333 result = curlx_dyn_addn(&pp->recvbuf, buffer, gotbytes); 334 if(result) 335 return result; 336 337 data->req.headerbytecount += (unsigned int)gotbytes; 338 339 pp->nread_resp += gotbytes; 340 } 341 342 do { 343 char *line = curlx_dyn_ptr(&pp->recvbuf); 344 char *nl = memchr(line, '\n', curlx_dyn_len(&pp->recvbuf)); 345 if(nl) { 346 /* a newline is CRLF in pp-talk, so the CR is ignored as 347 the line is not really terminated until the LF comes */ 348 size_t length = nl - line + 1; 349 350 /* output debug output if that is requested */ 351 #ifdef HAVE_GSSAPI 352 if(!conn->sec_complete) 353 #endif 354 Curl_debug(data, CURLINFO_HEADER_IN, line, length); 355 356 /* 357 * Pass all response-lines to the callback function registered for 358 * "headers". The response lines can be seen as a kind of headers. 359 */ 360 result = Curl_client_write(data, CLIENTWRITE_INFO, line, length); 361 if(result) 362 return result; 363 364 if(pp->endofresp(data, conn, line, length, code)) { 365 /* When at "end of response", keep the endofresp line first in the 366 buffer since it will be accessed outside (by pingpong 367 parsers). Store the overflow counter to inform about additional 368 data in this buffer after the endofresp line. */ 369 pp->nfinal = length; 370 if(curlx_dyn_len(&pp->recvbuf) > length) 371 pp->overflow = curlx_dyn_len(&pp->recvbuf) - length; 372 else 373 pp->overflow = 0; 374 *size = pp->nread_resp; /* size of the response */ 375 pp->nread_resp = 0; /* restart */ 376 gotbytes = 0; /* force break out of outer loop */ 377 break; 378 } 379 if(curlx_dyn_len(&pp->recvbuf) > length) 380 /* keep the remaining piece */ 381 curlx_dyn_tail((&pp->recvbuf), curlx_dyn_len(&pp->recvbuf) - length); 382 else 383 curlx_dyn_reset(&pp->recvbuf); 384 } 385 else { 386 /* without a newline, there is no overflow */ 387 pp->overflow = 0; 388 break; 389 } 390 391 } while(1); /* while there is buffer left to scan */ 392 393 } while(gotbytes == sizeof(buffer)); 394 395 pp->pending_resp = FALSE; 396 397 return result; 398 } 399 400 int Curl_pp_getsock(struct Curl_easy *data, 401 struct pingpong *pp, curl_socket_t *socks) 402 { 403 struct connectdata *conn = data->conn; 404 socks[0] = conn->sock[FIRSTSOCKET]; 405 406 if(pp->sendleft) { 407 /* write mode */ 408 return GETSOCK_WRITESOCK(0); 409 } 410 411 /* read mode */ 412 return GETSOCK_READSOCK(0); 413 } 414 415 bool Curl_pp_needs_flush(struct Curl_easy *data, 416 struct pingpong *pp) 417 { 418 (void)data; 419 return pp->sendleft > 0; 420 } 421 422 CURLcode Curl_pp_flushsend(struct Curl_easy *data, 423 struct pingpong *pp) 424 { 425 /* we have a piece of a command still left to send */ 426 size_t written; 427 CURLcode result; 428 429 if(!Curl_pp_needs_flush(data, pp)) 430 return CURLE_OK; 431 432 result = Curl_conn_send(data, FIRSTSOCKET, 433 pp->sendthis + pp->sendsize - pp->sendleft, 434 pp->sendleft, FALSE, &written); 435 if(result == CURLE_AGAIN) { 436 result = CURLE_OK; 437 written = 0; 438 } 439 if(result) 440 return result; 441 442 if(written != pp->sendleft) { 443 /* only a fraction was sent */ 444 pp->sendleft -= written; 445 } 446 else { 447 pp->sendthis = NULL; 448 pp->sendleft = pp->sendsize = 0; 449 pp->response = curlx_now(); 450 } 451 return CURLE_OK; 452 } 453 454 CURLcode Curl_pp_disconnect(struct pingpong *pp) 455 { 456 if(pp->initialised) { 457 curlx_dyn_free(&pp->sendbuf); 458 curlx_dyn_free(&pp->recvbuf); 459 memset(pp, 0, sizeof(*pp)); 460 } 461 return CURLE_OK; 462 } 463 464 bool Curl_pp_moredata(struct pingpong *pp) 465 { 466 return !pp->sendleft && curlx_dyn_len(&pp->recvbuf) > pp->nfinal; 467 } 468 469 #endif