Coverage Report

Created: 2026-07-25 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/streams/xp_socket.c
Line
Count
Source
1
/*
2
  +----------------------------------------------------------------------+
3
  | Copyright © The PHP Group and Contributors.                          |
4
  +----------------------------------------------------------------------+
5
  | This source file is subject to the Modified BSD License that is      |
6
  | bundled with this package in the file LICENSE, and is available      |
7
  | through the World Wide Web at <https://www.php.net/license/>.        |
8
  |                                                                      |
9
  | SPDX-License-Identifier: BSD-3-Clause                                |
10
  +----------------------------------------------------------------------+
11
  | Author: Wez Furlong <wez@thebrainroom.com>                           |
12
  +----------------------------------------------------------------------+
13
*/
14
15
#include "php.h"
16
#include "ext/standard/file.h"
17
#include "php_streams.h"
18
#include "php_io.h"
19
20
#if defined(PHP_WIN32) || defined(__riscos__)
21
# undef AF_UNIX
22
#endif
23
24
#ifdef AF_UNIX
25
#include <sys/un.h>
26
#endif
27
28
#ifndef MSG_DONTWAIT
29
# define MSG_DONTWAIT 0
30
#endif
31
32
#ifndef MSG_PEEK
33
# define MSG_PEEK 0
34
#endif
35
36
#ifdef PHP_WIN32
37
/* send/recv family on windows expects int */
38
# define XP_SOCK_BUF_SIZE(sz) (((sz) > INT_MAX) ? INT_MAX : (int)(sz))
39
#else
40
0
# define XP_SOCK_BUF_SIZE(sz) (sz)
41
#endif
42
43
const php_stream_ops php_stream_generic_socket_ops;
44
PHPAPI const php_stream_ops php_stream_socket_ops;
45
static const php_stream_ops php_stream_udp_socket_ops;
46
#ifdef AF_UNIX
47
static const php_stream_ops php_stream_unix_socket_ops;
48
static const php_stream_ops php_stream_unixdg_socket_ops;
49
50
0
#define PHP_STREAM_XPORT_IS_UNIX_DG(stream) php_stream_is(stream, &php_stream_unixdg_socket_ops)
51
0
#define PHP_STREAM_XPORT_IS_UNIX_ST(stream) php_stream_is(stream, &php_stream_unix_socket_ops)
52
#define PHP_STREAM_XPORT_IS_UNIX(stream) \
53
0
  (PHP_STREAM_XPORT_IS_UNIX_DG(stream) || PHP_STREAM_XPORT_IS_UNIX_ST(stream))
54
#else
55
#define PHP_STREAM_XPORT_IS_UNIX_DG(stream) false
56
#define PHP_STREAM_XPORT_IS_UNIX_STD(stream) false
57
#define PHP_STREAM_XPORT_IS_UNIX(stream) false
58
#endif
59
0
#define PHP_STREAM_XPORT_IS_UDP(stream) (php_stream_is(stream, &php_stream_udp_socket_ops))
60
0
#define PHP_STREAM_XPORT_IS_TCP(stream) (!PHP_STREAM_XPORT_IS_UNIX(stream) && !PHP_STREAM_XPORT_IS_UDP(stream))
61
62
static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam);
63
64
/* {{{ Generic socket stream operations */
65
static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t count)
66
0
{
67
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
68
0
  ssize_t didwrite;
69
0
  struct timeval *ptimeout;
70
71
0
  if (!sock || sock->socket == -1) {
72
0
    return 0;
73
0
  }
74
75
0
  if (sock->timeout.tv_sec == -1)
76
0
    ptimeout = NULL;
77
0
  else
78
0
    ptimeout = &sock->timeout;
79
80
0
retry:
81
0
  didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0);
82
83
0
  if (didwrite <= 0) {
84
0
    char *estr;
85
0
    int err = php_socket_errno();
86
87
0
    if (PHP_IS_TRANSIENT_ERROR(err)) {
88
0
      if (sock->is_blocked) {
89
0
        int retval;
90
91
0
        sock->timeout_event = false;
92
93
0
        do {
94
0
          retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout);
95
96
0
          if (retval == 0) {
97
0
            sock->timeout_event = true;
98
0
            break;
99
0
          }
100
101
0
          if (retval > 0) {
102
            /* writable now; retry */
103
0
            goto retry;
104
0
          }
105
106
0
          err = php_socket_errno();
107
0
        } while (err == EINTR);
108
0
      } else {
109
        /* EWOULDBLOCK/EAGAIN is not an error for a non-blocking stream.
110
         * Report zero byte write instead. */
111
0
        return 0;
112
0
      }
113
0
    }
114
115
0
    if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) {
116
0
      estr = php_socket_strerror(err, NULL, 0);
117
0
      php_stream_warn(stream, NetworkSendFailed,
118
0
          "Send of %zu bytes failed with errno=%d %s", count, err, estr);
119
0
      efree(estr);
120
0
    }
121
0
  }
122
123
0
  if (didwrite > 0) {
124
0
    php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0);
125
0
  }
126
127
0
  return didwrite;
