/*
 * $Id: session.c,v 0.12 1992/04/14 17:18:33 ceder Exp $
 * Copyright (C) 1991  Lysator Academic Computer Association.
 *
 * This file is part of the LysKOM server.
 * 
 * LysKOM 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 1, or (at your option) 
 * any later version.
 * 
 * LysKOM 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 LysKOM; see the file COPYING.  If not, write to
 * Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN,
 * or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, 
 * MA 02139, USA.
 *
 * Please mail bug reports to bug-lyskom@lysator.liu.se. 
 */
/*
 * session.c
 *
 * Session control and miscellaneous.
 */

static char *rcsid = "$Id: session.c,v 0.12 1992/04/14 17:18:33 ceder Exp $";


#include <time.h>
#include <stdlib.h>
#include "lyskomd.h"
#include <kom-types.h>
#include <services.h>
#include "manipulate.h"
#include "cache.h"
#include "com.h"
#include "connections.h"
#include "send-async.h"
#include <server/smalloc.h>
#include "log.h"
#include <kom-errno.h>
#include "config.h"
#include "internal-connections.h"


/*
 * Create an oldstyle username, user%host.domain@host.domain.
 * The RESULT must be initialized to a string (such as EMPTY_STRING).
 * A new string will be allocated in RESULT. (The old will be freed).
 */
static void
create_oldstyle_username(String *result,
			 Connection *connection)
{
    if ( s_strcpy(result, connection->username) != OK )
	restart_kom("create_oldstyle_username(): strcpy\n");

    if ( s_strcat(result, s_fcrea_str((const unsigned char *)"@")) != OK )
	restart_kom("create_oldstyle_username: s_strcat\n");

    if ( s_strcat(result, connection->hostname) != OK )
	restart_kom("prot_a_parse_packet: s_strcat II\n");
}

/*
 * This function is called whenever a person leaves a conf,
 * i e when he pepsi():s or logout():s.
 */

extern void
leave_conf(void)
{
    Membership * mship;

    if (active_connection->cwc != 0 )
    {
	if ((mship = locate_membership( active_connection->cwc, ACT_P ))
	    != NULL )
	{
	    time(&mship->last_time_read);
	    mark_person_as_changed (active_connection->pers_no);
	}
	else
	{
	    log("ERROR: leave_conf(): Can't find membership of cwc.\n");
	}
	
	cached_unlock_conf( active_connection->cwc );
	active_connection->cwc = 0;
    }
}

/*
 * Log in as user pers_no. If ACTPERS is a supervisor of pers_no the login
 * will succeed regardless of the passwd. An logout() will automatically
 * be performed if ACTPERS is logged in.
 */
extern Success
login_old (Pers_no       pers_no,
	   const String  passwd)
{
    Person *pers_p;

    GET_P_STAT(pers_p, pers_no, FAILURE);

#if 0
    if ( !logins_allowed  && !pers_p->privileges.wheel)
    {
	kom_errno = KOM_LOGIN_DISALLOWED;
	return FAILURE;
    }
#endif
    
    if ( !is_supervisor(pers_no, NULL, ACTPERS, ACT_P)
	&& chk_passwd(pers_p->pwd, passwd) == FAILURE )
    {
	kom_errno = KOM_PWD;
	return FAILURE;
    }

    logout();	/*+++ How many tries are allowed before disconnection? */

    active_connection->invisible = FALSE;

    ACTPERS = pers_no;
    ACT_P   = pers_p;

    cached_lock_person(pers_no);
        
    pers_p->last_login = time(&active_connection->session_start);
    ++pers_p->sessions;

    s_strcpy(&pers_p->username, active_connection->username);

    /* A Person should have separate fields for ident_user and hostname.
       But for now, we use the syntax username(ident_user)@hostname. */

    if (!s_empty(active_connection->ident_user))
    {
	if ( s_strcat(&pers_p->username,
		      s_fcrea_str((const unsigned char *)"(")) != OK )
	    restart_kom("login: s_strcat (\n");

	if ( s_strcat(&pers_p->username, active_connection->ident_user) != OK )
	    restart_kom("login: s_strcat ident_user\n");

	if ( s_strcat(&pers_p->username,
		      s_fcrea_str((const unsigned char *)")")) != OK )
	    restart_kom("login: s_strcat )\n");
    }

    if ( s_strcat(&pers_p->username,
		  s_fcrea_str((const unsigned char *)"@")) != OK )
	restart_kom("prot_a_parse_packet: s_strcat\n");

    if ( s_strcat(&pers_p->username, active_connection->hostname) != OK )
	restart_kom("prot_a_parse_packet: s_strcat II\n");

    mark_person_as_changed( pers_no );
    
    async_login(ACTPERS, active_connection->session_no);
    
    return OK;
}

