/* * $Id: session.c,v 0.69 2003/08/02 20:44:09 ceder Exp $ * Copyright (C) 1991-1994, 1996-2002 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. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include "timewrap.h" #include #include "oop.h" #include "ldifftime.h" #include "s-string.h" #include "kom-types.h" #include "services.h" #include "com.h" #include "async.h" #include "connections.h" #include "internal-connections.h" #include "kom-errno.h" #include "manipulate.h" #include "lyskomd.h" #include "log.h" #include "cache.h" #include "send-async.h" #include "kom-config.h" #include "server/smalloc.h" #include "param.h" #include "kom-memory.h" #include "string-malloc.h" #include "server-time.h" #include "timeval-util.h" #include "isc-interface.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("@")) != 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 change_conference():s or logout():s. */ extern void leave_conf(Connection *conn) { Membership * mship; if (conn->cwc != 0 ) { if ((mship = locate_membership( conn->cwc, conn->person )) != NULL ) { mship->last_time_read = current_time.tv_sec; mark_person_as_changed (conn->pers_no); } else { kom_log("ERROR: leave_conf(): Can't find membership of cwc.\n"); } cached_unlock_conf( conn->cwc ); conn->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; CHK_CONNECTION(FAILURE); GET_P_STAT(pers_p, pers_no, FAILURE); if ( !is_supervisor(pers_no, ACTPERS, ACT_P) && !ENA(wheel, 8) && /* OK -- In an RPC call */ chk_passwd(pers_p->pwd, passwd) == FAILURE ) { err_stat = pers_no; kom_errno = KOM_PWD; return FAILURE; } logout(); active_connection->flags.invisible = FALSE; ACTPERS = pers_no; ACT_P = pers_p; cached_lock_person(pers_no); pers_p->last_login = current_time.tv_sec; active_connection->session_start = current_time; ++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("(")) != 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(")")) != OK ) restart_kom("login: s_strcat )\n"); } if ( s_strcat(&pers_p->username, s_fcrea_str("@")) != 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 (param.log_login == TRUE) { char *id = s_crea_c_str (pers_p->username); kom_log ("Login %d %s\n", ACTPERS, id); string_free (id); } isc_set_acceptable_idle(active_connection->isc_session, param.active_timeout); 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; Bool same_person; CHK_CONNECTION(FAILURE); GET_P_STAT(pers_p, pers_no, FAILURE); if ( !is_supervisor(pers_no, ACTPERS, ACT_P) && !ENA(wheel, 8) && /* OK -- In an RPC call */ chk_passwd(pers_p->pwd, passwd) == FAILURE ) { err_stat = pers_no; kom_errno = KOM_PWD; return FAILURE; } same_person = (ACTPERS == pers_no) ? TRUE : FALSE; logout(); if (invisible) active_connection->flags.invisible = TRUE; else active_connection->flags.invisible = FALSE; /* Don't count this as a new session if the person already was logged on. The elisp-client version 0.38.1 logs on invisibly automatically to indicate that the user is inactve. */ if (same_person == FALSE) ++pers_p->sessions; ACTPERS = pers_no; ACT_P = pers_p; cached_lock_person(pers_no); pers_p->last_login = current_time.tv_sec; active_connection->session_start = current_time; 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("(")) != 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(")")) != OK ) restart_kom("login: s_strcat )\n"); } if ( s_strcat(&pers_p->username, s_fcrea_str("@")) != 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 (param.log_login == TRUE && same_person == FALSE) { char *id = s_crea_c_str (pers_p->username); kom_log ("Login %d %s\n", ACTPERS, id); string_free (id); } if (!active_connection->flags.invisible) async_login(ACTPERS, active_connection->session_no); isc_set_acceptable_idle(active_connection->isc_session, param.active_timeout); 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 ) { CHK_CONNECTION(FAILURE); if ( ACTPERS != 0 ) /* Is he logged in? Then log him out. */ { if (!active_connection->flags.invisible) { async_logout( ACTPERS, active_connection->session_no ); } leave_conf(active_connection); ACT_P->last_login = current_time.tv_sec; ACT_P->total_time_present += timeval_diff_sec(current_time, 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; isc_set_acceptable_idle(active_connection->isc_session, param.login_timeout); return OK; } /* * Change Conference. * * You are not allowed to change to a conference unless you are a * member in the conference. * * You can change_conference(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 change_conference's to it to allow faster response times. */ extern Success change_conference (Conf_no conference) { Who_info info; CHK_CONNECTION(FAILURE); CHK_LOGIN(FAILURE); if ( conference != 0 ) { CHK_EXIST(conference, FAILURE); if ( locate_membership( conference, ACT_P) == NULL ) { err_stat = conference; kom_errno = KOM_NOT_MEMBER; return FAILURE; } } leave_conf(active_connection); active_connection->cwc = conference; if ( conference != 0 ) cached_lock_conf( conference ); init_who_info(&info); if (!active_connection->flags.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; CHK_CONNECTION(FAILURE); init_who_info(&info); if ( s_strlen( what_am_i_doing ) > param.what_do_len ) { s_clear ( &what_am_i_doing ); err_stat = param.what_do_len; 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->flags.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; CHK_CONNECTION(FAILURE); cptr = active_connection; session = traverse_connections(0); while (session != 0) { cptr = get_conn_by_number(session); if ( cptr->person != NULL && cptr->flags.invisible == FALSE ) ++no_of_clients; session = traverse_connections(session); } result->no_of_persons = no_of_clients; result->info = tmp_alloc ( no_of_clients * sizeof(Who_info)); i = 0; session = traverse_connections(0); while ( i < no_of_clients && session != 0) { cptr = get_conn_by_number(session); if ( cptr->person != NULL && cptr->flags.invisible == FALSE ) { init_who_info(&result->info[ i ]); 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 = filter_conf_no( cptr->cwc, active_connection); result->info[ i ].session_no = cptr->session_no; ++i; } session = traverse_connections(session); } if ( i != no_of_clients ) kom_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; CHK_CONNECTION(FAILURE); cptr = active_connection; session = traverse_connections(0); while (session != 0) { cptr = get_conn_by_number(session); if ( cptr->person != NULL && cptr->flags.invisible == FALSE ) ++no_of_clients; session = traverse_connections(session); } result->no_of_persons = no_of_clients; result->info = tmp_alloc ( no_of_clients * sizeof(Who_info_ident)); i = 0; session = traverse_connections(0); while ( i < no_of_clients && session != 0) { cptr = get_conn_by_number(session); if ( cptr->person != NULL && cptr->flags.invisible == FALSE ) { init_who_info_ident(&result->info[i]); 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 = filter_conf_no( cptr->cwc, active_connection); result->info[i].session_no = cptr->session_no; ++i; } session = traverse_connections(session); } if ( i != no_of_clients ) kom_log("who_is_on_ident: 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_dynamic(int want_visible, int want_invisible, long active_last, Dynamic_session_info_list *result) { Connection *cptr; long no_of_clients = 0; long i; Session_no session; int include_it = 0; CHK_CONNECTION(FAILURE); cptr = active_connection; session = traverse_connections(0); while (session != 0) { cptr = get_conn_by_number(session); if (cptr->person == NULL || cptr->flags.invisible == TRUE) include_it = want_invisible; else include_it = want_visible; if (active_last != 0 && timeval_diff_sec(current_time, cptr->active_time) > active_last && cptr->flags.user_active_used) include_it = 0; if (include_it != 0) ++no_of_clients; session = traverse_connections(session); } result->no_of_sessions = no_of_clients; result->sessions = tmp_alloc(no_of_clients * sizeof(Dynamic_session_info)); i = 0; session = traverse_connections(0); while ( i < no_of_clients && session != 0) { cptr = get_conn_by_number(session); if (cptr->person == NULL || cptr->flags.invisible == TRUE) include_it = want_invisible; else include_it = want_visible; if (active_last != 0 && timeval_diff_sec(current_time, cptr->active_time) > active_last && cptr->flags.user_active_used) include_it = 0; if (include_it != 0) { init_dynamic_session_info(&result->sessions[i]); result->sessions[i].session = cptr->session_no; result->sessions[i].person = cptr->pers_no; result->sessions[i].working_conference = filter_conf_no( cptr->cwc, active_connection); result->sessions[i].idle_time = timeval_diff_sec( current_time, cptr->active_time); result->sessions[i].flags = cptr->flags; /* Special case: sessions that are not logged in are always invisible. */ if (cptr->person == NULL) result->sessions[i].flags.invisible = TRUE; result->sessions[i].what_am_i_doing = cptr->what_am_i_doing; ++i; } session = traverse_connections(session); } if ( i != no_of_clients ) kom_log("who_is_on_dynamic: i == %ld, no_of_clients == %ld\n", i, no_of_clients); return OK; } extern Success get_session_info (Session_no session_no, Session_info *result) { Connection *cptr; CHK_CONNECTION(FAILURE); CHK_LOGIN(FAILURE); cptr = get_conn_by_number(session_no); if ( cptr != NULL ) { init_session_info(result); result->person = cptr->pers_no; result->what_am_i_doing = cptr->what_am_i_doing; result->working_conference = filter_conf_no(cptr->cwc, active_connection); result->session = cptr->session_no; result->connection_time = cptr->session_start.tv_sec; result->idle_time = timeval_diff_sec(current_time, cptr->active_time); /* Backward compatibility. result->username is free()d in prot_a_reply() prot-a.c. */ create_oldstyle_username(&result->username, cptr); return OK; } else { err_stat = session_no; kom_errno = KOM_UNDEF_SESSION; return FAILURE; } } extern Success get_static_session_info (Session_no session_no, Static_session_info *result) { Connection *cptr; CHK_CONNECTION(FAILURE); CHK_LOGIN(FAILURE); cptr = get_conn_by_number(session_no); if ( cptr != NULL ) { init_static_session_info(result); result->username = cptr->username; result->hostname = cptr->hostname; result->ident_user = cptr->ident_user; result->connection_time = cptr->session_start.tv_sec; return OK; } else { err_stat = session_no; kom_errno = KOM_UNDEF_SESSION; return FAILURE; } } extern Success get_session_info_ident (Session_no session_no, Session_info_ident *result) { Connection *cptr; CHK_CONNECTION(FAILURE); CHK_LOGIN(FAILURE); cptr = get_conn_by_number(session_no); if ( cptr != NULL ) { init_session_info_ident(result); result->person = cptr->pers_no; result->what_am_i_doing = cptr->what_am_i_doing; result->working_conference = filter_conf_no(cptr->cwc, active_connection); result->session = cptr->session_no; result->connection_time = cptr->session_start.tv_sec; result->idle_time = timeval_diff_sec(current_time, cptr->active_time); result->username = cptr->username; result->hostname = cptr->hostname; result->ident_user = cptr->ident_user; return OK; } else { err_stat = session_no; kom_errno = KOM_UNDEF_SESSION; return FAILURE; } } extern Success who_am_i (Session_no *session_no) { CHK_CONNECTION(FAILURE); *session_no = active_connection->session_no; return OK; } extern Success disconnect (Session_no session_no) { Connection *cptr; CHK_CONNECTION(FAILURE); if ( session_no != active_connection->session_no && session_no != 0 ) CHK_LOGIN(FAILURE); cptr = get_conn_by_number(session_no); if ( cptr != NULL ) { if ( session_no == active_connection->session_no || session_no == 0 || ENA(wheel, 8) || /* OK -- In an RPC call */ is_supervisor(cptr->pers_no, ACTPERS, ACT_P) == TRUE ) { add_to_kill_list(cptr); return OK; } else { err_stat = session_no; kom_errno = KOM_PERM; return FAILURE; } } else { err_stat = session_no; 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; CHK_CONNECTION(FAILURE); session = traverse_connections(0); while (session != 0) { cptr = get_conn_by_number(session); if ( cptr->person != NULL && cptr->flags.invisible == FALSE ) ++no_of_clients; session = traverse_connections(session); } result->no_of_persons = no_of_clients; result->info = tmp_alloc ( no_of_clients * sizeof(Who_info)); i = 0; session = traverse_connections(0); while (session != 0 && i < no_of_clients) { cptr = get_conn_by_number(session); if ( cptr->person != NULL && cptr->flags.invisible == FALSE ) { init_who_info_old(&result->info[ i ]); result->info[ i ].person = cptr->pers_no; result->info[ i ].what_am_i_doing = cptr->what_am_i_doing; result->info[i].working_conference = filter_conf_no( cptr->cwc, active_connection); ++i; } session = traverse_connections(session); } if ( i != no_of_clients ) kom_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 *clk ) { CHK_CONNECTION(FAILURE); *clk = current_time.tv_sec; return OK; } /* * Set ena_level. 0 means don't use any privileges. */ extern Success enable (u_char ena_level) { CHK_CONNECTION(FAILURE); CHK_LOGIN(FAILURE); active_connection->ena_level = ena_level; return OK; } extern Success set_client_version (const String client_name, const String client_version) { CHK_CONNECTION(FAILURE); if (s_strlen(client_name) > param.client_data_len || s_strlen(client_version) > param.client_data_len) { err_stat = param.client_data_len; kom_errno = KOM_LONG_STR; return FAILURE; } if (s_empty(active_connection->client_name) == FALSE || s_empty(active_connection->client_version) == FALSE) { err_stat = 0; kom_errno = KOM_CLIENT_IS_CRAZY; return FAILURE; } s_strcpy(&active_connection->client_name, client_name); s_strcpy(&active_connection->client_version, client_version); return OK; } extern Success get_client_name (Session_no session_no, String *result) { Connection *cptr; CHK_CONNECTION(FAILURE); CHK_LOGIN(FAILURE); cptr = get_conn_by_number(session_no); if ( cptr != NULL ) { *result = cptr->client_name; return OK; } else { err_stat = session_no; kom_errno = KOM_UNDEF_SESSION; return FAILURE; } } extern Success get_client_version (Session_no session_no, String *result) { Connection *cptr; CHK_CONNECTION(FAILURE); CHK_LOGIN(FAILURE); cptr = get_conn_by_number(session_no); if ( cptr != NULL ) { *result = cptr->client_version; return OK; } else { err_stat = session_no; kom_errno = KOM_UNDEF_SESSION; return FAILURE; } } extern Success accept_async(Number_list *num_list) { int i; Success result; int found; CHK_CONNECTION(FAILURE); result = OK; /* * Check against maliciously long arrays. */ if (num_list->length > param.accept_async_len) { err_stat = 0; kom_errno = KOM_LONG_ARRAY; return FAILURE; } /* * Clear the accept list */ for (i = 0; i < ay_dummy_last; i++) { active_connection->want_async[i] = FALSE; } /* * Enter the new accept list -- non-silently ignore requests for * messages that this version of the server doesn't understand. */ for (i = 0; i < num_list->length; i++) { found = 0; if (num_list->data[i] >= 0 && num_list->data[i] < ay_dummy_last) { switch ((enum async)num_list->data[i]) { case ay_new_text_old: case ay_new_name: case ay_i_am_on: case ay_sync_db: case ay_leave_conf: case ay_login: case ay_rejected_connection: case ay_send_message: case ay_logout: case ay_deleted_text: case ay_new_text: case ay_new_recipient: case ay_sub_recipient: case ay_new_membership: case ay_new_user_area: #ifdef DEBUG_CALLS case ay_garb_ended: #endif found = 1; active_connection->want_async[num_list->data[i]] = TRUE; break; case ay_dummy_last: break; /* Trick: since we don't use a default label here gcc will warn if new values are added to ``enum async'' but not to this switch. */ } } if (!found && result == OK) /* Remember the first offender. */ { err_stat = num_list->data[i]; kom_errno = KOM_UNKNOWN_ASYNC; result = FAILURE; } } return result; } extern Success query_async(Number_list *result) { /* (This static buffer is mentioned in async.h). */ static long temp[ay_dummy_last]; int i; CHK_CONNECTION(FAILURE); result->length = 0; for (i = 0; i < ay_dummy_last; i++) { if (active_connection->want_async[i] != FALSE) { temp[result->length] = i; result->length += 1; } } result->data = temp; return OK; } extern Success user_active(void) { CHK_CONNECTION(FAILURE); active_connection->active_time = current_time; active_connection->flags.user_active_used = TRUE; return OK; }