128
0
}
129
130
static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock, bool has_buffered_data)
131
0
{
132
0
  int retval;
133
0
  struct timeval *ptimeout, zero_timeout;
134
135
0
  if (!sock || sock->socket == -1) {
136
0
    return;
137
0
  }
138
139
0
  sock->timeout_event = false;
140
141
0
  if (has_buffered_data) {
142
    /* If there is already buffered data, use no timeout. */
143
0
    zero_timeout.tv_sec = 0;
144
0
    zero_timeout.tv_usec = 0;
145
0
    ptimeout = &zero_timeout;
146
0
  } else if (sock->timeout.tv_sec == -1) {
147
0
    ptimeout = NULL;
148
0
  } else {
149
0
    ptimeout = &sock->timeout;
150
0
  }
151
152
0
  while(1) {
153
0
    retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout);
154
155
0
    if (retval == 0)
156
0
      sock->timeout_event = true;
157
158
0
    if (retval >= 0)
159
0
      break;
160
161
0
    if (php_socket_errno() != EINTR)
162
0
      break;
163
0
  }
164
0
}
165
166
static ssize_t php_sockop_read(php_stream *stream, char *buf, size_t count)
167
0
{
168
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
169
170
0
  if (!sock || sock->socket == -1) {
171
0
    return -1;
172
0
  }
173
174
0
  int recv_flags = 0;
175
  /* Special handling for blocking read. */
176
0
  if (sock->is_blocked) {
177
    /* Find out if there is any data buffered from the previous read. */
178
0
    bool has_buffered_data = stream->has_buffered_data;
179
    /* No need to wait if there is any data buffered or no timeout. */
180
0
    bool dont_wait = has_buffered_data ||
181
0
        (sock->timeout.tv_sec == 0 && sock->timeout.tv_usec == 0);
182
    /* Set MSG_DONTWAIT if no wait is needed or there is unlimited timeout which was
183
     * added by fix for #41984 committed in 9343c5404. */
184
0
    if (dont_wait || sock->timeout.tv_sec != -1) {
185
0
      recv_flags = MSG_DONTWAIT;
186
0
    }
187
    /* If the wait is needed or it is a platform without MSG_DONTWAIT support (e.g. Windows),
188
     * then poll for data. */
189
0
    if (!dont_wait || MSG_DONTWAIT == 0) {
190
0
      php_sock_stream_wait_for_data(stream, sock, has_buffered_data);
191
0
      if (sock->timeout_event) {
192
        /* It is ok to timeout if there is any data buffered so return 0, otherwise -1. */
193
0
        return has_buffered_data ? 0 : -1;
194
0
      }
195
0
    }
196
0
  }
197
198
0
  ssize_t nr_bytes = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(count), recv_flags);
199
0
  int err = php_socket_errno();
200
201
0
  if (nr_bytes < 0) {
202
0
    if (PHP_IS_TRANSIENT_ERROR(err)) {
203
0
      nr_bytes = 0;
204
0
    } else {
205
0
      stream->eof = 1;
206
0
    }
207
0
  } else if (nr_bytes == 0) {
208
0
    stream->eof = 1;
209
0
  }
210
211
0
  if (nr_bytes > 0) {
212
0
    php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0);
213
0
  }
214
215
0
  return nr_bytes;
216
0
}
217
218
219
static int php_sockop_close(php_stream *stream, int close_handle)
220
0
{
221
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
222
#ifdef PHP_WIN32
223
  int n;
224
#endif
225
226
0
  if (!sock) {
227
0
    return 0;
228
0
  }
229
230
0
  if (close_handle) {
231
232
#ifdef PHP_WIN32
233
    if (sock->socket == -1)
234
      sock->socket = SOCK_ERR;
235
#endif
236
0
    if (sock->socket != SOCK_ERR) {
237
#ifdef PHP_WIN32
238
      /* prevent more data from coming in */
239
      shutdown(sock->socket, SHUT_RD);
240
241
      /* try to make sure that the OS sends all data before we close the connection.
242
       * Essentially, we are waiting for the socket to become writeable, which means
243
       * that all pending data has been sent.
244
       * We use a small timeout which should encourage the OS to send the data,
245
       * but at the same time avoid hanging indefinitely.
246
       * */
247
      do {
248
        n = php_pollfd_for_ms(sock->socket, POLLOUT, 500);
249
      } while (n == -1 && php_socket_errno() == EINTR);
250
#endif
251
0
      closesocket(sock->socket);
252
0
      sock->socket = SOCK_ERR;
253
0
    }
254
255
0
  }
256
257
0
  pefree(sock, php_stream_is_persistent(stream));
258
259
0
  return 0;
260
0
}
261
262
static int php_sockop_flush(php_stream *stream)
263
0
{
264
#if 0
265
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
266
  return fsync(sock->socket);
267
#endif
268
0
  return 0;
269
0
}
270
271
static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb)
272
0
{
273
#ifdef ZEND_WIN32
274
  return 0;
275
#else
276
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
277
278
0
  return zend_fstat(sock->socket, &ssb->sb);
279
0
#endif
280
0
}
281
282
static inline int sock_sendto(php_netstream_data_t *sock, const char *buf, size_t buflen, int flags,
283
    struct sockaddr *addr, socklen_t addrlen
284
    )
