/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1990-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
/*
 * File: ifs_agent.c
 */
#include "ifs_agent.h"
#include <sig.h>

#define IFS_DEBUG

/*
 *name: AgentTable
 *	A list structure records the informations about agents.
 */
struct agent_item	*AgentTable;

/*
 *name:	AgentRegister
 *	Insert a new agent structure in the head of AgentTable
 * rtn: NULL if error
 */
struct agent_item *
AgentRegister( name, initfunc )
char	*name;
FUNC	*initfunc;
{
    struct agent_item	*newitem;

    newitem = (struct agent_item *) MallocZero( sizeof(*newitem) );
    if( newitem != NULL ) {
	newitem->next = AgentTable;
	newitem->name = strdup( name );
	(*initfunc)( newitem );
	AgentTable = newitem;
    }
    return newitem;
}

/*
 *name: AgentSearch
 *	Find a agent item from the AgentTable
 * rtn: NULL if not found
 */
struct agent_item *
AgentSearch( name )
char	*name;
{
    struct agent_item	*item;

    if( name != NULL ) {
	if( *name == 'p' )
	    name++;
	item = AgentTable;
	while( item != NULL ) {
	    if( strcmp( name, item->name ) == 0 )
		return item;
	    item = item->next;
	}
    }
    cserrno = E_PROTOCOL;
    return NULL;
}

/*
 * Initial functions of agents
 */
extern int FtpInit();
extern int GopherInit();
extern int HttpInit();
extern int NewsInit();
extern int RshInit();

int	IfsAbortFlag;
/*
 *name: IfsAbortSignal
 */
void
IfsAbortSignal(sig)
int	sig;
{
    IfsAbortFlag = 1;
}

/*
 *name: IfsInitial
 *	Initial the agents of IFS
 */
void
IfsInitial()
{
    signal( SIGHUP, IfsAbortSignal );
    AgentRegister( "ftp",	FtpInit );
    AgentRegister( "gopher",	GopherInit );
    AgentRegister( "http",	HttpInit );
    AgentRegister( "news",	NewsInit );
    AgentRegister( "rsh",	RshInit );
}

/*
 *name: MountListTable
 *	A list structure records the mount lists of individual users.
 */
struct mount_list	*MountListTable;

/*
 *name: MountListSearch
 *	Search the mount list of a special user.
 *	Insert a new mount list if not found.
 */
struct mount_list *
MountListSearch( uid )
int	uid;
{
    struct mount_list	*ml;

    ml = MountListTable;
    while( ml != NULL ) {
	if( ml->uid == uid )
	    return ml;
	ml = ml->next;
    }
    ml = (struct mount_list *) MallocZero( sizeof(*ml) );
    if( ml != NULL ) {
	ml->next = MountListTable;
	ml->uid = uid;
	MountListTable = ml;
    }
    return ml;
}

/*
 *name: MountItemDump
 *	Dump the mount information
 */
#ifdef IFS_DEBUG_DUMP
void MountItemDump( mi )
struct mount_item *mi;
{
    char	buf[ 1024 ], *ptr;
    int		n;

    logit( "mount_item: ++++++++++++++++++++\n" );
    sprintf( buf, "\tlpath: %s\n\trpath: %s\n\tmode: %d, timeout: %s\n\t",
			mi->lpath, mi->rpath, mi->mode, mi->timeout );
    logit( buf );
    ag_make_remote( mi, mi->rpath, buf );
    logit( buf );
    logit( "\nmount_item: --------------------\n" );
}
#else
#define MountItemDump(mi)	(0)
#endif

/*
 *name: AgentMount
 *	Create a new mount point at localpath and insert to the mount list
 * rtn: NULL if error
 */
