/* radare - LGPL - Copyright 2006-2015 - pancake */

#include <errno.h>
#include <r_types.h>
#include <r_util.h>
#include <r_socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#if EMSCRIPTEN
#define NETWORK_DISABLED 1
#else
#define NETWORK_DISABLED 0
#endif

R_LIB_VERSION(r_socket);


#if NETWORK_DISABLED
/* no network */
R_API RSocket *r_socket_new (int is_ssl) {
	return NULL;
}
R_API bool r_socket_is_connected (RSocket *s) {
	return false;
}
static int r_socket_unix_connect(RSocket *s, const char *file) {
	return -1;
}
R_API int r_socket_unix_listen (RSocket *s, const char *file) {
	return -1;
}
R_API bool r_socket_connect (RSocket *s, const char *host, const char *port, int proto, unsigned int timeout) {
	return false;
}
R_API int r_socket_close_fd (RSocket *s) {
	return -1;
}
R_API int r_socket_close (RSocket *s) {
	return -1;
}
R_API int r_socket_free (RSocket *s) {
	return -1;
}
R_API int r_socket_port_by_name(const char *name) {
	return -1;
}
R_API bool r_socket_listen (RSocket *s, const char *port, const char *certfile) {
	return false;
}
R_API RSocket *r_socket_accept(RSocket *s) {
	return NULL;
}
R_API int r_socket_block_time (RSocket *s, int block, int sec) {
	return -1;
}
R_API int r_socket_flush(RSocket *s) {
	return -1;
}
R_API int r_socket_ready(RSocket *s, int secs, int usecs) {
	return -1;
}
R_API char *r_socket_to_string(RSocket *s) {
	return NULL;
}
R_API int r_socket_write(RSocket *s, void *buf, int len) {
	return -1;
}
R_API int r_socket_puts(RSocket *s, char *buf) {
	return -1;
}
R_API void r_socket_printf(RSocket *s, const char *fmt, ...) {
	/* nothing here */
}
R_API int r_socket_read(RSocket *s, unsigned char *buf, int len) {
	return -1;
}
R_API int r_socket_read_block(RSocket *s, unsigned char *buf, int len) {
	return -1;
}
R_API int r_socket_gets(RSocket *s, char *buf,	int size) {
	return -1;
}
R_API RSocket *r_socket_new_from_fd (int fd) {
	return NULL;
}
R_API ut8* r_socket_slurp(RSocket *s, int *len) {
	return NULL;
}
#else

#if 0
winsock api notes
=================
close: closes the socket without flushing the data
WSACleanup: closes all network connections
#endif
#define BUFFER_SIZE 4096

R_API bool r_socket_is_connected (RSocket *s) {
#if __WINDOWS__ && !defined(__CYGWIN__) //&& !defined(__MINGW64__)
	char buf[2];
	r_socket_block_time (s, 0, 0);
	ssize_t ret = recv (s->fd, (char*)&buf, 1, MSG_PEEK);
	r_socket_block_time (s, 1, 0);
	return ret? true: false;
#else
	char buf[2];
	int ret = recv (s->fd, &buf, 1, MSG_PEEK | MSG_DONTWAIT);
	return ret? true: false;
#endif
}

#if __UNIX__ || defined(__CYGWIN__)
static int r_socket_unix_connect(RSocket *s, const char *file) {
	struct sockaddr_un addr;
	int sock = socket (PF_UNIX, SOCK_STREAM, 0);
	if (sock < 0) {
		free (s);
		return false;
	}
	// TODO: set socket options
	addr.sun_family = AF_UNIX;
	strncpy (addr.sun_path, file, sizeof (addr.sun_path)-1);

	if (connect (sock, (struct sockaddr *)&addr, sizeof(addr))==-1) {
		close (sock);
		free (s);
		return false;
	}
	s->fd = sock;
	s->is_ssl = false;
	return true;
}

