Coverage Report

Created: 2026-06-02 06:36

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_network.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
    default:
527
0
      return FAILURE;
528
0
  }
529
0
}
530
/* }}} */
531
532
/* These may look identical, but we need them this way so that
533
 * we can determine which type of socket we are dealing with
534
 * by inspecting stream->ops.
535
 * A "useful" side-effect is that the user's scripts can then
536
 * make similar decisions using stream_get_meta_data.
537
 * */
538
const php_stream_ops php_stream_generic_socket_ops = {
539
  php_sockop_write, php_sockop_read,
540
  php_sockop_close, php_sockop_flush,
541
  "generic_socket",
542
  NULL, /* seek */
543
  php_sockop_cast,
544
  php_sockop_stat,
545
  php_sockop_set_option,
546
};
547
548
549
const php_stream_ops php_stream_socket_ops = {
550
  php_sockop_write, php_sockop_read,
551
  php_sockop_close, php_sockop_flush,
552
  "tcp_socket",
553
  NULL, /* seek */
554
  php_sockop_cast,
555
  php_sockop_stat,
556
  php_tcp_sockop_set_option,
557
};
558
559
static const php_stream_ops php_stream_udp_socket_ops = {
560
  php_sockop_write, php_sockop_read,
561
  php_sockop_close, php_sockop_flush,
562
  "udp_socket",
563
  NULL, /* seek */
564
  php_sockop_cast,
565
  php_sockop_stat,
566
  php_tcp_sockop_set_option,
567
};
568
569
#ifdef AF_UNIX
570
static const php_stream_ops php_stream_unix_socket_ops = {
571
  php_sockop_write, php_sockop_read,
572
  php_sockop_close, php_sockop_flush,
573
  "unix_socket",
574
  NULL, /* seek */
575
  php_sockop_cast,
576
  php_sockop_stat,
577
  php_tcp_sockop_set_option,
578
};
579
static const php_stream_ops php_stream_unixdg_socket_ops = {
580
  php_sockop_write, php_sockop_read,
581
  php_sockop_close, php_sockop_flush,
582
  "udg_socket",
583
  NULL, /* seek */
584
  php_sockop_cast,
585
  php_sockop_stat,
586
  php_tcp_sockop_set_option,
587
};
588
#endif
589
590
591
/* network socket operations */
592
593
#ifdef AF_UNIX
594
static inline int parse_unix_address(php_stream *stream, php_stream_xport_param *xparam,
595
    struct sockaddr_un *unix_addr)
596
0
{
597
0
  memset(unix_addr, 0, sizeof(*unix_addr));
598
0
  unix_addr->sun_family = AF_UNIX;
599
600
  /* Abstract namespace does not need to be NUL-terminated, while path-based
601
   * sockets should be. */
602
0
  bool is_abstract_ns = xparam->inputs.namelen > 0 && xparam->inputs.name[0] == '\0';
603
0
  unsigned long max_length = is_abstract_ns ? sizeof(unix_addr->sun_path) : sizeof(unix_addr->sun_path) - 1;
604
605
  /* we need to be binary safe on systems that support an abstract
606
   * namespace */
607
0
  if (xparam->inputs.namelen > max_length) {
608
    /* On linux, when the path begins with a NUL byte we are
609
     * referring to an abstract namespace.  In theory we should
610
     * allow an extra byte below, since we don't need the NULL.
611
     * BUT, to get into this branch of code, the name is too long,
612
     * so we don't care. */
613
0
    xparam->inputs.namelen = max_length;
614
0
    php_stream_notice(stream, InvalidPath,
615
0
        "socket path exceeded the maximum allowed length of %lu bytes and was truncated",
616
0
        max_length);
617
0
  }
618
619
0
  memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen);
620
621
0
  return 1;
622
0
}
623
#endif
624
625
static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err)
626
0
{
627
0
  const char *colon;
628
0
  char *host = NULL;
629
630
0
  if (memchr(str, '\0', str_len)) {
631
0
    *err = ZSTR_INIT_LITERAL("The hostname must not contain null bytes", 0);
632
0
    return NULL;
633
0
  }
634
635
0
#ifdef HAVE_IPV6
636
0
  if (*(str) == '[' && str_len > 1) {
637
    /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */
638
0
    const char *p = memchr(str + 1, ']', str_len - 2);
639
0
    if (!p || *(p + 1) != ':') {
640
0
      if (get_err) {
641
0
        *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str);
642
0
      }
643
0
      return NULL;
644
0
    }
645
0
    *portno = atoi(p + 2);
646
0
    return estrndup(str + 1, p - str - 1);
647
0
  }
