Coverage Report

Created: 2025-06-13 06:43

/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
#ifdef HAVE_IPV6
624
0
  char *p;
625
626
0
  if (*(str) == '[' && str_len > 1) {
627
    /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */
628
0
    p = memchr(str + 1, ']', str_len - 2);
629
0
    if (!p || *(p + 1) != ':') {
630
0
      if (get_err) {
631
0
        *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str);
632
0
      }
633
0
      return NULL;
634
0
    }
635
0
    *portno = atoi(p + 2);
636
0
    return estrndup(str + 1, p - str - 1);
637
0
  }
638
0
#endif
639
0
  if (str_len) {
640
0
    colon = memchr(str, ':', str_len - 1);
641
0
  } else {
642
0
    colon = NULL;
643
0
  }
644
0
  if (colon) {
645
0
    *portno = atoi(colon + 1);
646
0
    host = estrndup(str, colon - str);
647
0
  } else {
648
0
    if (get_err) {
649
0
      *err = strpprintf(0, "Failed to parse address \"%s\"", str);
650
0
    }
651
0
    return NULL;
652
0
  }
653
654
0
  return host;
655
0
}
656
657
static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno)
658
0
{
659
0
  return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
660
0
}
661
662
static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
663
    php_stream_xport_param *xparam)
664
0
{
665
0
  char *host = NULL;
666
0
  int portno, err;
667
0
  long sockopts = STREAM_SOCKOP_NONE;
668
0
  zval *tmpzval = NULL;
669
670
0
#ifdef AF_UNIX
671
0
  if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
672
0
    struct sockaddr_un unix_addr;
673
674
0
    sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);
675
676
0
    if (sock->socket == SOCK_ERR) {
677
0
      if (xparam->want_errortext) {
678
0
        xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s",
679
0
            stream->ops == &php_stream_unix_socket_ops ? "" : "datagram",
680
0
            strerror(errno));
681
0
      }
682
0
      return -1;
683
0
    }
684
685
0
    parse_unix_address(xparam, &unix_addr);
686
687
0
    return bind(sock->socket, (const struct sockaddr *)&unix_addr,
688
0
      (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen);
689
0
  }
690
0
#endif
691
692
0
  host = parse_ip_address(xparam, &portno);
693
694
0
  if (host == NULL) {
695
0
    return -1;
696
0
  }
697
698
0
#ifdef IPV6_V6ONLY
699
0
  if (PHP_STREAM_CONTEXT(stream)
700
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL
701
0
    && Z_TYPE_P(tmpzval) != IS_NULL
702
0
  ) {
703
0
    sockopts |= STREAM_SOCKOP_IPV6_V6ONLY;
704
0
    sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval);
705
0
  }
706
0
#endif
707
708
0
#ifdef SO_REUSEPORT
709
0
  if (PHP_STREAM_CONTEXT(stream)
710
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL
711
0
    && zend_is_true(tmpzval)
712
0
  ) {
713
0
    sockopts |= STREAM_SOCKOP_SO_REUSEPORT;
714
0
  }
715
0
#endif
716
717
0
#ifdef SO_BROADCAST
718
0
  if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */
719
0
    && PHP_STREAM_CONTEXT(stream)
720
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
721
0
    && zend_is_true(tmpzval)
722
0
  ) {
723
0
    sockopts |= STREAM_SOCKOP_SO_BROADCAST;
724
0
  }
725
0
#endif
726
727
0
  sock->socket = php_network_bind_socket_to_local_addr(host, portno,
728
0
      stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
729
0
      sockopts,
730
0
      xparam->want_errortext ? &xparam->outputs.error_text : NULL,
731
0
      &err
732
0
      );
733
734
0
  if (host) {
735
0
    efree(host);
736
0
  }
737
738
0
  return sock->socket == -1 ? -1 : 0;
739
0
}
740
741
static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock,
742
    php_stream_xport_param *xparam)