R_API int r_socket_unix_listen (RSocket *s, const char *file) {
	struct sockaddr_un unix_name;
	int sock = socket (PF_UNIX, SOCK_STREAM, 0);
	if (sock < 0) {
		return false;
	}
	// TODO: set socket options
	unix_name.sun_family = AF_UNIX;
	strncpy (unix_name.sun_path, file, sizeof (unix_name.sun_path)-1);

	/* just to make sure there is no other socket file */
	unlink (unix_name.sun_path);

	if (bind (sock, (struct sockaddr *) &unix_name, sizeof (unix_name)) < 0) {
		close (sock);
		return false;
	}
	signal (SIGPIPE, SIG_IGN);

	/* change permissions */
	if (chmod (unix_name.sun_path, 0777) != 0) {
		close (sock);
		return false;
	}
	if (listen (sock, 1)) {
		close (sock);
		return false;
	}
	s->fd = sock;
	return true;
}
#endif

R_API RSocket *r_socket_new (int is_ssl) {
	RSocket *s = R_NEW0 (RSocket);
	if (!s) return NULL;
	s->is_ssl = is_ssl;
	s->port = 0;
#if __UNIX_
	signal (SIGPIPE, SIG_IGN);
#endif
	s->local = 0;
	s->fd = -1;
#if HAVE_LIB_SSL
	if (is_ssl) {
		s->sfd = NULL;
		s->ctx = NULL;
		s->bio = NULL;
#if OPENSSL_VERSION_NUMBER < 0x1010000fL
		if (!SSL_library_init ()) {
			r_socket_free (s);
			return NULL;
		}
		SSL_load_error_strings ();
#endif
	}
#endif
	return s;
}

