Skip to content
Snippets Groups Projects
Select Git revision
  • a96ce9ba992f5602e54d0ce60fed821697ab775d
  • master default protected
  • 9.0
  • marcus/wix3
  • 8.0
  • nt-tools
  • 7.8
  • 7.6
  • 7.4
  • 7.2
  • 7.0
  • 0.6
  • rosuav/latex-markdown-renderer
  • rxnpatch/rxnpatch
  • marcus/gobject-introspection
  • rxnpatch/8.0
  • rosuav/pre-listening-ports
  • rosuav/async-annotations
  • rosuav/pgsql-ssl
  • rxnpatch/rxnpatch-broken/2023-10-06T094250
  • grubba/fdlib
  • v8.0.2020
  • v8.0.2018
  • v8.0.2016
  • v8.0.2014
  • v8.0.2012
  • v8.0.2008
  • v8.0.2006
  • v8.0.2004
  • v8.0.2002
  • v8.0.2000
  • v8.0.1998
  • v8.0.1996
  • v8.0.1994
  • v8.0.1992
  • v8.0.1990
  • v8.0.1988
  • v8.0.1986
  • rxnpatch/clusters/8.0/2025-04-29T124414
  • rxnpatch/2025-04-29T124414
  • v8.0.1984
41 results

opcodes.c

