/*
 * $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;
}