R_API bool r_socket_connect (RSocket *s, const char *host, const char *port, int proto, unsigned int timeout) {
#if __WINDOWS__ && !defined(__CYGWIN__) //&& !defined(__MINGW64__)
	struct sockaddr_in sa;
	struct hostent *he;
	WSADATA wsadata;
	TIMEVAL Timeout;
	Timeout.tv_sec = timeout;
	Timeout.tv_usec = 0;

	if (WSAStartup (MAKEWORD (1, 1), &wsadata) == SOCKET_ERROR) {
		eprintf ("Error creating socket.");
		return false;
	}
	s->fd = socket (AF_INET, SOCK_STREAM, 0);
	if (s->fd == -1)
		return false;

	unsigned long iMode = 1;
	int iResult = ioctlsocket (s->fd, FIONBIO, &iMode);
	if (iResult != NO_ERROR) {
		eprintf ("ioctlsocket error: %d\n", iResult);
	}
	memset (&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	he = (struct hostent *)gethostbyname (host);
	if (he == (struct hostent*)0) {
		close (s->fd);
		return false;
	}
	sa.sin_addr = *((struct in_addr *)he->h_addr);
	s->port = r_socket_port_by_name (port);
	sa.sin_port = htons (s->port);
	if (!connect (s->fd, (const struct sockaddr*)&sa, sizeof (struct sockaddr))) {
		close (s->fd);
		return false;
	}
	iMode = 0;
	iResult = ioctlsocket (s->fd, FIONBIO, &iMode);
	if (iResult != NO_ERROR) {
		eprintf ("ioctlsocket error: %d\n", iResult);
	}
	fd_set Write, Err;
	FD_ZERO (&Write);
	FD_ZERO (&Err);
	FD_SET (s->fd, &Write);
	FD_SET (s->fd, &Err);
	select (0, NULL, &Write, &Err, &Timeout);
	if(FD_ISSET (s->fd, &Write)) {
		return true;
	}
	return false;
#elif __UNIX__ || defined(__CYGWIN__)
	int gai, ret;
	struct addrinfo hints, *res, *rp;
	if (!proto) {
		proto = R_SOCKET_PROTO_TCP;
	}
	signal (SIGPIPE, SIG_IGN);
	if (proto == R_SOCKET_PROTO_UNIX) {
		if (!r_socket_unix_connect (s, host)) {
			return false;
		}
	} else {
		memset (&hints, 0, sizeof (struct addrinfo));
		hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
		hints.ai_protocol = proto;
		gai = getaddrinfo (host, port, &hints, &res);
		if (gai != 0) {
			eprintf ("Error in getaddrinfo: %s\n", gai_strerror (gai));
			return false;
		}
		for (rp = res; rp != NULL; rp = rp->ai_next) {
			int flag = 1;

			s->fd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
			if (s->fd == -1) {
				perror ("socket");
				continue;
			}
			ret = setsockopt (s->fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof (flag));
			if (ret < 0) {
				perror ("setsockopt");
				close (s->fd);
				s->fd = -1;
				continue;
			}
			if (timeout > 0) {
				r_socket_block_time (s, 1, timeout);
				//fcntl (s->fd, F_SETFL, O_NONBLOCK, 1);
			}
			ret = connect (s->fd, rp->ai_addr, rp->ai_addrlen);

			if (timeout == 0 && ret == 0) {
				freeaddrinfo (res);
				return true;
			}
			if (ret == 0 /* || nonblocking */) {
				struct timeval tv;
				fd_set fdset, errset;
				FD_ZERO (&fdset);
				FD_SET (s->fd, &fdset);
				tv.tv_sec = 1; //timeout;
				tv.tv_usec = 0;

				if (r_socket_is_connected (s)) {
					freeaddrinfo (res);
					return true;
				}
				if (select (s->fd + 1, NULL, NULL, &errset, &tv) == 1) {
					int so_error;
					socklen_t len = sizeof so_error;
					ret = getsockopt (s->fd, SOL_SOCKET,
						SO_ERROR, &so_error, &len);

					if (ret == 0 && so_error == 0) {
						//fcntl (s->fd, F_SETFL, O_NONBLOCK, 0);
						//r_socket_block_time (s, 0, 0);
						freeaddrinfo (res);
						return true;
					}
				}
			}
			close (s->fd);
			s->fd = -1;
		}
		freeaddrinfo (res);
		if (!rp) {
			eprintf ("Could not resolve address '%s' or failed to connect\n", host);
			return false;
		}
	}
#endif
#if HAVE_LIB_SSL
	if (s->is_ssl) {
		s->ctx = SSL_CTX_new (SSLv23_client_method ());
		if (!s->ctx) {
			r_socket_free (s);
			return false;
		}
		s->sfd = SSL_new (s->ctx);
		SSL_set_fd (s->sfd, s->fd);
		if (SSL_connect (s->sfd) != 1) {
			r_socket_free (s);
			return false;
		}
	}
#endif
	return true;
}

/* close the file descriptor associated with the RSocket s */
R_API int r_socket_close_fd (RSocket *s) {
	return s->fd != -1 ? close (s->fd) : false;
}

/* shutdown the socket and close the file descriptor */
R_API int r_socket_close (RSocket *s) {
	int ret = false;
	if (!s) return false;
	if (s->fd != -1) {
#if __UNIX__ || defined(__CYGWIN__)
		shutdown (s->fd, SHUT_RDWR);
#endif
#if __WINDOWS__ && !defined(__CYGWIN__) //&& !defined(__MINGW64__)
		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms740481(v=vs.85).aspx
		shutdown (s->fd, SD_SEND);
		do {
			char buf = 0;
			ret = recv (s->fd, &buf, 1, 0);
		} while (ret != 0 && ret != SOCKET_ERROR);
		ret = closesocket (s->fd);
#else
		ret = close (s->fd);
#endif
	}
#if HAVE_LIB_SSL
	if (s->is_ssl && s->sfd) {
		SSL_free (s->sfd);
		s->sfd = NULL;
	}
#endif
	return ret;
}

/* shutdown the socket, close the file descriptor and free the RSocket */
R_API int r_socket_free (RSocket *s) {
	int res = r_socket_close (s);
#if HAVE_LIB_SSL
	if (s->is_ssl) {
		if (s->sfd)
			SSL_free (s->sfd);
		if (s->ctx)
			SSL_CTX_free (s->ctx);
	}
#endif
	free (s);
	return res;
}

R_API int r_socket_port_by_name(const char *name) {
	struct servent *p = getservbyname (name, "tcp");
	if (p && p->s_port)
		return ntohs (p->s_port);
	return r_num_get (NULL, name);
}

R_API bool r_socket_listen (RSocket *s, const char *port, const char *certfile) {
#if __UNIX__ || defined(__CYGWIN__)
	int optval = 1;
	int ret;
	struct linger linger = { 0 };
#endif
	if (r_sandbox_enable (0)) {
		return false;
	}
#if __WINDOWS__ && !defined(__CYGWIN__) //&& !defined(__MINGW64__)
	WSADATA wsadata;
	if (WSAStartup (MAKEWORD (1, 1), &wsadata) == SOCKET_ERROR) {
		eprintf ("Error creating socket.");
		return false;
	}
#endif
	if ((s->fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP))<0)
		return false;
#if __UNIX__ || defined(__CYGWIN__)
	linger.l_onoff = 1;
	linger.l_linger = 1;
	ret = setsockopt (s->fd, SOL_SOCKET, SO_LINGER, (void*)&linger, sizeof (linger));
	if (ret < 0) {
		return false;
	}
	{ // fix close after write bug //
	int x = 1500; // FORCE MTU
	ret = setsockopt (s->fd, SOL_SOCKET, SO_SNDBUF, (void*)&x, sizeof (int));
	if (ret < 0)
		return false;
	}
	ret = setsockopt (s->fd, SOL_SOCKET, SO_REUSEADDR, (void*)&optval, sizeof optval);
	if (ret < 0)
		return false;
#endif
	memset (&s->sa, 0, sizeof (s->sa));
	s->sa.sin_family = AF_INET;
	s->sa.sin_addr.s_addr = htonl (s->local? INADDR_LOOPBACK: INADDR_ANY);
	s->port = r_socket_port_by_name (port);
	if (s->port < 1) {
		return false;
	}
	s->sa.sin_port = htons (s->port); // TODO honor etc/services
	if (bind (s->fd, (struct sockaddr *)&s->sa, sizeof (s->sa)) < 0) {
		r_sys_perror ("bind");
		close (s->fd);
		return false;
	}
#if __UNIX__ || defined(__CYGWIN__)
	signal (SIGPIPE, SIG_IGN);
#endif
	if (listen (s->fd, 32) < 0) {
		close (s->fd);
		return false;
	}
#if HAVE_LIB_SSL
	if (s->is_ssl) {
		s->ctx = SSL_CTX_new (SSLv23_method ());
		if (!s->ctx) {
			r_socket_free (s);
			return false;
		}
		if (!SSL_CTX_use_certificate_chain_file (s->ctx, certfile)) {
			r_socket_free (s);
			return false;
		}
		if (!SSL_CTX_use_PrivateKey_file (s->ctx, certfile, SSL_FILETYPE_PEM)) {
			r_socket_free (s);
			return false;
		}
		SSL_CTX_set_verify_depth (s->ctx, 1);
	}
#endif
	return true;
}

