/*
 *  afp.c
 *
 *  Copyright (C) 2006 Alex deVries
 *  Portions copyright (C) 2007 Derrik Pates
 *
 */



#include "afpfs-ng/afp.h"
#include <config.h>

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "afpfs-ng/afp_protocol.h"
#include "afpfs-ng/libafpclient.h"
#include "server.h"
#include "afpfs-ng/dsi.h"
#include "dsi_protocol.h"
#include "afpfs-ng/utils.h"
#include "afp_replies.h"
#include "afp_internal.h"
#include "did.h"
#include "forklist.h"
#include "afpfs-ng/codepage.h"

struct afp_versions      afp_versions[] = {
            { "AFPVersion 1.1", 11 },
            { "AFPVersion 2.0", 20 },
            { "AFPVersion 2.1", 21 },
            { "AFP2.2", 22 },
            { "AFPX03", 30 },
            { "AFP3.1", 31 },
            { "AFP3.2", 32 },
            { NULL, 0}
        };

static int afp_blank_reply(struct afp_server *server, char * buf, unsigned int size, void * ignored);

int (*afp_replies[])(struct afp_server * server,char * buf, unsigned int len, void * other) = {
	NULL, afp_byterangelock_reply, afp_blank_reply, NULL,
	afp_blank_reply, NULL, afp_createdir_reply, afp_blank_reply, /* 0 - 7 */
	afp_blank_reply, afp_enumerate_reply, afp_blank_reply, afp_blank_reply, 
	NULL, NULL, NULL, NULL,                       /* 8 - 15 */
	afp_getsrvrparms_reply, afp_getvolparms_reply, afp_login_reply, afp_login_reply,
	afp_blank_reply, afp_mapid_reply, afp_mapname_reply, afp_blank_reply,  /*16 - 23 */
	afp_volopen_reply, NULL, afp_openfork_reply, afp_read_reply,
	afp_blank_reply, afp_blank_reply, afp_blank_reply, afp_blank_reply,    /*24 - 31 */
	NULL, afp_write_reply, afp_getfiledirparms_reply, afp_blank_reply,
	afp_changepassword_reply, afp_getuserinfo_reply, afp_getsrvrmsg_reply, NULL,      /*32 - 39 */

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,                       /*40 - 47 */
	afp_opendt_reply, afp_blank_reply, NULL, afp_geticon_reply,
	NULL, NULL, NULL, NULL,                       /*48 - 55 */
	afp_blank_reply, NULL, afp_getcomment_reply, afp_byterangelockext_reply,
	afp_readext_reply, afp_writeext_reply, 
	NULL, NULL,                       /*56 - 63 */
	afp_getsessiontoken_reply,afp_blank_reply, NULL, NULL,
	afp_enumerateext2_reply, NULL, NULL, NULL,    /*64 - 71 */
	afp_listextattrs_reply, NULL, NULL, NULL,
	afp_blank_reply, NULL, afp_blank_reply, afp_blank_reply,                       /*72 - 79 */

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,

};

/* 
 * afp_server_identify()
 *
 * Identifies a server
 *
 * Right now, this only does identification using the machine_type
 * given in getsrvrinfo, but this could later use mDNS to get 
 * more details.
 */
void afp_server_identify(struct afp_server * s)
{
	if (strcmp(s->machine_type,"Netatalk")==0)
		s->server_type=AFPFS_SERVER_TYPE_NETATALK;
	else if (strcmp(s->machine_type,"AirPort")==0)
		s->server_type=AFPFS_SERVER_TYPE_AIRPORT;
	else if (strcmp(s->machine_type,"Macintosh")==0)
		s->server_type=AFPFS_SERVER_TYPE_MACINTOSH;
	else
		s->server_type=AFPFS_SERVER_TYPE_UNKNOWN;
}

/* This is the simplest afp reply */
static int afp_blank_reply(struct afp_server *server, char * buf, unsigned int size, void * ignored)
{
	struct {
		struct dsi_header header __attribute__((__packed__));
	} * reply = (void *) buf;
	return reply->header.return_code.error_code;
}