743
0
{
744
0
  char *host = NULL, *bindto = NULL;
745
0
  int portno, bindport = 0;
746
0
  int err = 0;
747
0
  int ret;
748
0
  zval *tmpzval = NULL;
749
0
  long sockopts = STREAM_SOCKOP_NONE;
750
751
0
#ifdef AF_UNIX
752
0
  if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
753
0
    struct sockaddr_un unix_addr;
754
755
0
    sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);
756
757
0
    if (sock->socket == SOCK_ERR) {
758
0
      if (xparam->want_errortext) {
759
0
        xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket");
760
0
      }
761
0
      return -1;
762
0
    }
763
764
0
    parse_unix_address(xparam, &unix_addr);
765
766
0
    ret = php_network_connect_socket(sock->socket,
767
0
        (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen,
768
0
        xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout,
769
0
        xparam->want_errortext ? &xparam->outputs.error_text : NULL,
770
0
        &err);
771
772
0
    xparam->outputs.error_code = err;
773
774
0
    goto out;
775
0
  }
776
0
#endif
777
778
0
  host = parse_ip_address(xparam, &portno);
779
780
0
  if (host == NULL) {
781
0
    return -1;
782
0
  }
783
784
0
  if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) {
785
0
    if (Z_TYPE_P(tmpzval) != IS_STRING) {
786
0
      if (xparam->want_errortext) {
787
0
        xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string.");
788
0
      }
789
0
      efree(host);
790
0
      return -1;
791
0
    }
792
0
    bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text);
793
0
  }
794
795
0
#ifdef SO_BROADCAST
796
0
  if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */
797
0
    && PHP_STREAM_CONTEXT(stream)
798
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
799
0
    && zend_is_true(tmpzval)
800
0
  ) {
801
0
    sockopts |= STREAM_SOCKOP_SO_BROADCAST;
802
0
  }
803
0
#endif
804
805
0
  if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */
806
0
#ifdef AF_UNIX
807
0
    && stream->ops != &php_stream_unix_socket_ops
808
0
    && stream->ops != &php_stream_unixdg_socket_ops
809
0
#endif
810
0
    && PHP_STREAM_CONTEXT(stream)
811
0
    && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL
812
0
    && zend_is_true(tmpzval)
813
0
  ) {
814
0
    sockopts |= STREAM_SOCKOP_TCP_NODELAY;
815
0
  }
816
817
  /* Note: the test here for php_stream_udp_socket_ops is important, because we
818
   * want the default to be TCP sockets so that the openssl extension can
819
   * re-use this code. */
820
821
0
  sock->socket = php_network_connect_socket_to_host(host, portno,
822
0
      stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
823
0
      xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC,
824
0
      xparam->inputs.timeout,
825
0
      xparam->want_errortext ? &xparam->outputs.error_text : NULL,
826
0
      &err,
827
0
      bindto,
828
0
      bindport,
829
0
      sockopts
830
0
      );
831
832
0
  ret = sock->socket == -1 ? -1 : 0;
833
0
  xparam->outputs.error_code = err;
834
835
0
  if (host) {
836
0
    efree(host);
837
0
  }
838
0
  if (bindto) {
839
0
    efree(bindto);
840
0
  }
841
842
0
#ifdef AF_UNIX
843
0
out:
844
0
#endif
845
846
0
  if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) {
847
    /* indicates pending connection */
848
0
    return 1;
849
0
  }
850
851
0
  return ret;
852
0
}
853
854
static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock,
855
    php_stream_xport_param *xparam STREAMS_DC)