R_API RSocket *r_socket_accept(RSocket *s) {
	RSocket *sock;
	socklen_t salen = sizeof (s->sa);
	if (!s) {
		return NULL;
	}
	sock = R_NEW0 (RSocket);
	if (!sock) {
		return NULL;
	}
	//signal (SIGPIPE, SIG_DFL);
	sock->fd = accept (s->fd, (struct sockaddr *)&s->sa, &salen);
	if (sock->fd == -1) {
		r_sys_perror ("accept");
		free (sock);
		return NULL;
	}
#if HAVE_LIB_SSL
	sock->is_ssl = s->is_ssl;
	if (sock->is_ssl) {
		sock->sfd = NULL;
		sock->ctx = NULL;
		sock->bio = NULL;
		BIO *sbio = BIO_new_socket (sock->fd, BIO_NOCLOSE);
		sock->sfd = SSL_new (s->ctx);
		SSL_set_bio (sock->sfd, sbio, sbio);
		if (SSL_accept (sock->sfd) <= 0) {
			r_socket_free (sock);
			return NULL;
		}
		sock->bio = BIO_new (BIO_f_buffer ());
		sbio = BIO_new (BIO_f_ssl ());
		BIO_set_ssl (sbio, sock->sfd, BIO_CLOSE);
		BIO_push (sock->bio, sbio);
	}
#else
	sock->is_ssl = 0;
#endif
	return sock;
}