/* Handle a reply packet */
int afp_reply(unsigned short subcommand, struct afp_server * server, void * other) 
{
	int ret=0;

	/* No AFP packet is valid if it is smaller than a DSI header. */

	if (server->data_read<sizeof(struct dsi_header))
		return -1;

	if (afp_replies[subcommand]) {
		ret=(*afp_replies[subcommand])(server,
			server->incoming_buffer,
			server->data_read, other);
	} else {
		log_for_client(NULL,AFPFSD,LOG_WARNING,
			"AFP subcommand %d not supported\n",subcommand);
	}
	return ret;
}


static struct afp_server * server_base=NULL;

int server_still_valid(struct afp_server * server) 
{
	struct afp_server * s = server_base;

	for (;s;s=s->next)
		if (s==server) return 1;
	return 0;
}

static void add_server(struct afp_server *newserver)
{
        newserver->next=server_base;
        server_base=newserver;
}

struct afp_server * get_server_base(void) 
{
	return server_base;
}

struct afp_server * find_server_by_signature(char * signature) 
{
	struct afp_server * s;

	for (s=get_server_base();s;s=s->next) {
		if (memcmp(s->signature,signature,AFP_SIGNATURE_LEN)==0) {
			return s;
		}
	}
	return NULL;
}

struct afp_server * find_server_by_name(char * name) 
{
	struct afp_server * s;
	for (s=get_server_base(); s; s=s->next) {
		if (strcmp(s->server_name_utf8,name)==0) return s;
		if (strcmp(s->server_name,name)==0) return s;
	}

	return NULL;
}

struct afp_server * find_server_by_address(struct addrinfo *address)
{
    struct afp_server *s;

	for (s=server_base;s;s=s->next) {
        if (s->used_address != NULL && s->used_address->ai_addr != NULL &&
			address != NULL && address->ai_addr != NULL &&
			bcmp(&s->used_address->ai_addr, &address->ai_addr, 
				sizeof(struct sockaddr))==0) {
			return s;
		}
	}
    return NULL;
}

int something_is_mounted(struct afp_server * server)
{
	int i;


	for (i=0;i<server->num_volumes;i++) {
		if (server->volumes[i].mounted != AFP_VOLUME_UNMOUNTED ) 
			return 1;
	}
	return 0;
}

int afp_unmount_all_volumes(struct afp_server * server) 
{

        int i;
        for (i=0;i<server->num_volumes;i++) {
                if (server->volumes[i].mounted == AFP_VOLUME_MOUNTED) {
                        if (afp_unmount_volume(&server->volumes[i]))
                                return 1;
		}
        }
	return 0;
}


int afp_unmount_volume(struct afp_volume * volume)
{

	struct afp_server * server;

	if (volume==NULL)
		return -1;

	server=volume->server;
	if (volume->mounted != AFP_VOLUME_MOUNTED) {
		return -1;
	}

	volume->mounted=AFP_VOLUME_UNMOUNTING;

	/* close the volume */

	afp_flush(volume);

	free_entire_did_cache(volume);
	remove_fork_list(volume);
	if (volume->dtrefnum) afp_closedt(server,volume->dtrefnum);
	volume->dtrefnum=0;

	if (libafpclient->unmount_volume)
		libafpclient->unmount_volume(volume);

	volume->mounted=AFP_VOLUME_UNMOUNTED;

	/* Figure out if this is the last volume of the server */

	if (something_is_mounted(server)) return 0;

	/* Logout */
	afp_logout(server,DSI_DONT_WAIT /* don't wait */);

	afp_server_remove(server);

	return -1;

}


void afp_free_server(struct afp_server ** sp)
{
	struct dsi_request * p, *next;
	struct afp_volume * volumes;
	struct afp_server * server;

	if (sp==NULL) return;
	
	server=*sp;

	if (!server) return;

	for (p=server->command_requests;p;) {
		log_for_client(NULL,AFPFSD,LOG_NOTICE,"FSLeft in queue: %p, id: %d command: %d\n",                p,p->requestid,p->subcommand);
		next=p->next;
		free(p);
		p=next;
	}

	volumes=server->volumes;

	loop_disconnect(server);

	if (server->incoming_buffer) free(server->incoming_buffer);
	if (server->attention_buffer) free(server->attention_buffer);
	if (volumes) free(volumes);

	free(server);

	*sp=NULL;
}