856
0
{
857
0
  int clisock;
858
0
  bool nodelay = 0;
859
0
  zval *tmpzval = NULL;
860
861
0
  xparam->outputs.client = NULL;
862
863
0
  if ((NULL != PHP_STREAM_CONTEXT(stream)) &&
864
0
    (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL &&
865
0
    zend_is_true(tmpzval)) {
866
0
    nodelay = 1;
867
0
  }
868
869
0
  clisock = php_network_accept_incoming(sock->socket,
870
0
    xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
871
0
    xparam->want_addr ? &xparam->outputs.addr : NULL,
872
0
    xparam->want_addr ? &xparam->outputs.addrlen : NULL,
873
0
    xparam->inputs.timeout,
874
0
    xparam->want_errortext ? &xparam->outputs.error_text : NULL,
875
0
    &xparam->outputs.error_code,
876
0
    nodelay);
877
878
0
  if (clisock >= 0) {
879
0
    php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata));
880
881
0
    memcpy(clisockdata, sock, sizeof(*clisockdata));
882
0
    clisockdata->socket = clisock;
883
0
#ifdef __linux__
884
    /* O_NONBLOCK is not inherited on Linux */
885
0
    clisockdata->is_blocked = 1;
886
0
#endif
887
888
0
    xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+");
889
0
    if (xparam->outputs.client) {
890
0
      xparam->outputs.client->ctx = stream->ctx;
891
0
      if (stream->ctx) {
892
0
        GC_ADDREF(stream->ctx);
893
0
      }
894
0
    }
895
0
  }
896
897
0
  return xparam->outputs.client == NULL ? -1 : 0;
898
0
}
899
900
static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
901
0
{
902
0
  php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
903
0
  php_stream_xport_param *xparam;
904
905
0
  switch(option) {
906
0
    case PHP_STREAM_OPTION_XPORT_API:
907
0
      xparam = (php_stream_xport_param *)ptrparam;
908
909
0
      switch(xparam->op) {
910
0
        case STREAM_XPORT_OP_CONNECT:
911
0
        case STREAM_XPORT_OP_CONNECT_ASYNC:
912
0
          xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam);
913
0
          return PHP_STREAM_OPTION_RETURN_OK;
914
915
0
        case STREAM_XPORT_OP_BIND:
916
0
          xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam);
917
0
          return PHP_STREAM_OPTION_RETURN_OK;
918
919
920
0
        case STREAM_XPORT_OP_ACCEPT:
921
0
          xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC);
922
0
          return PHP_STREAM_OPTION_RETURN_OK;
923
0
        default:
924
          /* fall through */
925
0
          ;
926
0
      }
927
0
  }
928
0
  return php_sockop_set_option(stream, option, value, ptrparam);
929
0
}
930
931
932
PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen,
933
    const char *resourcename, size_t resourcenamelen,
934
    const char *persistent_id, int options, int flags,
935
    struct timeval *timeout,
936
    php_stream_context *context STREAMS_DC)
937
0
{
938
0
  php_stream *stream = NULL;
939
0
  php_netstream_data_t *sock;
940
0
  const php_stream_ops *ops;
941
942
  /* which type of socket ? */
943
0
  if (strncmp(proto, "tcp", protolen) == 0) {
944
0
    ops = &php_stream_socket_ops;
945
0
  } else if (strncmp(proto, "udp", protolen) == 0) {
946
0
    ops = &php_stream_udp_socket_ops;
947
0
  }
948
0
#ifdef AF_UNIX
949
0
  else if (strncmp(proto, "unix", protolen) == 0) {
950
0
    ops = &php_stream_unix_socket_ops;
951
0
  } else if (strncmp(proto, "udg", protolen) == 0) {
952
0
    ops = &php_stream_unixdg_socket_ops;
953
0
  }
954
0
#endif
955
0
  else {
956
    /* should never happen */
957
0
    return NULL;
958
0
  }
959
960
0
  sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0);
961
0
  memset(sock, 0, sizeof(php_netstream_data_t));
962
963
0
  sock->is_blocked = 1;
964
0
  sock->timeout.tv_sec = FG(default_socket_timeout);
965
0
  sock->timeout.tv_usec = 0;
966
967
  /* we don't know the socket until we have determined if we are binding or
968
   * connecting */
969
0
  sock->socket = -1;
970
971
0
  stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+");
972
973
0
  if (stream == NULL) {
974
0
    pefree(sock, persistent_id ? 1 : 0);
975
0
    return NULL;
976
0
  }
977
978
0
  return stream;
979
0
}