/*
 * Log in as user pers_no. If ACTPERS is a supervisor of pers_no the login
 * will succeed regardless of the passwd. An logout() will automatically
 * be performed if ACTPERS is logged in.
 */
extern Success
login (Pers_no       pers_no,
       const String  passwd,
       Bool	     invisible)
{
    Person *pers_p;

    GET_P_STAT(pers_p, pers_no, FAILURE);

#if 0
    if ( !logins_allowed  && !pers_p->privileges.wheel)
    {
	kom_errno = KOM_LOGIN_DISALLOWED;
	return FAILURE;
    }
#endif
    
    if ( !is_supervisor(pers_no, NULL, ACTPERS, ACT_P)
	&& chk_passwd(pers_p->pwd, passwd) == FAILURE )
    {
	kom_errno = KOM_PWD;
	return FAILURE;
    }

    logout();	/*+++ How many tries are allowed before disconnection? */

    active_connection->invisible = !!invisible;		/* Normalize 17. */

    ACTPERS = pers_no;
    ACT_P   = pers_p;

    cached_lock_person(pers_no);
        
    pers_p->last_login = time(&active_connection->session_start);
    ++pers_p->sessions;

    s_strcpy(&pers_p->username, active_connection->username);

    /* A Person should have separate fields for ident_user and hostname.
       But for now, we use the syntax username(ident_user)@hostname. */

    if (!s_empty(active_connection->ident_user))
    {
	if ( s_strcat(&pers_p->username,
		      s_fcrea_str((const unsigned char *)"(")) != OK )
	    restart_kom("login: s_strcat (\n");

	if ( s_strcat(&pers_p->username, active_connection->ident_user) != OK )
	    restart_kom("login: s_strcat ident_user\n");

	if ( s_strcat(&pers_p->username,
		      s_fcrea_str((const unsigned char *)")")) != OK )
	    restart_kom("login: s_strcat )\n");
    }

    if ( s_strcat(&pers_p->username,
		  s_fcrea_str((const unsigned char *)"@")) != OK )
	restart_kom("prot_a_parse_packet: s_strcat\n");

    if ( s_strcat(&pers_p->username, active_connection->hostname) != OK )
	restart_kom("prot_a_parse_packet: s_strcat II\n");

    mark_person_as_changed( pers_no );

    if (!active_connection->invisible)
	async_login(ACTPERS, active_connection->session_no);
    
    return OK;
}


/*
 * Log out. Does not disconnect the client. The person is also automatically
 * logged out if the connection is closed. Takes no action if noone is logged
 * in on this line. Never fails.
 */
extern Success
logout(	void )
{
    if ( ACTPERS != 0 )		/* Is he logged in? Then log him out. */
    {
	if (!active_connection->invisible)
	{
	    async_i_am_off( ACTPERS );
	    async_logout( ACTPERS, active_connection->session_no );
	}
	
	leave_conf();
	ACT_P->total_time_present +=
	    difftime(time(&ACT_P->last_login),
		     active_connection->session_start);

	cached_unlock_person( ACTPERS );
	mark_person_as_changed( ACTPERS );
    }
    
    
    s_clear(&active_connection -> what_am_i_doing);
    
    active_connection->person		= NULL;
    active_connection->cwc		= 0;
    active_connection->pers_no		= 0;
    active_connection->ena_level	= 0;
    
    return OK;
}


/*
 * Change Conference.
 *
 * You are not allowed to change to a conference unless you are a
 * member in the conference.
 *
 * You can pepsi(0) to indicate that you are no longer in a certain
 * conference.
 *
 * There are two reasons to use this call:
 *	1) Other persons want to know what you are doing.
 *	2) When the server checks that you have permission to
 *	   read a certain text it first checks if you current working
 *	   conference is a recipient of the text, since that is a
 *	   cheap test. If that test fails it will have to read in the
 *	   conference structs for the recipients of the text until it
 *	   finds an open one.
 *
 * In the future the server might read in the conference in advance when
 * someone pepsi's to it to allow faster response times.
 */