285
0
{
286
0
  int ret;
287
0
  if (addr) {
288
0
    ret = sendto(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, addr, XP_SOCK_BUF_SIZE(addrlen));
289
290
0
    return (ret == SOCK_CONN_ERR) ? -1 : ret;
291
0
  }
292
#ifdef PHP_WIN32
293
  return ((ret = send(sock->socket, buf, buflen > INT_MAX ? INT_MAX : (int)buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret;
294
#else
295
0
  return ((ret = send(sock->socket, buf, buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret;
296
0
#endif
297
0
}
298
299
static inline int sock_recvfrom(php_netstream_data_t *sock, char *buf, size_t buflen, int flags,
300
    zend_string **textaddr,
301
    struct sockaddr **addr, socklen_t *addrlen
302
    )
303
0
{
304
0
  int ret;
305
0
  int want_addr = textaddr || addr;
306
307
0
  if (want_addr) {
308
0
    php_sockaddr_storage sa;
309
0
    socklen_t sl = sizeof(sa);
310
0
    ret = recvfrom(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, (struct sockaddr*)&sa, &sl);
311
0
    ret = (ret == SOCK_CONN_ERR) ? -1 : ret;
312
#ifdef PHP_WIN32
313
    /* POSIX discards excess bytes without signalling failure; emulate this on Windows */
314
    if (ret == -1 && WSAGetLastError() == WSAEMSGSIZE) {
315
      ret = buflen;
316
    }
317
#endif
318
0
    if (sl) {
319
0
      php_network_populate_name_from_sockaddr((struct sockaddr*)&sa, sl,
320
0
          textaddr, addr, addrlen);
321
0
    } else {
322
0
      if (textaddr) {
323
0
        *textaddr = ZSTR_EMPTY_ALLOC();
324
0
      }
325
0
      if (addr) {
326
0
        *addr = NULL;
327
0
        *addrlen = 0;
328
0
      }
329
0
    }
330
0
  } else {
331
0
    ret = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags);
332
0
    ret = (ret == SOCK_CONN_ERR) ? -1 : ret;
333
0
  }
334
335
0
  return ret;
336
0
}
337
338
static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
339
0
{
340
0
  int oldmode, flags;
341
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
342
0
  php_stream_xport_param *xparam;
343
344
0
  if (!sock) {
345
0
    return PHP_STREAM_OPTION_RETURN_NOTIMPL;
346
0
  }
347
348
0
  switch(option) {
349
0
    case PHP_STREAM_OPTION_CHECK_LIVENESS:
350
0
      {
351
0
        struct timeval tv;
352
0
        char buf;
353
0
        int alive = 1;
354
355
0
        if (value == -1) {
356
0
          if (sock->timeout.tv_sec == -1) {
357
0
            tv.tv_sec = FG(default_socket_timeout);
358
0
            tv.tv_usec = 0;
359
0
          } else {
360
0
            tv = sock->timeout;
361
0
          }
362
0
        } else {
363
0
          tv.tv_sec = value;
364
0
          tv.tv_usec = 0;
365
0
        }
366
367
0
        if (sock->socket == -1) {
368
0
          alive = 0;
369
0
        } else if (
370
0
          (
371
0
            value == 0 &&
372
0
            !(stream->flags & PHP_STREAM_FLAG_NO_IO) &&
373
0
            ((MSG_DONTWAIT != 0) || !sock->is_blocked)
374
0
          ) ||
375
0
          php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0
376
0
        ) {
377
          /* the poll() call was skipped if the socket is non-blocking (or MSG_DONTWAIT is available) and if the timeout is zero */
378
#ifdef PHP_WIN32
379
          int ret;
380
#else
381
0
          ssize_t ret;
382
0
#endif
383
384
0
          ret = recv(sock->socket, &buf, sizeof(buf), MSG_PEEK|MSG_DONTWAIT);
385
0
          if (0 == ret) {
386
            /* the counterpart did properly shutdown */
387
0
            alive = 0;
388
0
          } else if (0 > ret) {
389
0
            int err = php_socket_errno();
390
0
            if (err != EWOULDBLOCK && err != EMSGSIZE && err != EAGAIN) {
391
              /* there was an unrecoverable error */
392
0
              alive = 0;
393
0
            }
394
0
          }
395
0
        }
396
0
        return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
397
0
      }
398
399
0
    case PHP_STREAM_OPTION_BLOCKING:
400
0
      oldmode = sock->is_blocked;
401
0
      if (SUCCESS == php_set_sock_blocking(sock->socket, value)) {
402
0
        sock->is_blocked = value;
403
0
        return oldmode;
404
0
      }
405
0
      return PHP_STREAM_OPTION_RETURN_ERR;
406
407
0
    case PHP_STREAM_OPTION_READ_TIMEOUT:
408
0
      sock->timeout = *(struct timeval*)ptrparam;
409
0
      sock->timeout_event = false;
410
0
      return PHP_STREAM_OPTION_RETURN_OK;
411
412
0
    case PHP_STREAM_OPTION_META_DATA_API:
413
0
      add_assoc_bool((zval *)ptrparam, "timed_out", sock->timeout_event);
414
0
      add_assoc_bool((zval *)ptrparam, "blocked", sock->is_blocked);
415
0
      add_assoc_bool((zval *)ptrparam, "eof", stream->eof);
416
0
      return PHP_STREAM_OPTION_RETURN_OK;
417
418
0
    case PHP_STREAM_OPTION_XPORT_API:
419
0
      xparam = (php_stream_xport_param *)ptrparam;
420
421
0
      switch (xparam->op) {
422
0
        case STREAM_XPORT_OP_LISTEN:
423
0
          xparam->outputs.returncode = (listen(sock->socket, xparam->inputs.backlog) == 0) ?  0: -1;
424
0
          return PHP_STREAM_OPTION_RETURN_OK;
425
426
0
        case STREAM_XPORT_OP_GET_NAME:
427
0
          xparam->outputs.returncode = php_network_get_sock_name(sock->socket,
428
0
              xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
429
0
              xparam->want_addr ? &xparam->outputs.addr : NULL,
430
0
              xparam->want_addr ? &xparam->outputs.addrlen : NULL
431
0
              );
432
0
          return PHP_STREAM_OPTION_RETURN_OK;
433
434
0
        case STREAM_XPORT_OP_GET_PEER_NAME:
435
0
          xparam->outputs.returncode = php_network_get_peer_name(sock->socket,
436
0
              xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
437
0
              xparam->want_addr ? &xparam->outputs.addr : NULL,
438
0
              xparam->want_addr ? &xparam->outputs.addrlen : NULL
439
0
              );
440
0
          return PHP_STREAM_OPTION_RETURN_OK;
441
442
0
        case STREAM_XPORT_OP_SEND:
443
0
          flags = 0;
444
0
          if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) {
445
0
            flags |= MSG_OOB;
446
0
          }
447
0
          xparam->outputs.returncode = sock_sendto(sock,
448
0
              xparam->inputs.buf, xparam->inputs.buflen,
449
0
              flags,
450
0
              xparam->inputs.addr,
451
0
              xparam->inputs.addrlen);
452
0
          if (xparam->outputs.returncode == -1) {
453
0
            char *err = php_socket_strerror(php_socket_errno(), NULL, 0);
454
0
            php_stream_warn(stream, NetworkSendFailed, "%s", err);
455
0
            efree(err);
456
0
          }
457
0
          return PHP_STREAM_OPTION_RETURN_OK;
458
459
0
        case STREAM_XPORT_OP_RECV:
460
0
          flags = 0;
461
0
          if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) {
462
0
            flags |= MSG_OOB;
463
0
          }
464
0
          if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) {
465
0
            flags |= MSG_PEEK;
466
0
          }
467
0
          xparam->outputs.returncode = sock_recvfrom(sock,
468
0
              xparam->inputs.buf, xparam->inputs.buflen,
469
0
              flags,
470
0
              xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
471
0
              xparam->want_addr ? &xparam->outputs.addr : NULL,
472
0
              xparam->want_addr ? &xparam->outputs.addrlen : NULL
473
0
              );
474
0
          return PHP_STREAM_OPTION_RETURN_OK;
475
476
477
0
#ifdef HAVE_SHUTDOWN
478
# ifndef SHUT_RD
479
#  define SHUT_RD 0
480
# endif
481
# ifndef SHUT_WR
482
#  define SHUT_WR 1
483
# endif
484
# ifndef SHUT_RDWR
485
#  define SHUT_RDWR 2
486
# endif
487
0
        case STREAM_XPORT_OP_SHUTDOWN: {
488
0
          static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR};
489
490
0
          xparam->outputs.returncode = shutdown(sock->socket, shutdown_how[xparam->how]);
491
0
          return PHP_STREAM_OPTION_RETURN_OK;
492
0
        }
493
0
#endif
494
495
0
        default:
496
0
          break;
497
0
      }
498
0
  }
499
500
0
  return PHP_STREAM_OPTION_RETURN_NOTIMPL;
501
0
}
502
503
static int php_sockop_cast(php_stream *stream, int castas, void **ret)
504
0
{
505
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
506
507
0
  if (!sock) {
508
0
    return FAILURE;
509
0
  }
510
511
0
  switch(castas)  {
512
0
    case PHP_STREAM_AS_STDIO:
513
0
      if (ret) {
514
0
        *(FILE**)ret = fdopen(sock->socket, stream->mode);
515
0
        if (*ret)
516
0
          return SUCCESS;
517
0
        return FAILURE;
518
0
      }
519
0
      return SUCCESS;
520
0
    case PHP_STREAM_AS_FD_FOR_SELECT:
521
0
    case PHP_STREAM_AS_FD:
522
0
    case PHP_STREAM_AS_SOCKETD:
523
0
      if (ret)
524
0
        *(php_socket_t *)ret = sock->socket;
525
0
      return SUCCESS;
526
0
    case PHP_STREAM_AS_FD_FOR_COPY:
527
0
      if (ret) {
528
0
        php_io_fd *copy_fd = (php_io_fd *) ret;
529
0
        copy_fd->socket = sock->socket;
530
0
        copy_fd->fd_type = PHP_IO_FD_SOCKET;
531
0
        copy_fd->timeout = sock->timeout;
532
0
        copy_fd->is_blocked = sock->is_blocked;
533
0
      }
534
0
      return SUCCESS;
535
0
    default:
536
0
      return FAILURE;
537
0
  }
538
0
}
539
540
/* These may look identical, but we need them this way so that
541
 * we can determine which type of socket we are dealing with
542
 * by inspecting stream->ops.
543
 * A "useful" side-effect is that the user's scripts can then
544
 * make similar decisions using stream_get_meta_data.
545
 * */
546
const php_stream_ops php_stream_generic_socket_ops = {
547
  php_sockop_write, php_sockop_read,
548
  php_sockop_close, php_sockop_flush,
549
  "generic_socket",
550
  NULL, /* seek */
551
  php_sockop_cast,
552
  php_sockop_stat,
553
  php_sockop_set_option,
554
};
555
556
557
const php_stream_ops php_stream_socket_ops = {
558
  php_sockop_write, php_sockop_read,
559
  php_sockop_close, php_sockop_flush,
560
  "tcp_socket",
561
  NULL, /* seek */
562
  php_sockop_cast,
563
  php_sockop_stat,
564
  php_tcp_sockop_set_option,
565
};
566
567
static const php_stream_ops php_stream_udp_socket_ops = {
568
  php_sockop_write, php_sockop_read,
569
  php_sockop_close, php_sockop_flush,
570
  "udp_socket",
571
  NULL, /* seek */
572
  php_sockop_cast,
573
  php_sockop_stat,
574
  php_tcp_sockop_set_option,
575
};
576
577
#ifdef AF_UNIX
578
static const php_stream_ops php_stream_unix_socket_ops = {
579
  php_sockop_write, php_sockop_read,
580
  php_sockop_close, php_sockop_flush,
581
  "unix_socket",
582
  NULL, /* seek */
583
  php_sockop_cast,
584
  php_sockop_stat,
585
  php_tcp_sockop_set_option,
586
};
587
static const php_stream_ops php_stream_unixdg_socket_ops = {
588
  php_sockop_write, php_sockop_read,
589
  php_sockop_close, php_sockop_flush,
590
  "udg_socket",
591
  NULL, /* seek */
592
  php_sockop_cast,
593
  php_sockop_stat,
594
  php_tcp_sockop_set_option,
595
};
596
#endif
597
598
599
/* network socket operations */
600
601
#ifdef AF_UNIX
602
static inline int parse_unix_address(php_stream *stream, php_stream_xport_param *xparam,
603
    struct sockaddr_un *unix_addr)
604
0
{
605
0
  memset(unix_addr, 0, sizeof(*unix_addr));
606
0
  unix_addr->sun_family = AF_UNIX;
607
608
  /* Abstract namespace does not need to be NUL-terminated, while path-based
609
   * sockets should be. */
610
0
  bool is_abstract_ns = xparam->inputs.namelen > 0 && xparam->inputs.name[0] == '\0';
611
0
  unsigned long max_length = is_abstract_ns ? sizeof(unix_addr->sun_path) : sizeof(unix_addr->sun_path) - 1;
612
613
  /* we need to be binary safe on systems that support an abstract
614
   * namespace */
615
0
  if (xparam->inputs.namelen > max_length) {
616
    /* On linux, when the path begins with a NUL byte we are
617
     * referring to an abstract namespace.  In theory we should
618
     * allow an extra byte below, since we don't need the NULL.
619
     * BUT, to get into this branch of code, the name is too long,
620
     * so we don't care. */
621
0
    xparam->inputs.namelen = max_length;
622
0
    php_stream_notice(stream, InvalidPath,
623
0
        "socket path exceeded the maximum allowed length of %lu bytes and was truncated",
624
0
        max_length);
625
0
  }
626
627
0
  memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen);
628
629
0
  return 1;
630
0
}
631
#endif
632
633
static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err)
634
0
{
635
0
  const char *colon;
636
0
  char *host = NULL;
637
638
0
  if (memchr(str, '\0', str_len)) {
639
0
    *err = ZSTR_INIT_LITERAL("The hostname must not contain null bytes", 0);
640
0
    return NULL;
641
0
  }
642
643
0
#ifdef HAVE_IPV6
644
0
  if (*(str) == '[' && str_len > 1) {
645
    /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */
646
0
    const char *p = memchr(str + 1, ']', str_len - 2);
647
0
    if (!p || *(p + 1) != ':') {
648
0
      if (get_err) {
649
0
        *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str);
650
0
      }
651
0
      return NULL;
652
0
    }
653
0
    *portno = atoi(p + 2);
654
0
    return estrndup(str + 1, p - str - 1);
655
0
  }
