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,191 @@ 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 TSRMLS_CC );
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 TSRMLS_CC );
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 {
216
- nr_bytes = SSL_read (sslsock -> ssl_handle , buf , count );
243
+ /* Now, do the IO operation. Don't block if we can't complete... */
244
+ if (read ) {
245
+ nr_bytes = SSL_read (sslsock -> ssl_handle , buf , count );
246
+ } else {
247
+ nr_bytes = SSL_write (sslsock -> ssl_handle , buf , count );
248
+ }
217
249
250
+ /* Now, how much time until we time out? */
251
+ if (has_timeout ) {
252
+ left_time = subtract_timeval ( * timeout , elapsed_time );
253
+ }
254
+
255
+ /* If we didn't do anything on the last loop (or an error) check to see if we should retry or exit. */
218
256
if (nr_bytes <= 0 ) {
257
+
258
+ /* Get the error code from SSL, and check to see if it's an error or not. */
259
+ int err = SSL_get_error (sslsock -> ssl_handle , nr_bytes );
219
260
retry = handle_ssl_error (stream , nr_bytes , 0 TSRMLS_CC );
261
+
262
+ /* If we get this (the above doesn't check) then we'll retry as well. */
263
+ if (errno == EAGAIN && err == SSL_ERROR_WANT_READ && read ) {
264
+ retry = 1 ;
265
+ }
266
+ if (errno == EAGAIN && SSL_ERROR_WANT_WRITE && read == 0 ) {
267
+ retry = 1 ;
268
+ }
269
+
270
+ /* Also, on reads, we may get this condition on an EOF. We should check properly. */
271
+ if (read ) {
220
272
stream -> eof = (retry == 0 && errno != EAGAIN && !SSL_pending (sslsock -> ssl_handle ));
273
+ }
221
274
275
+ /* Now, if we have to wait some time, and we're supposed to be blocking, wait for the socket to become
276
+ * available. Now, php_pollfd_for uses select to wait up to our time_left value only...
277
+ */
278
+ if (retry && blocked ) {
279
+ if (read ) {
280
+ php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_WRITE ) ?
281
+ (POLLOUT |POLLPRI ) : (POLLIN |POLLPRI ), has_timeout ? & left_time : NULL );
282
+ } else {
283
+ php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_READ ) ?
284
+ (POLLIN |POLLPRI ) : (POLLOUT |POLLPRI ), has_timeout ? & left_time : NULL );
285
+ }
286
+ }
222
287
} else {
223
- /* we got the data */
288
+ /* Else, if we got bytes back, check for possible errors. */
289
+ int err = SSL_get_error (sslsock -> ssl_handle , nr_bytes );
290
+
291
+ /* If we didn't get any error, then let's return it to PHP. */
292
+ if (err == SSL_ERROR_NONE )
224
293
break ;
294
+
295
+ /* Otherwise, we need to wait again (up to time_left or we get an error) */
296
+ if (blocked )
297
+ if (read ) {
298
+ php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_WRITE ) ?
299
+ (POLLOUT |POLLPRI ) : (POLLIN |POLLPRI ), has_timeout ? & left_time : NULL );
300
+ } else {
301
+ php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_READ ) ?
302
+ (POLLIN |POLLPRI ) : (POLLOUT |POLLPRI ), has_timeout ? & left_time : NULL );
303
+ }
225
304
}
305
+ /* Finally, we keep going until we got data, and an SSL_ERROR_NONE, unless we had an error. */
226
306
} while (retry );
227
307
308
+ /* Tell PHP if we read / wrote bytes. */
228
309
if (nr_bytes > 0 ) {
229
310
php_stream_notify_progress_increment (stream -> context , nr_bytes , 0 );
230
311
}
312
+
313
+ /* And if we were originally supposed to be blocking, let's reset the socket to that. */
314
+ if (blocked ) {
315
+ php_set_sock_blocking (sslsock -> s .socket , 1 TSRMLS_CC );
316
+ sslsock -> s .is_blocked = 1 ;
231
317
}
232
- else
233
- {
318
+ } else {
319
+ /*
320
+ * This block is if we had no timeout... We will just sit and wait forever on the IO operation.
321
+ */
322
+ if (read ) {
234
323
nr_bytes = php_stream_socket_ops .read (stream , buf , count TSRMLS_CC );
324
+ } else {
325
+ nr_bytes = php_stream_socket_ops .write (stream , buf , count TSRMLS_CC );
235
326
}
327
+ }
236
328
329
+ /* PHP doesn't expect a negative return. */
237
330
if (nr_bytes < 0 ) {
238
331
nr_bytes = 0 ;
239
332
}
240
333
241
334
return nr_bytes ;
242
335
}
243
336
337
+ struct timeval subtract_timeval ( struct timeval a , struct timeval b )
338
+ {
339
+ struct timeval difference ;
340
+
341
+ difference .tv_sec = a .tv_sec - b .tv_sec ;
342
+ difference .tv_usec = a .tv_usec - b .tv_usec ;
343
+
344
+ if (a .tv_usec < b .tv_usec ) {
345
+ b .tv_sec -= 1L ;
346
+ b .tv_usec += 1000000L ;
347
+ }
348
+
349
+ return difference ;
350
+ }
351
+
352
+ int compare_timeval ( struct timeval a , struct timeval b )
353
+ {
354
+ if (a .tv_sec > b .tv_sec || (a .tv_sec == b .tv_sec && a .tv_usec > b .tv_usec ) ) {
355
+ return 1 ;
356
+ } else if ( a .tv_sec == b .tv_sec && a .tv_usec == b .tv_usec ) {
357
+ return 0 ;
358
+ } else {
359
+ return -1 ;
360
+ }
361
+ }
244
362
245
363
static int php_openssl_sockop_close (php_stream * stream , int close_handle TSRMLS_DC )
246
364
{
@@ -492,16 +610,9 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
492
610
493
611
if (has_timeout ) {
494
612
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
- }
613
+ elapsed_time = subtract_timeval ( cur_time , start_time );
501
614
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 )) {
615
+ if (compare_timeval ( elapsed_time , * timeout ) > 0 ) {
505
616
php_error_docref (NULL TSRMLS_CC , E_WARNING , "SSL: crypto enabling timeout" );
506
617
return -1 ;
507
618
}
@@ -517,12 +628,7 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
517
628
struct timeval left_time ;
518
629
519
630
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
- }
631
+ left_time = subtract_timeval ( * timeout , elapsed_time );
526
632
}
527
633
php_pollfd_for (sslsock -> s .socket , (err == SSL_ERROR_WANT_READ ) ?
528
634
(POLLIN |POLLPRI ) : POLLOUT , has_timeout ? & left_time : NULL );
0 commit comments