
/*
    LinuxWare daemon - Netware like server for Linux

    Copyright (C) 1994, 1995  Ales Dryak <e-mail: A.Dryak@sh.cvut.cz>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <utime.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include "logging.h"
#include "ncp.h"
#include "fsystem.h"
#include "handlers.h"
#include "fileops.h"

static int get_mode(int attr)
{
	int mode=0;
	
	if (attr&AR_READ) mode|=S_IRUSR|S_IRGRP|S_IROTH;
	if (attr&AR_WRITE) mode|=S_IWUSR;
	if (attr&AR_ARCHIVE) mode|=S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR;
	if (mode==0) mode=S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR; /* common rights */
	return mode;
}

void handle_ncp_request_main(struct ncp_request* pkt,int size,connection* c)
{
	struct ncp_request_sf* sfrq=(struct ncp_request_sf*)pkt;
	struct ncp_request_sfnew* sfrqn=(struct ncp_request_sfnew*)pkt;
	switch(pkt->func)
	{
	case 20: /* get date info */
	{
		struct ncp_date_info* nfo=(struct ncp_date_info*)get_conn_buf(c);
		
		ncp_get_date(nfo);
		ncp_send(c,sizeof(*nfo));
		return;
	}
	case 21: /* messages?? */
		ncp_handle_r21(sfrq->s_func,ntohs(sfrq->s_len),(char*)(sfrq+1),c);
		return;
	case 22: /* file server directory ops ?? */
		ncp_handle_r22(sfrq->s_func,ntohs(sfrq->s_len),(char*)(sfrq+1),c);
		return;
	case 23: /* bindery */ 
		ncp_handle_r23(sfrq->s_func,ntohs(sfrq->s_len),(char*)(sfrq+1),c);
		return;
	case 24: /* end of job */
	{
		/* free temporary dir handles now? */
		dir_handle* dh;
		int h;
		
		for(h=1;h<MAX_DIR_CONN;h++) 
		{
			if ((dh=get_dir_handle(c,h))!=NULL)
			{
				if (!dh->permanent)
				{
					LPRINTF(LL_INFO,("free temp handle: %i conn: %i\n",h,get_conn_id(c)));
					free_dir(c,h);
				}
			}
				
		}
		ncp_send_ok(c);
		return;
	}
	case 25: /* log out */
	{
		if (c->logged_obj!=NULL) LPRINTF(LL_INFO,("logout: %s conn: %i\n",c->logged_obj->name.name,get_conn_id(c)));
		c->logged_obj=NULL;		
		ncp_send_ok(c);
		return;
	}
	case 33: /* negotiate buffer size */
	{
		word bsize=ntohs(*(word*)(pkt+1));
		*(word*)(get_conn_buf(c))=htons(bsize>BUF_SIZE?BUF_SIZE:bsize);
		ncp_send(c,2);
		return;
	}
	case 62: /* file search initialize */
	{
		/* handle search with not null path request prop.! */	
		struct ncp_file_search_init_rq* rq=(struct ncp_file_search_init_rq*)(pkt+1);
		struct ncp_file_search_init_rpl* rpl=(struct ncp_file_search_init_rpl*)get_conn_buf(c);
		dir_handle* dh;
		
		if ((dh=get_dir_handle(c,rq->dir_handle))!=NULL)
		{
			char path[MAX_PATH];
			DIR* dir;
			
			rq->path[rq->path_len]=0;
			if (nw2unix_path(path,dh,rq->path)!=NULL &&
			    (dir=opendir(path))!=NULL)
			{
				int sh;
				
				closedir(dir);
				LPRINTF(LL_DEBUG,("opendir %s OK\n",path));
				if ((sh=alloc_search(c,dh,rq->path))>=0)
				{
					rpl->vol_id=get_volume_id(dh->volume);
					rpl->dir_id=htons(sh);
					rpl->seq_num=htons(-1);
					rpl->access=255; /* hard wired */
					ncp_send(c,sizeof(*rpl));
					return;
				}
			}
		}
		ncp_send_failure(c,COMPL_FAILURE);
		
		return;
	}
	case 63: /* file search continue */
	{
		struct ncp_file_search_cont_rq* rq=(struct ncp_file_search_cont_rq*)(pkt+1);
		struct ncp_file_search_cont_rpl* rpl=(struct ncp_file_search_cont_rpl*)get_conn_buf(c);
		word dir_id;
		search_handle* sh;
		
		dir_id=ntohs(rq->dir_id);
		if ((sh=get_search_handle(c,dir_id))!=NULL 
#if 0
		&& (
		    (rq->vol_id<MAX_VOLUME && get_volume(rq->vol_id)->mounted &&
		    rq->vol_id==get_volume_id(sh->dir->volume)) || 
		    (sh->dir==get_dir_handle(c,ROOT_DIR_ID) && rq->vol_id==ROOT_VOLUME_ID) )
#endif		   
		     )
		{
			rq->path[rq->path_len]=0;
			if (nw_file_search_path(&(rpl->f_name),&(rpl->context),&(rpl->prop),
			    sh->path,rq->path,rq->attr,rq->context))
			{
				rpl->zero=0;
				ncp_send(c,sizeof(*rpl));
				return;
			}
		}
		ncp_send_failure(c,COMPL_FAILURE);
		
		return;
	}
	case 64: /* search for file */
	{
		struct ncp_search_file_rq* rq=(struct ncp_search_file_rq*)(pkt+1);
		struct ncp_search_file_rpl* rpl=(struct ncp_search_file_rpl*)get_conn_buf(c);
		dir_handle* dh;
		
		LPRINTF(LL_DEBUG,("search for file: dir %i\n",rq->dir_id));
		if ((dh=get_dir_handle(c,rq->dir_id))!=NULL)
		{
			
			LPRINTF(LL_DEBUG,("search start\n"));
			rq->path.name[rq->path.name_len]=0;
			if (nw_file_search1(&(rpl->f_name),&(rpl->context),&(rpl->prop),
			    dh,rq->path.name,rq->attr,rq->context))
			{
				rpl->zero=0;
				ncp_send(c,sizeof(*rpl));
				return;
			}
		}
		ncp_send_failure(c,COMPL_FAILURE);
		
		return;
	}
	case 66: /* close file */
	{
		struct ncp_close_file* rq=(struct ncp_close_file*)(pkt+1);
		int file_idx=ncp_get_file_idx(&rq->id);
		
		if (get_file_handle(c,file_idx)!=NULL)
		{
			free_file(c,file_idx);
			ncp_send_ok(c);
		}
		else
		{
			ncp_send_failure(c,COMPL_FAILURE);
		}
		return;
		
	}
	case 77: /* create new file */
	case 67: /* create file */
	{
		struct ncp_creat_file* rq=(struct ncp_creat_file*)(pkt+1);
		struct ncp_open_file_rpl* rpl=(struct ncp_open_file_rpl*)get_conn_buf(c);
		dir_handle* dh;
		
		if ((dh=get_dir_handle(c,rq->dir_handle))!=NULL)
		{
			char path[MAX_PATH];
			int fh_idx;
			int fd;
			int mode=get_mode(rq->attr);
			
			rq->path.name[rq->path.name_len]=0;
			LPRINTF(LL_DEBUG,("create file mode: %o attr: %o\n",mode,rq->attr));
			if (nw2unix_path(path,dh,rq->path.name)!=NULL && 
			   (fd=open(path,O_CREAT|O_WRONLY|O_TRUNC|(pkt->func==77?O_EXCL:0),mode))!=-1)
			{
				if ((fh_idx=alloc_file(c,fd,path))>=0)
				{
					struct stat fs;
					
					fstat(fd,&fs);
					ncp_set_file_id(&(rpl->id),fh_idx);
					rpl->zero=0;
					memset(rpl->f_name.name,0,sizeof(rpl->f_name)); 
					strncpy(rpl->f_name.name,rq->path.name,sizeof(rpl->f_name)-1);
					ncp_set_filedir_prop(&(rpl->prop),&fs);
					ncp_send(c,sizeof(*rpl));
					return;
				}
				close(fd);
			}
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 68: /* erase file */
	{
		struct ncp_creat_file* rq=(struct ncp_creat_file*)(pkt+1);
		dir_handle* dh;
		
		if ((dh=get_dir_handle(c,rq->dir_handle))!=NULL)
		{
			char path[MAX_PATH];
			
			rq->path.name[rq->path.name_len]=0;
			if (nw2unix_path(path,dh,rq->path.name)!=NULL)
			{
				LPRINTF(LL_DEBUG,("erase: %s\n",path));
			   	if (unlink(path)==0)
			   	{
					ncp_send_ok(c);
					return;
				}
			}
			LPRINTF(LL_DEBUG,("erase1: \n"));
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 69: /* rename file */
	{
		struct ncp_rename_file* rq=(struct ncp_rename_file*)(pkt+1);
		struct ncp_rename_file_new* rq_new=(struct ncp_rename_file_new*)(((char*)(rq+1))+rq->old_path.name_len-1);
		dir_handle* odh;
		dir_handle* ndh;
		
		if ((odh=get_dir_handle(c,rq->old_handle))!=NULL &&
		    (ndh=get_dir_handle(c,rq_new->new_handle))!=NULL)
		{
			char opath[MAX_PATH];
			char npath[MAX_PATH];
			
			rq->old_path.name[rq->old_path.name_len]=0;
			rq_new->new_path.name[rq_new->new_path.name_len]=0;
			if (nw2unix_path(opath,odh,rq->old_path.name)!=NULL &&
			    nw2unix_path(npath,ndh,rq_new->new_path.name)!=NULL)
			{
				LPRINTF(LL_DEBUG,("rename: '%s' to '%s'\n",opath,npath));
			   	if (rename(opath,npath)==0)
			   	{
					ncp_send_ok(c);
					return;
				}
			}
			LPRINTF(LL_INFO,("rename1\n"));
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 70: /* set file atributes */
	{
		struct ncp_set_file_attr* rq=(struct ncp_set_file_attr*)(pkt+1);
		dir_handle* dh;
		
		if ((dh=get_dir_handle(c,rq->dir_handle))!=NULL)
		{
			char path[MAX_PATH];
			
			rq->path.name[rq->path.name_len]=0;
			if (nw2unix_path(path,dh,rq->path.name)!=NULL)
			{
				LPRINTF(LL_DEBUG,("set attr: '%s' new mode: %o\n",path,get_mode(rq->attr)));
			   	if (chmod(path,get_mode(rq->attr))==0)
			   	{
					ncp_send_ok(c);
					return;
				}
			}
			LPRINTF(LL_DEBUG,("set attr1\n"));
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 71: /* get file size*/
	{
		struct ncp_get_filesize* rq=(struct ncp_get_filesize*)(pkt+1);
		dword* rpl=(dword*)get_conn_buf(c);
		int file_idx=ncp_get_file_idx(&rq->id);
		file_handle* fh;
		
		if ((fh=get_file_handle(c,file_idx))!=NULL)
		{
			struct stat fs;
			if (fstat(fh->fd,&fs)==0)
			{
				*rpl=htonl(fs.st_size);
				ncp_send(c,sizeof(*rpl));
				return;
			}
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 72: /* read file */
	{
		struct ncp_read_file_rq* rq=(struct ncp_read_file_rq*)(pkt+1);
		struct ncp_read_file_rpl* rpl=(struct ncp_read_file_rpl*)get_conn_buf(c);
		int file_idx=ncp_get_file_idx(&rq->id);
		file_handle* fh;
		
		if ((fh=get_file_handle(c,file_idx))!=NULL)
		{
			LPRINTF(LL_DEBUG,("read at ofs %lu len %u\n",ntohl(rq->offset),htons(rq->to_read)));
			if (lseek(fh->fd,ntohl(rq->offset),SEEK_SET)!=-1)
			{
				int r;
				int odd=(ntohl(rq->offset)&1);
				if ((r=read(fh->fd,rpl->data+odd,ntohs(rq->to_read)))!=-1)
				{
					rpl->read=htons(r);
					ncp_send(c,sizeof(*rpl)-1+odd+r);
					return;
				}
			}
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 73: /* write file */
	{
		struct ncp_write_file* rq=(struct ncp_write_file*)(pkt+1);
		int file_idx=ncp_get_file_idx(&rq->id);
		file_handle* fh;
		
		if ((fh=get_file_handle(c,file_idx))!=NULL)
		{
			if (lseek(fh->fd,ntohl(rq->offset),SEEK_SET)!=-1)
			{
				int r;
				if ((r=write(fh->fd,rq->data,ntohs(rq->to_write)))==ntohs(rq->to_write))
				{
					ncp_send_ok(c);
					return;
				}
			}
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 75: /* set file time/date */
	{
		struct ncp_set_file_time* rq=(struct ncp_set_file_time*)(pkt+1);
		int file_idx=ncp_get_file_idx(&rq->id);
		file_handle* fh;
		struct utimbuf ubuf;
		
		if ((fh=get_file_handle(c,file_idx))!=NULL)
		{
			if ((ubuf.actime=ubuf.modtime=nw2time(ntohs(rq->date),ntohs(rq->time)))>=0 &&
			     utime(fh->path,&ubuf)==0)
			{
				ncp_send_ok(c);
				return;
			}
			LPRINTF(LL_INFO,("can't set time '%s' %i %s\n",fh->path,ubuf.modtime,ctime(&ubuf.modtime)));
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 76: /* open file */
	{
		struct ncp_open_file_rq* rq=(struct ncp_open_file_rq*)(pkt+1);
		struct ncp_open_file_rpl* rpl=(struct ncp_open_file_rpl*)get_conn_buf(c);
		dir_handle* dh;
		
		if ((dh=get_dir_handle(c,rq->dir_handle))!=NULL)
		{
			char path[MAX_PATH];
			int fh_idx;
			int fd;
			int acc;
			
			if (rq->access&AR_READ) acc=O_RDONLY;
			if (rq->access&AR_WRITE) acc=O_WRONLY;
			if (rq->access&AR_READ && rq->access&AR_WRITE) acc=O_RDWR;
			rq->path.name[rq->path.name_len]=0;
			if (nw2unix_path(path,dh,rq->path.name)!=NULL && 
			   (fd=open(path,acc))!=-1)
			{
				if ((fh_idx=alloc_file(c,fd,path))>=0)
				{
					struct stat fs;
					char* p;
					
					fstat(fd,&fs);
					ncp_set_file_id(&(rpl->id),fh_idx);
					rpl->zero=0;
					for(p=rq->path.name+strlen(rq->path.name);p>=rq->path.name;p--)
					{
						if (*p=='\\') break;
					}
					p++;
					memset(rpl->f_name.name,0,sizeof(rpl->f_name)); 
					strncpy(rpl->f_name.name,p,sizeof(rpl->f_name)-1);
					ncp_set_filedir_prop(&(rpl->prop),&fs);
					ncp_send(c,sizeof(*rpl));
					return;
				}
				close(fd);
			}
		}
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
	case 87: /*  new file/dir ops ?? */
		ncp_handle_r87(sfrqn->s_func,0,(char*)(sfrqn+1),c);
		return;
	default: 
		LPRINTF(LL_FULL,("Unknown function (%d)\n",pkt->func));
		ncp_send_failure(c,COMPL_FAILURE);
		return;
	}
}

void ncp_set_file_id(struct ncp_file_id* id,int idx)
{
	idx++;
	id->id[4]=0;
	id->id[5]=0;
	id->id[0]=(idx>>24)&0xFF;
	id->id[1]=(idx>>16)&0xFF;
	id->id[2]=(idx>>8)&0xFF;
	id->id[3]=(idx)&0xFF;
}

int ncp_get_file_idx(struct ncp_file_id* id)
{
	return ((id->id[0]<<24) | (id->id[1]<<16) | (id->id[2]<<8) | id->id[3])-1;
}