648
0
#endif
649
0
  if (str_len) {
650
0
    colon = memchr(str, ':', str_len - 1);
651
0
  } else {
652
0
    colon = NULL;
653
0
  }
654
0
  if (colon) {
655
0
    *portno = atoi(colon + 1);
656
0
    host = estrndup(str, colon - str);
657
0
  } else {
658
0
    if (get_err) {
659
0
      *err = strpprintf(0, "Failed to parse address \"%s\"", str);
660
0
    }
661
0
    return NULL;
662
0
  }
663
664
0
  return host;
665
0
}
666
667
static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno)
668
0
{
669
0
  return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
670
0
}
671
672
static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
673
    php_stream_xport_param *xparam)
674
0
{
675
0
  char *host = NULL;
676
0
  int portno, err;
677
0
  long sockopts = STREAM_SOCKOP_NONE;
678
0
  zval *tmpzval = NULL;
679
0
  php_sockvals sockvals = {0};
680
681
0
#ifdef AF_UNIX
682
0
  if (PHP_STREAM_XPORT_IS_UNIX(stream)) {
683
0
    struct sockaddr_un unix_addr;
684
685
0
    sock->socket = socket(PF_UNIX, PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? SOCK_STREAM : SOCK_DGRAM, 0);
686
687
0
    if (sock->socket == SOCK_ERR) {
688
0
      if (xparam->want_errortext) {
689
0
        char errstr[256];
690
0
        xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s",
691
0
            PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? "" : " datagram",
692
0
            php_socket_strerror_s(errno, errstr, sizeof(errstr)));
693
0
      }
694
0
      return -1;
695
0
    }
696
697
0
    parse_unix_address(stream, xparam, &unix_addr);
698
699
0
    int result = bind(sock->socket, (const struct sockaddr *)&unix_addr,
700
0
      (socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen);
701
0
    if (result == -1 && xparam->want_errortext) {
702
0
      char errstr[256];
703
0
      xparam->outputs.error_text = strpprintf(0, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
704
0
    }
705
0
    return result;
706
0
  }
707
0
#endif
708
709
0
  host = parse_ip_address(xparam, &portno);
710
711
0
  if (host == NULL) {
712
0
    return -1;
713
0
  }
714
715
0
#ifdef IPV6_V6ONLY
716
0
  if (PHP_STREAM_CONTEXT(stream)
717
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL
718
0
    && Z_TYPE_P(tmpzval) != IS_NULL
719
0
  ) {
720
0
    sockopts |= STREAM_SOCKOP_IPV6_V6ONLY;
721
0
    sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval);
722
0
  }
723
0
#endif
724
725
0
#ifdef SO_REUSEPORT
726
0
  if (PHP_STREAM_CONTEXT(stream)
727
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL
728
0
    && zend_is_true(tmpzval)
729
0
  ) {
730
0
    sockopts |= STREAM_SOCKOP_SO_REUSEPORT;
731
0
  }
732
0
#endif
733
734
0
#ifdef SO_REUSEADDR
735
  /* SO_REUSEADDR is enabled by default so this option is just to disable it if set to false. */
736
0
  if (!PHP_STREAM_CONTEXT(stream)
737
0
    || (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseaddr")) == NULL
738
0
    || zend_is_true(tmpzval)
739
0
  ) {
740
0
    sockopts |= STREAM_SOCKOP_SO_REUSEADDR;
741
0
  }
742
0
#endif
743
744
0
#ifdef SO_BROADCAST
745
0
  if (PHP_STREAM_XPORT_IS_UDP(stream) /* SO_BROADCAST is only applicable for UDP */
746
0
    && PHP_STREAM_CONTEXT(stream)
747
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
748
0
    && zend_is_true(tmpzval)
749
0
  ) {
750
0
    sockopts |= STREAM_SOCKOP_SO_BROADCAST;
751
0
  }
752
0
#endif
753
754
0
#ifdef SO_KEEPALIVE
755
0
  if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
756
0
    && PHP_STREAM_CONTEXT(stream)
757
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_keepalive")) != NULL
758
0
    && zend_is_true(tmpzval)
759
0
  ) {
760
0
    sockopts |= STREAM_SOCKOP_SO_KEEPALIVE;
761
0
  }
762
0
#endif
763
764
  /* Parse TCP keepalive parameters - only for TCP streams */
765
0
  if (PHP_STREAM_XPORT_IS_TCP(stream)) {
766
0
#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
767
0
    if (PHP_STREAM_CONTEXT(stream)
768
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle")) != NULL
769
0
    ) {
770
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
771
0
      sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval);
772
0
    }
773
0
#endif
774
775
0
#ifdef TCP_KEEPINTVL
776
0
    if (PHP_STREAM_CONTEXT(stream)
777
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepintvl")) != NULL
778
0
    ) {
779
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPINTVL;
780
0
      sockvals.keepalive.keepintvl = (int)zval_get_long(tmpzval);
781
0
    }
782
0
#endif
783
784
0
#ifdef TCP_KEEPCNT
785
0
    if (PHP_STREAM_CONTEXT(stream)
786
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepcnt")) != NULL
787
0
    ) {
788
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPCNT;
789
0
      sockvals.keepalive.keepcnt = (int)zval_get_long(tmpzval);
790
0
    }
791
0
#endif
792
0
  }