struct mount_item *
AgentMount( ml, url, localpath, option )
struct mount_list *ml;
char	*url;
char	*localpath;
char	*option;
{
    struct mount_item	*mi;
    char	proto[ STRLEN ];
    char	*host, *ptr, *pass;

    /* url format:  "proto:/user:pass@host:port/remote-path" */
    /* split proto & user:pass@host:port, and remote-path */
    strncpy( proto, url, sizeof(proto) );
    if( (host = strchr( proto, '/' )) == NULL || host[-1] != ':' ) {
	return NULL;
    }
    host[-1] = '\0';
    *host++ = '\0';

    /* check to protocol */
    if( AgentSearch( proto ) == NULL ) {
	return NULL;
    }

    /* allocate a new structure */
    mi = (struct mount_item *) MallocZero( sizeof( *mi ) );
    mi->lpath = strdup( localpath );
    mi->proto = strdup( proto );

    /* split the remote-path */
    if( (ptr = strchr( host, '/' )) != NULL ) {
	mi->rpath = strdup( ptr );
	*ptr = '\0';
    } else {
	mi->rpath = strdup( "" );
    }

    /* split "user:pass" & "host:port" */
    if( (ptr = strchr( host, '@' )) != NULL ) {
	*ptr++ = '\0';
	if( (pass = strchr( host, ':' )) != NULL ) {
	    *pass++ = '\0';
	    mi->passlen = strlen( pass ) + 1;
	    mi->pass = SecurityDataAccess( 0, pass, mi->passlen );
	}
	mi->user = strdup( host );
	host = ptr;
    }

    /* parse the host & port number */
    if( (ptr = strchr( host, ':' )) != NULL ) {
	*ptr++ = '\0';
	mi->port = (int)strtol(ptr, (char**)0, 0);
    }
    mi->host = strdup( host );

    /* parse the timeout value */
    sprintf( mi->timeout, "/%d", option ? (int)strtol(option, (char**)0, 0) : 86400 );

    /* Insert the mount infomation to the mount list */
    mi->next = ml->mitem;
    ml->mitem = mi;
    MakePath( localpath );
    mkdir( localpath, 0775 );

    return mi;
}

/*
 *name: AgentUnmount
 *	Unmount a mount point and remove from the mount list
 * rtn: -1 if error
 */
int
AgentUnmount( ml, lpath )
struct mount_list *ml;
char	*lpath;
{
    struct mount_item	*mi, *last;
    struct server_info	srv;

    mi = ml->mitem;
    if( strcmp( mi->lpath, lpath ) == 0 ) {
	/* remove the first mount information in mount list */
	ml->mitem = mi->next;
    } else {
	while( 1 ) {
	    last = mi;
	    mi = mi->next;
	    if( mi == NULL ) {
		/* lpath is not exists in mount list */
		return -1;
	    }
	    if( strcmp( mi->lpath, lpath ) == 0 )
		break;
	}
	/* remove the mount information after 'last' */
	last->next = mi->next;
    }

    /* run disconnect before destroy */
    srv.mitem = mi;
    srv.agent = AgentSearch( mi->proto );
    if( srv.agent != NULL ) {
	(* (srv.agent->disconnect) )( &srv );
    }

    /* destroy the mount information */
    free( mi->lpath );
    free( mi->proto );
    free( mi->user );
    free( mi->host );
    free( mi->rpath );
    free( mi );
    return 0;
}

/*
 *name: AgentAutoMount
 *	Create an auto mount point and insert to the mount list
 * rtn: -1 if error
 */
int
AgentAutoMount( ml, lpath )
struct mount_list *ml;
char	*lpath;
{
    char	*ptr, *url, *urlend;

    /* matching "(...):/(...)" */
    ptr = lpath;
    for (;;)
    {
	if (!(ptr = strchr(ptr, ':')))
	    return -1;
	if (ptr[1] == '/')
	{
	    if (ptr[2])
		break;
	    return -1;
	}
	ptr++;
    }
    urlend = strchr( ptr+2, '/' );
    while( *ptr != '/' )
	if( ptr-- <= lpath ) {
	    /* not match with "(...)/proto:/(....)" */
	    return -1;
	}

    /* split localpath and url */
    url = ptr+1;
    if( urlend != NULL ) *urlend = '\0';

    AgentMount( ml, url, lpath, NULL );

    /* restore the lpath */
    if( urlend != NULL ) *urlend = '/';
    return 0;
}

