/* +----------------------------------------------------------------------+ | PHP version 4.0 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2001 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.02 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available at through the world-wide-web at | | http://www.php.net/license/2_02.txt. | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Paul Panotzki - Bunyip Information Systems | | Jim Winstead | | Sascha Schumann | +----------------------------------------------------------------------+ */ /* $Id$ */ /* Synced with php 3.0 revision 1.121 1999-06-18 [ssb] */ /* Synced with php 3.0 revision 1.133 1999-07-21 [sas] */ #include "php.h" #include "php_globals.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_FCNTL_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #include #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef PHP_WIN32 #include #else #include #include #if HAVE_ARPA_INET_H #include #endif #endif #ifdef PHP_WIN32 #undef AF_UNIX #endif #if defined(AF_UNIX) #include #endif #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include "base64.h" #include "file.h" #include "url.h" #include "fsock.h" #include "php_network.h" #ifdef ZTS static int fsock_globals_id; #else extern int le_fp; #endif #define CLOSE_SOCK(free_sock) \ if(socketd >= 0) { \ close(socketd); \ } \ if (free_sock) { \ efree(sock); \ } \ if (key) { \ efree(key); \ } #define SEARCHCR() do { \ for (p = READPTR(sock), pe = p + MIN(TOREAD(sock), maxlen); \ *p != '\n'; ) if (++p >= pe) { p = NULL; break; } \ } while (0) #ifdef PHP_WIN32 #define EWOULDBLOCK WSAEWOULDBLOCK #else #include "build-defs.h" #endif /* {{{ php_lookup_hostname */ /* * Converts a host name to an IP address. */ PHPAPI int php_lookup_hostname(const char *addr, struct in_addr *in) { struct hostent *host_info; if (!inet_aton(addr, in)) { /* XXX NOT THREAD SAFE */ host_info = gethostbyname(addr); if (host_info == 0) { /* Error: unknown host */ return -1; } *in = *((struct in_addr *) host_info->h_addr); } return 0; } /* }}} */ /* {{{ php_is_persistent_sock */ PHPAPI int php_is_persistent_sock(int sock) { char *key; TSRMLS_FETCH(); if (zend_hash_find(&FG(ht_fsock_socks), (char *) &sock, sizeof(sock), (void **) &key) == SUCCESS) { return 1; } return 0; } /* }}} */ /* {{{ php_fsockopen() */ /* This function takes an optional third argument which should be passed by reference. The error code from the connect call is written to this variable. */ static void php_fsockopen(INTERNAL_FUNCTION_PARAMETERS, int persistent) { pval **args[5]; int *sock=emalloc(sizeof(int)); int *sockp; int arg_count=ZEND_NUM_ARGS(); int socketd = -1; unsigned char udp = 0; struct timeval timeout = { 60, 0 }; unsigned short portno; unsigned long conv; char *key = NULL; if (arg_count > 5 || arg_count < 2 || zend_get_parameters_array_ex(arg_count,args)==FAILURE) { CLOSE_SOCK(1); WRONG_PARAM_COUNT; } switch(arg_count) { case 5: convert_to_double_ex(args[4]); conv = (unsigned long) ((*args[4])->value.dval * 1000000.0); timeout.tv_sec = conv / 1000000; timeout.tv_usec = conv % 1000000; /* fall-through */ case 4: zval_dtor(*args[3]); ZVAL_STRING(*args[3],"",1); /* fall-through */ case 3: zval_dtor(*args[2]); ZVAL_LONG(*args[2],0); break; } convert_to_string_ex(args[0]); convert_to_long_ex(args[1]); portno = (unsigned short) (*args[1])->value.lval; key = emalloc((*args[0])->value.str.len + 10); sprintf(key, "%s:%d", (*args[0])->value.str.val, portno); if (persistent && zend_hash_find(&FG(ht_fsock_keys), key, strlen(key) + 1, (void *) &sockp) == SUCCESS) { CLOSE_SOCK(0); *sock = *sockp; ZEND_REGISTER_RESOURCE(return_value,sock,php_file_le_socket()); return; } if (portno) { struct sockaddr_in server; memset(&server, 0, sizeof(server)); if(Z_STRLEN_PP(args[0]) >= 6 && !memcmp(Z_STRVAL_PP(args[0]), "udp://", sizeof("udp://")-1)) { udp = 1; } socketd = socket(AF_INET,udp ? SOCK_DGRAM : SOCK_STREAM,0); if (socketd == SOCK_ERR) { CLOSE_SOCK(1); RETURN_FALSE; } server.sin_family = AF_INET; if(php_lookup_hostname(udp ? &(*args[0])->value.str.val[6] : (*args[0])->value.str.val,&server.sin_addr)) { CLOSE_SOCK(1); RETURN_FALSE; } server.sin_port = htons(portno); if (php_connect_nonb(socketd, (struct sockaddr *)&server, sizeof(server), &timeout) == SOCK_CONN_ERR) { CLOSE_SOCK(1); if (arg_count>2) { zval_dtor(*args[2]); ZVAL_LONG(*args[2],errno); } if (arg_count>3) { zval_dtor(*args[3]); ZVAL_STRING(*args[3],strerror(errno),1); } RETURN_FALSE; } #if defined(AF_UNIX) } else { /* Unix domain socket. s->strval is socket name. */ struct sockaddr_un unix_addr; socketd = socket(AF_UNIX,SOCK_STREAM,0); if (socketd == SOCK_ERR) { CLOSE_SOCK(1); RETURN_FALSE; } memset(&unix_addr, (char)0, sizeof(unix_addr)); unix_addr.sun_family = AF_UNIX; strlcpy(unix_addr.sun_path, (*args[0])->value.str.val, sizeof(unix_addr.sun_path)); if (php_connect_nonb(socketd, (struct sockaddr *) &unix_addr, sizeof(unix_addr), &timeout) == SOCK_CONN_ERR) { CLOSE_SOCK(1); if (arg_count>2) { zval_dtor(*args[2]); ZVAL_LONG(*args[2],errno); } if (arg_count>3) { zval_dtor(*args[3]); ZVAL_STRING(*args[3],strerror(errno),1); } RETURN_FALSE; } #endif /* AF_UNIX */ } #if 0 if ((fp = fdopen (socketd, "r+")) == NULL){ RETURN_LONG(-6); /* FIXME */ } #ifdef HAVE_SETVBUF if ((setvbuf(fp, NULL, _IONBF, 0)) != 0){ RETURN_LONG(-7); /* FIXME */ } #endif #endif *sock=socketd; if (persistent) { zend_hash_update(&FG(ht_fsock_keys), key, strlen(key) + 1, sock, sizeof(*sock), NULL); zend_hash_update(&FG(ht_fsock_socks), (char *) sock, sizeof(*sock), key, strlen(key) + 1, NULL); } if(key) efree(key); ZEND_REGISTER_RESOURCE(return_value,sock,php_file_le_socket()); } /* }}} */ /* {{{ proto int fsockopen(string hostname, int port [, int errno [, string errstr [, double timeout]]]) Open Internet or Unix domain socket connection */ PHP_FUNCTION(fsockopen) { php_fsockopen(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ /* {{{ proto int pfsockopen(string hostname, int port [, int errno [, string errstr [, double timeout]]]) Open persistent Internet or Unix domain socket connection */ PHP_FUNCTION(pfsockopen) { php_fsockopen(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ #define SOCK_DESTROY(sock) \ if(sock->readbuf) pefree(sock->readbuf, sock->persistent); \ if(sock->prev) sock->prev->next = sock->next; \ if(sock->next) sock->next->prev = sock->prev; \ if(sock == FG(phpsockbuf)) \ FG(phpsockbuf) = sock->next; \ pefree(sock, sock->persistent) PHPAPI void php_cleanup_sockbuf(int persistent TSRMLS_DC) { php_sockbuf *now, *next; for(now = FG(phpsockbuf); now; now = next) { next = now->next; if(now->persistent == persistent) { SOCK_DESTROY(now); } } } #define TOREAD(sock) ((sock)->writepos - (sock)->readpos) #define READPTR(sock) ((sock)->readbuf + (sock)->readpos) #define WRITEPTR(sock) ((sock)->readbuf + (sock)->writepos) #define SOCK_FIND(sock,socket) \ php_sockbuf *sock; \ TSRMLS_FETCH(); \ sock = php_sockfind(socket TSRMLS_CC); \ if(!sock) sock = php_sockcreate(socket TSRMLS_CC) static php_sockbuf *php_sockfind(int socket TSRMLS_DC) { php_sockbuf *buf = NULL, *tmp; for(tmp = FG(phpsockbuf); tmp; tmp = tmp->next) if(tmp->socket == socket) { buf = tmp; break; } return buf; } static php_sockbuf *php_sockcreate(int socket TSRMLS_DC) { php_sockbuf *sock; int persistent = php_is_persistent_sock(socket); sock = pecalloc(sizeof(*sock), 1, persistent); sock->socket = socket; if((sock->next = FG(phpsockbuf))) FG(phpsockbuf)->prev = sock; sock->persistent = persistent; sock->is_blocked = 1; sock->chunk_size = FG(def_chunk_size); sock->timeout.tv_sec = -1; FG(phpsockbuf) = sock; return sock; } PHPAPI php_sockbuf *php_get_socket(int socket) { SOCK_FIND(sock, socket); return sock; } PHPAPI size_t php_sock_set_def_chunk_size(size_t size) { size_t old; TSRMLS_FETCH(); old = FG(def_chunk_size); if(size <= PHP_FSOCK_CHUNK_SIZE || size > 0) FG(def_chunk_size) = size; return old; } PHPAPI int php_sockdestroy(int socket) { int ret = 0; php_sockbuf *sock; TSRMLS_FETCH(); sock = php_sockfind(socket TSRMLS_CC); if(sock) { ret = 1; SOCK_DESTROY(sock); } return ret; } #if !defined(PHP_WIN32) #undef closesocket #define closesocket close #endif #ifndef HAVE_SHUTDOWN #undef shutdown #define shutdown #endif #define SOCK_CLOSE(s) shutdown(s, 0); closesocket(s) PHPAPI int php_sock_close(int socket) { int ret = 0; php_sockbuf *sock; TSRMLS_FETCH(); sock = php_sockfind(socket TSRMLS_CC); if(sock) { if(!sock->persistent) { SOCK_CLOSE(sock->socket); SOCK_DESTROY(sock); } } else { SOCK_CLOSE(socket); } return ret; } #define MAX_CHUNKS_PER_READ 10 static void php_sockwait_for_data(php_sockbuf *sock) { fd_set fdr, tfdr; int retval; struct timeval timeout, *ptimeout; FD_ZERO(&fdr); FD_SET(sock->socket, &fdr); sock->timeout_event = 0; if (sock->timeout.tv_sec == -1) ptimeout = NULL; else ptimeout = &timeout; while(1) { tfdr = fdr; timeout = sock->timeout; retval = select(sock->socket + 1, &tfdr, NULL, NULL, ptimeout); if (retval == 0) sock->timeout_event = 1; if (retval >= 0) break; } } static size_t php_sockread_internal(php_sockbuf *sock) { char buf[PHP_FSOCK_CHUNK_SIZE]; int nr_bytes; size_t nr_read = 0; /* For blocking sockets, we wait until there is some data to read (real data or EOF) Otherwise, recv() may time out and return 0 and therefore sock->eof would be set errornously. */ if(sock->is_blocked) { php_sockwait_for_data(sock); if (sock->timeout_event) return 0; } /* read at a maximum sock->chunk_size */ nr_bytes = recv(sock->socket, buf, sock->chunk_size, 0); if(nr_bytes > 0) { if(sock->writepos + nr_bytes > sock->readbuflen) { sock->readbuflen += sock->chunk_size; sock->readbuf = perealloc(sock->readbuf, sock->readbuflen, sock->persistent); } memcpy(WRITEPTR(sock), buf, nr_bytes); sock->writepos += nr_bytes; nr_read = nr_bytes; } else if(nr_bytes == 0 || (nr_bytes < 0 && errno != EWOULDBLOCK)) { sock->eof = 1; } return nr_read; } static void php_sockread_total(php_sockbuf *sock, size_t maxread) { while(!sock->eof && TOREAD(sock) < maxread && !sock->timeout_event) { php_sockread_internal(sock); } } static size_t php_sockread(php_sockbuf *sock) { size_t nr_bytes; size_t nr_read = 0; int i; for(i = 0; !sock->eof && i < MAX_CHUNKS_PER_READ; i++) { nr_bytes = php_sockread_internal(sock); if(nr_bytes == 0) break; nr_read += nr_bytes; } return nr_read; } PHPAPI int php_sockset_blocking(int socket, int mode) { int old; SOCK_FIND(sock, socket); old = sock->is_blocked; sock->is_blocked = mode; return old; } PHPAPI void php_sockset_timeout(int socket, struct timeval *timeout) { SOCK_FIND(sock, socket); sock->timeout = *timeout; sock->timeout_event = 0; } #define SOCK_FIND_AND_READ_MAX(max) \ SOCK_FIND(sock, socket); \ if(sock->is_blocked) php_sockread_total(sock, max); else php_sockread(sock) /* {{{ php_sock_fgets() */ /* * FIXME: fgets depends on '\n' as line delimiter */ static char * php_sock_fgets_internal(char * buf, size_t maxlen, php_sockbuf * sock) { char *p = NULL, *pe; char *ret = NULL; size_t amount = 0; if (maxlen==0) { buf[0] = 0; return buf; } SEARCHCR(); if(!p) { if(sock->is_blocked) { while(!p && !sock->eof && !sock->timeout_event && TOREAD(sock) < maxlen) { php_sockread_internal(sock); SEARCHCR(); } } else { php_sockread(sock); SEARCHCR(); } } if(p) { amount = (ptrdiff_t) p - (ptrdiff_t) READPTR(sock) + 1; } else { amount = TOREAD(sock); } amount = MIN(amount, maxlen); if(amount > 0) { memcpy(buf, READPTR(sock), amount); sock->readpos += amount; } buf[amount] = '\0'; /* signal error only, if we don't return data from this call and if there is no data to read and if the eof flag is set */ if(amount || TOREAD(sock) || !sock->eof) { ret = buf; } return ret; } PHPAPI char *php_sock_fgets(char *buf, size_t maxlen, int socket) { SOCK_FIND(sock, socket); return php_sock_fgets_internal(buf, maxlen, sock); } /* }}} */ /* * FIXME: fgetc returns EOF, if no data is available on a nonblocking socket. * I don't have any documentation on the semantics of fgetc in this case. * * ss@2ns.de 19990528 */ PHPAPI int php_sock_fgetc(int socket) { int ret = EOF; SOCK_FIND_AND_READ_MAX(1); if(TOREAD(sock) > 0) { ret = *READPTR(sock); sock->readpos++; } return ret; } PHPAPI int php_sock_feof(int socket) { int ret = 0; SOCK_FIND(sock, socket); if(!sock->is_blocked) php_sockread(sock); if(!TOREAD(sock) && sock->eof) ret = 1; return ret; } /* {{{ stream abstraction */ #if HAVE_PHP_STREAM static size_t php_sockop_write(php_stream * stream, const char * buf, size_t count) { php_sockbuf * sock = (php_sockbuf*)stream->abstract; return send(sock->socket, buf, count, 0); } static size_t php_sockop_read(php_stream * stream, char * buf, size_t count) { php_sockbuf * sock = (php_sockbuf*)stream->abstract; size_t ret = 0; if (sock->is_blocked) php_sockread_total(sock, count); else php_sockread(sock); if(count < 0) return ret; ret = MIN(TOREAD(sock), count); if (ret) { memcpy(buf, READPTR(sock), ret); sock->readpos += ret; } return ret; } static int php_sockop_close(php_stream * stream) { php_sockbuf * sock = (php_sockbuf*)stream->abstract; SOCK_CLOSE(sock->socket); SOCK_DESTROY(sock); return 0; } static int php_sockop_flush(php_stream * stream) { php_sockbuf * sock = (php_sockbuf*)stream->abstract; return fsync(sock->socket); } static int php_sockop_cast(php_stream * stream, int castas, void ** ret) { php_sockbuf * sock = (php_sockbuf*)stream->abstract; switch(castas) { case PHP_STREAM_AS_STDIO: if (ret) { /* DANGER!: data buffered in stream->readbuf will be forgotten! */ if (TOREAD(sock) > 0) zend_error(E_WARNING, "%s(): buffered data lost during conversion to FILE*!", get_active_function_name()); *ret = fdopen(sock->socket, stream->mode); if (*ret) return SUCCESS; return FAILURE; } return SUCCESS; case PHP_STREAM_AS_FD: case PHP_STREAM_AS_SOCKETD: if (ret) *ret = (void*)sock->socket; return SUCCESS; default: return FAILURE; } } static char * php_sockop_gets(php_stream * stream, char *buf, size_t size) { php_sockbuf * sock = (php_sockbuf*)stream->abstract; return php_sock_fgets_internal(buf, size, sock); } php_stream_ops php_stream_socket_ops = { php_sockop_write, php_sockop_read, php_sockop_close, php_sockop_flush, NULL, php_sockop_gets, php_sockop_cast, "socket" }; #endif /* }}} */ /* {{{ php_sock_fread() */ PHPAPI size_t php_sock_fread(char *ptr, size_t size, int socket) { size_t ret = 0; SOCK_FIND_AND_READ_MAX(size); if(size < 0) return ret; ret = MIN(TOREAD(sock), size); if(ret) { memcpy(ptr, READPTR(sock), ret); sock->readpos += ret; } return ret; } /* }}} */ /* {{{ module start/shutdown functions */ /* {{{ php_msock_destroy */ PHPAPI void php_msock_destroy(int *data) { close(*data); } /* }}} */ PHP_RSHUTDOWN_FUNCTION(fsock) { php_cleanup_sockbuf(0 TSRMLS_CC); return SUCCESS; } /* }}} */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: sw=4 ts=4 tw=78 fdm=marker * vim<600: sw=4 ts=4 tw=78 */