793
794
0
  sock->socket = php_network_bind_socket_to_local_addr_ex(host, portno,
795
0
      PHP_STREAM_XPORT_IS_UDP(stream) ? SOCK_DGRAM : SOCK_STREAM,
796
0
      sockopts,
797
0
      sockvals.mask ? &sockvals : NULL,
798
0
      xparam->want_errortext ? &xparam->outputs.error_text : NULL,
799
0
      &err
800
0
      );
801
802
0
  if (host) {
803
0
    efree(host);
804
0
  }
805
806
0
  return sock->socket == -1 ? -1 : 0;
807
0
}
808
809
static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock,
810
    php_stream_xport_param *xparam)
811
0
{
812
0
  char *host = NULL, *bindto = NULL;
813
0
  int portno, bindport = 0;
814
0
  int err = 0;
815
0
  int ret;
816
0
  zval *tmpzval = NULL;
817
0
  long sockopts = STREAM_SOCKOP_NONE;
818
0
  php_sockvals sockvals = {0};
819
820
0
#ifdef AF_UNIX
821
0
  if (PHP_STREAM_XPORT_IS_UNIX(stream)) {
822
0
    struct sockaddr_un unix_addr;
823
824
0
    sock->socket = socket(PF_UNIX, PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? SOCK_STREAM : SOCK_DGRAM, 0);
825
826
0
    if (sock->socket == SOCK_ERR) {
827
0
      if (xparam->want_errortext) {
828
0
        xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket");
829
0
      }
830
0
      return -1;
831
0
    }
832
833
0
    parse_unix_address(stream, xparam, &unix_addr);
834
835
0
    ret = php_network_connect_socket(sock->socket,
836
0
        (const struct sockaddr *)&unix_addr, (socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen,
837
0
        xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout,
838
0
        xparam->want_errortext ? &xparam->outputs.error_text : NULL,
839
0
        &err);
840
841
0
    xparam->outputs.error_code = err;
842
843
0
    goto out;
844
0
  }
845
0
#endif
846
847
0
  host = parse_ip_address(xparam, &portno);
848
849
0
  if (host == NULL) {
850
0
    return -1;
851
0
  }
852
853
0
  if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) {
854
0
    if (Z_TYPE_P(tmpzval) != IS_STRING) {
855
0
      if (xparam->want_errortext) {
856
0
        xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string.");
857
0
      }
858
0
      efree(host);
859
0
      return -1;
860
0
    }
861
0
    bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text);
862
0
  }
863
864
0
#ifdef SO_BROADCAST
865
0
  if (PHP_STREAM_XPORT_IS_UDP(stream) /* SO_BROADCAST is only applicable for UDP */
866
0
    && PHP_STREAM_CONTEXT(stream)
867
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
868
0
    && zend_is_true(tmpzval)
869
0
  ) {
870
0
    sockopts |= STREAM_SOCKOP_SO_BROADCAST;
871
0
  }