struct mount_item *
ag_find_mount( ml, lpath, rpath )
struct mount_list *ml;
char	*lpath, *rpath;
{
    struct mount_item	*mi;
    char	pathbuf[ STRLEN ];
    char	ch;
    int		len, n;

    len = strlen( lpath );
    ch = lpath[len-1];
    switch( ch ) {
	case '*':
	    if( len>=4 && strncmp(&lpath[len-4], "/*.*", 4) == 0 )
		len -= 4;
	    else if( len >= 2 && lpath[len-2] == '/' )
		len -= 2;
	    break;
	case '?':
	    if( len>=13 && strncmp(&lpath[len-13], "/????????.???", 13) == 0 )
		len -= 13;
	    break;
	case '.':
	    if( len >= 2 && lpath[len-2] == '/' )
		len -= 2;
	    break;
    }
    lpath[ len ] = '\0';

    mi = ml->mitem;
    while( 1 ) {
	if( mi == NULL && AgentAutoMount( ml, lpath ) == 0 ) {
	    mi = ml->mitem;
	}
	if( mi == NULL )
	    break;
	len = strlen( mi->lpath );
	if( strncmp( lpath, mi->lpath, len ) == 0 &&
	    (lpath[len] == '/' || lpath[len] == '\0') ) {
	    if( (n = strlen( mi->rpath )) > 0 ) {
		strcpy( rpath, mi->rpath );
	    }
	    strncpy( rpath + n, lpath + len, STRLEN - n );
	    return mi;
	}
	mi = mi->next;
    }
    sprintf( pathbuf, "%s/._dir", lpath );
    if( (n = open( pathbuf, O_RDWR|O_CREAT, 0644 )) > 0 )
	close( n );
    return NULL;
}

int
ag_make_remote( mi, rpath, buf )
struct mount_item	*mi;
char	*rpath, *buf;
{
    sprintf( buf, "%s://", mi->proto ? mi->proto : "-" );
    buf += strlen( buf );
    if( mi->user ) {
	sprintf( buf, mi->pass ? "%s:*@" : "%s@", mi->user );
	buf += strlen( buf );
    }
    sprintf( buf, mi->port ? "%s:%d" : "%s", mi->host, mi->port );
    buf += strlen( buf );
    if( rpath ) {
	if( *rpath == '~' )  *buf++ = '/';
	sprintf( buf, "%s", rpath );
    }
    return 0;
}

int
ag_showmount( ml, lpath )
struct mount_list *ml;
char	*lpath;
{
    struct mount_item	*item;
    char	rpath[ STRLEN ];
    char	buf[ STRLEN ];
    int		n, num;

    n = sizeof(csusrmsg);
    item = ml->mitem;
    if( lpath == NULL || *lpath == '-' ) {
	num = 0;
	while( item != NULL ) {
	    num++;
	    item = item->next;
	}
	sfsprintf( csusrmsg, n, "0 %d entry(ies)", num );
    } else if( *lpath != '/' ) {
	num = (int)strtol( lpath, (char**)0, 0 );
	while( num > 0 && item != NULL ) {
	    num--;
	    item = item->next;
	}
	if( item == NULL ) {
	    sfsprintf( csusrmsg, n, "1 unknown %s", lpath );
	} else {
	    ag_make_remote( item, item->rpath, buf );
	    sfsprintf( csusrmsg, n, "0 %s %s", buf, item->lpath );
	}
    } else {
	item = ag_find_mount( ml, lpath, rpath );
	if( item == NULL ) {
	    sfsprintf( csusrmsg, n, "1 unknown %s", lpath );
	} else {
	    ag_make_remote( item, rpath, buf );
	    sfsprintf( csusrmsg, n, "0 %s %s", buf, lpath );
	}
    }
    return 0;
}

int
ag_parsepath( srv, uid, argc, argv, proxy )
struct server_info *srv;
int	uid, argc;
char	*argv[];
char	*proxy;
{
    struct mount_list	*ml;
    struct mount_item	*mitem;
    char	*proto;

    IfsAbortFlag = 0;
    if( argc < 2 ) {
	cserrno = E_ARGUMENT;
	return -1;
    }
    ml = MountListSearch( uid );
    if( (mitem = ag_find_mount( ml, argv[1], srv->rpath )) == NULL ) {
	strncpy( csusrmsg, argv[1], sizeof( csusrmsg ) );
	cserrno = E_MOUNT;
	return -1;
    }
    srv->mitem = mitem;
    srv->lpath = argv[1];
    srv->flags = 0;
    srv->proxy = proxy;
    if( mitem->proto[0] == 'p' ) {	/* using proxy to connect socket */
	srv->flags |= IFS_PROXY;
    }
    proto = mitem->proto;
    if( (srv->agent = AgentSearch( mitem->proto )) == NULL ) {
	strncpy( csusrmsg, mitem->proto, sizeof( csusrmsg ) );
	cserrno = E_PROTOCOL;
	return -1;
    }
    if( mitem->host == NULL ) {
	cserrno = E_GETHOST;
	return -1;
    }

#ifdef DEBUG
    if( 1 ) {
	int	flog;
	char	remote[ STRLEN ];
	char	buf[ STRLEN ];

	MountItemDump( mitem );
	ag_make_remote( mitem, srv->rpath, remote );
	sfsprintf( buf, STRLEN, "   %s (fd=%d)\n", remote, srv->mitem->mode );
	logit( buf );
    }
#endif
    return 0;
}