R_API int r_socket_block_time (RSocket *s, int block, int sec) {
#if __UNIX__ || defined(__CYGWIN__)
	int ret, flags;
#endif
	if (!s) return false;
#if __UNIX__ || defined(__CYGWIN__)
	flags = fcntl (s->fd, F_GETFL, 0);
	if (flags < 0)
		return false;
	ret = fcntl (s->fd, F_SETFL, block?
			(flags & ~O_NONBLOCK):
			(flags | O_NONBLOCK));
	if (ret < 0)
		return false;
#elif __WINDOWS__ && !defined(__CYGWIN__) //&& !defined(__MINGW64__)
	// HACK: nonblocking io on w32 behaves strange
	return true;
	ioctlsocket (s->fd, FIONBIO, (u_long FAR*)&block);
#endif
	if (sec > 0) {
		struct timeval tv = {0};
		tv.tv_sec = sec;
		tv.tv_usec = 0;
		if (setsockopt (s->fd, SOL_SOCKET, SO_RCVTIMEO,
				(char *)&tv, sizeof (tv)) < 0)
			return false;
	}
	return true;
}

R_API int r_socket_flush(RSocket *s) {
#if HAVE_LIB_SSL
	if (s->is_ssl && s->bio)
		return BIO_flush(s->bio);
#endif
	return true;
}

// XXX: rewrite it to use select //
/* waits secs until new data is received.	  */
/* returns -1 on error, 0 is false, 1 is true */
R_API int r_socket_ready(RSocket *s, int secs, int usecs) {
#if __UNIX__ || defined(__CYGWIN__)
	//int msecs = (1000 * secs) + (usecs / 1000);
	int msecs = (usecs / 1000);
	struct pollfd fds[1];
	fds[0].fd = s->fd;
	fds[0].events = POLLIN | POLLPRI;
	fds[0].revents = POLLNVAL | POLLHUP | POLLERR;
	return poll ((struct pollfd *)&fds, 1, msecs);
#elif __WINDOWS__ && !defined(__CYGWIN__) //&& !defined(__MINGW64__)
	return 1;
#if XXX_THIS_IS_NOT_WORKING_WELL
	fd_set rfds;
	struct timeval tv;
	if (s->fd == -1) {
		return -1;
	}
	FD_ZERO (&rfds);
	FD_SET (s->fd, &rfds);
	tv.tv_sec = secs;
	tv.tv_usec = usecs;
	if (select (s->fd+1, &rfds, NULL, NULL, &tv) == -1)
		return -1;
	return FD_ISSET (0, &rfds);
#endif
#else
	return true; /* always ready if unknown */
#endif
}

R_API char *r_socket_to_string(RSocket *s) {
#if __WINDOWS__ && !defined(__CYGWIN__) //&& !defined(__MINGW64__)
	char *str = malloc (32);
	snprintf (str, 31, "fd%d", s->fd);
	return str;
#elif __UNIX__ || defined(__CYGWIN__)
	char *str = NULL;
	struct sockaddr sa;
	socklen_t sl = sizeof (sa);
	memset (&sa, 0, sizeof (sa));
	if (!getpeername (s->fd, &sa, &sl)) {
		struct sockaddr_in *sain = (struct sockaddr_in*) &sa;
		ut8 *a = (ut8*) &(sain->sin_addr);
		if ((str = malloc (32)))
			sprintf (str, "%d.%d.%d.%d:%d",
				a[0],a[1],a[2],a[3], ntohs (sain->sin_port));
	} else eprintf ("getperrname: failed\n"); //r_sys_perror ("getpeername");
	return str;
#else
	return NULL;
#endif
}