872
0
#endif
873
874
0
  if (PHP_STREAM_XPORT_IS_TCP(stream) /* TCP_NODELAY is only applicable for TCP */
875
0
    && PHP_STREAM_CONTEXT(stream)
876
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL
877
0
    && zend_is_true(tmpzval)
878
0
  ) {
879
0
    sockopts |= STREAM_SOCKOP_TCP_NODELAY;
880
0
  }
881
882
0
#ifdef SO_KEEPALIVE
883
0
  if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
884
0
    && PHP_STREAM_CONTEXT(stream)
885
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_keepalive")) != NULL
886
0
    && zend_is_true(tmpzval)
887
0
  ) {
888
0
    sockopts |= STREAM_SOCKOP_SO_KEEPALIVE;
889
0
  }
890
0
#endif
891
892
  /* Parse TCP keepalive parameters - only for TCP streams */
893
0
  if (PHP_STREAM_XPORT_IS_TCP(stream)) {
894
0
#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
895
0
    if (PHP_STREAM_CONTEXT(stream)
896
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle")) != NULL
897
0
    ) {
898
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
899
0
      sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval);
900
0
    }
901
0
#endif
902
903
0
#ifdef TCP_KEEPINTVL
904
0
    if (PHP_STREAM_CONTEXT(stream)
905
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepintvl")) != NULL
906
0
    ) {
907
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPINTVL;
908
0
      sockvals.keepalive.keepintvl = (int)zval_get_long(tmpzval);
909
0
    }
910
0
#endif
911
912
0
#ifdef TCP_KEEPCNT
913
0
    if (PHP_STREAM_CONTEXT(stream)
914
0
      && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepcnt")) != NULL
915
0
    ) {
916
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPCNT;
917
0
      sockvals.keepalive.keepcnt = (int)zval_get_long(tmpzval);
918
0
    }
919
0
#endif
920
0
  }
921
922
  /* Note: the test here for php_stream_udp_socket_ops is important, because we
923
   * want the default to be TCP sockets so that the openssl extension can
924
   * re-use this code. */
925
926
0
  sock->socket = php_network_connect_socket_to_host_ex(host, portno,
927
0
      PHP_STREAM_XPORT_IS_UDP(stream) ? SOCK_DGRAM : SOCK_STREAM,
928
0
      xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC,
929
0
      xparam->inputs.timeout,
930
0
      xparam->want_errortext ? &xparam->outputs.error_text : NULL,
931
0
      &err,
932
0
      bindto,
933
0
      bindport,
934
0
      sockopts,
935
0
      sockvals.mask ? &sockvals : NULL
936
0
      );
937
938
0
  ret = sock->socket == -1 ? -1 : 0;
939
0
  xparam->outputs.error_code = err;
940
941
0
  if (host) {
942
0
    efree(host);
943
0
  }
944
0
  if (bindto) {
945
0
    efree(bindto);
946
0
  }
947
948
0
#ifdef AF_UNIX
949
0
out:
950
0
#endif
951
952
0
  if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) {
953
    /* indicates pending connection */
954
0
    return 1;
955
0
  }
956
957
0
  return ret;
958
0
}
959
960
static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock,
961
    php_stream_xport_param *xparam STREAMS_DC)
962
0
{
963
0
  php_sockvals sockvals = {0};
964
0
  zval *tmpzval = NULL;
965
966
0
  xparam->outputs.client = NULL;
967
968
0
  if (PHP_STREAM_CONTEXT(stream)) {
969
0
    tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay");
970
0
    if (tmpzval != NULL && zend_is_true(tmpzval)) {
971
0
      sockvals.mask |= PHP_SOCKVAL_TCP_NODELAY;
972
0
      sockvals.tcp_nodelay = 1;
973
0
    }
974
0
    tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle");
975
0
    if (tmpzval != NULL) {
976
0
      sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
977
0
      sockvals.keepalive.keepidle = (int)zval_get_long(tmpzval);
978
0
    }
979
0
  }
980
981
0
  php_socket_t clisock = php_network_accept_incoming_ex(sock->socket,
982
0
    xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
983
0
    xparam->want_addr ? &xparam->outputs.addr : NULL,
984
0
    xparam->want_addr ? &xparam->outputs.addrlen : NULL,
985
0
    xparam->inputs.timeout,
986
0
    xparam->want_errortext ? &xparam->outputs.error_text : NULL,
987
0
    &xparam->outputs.error_code,
988
0
    &sockvals);
989
990
0
  if (clisock != SOCK_ERR) {
991
0
    php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata));