/*
 *name:	cserrmsg
 */
static char *
cserrmsg()
{
    char        *msg;

    switch( cserrno ) {
        case E_NIL:       msg = "nil";                          break;
        case E_COMMAND:   msg = "unimplement command";          break;
        case E_ARGUMENT:  msg = "invalid arguments";            break;
        case E_MOUNT:     msg = "unmounted path";               break;
        case E_PROTOCOL:  msg = "unknown protocol";             break;
        case E_OPENDEST:  msg = "dest-file open error";         break;
        case E_GETHOST:   msg = "unknown hostname";             break;
        case E_SOCKET:    msg = "can't open stream socket";     break;
        case E_CONNECT:   msg = "can't connect to server";      break;
        case E_USERAUTH:  msg = "user authentication error";    break;
        case E_DATAXFER:  msg = "data transfer error";          break;
        default:          msg = "undefined error number";       break;
    }
    return msg;
}

int
csputmsg( buf, bsize, rcode )
char    *buf;
int     bsize;
int     rcode;
{
    int         len;

    if( rcode < 0 ) {
        len = sfsprintf( buf, bsize, "E %d %s %s\n",
                                        cserrno, cserrmsg(), csusrmsg );
    } else {
        len = sfsprintf( buf, bsize, "I %d ok %s\n", E_NIL, csusrmsg );
    }
    logit( buf );
    return len;
}

/*
 *name: IfsCacheValid
 */
int
IfsCacheValid( fpath, expire )
char		*fpath;
unsigned long	expire;
{
	struct stat	st;
	char		buf[ STRLEN ];
	unsigned long	mtime;

	if( stat(fpath, &st) ) {
		return 0;		/* no such file */
	} else if( S_ISDIR( st.st_mode ) ) {
		strcpy( buf, fpath );
		strcat( buf, "/._dir" );
		if( lstat(buf, &st) )
			return 0;	/* no file: fpath/._dir */
		if( expire == CS_NEVER )
			return 1;
	}
	mtime = st.st_mtime;
	return ( mtime > cs.time || (cs.time - mtime) < expire );
}

/*
 * Usage: mount url local-path option
 *	  url format: protocol:/user:passwd@host:port/remote-path
 */
int
IfsMount( ret, retsize, uid, argc, argv )
char	*ret;
int	uid, argc;
char	*argv[];
{
    struct mount_list	*ml;
    struct mount_item	*mi;
    char	remote[ STRLEN ], *option;
    int		n;

    ml = MountListSearch( uid );
    n = sizeof( csusrmsg );
    if( argc < 3 ) {
	ag_showmount( ml, argc < 2 ? NULL : argv[1] );
    } else if( *argv[1] == '-' ) {
	if( AgentUnmount( ml, argv[2] ) ) {
	    sfsprintf( csusrmsg, n, "1 error %s", argv[1] );
	} else {
	    sfsprintf( csusrmsg, n, "0 unmount %s", argv[1] );
	}
    } else {
	option = (argc < 4 ? NULL : argv[3]);
	if( (mi = AgentMount( ml, argv[1], argv[2], option )) == NULL )
	    return csputmsg( ret, retsize, -1 );
	ag_make_remote( mi, mi->rpath, remote );
	sfsprintf( csusrmsg, n, "0 mount %s %s", remote, argv[2] );
    }
    return csputmsg( ret, retsize, 0 );
}

/*
 * Usage: connect protocol server user-info
 */
int
IfsConnect( ret, retsize, uid, argc, argv )
char	*ret;
int     argc;
char    *argv[];
{
    struct server_info	srv;

    if( ag_parsepath( &srv, uid, argc, argv, NiL ) == 0 &&
	(* (srv.agent->connect))( &srv ) == 0 )
	return csputmsg( ret, retsize, 0 );
    return csputmsg( ret, retsize, -1 );
}

/*
 * Usage: disconnect protocol server
 */
