40
40
int php_openssl_apply_verification_policy (SSL * ssl , X509 * peer , php_stream * stream TSRMLS_DC );
41
41
SSL * php_SSL_new_from_context (SSL_CTX * ctx , php_stream * stream TSRMLS_DC );
42
42
int php_openssl_get_x509_list_id (void );
43
+ static struct timeval subtract_timeval ( struct timeval a , struct timeval b );
44
+ static int compare_timeval ( struct timeval a , struct timeval b );
45
+ static size_t php_openssl_sockop_io (int read , php_stream * stream , char * buf , size_t count TSRMLS_DC );
43
46
44
47
/* This implementation is very closely tied to the that of the native
45
48
* sockets implemented in the core.
@@ -171,76 +174,199 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init
171
174
return retry ;
172
175
}
173
176
174
-
175
177
static size_t php_openssl_sockop_write (php_stream * stream , const char * buf , size_t count TSRMLS_DC )
178
+ {
179
+ return php_openssl_sockop_io ( 0 , stream , buf , count );
180
+ }
181
+
182
+ static size_t php_openssl_sockop_read (php_stream * stream , char * buf , size_t count TSRMLS_DC )
183
+ {
184
+ return php_openssl_sockop_io ( 1 , stream , buf , count );
185
+ }
186
+
187
+ /**
188
+ * Factored out common functionality (blocking, timeout, loop management) for read and write.
189
+ * Perform IO (read or write) to an SSL socket. If we have a timeout, we switch to non-blocking mode
190
+ * for the duration of the operation, using select to do our waits. If we time out, or we have an error
191
+ * report that back to PHP
192
+ *
193
+ */
194
+ static size_t php_openssl_sockop_io (int read , php_stream * stream , char * buf , size_t count TSRMLS_DC )
176
195
{
177
196
php_openssl_netstream_data_t * sslsock = (php_openssl_netstream_data_t * )stream -> abstract ;
178
- int didwrite ;
197
+ int nr_bytes = 0 ;
179
198
199
+ /* Only do this if SSL is active. */
180
200
if (sslsock -> ssl_active ) {
181
201
int retry = 1 ;
202
+ struct timeval start_time ,
203
+ * timeout ;
204
+ int blocked = sslsock -> s .is_blocked ,
205
+ has_timeout = 0 ;
182
206
183
- do {
184
- didwrite = SSL_write (sslsock -> ssl_handle , buf , count );
207
+ /* Begin by making the socket non-blocking. This allows us to check the timeout. */
208
+ if (SUCCESS == php_set_sock_blocking (sslsock -> s .socket , 0 TSRMLS_CC )) {
209
+ sslsock -> s .is_blocked = 0 ;
210
+ }
185
211
186
- if (didwrite <= 0 ) {
187
- retry = handle_ssl_error (stream , didwrite , 0 TSRMLS_CC );
188
- } else {
189
- break ;
190
- }
191
- } while (retry );
212
+ /* Get the timeout value (and make sure we are to check it. */
213
+ timeout = sslsock -> is_client ? & sslsock -> connect_timeout : & sslsock -> s .timeout ;
214
+ has_timeout = !sslsock -> s .is_blocked && (timeout -> tv_sec || timeout -> tv_usec );
192
215
193
- if (didwrite > 0 ) {
194
- php_stream_notify_progress_increment (stream -> context , didwrite , 0 );
195
- }
196
- } else {
197
- didwrite = php_stream_socket_ops .write (stream , buf , count TSRMLS_CC );
216
+ /* gettimeofday is not monotonic; using it here is not strictly correct */
217
+ if (has_timeout ) {
218
+ gettimeofday (& start_time , NULL );
198
219
}
199
220
200
- if ( didwrite < 0 ) {
201
- didwrite = 0 ;
202
- }
221
+ /* Main IO loop. */
222
+ do {
223
+ struct timeval cur_time , elapsed_time , left_time ;
203
224
204
- return didwrite ;
205
- }
225
+ /* If we have a timeout to check, figure out how much time has elapsed since we started. */
226
+ if (has_timeout ) {
227
+ gettimeofday (& cur_time , NULL );
206
228
207
- static size_t php_openssl_sockop_read (php_stream * stream , char * buf , size_t count TSRMLS_DC )
208
- {
209
- php_openssl_netstream_data_t * sslsock = (php_openssl_netstream_data_t * )stream -> abstract ;
210
- int nr_bytes = 0 ;
229
+ /* Determine how much time we've taken so far. */
230
+ elapsed_time = subtract_timeval ( cur_time , start_time );
211
231
212
- if (sslsock -> ssl_active ) {
213
- int retry = 1 ;
232
+ /* and return an error if we've taken too long. */
233
+ if (compare_timeval ( elapsed_time , * timeout ) > 0 ) {
234
+ /* If the socket was originally blocking, set it back. */
235
+ if (blocked ) {
236
+ php_set_sock_blocking (sslsock -> s .socket , 1 TSRMLS_CC );
237
+ sslsock -> s .is_blocked = 1 ;
238
+ }
239
+ return -1 ;
240
+ }
241
+ }
214
242
215
- do {
243
+ /* Now, do the IO operation. Don't block if we can't complete... */
244
+ if (read ) {
216
245
nr_bytes = SSL_read (sslsock -> ssl_handle , buf , count );
217
246
247
+ if (sslsock -> reneg && sslsock -> reneg -> should_close ) {
248
+ /* renegotiation rate limiting triggered */
249
+ php_stream_xport_shutdown (stream , (stream_shutdown_t )SHUT_RDWR );
250
+ nr_bytes = 0 ;
251
+ stream -> eof = 1 ;
252
+ break ;
253
+ }
254
+ } else {
255
+ nr_bytes = SSL_write (sslsock -> ssl_handle , buf , count );
256
+ }
257
+
258
+ /* Now, how much time until we time out? */
259
+ if (has_timeout ) {
260
+ left_time = subtract_timeval ( * timeout , elapsed_time );
261
+ }
262
+
263
+ /* If we didn't do anything on the last loop (or an error) check to see if we should retry or exit. */
218
264
if (nr_bytes <= 0 ) {
265
+
266
+ /* Get the error code from SSL, and check to see if it's an error or not. */
267
+ int err = SSL_get_error (sslsock -> ssl_handle , nr_bytes );
219
268
retry = handle_ssl_error (stream , nr_bytes , 0 TSRMLS_CC );
269
+
270
+ /* If we get this (the above doesn't check) then we'll retry as well. */
271
+ if (errno == EAGAIN && err == SSL_ERROR_WANT_READ && read ) {
272
+ retry = 1 ;
273
+ }
274
+ if (errno == EAGAIN && SSL_ERROR_WANT_WRITE && read == 0 ) {
275
+ retry = 1 ;
276
+ }
277
+
278
+ /* Also, on reads, we may get this condition on an EOF. We should check properly. */
279
+ if (read ) {
220
280
stream -> eof = (retry == 0 && errno != EAGAIN && !SSL_pending (sslsock -> ssl_handle ));
281
+ }
221
282
283
+ /* Now, if we have to wait some time, and we're supposed to be blocking, wait for the socket to become
284
+ * available. Now, php_pollfd_for uses select to wait up to our time_left value only...
285
+ */
286
+ if (retry && blocked ) {
287
+ if (read ) {
288
+ php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_WRITE ) ?
289
+ (POLLOUT |POLLPRI ) : (POLLIN |POLLPRI ), has_timeout ? & left_time : NULL );
290
+ } else {
291
+ php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_READ ) ?
292
+ (POLLIN |POLLPRI ) : (POLLOUT |POLLPRI ), has_timeout ? & left_time : NULL );
293
+ }
294
+ }
222
295
} else {
223
- /* we got the data */
296
+ /* Else, if we got bytes back, check for possible errors. */
297
+ int err = SSL_get_error (sslsock -> ssl_handle , nr_bytes );
298
+
299
+ /* If we didn't get any error, then let's return it to PHP. */
300
+ if (err == SSL_ERROR_NONE )
224
301
break ;
302
+
303
+ /* Otherwise, we need to wait again (up to time_left or we get an error) */
304
+ if (blocked )
305
+ if (read ) {
306
+ php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_WRITE ) ?
307
+ (POLLOUT |POLLPRI ) : (POLLIN |POLLPRI ), has_timeout ? & left_time : NULL );
308
+ } else {
309
+ php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_READ ) ?
310
+ (POLLIN |POLLPRI ) : (POLLOUT |POLLPRI ), has_timeout ? & left_time : NULL );
311
+ }
225
312
}
313
+ /* Finally, we keep going until we got data, and an SSL_ERROR_NONE, unless we had an error. */
226
314
} while (retry );
227
315
316
+ /* Tell PHP if we read / wrote bytes. */
228
317
if (nr_bytes > 0 ) {
229
318
php_stream_notify_progress_increment (stream -> context , nr_bytes , 0 );
230
319
}
320
+
321
+ /* And if we were originally supposed to be blocking, let's reset the socket to that. */
322
+ if (blocked ) {
323
+ php_set_sock_blocking (sslsock -> s .socket , 1 TSRMLS_CC );
324
+ sslsock -> s .is_blocked = 1 ;
231
325
}
232
- else
233
- {
326
+ } else {
327
+ /*
328
+ * This block is if we had no timeout... We will just sit and wait forever on the IO operation.
329
+ */
330
+ if (read ) {
234
331
nr_bytes = php_stream_socket_ops .read (stream , buf , count TSRMLS_CC );
332
+ } else {
333
+ nr_bytes = php_stream_socket_ops .write (stream , buf , count TSRMLS_CC );
235
334
}
335
+ }
236
336
337
+ /* PHP doesn't expect a negative return. */
237
338
if (nr_bytes < 0 ) {
238
339
nr_bytes = 0 ;
239
340
}
240
341
241
342
return nr_bytes ;
242
343
}
243
344
345
+ struct timeval subtract_timeval ( struct timeval a , struct timeval b )
346
+ {
347
+ struct timeval difference ;
348
+
349
+ difference .tv_sec = a .tv_sec - b .tv_sec ;
350
+ difference .tv_usec = a .tv_usec - b .tv_usec ;
351
+
352
+ if (a .tv_usec < b .tv_usec ) {
353
+ b .tv_sec -= 1L ;
354
+ b .tv_usec += 1000000L ;
355
+ }
356
+
357
+ return difference ;
358
+ }
359
+
360
+ int compare_timeval ( struct timeval a , struct timeval b )
361
+ {
362
+ if (a .tv_sec > b .tv_sec || (a .tv_sec == b .tv_sec && a .tv_usec > b .tv_usec ) ) {
363
+ return 1 ;
364
+ } else if ( a .tv_sec == b .tv_sec && a .tv_usec == b .tv_usec ) {
365
+ return 0 ;
366
+ } else {
367
+ return -1 ;
368
+ }
369
+ }
244
370
245
371
static int php_openssl_sockop_close (php_stream * stream , int close_handle TSRMLS_DC )
246
372
{
@@ -492,16 +618,9 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
492
618
493
619
if (has_timeout ) {
494
620
gettimeofday (& cur_time , NULL );
495
- elapsed_time .tv_sec = cur_time .tv_sec - start_time .tv_sec ;
496
- elapsed_time .tv_usec = cur_time .tv_usec - start_time .tv_usec ;
497
- if (cur_time .tv_usec < start_time .tv_usec ) {
498
- elapsed_time .tv_sec -= 1L ;
499
- elapsed_time .tv_usec += 1000000L ;
500
- }
621
+ elapsed_time = subtract_timeval ( cur_time , start_time );
501
622
502
- if (elapsed_time .tv_sec > timeout -> tv_sec ||
503
- (elapsed_time .tv_sec == timeout -> tv_sec &&
504
- elapsed_time .tv_usec > timeout -> tv_usec )) {
623
+ if (compare_timeval ( elapsed_time , * timeout ) > 0 ) {
505
624
php_error_docref (NULL TSRMLS_CC , E_WARNING , "SSL: crypto enabling timeout" );
506
625
return -1 ;
507
626
}
@@ -517,12 +636,7 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
517
636
struct timeval left_time ;
518
637
519
638
if (has_timeout ) {
520
- left_time .tv_sec = timeout -> tv_sec - elapsed_time .tv_sec ;
521
- left_time .tv_usec = timeout -> tv_usec - elapsed_time .tv_usec ;
522
- if (timeout -> tv_usec < elapsed_time .tv_usec ) {
523
- left_time .tv_sec -= 1L ;
524
- left_time .tv_usec += 1000000L ;
525
- }
639
+ left_time = subtract_timeval ( * timeout , elapsed_time );
526
640
}
527
641
php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_READ ) ?
528
642
(POLLIN |POLLPRI ) : POLLOUT , has_timeout ? & left_time : NULL );
0 commit comments