extern Success
pepsi (Conf_no		conference)		
{
    Who_info	 info = EMPTY_WHO_INFO;
    
    CHK_LOGIN(FAILURE);
    if (  conference != 0 )
    {
	CHK_EXIST(conference, FAILURE);

	if ( locate_membership( conference, ACT_P) == NULL )
	{
	    kom_errno = KOM_NOT_MEMBER;
	    return FAILURE;
	}
    }

    leave_conf();
    
    active_connection->cwc = conference;
    if ( conference != 0 )
	cached_lock_conf( conference );

    if (!active_connection->invisible)
    {
	info.person = ACTPERS;
	info.what_am_i_doing = active_connection->what_am_i_doing;
	info.working_conference = conference;
	info.session_no = active_connection->session_no;

	/* Bug compatibility. */
	create_oldstyle_username(&info.username, active_connection);

	async_i_am_on(info);

	s_clear(&info.username);
    }
    
    return OK;
}


/*
 * Tell the server what you are doing. This string is sent to anyone
 * who does a 'who_is_on()'.
 */
extern Success
change_what_i_am_doing (String  what_am_i_doing)
{
    Who_info info = EMPTY_WHO_INFO; 

    if ( s_strlen( what_am_i_doing ) > WHAT_DO_LEN )
    {
	s_clear ( &what_am_i_doing );
	kom_errno = KOM_LONG_STR;
	return FAILURE;
    }
    
    s_clear ( &active_connection->what_am_i_doing );
    active_connection->what_am_i_doing = what_am_i_doing;

    if (!active_connection->invisible)
    {
	info.person = ACTPERS;
	info.what_am_i_doing = active_connection->what_am_i_doing;
	create_oldstyle_username(&info.username, active_connection);
	info.working_conference = active_connection->cwc;
	info.session_no = active_connection->session_no;
    
	async_i_am_on(info);

	s_clear(&info.username);
    }

    return OK;
}


/*
 * Get info about what all the currently logged in persons are doing.
 */
extern Success
who_is_on( Who_info_list *result )
{
    Connection *cptr;
    int 	    no_of_clients = 0;
    int		    i;
    Session_no      session;
    
    cptr = active_connection;
    
    for ( session = 0; (session = traverse_connections(session)) != 0; )
    {
	cptr = get_conn_by_number(session);

	if ( cptr->person != NULL && cptr->invisible == FALSE )
	    ++no_of_clients;
    }

    result->no_of_persons = no_of_clients;
    result->info = tmp_alloc ( no_of_clients * sizeof(Who_info));

    for ( session = 0, i = 0;
	 i < no_of_clients && (session = traverse_connections(session)) != 0; )
    {
	cptr = get_conn_by_number(session);

	if ( cptr->person != NULL && cptr->invisible == FALSE )
	{
	    result->info[ i ] = EMPTY_WHO_INFO;
	    result->info[ i ].person = cptr->pers_no;
	    result->info[ i ].what_am_i_doing = cptr->what_am_i_doing;

	    /* Backward compatibility: Old clients want the username to be
	       user%host@host. result->info[i].username is free()d in
	       prot_a_output_who_info_list() in prot-a-output.c. */
	    create_oldstyle_username(&result->info[i].username, cptr);

	    result->info[ i ].working_conference = cptr->cwc;
	    result->info[ i ].session_no = cptr->session_no;
	    ++i;
	}
    }

    if ( i != no_of_clients )
	log("who_is_on: i == %d, no_of_clients == %d\n",
	    i, no_of_clients);

    return OK;
}

/*
 * Get info about what all the currently logged in persons are doing.
 */
extern Success
who_is_on_ident( Who_info_ident_list *result )
{
    Connection *cptr;
    int 	    no_of_clients = 0;
    int		    i;
    Session_no      session;
    
    cptr = active_connection;
    
    for ( session = 0; (session = traverse_connections(session)) != 0; )
    {
	cptr = get_conn_by_number(session);

	if ( cptr->person != NULL && cptr->invisible == FALSE )
	    ++no_of_clients;
    }

    result->no_of_persons = no_of_clients;
    result->info = tmp_alloc ( no_of_clients * sizeof(Who_info_ident));

    for ( session = 0, i = 0;
	 i < no_of_clients && (session = traverse_connections(session)) != 0; )
    {
	cptr = get_conn_by_number(session);

	if ( cptr->person != NULL && cptr->invisible == FALSE )
	{
	    result->info[i] = EMPTY_WHO_INFO_IDENT;
	    result->info[i].person = cptr->pers_no;
	    result->info[i].what_am_i_doing = cptr->what_am_i_doing;
	    result->info[i].username = cptr->username;
	    result->info[i].hostname = cptr->hostname;
	    result->info[i].ident_user = cptr->ident_user;
	    result->info[i].working_conference = cptr->cwc;
	    result->info[i].session_no = cptr->session_no;
	    ++i;
	}
    }

    if ( i != no_of_clients )
	log("who_is_on_ident: i == %d, no_of_clients == %d\n",
	    i, no_of_clients);

    return OK;
}


