/*
 * Copyright (C) 2001, 2002, and 2003  Roy Keene
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *      email: libopennet@rkeene.org
 */


/*
 *	Network related functions
 */
#include "compat.h"
#include "opennet.h"
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include <stdarg.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#include "parse.h"
#include "crc.h"

#ifndef O_BINARY
#define O_BINARY 0x0
#endif
#ifndef S_IFSOCK
#define S_IFSOCK 0140000
#endif
#ifndef O_LARGEFILE
#define O_LARGEFILE 0x0
#endif


//extern int errno;
struct opennet_url_info {
	char *url;
	int flags;
	mode_t mode;
};
struct opennet_url_info opennet_urls[256];

/* Do things required for network access. */
int opennet_init(void) {
#ifdef _USE_WIN32_  
	WSADATA wsaData;

	if (WSAStartup(MAKEWORD(2, 0), &wsaData)!=0) {
		return(-1);                           
	}
	if (wsaData.wVersion!=MAKEWORD(2, 0)) {
		/* Cleanup Winsock stuff */    
		WSACleanup();              
		return(-1);  
	}
#endif
	return(0);
}

static int createconnection_tcp(char *host, int port) {
	int sockid;
	struct hostent *hostinfo;
	struct sockaddr_in sock;

#ifdef HAVE_INET_ATON
	if (!inet_aton(host,&sock.sin_addr)) {
#else
#ifdef HAVE_INET_ADDR
	if ( (sock.sin_addr.s_addr=inet_addr(host) )==-1) {
#else
	{
#endif
#endif
		if ((hostinfo=gethostbyname(host))==NULL) return(-EPERM);
		memcpy(&sock.sin_addr.s_addr,hostinfo->h_addr_list[0],hostinfo->h_length);
	}

	sock.sin_family=AF_INET;
	sock.sin_port=htons(port);
	if ((sockid=socket(AF_INET, SOCK_STREAM, 0))<0) {
		return(-1);
	}
	if (connect(sockid, (struct sockaddr *) &sock, sizeof(sock))<0) {
		close(sockid);
		return(-1);
	}
	return(sockid);
}

int open_net(const char *pathname, int flags, mode_t mode) {
	char scheme[5], host[512], file[1024];
	char username[128], password[128];
	char outbuf[8192];
	char *read_buf, *read_buf_s=NULL;
	char *smallbuf, *tmpbuf;
	int port, fd, x, ftpfd=-1, retrmode=0;

	if (!parse_url(pathname,scheme,username,password,host,&port,file)) {
		if ((fd=createconnection_tcp(host,port))<0) return(-1);

		switch (elfcrc(0, scheme, strlen(scheme))) {
			case 457648: /* http */
				if ((flags&O_WRONLY)==O_WRONLY || (flags&O_RDWR)==O_RDWR) {
					close(fd);
					return(-1);
				}
				read_buf_s = read_buf = malloc(50);

				snprintf(outbuf, sizeof(outbuf), "GET %s HTTP/1.0\nHost: %s\n", file, host);

				write(fd, outbuf, strlen(outbuf));

				if (strcmp(username, "")) {
					smallbuf=malloc(strlen(password)+strlen(username)+3);
					smallbuf[0]=0;
					strcat(smallbuf,username);
					strcat(smallbuf,":");
					strcat(smallbuf,password);
					tmpbuf=mime64(smallbuf);

					snprintf(outbuf, sizeof(outbuf), "Authorization: Basic %s\n", tmpbuf);
					write(fd, outbuf, strlen(outbuf));

					free(tmpbuf);
					free(smallbuf);
					write(fd, "\n", 1);
				}
				write(fd, "\n", 1);
				x=read(fd, read_buf, 50);
				strsep(&read_buf," ");
				if (strncmp("200 ",read_buf,4) && strncmp("302 ",read_buf,4)) {
					free(read_buf_s);
					close(fd);
//					errno=ENOENT;
					return(-ENOENT);
				}
				retrmode=read_buf[0];
				read_buf=read_buf_s;
				read_buf[4]=0;
				while (1) {
					read(fd, read_buf, 1);
					if (read_buf[0]==read_buf[2]&&read_buf[0]=='\n') break;
					if (!strncmp(":noi", read_buf, 4) && retrmode=='3') {
						tmpbuf=smallbuf=malloc(512);
						read(fd, read_buf, 1);
						read(fd, smallbuf, 510);
						close(fd);
						return(open_net(strsep(&smallbuf,"\r\n"),flags,mode));
						free(tmpbuf);
					}
					read_buf[3]=read_buf[2];
					read_buf[2]=read_buf[1];
					read_buf[1]=read_buf[0];
				}

				free(read_buf_s);

				if (opennet_urls[fd].url!=NULL) free(opennet_urls[fd].url);
				opennet_urls[fd].url=strdup(pathname);
				opennet_urls[fd].flags=flags;
				opennet_urls[fd].mode=mode;
				return(fd);
				break;
			case 28080: /* ftp */
				if ((flags&O_RDWR)==O_RDWR) {
					close(fd);
					return(-1);
				}

				read_buf_s=read_buf=malloc(1024);
				read_buf[0]=0;

				while (1) {
					if ((x=read(fd, read_buf, 1024))<1024) {
						if (x<0) {
							free(read_buf_s);
//							errno=EIO;
							return(-EIO);
						}
						/* It's waiting for input... */
					}
					read_buf[x]=0;

					while ((smallbuf=strsep(&read_buf,"\n"))!=NULL) {
						switch (elfcrc(0, smallbuf, 4)) {
							case 231456: /* 550 */
								if (ftpfd!=-1) close(ftpfd);
								close(fd);
								free(read_buf_s);
//								errno=ENOENT;
								return(-ENOENT);
								break;
							case 230944: /* 530 */
								if (ftpfd!=-1) close(ftpfd);
								close(fd);
								free(read_buf_s);
//								errno=EIO;
								return(-EIO);
								break;
							case 231504: /* 553 */
								if (ftpfd!=-1) close(ftpfd);
								close(fd);
								free(read_buf_s);
//								errno=EPERM;
								return(-EPERM);
								break;
							case 218400: /* 220 */
								write(fd, "USER ", 5);
								if (strcmp(username,"")) {
									write(fd, username, strlen(username));
								} else {
									write(fd, "anonymous", 9);
								}
								write(fd, "\n", 1);
								break;
							case 222768: /* 331 */
								write(fd, "PASS ", 5);
								if (strcmp(password,"")) {
									write(fd, password, strlen(password));
								} else {
									write(fd, "user@host.com", 13);
								}
								write(fd, "\n", 1);
								break;
							case 218512: /* 227 */
								strsep(&smallbuf,"(");
								host[0]=0;
								for (x=0;x<4;x++) {
									strncat(host,strsep(&smallbuf,","),5);
									strcat(host,".");
								}
								host[strlen(host)-1]=0;
								port=(atoi(strsep(&smallbuf,","))<<8)+atoi(strsep(&smallbuf,")\n\r "));
								write(fd, "TYPE I\n", 7);
								break;
							case 217888:
								if ((flags&O_RDONLY)==O_RDONLY) write(fd,"RETR ",5);
								if ((flags&O_WRONLY)==O_WRONLY) write(fd,"STOR ",5);
								write(fd, file, strlen(file));
								write(fd, "\n", 1);
								if ((ftpfd=createconnection_tcp(host,port))<0) {
									close(fd);
//									errno=ENOENT;
									return(-ENOENT);
								}
								break;
							case 215072: /* 150 */ 
								if (opennet_urls[ftpfd].url!=NULL) free(opennet_urls[ftpfd].url);
								opennet_urls[ftpfd].url=strdup(pathname);
								opennet_urls[ftpfd].flags=flags;
								opennet_urls[ftpfd].mode=mode;
								return(ftpfd);
								break;
							case 218656: /* 230 */
								write(fd, "PASV\n", 5);
								break;
							default:
								break;
						}
					}
					read_buf=read_buf_s;
				}

				
				break;
		}

		free(read_buf_s);
		close(fd);
	} else {
		fd=open(pathname,flags|O_BINARY|O_LARGEFILE,mode);
		if (opennet_urls[fd].url!=NULL) free(opennet_urls[fd].url);
		opennet_urls[fd].url=strdup(pathname);
		opennet_urls[fd].flags=flags;
		opennet_urls[fd].mode=mode;
		return(fd);
	}

	return(-1);
}

off_t lseek_net(int filedes, off_t offset, int whence) {
	struct stat file_status;
	char tmpbuf[1024];
	off_t retval, rset=0, i;
	size_t n;
	ssize_t read_val;
	int newfd;

	fstat(filedes, &file_status);
	if ((file_status.st_mode&S_IFSOCK)==S_IFSOCK) {
		if (whence!=SEEK_CUR || offset<=0) {
			if (whence!=SEEK_SET) return(-1); /* For now ... */
			if (opennet_urls[filedes].url==NULL) return(-1);
			if ((newfd=open_net(opennet_urls[filedes].url,opennet_urls[filedes].flags,opennet_urls[filedes].mode))<0) return(-1);
			close(filedes);
			dup2(newfd,filedes);
			if (offset==0) return(0);
		}
	} else {
		retval=lseek(filedes, offset, whence);
		if (!(retval<0 && whence==SEEK_CUR && offset>0)) {
			return(retval);
		}
	}

	retval=0;
	for (i=0; i<offset; i++) {
		n=offset-rset;
		if (n>sizeof(tmpbuf)) n=sizeof(tmpbuf);
		read_val=read(filedes, tmpbuf, n);
		if (read_val<=0) { retval=-1; break; }
		rset+=read_val;
	}
	retval=offset+1;
	return(retval);
}

FILE *fopen_net(const char *path, const char *mode) {
	FILE *ret = NULL;
	int fd = -1;
	mode_t openmode = -1;

	if (strcmp(mode, "r") == 0 || strcmp(mode, "rb") == 0) {
		openmode = O_RDONLY;
	}
	if (strcmp(mode, "w") == 0 || strcmp(mode, "wb") == 0) {
		openmode = O_WRONLY;
	}

	/* open_net() only handles WRONLY and RDONLY... */
	if (openmode != O_WRONLY && openmode != O_RDONLY) {
		return(fopen(path, mode));
	}

	fd = open_net(path, openmode, 0644);
	if (fd < 0) {
		return(NULL);
	}

	ret = fdopen(fd, mode);

	return(ret);
}

int fseek_net(FILE *stream, long offset, int whence) {
	return(-1);
}