int afp_server_remove(struct afp_server *s) 
{
	
	struct dsi_request * p;
	struct afp_server *s2;


	if (s==NULL) 
		goto out;

	for (p=s->command_requests;p;p=p->next) {
		pthread_mutex_lock(&p->waiting_mutex);
		p->done_waiting=1;
		pthread_cond_signal(&p->waiting_cond);
		pthread_mutex_unlock(&p->waiting_mutex);
	}

	if (s==server_base) {
		server_base=s->next;
		afp_free_server(&s);
		goto out;
	}

	for (s2=server_base;s2;s2=s2->next) {
		if (s==s2->next) {
			s2->next=s->next;
			afp_free_server(&s);
			goto out;
		}
	}
	return -1;
out:
	return 0;

}

struct afp_server * afp_server_init(struct addrinfo * address)
{
	struct afp_server * s;
	struct passwd *pw;

	if ((s = malloc(sizeof(*s)))==NULL) 
		return NULL;
	memset((void *) s, 0, sizeof(*s));
	s->exit_flag = 0;
	s->path_encoding=kFPUTF8Name;  /* This is a default */
	s->next=NULL;
	s->bufsize=4096;
	s->incoming_buffer=malloc(s->bufsize);

	s->attention_quantum=AFP_DEFAULT_ATTENTION_QUANTUM;
	s->attention_buffer=malloc(s->attention_quantum);
	s->attention_len=0;

	s->connect_state=SERVER_STATE_DISCONNECTED;
	s->address = address;

	/* FIXME this shouldn't be set here */
	pw=getpwuid(geteuid());
	memcpy(&s->passwd,pw,sizeof(struct passwd));
	return s;
}

static void setup_default_outgoing_token(struct afp_token * token)
{
	char foo[] = {0x54,0xc0,0x75,0xb0,0x15,0xe6,0x1c,0x13,
	0x86,0x75,0xd2,0xc2,0xfd,0x03,0x4e,0x3b};
	token->length=16;
	memcpy(token->data,foo,16);
}

static int resume_token(struct afp_server * server)
{

	struct afp_token outgoing_token;
	time_t now;
	int ret;

	/* Get the time */

	time(&now);

	/* Setup the outgoing token */
	setup_default_outgoing_token(&outgoing_token);

	ret=afp_getsessiontoken(server,kReconnWithTimeAndID,
		(unsigned int) now,
		&outgoing_token,&server->token);

	return ret;

}
static int setup_token(struct afp_server * server)
{

	time_t now;
	int ret;
	struct afp_token outgoing_token;

	/* Get the time */

	time(&now);

	/* Setup the outgoing token */
	setup_default_outgoing_token(&outgoing_token);

	ret=afp_getsessiontoken(server,kLoginWithTimeAndID,
		(unsigned int) now,
		&outgoing_token,&server->token);

	return ret;

}

int afp_server_login(struct afp_server *server, 
	char * mesg, unsigned int *l, unsigned int max) 
{
	int rc;

	rc=afp_dologin(server,server->using_uam,
		server->username,server->password);

	switch(rc) {
	case -1:
		*l+=snprintf(mesg,max-*l,
			"Could not find a valid UAM\n");
		goto error;
	case kFPAuthContinue:
		*l+=snprintf(mesg,max-*l,
			"Authentication method unsupported by AFPFS\n");
		goto error;
	case kFPBadUAM:
		*l+=snprintf(mesg,max-*l,
			"Specified UAM is unknown\n");
		goto error;
	case kFPBadVersNum:
		*l+=snprintf(mesg,max-*l,
			"Server does not support this AFP version\n");
	case kFPCallNotSupported:
	case kFPMiscErr:
		*l+=snprintf(mesg,max-*l,
			"Already logged in\n");
		break;
	case kFPNoServer:
		*l+=snprintf(mesg,max-*l,
			"Authentication server not responding\n");
		goto error;
	case kFPPwdExpiredErr:
	case kFPPwdNeedsChangeErr:
		*l+=snprintf(mesg,max-*l,
			"Warning: password needs changing\n");
		goto error;
	case kFPServerGoingDown:
		*l+=snprintf(mesg,max-*l,
			"Server going down, so I can't log you in.\n");
		goto error;
	case kFPUserNotAuth:
		*l+=snprintf(mesg,max-*l,
			"Authentication failed\n");
		goto error;
	case 0: break;
	default:
		*l+=snprintf(mesg,max-*l,
			"Unknown error, maybe username/passwd needed?\n");
		goto error;
	}

	if (server->flags & kSupportsReconnect) {
		/* Get the session */

		if (server->need_resume) {
			resume_token(server); 
			server->need_resume=0;
		} else {
			setup_token(server);
		}
	}

	return 0;
error:
	return 1;
}