/* Read/Write functions */
R_API int r_socket_write(RSocket *s, void *buf, int len) {
	int ret, delta = 0;
#if __UNIX__ || defined(__CYGWIN__)
	signal (SIGPIPE, SIG_IGN);
#endif
	for (;;) {
		int b = 1500; //65536; // Use MTU 1500?
		if (b > len) {
			b = len;
		}
#if HAVE_LIB_SSL
		if (s->is_ssl) {
			if (s->bio)
				ret = BIO_write (s->bio, buf+delta, b);
			else
				ret = SSL_write (s->sfd, buf+delta, b);
		} else
#endif
		{
			ret = send (s->fd, buf+delta, b, 0);
		}
		//if (ret == 0) return -1;
		if (ret<1) break;
		if (ret == len)
			return len;
		delta += ret;
		len -= ret;
	}
	if (ret == -1)
		return -1;
	return delta;
}

R_API int r_socket_puts(RSocket *s, char *buf) {
	return r_socket_write (s, buf, strlen (buf));
}

R_API void r_socket_printf(RSocket *s, const char *fmt, ...) {
	char buf[BUFFER_SIZE];
	va_list ap;
	if (s->fd >= 0) {
		va_start (ap, fmt);
		vsnprintf (buf, BUFFER_SIZE, fmt, ap);
		r_socket_write (s, buf, strlen (buf));
		va_end (ap);
	}
}

R_API int r_socket_read(RSocket *s, unsigned char *buf, int len) {
	if (!s) {
		return -1;
	}
#if HAVE_LIB_SSL
	if (s->is_ssl) {
		if (s->bio) {
			return BIO_read (s->bio, buf, len);
		}
		return SSL_read (s->sfd, buf, len);
	}
#endif
#if __WINDOWS__ && !defined(__CYGWIN__) //&& !defined(__MINGW64__)
rep:
	{
	int ret = recv (s->fd, (void *)buf, len, 0);
	//if (ret != len)
	//	return 0;
	//r_sys_perror ("recv");
	if (ret == -1) goto rep;
	return ret;
	}
#else
	return read (s->fd, buf, len);
#endif
}

R_API int r_socket_read_block(RSocket *s, unsigned char *buf, int len) {
	int r, ret = 0;
	for (ret = 0; ret < len; ) {
		r = r_socket_read (s, buf+ret, len-ret);
		if (r < 1) {
			break;
		}
		ret += r;
	}
	return ret;
}

R_API int r_socket_gets(RSocket *s, char *buf,	int size) {
	int i = 0;
	int ret = 0;

	if (s->fd == -1) {
		return -1;
	}
	while (i < size) {
		ret = r_socket_read (s, (ut8 *)buf + i, 1);
		if (ret == 0) {
			if (i > 0) {
				return i;
			}
			return -1;
		}
		if (ret < 0) {
			r_socket_close (s);
			return i == 0? -1: i;
		}
		if (buf[i] == '\r' || buf[i] == '\n') {
			buf[i] = 0;
			break;
		}
		i += ret;
	}
	buf[i]='\0';
	return i;
}

R_API RSocket *r_socket_new_from_fd (int fd) {
	RSocket *s = R_NEW0 (RSocket);
	if (s) {
		s->fd = fd;
	}
	return s;
}

R_API ut8* r_socket_slurp(RSocket *s, int *len) {
	int blockSize = 4096;
	ut8 *ptr, *buf = malloc (blockSize);
	int copied = 0;
	if (len) {
		*len = 0;
	}
	for (;;) {
		int rc = r_socket_read (s, buf + copied, blockSize);
		if (rc > 0) {
			copied += rc;
		}
		ptr = realloc (buf, copied + blockSize);
		if (ptr) {
			buf = ptr;
		} else {
			break;
		}
		if (rc < 1) {
			break;
		}
	}
	if (copied == 0) {
		R_FREE (buf);
	}
	if (len) {
		*len = copied;
	}
	return buf;
}

#endif // EMSCRIPTEN
