Coverage Report

Created: 2025-12-31 07:28

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