Blame
  • text.c 50.13 KiB
    /*
     * $Id: text.c,v 0.27 1994/02/20 16:20:20 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. 
     */
    /*
     * text.c
     *
     * All atomic calls that deals with texts.
     */
    
    static char *rcsid = "$Id: text.c,v 0.27 1994/02/20 16:20:20 ceder Exp $";
    #include "rcs.h"
    USE(rcsid);
    
    #include <errno.h>
    #include <stdio.h>
    #include <time.h>
    #include <setjmp.h>
    #include <sys/types.h>
    #ifdef HAVE_STDARG_H
    #  include <stdarg.h>
    #endif
    #include <sys/types.h>
    
    #include "server/smalloc.h"
    #include "s-string.h"
    #include "misc-types.h"
    #include "kom-types.h"
    #include "services.h"
    #include "manipulate.h"
    #include "lyskomd.h"
    #include "kom-errno.h"
    #include "config.h"
    #include "com.h"
    #include "connections.h"
    #include "internal-connections.h"
    #include "cache.h"
    #include "log.h"
    #include "minmax.h"
    #include "admin.h"
    #include "send-async.h"
    #include "param.h"
    #include "kom-memory.h"
    
    /*
     * Static functions
     */
    
    
    /*
     * Add text_no to the list of texts in a conference. Return the local number
     * the text gets in this conference.
     */
    
    static Local_text_no
    add_text_in_conf(Conference * conf_c,
    		 Text_no      text_no)
    {
        Text_list * t;
    
        time( & conf_c->last_written );
        
        /* Add number last on the text_list */
        t = &(conf_c->texts);
        t->texts = srealloc(t->texts, ++(t->no_of_texts) * sizeof(Text_no));
        t->texts[ t->no_of_texts - 1 ] = text_no;
    
        return t->first_local_no + t->no_of_texts - 1;
    }
    
    
    /*
     * Set the global text_no of a certain local text_no if that local text_no
     * exists in the conf. No action is taken if the local text_no doesn't exist.
     * This function is probably only used to set the number to zero when a text
     * is deleted.
     */
    
    static void
    set_loc_no (Conference    * conf_c,
    	    Local_text_no   loc_no,
    	    Text_no	    text_no)
    {
        if ( loc_no < conf_c->texts.first_local_no
    	|| loc_no > conf_c->texts.first_local_no + conf_c->texts.no_of_texts )
        {
    	return;			/* Doesn't exist in conf.stat. */
        }
    
        conf_c->texts.texts[ loc_no - conf_c->texts.first_local_no ] = text_no;
    
        return;
    }
    /*
     * Count how many recipients and cc_recipients a text has.
     */
    
    static int
    count_recipients( Text_stat  *t_stat )
    {
        int 	 n = 0;
        Misc_info  * misc;
    
        for ( misc = t_stat->misc_items;
    	 misc < t_stat->misc_items + t_stat->no_of_misc; misc++ )
        {
    	if ( misc->type == recpt || misc->type == cc_recpt )
    	{
    	    n++;
    	}
        }
    
        return n;
    }
    
    /*
     * Count how many footnotes a text has.
     */
    
    static int
    count_footn( Text_stat  *t_stat )
    {
        int 	 n = 0;
        Misc_info  * misc;
    
        for ( misc = t_stat->misc_items;
    	 misc < t_stat->misc_items + t_stat->no_of_misc; misc++ )
        {
    	if ( misc->type == footn_in )
    	{
    	    n++;
    	}
        }
    
        return n;
    }
    
    /*
     * Count how many commments a text has.
     */
    
    static int
    count_comment( Text_stat  *t_stat )
    {
        int 	 n = 0;
        Misc_info  * misc;
    
        for ( misc = t_stat->misc_items;
    	 misc < t_stat->misc_items + t_stat->no_of_misc; misc++ )
        {
    	if ( misc->type == comm_in )
    	{
    	    n++;
    	}
        }
    
        return n;
    }
    
    /*
     * Check if ACTPERS is allowed to read this text.
     * Returns TRUE if he is allowed to read it.
     */
    
    
    /*
     * Check if CONF_NO is a recipient of the text whose text_stat is given.
     */
    
    static Bool
    is_recipient(Conf_no	 conf_no,
    	     Text_stat * t_stat)
    {
        int i;
        
        for ( i = 0; i < t_stat->no_of_misc; i++ )
        {
    	switch( t_stat->misc_items[ i ].type )
    	{
    	case recpt:
    	    if ( t_stat->misc_items[ i ].datum.recipient == conf_no )
    	    {
    		return TRUE;
    	    }
    	    break;
    	    
    	case cc_recpt:
    	    if ( t_stat->misc_items[ i ].datum.cc_recipient == conf_no )
    	    {
    		return TRUE;
    	    }
    	    break;
    	    
    	case rec_time:
    	case comm_to:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	case sent_by:
    	case sent_at:
    	case loc_no:
    	    break;
    	    
    #ifndef COMPILE_CHECKS
    	default:
    	    restart_kom("is_recipient(): illegal misc_item\n");
    #endif
    	}
        }
    
        return FALSE;
    }
    
    
    /*
     * Check if comment is a comment to parent.
     */
    static Bool
    is_comment_to(Text_no    comment,
    	      Text_stat *parent)
    {
        int i;
    
        for ( i = 0; i < parent->no_of_misc; i++ )
        {
    	switch( parent->misc_items[ i ].type )
    	{
    	case comm_in:
    	    if ( parent->misc_items[ i ].datum.commented_in == comment )
    		return TRUE;
    	    break;
    	default:
    	    break;
    	}
        }
    
        return FALSE;
    }
    
    /*
     * Check if footnote is a footnote to parent.
     */
    static Bool
    is_footnote_to(Text_no    footnote,
    	       Text_stat *parent)
    {
        int i;
    
        for ( i = 0; i < parent->no_of_misc; i++ )
        {
    	switch( parent->misc_items[ i ].type )
    	{
    	case footn_in:
    	    if ( parent->misc_items[ i ].datum.footnoted_in == footnote )
    		return TRUE;
    	    break;
    	default:
    	    break;
    	}
        }
    
        return FALSE;
    }
    /*
     * Return the conference which the text goes to. This is normally conf_no, but
     * it may be a super_conf. If ACTPERS is not allowed to submit a text to
     * conf_no or a super_conf, return 0.
     */
    static Conf_no
    submit_to(Conf_no	conf_no, /* The conference the user is trying to */
    	  			 /* submit a text to. */
    	  Conference  * conf_c)	 /* May be NULL */
    {
        int i;
        Access acc;
        
        CHK_LOGIN(0);
        
        if ( conf_c == NULL )
    	GET_C_STAT(conf_c, conf_no, 0);
    
        for ( i=0; i < param.max_super_conf_loop; i++)
        {
    	acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P);
    	
    	if ( acc <= none )
    	    return 0;
    	
    	if (conf_c->permitted_submitters == 0
    	    || acc == unlimited
    	    || locate_membership( conf_c->permitted_submitters, ACT_P) != NULL)
    	{
    	    return conf_no;
    	}
    
    
    	if ((conf_no = conf_c->super_conf) == 0)
    	    return 0;
    	
    	GET_C_STAT(conf_c, conf_no, 0);
        }
    
        return 0;
    }
    
    
    /*
     * Say that FOOTNOTE is a footnote to TEXT.
     */
    
    static Success
    do_add_footnote(Text_no footnote,
    		Text_no text)
    {
        Text_stat *foot_s, *text_s;
    
        GET_T_STAT(foot_s, footnote, FAILURE);
        GET_T_STAT(text_s, text, FAILURE);
    
        ADD_MISC(foot_s, footn_to, footnote_to, text);
        ADD_MISC(text_s, footn_in, footnoted_in, footnote);
    
        mark_text_as_changed( footnote );
        mark_text_as_changed( text );
        
        return OK;
    }
    
    
    /*
     * Say that COMMENT is a comment to TEXT.
     */
    
    static Success
    do_add_comment(Text_no comment,
    	       Text_no text)
    {
        Text_stat *comm_s, *text_s;
        
        GET_T_STAT(comm_s, comment, FAILURE);
        GET_T_STAT(text_s, text, FAILURE);
    
        ADD_MISC(comm_s, comm_to, comment_to, text);
        ADD_MISC(text_s, comm_in, commented_in, comment);
    
        mark_text_as_changed( comment );
        mark_text_as_changed( text );
        
        return OK;
    }
    
        
    /*
     * Say that RECEIVER is a recipient of TEXT.
     */
    static Success
    do_add_recpt(Text_no 	 text,
    	     Text_stat * text_s, /* May be NULL */
    	     Conf_no	 receiver)
    {
        Conference *rece_c;
    
        if ( text_s == NULL )
        {
    	GET_T_STAT(text_s, text, FAILURE);
        }
        
        GET_C_STAT(rece_c, receiver, FAILURE);
    
        ADD_MISC(text_s, recpt, recipient, receiver);
        ADD_MISC(text_s, loc_no, local_no, add_text_in_conf(rece_c, text));
    
        mark_text_as_changed(text);
        mark_conference_as_changed(receiver);
        
        return OK;
    }
       
    
    static Success
    do_add_cc_recpt(Text_no new_text,
    		Text_stat  *text_s, /* May be NULL */
    		Conf_no receiver)
    {
        Conference *rece_c;
    
        if ( text_s == NULL )
    	GET_T_STAT(text_s, new_text, FAILURE);
    
        GET_C_STAT(rece_c, receiver, FAILURE);
    
        ADD_MISC(text_s, cc_recpt, cc_recipient, receiver);
        ADD_MISC(text_s, loc_no, local_no, add_text_in_conf(rece_c, new_text));
    
        mark_text_as_changed( new_text );
        mark_conference_as_changed( receiver );
        
        return OK;
    }
        
    
    /*
     * Return number of lines in a text
     */
    
    static u_short
    count_lines( String str )
    {
        u_short l = 0;
    
        while ( str.len-- > 0 )
        {
    	if( *str.string++ == '\n' )
    	{
    	    l++;
    	}
        }
    
        return l;
    }
    
    
    /*
     * Delete misc_info at location loc.
     * If it is a recpt, cc_recpt, comm_to or footn_to delete any
     * loc_no, rec_time, sent_by or sent_at that might follow it.
     *
     * Note that the Misc_info is not reallocated.
     */
    
    static void
    do_delete_misc (u_short	  * no_of_misc,
    		Misc_info * misc,
    		int	    loc)
    {
        int del = 1;		/* Number of items to delete. */
        				/* Always delete at least one item. */
        Bool ready;
        
        /* Check range of loc */
    
        if ( loc < 0 || loc >= *no_of_misc )
        {
    	restart_kom("do_delete_misc() - loc out of range");
        }
    
        ready = FALSE;
        
        while ( ready == FALSE && loc + del < *no_of_misc )
        {
    	switch ( misc[ loc + del ].type )
    	{
    	case loc_no:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    del++;
    	    break;
    
    	case recpt:
    	case cc_recpt:
    	case footn_to:
    	case footn_in:
    	case comm_to:
    	case comm_in:
    	    ready = TRUE;
    	    break;
    	    
    #ifndef COMPILE_CHECKS
    	default:
    	    restart_kom("do_delete_misc() - illegal misc");
    #endif
    	}
        }
    
        *no_of_misc -= del;
    
        /* Move items beyond the deleted ones. */
    
        while ( loc < *no_of_misc )
        {
    	misc[ loc ] = misc[ loc + del ];
    	loc++;
        }
    }
    
    /*
     * Delete leading zeroes from the text_list.
     * Returns TRUE if some zeroes was deleted.
     */
    static Bool
    adjust_text_list(Text_list *text_list)
    {
        u_long zeroes;
        u_long i;
    
        for (zeroes = 0;
    	 zeroes < text_list->no_of_texts && text_list->texts[ zeroes ] == 0;
    	 zeroes++)
    	;
    
        if ( zeroes > 0 )
        {
    	text_list->no_of_texts -= zeroes;
    	text_list->first_local_no += zeroes;
    
    	for ( i = 0; i < text_list->no_of_texts; i++)
    	    text_list->texts[ i ] = text_list->texts[ i + zeroes ];
    
    	text_list->texts = srealloc(text_list->texts,
    				    (text_list->no_of_texts
    				     * sizeof(Text_no)));
        }
    
        return zeroes > 0;
    }
    
        
    /*
     * Delete a recipient from a text. Does not fail if the recipient doesn't
     * exist - that is not an error.
     */
    static Success
    do_sub_recpt (Text_no	     text_no,
    	      Text_stat    * text_s,  /* May be NULL */
    	      Conf_no	     conf_no,
    	      Conference   * conf_s ) /* May be NULL */
    {
        int i;
    
        if ( text_s == NULL )
    	GET_T_STAT(text_s, text_no, FAILURE);
        
        if ( conf_s == NULL )
        {
    	conf_s = cached_get_conf_stat( conf_no ); /* Might still be NULL. */
        }
        	
        for ( i = 0; i < text_s->no_of_misc; i++ )
        {
    	switch ( text_s->misc_items[ i ].type)
    	{
    	case recpt:
    	    if ( text_s->misc_items[ i ].datum.recipient == conf_no )
    	    {
    		if ( conf_s != NULL )
    		{
    		    /* Only if the conference exists: */
    		    set_loc_no( conf_s,
    			       text_s->misc_items[ i+1 ].datum.local_no, 0 );
    		    adjust_text_list (&conf_s->texts);
    		    mark_conference_as_changed (conf_no);
    		}
    		
    		do_delete_misc ( &text_s->no_of_misc, text_s->misc_items, i );
    		mark_text_as_changed (text_no);
    
    		return OK;
    	    }
    	    break;
    
    	case cc_recpt:
    	    if ( text_s->misc_items[ i ].datum.cc_recipient == conf_no )
    	    {
    		if ( conf_s != NULL )
    		{
    		    set_loc_no( conf_s,
    			       text_s->misc_items[ i+1 ].datum.local_no, 0 );
    		    mark_conference_as_changed( conf_no );
    		}
    		
    		do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i );
    		mark_text_as_changed( text_no );
    		
    		return OK;
    	    }
    	    break;
    
    	case comm_to:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	case loc_no:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    break;
    	    
    #ifndef COMPILE_CHECKS
    	default:
    	    log("%s: do_sub_recpt(): bad misc_item.\n", __FILE__);
    	    break;
    #endif
    	}
        }
    
        kom_errno = KOM_NOT_RECIPIENT;
        return FAILURE;
    }
    
    /*
     * Delete the link between comment and comment_to.
     */
    static void
    do_sub_comment (Text_no	       comment,    /* The comment. */
    		Text_stat    * text_s,
    		Text_no	       comment_to, /* The commented. */
    		Text_stat    * parent_s )
    {
        int i;
        Bool ready;
    
        if ( text_s == NULL )
    	VOID_GET_T_STAT(text_s, comment);
    
        if ( parent_s == NULL )
    	VOID_GET_T_STAT(parent_s, comment_to);
        
        for ( ready = FALSE, i = 0; !ready && i < text_s->no_of_misc; i++ )
        {
    	switch ( text_s->misc_items[ i ].type)
    	{
    	case comm_to:
    	    if ( text_s->misc_items[ i ].datum.comment_to == comment_to )
    	    {
    		do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i );
    		mark_text_as_changed( comment );
    		ready = TRUE;
    	    }
    	    break;
    
    	default:
    	    break;
    	}
        }
    
        if ( !ready )
    	restart_kom("do_sub_comment(): part 1 failed.\n");
    
    
        for ( ready = FALSE, i = 0; !ready && i < parent_s->no_of_misc; i++ )
        {
    	switch ( parent_s->misc_items[ i ].type)
    	{
    	case comm_in:
    	    if ( parent_s->misc_items[ i ].datum.commented_in == comment )
    	    {
    		do_delete_misc (&parent_s->no_of_misc,
    				parent_s->misc_items, i );
    		mark_text_as_changed( comment_to );
    		ready = TRUE;
    	    }
    	    break;
    
    	default:
    	    break;
    	}
        }
    
        if ( !ready )
    	restart_kom("do_sub_comment(): part 2 failed.\n");
        
        return;
    }
    
    
    /*
     * Delete the link between footnote and footnote_to.
     */
    static void
    do_sub_footnote (Text_no       footnote,
    		Text_stat    * text_s,
    		Text_no	       footnote_to,
    		Text_stat    * parent_s )
    {
        int i;
        Bool ready;
    
        if ( text_s == NULL )
    	VOID_GET_T_STAT(text_s, footnote);
    
        if ( parent_s == NULL )
    	VOID_GET_T_STAT(parent_s, footnote_to);
    
        for ( ready = FALSE, i = 0; !ready && i < text_s->no_of_misc; i++ )
        {
    	switch ( text_s->misc_items[ i ].type)
    	{
    	case footn_to:
    	    if ( text_s->misc_items[ i ].datum.footnote_to == footnote_to )
    	    {
    		do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i );
    		mark_text_as_changed( footnote );
    		ready = TRUE;
    	    }
    	    break;
    
    	default:
    	    break;
    	}
        }
    
        if ( !ready )
    	restart_kom("do_sub_footnote(): part 1 failed.\n");
    
    
        for ( ready = FALSE, i = 0; !ready && i < parent_s->no_of_misc; i++ )
        {
    	switch ( parent_s->misc_items[ i ].type)
    	{
    	case footn_in:
    	    if ( parent_s->misc_items[ i ].datum.footnoted_in == footnote )
    	    {
    		do_delete_misc (&parent_s->no_of_misc,
    				parent_s->misc_items, i );
    		mark_text_as_changed( footnote_to );
    		ready = TRUE;
    	    }
    	    break;
    
    	default:
    	    break;
    	}
        }
    
        if ( !ready )
    	restart_kom("do_sub_footnote(): part 2 failed.\n");
        
        return;
    }
    
    
    /*
     * Who is sender of misc_item I? Returns 0 if there is no sent_by misc_item.
     */
    static Pers_no
    sender(Text_stat * t_stat,
           int	   i)
    {
        for (i++ ; i < t_stat->no_of_misc; i++)
        {
    	switch ( t_stat->misc_items[ i ].type )
    	{
    	case sent_by:
    	    return t_stat->misc_items[ i ].datum.sender;
    
    	case recpt:
    	case cc_recpt:
    	case comm_to:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	case sent_at:
    	    return 0;		/* No sender. */
    
    	case loc_no:
    	case rec_time:
    	    break;		/* These may come before a sent_by. */
    
    #ifndef COMPILE_CHECKS
    	default:
    	    log("ERROR: sender(): Illegal misc_item found.\n");
    	    return 0;
    #endif
    	}
        }
        return 0;			/* No sender. */
    }
    
    
    /*
     * Check if ACTPERS has sent this text to conference CONF_NO
     */
    static Bool
    is_sender(Text_stat * text_s,
    	  Conf_no     conf_no)
    {
        int i;
    
        if ( !ACTPERS )
        {
    	return FALSE;
        }
        
        for ( i = 0; i < text_s->no_of_misc; i++ )
        {
    	switch( text_s->misc_items[ i ].type )
    	{
    	case recpt:
    	    if ( text_s->misc_items[ i ].datum.recipient == conf_no
    		&& sender ( text_s, i ) == ACTPERS )
    	    {
    		return TRUE;
    	    }
    	    break;
    
    	case cc_recpt:
    	    if ( text_s->misc_items[ i ].datum.cc_recipient == conf_no
    		&& sender ( text_s, i ) == ACTPERS )
    	    {
    		return TRUE;
    	    }
    	    break;
    
    	case loc_no:
    	case rec_time:
    	case comm_to:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	case sent_by:
    	case sent_at:
    	    break;
    
    #ifndef COMPILE_CHECKS
    	default:
    	    restart_kom("is_sender(): Illegal misc_item found.\n");
    #endif
    	}
        }
    
        return FALSE;
    }
    
    
    /*
     * Check if ACTPERS has sent this text as a comment to parent.
     */
    static Bool
    is_comm_sender(Text_stat * text_s,
    	       Text_no     parent)
    {
        int i;
    
        if ( !ACTPERS )
        {
    	return FALSE;
        }
        
        for ( i = 0; i < text_s->no_of_misc; i++ )
        {
    	switch( text_s->misc_items[ i ].type )
    	{
    	case comm_to:
    	    if ( text_s->misc_items[ i ].datum.comment_to == parent
    		&& sender ( text_s, i ) == ACTPERS )
    	    {
    		return TRUE;
    	    }
    	    break;
    
    
    	case recpt:
    	case cc_recpt:
    	case loc_no:
    	case rec_time:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	case sent_by:
    	case sent_at:
    	    break;
    
    #ifndef COMPILE_CHECKS
    	default:
    	    restart_kom("is_comm_sender(): Illegal misc_item found.\n");
    #endif
    	}
        }
    
        return FALSE;
    }
    
        
    /*
     * Check if ACTPERS is allowed to add a footnote to a text. Sets errno if
     * there is an error.
     */
    
    static Success
    check_footn(Text_stat * t_stat)
    {
        if ( t_stat->author != ACTPERS )
        {
    	kom_errno = KOM_NOT_AUTHOR;
    	return FAILURE;
        }
    
        if ( count_footn( t_stat ) >= param.max_foot )
        {
    	kom_errno = KOM_FOOT_LIMIT;
    	return FAILURE;
        }
    
        return OK;
    }
    
    /*
     * Check if ACTPERS is allowed to add a comment to a text. Sets errno if
     * there is an error. Note that it is allowed to comment a text even if
     * you are not allowed to read it.
     */
    
    static Success
    check_comm(Text_stat * t_stat)
    {
        if ( count_comment( t_stat ) >= param.max_comm )
        {
    	kom_errno = KOM_COMM_LIMIT;
    	return FAILURE;
        }
    
        return OK;
    }
    
    
    /*
     * Return a pointer to a Mark if pers_no has marked the text text_no.
     * Otherwise, return NULL.
     */
    static Mark *
    locate_mark(Pers_no pers_no,
    	    Person *pers_p,	/* May be NULL. */
    	    Text_no text_no)
    {
        Mark *mp;
        Mark *result = NULL;
        int i;
        Mark_list mlist;
    
        if ( pers_p == NULL )
    	GET_P_STAT(pers_p, pers_no, NULL);
    
        mlist = pers_p->marks;
        
        for ( i = mlist.no_of_marks, mp = mlist.marks;
    	 i > 0 && result == NULL;
    	 i--, mp++ )
        {
    	if ( mp->text_no == text_no )
    	    result = mp;
        }
    
        return result;
    }
    
    /*
     * Skip one misc-item with all additional data.
     * 
     * This is only used from get_text_stat.
     */
    static void
    skip_recp (Misc_info ** misc,
    	   Text_stat  * text_stat )
    {
        Bool ready = FALSE;
        
        ++(*misc);
        while ( !ready && *misc < text_stat->misc_items + text_stat->no_of_misc )
        {
    	switch ( (*misc)->type )
    	{
    	case loc_no:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    ++(*misc);
    	    break;
    
    	case recpt:
    	case cc_recpt:
    	case comm_to:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	    ready = TRUE;
    	    break;
    #ifndef COMPILE_CHECKS
    	default:
    	    restart_kom("skip_recp() - illegal misc\n");
    #endif
    	}
        }
    }
    
    /*
     * End of static functions.
     */
    
    /*
     * Functions that are exported to the rest of the server.
     */
    
    /*
     * Check if ACTPERS is allowed to read this text.
     * Returns TRUE if he is allowed to read it.
     */
    Bool
    text_read_access(Text_no     text_no,
    		 Text_stat * text_stat)
    {
        int i;
        Misc_info *misc;
        Conference *recipient;
    
        /*
         * Nope, people who aren't logged in, should NOT be
         * allowed to read any texts but motd_of_lyskom!
         */
        if ( text_no == kom_info.motd_of_lyskom )
    	return TRUE;
    
        CHK_LOGIN(FALSE);
        
        if ( text_stat == NULL )
    	GET_T_STAT(text_stat, text_no, FALSE);
        
        if ( ENA(wheel, 10))
    	return TRUE;
    
        if ( ACTPERS )
        {
    	if ( text_stat->author == ACTPERS )
    	    return TRUE;
    	
    	/* Check if ACTPERS or current working conference is a recipient */
    	
    	for (i = text_stat->no_of_misc, misc = text_stat->misc_items;
    	     i; i--, misc++)
    	{
     	    if ( misc->type == recpt
    		&& (misc->datum.recipient == active_connection->cwc
    		    || misc->datum.recipient == ACTPERS ))
    	    {
    		return TRUE;
    	    }
    
     	    if ( misc->type == cc_recpt
    		&& (misc->datum.cc_recipient == active_connection->cwc
    		    || misc->datum.cc_recipient == ACTPERS ))
    	    {
    		return TRUE;
    	    }
    	}
    
    	/* Check if ACTPERS is member in any of the recipients */
    	
    	for (i = text_stat->no_of_misc, misc = text_stat->misc_items;
    	     i; i--, misc++)
    	{
     	    if ( misc->type == recpt
    		&& locate_membership( misc->datum.recipient, ACT_P) != NULL)
    	    {
    		return TRUE;
    	    }
    
     	    if ( misc->type == cc_recpt
    		&& locate_membership( misc->datum.cc_recipient, ACT_P) != NULL)
    	    {
    		return TRUE;
    	    }
    	}
    
    	if ( locate_mark(ACTPERS, ACT_P, text_no) != NULL )
    	{
    	    return TRUE;
    	}
        }
    
        /* Check if any of the recipients is an open conference, */
        /* or if ACTPERS is a supervisor. (Note: ACTPERS is not */
        /* supervisor of anything if he isn't logged in.)       */
    
        for (i = text_stat->no_of_misc, misc = text_stat->misc_items;
    	 i; i--, misc++)
        {
     	if ( misc->type == recpt
    	    && (recipient =
    		cached_get_conf_stat( misc->datum.recipient )) != NULL )
    	{
    	    if ( !recipient->type.rd_prot
    		|| is_supervisor(misc->datum.recipient, recipient,
    				 ACTPERS, ACT_P) == TRUE)
    	    {
    		return TRUE;
    	    }
    	}
    
     	if ( misc->type == cc_recpt
    	    && (recipient =
    		cached_get_conf_stat( misc->datum.cc_recipient )) != NULL )
    	{
    	    if ( !recipient->type.rd_prot
    		|| is_supervisor(misc->datum.cc_recipient, recipient,
    				 ACTPERS, ACT_P) == TRUE)
    	    {
    		return TRUE;
    	    }
    	}
        }
    
        return FALSE;
    }
    
    
    /*
     * Delete a text from the database. Deletes all "links" to/from other
     * texts/persons/conferences. (e. g. comm_to/comm_in)
     */
    Success
    do_delete_text(Text_no    text_no,
    	       Text_stat *text_s)
    {
        Person *author;
        Bool found = FALSE;
        u_long i;
        
        if ( text_s == NULL )
    	GET_T_STAT(text_s, text_no, FAILURE);
    
        if ((author = cached_get_person_stat (text_s->author)) != NULL)
        {
    	/* Delete this text from created_texts. */
    
    	for ( i = 0; i < author->created_texts.no_of_texts; i++ )
    	    if ( author->created_texts.texts[ i ] == text_no )
    	    {
    		author->created_texts.texts[ i ] = 0;
    		adjust_text_list (&author->created_texts);
    		mark_person_as_changed (text_s->author);
    		found = TRUE;
    		break;
    	    }
    
    	if ( !found )
    	    log("ERROR: %s: do_delete_text(): %s", 
    		__FILE__, "text not found in authors created_texts.");
        }
    
        while ( text_s->no_of_misc > 0 )
        {
    	switch ( text_s->misc_items[ 0 ].type )
    	{
    	case recpt:
    	    if ( do_sub_recpt(text_no, text_s,
    			      text_s->misc_items[ 0 ].datum.recipient, NULL)
    		!= OK )
    		restart_kom("do_delete_text(): error pos 1.\n");
    	    break;
    	    
    	case cc_recpt:
    	    if ( do_sub_recpt(text_no, text_s,
    			      text_s->misc_items[ 0 ].datum.cc_recipient, NULL)
    		!= OK )
    		restart_kom("do_delete_text(): error pos 2.\n");
    	    break;
    	    
    	case comm_to:
    	    do_sub_comment(text_no, text_s,
    			   text_s->misc_items[ 0 ].datum.comment_to, NULL);
    	    break;
    	    
    	case comm_in:
    	    do_sub_comment(text_s->misc_items[ 0 ].datum.commented_in, NULL,
    			   text_no, text_s);
    	    break;
    	    
    	case footn_to:
    	    do_sub_footnote(text_no, text_s,
    			    text_s->misc_items[ 0 ].datum.footnote_to, NULL);
    	    break;
    	    
    	case footn_in:
    	    do_sub_footnote(text_s->misc_items[ 0 ].datum.footnoted_in, NULL,
    			    text_no, text_s);
    	    break;
    	    
    
    	case loc_no:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    restart_kom("do_delete_text(): Illegal misc-item syntax.\n");
    #ifndef COMPILE_CHECKS
    	default:
    	    restart_kom("do_delete_text(): Illegal misc-item.\n");
    #endif
    	}
        }
    
        cached_delete_text(text_no);
        return OK;
    }
    
    /*
     * Atomic calls.
     */
    
    /*
     * Calls to handle marks.
     *
     * Marks are secret. No else can know what you have marked.
     */
    
    /*
     * Get text_nos of all marked texts.
     */
    extern Success
    get_marks( Mark_list *result )
    {
        CHK_LOGIN(FAILURE);
        *result = ACT_P->marks;
        return OK;
    }
    
    
    
    
    /*
     *  Get the text. The text will not be marked as read until you
     *  explicitly mark_as_read() it. start_char = 0 && end_char = END_OF_STRING
     *  gives the entire text.
     */
    extern Success
    get_text (Text_no       text_no,
    	  String_size   start_char,
    	  String_size   end_char,
    	  String      * result)
    {
        Text_stat  * text_s;
    
        GET_T_STAT(text_s, text_no, FAILURE);
        
        /* Check if ACTPERS has read acess to the text */
        
        if ( text_read_access( text_no, text_s ) != TRUE )
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	err_stat = text_no;
    	
        	return FAILURE;
        }
    
        *result = cached_get_text( text_no );
    
        if ( start_char > result->len )
        {
    	kom_errno = KOM_INDEX_OUT_OF_RANGE;
    	return FAILURE;
        }
    
        /* Should use something in s-string.c to do this. +++ */
        result->string += start_char;
        result->len = min ( result->len - 1, end_char ) + 1 - start_char;
    
        if ( ACTPERS )
        {
    	++ACT_P->no_of_text_fetches;
    	mark_person_as_changed( ACTPERS );
        }
    	
        return OK;
    }
    
    
    
    /*
     * Check if person is a member in any of the recipients or cc_recipients
     * of text_s
     */
    static  Bool
    is_member_in_recpt(Person    *person,
    		   Text_stat *text_s)
    {
        int i;
    
        for ( i = 0; i < text_s->no_of_misc; i++ )
        {
    	switch(text_s->misc_items[ i ].type)
    	{
    	case recpt:
    	    if ( locate_membership(text_s->misc_items[ i ].datum.recipient,
    				   person) != NULL )
    	    {
    		return TRUE;
    	    }
    	    break;
    
    	case cc_recpt:
    	    if ( locate_membership(text_s->misc_items[ i ].datum.cc_recipient,
    				   person) != NULL )
    	    {
    		return TRUE;
    	    }
    	    break;
    
    	case comm_to:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	case loc_no:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    break;
    
    #ifndef COMPILE_CHECKS
    	default:
    	    log("%s: is_member_in_recpt(): bad misc_item.\n", __FILE__);
    	    break;
    #endif
    	}
        }
    
        return FALSE;
    }
    
    
    /*
     * Copy the text_stat original into result, but only those part
     * that viewer is allowed to see. (Censor away information about
     * conferences that viewer is not allowed to know about).
     *
     * All memory is allocated via tmp_alloc().
     */
    static void
    filter_secret_info(Text_stat *result,
    		   Text_stat *original,
    		   Pers_no    viewer,
    		   Person    *viewer_p)	/* May be NULL. */
    {
        Misc_info	* orig;
        Misc_info	* copy;		/* Censored Misc_infos */
    
        /* ^^^ Possible optimisation:
           No need to copy unless there is a secret conf among the recipients. */
    
        *result = *original;
    
        result->misc_items = tmp_alloc(result->no_of_misc * sizeof ( Misc_info));
        result->no_of_misc = 0;
        copy = result->misc_items;
        orig = original->misc_items;    
        
        while ( orig < original->misc_items + original->no_of_misc )
        {
    	switch( orig->type )
    	{
    	case recpt:
    	    if ( (fast_access_perm (orig->datum.recipient, viewer, viewer_p)
    		  <= none )
    		&& !ENA(admin, 4))
    	    {
    		skip_recp ( &orig, original );
    	    }
    	    else
    	    {
    		*copy++ = *orig++;
    		++result->no_of_misc;
    	    }
    	    break;
    
    	case cc_recpt:
    	    if ( (fast_access_perm (orig->datum.cc_recipient, viewer, viewer_p)
    		  <= none)
    		&& !ENA(admin, 4))
    	    {
    		skip_recp ( &orig, original );
    	    }
    	    else
    	    {
    		*copy++ = *orig++;
    		++result->no_of_misc;
    	    }
    	    break;
    
    	case loc_no:
    	case rec_time:
    	case comm_to:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	case sent_by:
    	case sent_at:
    	    *copy++ = *orig++;
    	    ++result->no_of_misc;
    	    break;
    
    #ifndef COMPILE_CHECKS
    	default:
    	    restart_kom("filter_secret_info() - illegal misc_item!\n");    
    #endif
    	}
        }
    }
    
    /*
     * Get text status.
     *
     * If there are recipients of the text that are secret confs
     * those misc-items will be censored.
     */
    extern Success
    get_text_stat (Text_no	  text_no,
    	       Text_stat *result )
    {
        Text_stat	* text_stat;
    
        GET_T_STAT(text_stat, text_no, FAILURE);
        
        if ( !text_read_access(text_no, text_stat) && !ENA(admin, 2))
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
    
        filter_secret_info(result, text_stat, ACTPERS, ACT_P);
        return OK;
    }
    
    /*
     * Functions local to create_text:
     */
    
    /*
     * Check that the recipient or cc_recipient at LOC is not already a
     * recipient or cc_recipient of this text.
     */
    static Success
    check_double_subm (Misc_info	* misc,
    		   int		  loc,
    		   Conf_no	  addressee)
    {
        int j;
        
        for ( j = 0; j < loc; j++)
        {
    	switch ( misc[ j ].type )
    	{
    	case recpt:
    	    if ( misc[ j ].datum.recipient == addressee )
    		return FAILURE;
    
    	    break;
    
    	case cc_recpt:
    	    if ( misc[ j ].datum.cc_recipient == addressee )
    		return FAILURE;
    
    	    break;
    
    	case comm_to:
    	case comm_in:
    	case footn_to:
    	case footn_in:
    	case loc_no:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    break;
    	    
    #ifndef COMPILE_CHECKS
    	default:
    	    log(__FILE__, ": check_double_subm(): bad misc_item.\n");
    	    break;
    #endif
    	}
        }
        return OK;
    }
    
    /*
     * Check that none of the first 'pos' misc_items pointed to by misc
     * is a comment or footnote to text forbidden.
     */
    static Success
    check_double_comm (Misc_info *misc,
    		   int        pos,
    		   Text_no    forbidden)
    {
        for(;pos > 0; pos--, misc++)
        {
    	switch(misc->type)
    	{
    	case comm_to:
    	    if ( misc->datum.comment_to == forbidden )
    		return FAILURE;
    	    break;
    
    	case footn_to:
    	    if ( misc->datum.footnote_to == forbidden )
    		return FAILURE;
    	    break;
    
    	case recpt:
    	case cc_recpt:
    	case comm_in:
    	case footn_in:
    	case loc_no:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    break;
    	    
    #ifndef COMPILE_CHECKS
    	default:
    	    log("%s: check_double_subm(): bad misc_item.\n", __FILE__);
    	    break;
    #endif
    	}
        }
    
        return OK;
    }
    
    	
        
    /*
     * Check that all misc_items are legal. Return OK / FAILURE.
     * Update recpt & cc_recpt fields if the text goes to a super_conf.
     * Signal an error if a conference is recipient more than once, or if
     * a text is a comment to the same text more than once.
     */
    static Success
    create_text_check_misc (u_short     * no_of_misc,
    			Misc_info   * misc )
    {
        int		 i;
        Text_stat  * parent;
        Conf_no	 addressee;
    
        for ( i = 0; i < *no_of_misc; i++)
        {
    	err_stat = i;	/* In case of an error, err_stat indicates which
    			   misc_item caused the error. */
    
    	switch( misc[ i ].type )
    	{
    	case footn_to:
    	    /* Check that ACTPERS is the author to the text. */
    
    	    GET_T_STAT( parent, misc[ i ].datum.footnote_to, FAILURE);
    
    	    if ( check_footn(parent) == FAILURE )
    		return FAILURE;
    
    	    if ( check_double_comm(misc, i, misc[ i ].datum.footnote_to)
    		!= OK )
    	    {
    		kom_errno = KOM_ILL_MISC;
    		return FAILURE;
    	    }
    	    
    	    break;
    
    	case comm_to:
    	    /* Check that the text exists. */
    
    	    GET_T_STAT( parent, misc[ i ].datum.comment_to, FAILURE);
    
    	    if ( check_comm(parent) == FAILURE )
    		return FAILURE;
    	    
    	    if ( check_double_comm(misc, i, misc[ i ].datum.comment_to)
    		!= OK )
    	    {
    		kom_errno = KOM_ILL_MISC;
    		return FAILURE;
    	    }
    	    
    	    break;
    
    	case recpt:
    	    /* Check that ACTPERS has write access to the conference or a
    	       superconference. */
    	    /* Superconfs are recursive */
     
     	    addressee = submit_to( misc[ i ].datum.recipient, NULL);
    
    	    /* Update in case of super_conf */
    	    
    	    if ((misc[ i ].datum.recipient = addressee) == 0)
    	    {
    		kom_errno = KOM_ACCESS;
    		return FAILURE;
    	    }
    
    	    /* Check that this recipient is not already a recipient. */
    
    	    if ( check_double_subm(misc, i, addressee) != OK )
    	    {
    		kom_errno = KOM_ILL_MISC;
    		return FAILURE;
    	    }
    	    
    	    break;
    	    
    	case cc_recpt:
    	    /* Check that ACTPERS has write access to the conference or a
    	       superconference. */
    	    /* Superconfs are recursive */
    
     	    addressee = submit_to( misc[ i ].datum.cc_recipient, NULL);
    
    	    /* Update in case of super_conf */
    	    
    	    if ((misc[ i ].datum.cc_recipient = addressee) == 0)
    	    {
    		kom_errno = KOM_ACCESS;
    		return FAILURE;
    	    }
    
    	    /* Check that this recipient is not already a recipient. */
    
    	    if (check_double_subm(misc, i, addressee) != OK)
    	    {
    		kom_errno = KOM_ILL_MISC;
    		return FAILURE;
    	    }
    	    
    	    break;
    
    	case loc_no:		/* Ignore loc_no */
    	    break;
    
    	case comm_in:
    	case footn_in:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    /* Fall through */
    #ifndef COMPILE_CHECKS
     	default:
    #endif
    	    kom_errno = KOM_ILL_MISC;
    	    return FAILURE;
    	}
        }
    
        return OK;
    }
    
    /*
     * Fix all double references. Eg if misc contains comm_to, add a comm_in field.
     * No access-permission checking is done.
     */
    static Success
    create_text_add_miscs(Text_no     new_text,
    		      int         no_of_misc,
    		      Misc_info * misc)
    {
        int		 i;
    
        for ( i = 0; i < no_of_misc; i++)
        {
    	err_stat = i;	/* In case of an error, err_stat indicates which
    			   misc_item caused the error. */
    
    	switch( misc[ i ].type )
    	{
    	case footn_to:
    	    if ( do_add_footnote(new_text, misc[ i ].datum.footnote_to) != OK)
    		return FAILURE;
    
    	    break;
    
    	case comm_to:
    	    if ( do_add_comment(new_text, misc[ i ].datum.comment_to) != OK )
    		return FAILURE;
    
    	    break;
    
    	case recpt:
    	    if ( do_add_recpt(new_text, NULL, misc[ i ].datum.recipient) != OK)
    		return FAILURE;
    
    	    break;
    
    	case cc_recpt:
    	    if ( do_add_cc_recpt(new_text, NULL,
    				 misc[ i ].datum.cc_recipient) !=OK )
    		return FAILURE;
    
    	    break;
    
    	case loc_no:		/* Ignore loc_no. */
    	    break;
    
    	case comm_in:
    	case footn_in:
    	case rec_time:
    	case sent_by:
    	case sent_at:
    	    /* Fall through */
    #ifndef COMPILE_CHECKS
    	default:
    #endif
    	    restart_kom("create_text_add_misc() - illegal Info_type");
    	}
        }
        return OK;
    }
    
    /*
     * Send an asynchronous message, but filter any secret information.
     */
    
    static void
    send_async_new_text (Text_no text_no,
    		     Text_stat *text_s)
    {
        Connection *cptr;
        Session_no i = 0;
        Text_stat filtered;
    
        init_text_stat(&filtered);
    
        while ( (i = traverse_connections(i)) != 0 )
        {
    	cptr = get_conn_by_number(i);
    
    	/*
    	 * filter_secret_info copies the text-stat to filtered, but
    	 * since it is allocated with tmp_alloc we do not need to free
    	 * the memory now.
    	 */
    	filter_secret_info(&filtered, text_s, 
    			   cptr->pers_no, cptr->person);
    
    	if ( cptr->person != NULL
    	    && is_member_in_recpt (cptr->person, &filtered) == TRUE)
    	{
    	    async_new_text (cptr, text_no, &filtered);
    	}
        }
    }
    
    /* 
     * Special case handling of special text numbers.
     *
     * In the KOMmunity, there has arisen an odd phenomenon known as
     * "jubel" to the local citizens of LysKOM.  There are several kinds
     * of "jubel"; a text number divisible by 1000 is one famous such.
     *
     * There is a feeling that jubels, at least the more prominent jubels,
     * should not be created by automatic postings.  This code is
     * primarily aimed at stopping such things, but it can have other, eh,
     * interesting uses as well...
     */
    struct jubel {
        struct jubel *next;
        Pers_no bad_guy;
        Text_no divisor;		/* 0 is used to represent infinity */
        Text_no remainder;
    };
    static struct jubel *jubel_root = NULL;
    
    /*
     * register_jubel(char*)  -  tell lyskomd about a jubel, and a
     * person which is not allowed to create that jubel.  The argument is
     * a string consisting of two or three numbers:
     *       persno textno
     *       persno divisor remainder
     * meaning that PERSNO is not allowed to create text TEXTNO, or any
     * text number which fulfills the relation textno%DIVISOR==REMAINDER.
     */
    void
    register_jubel(Pers_no pno, 
    	       Text_no divis,	/* 0 if no division should be made. */
    	       Text_no tno)
    {
        struct jubel *j = smalloc(sizeof(struct jubel));
        j->next = jubel_root;
        j->bad_guy = pno;
        j->divisor = divis;
        j->remainder = tno;
        jubel_root = j;
    }
    
    /* Free the jubel list */
    void
    free_all_jubel(void)
    {
        struct jubel *a;
        struct jubel *b;
        
        a = jubel_root;
        while (a != NULL)
        {
    	b = a;
    	a = a->next;
    	sfree(b);
        }
        jubel_root = NULL;
    }
        
    /* Check if it is ok for ACTPERS to create the next text. */
    static Bool
    ok_to_create_next_text(void)
    {
        struct jubel *j;
        Text_no next_tno;
        Bool is_jubel = FALSE;
        
        next_tno = query_next_text_num();
        for (j = jubel_root; j != NULL; j = j->next)
        {
    	if ((j->divisor == 0 && next_tno == j->remainder)
    	    || (j->divisor != 0 && next_tno%j->divisor == j->remainder))
    	{
    	    if (ACTPERS == j->bad_guy)
    	    {
    		log("Stopped person %d from creating jubel %lu",
    		    ACTPERS, next_tno);
    		return FALSE;
    	    }
    	    else
    		is_jubel = TRUE;
    	}
        }
    
        if (is_jubel)
    	log("Granted jubel %lu to person %d.\n", next_tno, ACTPERS);
    
        return TRUE;
    }
    
    
    /*
     * Create a text.
     *
     * The recipients may change. See doc for set_permitted_submitters.
     *
     * The only allowed Misc_items are recpt, cc_recpt, comm_to and footn_to.
     * loc_no are allowed, but ignored.
     *
     * Returns text_no of the created text, or 0 if there was an error.
     */
    extern Text_no
    create_text(const String  message,
    	    u_short	  no_of_misc,
    	    Misc_info   * misc )
    {
        Text_no text;
        Text_stat * t_stat;
        extern int errno;
    
        CHK_LOGIN(0);
    
        /* Check the length of the text. */
    
        if ( s_strlen (message) > param.text_len )
        {
    	kom_errno = KOM_LONG_STR;
    	return 0;
        }
    
        /* Check that the author is allowed to write this text number */
    
        if (ok_to_create_next_text() == FALSE)
        {
    	kom_errno = KOM_TEMPFAIL;
    	return 0;
        }
    
        /* Check all misc-items */
        
        if ( create_text_check_misc(&no_of_misc, misc) != OK
    	|| (text = cached_create_text( message )) == 0)
        {
    	return 0;
        }
    
        if ( (t_stat = cached_get_text_stat( text )) == NULL )
        {
    	restart_kom("%s.\nText == %d, kom_errno == %d, errno == %d\n",
    		    "create_text: can't get text-stat of newly created text",
    		    text, kom_errno, errno);
        }
    
        t_stat->author = ACTPERS;
        t_stat->creation_time = time(NULL);
        t_stat->no_of_lines = count_lines( message );
        t_stat->no_of_chars = s_strlen( message );
        
        if ( create_text_add_miscs(text, no_of_misc, misc) != OK )
        {
    	log("ERROR: create_text(): can't add miscs.\n");
    	return 0;
        }
        
        ACT_P->created_texts.texts
    	= srealloc(ACT_P->created_texts.texts,
    		   ++(ACT_P->created_texts.no_of_texts) * sizeof(Text_no));
        ACT_P->created_texts.texts[ ACT_P->created_texts.no_of_texts - 1 ] = text;
        ACT_P->created_lines += t_stat->no_of_lines;
        ACT_P->created_bytes += t_stat->no_of_chars;
    
        mark_person_as_changed (ACTPERS);
        mark_text_as_changed (text);
    
        send_async_new_text (text, t_stat);     /* Send asynchronous message. */
    
        return text;
    }
    
    /*
     * Create an anonymous text.
     *
     * This is just like create_text, but the author of the text is set to
     * Pers_no 0, to guarantee that the author is anonymous.
     *
     * (This should really be called create_ftp_text (in analogy with
     * change-conference which was truncated to CC, which was transmuted
     * into pepsi) but public demand said NO!)
     *
     * Returns text_no of the created text, or 0 if there was an error.
     */
    extern Text_no
    create_anonymous_text(const String message,
    		      u_short	  no_of_misc,
    		      Misc_info   * misc )
    {
        Text_no text;
        Text_stat * t_stat;
        extern int errno;
    
        CHK_LOGIN(0);
    
        /* Check the length of the text. */
    
        if ( s_strlen (message) > param.text_len )
        {
    	kom_errno = KOM_LONG_STR;
    	return 0;
        }
    
        /* Check that the author is allowed to write this text number */
    
        if (ok_to_create_next_text() == FALSE)
        {
    	kom_errno = KOM_TEMPFAIL;
    	return 0;
        }
    
        /* Check all misc-items */
        
        if ( create_text_check_misc(&no_of_misc, misc) != OK
    	|| (text = cached_create_text( message )) == 0)
        {
    	return 0;
        }
    
        if ( (t_stat = cached_get_text_stat( text )) == NULL )
        {
    	restart_kom("%screated text.\nText = %d, kom_errno = %d, errno = %d\n",
    		    "create_anonymous_text: can't get text-stat of newly ",
    		    text, kom_errno, errno);
        }
    
        t_stat->author = 0;
        t_stat->creation_time = time(NULL);
        t_stat->no_of_lines = count_lines( message );
        t_stat->no_of_chars = s_strlen( message );
        
        if ( create_text_add_miscs(text, no_of_misc, misc) != OK )
        {
    	log("ERROR: create_text(): can't add miscs.\n");
    	return 0;
        }
    
        mark_text_as_changed (text);
        /* Don't add this person to create - we want true anonymity! */
    
        send_async_new_text (text, t_stat);
    
        return text;
    }
    
    /*
     * Delete a text.
     *
     * Only a supervisor of the author may delete a text.
     */
    extern Success
    delete_text( Text_no text_no )
    {
        Text_stat *text_s;
        
        CHK_LOGIN(FAILURE);
        GET_T_STAT(text_s, text_no, FAILURE);
    
        if ( !is_supervisor(text_s->author, NULL, ACTPERS, ACT_P)
    	&& !ENA(admin, 5) )
        {
    	kom_errno = (text_read_access(text_no, text_s)
    		     ? KOM_NOT_AUTHOR : KOM_NO_SUCH_TEXT);
    	return FAILURE;
        }
    
        return do_delete_text(text_no, text_s);
    }
    
    /*
     * Lookup a text according to creation-time.
     * The text-no of the text created closest before TIME is returned.
     * The text text-no might not be readable.
     */
    extern Success
    get_last_text(struct tm *wanted_time,
    	      Text_no *result)
    {
        struct tm *texttime;
        Text_no lower  = 0;
        Text_no higher = query_next_text_num() - 1;
        Text_stat *text_stat = NULL;
        Text_no try;
        Text_no middle;
    
        /*
         * We search for the text in the interval [lower, higher]
         * (inclusive). Middle is set to the middle (rounded towards
         * infinity), and we search from there and upward until we find a
         * text.
         */
    
        while (lower < higher)
        {
    	middle = (lower + higher)/2 + 1; /* Binary search */
    	try = middle;
    	text_stat = NULL;
    
            while (text_stat == NULL && try <= higher)
    	    text_stat = cached_get_text_stat (try++);
    
    	if ( text_stat == NULL )
    	    higher = middle - 1;
    	else
    	{
    	    texttime = localtime(&text_stat->creation_time);
    	    if (texttime->tm_year > wanted_time->tm_year
    		|| texttime->tm_mon > wanted_time->tm_mon
    		|| texttime->tm_mday > wanted_time->tm_mday
    		|| texttime->tm_hour > wanted_time->tm_hour
    		|| texttime->tm_min > wanted_time->tm_min
    		|| texttime->tm_sec > wanted_time->tm_sec)
    	    {
    		lower = try - 1;
    	    }
    	    else		/* The intervall is "halved" */
    		higher = middle - 1;
    	}
        }
    
        *result = lower;
    
        return OK;
    }
    
    
    /*
     * Return next existing text-no.
     */
    
    extern  Success
    find_next_text_no (Text_no start,
    		   Text_no *result)
    {
        Text_no    highest = query_next_text_num();
        Text_stat *text_s;
    
        while (++start < highest)
        {
    	text_s = cached_get_text_stat(start);
    	if (text_s != NULL && text_read_access(start, text_s) == TRUE)
    	{
    	    *result = start;
    	    return OK;
    	}
        }
    
        kom_errno = KOM_NO_SUCH_TEXT;
        return FAILURE;
    }
    
    
    /*
     * Return next/previous existing text-no.
     */
    
    extern  Success
    find_previous_text_no (Text_no start,
    		       Text_no *result)
    {
        Text_stat *text_s;
    
        while (start-- > 0)
        {
    	text_s = cached_get_text_stat(start);
    	if (text_s != NULL && text_read_access(start, text_s) == TRUE)
    	{
    	    *result = start;
    	    return OK;
    	}
        }
    
        kom_errno = KOM_NO_SUCH_TEXT;
        return FAILURE;
    }
    
    /*
     * Add a recipient to a text.
     */
    extern Success
    add_recipient(	Text_no		text_no,
    		Conf_no		conf_no,
    		Info_type	type )	/* recpt or cc_recpt */
    {
        Text_stat  * t_stat;
        Conference * conf_c;
        
        CHK_LOGIN(FAILURE);
        GET_T_STAT(t_stat, text_no, FAILURE);
        GET_C_STAT(conf_c, conf_no, FAILURE);
    
        if ( !text_read_access(text_no, t_stat ) && !ENA(admin, 4))
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
        
        if ( is_recipient(conf_no, t_stat) )
        {
    	kom_errno = KOM_ALREADY_RECIPIENT;
    	return FAILURE;
        }
    
        if ( count_recipients( t_stat ) >= param.max_recipients )
        {
    	kom_errno = KOM_RECIPIENT_LIMIT;
    	return FAILURE;
        }
    
        switch ( type )
        {
        case recpt:
    	if ( do_add_recpt(text_no, t_stat, conf_no) != OK)
    	    return FAILURE;
    	break;
    
        case cc_recpt:
    	if ( do_add_cc_recpt(text_no, t_stat, conf_no) != OK)
    	    return FAILURE;
    	break;
    
        case comm_to:
        case comm_in:
        case footn_to:
        case footn_in:
        case loc_no:
        case rec_time:
        case sent_by:
        case sent_at:
    	/* Fall through */
    #ifndef COMPILE_CHECKS
        default:
    #endif
    	kom_errno = KOM_ILLEGAL_INFO_TYPE;
    	return FAILURE;
        }
        
        if ( t_stat->author != ACTPERS )
    	ADD_MISC(t_stat, sent_by, sender, ACTPERS);
    
        ADD_MISC(t_stat, sent_at, sent_at, time(NULL));
    
        mark_text_as_changed( text_no );
        return OK;
    }
    
    
    /*
     * Subtract a recipient from a text.
     *
     * This may be done by
     *	a) a supervisor of the author.
     *	b) a supervisor of the recipient.
     *	c) the sender of the text to the recipient
     */
    extern Success
    sub_recipient(	Text_no		text_no,
    		Conf_no		conf_no)
    {
        Text_stat * text_s;
        Conference * conf_c;
        
        CHK_LOGIN(FAILURE);
        GET_T_STAT(text_s, text_no, FAILURE);
    
        if ( !text_read_access(text_no, text_s ) && !ENA(admin, 4))
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
        
        if ( !is_recipient( conf_no, text_s ) )
        {
    	kom_errno = KOM_NOT_RECIPIENT;
    	return FAILURE;
        }
        
        GET_C_STAT(conf_c, conf_no, FAILURE);
    
        
        if (   !is_supervisor (text_s->author, NULL, ACTPERS, ACT_P)
    	&& !is_supervisor (conf_no, conf_c, ACTPERS, ACT_P)
    	&& !is_sender (text_s, conf_no) )
        {
    	kom_errno = KOM_PERM;
    	return FAILURE;
        }
    
        return do_sub_recpt( text_no, text_s, conf_no, conf_c );
    }
    
    /*
     * Add a comment-link between two existing texts.
     */
    extern Success
    add_comment(Text_no	comment,
    	    Text_no 	comment_to)
    {
        Text_stat *child_s, *parent_s;
    
        CHK_LOGIN(FAILURE);
    
        /* The following code can not cope with a text that is a comment
           to itself. That is considered to be a bug. Work around it for
           now, until a proper misc-info-list handling package is written.
           FIXME +++ /// */
        if (comment == comment_to)
        {
    	kom_errno = KOM_INDEX_OUT_OF_RANGE;
    	return FAILURE;
        }
    
        GET_T_STAT(child_s, comment, FAILURE);
        GET_T_STAT(parent_s, comment_to, FAILURE);
        
        if ( check_comm(parent_s) != OK )
    	return FAILURE;
    
        if ( !text_read_access(comment, child_s) )
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
    
        /* Check if already comment */
        if ( is_comment_to(comment, parent_s) )
        {
    	kom_errno = KOM_ALREADY_COMMENT;
    	return FAILURE;
        }
    
        if ( do_add_comment(comment, comment_to) != OK)
    	return FAILURE;
    
        if ( child_s->author != ACTPERS )
    	ADD_MISC(child_s, sent_by, sender, ACTPERS);
    
        ADD_MISC(child_s, sent_at, sent_at, time(NULL));
    
        mark_text_as_changed (comment);
        return OK;
    }
    
    /*
     * Delete a comment-link between two texts.
     *
     * This can be done by:
     *	a) a supervisor of any of the authors.
     *	b) the sender of the comment.
     */
    extern Success
    sub_comment(	Text_no	comment,	/* 'comment' is no longer a comment */
    		Text_no parent )	/* to 'parent' */
    {
        Text_stat  * text_s;
        Text_stat  * parent_s;
    
    
        CHK_LOGIN(FAILURE);
    
        GET_T_STAT(text_s, comment, FAILURE);
        GET_T_STAT(parent_s, parent, FAILURE);
    
        if ( !text_read_access(comment, text_s ) && !ENA(admin, 4))
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
        
        if ( !text_read_access(parent, parent_s ) && !ENA(admin, 4))
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
        
        if ( !is_comment_to( comment, parent_s ) )
        {
    	kom_errno = KOM_NOT_COMMENT;
    	return FAILURE;
        }
        
        
        if ( !is_supervisor (text_s->author, NULL, ACTPERS, ACT_P)
    	&& !is_supervisor (parent_s->author, NULL, ACTPERS, ACT_P)
    	&& !is_comm_sender (text_s, parent) )
        {
    	kom_errno = KOM_PERM;
    	return FAILURE;
        }
    
        do_sub_comment( comment, text_s, parent, parent_s );
        return OK;
    }
    
    /*
     * Add a footnote-link between two existing texts. Only the author
     * may do this. The texts must have the same author.
     */
    extern Success
    add_footnote(	Text_no		footnote,
    		Text_no 	footnote_to )
    {
        Text_stat *text_s, *parent_s;
    
        CHK_LOGIN(FAILURE);
        GET_T_STAT(text_s, footnote, FAILURE);
        GET_T_STAT(parent_s, footnote_to, FAILURE);
        
        /* The following code can not cope with a text that is a footnote
           to itself. That is considered to be a bug. Work around it for
           now, until a proper misc-info-list handling package is written.
           FIXME /// +++ */
        if (footnote == footnote_to)
        {
    	kom_errno = KOM_INDEX_OUT_OF_RANGE;
    	return FAILURE;
        }
    
        if ( !text_read_access(footnote, text_s)
    	|| !text_read_access(footnote_to, parent_s) )
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
    
        if ( check_footn(parent_s) != OK )
    	return FAILURE;
    
        if ( text_s->author != parent_s->author )
        {
    	kom_errno = KOM_NOT_AUTHOR;
    	return FAILURE;
        }
        
        /* Check if already footnote */
        if ( is_footnote_to(footnote, parent_s) )
        {
    	kom_errno = KOM_ALREADY_FOOTNOTE;
    	return FAILURE;
        }
    
        if ( do_add_footnote(footnote, footnote_to) != OK)
    	return FAILURE;
    
        ADD_MISC(text_s, sent_at, sent_at, time(NULL));
    
        mark_text_as_changed( footnote );
        return OK;
    }
    
    /*
     * Delete a footnote-link between two texts.
     * Only the author may do this.
     */
    extern Success
    sub_footnote(	Text_no	footnote,	/* 'footnote' is no longer a  */
    		Text_no parent )	/* footnote to 'parent' */
    {
        Text_stat  * text_s;
        Text_stat  * parent_s;
    
    
        CHK_LOGIN(FAILURE);
    
        GET_T_STAT(text_s, footnote, FAILURE);
        GET_T_STAT(parent_s, parent, FAILURE);
    
        if ( !text_read_access(footnote, text_s ) && !ENA(admin, 4))
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
        
        if ( !text_read_access(parent, parent_s ) && !ENA(admin, 4))
        {
    	kom_errno = KOM_NO_SUCH_TEXT;
    	return FAILURE;
        }
        
        if ( !is_footnote_to( footnote, parent_s ) )
        {
    	kom_errno = KOM_NOT_FOOTNOTE;
    	return FAILURE;
        }
        
        
        if ( text_s->author != ACTPERS && parent_s->author != ACTPERS )
        {
    	kom_errno = KOM_PERM;
    	return FAILURE;
        }
    
        do_sub_footnote( footnote, text_s, parent, parent_s );
        return OK;
    }
    
    
    /*
     * Get mapping from Local_text_no to (global) Text_no for part of
     * a conference.
     *
     * BUG: You should be allowed to get the mapping if it is a letterbox
     *	which is not unread_is_secret. +++ (Really? I don't think so. /ceder)
     */
    extern  Success
    get_map (Conf_no	 conf_no,
    	 Local_text_no	 first_local_no,
    	 Local_text_no   no_of_texts,
    	 Text_list     * result)
    {
        Conference  * conf_c;
        Local_text_no highest_wanted_no, highest;
        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 ( acc == read_protected )
        {
    	kom_errno = KOM_ACCESS;
    	return FAILURE;
        }
        
        highest = conf_c->texts.first_local_no + conf_c->texts.no_of_texts;
        
        if ( first_local_no >= highest )
        {
    	kom_errno = KOM_NO_SUCH_LOCAL_TEXT;
    	return FAILURE;
        }
        
        result->first_local_no = max(conf_c->texts.first_local_no,
    				 first_local_no);
        highest_wanted_no = min(highest, first_local_no + no_of_texts);
    
        if ( highest_wanted_no >= result->first_local_no )
    	result->no_of_texts = highest_wanted_no - result->first_local_no;
        else
    	result->no_of_texts = 0;
    
        result->texts = &conf_c->texts.texts[ result->first_local_no
    					  - conf_c->texts.first_local_no ];
    
        return OK;
    }