/* * $Id: conference.c,v 0.17 1992/04/09 09:17:44 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. */ /* * conference.c * * All atomic calls that deals with conferences. */ static char *rcsid = "$Id: conference.c,v 0.17 1992/04/09 09:17:44 ceder Exp $"; #include <time.h> #include <ctype.h> #include <stdlib.h> #include "lyskomd.h" #include <kom-types.h> #include <services.h> #include "manipulate.h" #include <kom-errno.h> #include <server/smalloc.h> #include "cache.h" #include "log.h" #include <config.h> #include "com.h" #include "connections.h" #include "send-async.h" #include <debug.h> #include <parser.h> #include "internal-connections.h" #include "internal-services.h" /* * Defined in conference.c. This will go away when we use regexp-matching. * This is only temporary. +++*** */ extern Conf_no *conf_table; /* * Static functions */ /* * Delete a conference. Delete all references to this conf. */ static void do_delete_conf (Conf_no conf_no, Conference * conf_c) /* Not NULL */ { int i; if ( do_set_presentation(conf_no, conf_c, 0) != OK ) { log("ERROR: do_delete_conf() - couldn't unmark presentation.\n"); } if ( do_set_etc_motd(conf_no, conf_c, 0) != OK ) { log("ERROR: do_delete_conf() - couldn't unmark motd.\n"); } /* Delete all members */ /* Note that because of the way do_sub_member is written it is important */ /* that the loop is executed this way. */ for ( i = conf_c->members.no_of_members - 1; i >= 0; i-- ) { do_sub_member( conf_no, conf_c, conf_c->members.members + i, conf_c->members.members[ i ].member, NULL, NULL); } sfree( conf_c->members.members ); conf_c->members.members = NULL; /* Delete the name */ s_clear( &conf_c->name ); cached_change_name( conf_no, EMPTY_STRING); /* texts */ /* * The texts are not deleted at once, but since they now have no recipient * they will not live long. */ /* ??? Note that there will still be a recipient in the texts, but it will not exist in reality. This might cause problems. A do_sub_recpt() should maybe be used. */ /* Yes, do! Also, send out asynchronous message about deleted conf. */ sfree( conf_c->texts.texts ); conf_c->texts.texts = NULL; cached_delete_conf( conf_no ); return; } /* * Functions that are exported to the server. */ /* * Return TRUE if NAME is not already used. */ Bool legal_name( String name ) { if (name.len == 0 || name.len > CONF_NAME_LEN ) { return FALSE; } while( name.len-- ) { if ( /* !isascii(*name.string) && */ !isprint( *name.string ) ) { return FALSE; } name.string++; } return TRUE; } /* * Return TRUE if name is unique, or if the only match is conf_no. Use 0 * as conf_no if it should not be allowed to be changed. */ Bool unique_name( const String name, Conf_no conf_no ) { Parse_info parse_info; Parse_token *name_token; Parse_token *existing_token; Bool exact_match_found; Bool diff_found; int i; int n; parse_info = parse(name, match_table, FALSE, TRUE, s_fcrea_str(WHITESPACE), DEFAULT_COLLAT_TAB); if ( parse_info.no_of_matches == 0 ) /* Doesn't match any name. */ { sfree(parse_info.indexes); return TRUE; } if ( parse_info.no_of_matches == -1 ) /* Error. */ { log("unique_name(): parse returned error.\n"); sfree(parse_info.indexes); return FALSE; } if ( parse_info.no_of_matches == 1 && parse_info.indexes[ 0 ] == -1 ) { /* Empty name is not allowed. */ sfree(parse_info.indexes); return FALSE; } /* The name matches some conference. Check if they are equal. */ name_token = tokenize(name, s_fcrea_str(WHITESPACE)); exact_match_found = FALSE; for ( i = 0; !exact_match_found && i < parse_info.no_of_matches; i++ ) { existing_token = match_table[ parse_info.indexes[ i ] ].tokens; diff_found = FALSE; for ( n = 0; (!diff_found && !s_empty(existing_token[ n ].word) && !s_empty(name_token[ n ].word)); ++n) { if ( !s_usr_streq(existing_token[ n ].word, name_token[ n ].word, DEFAULT_COLLAT_TAB) ) { diff_found = TRUE; } } if (! s_empty(existing_token[ n ].word) || ! s_empty(name_token[ n ].word) ) { /* The length (number of words) differed. */ diff_found = TRUE; } if ( !diff_found && conf_table[ parse_info.indexes[ i ] ] != conf_no ) exact_match_found = TRUE; } sfree(parse_info.indexes); free_tokens(name_token); return exact_match_found ? FALSE : TRUE; } /* * Create a conference. */ Conf_no do_create_conf(String name, Pers_no creator, Conf_no supervisor, Conf_no super_conf, Conf_type type) { Conf_no conf_no; Conference * conf_c; /* Allocate memory for conf_c */ conf_no = cached_create_conf( name ); if ( (conf_c = cached_get_conf_stat( conf_no ) ) == NULL) { restart_kom("create_conf() - can't get conf_stat"); } conf_c->creator = creator; conf_c->creation_time = time(NULL); conf_c->presentation= 0; /* No presentation yet */ conf_c->supervisor = supervisor; conf_c->permitted_submitters = 0; conf_c->super_conf = super_conf; conf_c->type = type; conf_c->last_written= conf_c->creation_time; conf_c->msg_of_day = 0; conf_c->nice = DEFAULT_NICE; conf_c->members = EMPTY_MEMBER_LIST; conf_c->texts = EMPTY_TEXT_LIST; mark_conference_as_changed (conf_no); return conf_no; } /* * Return TRUE if viewer is a supervisor to CONF. */ Bool is_supervisor(Conf_no conf, Conference * conf_c, /* May be NULL */ Pers_no viewer, Person * viewer_p) /* May be NULL */ { if (viewer == 0) /* Not yet logged in. */ return FALSE; if (ENA(wheel, 8)) return TRUE; if (viewer == conf) /* A person is ALWAYS supervisor to */ return TRUE; /* his/her own mailbox! */ if ( conf_c == NULL ) /* Get conference if unknown to the caller */ GET_C_STAT( conf_c, conf, FALSE ); if ( !conf_c->supervisor ) return FALSE; if (viewer == conf_c->supervisor) return TRUE; if ( viewer_p == NULL ) GET_P_STAT(viewer_p, viewer, FALSE); if ( locate_membership(conf_c->supervisor, viewer_p) != NULL) return TRUE; return FALSE; } /* * Atomic functions */ /* * Change name of a person or conference. You must be supervisor * of the conference to use this call. */ extern Success change_name (Conf_no conf_no, String new_name) { Conference * conf_c; Access acc; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P); if ( acc <= none ) { kom_errno = KOM_UNDEF_CONF; return FAILURE; } if ( !ACT_P->privileges.change_name || (acc != unlimited && !ENA(admin, 3))) { kom_errno = KOM_PERM; return FAILURE; } if ( !legal_name( new_name ) ) { kom_errno = KOM_BAD_NAME; return FAILURE; } if ( !unique_name( new_name, conf_no ) ) { kom_errno = KOM_CONF_EXISTS; return FAILURE; } async_new_name(conf_no, conf_c->name, new_name); s_strcpy(&conf_c->name, new_name); mark_conference_as_changed(conf_no); cached_change_name(conf_no, new_name); return OK; } /* * Create a conference: Default modes: * ACTPERS becomes supervisor and super_conf. * Anyone can submitt texts. * Noone (not even the creator) is a member in the conf. * * If ANYONE_CAN_CREATE_NEW_CONFS (#defined in config.h) is not true * you must have the 'create_conf' capability. * * It is currently not allowed to have a conference that is secret * and not rd_prot. This restriction might be lifted in the future * (but I don't understand the use of such a conference...) */ extern Conf_no create_conf(String name, Conf_type type) { Conf_no conf_no; CHK_LOGIN(0); if (!ANYONE_CAN_CREATE_NEW_CONFS && !ENA(create_conf, 0) ) { kom_errno = KOM_PERM; return 0; } if ( !legal_name( name ) ) { kom_errno = KOM_BAD_NAME; return 0; } if ( !unique_name( name, 0 ) ) { kom_errno = KOM_CONF_EXISTS; return 0; } if ( type.letter_box ) /* A letter_box can only be created via */ { /* create_person. */ kom_errno = KOM_PERM; return 0; } if ( type.secret && !type.rd_prot ) { kom_errno = KOM_SECRET_PUBLIC; return 0; } conf_no = do_create_conf(name, ACTPERS, ACTPERS, ACTPERS, type); if ( conf_no != 0) { ACT_P->created_confs++; mark_person_as_changed( ACTPERS ); } return conf_no; } /* * Log out a person from any connection he might be logged on to. */ static void logout_person(Pers_no pers_no) { Session_no i = 0; Connection *real_active_connection; real_active_connection = active_connection; while ( (i = traverse_connections(i)) != 0) { active_connection = get_conn_by_number(i); if ( active_connection->pers_no == pers_no ) logout(); } active_connection = real_active_connection; } /* * Delete a conference or person. You must be supervisor of the * conference to be allowed to delete it. */ extern Success delete_conf (Conf_no conf_no ) { Conference * conf_c; Access acc; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P); if ( acc != unlimited ) { kom_errno = (acc <= none) ? KOM_UNDEF_CONF : KOM_PERM ; return FAILURE; } if ( conf_c->type.letter_box ) { /* Make sure the person that is deleted is not logged in. */ logout_person (conf_no); if ( do_delete_pers (conf_no) != OK ) { log("ERROR: delete_conf(): can't delete person.\n"); } } do_delete_conf (conf_no, conf_c); return OK; } /* * Map conference name to number. Can be done without logging in. * Secret conferences will not be returned unless ACTPERS is supervisor * of, or member in, that conference. */ extern Success lookup_name (const String name, Conf_list_old * result) { Conf_no * no, * no_copy; Conf_type * type, *type_copy; int i; if ( cached_lookup_name( name, result ) != OK ) return FAILURE; no = no_copy = result->conf_nos; type = type_copy = result->type_of_conf; for ( i = result->no_of_conf_nos; i > 0; i-- ) { if ( fast_access_perm (*no, ACTPERS, ACT_P) <= none ) --result->no_of_conf_nos; else { *no_copy++ = *no; *type_copy++ = *type; } ++no; ++type; } return OK; } /* * Get status for a conference. */ extern Success get_conf_stat (Conf_no conf_no, Conference * result ) { Conference * conf_c; Access acc; GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P); if ( acc == error ) return FAILURE; if ( acc == none ) { kom_errno = KOM_UNDEF_CONF; return FAILURE; } *result = *conf_c; return OK; } extern Success get_conf_stat_old (Conf_no conf_no, int mask, Conference * result ) { Conference * conf_c; Access acc; GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P); if ( acc == error ) return FAILURE; if ( acc == none ) { kom_errno = KOM_UNDEF_CONF; return FAILURE; } *result = *conf_c; if ( !(mask & 1) ) result->name = EMPTY_STRING; return OK; } /* * Set or delete the presentation of a conference. If text_no == 0 there * will be no presentation. * * The text's mark-count will be increased so that it will not be deleted * when it gets old. * * BUGS: This should probably be a Info_type 'presentation to conf no #' * for at least two reasons: * 1) Given the text, there is no way to know that it * is a presentation unless what the user writes * tells you so. * 2) Presentations should always be readable by everyone. * But to be readable an open conference needs to be * recipient of the text. Since all presentations should * go to the presentation-confs (that is the clients * responsibility!) this is no big problem, but it depends * somewhat on how the text-garber is implemented. */ extern Success set_presentation (Conf_no conf_no, Text_no text_no ) { Conference * conf_c; Access acc; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm(conf_no, conf_c, ACTPERS, ACT_P); if ( acc < unlimited ) { kom_errno = (acc <= none) ? KOM_UNDEF_CONF : KOM_PERM; return FAILURE; } return do_set_presentation(conf_no, conf_c, text_no); } /* * Set a message-of-the-day for a conference. There should normally * be no motd unless something extraordinary happens. If there is * a motd the client should show it as soon as possible. * * Only the supervisor can change the motd. * * BUG: +++ There is no asynchronous message that reports new motds. */ extern Success set_etc_motd( Conf_no conf_no, Text_no text_no ) { Conference * conf_c; Access acc; BUGDECL; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); if ( (acc = access_perm(conf_no, conf_c, ACTPERS, ACT_P)) < unlimited ) { kom_errno = (acc <= none ) ? KOM_UNDEF_CONF : KOM_PERM; BUG(("set_etc_motd failed. Conf %ld Text %ld Acc %ld < %ld (%d).\n", (u_long)conf_no, (u_long)text_no, (u_long)acc, (u_long)unlimited, acc < unlimited)); return FAILURE; } return do_set_etc_motd(conf_no, conf_c, text_no); } /* * Set a new supervisor for a conference. May only be used by * the old supervisor. * * NEW_SUPER is either a person or a conference number. If it is a * conference number it means that all the members in NEW_SUPER become * supervisors to CONF_NO. (NEW_SUPER should normally be rd_prot to * prevent anyone from makeing themselves supervisors). */ extern Success set_supervisor( Conf_no conf_no, Conf_no new_super ) { Conference * conf_c; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); if (new_super != 0) CHK_EXIST(new_super, FAILURE); if ( !is_supervisor(conf_no, conf_c, ACTPERS, ACT_P) && !ENA(admin, 6) ) { kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_PERM; return FAILURE; } conf_c->supervisor = new_super; mark_conference_as_changed( conf_no ); return OK; } /* * Allow certain users to submit texts to CONF_NO. * * If NEW_PERM_SUB == 0 anyone may submit texts. * If it is a person only that person can submit texts to CONF_NO. * If it is a conference only the members in that conference can * submit texts. * * If anyone tries to submit a text to a conference he is not allowed * to submit texts to the text will silently be redirected to the * superconf. If he is not allowed to submit to that conference either * it will be redirected again and so forth, but there is a limit * (MAX_SUPER_CONF_LOOP) on how many redirections will be done. * * It is possible to have a secret conference as super_conf. Users * will then be able to send texts to it, but they will not see * where the text went... */ extern Success set_permitted_submitters (Conf_no conf_no, Conf_no new_perm_sub ) { Conference * conf_c; CHK_LOGIN(FAILURE); if (new_perm_sub != 0) CHK_EXIST(new_perm_sub, FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); if ( !is_supervisor(conf_no, conf_c, ACTPERS, ACT_P) && !ENA(admin, 6) ) { kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_PERM; return FAILURE; } conf_c->permitted_submitters = new_perm_sub; mark_conference_as_changed( conf_no ); return OK; } /* * Set the super_conf of CONF_NO. This call may only be used of a * supervisor of CONF_NO. * * See documentation for set_permitted_submitters(). */ extern Success set_super_conf (Conf_no conf_no, Conf_no new_super_conf ) { Conference * conf_c; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); if (new_super_conf != 0) CHK_EXIST(new_super_conf, FAILURE); if ( !is_supervisor(conf_no, conf_c, ACTPERS, ACT_P) && !ENA(admin, 5) ) { kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_PERM; return FAILURE; } conf_c->super_conf = new_super_conf; mark_conference_as_changed( conf_no ); return OK; } /* * Set the type of a conference. Only the supervisor of a conference can * set the conf_type. The letter_box flag can not be changed. * * BUG: It is allowed to set the type to 'secret' for persons. I don't * think we want it to be that way. */ extern Success set_conf_type (Conf_no conf_no, Conf_type type ) { Conference * conf_c; Access acc; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); if ( type.secret && !type.rd_prot ) { kom_errno = KOM_SECRET_PUBLIC; return 0; } acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P) ; if ( acc < unlimited ) { kom_errno = ( acc <= none ) ? KOM_UNDEF_CONF : KOM_PERM ; return FAILURE; } if ( type.letter_box != conf_c->type.letter_box ) { kom_errno = KOM_LETTER_BOX; return FAILURE; } conf_c->type = type; mark_conference_as_changed (conf_no); return OK; } /* * Set garb_nice for a conference. This controls how long messages * to a conference will live. The argument is probably the number of * days a message will live. Only the supervisor of the conference * may change the garb_nice. */ extern Success set_garb_nice( Conf_no conf_no, Garb_nice nice ) /* number of days */ { Conference * conf_c; Access acc; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P); if ( acc < unlimited ) { kom_errno = (acc <= none) ? KOM_UNDEF_CONF : KOM_PERM; return FAILURE; } conf_c->nice = nice; mark_conference_as_changed( conf_no ); return OK; } /* * Change presentation of a conference. If text_no is 0, there will be * no presentation. */ Success do_set_presentation(Conf_no conf_no, Conference * conf_c, Text_no text_no) { Text_stat * old_pres; Text_stat * new_pres = NULL; /* Initialized to stop gcc complaining * about this being used uninitialized. * This initialization is in fact not needed * since it would not be used uninitialized.*/ /* Check that the new presentation exists before deleting the old*/ if ( text_no != 0 ) { GET_T_STAT(new_pres, text_no, FAILURE); if ( new_pres->no_of_marks >= MAX_MARKS_TEXT ) { log("LIMIT: do_set_presentation(%d, ptr, %d): New presentation" " has %d marks.\n", conf_no, text_no, new_pres->no_of_marks); kom_errno = KOM_MARK_LIMIT; return FAILURE; } } /* Unmark the previous presentation if it exists. */ if ( conf_c->presentation != 0 && (old_pres = cached_get_text_stat(conf_c->presentation)) != NULL) { if ( old_pres->no_of_marks > 0 ) { --old_pres->no_of_marks; mark_text_as_changed( conf_c->presentation ); } else { log("ERROR: do_set_presentation(): Old presentation not marked\n"); } } /* Mark the new presentation */ if ( text_no != 0 ) { ++new_pres->no_of_marks; mark_text_as_changed( text_no ); } conf_c->presentation = text_no; mark_conference_as_changed( conf_no ); return OK; } /* * Change motd of a conference. If text_no is 0, there will be * no motd. */ Success do_set_etc_motd(Conf_no conf_no, Conference * conf_c, Text_no text_no) { Text_stat * old_motd; Text_stat * new_motd = NULL; /* To stop gcc complaining. */ /* Check that the new motd exists before deleting the old*/ if ( text_no != 0 ) { GET_T_STAT(new_motd, text_no, FAILURE); if ( new_motd->no_of_marks >= MAX_MARKS_TEXT ) { log("LIMIT: do_set_motd(%d, ptr, %d): New motd has %d marks.\n", conf_no, text_no, new_motd->no_of_marks); kom_errno = KOM_MARK_LIMIT; return FAILURE; } } /* Unmark the previous motd if it exists. */ if ( conf_c->msg_of_day != 0 && (old_motd = cached_get_text_stat(conf_c->msg_of_day)) != NULL) { if ( old_motd->no_of_marks > 0 ) { --old_motd->no_of_marks; mark_text_as_changed( conf_c->msg_of_day ); } else { log("ERROR: do_set_motd(): Old motd not marked\n"); } } /* Mark the new motd */ if ( text_no != 0 ) { ++new_motd->no_of_marks; mark_text_as_changed( text_no ); } conf_c->msg_of_day = text_no; mark_conference_as_changed( conf_no ); return OK; }