struct afp_volume * find_volume_by_name(struct afp_server * server, 
	const char * volname)
{
	int i;
	struct afp_volume * using_volume=NULL;
	char converted_volname[AFP_VOLUME_NAME_LEN];

	memset(converted_volname,0,AFP_VOLUME_NAME_LEN);

	convert_utf8pre_to_utf8dec(volname,strlen(volname),
		converted_volname,AFP_VOLUME_NAME_LEN);

 	for (i=0;i<server->num_volumes;i++)  {
		if (strcmp(converted_volname,
			server->volumes[i].volume_name_printable)==0) 
		{
			using_volume=&server->volumes[i];
			goto out;
		}
	}
out:

	return using_volume;
}

int afp_connect_volume(struct afp_volume * volume, struct afp_server * server,
	char * mesg, unsigned int * l, unsigned int max)
{
	unsigned short bitmap=
			kFPVolAttributeBit|kFPVolSignatureBit|
			kFPVolCreateDateBit|kFPVolIDBit |
			kFPVolNameBit;
	char new_encoding;
     	int ret;

	if (server->using_version->av_number>=30) 
		bitmap|= kFPVolNameBit|kFPVolBlockSizeBit;

	ret = afp_volopen(volume,bitmap,
		(strlen(volume->volpassword)>0) ? volume->volpassword : NULL);
	switch(ret){
	case kFPAccessDenied:
		*l+=snprintf(mesg,max-*l,
			"Incorrect volume password\n");
		goto error;
	case kFPNoErr:
		break;
	case kFPBitmapErr:
	case kFPMiscErr:
	case kFPObjectNotFound:
	case kFPParamErr:
		*l+=snprintf(mesg,max-*l,
			"Could not open volume\n");
		goto error;
	case ETIMEDOUT:
		*l+=snprintf(mesg,max-*l,
			"Timed out waiting to open volume\n");
		goto error;
	}

	/* It is said that if a volume's encoding will be the same 
	 * the server's. */
	if (volume->attributes & kSupportsUTF8Names)
		new_encoding=kFPUTF8Name;
	else
		new_encoding=kFPLongName;

	if (new_encoding != server->path_encoding) {
		*l+=snprintf(mesg,max-*l,
			"Volume %s changes the server's encoding\n",
			volume->volume_name_printable);
	}

	server->path_encoding=new_encoding;

	if (volume->signature != AFP_VOL_FIXED) {
		*l+=snprintf(mesg,max-*l,
			"Volume %s does not support fixed directories\n",
			volume->volume_name_printable);
		afp_unmount_volume(volume);
		goto error;

	}

	if (server->using_version->av_number >=30) {
		if ((volume->server->server_type==AFPFS_SERVER_TYPE_NETATALK) &&
			(~ volume->attributes & kSupportsUnixPrivs)) {
			volume->extra_flags &=~VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX;
		} else {
			volume->extra_flags |= VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX;
		}
	} else {
		/* This is very odd, but AFP 2.x doesn't give timestamps for directories */

	}
	
	volume->mounted=AFP_VOLUME_MOUNTED;

	return 0;
error:
	return 1;
}

int afp_server_reconnect(struct afp_server * s, char * mesg,
	unsigned int *l, unsigned int max)
{
	int i;
	struct afp_volume * v;

        if (afp_server_connect(s,0))  {
		*l+=snprintf(mesg,max-*l,"Error resuming connection to %s\n",
			s->server_name_printable);
                return 1;
        }

        dsi_opensession(s);

	if(afp_server_login(s,mesg,l,max)) return 1;

         for (i=0;i<s->num_volumes;i++) {
                v=&s->volumes[i];
                if (strlen(v->mountpoint)) {
			if (afp_connect_volume(v,v->server,mesg,l,max))
				*l+=snprintf(mesg,max-*l,
                                        "Could not mount %s\n",
					v->volume_name_printable);
                }
        }

        return 0;
}