656
0
#endif
657
0
  if (str_len) {
658
0
    colon = memchr(str, ':', str_len - 1);
659
0
  } else {
660
0
    colon = NULL;
661
0
  }
662
0
  if (colon) {
663
0
    *portno = atoi(colon + 1);
664
0
    host = estrndup(str, colon - str);
665
0
  } else {
666
0
    if (get_err) {
667
0
      *err = strpprintf(0, "Failed to parse address \"%s\"", str);
668
0
    }
669
0
    return NULL;
670
0
  }
671
672
0
  return host;
673
0
}
674
675
static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno)
676
0
{
677
0
  return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
678
0
}
679
680
static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
681
    php_stream_xport_param *xparam)
682
0
{
683
0
  char *host = NULL;
684
0
  int portno, err;
685
0
  long sockopts = STREAM_SOCKOP_NONE;
686
0
  zval *tmpzval = NULL;
687
0
  php_sockvals sockvals = {0};
688
689
0
#ifdef AF_UNIX
690
0
  if (PHP_STREAM_XPORT_IS_UNIX(stream)) {
691
0
    struct sockaddr_un unix_addr;
692
693
0
    sock->socket = socket(PF_UNIX, PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? SOCK_STREAM : SOCK_DGRAM, 0);
694
695
0
    if (sock->socket == SOCK_ERR) {
696
0
      if (xparam->want_errortext) {
697
0
        char errstr[256];
698
0
        xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s",
699
0
            PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? "" : " datagram",
700
0
            php_socket_strerror_s(errno, errstr, sizeof(errstr)));
701
0
      }
702
0
      return -1;
703
0
    }
704
705
0
    parse_unix_address(stream, xparam, &unix_addr);
706
707
0
    int result = bind(sock->socket, (const struct sockaddr *)&unix_addr,
708
0
      (socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen);
709
0
    if (result == -1 && xparam->want_errortext) {
710
0
      char errstr[256];
711
0
      xparam->outputs.error_text = strpprintf(0, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
712
0
    }
713
0
    return result;
714
0
  }
715
0
#endif
716
717
0
  host = parse_ip_address(xparam, &portno);
718
719
0
  if (host == NULL) {
720
0
    return -1;
721
0
  }
722
723
0
#ifdef IPV6_V6ONLY
724
0
  if (PHP_STREAM_CONTEXT(stream)
725
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL
726
0
    && Z_TYPE_P(tmpzval) != IS_NULL
727
0
  ) {
728
0
    sockopts |= STREAM_SOCKOP_IPV6_V6ONLY;
729
0
    sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval);
730
0
  }
731
0
#endif
732
733
0
#ifdef SO_REUSEPORT
734
0
  if (PHP_STREAM_CONTEXT(stream)
735
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL
736
0
    && zend_is_true(tmpzval)
737
0
  ) {
738
0
    sockopts |= STREAM_SOCKOP_SO_REUSEPORT;
739
0
  }
740
0
#endif
741
742
0
#ifdef SO_REUSEADDR
743
  /* SO_REUSEADDR is enabled by default so this option is just to disable it if set to false. */
744
0
  if (!PHP_STREAM_CONTEXT(stream)
745
0
    || (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseaddr")) == NULL
746
0
    || zend_is_true(tmpzval)
747
0
  ) {
748
0
    sockopts |= STREAM_SOCKOP_SO_REUSEADDR;
749
0
  }
750
0
#endif
751
752
0
#ifdef SO_BROADCAST
753
0
  if (PHP_STREAM_XPORT_IS_UDP(stream) /* SO_BROADCAST is only applicable for UDP */
754
0
    && PHP_STREAM_CONTEXT(stream)
755
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
756
0
    && zend_is_true(tmpzval)
757
0
  ) {
758
0
    sockopts |= STREAM_SOCKOP_SO_BROADCAST;
759
0
  }
760
0
#endif
761
762
0
#ifdef SO_KEEPALIVE
763
0
  if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
764
0
    && PHP_STREAM_CONTEXT(stream)
765
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_keepalive")) != NULL
766
0
    && zend_is_true(tmpzval)
767
0
  ) {
768
0
    sockopts |= STREAM_SOCKOP_SO_KEEPALIVE;
769
0
  }
770
0
#endif
771
772
  /* Parse TCP keepalive parameters - only for TCP streams */
773
0
  if (PHP_STREAM_XPORT_IS_TCP(stream)) {
774
0
#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
775
0
    if (PHP_STREAM_CONTEXT(stream)
776
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle")) != NULL
777
0
    ) {
778
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
779
0
      sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval);
780
0
    }
781
0
#endif
782
783
0
#ifdef TCP_KEEPINTVL
784
0
    if (PHP_STREAM_CONTEXT(stream)
785
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepintvl")) != NULL
786
0
    ) {
787
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPINTVL;
788
0
      sockvals.keepalive.keepintvl = (int)zval_get_long(tmpzval);
789
0
    }
790
0
#endif
791
792
0
#ifdef TCP_KEEPCNT
793
0
    if (PHP_STREAM_CONTEXT(stream)
794
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepcnt")) != NULL
795
0
    ) {
796
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPCNT;
797
0
      sockvals.keepalive.keepcnt = (int)zval_get_long(tmpzval);
798
0
    }
799
0
#endif
800
0
  }