extern  Success
get_session_info  (Session_no session_no,
		   Session_info *result)
{
    Connection *cptr;

    CHK_LOGIN(FAILURE);

    cptr = get_conn_by_number(session_no);

    if ( cptr != NULL )
    {
	*result = EMPTY_SESSION_INFO;
	result->person = cptr->pers_no;
	result->what_am_i_doing = cptr->what_am_i_doing;
	result->working_conference = cptr->cwc;
	result->session = cptr->session_no;
	result->connection_time = cptr->session_start;
	result->idle_time = difftime(time(NULL),
				     cptr->last_request);

	/* Backward compatibility. result->username is free()d in
	   prot_a_reply() prot-a.c. */
	create_oldstyle_username(&result->username, cptr);

	return OK;
    }
    else
    {
	kom_errno = KOM_UNDEF_SESSION;
	return FAILURE;
    }
}

extern  Success
get_session_info_ident  (Session_no session_no,
			 Session_info_ident *result)
{
    Connection *cptr;

    CHK_LOGIN(FAILURE);

    cptr = get_conn_by_number(session_no);

    if ( cptr != NULL )
    {
	*result = EMPTY_SESSION_INFO_IDENT;
	result->person = cptr->pers_no;
	result->what_am_i_doing = cptr->what_am_i_doing;
	result->working_conference = cptr->cwc;
	result->session = cptr->session_no;
	result->connection_time = cptr->session_start;
	result->idle_time = difftime(time(NULL),
				     cptr->last_request);
	result->username = cptr->username;
	result->hostname = cptr->hostname;
	result->ident_user = cptr->ident_user;

	return OK;
    }
    else
    {
	kom_errno = KOM_UNDEF_SESSION;
	return FAILURE;
    }
}

extern  Success
who_am_i (Session_no *session_no)
{
    *session_no = active_connection->session_no;
    return OK;
}



extern  Success
disconnect (Session_no session_no)
{
    Connection *cptr;

    if ( session_no != active_connection->session_no )
	CHK_LOGIN(FAILURE);

    cptr = get_conn_by_number(session_no);

    if ( cptr != NULL )
    {
	if ( is_supervisor(cptr->pers_no, NULL, ACTPERS, ACT_P)
	    || session_no == active_connection->session_no )
	{
	    add_to_kill_list(cptr);
	    return OK;
	}
	else
	{
	    kom_errno = KOM_PERM;
	    return FAILURE;
	}
    }
    else
    {
	kom_errno = KOM_UNDEF_SESSION;
	return FAILURE;
    }
}


/* Get less info */
extern Success
who_is_on_old( Who_info_list_old *result )
{
    Connection *cptr;
    int 	    no_of_clients = 0;
    int		    i;
    Session_no	    session = 0;

    
    while ( (session = traverse_connections(session)) != 0)
    {
	cptr = get_conn_by_number(session);

	if ( cptr->person != NULL && cptr->invisible == FALSE )
	    ++no_of_clients;
    }

    result->no_of_persons = no_of_clients;
    result->info = tmp_alloc ( no_of_clients * sizeof(Who_info));

    for (session = 0, i = 0;
	 i < no_of_clients && (session = traverse_connections(session)) != 0;)
    {
	cptr = get_conn_by_number(session);

	if ( cptr->person != NULL && cptr->invisible == FALSE )
	{
	    result->info[ i ] = EMPTY_WHO_INFO_OLD;
	    result->info[ i ].person = cptr->pers_no;
	    result->info[ i ].what_am_i_doing = cptr->what_am_i_doing;
	    result->info[ i ].working_conference = cptr->cwc;
	    ++i;
	}
    }
    
    if ( i != no_of_clients )
	log("who_is_on_old: i == %d, no_of_clients == %d\n",
	    i, no_of_clients);

    return OK;
}

/*
 * Ask the server what it thinks the time is.
 */
extern Success
get_time( time_t *clock )
{
    time(clock);
    
    return OK;
}



/*
 * Set ena_level. 0 means don't use any privileges. ///
 */
extern Success
enable (u_char ena_level)
{
    CHK_LOGIN(FAILURE);
    active_connection->ena_level = ena_level;
    return OK;
}