int afp_server_connect(struct afp_server *server, int full)
{
	int 	error = 0;
	struct 	timeval t1, t2;
	struct 	addrinfo *address;
	char	log_msg[64];
	char	ip_addr[INET6_ADDRSTRLEN];

	address = server->address;
   	while (address) 
	{
		switch(address->ai_family) 
		{
			case AF_INET6:
			    inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)address->ai_addr)->sin6_addr),
			            ip_addr, INET6_ADDRSTRLEN);
			break;
			case AF_INET:
			    inet_ntop(AF_INET, &(((struct sockaddr_in *)address->ai_addr)->sin_addr),
			            ip_addr, INET6_ADDRSTRLEN);
			break;
			default:
				snprintf(ip_addr, 22, "unknown address family");
			break;
		}

		snprintf(log_msg, sizeof(log_msg), "trying %s ...", ip_addr);

		log_for_client(NULL, AFPFSD, LOG_NOTICE, log_msg);

       	server->fd = socket(address->ai_family,
                        address->ai_socktype, address->ai_protocol);

        if (server->fd >= 0) 
		{
        	    if (connect(server->fd, address->ai_addr, address->ai_addrlen) == 0)
        	        break;
        	    close(server->fd);
        	    server->fd	= -1;
       	}
        address	= address->ai_next;
   	}

	if(server->fd < 0)
	{
		error = errno;
		goto error;
	}

	server->exit_flag		= 0;
	server->lastrequestid	= 0;
	server->connect_state	= SERVER_STATE_CONNECTED;
	server->used_address	= address;

	add_server(server);

	add_fd_and_signal(server->fd);
	if (!full) {
		return 0;
	}

	/* Get the status, and calculate the transmit time.  We use this to
	* calculate our rx quantum. */
	gettimeofday(&t1,NULL);

	if ((error=dsi_getstatus(server))!=0) 
		goto error;
	gettimeofday(&t2,NULL);

        afp_server_identify(server);

	if ((t2.tv_sec - t1.tv_sec) > 0)
		server->tx_delay= (t2.tv_sec - t1.tv_sec) * 1000;
	else
		server->tx_delay= (t2.tv_usec - t1.tv_usec) / 1000;

	/* Calculate the quantum based on our tx_delay and a threshold */
	/* For now, we'll just set a default */
	/* This is the default in 10.4.x where x > 7 */
	server->rx_quantum = 128 * 1024;


	return 0;
error:
	return -error;
}

struct afp_versions * pick_version(unsigned char *versions,
	unsigned char requested) 
{
	/* Pick the right version number.  This means either the 
	   one requested or the last one. Set both the number and the
	   string. */
	int i;
	char version_num=0;
	char found_version=0;
	struct afp_versions * p;
	char highest=0;

	if (requested)
		requested= min(requested,AFP_MAX_SUPPORTED_VERSION);

	for (i=0;versions[i] && (i<SERVER_MAX_VERSIONS);i++) {
		version_num=versions[i];
		highest=max(highest,version_num);
		if (versions[i]==requested) {
			found_version=1;
			break;
		}
	};

	if (!found_version)
		version_num=highest;

	for (p=afp_versions;p->av_name;p++) {
		if (p->av_number==version_num) {
			return p;
		}
	}
	return NULL;
}

int pick_uam(unsigned int uam2, unsigned int uam1)
{

	int i;
	for (i=15;i>=0;i--) {
		if ( ((1<<i)) & (uam2 & uam1)) return (1<<i);

	}
	return -1;
}

int afp_list_volnames(struct afp_server * server, char * names, int max)
{
	int len=0;
	int i;

	for (i=0;i<server->num_volumes;i++) {
		if (i<server->num_volumes-1) 
			len+=snprintf(names+len,max-len,"%s, ",
				server->volumes[i].volume_name_printable);
		else 
			len+=snprintf(names+len,max-len,"%s",
				server->volumes[i].volume_name_printable);
	}
	return len;
}