801
802
0
  sock->socket = php_network_bind_socket_to_local_addr_ex(host, portno,
803
0
      PHP_STREAM_XPORT_IS_UDP(stream) ? SOCK_DGRAM : SOCK_STREAM,
804
0
      sockopts,
805
0
      sockvals.mask ? &sockvals : NULL,
806
0
      xparam->want_errortext ? &xparam->outputs.error_text : NULL,
807
0
      &err
808
0
      );
809
810
0
  if (host) {
811
0
    efree(host);
812
0
  }
813
814
0
  return sock->socket == -1 ? -1 : 0;
815
0
}
816
817
static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock,
818
    php_stream_xport_param *xparam)
819
0
{
820
0
  char *host = NULL, *bindto = NULL;
821
0
  int portno, bindport = 0;
822
0
  int err = 0;
823
0
  int ret;
824
0
  zval *tmpzval = NULL;
825
0
  long sockopts = STREAM_SOCKOP_NONE;
826
0
  php_sockvals sockvals = {0};
827
828
0
#ifdef AF_UNIX
829
0
  if (PHP_STREAM_XPORT_IS_UNIX(stream)) {
830
0
    struct sockaddr_un unix_addr;
831
832
0
    sock->socket = socket(PF_UNIX, PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? SOCK_STREAM : SOCK_DGRAM, 0);
833
834
0
    if (sock->socket == SOCK_ERR) {
835
0
      if (xparam->want_errortext) {
836
0
        xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket");
837
0
      }
838
0
      return -1;
839
0
    }
840
841
0
    parse_unix_address(stream, xparam, &unix_addr);
842
843
0
    ret = php_network_connect_socket(sock->socket,
844
0
        (const struct sockaddr *)&unix_addr, (socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen,
845
0
        xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout,
846
0
        xparam->want_errortext ? &xparam->outputs.error_text : NULL,
847
0
        &err);
848
849
0
    xparam->outputs.error_code = err;
850
851
0
    goto out;
852
0
  }
853
0
#endif
854
855
0
  host = parse_ip_address(xparam, &portno);
856
857
0
  if (host == NULL) {
858
0
    return -1;
859
0
  }
860
861
0
  if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) {
862
0
    if (Z_TYPE_P(tmpzval) != IS_STRING) {
863
0
      if (xparam->want_errortext) {
864
0
        xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string.");
865
0
      }
866
0
      efree(host);
867
0
      return -1;
868
0
    }
869
0
    bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text);
870
0
  }
