Coverage Report

Created: 2025-07-23 06:33

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