992
993
0
    memcpy(clisockdata, sock, sizeof(*clisockdata));
994
0
    clisockdata->socket = clisock;
995
0
#ifdef __linux__
996
    /* O_NONBLOCK is not inherited on Linux */
997
0
    clisockdata->is_blocked = true;
998
0
#endif
999
1000
0
    xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+");
1001
0
    if (xparam->outputs.client) {
1002
0
      xparam->outputs.client->ctx = stream->ctx;
1003
0
      if (stream->ctx) {
1004
0
        GC_ADDREF(stream->ctx);
1005
0
      }
1006
0
    }
1007
0
  }
1008
1009
0
  return xparam->outputs.client == NULL ? -1 : 0;
1010
0
}
1011
1012
static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
1013
0
{
1014
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
1015
0
  php_stream_xport_param *xparam;
1016
1017
0
  switch(option) {
1018
0
    case PHP_STREAM_OPTION_XPORT_API:
1019
0
      xparam = (php_stream_xport_param *)ptrparam;
1020
1021
0
      switch(xparam->op) {
1022
0
        case STREAM_XPORT_OP_CONNECT:
1023
0
        case STREAM_XPORT_OP_CONNECT_ASYNC:
1024
0
          xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam);
1025
0
          return PHP_STREAM_OPTION_RETURN_OK;
1026
1027
0
        case STREAM_XPORT_OP_BIND:
1028
0
          xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam);
1029
0
          return PHP_STREAM_OPTION_RETURN_OK;
1030
1031
1032
0
        case STREAM_XPORT_OP_ACCEPT:
1033
0
          xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC);
1034
0
          return PHP_STREAM_OPTION_RETURN_OK;
1035
0
        default:
1036
          /* fall through */
1037
0
          ;
1038
0
      }
1039
0
  }
1040
0
  return php_sockop_set_option(stream, option, value, ptrparam);
1041
0
}
1042
1043
1044
PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen,
1045
    const char *resourcename, size_t resourcenamelen,
1046
    const char *persistent_id, int options, int flags,
1047
    struct timeval *timeout,
1048
    php_stream_context *context STREAMS_DC)
1049
0
{
1050
0
  php_stream *stream = NULL;
1051
0
  php_netstream_data_t *sock;
1052
0
  const php_stream_ops *ops;
1053
1054
  /* which type of socket ? */
1055
0
  if (strncmp(proto, "tcp", protolen) == 0) {
1056
0
    ops = &php_stream_socket_ops;
1057
0
  } else if (strncmp(proto, "udp", protolen) == 0) {
1058
0
    ops = &php_stream_udp_socket_ops;
1059
0
  }
1060
0
#ifdef AF_UNIX
1061
0
  else if (strncmp(proto, "unix", protolen) == 0) {
1062
0
    ops = &php_stream_unix_socket_ops;
1063
0
  } else if (strncmp(proto, "udg", protolen) == 0) {
1064
0
    ops = &php_stream_unixdg_socket_ops;
1065
0
  }
1066
0
#endif
1067
0
  else {
1068
    /* should never happen */
1069
0
    return NULL;
1070
0
  }
1071
1072
0
  sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0);
1073
0
  memset(sock, 0, sizeof(php_netstream_data_t));
1074
1075
0
  sock->is_blocked = true;
1076
0
  sock->timeout.tv_sec = FG(default_socket_timeout);
1077
0
  sock->timeout.tv_usec = 0;
1078
1079
  /* we don't know the socket until we have determined if we are binding or
1080
   * connecting */
1081
0
  sock->socket = -1;
1082
1083
0
  stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+");
1084
1085
0
  if (stream == NULL) {
1086
0
    pefree(sock, persistent_id ? 1 : 0);
1087
0
    return NULL;
1088
0
  }
1089
1090
0
  return stream;
1091
0
}