871
872
0
#ifdef SO_BROADCAST
873
0
  if (PHP_STREAM_XPORT_IS_UDP(stream) /* SO_BROADCAST is only applicable for UDP */
874
0
    && PHP_STREAM_CONTEXT(stream)
875
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
876
0
    && zend_is_true(tmpzval)
877
0
  ) {
878
0
    sockopts |= STREAM_SOCKOP_SO_BROADCAST;
879
0
  }
880
0
#endif
881
882
0
  if (PHP_STREAM_XPORT_IS_TCP(stream) /* TCP_NODELAY is only applicable for TCP */
883
0
    && PHP_STREAM_CONTEXT(stream)
884
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL
885
0
    && zend_is_true(tmpzval)
886
0
  ) {
887
0
    sockopts |= STREAM_SOCKOP_TCP_NODELAY;
888
0
  }
889
890
0
#ifdef SO_KEEPALIVE
891
0
  if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
892
0
    && PHP_STREAM_CONTEXT(stream)
893
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_keepalive")) != NULL
894
0
    && zend_is_true(tmpzval)
895
0
  ) {
896
0
    sockopts |= STREAM_SOCKOP_SO_KEEPALIVE;
897
0
  }
898
0
#endif
899
900
  /* Parse TCP keepalive parameters - only for TCP streams */
