Skip to content

Commit 3ed7c46

Browse files
committed
ext/curl: add socket callback options bridging to ext/sockets
Expose libcurl's CURLOPT_SOCKOPTFUNCTION, CURLOPT_OPENSOCKETFUNCTION and CURLOPT_CLOSESOCKETFUNCTION, letting userland hook into socket creation, configuration and teardown. These are useful for application security, in particular SSRF protection (validating the resolved address before connecting) and low-level socket hardening. Following the existing curl_write_header / curl_prereqfunction bridge model, the callbacks exchange ext/sockets Socket objects so they are fully usable in pure PHP (socket_create/socket_bind, socket_set_option, socket_close): - sockopt: fn(CurlHandle $ch, Socket $socket, int $purpose): int returns CURL_SOCKOPT_OK / _ERROR / _ALREADY_CONNECTED - opensocket: fn(CurlHandle $ch, int $purpose, array $address): Socket|false $address = [family, socktype, protocol, ip, port]; returning false aborts the connection (CURL_SOCKET_BAD) - closesocket: fn(CurlHandle $ch, Socket $socket): void This is achieved through the C API exported by ext/sockets (socket_ce, the php_socket struct and socket_import_file_descriptor()), so ext/curl now depends on ext/sockets. The whole feature is guarded by HAVE_SOCKETS; without the sockets extension curl still builds, just without these options. Notable details: - Descriptors owned by libcurl are detached (bsd_socket = -1) before the temporary Socket object is released, to avoid a double close. - The close-socket callback is disabled before curl_easy_cleanup() so libcurl closes pooled sockets natively rather than calling into PHP during handle destruction. - Setting an option to null restores libcurl's native default. New constants: CURLOPT_SOCKOPTFUNCTION, CURLOPT_OPENSOCKETFUNCTION, CURLOPT_CLOSESOCKETFUNCTION, CURL_SOCKOPT_OK, CURL_SOCKOPT_ERROR, CURL_SOCKOPT_ALREADY_CONNECTED, CURLSOCKTYPE_IPCXN, CURLSOCKTYPE_ACCEPT.
1 parent 9898293 commit 3ed7c46

12 files changed

Lines changed: 739 additions & 1 deletion

NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ PHP NEWS
2525
- BCMath:
2626
. Added NUL-byte validation to BCMath functions. (jorgsowa)
2727

28+
- Curl:
29+
. Added CURLOPT_SOCKOPTFUNCTION, CURLOPT_OPENSOCKETFUNCTION and
30+
CURLOPT_CLOSESOCKETFUNCTION options, bridging libcurl's socket callbacks to
31+
ext/sockets Socket objects (requires the sockets extension).
32+
2833
- Date:
2934
. Update timelib to 2022.16. (Derick)
3035

UPGRADING

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ PHP 8.6 UPGRADE NOTES
171171
. It is now possible to define the `__debugInfo()` magic method on enums.
172172
RFC: https://wiki.php.net/rfc/debugable-enums
173173

174+
- Curl:
175+
. Added the CURLOPT_SOCKOPTFUNCTION, CURLOPT_OPENSOCKETFUNCTION and
176+
CURLOPT_CLOSESOCKETFUNCTION options, which let userland hook into libcurl's
177+
socket creation, configuration and teardown. The callbacks exchange
178+
ext/sockets Socket objects, e.g. to validate the resolved address before
179+
connecting (SSRF protection) or to set low-level socket options. These
180+
options require the sockets extension; the new CURL_SOCKOPT_OK,
181+
CURL_SOCKOPT_ERROR, CURL_SOCKOPT_ALREADY_CONNECTED, CURLSOCKTYPE_IPCXN and
182+
CURLSOCKTYPE_ACCEPT constants are defined alongside them.
183+
174184
- Fileinfo:
175185
. finfo_file() now works with remote streams.
176186

ext/curl/config.m4

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ if test "$PHP_CURL" != "no"; then
7575
PHP_NEW_EXTENSION([curl],
7676
[interface.c multi.c share.c curl_file.c],
7777
[$ext_shared])
78+
dnl The CURLOPT_SOCKOPT/OPENSOCKET/CLOSESOCKETFUNCTION callbacks bridge to
79+
dnl ext/sockets Socket objects (guarded by HAVE_SOCKETS). The dependency is
80+
dnl optional: without sockets, curl still builds, just without those options.
81+
PHP_ADD_EXTENSION_DEP(curl, sockets, true)
7882
PHP_INSTALL_HEADERS([ext/curl], [php_curl.h])
7983
PHP_SUBST([CURL_SHARED_LIBADD])
8084
fi