int
IfsDisconnect( ret, retsize, uid, argc, argv )
char	*ret;
int     argc;
char    *argv[];
{
    struct server_info	srv;

    if( ag_parsepath( &srv, uid, argc, argv, NiL ) == 0 &&
	(* (srv.agent->disconnect))( &srv ) == 0 )
	return csputmsg( ret, retsize, 0 );
    return csputmsg( ret, retsize, -1 );
}

/*
 * Usage: listdents local-path
 */
int
IfsListDEnts( ret, retsize, uid, argc, argv )
char	*ret;
int     argc;
char    *argv[];
{
    struct server_info	srv;

    if( ag_parsepath( &srv, uid, argc, argv, NiL ) == 0 &&
	(* (srv.agent->listdents))( &srv ) == 0 )
	return csputmsg( ret, retsize, 0 );
    return csputmsg( ret, retsize, -1 );
}

/*
 * Usage: getfile local-path
 */
int
IfsGetFile( ret, retsize, uid, argc, argv )
char	*ret;
int	argc;
char	*argv[];
{
    struct server_info	srv;

    if( ag_parsepath( &srv, uid, argc, argv, NiL ) == 0  &&
	(* (srv.agent->getfile))( &srv ) == 0 )
	return csputmsg( ret, retsize, 0 );
    return csputmsg( ret, retsize, -1 );
}

/*
 * Usage: open logical path [name=value] ...
 *	  stat logical path [name=value] ...
 */
int
IfsReal(ret, retsize, uid, argc, argv)
char*		ret;
int		retsize;
int		uid;
int		argc;
char*		argv[];
{
	register int		n;
	char*			physical;
	char*			proxy;
	unsigned long		expire;
	struct server_info	srv;
	char			buf[PATH_MAX];

	if (argc < 3) {
		cserrno = E_ARGUMENT;
		return sfsprintf(ret, retsize, "%s\n", argc<2?"":argv[1]);
	}
	physical = argv[1];
	expire = 86400;
	proxy = 0;
	for (n = 3; n < argc; n++) {
		if (strneq(argv[n], "expire=", 7))
			expire = strelapsed(argv[n] + 7, NiL, 1);
		else if (strneq(argv[n], "physical=", 9))
			physical = argv[n] + 9;
		else if (strneq(argv[n], "proxy=", 6))
			proxy = argv[n] + 6;
	}
	if( *argv[0] == 's' )	/* stat command */
		expire = CS_NEVER;
	if( argv[2][0] == '.' && argv[2][1] == '\0' )
		sfsprintf(buf, sizeof(buf), "%s", physical);
	else
		sfsprintf(buf, sizeof(buf), "%s/%s", physical, argv[2]);
	argv[1] = buf;
	argv[2] = 0;
	sfsprintf(ret, retsize, "\ncmd> %s %s\n", argv[0], buf );
	logit(ret);
	if ( ag_parsepath(&srv, uid, 2, argv, proxy) ) {
		n = -1;			/* unmounted path */
	} else {
		physical = strdup(srv.lpath);
		if( IfsCacheValid(physical, expire) ||
		    !(*srv.agent->getfile)(&srv) )
			n = 0;		/* file validated */
		else
			n = -1;		/* getfile failed */
		strcpy( buf, physical );
		free( physical );
	}
	csputmsg(ret, retsize, n);
	return sfsprintf(ret, retsize, "%s\n", buf);
}

/*
 * Usage: putfile [source] local-path
 */
int
IfsPutFile( ret, retsize, uid, argc, argv )
char	*ret;
int     argc;
char    *argv[];
{
    struct server_info	srv;
    char	*src, *dst;

    if( argc > 2 ) {
	src = argv[1];
	dst = argv[2];
	CopyFile( src, dst ); 
	argv[1] = dst;
    }
    if( ag_parsepath( &srv, uid, argc, argv, NiL ) == 0 &&
	(* (srv.agent->putfile))( &srv ) == 0 )
	return csputmsg( ret, retsize, 0 );
    return csputmsg( ret, retsize, -1 );
}

/*
 * Usage: userdef protocol arguments
 */
int
IfsUserDef( ret, retsize, uid, argc, argv )
char	*ret;
int     argc;
char    *argv[];
{
    struct server_info	srv;

    if( ag_parsepath( &srv, uid, argc, argv, NiL ) == 0 &&
	(* (srv.agent->userdef))( &srv, argc -1, &argv[1] ) == 0 )
	return csputmsg( ret, retsize, 0 );
    return csputmsg( ret, retsize, -1 );
}