901
0
  if (PHP_STREAM_XPORT_IS_TCP(stream)) {
902
0
#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
903
0
    if (PHP_STREAM_CONTEXT(stream)
904
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle")) != NULL
905
0
    ) {
906
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
907
0
      sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval);
908
0
    }
909
0
#endif
910
911
0
#ifdef TCP_KEEPINTVL
912
0
    if (PHP_STREAM_CONTEXT(stream)
913
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepintvl")) != NULL
914
0
    ) {
915
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPINTVL;
916
0
      sockvals.keepalive.keepintvl = (int)zval_get_long(tmpzval);
917
0
    }
918
0
#endif
919
920
0
#ifdef TCP_KEEPCNT
921
0
    if (PHP_STREAM_CONTEXT(stream)
922
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepcnt")) != NULL
923
0
    ) {
924
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPCNT;
925
0
      sockvals.keepalive.keepcnt = (int)zval_get_long(tmpzval);
926
0
    }
927
0
#endif
928
0
  }
929
930
  /* Note: the test here for php_stream_udp_socket_ops is important, because we
931
   * want the default to be TCP sockets so that the openssl extension can
932
   * re-use this code. */
933
934
0
  sock->socket = php_network_connect_socket_to_host_ex(host, portno,
935
0
      PHP_STREAM_XPORT_IS_UDP(stream) ? SOCK_DGRAM : SOCK_STREAM,
936
0
      xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC,
937
0
      xparam->inputs.timeout,
938
0
      xparam->want_errortext ? &xparam->outputs.error_text : NULL,
939
0
      &err,
940
0
      bindto,
941
0
      bindport,
942
0
      sockopts,
943
0
      sockvals.mask ? &sockvals : NULL
944
0
      );
945
946
0
  ret = sock->socket == -1 ? -1 : 0;
947
0
  xparam->outputs.error_code = err;
948
949
0
  if (host) {
950
0
    efree(host);
951
0
  }
952
0
  if (bindto) {
953
0
    efree(bindto);
954
0
  }
955
956
0
#ifdef AF_UNIX
957
0
out:
958
0
#endif
959
960
0
  if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) {
961
    /* indicates pending connection */
962
0
    return 1;
963
0
  }