ext/curl/config.w32

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ if (PHP_CURL != "no") {
2525
WARNING("zstd in curl not enabled; library not found");
2626
}
2727
EXTENSION("curl", "interface.c multi.c share.c curl_file.c");
28+
// The socket callbacks (CURLOPT_SOCKOPT/OPENSOCKET/CLOSESOCKETFUNCTION)
29+
// bridge to ext/sockets Socket objects, guarded by HAVE_SOCKETS. Optional:
30+
// without sockets, curl still builds, just without those options.
31+
ADD_EXTENSION_DEP('curl', 'sockets', true);
2832
AC_DEFINE('HAVE_CURL', 1, "Define to 1 if the PHP extension 'curl' is available.");
2933
ADD_FLAG("CFLAGS_CURL", "/D PHP_CURL_EXPORTS=1");
3034
if (curl_location.match(/libcurl_a\.lib$/)) {

ext/curl/curl.stub.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2327,6 +2327,63 @@
23272327
*/
23282328
const CURL_FNMATCHFUNC_NOMATCH = UNKNOWN;
23292329

2330+
#ifdef HAVE_SOCKETS
2331+
/**
2332+
* Used with CURLOPT_SOCKOPTFUNCTION, which receives a Socket wrapping the
2333+
* descriptor libcurl just created and must return one of the CURL_SOCKOPT_*
2334+
* constants below.
2335+
* @var int
2336+
* @cvalue CURLOPT_SOCKOPTFUNCTION
2337+
*/
2338+
const CURLOPT_SOCKOPTFUNCTION = UNKNOWN;
2339+
/**
2340+
* Used with CURLOPT_OPENSOCKETFUNCTION, which receives the resolved address and
2341+
* must return a Socket to use for the connection, or false to abort it.
2342+
* @var int
2343+
* @cvalue CURLOPT_OPENSOCKETFUNCTION
2344+
*/
2345+
const CURLOPT_OPENSOCKETFUNCTION = UNKNOWN;
2346+
/**
2347+
* Used with CURLOPT_CLOSESOCKETFUNCTION, which is notified when libcurl is done
2348+
* with a socket created by the open-socket callback.
2349+
* @var int
2350+
* @cvalue CURLOPT_CLOSESOCKETFUNCTION
2351+
*/
2352+
const CURLOPT_CLOSESOCKETFUNCTION = UNKNOWN;
2353+
/**
2354+
* Return value for the CURLOPT_SOCKOPTFUNCTION callback: proceed normally.
2355+
* @var int
2356+
* @cvalue CURL_SOCKOPT_OK
2357+
*/
2358+
const CURL_SOCKOPT_OK = UNKNOWN;
2359+
/**
2360+
* Return value for the CURLOPT_SOCKOPTFUNCTION callback: abort the connection.
2361+
* @var int
2362+
* @cvalue CURL_SOCKOPT_ERROR
2363+
*/
2364+
const CURL_SOCKOPT_ERROR = UNKNOWN;
2365+
/**
2366+
* Return value for the CURLOPT_SOCKOPTFUNCTION callback: the socket is already
2367+
* connected, so libcurl should skip its own connect step.
2368+
* @var int
2369+
* @cvalue CURL_SOCKOPT_ALREADY_CONNECTED
2370+
*/
2371+
const CURL_SOCKOPT_ALREADY_CONNECTED = UNKNOWN;
2372+
/**
2373+
* Purpose passed to the socket callbacks: a socket for a regular IP connection.
2374+
* @var int
2375+
* @cvalue CURLSOCKTYPE_IPCXN
2376+
*/
2377+
const CURLSOCKTYPE_IPCXN = UNKNOWN;
2378+
/**
2379+
* Purpose passed to the socket callbacks: a socket created from accept() (e.g.
2380+
* active FTP).
2381+
* @var int
2382+
* @cvalue CURLSOCKTYPE_ACCEPT
2383+
*/
2384+
const CURLSOCKTYPE_ACCEPT = UNKNOWN;
2385+
#endif
2386+
23302387
/* Available since 7.21.2 */
23312388
/**
23322389
* @var int

ext/curl/curl_arginfo.h

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/curl/curl_private.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ typedef struct {
8484
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
8585
zend_fcall_info_cache sshhostkey;
8686
#endif
87+
#ifdef HAVE_SOCKETS
88+
zend_fcall_info_cache sockopt;
89+
zend_fcall_info_cache opensocket;
90+
zend_fcall_info_cache closesocket;
91+
#endif
8792
} php_curl_handlers;
8893

8994
struct _php_curl_error {

0 commit comments

Comments
 (0)