964
965
0
  return ret;
966
0
}
967
968
static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock,
969
    php_stream_xport_param *xparam STREAMS_DC)
970
0
{
971
0
  php_sockvals sockvals = {0};
972
0
  zval *tmpzval = NULL;
973
974
0
  xparam->outputs.client = NULL;
975
976
0
  if (PHP_STREAM_CONTEXT(stream)) {
977
0
    tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay");
978
0
    if (tmpzval != NULL && zend_is_true(tmpzval)) {
979
0
      sockvals.mask |= PHP_SOCKVAL_TCP_NODELAY;
980
0
      sockvals.tcp_nodelay = 1;
981
0
    }
982
0
    tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle");
983
0
    if (tmpzval != NULL) {
984
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
985
0
      sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval);
986
0
    }
987
0
  }
988
989
0
  php_socket_t clisock = php_network_accept_incoming_ex(sock->socket,
990
0
    xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
991
0
    xparam->want_addr ? &xparam->outputs.addr : NULL,
992
0
    xparam->want_addr ? &xparam->outputs.addrlen : NULL,
993
0
    xparam->inputs.timeout,
994
0
    xparam->want_errortext ? &xparam->outputs.error_text : NULL,
995
0
    &xparam->outputs.error_code,
996
0
    &sockvals);
997
998
0
  if (clisock != SOCK_ERR) {
999
0
    php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata));
1000
1001
0
    memcpy(clisockdata, sock, sizeof(*clisockdata));
1002
0
    clisockdata->socket = clisock;
1003
0
#ifdef __linux__
1004
    /* O_NONBLOCK is not inherited on Linux */
1005
0
    clisockdata->is_blocked = true;
1006
0
#endif
1007
1008
0
    xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+");
1009
0
    if (xparam->outputs.client) {
1010
0
      xparam->outputs.client->ctx = stream->ctx;
1011
0
      if (stream->ctx) {
1012
0
        GC_ADDREF(stream->ctx);
1013
0
      }
1014
0
    }
1015
0
  }
1016
1017
0
  return xparam->outputs.client == NULL ? -1 : 0;
1018
0
}
1019
1020
static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
1021
0
{
1022
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
1023
0
  php_stream_xport_param *xparam;
1024
1025
0
  switch(option) {
1026
0
    case PHP_STREAM_OPTION_XPORT_API:
1027
0
      xparam = (php_stream_xport_param *)ptrparam;
1028
1029
0
      switch(xparam->op) {
1030
0
        case STREAM_XPORT_OP_CONNECT:
1031
0
        case STREAM_XPORT_OP_CONNECT_ASYNC:
1032
0
          xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam);
1033
0
          return PHP_STREAM_OPTION_RETURN_OK;
1034
1035
0
        case STREAM_XPORT_OP_BIND:
1036
0
          xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam);
1037
0
          return PHP_STREAM_OPTION_RETURN_OK;
1038
1039
1040
0
        case STREAM_XPORT_OP_ACCEPT:
1041
0
          xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC);
1042
0
          return PHP_STREAM_OPTION_RETURN_OK;
1043
0
        default:
1044
          /* fall through */
1045
0
          ;
1046
0
      }
1047
0
  }
1048
0
  return php_sockop_set_option(stream, option, value, ptrparam);
1049
0
}
1050
1051
1052
PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen,
1053
    const char *resourcename, size_t resourcenamelen,
1054
    const char *persistent_id, int options, int flags,
1055
    struct timeval *timeout,
1056
    php_stream_context *context STREAMS_DC)
1057
0
{
1058
0
  php_stream *stream = NULL;
1059
0
  php_netstream_data_t *sock;
1060
0
  const php_stream_ops *ops;
1061
1062
  /* which type of socket ? */
1063
0
  if (strncmp(proto, "tcp", protolen) == 0) {
1064
0
    ops = &php_stream_socket_ops;
1065
0
  } else if (strncmp(proto, "udp", protolen) == 0) {
1066
0
    ops = &php_stream_udp_socket_ops;
1067
0
  }
1068
0
#ifdef AF_UNIX
1069
0
  else if (strncmp(proto, "unix", protolen) == 0) {
1070
0
    ops = &php_stream_unix_socket_ops;
1071
0
  } else if (strncmp(proto, "udg", protolen) == 0) {
1072
0
    ops = &php_stream_unixdg_socket_ops;
1073
0
  }
1074
0
#endif
1075
0
  else {
1076
    /* should never happen */
1077
0
    return NULL;
1078
0
  }
1079
1080
0
  sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0);
1081
0
  memset(sock, 0, sizeof(php_netstream_data_t));
1082
1083
0
  sock->is_blocked = true;
1084
0
  sock->timeout.tv_sec = FG(default_socket_timeout);
1085
0
  sock->timeout.tv_usec = 0;
1086
1087
  /* we don't know the socket until we have determined if we are binding or
1088
   * connecting */
1089
0
  sock->socket = -1;
1090
1091
0
  stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+");
1092
1093
0
  if (stream == NULL) {
1094
0
    pefree(sock, persistent_id ? 1 : 0);
1095
0
    return NULL;
1096
0
  }
1097
1098
0
  return stream;
1099
0
}