From 15a053894d117641f4d0e98614db1c343c9a4a1c Mon Sep 17 00:00:00 2001
From: Per Cederqvist <ceder@lysator.liu.se>
Date: Mon, 20 May 1991 21:25:23 +0000
Subject: [PATCH] Initial RCS checkin.

---
 src/server/send-async.c   |  458 ++++++++
 src/server/send-async.h   |   43 +
 src/server/session.c      |  405 +++++++
 src/server/simple-cache.c | 1674 +++++++++++++++++++++++++++++
 src/server/text-garb.c    |  125 +++
 src/server/text-garb.h    |    3 +
 src/server/text.c         | 2103 +++++++++++++++++++++++++++++++++++++
 7 files changed, 4811 insertions(+)
 create mode 100644 src/server/send-async.c
 create mode 100644 src/server/send-async.h
 create mode 100644 src/server/session.c
 create mode 100644 src/server/simple-cache.c
 create mode 100644 src/server/text-garb.c
 create mode 100644 src/server/text-garb.h
 create mode 100644 src/server/text.c

diff --git a/src/server/send-async.c b/src/server/send-async.c
new file mode 100644
index 000000000..b1de21f74
--- /dev/null
+++ b/src/server/send-async.c
@@ -0,0 +1,458 @@
+/*
+ * send-async.c -- Send messages about events to all connected clients.
+ *
+ * Written by Per Cederqvist 1990-07-22--23
+ */
+
+#include <stdio.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include <services.h>
+#include "async.h"
+#include "manipulate.h"
+#include "com.h"
+#include "connections.h"
+#include "send-async.h"
+#include "prot-a-send-async.h"
+#include "prot-a-output.h"
+#include "log.h"
+#include "internal-connections.h"
+    
+
+extern Bool		  send_async_messages;
+
+
+/*
+ * 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(__FILE__ ": is_member_in_recpt(): bad misc_item.\n");
+	    break;
+#endif
+	}
+    }
+
+    return FALSE;
+}
+
+void
+async_new_text(Text_no    text_no, 
+	       Text_stat *text_s)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0 )
+    {
+	cptr = get_conn_by_number(i);
+	       
+	if ( cptr->person != NULL
+	    && is_member_in_recpt( cptr->person, text_s ) )
+	{
+	    switch(cptr->protocol)
+	    {
+	    case 0:
+		break;
+	    case 'A':
+		prot_a_async_new_text(cptr, text_no, text_s);
+		break;
+	    default:
+		restart_kom("async_new_text(): bad protocol.\n");
+		break;
+	    }
+	    
+	}
+    }
+}
+
+    
+    
+	    
+void
+async_i_am_on(Who_info info)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0 )
+    {
+	cptr = get_conn_by_number(i);
+
+	switch(cptr->protocol)
+	{
+	case 0:			/* Not yet logged on. */
+	    break;
+	case 'A':
+	    prot_a_async_i_am_on(cptr, info);
+	    break;
+	default:
+	    restart_kom("async_i_am_on(): bad protocol.\n");
+	    break;
+	}
+    }
+}
+
+
+void
+async_i_am_off(Pers_no pers_no)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0 )
+    {
+	cptr = get_conn_by_number(i);
+
+	if ( cptr == NULL )
+	{
+	    log("async_i_am_off(): cptr == NULL\n");
+	    return;
+	}
+
+	switch(cptr->protocol)
+	{
+	case 0:
+	    break;
+	case 'A':
+	    prot_a_async_i_am_off(cptr, pers_no);
+	    break;
+	default:
+	    restart_kom("async_i_am_off(): bad protocol.\n");
+	    break;
+	}
+    }
+}
+
+void
+async_logout(Pers_no pers_no, 
+	     Session_no session_no)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0)
+    {
+	cptr = get_conn_by_number(i);
+
+	if ( cptr == NULL )
+	{
+	    log("async_logout(): cptr == NULL\n");
+	    return;
+	}
+
+	switch(cptr->protocol)
+	{
+	case 0:
+	    break;
+	case 'A':
+	    prot_a_async_logout(cptr, pers_no, session_no);
+	    break;
+	default:
+	    restart_kom("async_logout(): bad protocol.\n");
+	    break;
+	}
+    }
+}
+
+
+
+void
+async_new_name(Conf_no 	     conf_no,
+	       const String  old_name,
+	       const String  new_name)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0)
+    {
+	cptr = get_conn_by_number(i);
+
+	if ( cptr == NULL )
+	{
+	    log("async_new_name(): cptr == NULL\n");
+	    return;
+	}
+
+	switch(cptr->protocol)
+	{
+	case 0:
+	    break;
+	case 'A':
+	    prot_a_async_new_name(cptr, conf_no, old_name, new_name);
+	    break;
+		
+	default:
+	    restart_kom("async_new_name(): bad protocol.\n");
+	    break;
+	}
+    }
+}
+
+
+
+
+#if 0    
+conf_deleted
+conf_created
+#endif
+
+void
+async_sync_db(void)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0 )
+    {
+	cptr = get_conn_by_number(i);
+
+	if ( cptr == NULL )
+	{
+	    log("async_sync_db(): cptr == NULL\n");
+	    return;
+	}
+
+	switch(cptr->protocol)
+	{
+	case 0:
+	    break;
+	case 'A':
+	    prot_a_async_sync_db(cptr);
+	    break;
+	default:
+	    restart_kom("async_sync_db(): bad protocol.\n");
+	    break;
+	}
+    }
+}
+
+
+extern void
+async_forced_leave_conf (struct connection *cptr,
+			 Conf_no 	   conf_no)
+{
+    if (!send_async_messages)
+	return;
+
+    switch(cptr->protocol)
+    {
+    case 0:
+	break;
+    case 'A':
+	prot_a_async_forced_leave_conf(cptr, conf_no);
+	break;
+    default:
+	restart_kom("async_forced_leave_conf(): bad protocol.\n");
+	break;
+    }
+}
+
+void
+async_login(Pers_no	pers_no,
+	    int		client_no)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0)
+    {
+	cptr = get_conn_by_number(i);
+
+	if ( cptr == NULL )
+	{
+	    log("async_login(): cptr == NULL\n");
+	    return;
+	}
+
+	switch(cptr->protocol)
+	{
+	case 0:
+	    break;
+	case 'A':
+	    prot_a_async_login(cptr, pers_no, client_no);
+	    break;
+		
+	default:
+	    restart_kom("async_login(): bad protocol.\n");
+	    break;
+	}
+    }
+}
+
+void
+async_broadcast(Pers_no	pers_no,
+		String	message)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0)
+    {
+	cptr = get_conn_by_number(i);
+
+	if ( cptr == NULL )
+	{
+	    log("async_broadcast(): cptr == NULL\n");
+	    return;
+	}
+
+	switch(cptr->protocol)
+	{
+	case 0:
+	    break;
+	case 'A':
+	    prot_a_async_broadcast(cptr, pers_no, message);
+	    break;
+		
+	default:
+	    restart_kom("async_broadcast(): bad protocol.\n");
+	    break;
+	}
+    }
+}
+
+
+void
+async_rejected_connection(void)
+{
+    Connection *cptr;
+    Session_no i = 0;
+
+    if (!send_async_messages)
+	return;
+
+    while ( (i = traverse_connections(i)) != 0)
+    {
+	cptr = get_conn_by_number(i);
+
+	if ( cptr == NULL )
+	{
+	    log("async_rejected_connections(): cptr == NULL\n");
+	    return;
+	}
+
+	switch(cptr->protocol)
+	{
+	case 0:
+	    break;
+	case 'A':
+	    prot_a_async_rejected_connection(cptr);
+	    break;
+		
+	default:
+	    restart_kom("async_rejected_connection(): bad protocol.\n");
+	    break;
+	}
+    }
+}
+
+
+/*
+ * Returns failure if no message was sent.
+ */
+Success
+async_send_message(Pers_no recipient,
+		   Pers_no sender,
+		   String  message)
+{
+    Connection *cptr;
+    Session_no i = 0;
+    Success retval = FAILURE;
+
+    if (!send_async_messages)
+	return FAILURE;
+
+    while ( (i = traverse_connections(i)) != 0)
+    {
+	cptr = get_conn_by_number(i);
+
+	if ( cptr == NULL )
+	{
+	    log("async_send_message(): cptr == NULL\n");
+	    return FAILURE;
+	}
+
+	switch(cptr->protocol)
+	{
+	case 0:
+	    break;
+	case 'A':
+	    if ( recipient == 0 ||
+		(recipient == cptr->pers_no && recipient != 0 ))
+	    {
+		prot_a_async_send_message(cptr, recipient, sender, message);
+		retval = OK;
+	    }
+	    break;
+		
+	default:
+	    restart_kom("async_send_message(): bad protocol.\n");
+	    break;
+	}
+    }
+    
+    return retval;
+}
diff --git a/src/server/send-async.h b/src/server/send-async.h
new file mode 100644
index 000000000..a412a6374
--- /dev/null
+++ b/src/server/send-async.h
@@ -0,0 +1,43 @@
+extern void
+async_new_text(Text_no text_no, 
+	       Text_stat *text_s);
+
+extern void
+async_i_am_on(Who_info info);
+
+extern void
+async_i_am_off(Pers_no person);
+
+extern void
+async_logout(Pers_no pers_no, 
+	     Session_no session_no);
+
+extern void
+async_new_name(Conf_no 	     conf_no,
+	       const String  old_name,
+	       const String  new_name);
+
+extern void
+async_sync_db(void);
+
+
+extern void
+async_forced_leave_conf (Connection *cptr,
+			 Conf_no 	   conf_no);
+
+
+extern void
+async_login(Pers_no	pers_no,
+	    int		client_no);
+
+extern void
+async_broadcast(Pers_no	pers_no,
+		String	message);
+
+extern void
+async_rejected_connection(void);
+
+extern Success
+async_send_message(Pers_no recipient,
+		   Pers_no sender,
+		   String  message);
diff --git a/src/server/session.c b/src/server/session.c
new file mode 100644
index 000000000..5d2d38520
--- /dev/null
+++ b/src/server/session.c
@@ -0,0 +1,405 @@
+/*
+ * session.c
+ *
+ * Session control and miscellaneous.
+ */
+
+#include <time.h>
+#include <stdlib.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include <services.h>
+#include "manipulate.h"
+#include "cache.h"
+#include "com.h"
+#include "connections.h"
+#include "send-async.h"
+#include "smalloc.h"
+#include "log.h"
+#include <kom-errno.h>
+#include "config.h"
+#include "internal-connections.h"
+
+/*
+ * This function is called whenever a person leaves a conf,
+ * i e when he pepsi():s or logout():s.
+ */
+
+extern void
+leave_conf(void)
+{
+    Membership * mship;
+
+    if (active_connection->cwc != 0 )
+    {
+	if ((mship = locate_membership( active_connection->cwc, ACT_P ))
+	    != NULL )
+	{
+	    time(&mship->last_time_read);
+	    mark_person_as_changed (active_connection->pers_no);
+	}
+	else
+	{
+	    log("ERROR: leave_conf(): Can't find membership of cwc.\n");
+	}
+	
+	cached_unlock_conf( active_connection->cwc );
+	active_connection->cwc = 0;
+    }
+}
+
+/*
+ * Log in as user pers_no. If ACTPERS is a supervisor of pers_no the login
+ * will succeed regardless of the passwd. An logout() will automatically
+ * be performed if ACTPERS is logged in.
+ */
+extern Success
+login (Pers_no       pers_no,
+       const String  passwd)
+{
+    Person *pers_p;
+
+    GET_P_STAT(pers_p, pers_no, FAILURE);
+    
+#if 0
+    if ( !logins_allowed  && !pers_p->privileges.wheel)
+    {
+	kom_errno = KOM_LOGIN_DISALLOWED;
+	return FAILURE;
+    }
+#endif
+    
+    if ( !is_supervisor(pers_no, NULL)
+	&& chk_passwd(pers_p->pwd, passwd) == FAILURE )
+    {
+	kom_errno = KOM_PWD;
+	return FAILURE;
+    }
+
+    logout();	/*+++ How many tries are allowed before disconnection? */
+
+    ACTPERS = pers_no;
+    ACT_P   = pers_p;
+
+    cached_lock_person(pers_no);
+        
+    pers_p->last_login = time(&active_connection->session_start);
+    ++pers_p->sessions;
+
+    s_strcpy(&pers_p->username, active_connection->username);
+     
+    mark_person_as_changed( pers_no );
+    
+    async_login(ACTPERS, active_connection->session_no);
+    
+    return OK;
+}
+
+
+/*
+ * Log out. Does not disconnect the client. The person is also automatically
+ * logged out if the connection is closed. Takes no action if noone is logged
+ * in on this line. Never fails.
+ */
+extern Success
+logout(	void )
+{
+    if ( ACTPERS != 0 )		/* Is he logged in? Then log him out. */
+    {
+	async_i_am_off( ACTPERS );
+	async_logout( ACTPERS, active_connection->session_no );
+	
+	leave_conf();
+	ACT_P->total_time_present +=
+	    difftime(time(&ACT_P->last_login),
+		     active_connection->session_start);
+
+	cached_unlock_person( ACTPERS );
+	mark_person_as_changed( ACTPERS );
+    }
+    
+    
+    s_clear(&active_connection -> what_am_i_doing);
+    
+    active_connection->person		= NULL;
+    active_connection->cwc		= 0;
+    active_connection->pers_no		= 0;
+    active_connection->ena_level	= 0;
+    
+    return OK;
+}
+
+
+/*
+ * Change Conference.
+ *
+ * You are not allowed to change to a conference unless you are a
+ * member in the conference.
+ *
+ * You can pepsi(0) to indicate that you are no longer in a certain
+ * conference.
+ *
+ * There are two reasons to use this call:
+ *	1) Other persons want to know what you are doing.
+ *	2) When the server checks that you have permission to
+ *	   read a certain text it first checks if you current working
+ *	   conference is a recipient of the text, since that is a
+ *	   cheap test. If that test fails it will have to read in the
+ *	   conference structs for the recipients of the text until it
+ *	   finds an open one.
+ *
+ * In the future the server might read in the conference in advance when
+ * someone pepsi's to it to allow faster response times.
+ */
+extern Success
+pepsi (Conf_no		conference)		
+{
+    Who_info	 info = EMPTY_WHO_INFO;
+    
+    CHK_LOGIN(FAILURE);
+    if (  conference != 0 )
+    {
+	CHK_EXIST(conference, FAILURE);
+
+	if ( locate_membership( conference, ACT_P) == NULL )
+	{
+	    kom_errno = KOM_NOT_MEMBER;
+	    return FAILURE;
+	}
+    }
+
+    leave_conf();
+    
+    active_connection->cwc = conference;
+    if ( conference != 0 )
+	cached_lock_conf( conference );
+
+    info.person = ACTPERS;
+    info.what_am_i_doing = active_connection->what_am_i_doing;
+    info.username = active_connection->username;
+    info.working_conference = conference;
+    info.session_no = active_connection->session_no;
+    
+    async_i_am_on(info);
+
+    return OK;
+}
+
+
+/*
+ * Tell the server what you are doing. This string is sent to anyone
+ * who does a 'who_is_on()'.
+ */
+extern Success
+change_what_i_am_doing (String  what_am_i_doing)
+{
+    Who_info info = EMPTY_WHO_INFO; 
+
+    if ( s_strlen( what_am_i_doing ) > WHAT_DO_LEN )
+    {
+	s_clear ( &what_am_i_doing );
+	kom_errno = KOM_LONG_STR;
+	return FAILURE;
+    }
+    
+    s_clear ( &active_connection->what_am_i_doing );
+    active_connection->what_am_i_doing = what_am_i_doing;
+
+    info.person = ACTPERS;
+    info.what_am_i_doing = active_connection->what_am_i_doing;
+    info.username = active_connection->username;
+    info.working_conference = active_connection->cwc;
+    info.session_no = active_connection->session_no;
+    
+    async_i_am_on(info);
+
+    return OK;
+}
+
+/*
+ * Get info about what all the currently logged in persons are doing.
+ */
+extern Success
+who_is_on( Who_info_list *result )
+{
+    Connection *cptr;
+    int 	    no_of_clients = 0;
+    int		    i;
+    Session_no      session;
+    
+    cptr = active_connection;
+    
+    for ( session = 0; (session = traverse_connections(session)) != 0; )
+    {
+	cptr = get_conn_by_number(session);
+
+	if ( cptr->person != NULL )
+	    ++no_of_clients;
+    }
+
+    result->no_of_persons = no_of_clients;
+    result->info = tmp_alloc ( no_of_clients * sizeof(Who_info));
+
+    for ( session = 0, i = 0;
+	 i < no_of_clients && (session = traverse_connections(session)) != 0; )
+    {
+	cptr = get_conn_by_number(session);
+
+	if ( cptr->person != NULL )
+	{
+	    result->info[ i ] = EMPTY_WHO_INFO;
+	    result->info[ i ].person = cptr->pers_no;
+	    result->info[ i ].what_am_i_doing = cptr->what_am_i_doing;
+	    result->info[ i ].username = cptr->username;
+	    result->info[ i ].working_conference = cptr->cwc;
+	    result->info[ i ].session_no = cptr->session_no;
+	    ++i;
+	}
+    }
+
+    if ( i != no_of_clients )
+	log("who_is_on: i == %d, no_of_clients == %d\n",
+	    i, no_of_clients);
+
+    return OK;
+}
+
+
+extern  Success
+get_session_info  (Session_no session_no,
+		   Session_info *result)
+{
+    Connection *cptr;
+
+    CHK_LOGIN(FAILURE);
+
+    cptr = get_conn_by_number(session_no);
+
+    if ( cptr != NULL )
+    {
+	*result = EMPTY_SESSION_INFO;
+	result->person = cptr->pers_no;
+	result->what_am_i_doing = cptr->what_am_i_doing;
+	result->username = cptr->username;
+	result->working_conference = cptr->cwc;
+	result->session = cptr->session_no;
+	result->connection_time = cptr->session_start;
+	result->idle_time = difftime(time(NULL),
+				     cptr->last_request);
+
+	return OK;
+    }
+    else
+    {
+	kom_errno = KOM_UNDEF_SESSION;
+	return FAILURE;
+    }
+}
+
+extern  Success
+who_am_i (Session_no *session_no)
+{
+    *session_no = active_connection->session_no;
+    return OK;
+}
+
+
+
+extern  Success
+disconnect (Session_no session_no)
+{
+    Connection *cptr;
+
+    if ( session_no != active_connection->session_no )
+	CHK_LOGIN(FAILURE);
+
+    cptr = get_conn_by_number(session_no);
+
+    if ( cptr != NULL )
+    {
+	if ( is_supervisor(cptr->pers_no, NULL)
+	    || session_no == active_connection->session_no )
+	{
+	    cptr->kill_me = TRUE;
+	    return OK;
+	}
+	else
+	{
+	    kom_errno = KOM_PERM;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	kom_errno = KOM_UNDEF_SESSION;
+	return FAILURE;
+    }
+}
+
+
+/* Get less info */
+extern Success
+who_is_on_old( Who_info_list_old *result )
+{
+    Connection *cptr;
+    int 	    no_of_clients = 0;
+    int		    i;
+    Session_no	    session = 0;
+
+    
+    while ( (session = traverse_connections(session)) != 0)
+    {
+	cptr = get_conn_by_number(session);
+
+	if ( cptr->person != NULL )
+	    ++no_of_clients;
+    }
+
+    result->no_of_persons = no_of_clients;
+    result->info = tmp_alloc ( no_of_clients * sizeof(Who_info));
+
+    for (session = 0, i = 0;
+	 i < no_of_clients && (session = traverse_connections(session)) != 0;)
+    {
+	cptr = get_conn_by_number(session);
+
+	if ( cptr->person != NULL )
+	{
+	    result->info[ i ] = EMPTY_WHO_INFO_OLD;
+	    result->info[ i ].person = cptr->pers_no;
+	    result->info[ i ].what_am_i_doing = cptr->what_am_i_doing;
+	    result->info[ i ].working_conference = cptr->cwc;
+	    ++i;
+	}
+    }
+    
+    if ( i != no_of_clients )
+	log("who_is_on: i == %d, no_of_clients == %d\n",
+	    i, no_of_clients);
+
+    return OK;
+}
+
+/*
+ * Ask the server what it thinks the time is.
+ */
+extern Success
+get_time( time_t *clock )
+{
+    time(clock);
+    
+    return OK;
+}
+
+
+
+/*
+ * Set ena_level. 0 means don't use any privileges. ///
+ */
+extern Success
+enable (u_char ena_level)
+{
+    CHK_LOGIN(FAILURE);
+    active_connection->ena_level = ena_level;
+    return OK;
+}
diff --git a/src/server/simple-cache.c b/src/server/simple-cache.c
new file mode 100644
index 000000000..d5ab99a80
--- /dev/null
+++ b/src/server/simple-cache.c
@@ -0,0 +1,1674 @@
+/*
+ * This module contains some simple simulations of the routines in
+ * cache.c.
+ *
+ * Extracted from ram-cache.c and rewritten by ceder.
+ *
+ * New database format with texts in their own file by Inge Wallin.
+ *
+ * New save algorithm by ceder.
+ */
+
+/*
+ * Things to implemet a.s.a.p:
+ *
+ *  +++ Limit on lru.
+ *  +++ Better allocation scheme of Cache-nodes.
+ */
+
+/*
+ * Possible improvements: +++
+ *  When there are consecutive items in file A that shall be copied
+ *  to file B, copy them in one transfer (up to a certain limit).
+ *
+ *  In pre_sync: compute size of, and allocate disk space for file B.
+ */
+
+/*
+ * All functions that can fail sets kom_errno to a suitable value
+ * if they fail.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/file.h>
+
+#include <kom-errno.h>
+#include <kom-types.h>
+#include "s-collat-tables.h"
+#include "smalloc.h"
+#include "cache.h"
+#include <debug.h>
+#include "lyskomd.h"
+#include "parser.h"
+#include "ram-parse.h"
+#include "log.h"
+#include "com.h"
+#include "isc.h"
+#include "ram-output.h"
+#include "connections.h"
+#include "prot-a-output.h"
+#include "send-async.h"
+#include "memory.h"
+#include "exp.h"
+
+#include "limits.h"
+
+typedef struct cache_node {
+    struct {
+	u_int	exists : 1;
+	u_int	exists_b : 1;
+	u_int	dirty : 1;		/* [r *ptr modifierad? */
+    } s;
+    void   *snap_shot;		/* Dirty data to be written to file B. */
+				/* (Dirty relative to file A). */
+    void   *ptr;		/* In-core data. */
+    long   pos;			/* Position to element in file A. */
+    long   size;		/* Size on disk. */
+    long   pos_b;		/* Position to element in file B. */
+    long   size_b;		/* Size in file B. */
+    struct cache_node *lru;
+    struct cache_node *mru;
+    int	lock_cnt;
+} Cache_node;
+
+const Cache_node EMPTY_CACHE_NODE =
+    ((Cache_node){{ 0, 0, 0}, NULL, NULL, 0, 0, 0, 0, NULL, NULL, 0});
+
+static Cache_node *pers_mru = NULL;
+static Cache_node *pers_lru = NULL;
+static Cache_node *conf_mru = NULL;
+static Cache_node *conf_lru = NULL;
+static Cache_node *text_mru = NULL;
+static Cache_node *text_lru = NULL;
+
+static Cache_node	* pers_arr[ MAX_CONF ];
+static Cache_node 	* conf_arr[ MAX_CONF ];
+static Small_conf	* small_conf_arr[ MAX_CONF ];
+static int		  next_free_num = 1;
+
+static Cache_node 	* text_arr[ MAX_TEXT ];
+static int next_text_num = 1;
+
+/* Defined in ramkomd.c */
+extern char datafilename[1024];
+extern char backupfilename[1024];
+extern char textfilename[1024];
+
+/*
+ * The elements in the following lists with same index refers to the same
+ * conference.
+ */
+static int    no_of_match_info;
+EXPORT Matching_info *match_table = NULL;
+EXPORT Conf_no       *conf_table  = NULL;
+
+
+static FILE	*text_file= NULL;
+static FILE	*file_a = NULL;	/* Current file. */
+static FILE	*file_b = NULL;	/* File under construction. */
+
+/*
+ * Four state variables for the background save.
+ */
+static enum {
+    sync_save_conf,
+    sync_save_pers,
+    sync_save_text,
+    sync_error,
+    sync_wait,
+    sync_ready
+} sync_state;
+
+static long sync_next;
+
+static Conf_no highest_conf_no;
+static Text_no highest_text_no;
+
+BUGDECL;
+
+
+/* Macros */
+
+#define TRACE2(format, arg) if ( buglevel > 2 ) printf(format, arg)
+#define TRACE1(format) if ( buglevel > 2 ) printf(format)
+#define TRACESTR(str)  if ( buglevel > 2 ) s_puts(str)
+
+
+static Person *
+read_person(FILE *fp,
+	    long pos,
+	    long size)
+{
+    Person *p;
+
+    p = alloc_person();
+    fseek(fp, pos+1, SEEK_SET);	/* Skip '+' */
+    if ( fparse_person(fp, p) != OK )
+    {
+	free_person(p);
+	return NULL;
+    }
+    else
+	return p;
+}
+
+
+
+static Conference *
+read_conference(FILE *fp,
+		long pos,
+		long size)
+{
+    Conference *c;
+
+    c = alloc_conference();
+    fseek(fp, pos+1, SEEK_SET);	/* Skip '+' */
+    if ( fparse_conference(fp, c) != OK )
+    {
+	free_conference(c);
+	return NULL;
+    }
+    else
+	return c;
+}
+	
+
+static Text_stat *
+read_text_stat(FILE *fp,
+	       long pos,
+	       long size)
+{
+    Text_stat *t;
+
+    t = alloc_text_stat();
+    fseek(fp, pos+1, SEEK_SET);	/* Skip '+' */
+    if ( fparse_text_stat(fp, t) != OK )
+    {
+	free_text_stat(t);
+	return NULL;
+    }
+    else
+	return t;
+}
+
+
+static void
+unlink_lru(Cache_node *node,
+	   Cache_node **lru,
+	   Cache_node **mru)
+{
+    Cache_node *link;
+    
+    link = node->lru;
+
+    if ( node->lru != NULL )
+	node->lru->mru = node->mru;
+    else if (*lru == node)
+	*lru = node->mru;
+
+    if ( node->mru != NULL )
+	node->mru->lru = link;
+    else if (*mru == node)
+	*mru = link;
+}
+
+static void
+insert_mru(Cache_node *node,
+	   Cache_node **lru,
+	   Cache_node **mru)
+{
+    node->mru = NULL;
+    node->lru = *mru;
+    *mru = node;
+    if ( *lru == NULL )
+	*lru = node;
+
+    if ( node->lru != NULL )
+	node->lru->mru = node;
+}
+
+static void
+pers_set_mru(Pers_no pers_no)
+{
+    Cache_node *node = pers_arr[ pers_no ];
+
+    if (node == NULL)
+	restart_kom("pers_set_mru(%d): nonexistent.\n", pers_no);
+
+    unlink_lru(node, &pers_lru, &pers_mru);
+    insert_mru(node, &pers_lru, &pers_mru);
+}
+
+static void
+text_set_mru(Text_no text_no)
+{
+    Cache_node *node = text_arr[ text_no ];
+
+    if ( node == NULL )
+	restart_kom("text_set_mru(%d): nonexistent.\n", text_no);
+
+    unlink_lru(node, &text_lru, &text_mru);
+    insert_mru(node, &text_lru, &text_mru);
+}
+
+
+static void
+conf_set_mru(Conf_no conf_no)
+{
+    Cache_node *node = conf_arr[ conf_no ];
+
+    if ( node == NULL )
+	restart_kom("conf_set_mru(%d): nonexistent.\n", conf_no);
+
+    unlink_lru(node, &conf_lru, &conf_mru);
+    insert_mru(node, &conf_lru, &conf_mru);
+}
+
+	
+/*
+ * Name caching routines
+ */
+
+/*
+ * change_name changes the cached conference name. It is only called when
+ * a conference name is changed or a conference is deleted.
+ */
+void
+cached_change_name( Conf_no name_num,
+		    String new_name )
+{
+    if ( name_num < 1 || name_num >= next_free_num )
+	restart_kom("cached_change_name(%d, ----): next_free_num==%d",
+		    name_num, next_free_num);
+
+    s_clear( &small_conf_arr[name_num]->name );
+    s_strcpy( &small_conf_arr[name_num]->name, new_name);
+    build_matching_info();
+}
+
+
+extern void
+cached_set_conf_type (Conf_no   conf_no,
+		      Conf_type type)
+{
+    if ( conf_no < 1 || conf_no >= next_free_num )
+	restart_kom("cached_set_conf_type(%d, ----): next_free_num==%d",
+		    conf_no, next_free_num);
+
+    small_conf_arr[ conf_no ]->type = type;
+}
+
+
+extern Conf_type
+cached_get_conf_type (Conf_no conf_no)
+{
+    if ( conf_no < 1 || conf_no >= next_free_num )
+	restart_kom("cached_get_conf_type(%d): next_free_num==%d",
+		    conf_no, next_free_num);
+    
+    return small_conf_arr [ conf_no ]->type;
+}
+
+
+
+/*
+ * Various function calls to tell the cache that something is changed.
+ */
+
+void
+mark_person_as_changed(Pers_no	pers_no)
+{
+    TRACE2("Person %d is changed\n", pers_no);
+    if ( pers_arr[ pers_no ] == NULL || pers_arr[ pers_no ]->s.exists == 0)
+	restart_kom("mark_person_as_changed(%d): nonexistent.\n", pers_no);
+
+    pers_arr[ pers_no ]->s.dirty = 1;
+    pers_set_mru( pers_no );
+}
+
+
+void
+mark_conference_as_changed(Conf_no	conf_no)
+{
+    TRACE2("Conf.  %d is changed\n", conf_no);
+    if ( conf_arr[ conf_no ] == NULL || conf_arr[ conf_no ]->s.exists == 0)
+	restart_kom("mark_conference_as_changed(%d): nonexistent.\n", conf_no);
+
+    conf_arr[ conf_no ]->s.dirty = 1;
+    conf_set_mru( conf_no );
+    small_conf_arr[ conf_no ]->highest_local_no
+	= (((Conference *)conf_arr [ conf_no ]->ptr)->texts.first_local_no - 1
+	   + ((Conference *)conf_arr [ conf_no ]->ptr)->texts.no_of_texts );
+}
+
+void
+mark_text_as_changed( Text_no text_no )
+{
+    TRACE2("Text %d is changed.\n", text_no);
+    if ( text_no < 1 || text_no >= next_text_num
+	|| text_arr[ text_no ] == NULL || text_arr[ text_no ]->s.exists == 0)
+    {
+	restart_kom("mark_text_as_changed(%d): nonexistent.\n", text_no);
+    }
+
+    text_arr[ text_no ]->s.dirty = 1;
+    text_set_mru( text_no );
+}    
+
+
+static Cache_node *
+alloc_cache_node(void)
+{
+    Cache_node *c;
+
+    c = smalloc(sizeof(Cache_node));
+    *c = EMPTY_CACHE_NODE;
+    return c;
+}
+
+/*
+ * Person-related calls
+ */
+
+
+extern Success
+cached_create_person( Pers_no person )
+{
+    TRACE2("Person %d is being created.\n", person);
+
+    if ( person < 1 || person >= next_free_num )
+    {
+	restart_kom("cached_create_person(%d): next_free_num == %d.\n",
+		    person, next_free_num);
+    }
+
+    if ( pers_arr[ person ] != NULL )
+    {
+	restart_kom("cached_create_person(%d): Person existed.\n",
+		    person);
+    }
+
+    pers_arr[ person ] = alloc_cache_node();
+    pers_arr[ person ]->ptr = alloc_person();
+    pers_arr[ person ]->s.dirty = 1;
+    pers_arr[ person ]->s.exists = 1;
+    pers_set_mru( person );
+    return OK;
+}
+
+
+extern Person *
+cached_get_person_stat( Pers_no	person )
+{
+    TRACE2("cached_get_person_stat %d\n", person);
+
+    if ( person == 0 )
+    {
+	kom_errno = KOM_CONF_ZERO;
+	return NULL;
+    }
+    
+    if ( person >= next_free_num || pers_arr[ person ] == NULL
+	|| pers_arr[ person ]->s.exists == 0 )
+    {
+	kom_errno = KOM_UNDEF_PERS;
+	return NULL;
+    }
+
+    if ( pers_arr[ person ]->ptr != NULL )
+    {
+	pers_set_mru( person );
+	return pers_arr[ person ]->ptr;
+    }
+
+    if ( pers_arr[ person ]->snap_shot != NULL )
+    {
+	pers_arr[ person ]->ptr = copy_person(pers_arr[ person ]->snap_shot);
+	pers_set_mru (person);
+	return pers_arr[ person ]->ptr;
+    }
+
+    pers_arr[ person ]->ptr = read_person(file_a,
+					  pers_arr[ person ]->pos,
+					  pers_arr[ person ]->size);
+
+    pers_set_mru (person);
+    return pers_arr[ person ]->ptr;
+}
+
+
+/*
+ * Conference-related calls
+ */
+extern Conf_no	/* Also cache the name */
+cached_create_conf (String  name)
+{
+    Conference * conf_c;
+    Conf_no	 conf_no;
+
+    TRACE1("cached_create_conf( ");
+    TRACESTR(name);
+    TRACE1(" )\n");
+
+    if ( next_free_num >= MAX_CONF )
+    {
+	kom_errno = KOM_INDEX_OUT_OF_RANGE;
+	return 0;
+    }
+    
+    conf_no = next_free_num++;
+
+    conf_arr[ conf_no ] = alloc_cache_node();
+    conf_arr[ conf_no ]->s.exists = 1;
+    conf_arr[ conf_no ]->s.dirty = 1;
+    conf_arr[ conf_no ]->ptr = conf_c = alloc_conference();
+    conf_set_mru(conf_no);    
+
+    pers_arr[ conf_no ] = NULL;
+    small_conf_arr[ conf_no ] = alloc_small_conf();
+
+    conf_c->name = EMPTY_STRING;
+    s_strcpy(&conf_c->name, name);
+    cached_change_name( conf_no, name);
+
+    TRACE2(" number %d\n", conf_no);
+    
+    return conf_no;
+}
+
+
+extern Success
+cached_delete_conf( Conf_no	conf )
+{
+    if ( conf == 0 )
+    {
+	kom_errno = KOM_CONF_ZERO;
+	return FAILURE;
+    }
+    
+    if ( conf >= next_free_num || conf_arr[ conf ] == NULL
+	|| conf_arr[ conf ]->s.exists == 0 )
+    {
+	kom_errno = KOM_UNDEF_CONF;
+	return FAILURE;
+    }
+
+    if ( conf_arr[ conf ]->lock_cnt > 0 )
+	log("cached_delete_conf(%d): lock_cnt === %d\n",
+	    conf, conf_arr[ conf ]->lock_cnt);
+
+    free_conference(conf_arr[ conf ]->ptr);
+    conf_arr[ conf ]->ptr = NULL;
+    conf_arr[ conf ]->s.exists = 0;
+
+    return OK;
+}
+
+Success
+cached_delete_person(Pers_no pers)
+{
+    if ( pers == 0 )
+    {
+	kom_errno = KOM_CONF_ZERO;
+	return FAILURE;
+    }
+    
+    if ( pers >= next_free_num || pers_arr[ pers ] == NULL
+	|| pers_arr[ pers ]->s.exists == 0 )
+    {
+	log("cached_delete_person(): attempt to"
+	    " delete non-existing person.\n");
+	kom_errno = KOM_UNDEF_PERS;
+	return FAILURE;
+    }
+
+    if ( pers_arr[ pers ]->lock_cnt > 0 )
+	log("cached_delete_pers(%d): lock_cnt === %d\n",
+	    pers, pers_arr[ pers ]->lock_cnt);
+
+    free_person(pers_arr[ pers ]->ptr);
+    pers_arr[ pers ]->ptr = NULL;
+    pers_arr[ pers ]->s.exists = 0;
+    return OK;
+}
+
+Success
+cached_delete_text(Text_no text)
+{
+    if ( text == 0 )
+    {
+	kom_errno = KOM_TEXT_ZERO;
+	return FAILURE;
+    }
+    
+    if ( text >= next_text_num || text_arr[ text ] == NULL
+	|| text_arr[ text ]->s.exists == 0 )
+    {
+	log("cached_delete_text(): attempt to"
+	    " delete non-existing text %d.\n", text);
+	kom_errno = KOM_NO_SUCH_TEXT;
+	return FAILURE;
+    }
+
+    if ( text_arr[ text ]->lock_cnt > 0 )
+	log("cached_delete_text(%d): lock_cnt === %d\n",
+	    text, text_arr[ text ]->lock_cnt);
+
+
+    free_text_stat(text_arr[ text ]->ptr);
+    text_arr[ text ]->ptr = NULL;
+    text_arr[ text ]->s.exists = 0;
+
+    return OK;
+}
+
+
+extern Conference *
+cached_get_conf_stat(	Conf_no		conf_no )
+{
+    TRACE2("cached_get_conf_stat %d\n", conf_no);
+
+    if ( conf_no == 0 )
+    {
+	kom_errno = KOM_CONF_ZERO;
+	return NULL;
+    }
+    
+    if ( conf_no >= next_free_num || conf_arr[ conf_no ] == NULL
+	|| conf_arr[ conf_no ]->s.exists == 0 )
+    {
+	kom_errno = KOM_UNDEF_CONF;
+	return NULL;
+    }
+
+    if ( conf_arr[ conf_no ]->ptr != NULL )
+    {
+	conf_set_mru( conf_no );
+	return conf_arr[ conf_no ]->ptr;
+    }
+
+    if ( conf_arr[ conf_no ]->snap_shot != NULL )
+    {
+	conf_arr[ conf_no ]->ptr = copy_conf(conf_arr[ conf_no ]->snap_shot);
+	conf_set_mru (conf_no);
+	return conf_arr[ conf_no ]->ptr;
+    }
+
+    conf_arr[ conf_no ]->ptr
+	= read_conference(file_a, conf_arr[ conf_no ]->pos, 
+			  conf_arr[ conf_no ]->size);
+
+    conf_set_mru (conf_no);
+    return conf_arr[ conf_no ]->ptr;
+}
+
+/*
+ * Return TRUE if conf_no exists.
+ */
+Bool
+cached_conf_exists(Conf_no conf_no)
+{
+    return (conf_no > 0 && conf_no < next_free_num
+	    && conf_arr[ conf_no ]->s.exists != 0) ? TRUE : FALSE;
+}
+
+    
+/*
+ * Calls to handle texts
+ */
+
+/*
+ * +++ Should return Success.
+ */
+extern String
+cached_get_text( Text_no text )
+{
+    String  the_string;
+    Text_stat *t_stat;
+
+    TRACE2("cached_get_text %d\n", text);
+
+    if ( (t_stat = cached_get_text_stat (text)) == NULL )
+	return EMPTY_STRING;
+    else
+    {
+	the_string.string = tmp_alloc( t_stat->no_of_chars );
+	the_string.len = t_stat->no_of_chars;
+	fseek(text_file, t_stat->file_pos, SEEK_SET);
+
+	if ( fread(the_string.string, sizeof(char), the_string.len, text_file)
+	    != the_string.len )
+	{
+	    log("WARNING: cached_get_text: "
+		"couldn't read enough characters of text %d\n",
+		text);
+	    return EMPTY_STRING;
+	}
+		    
+	return the_string;
+    }
+}
+
+
+extern Text_stat *	/* NULL on error */
+cached_get_text_stat(	Text_no		text )
+{
+    TRACE2("cached_get_text_stat(%d);  next_text_num == ", text);
+    TRACE2("%d\n", next_text_num);
+
+    if ( text == 0 )
+    {
+	kom_errno = KOM_TEXT_ZERO;
+	return NULL;
+    }
+    
+    if ( text >= next_text_num || text_arr[ text ] == NULL
+	|| text_arr[ text ]->s.exists == 0 )
+    {
+	TRACE1("cached_get_text_stat: no such text.\n");
+	kom_errno = KOM_NO_SUCH_TEXT;
+	return NULL;
+    }
+
+    if ( text_arr[ text ]->ptr != NULL )
+    {
+	TRACE1("Found in ptr.\n");
+	text_set_mru( text );
+	return text_arr[ text ]->ptr;
+    }
+
+    if ( text_arr[ text ]->snap_shot != NULL )
+    {
+	TRACE1("Found in snap_shot\n");
+	text_arr[ text ]->ptr = copy_text_stat(text_arr[ text ]->snap_shot);
+	text_set_mru (text);
+	return text_arr[ text ]->ptr;
+    }
+
+    TRACE1("Found in file A.\n");
+    text_arr[ text ]->ptr
+	= read_text_stat(file_a, text_arr[ text ]->pos,
+			 text_arr[ text ]->size);
+
+    text_set_mru (text);
+    return text_arr[ text ]->ptr;
+}
+
+
+
+/*
+ * The text is set up with an empty misc-field. The misc field is
+ * than initialized by create_text.
+ */
+
+extern Text_no
+cached_create_text( String message)
+{
+    Text_no tno;
+
+    tno = next_text_num++;
+
+    TRACE2("cached_create_text (len=%d)\n", message.len);
+
+    if ( tno >= MAX_TEXT )
+    {
+	kom_errno = KOM_INDEX_OUT_OF_RANGE;
+	next_text_num = MAX_TEXT;
+	
+	return 0;
+    }
+
+    text_arr[ tno ] = alloc_cache_node();
+
+    text_arr[ tno ]->s.exists = 1;
+    text_arr[ tno ]->s.dirty = 1;
+    text_arr[ tno ]->ptr = alloc_text_stat();
+    ((Text_stat *)text_arr[ tno ]->ptr)->no_of_misc = 0;
+    ((Text_stat *)text_arr[ tno ]->ptr)->misc_items = NULL;
+    ((Text_stat *)text_arr[ tno ]->ptr)->no_of_marks = 0;
+    ((Text_stat *)text_arr[ tno ]->ptr)->no_of_lines = 0;
+    ((Text_stat *)text_arr[ tno ]->ptr)->no_of_chars = 0;
+    fseek(text_file, 0, SEEK_END);
+    ((Text_stat *)text_arr[ tno ]->ptr)->file_pos = ftell(text_file);
+
+    text_set_mru( tno );
+
+    if ( fwrite(message.string, sizeof(char), message.len, text_file)
+	!= message.len ) {
+	log("WARNING: cached_create_text: Couldn't write the text %d\n",
+	    tno);
+    }
+
+    fflush(text_file);
+    
+    TRACE2("cached_create_text -> %d\n", tno);
+    
+    return tno;
+}
+
+
+Text_no
+traverse_text(Text_no seed)
+{
+    seed++;
+    
+    while ( seed < next_text_num
+	   && (text_arr[ seed ] == NULL
+	       || text_arr[ seed ]->s.exists == 0 ) )
+    {
+	seed++;
+    }
+    
+    return (seed >= next_text_num) ? 0 : seed ;
+}
+
+Pers_no
+traverse_person(Pers_no seed)
+{
+    seed++;
+
+    while ( seed < next_free_num
+	   && (pers_arr[ seed ] == NULL
+	       | pers_arr[ seed ]->s.exists == 0 ))
+    {
+	seed++;
+    }
+
+    return (seed >= next_free_num) ? 0 : seed ;
+}
+
+Conf_no
+traverse_conference(Conf_no seed)
+{
+    seed++;
+    
+    while ( seed < next_free_num
+	   && (conf_arr[ seed ] == NULL
+	       || conf_arr[ seed ]->s.exists == 0 ))
+    {
+	seed++;
+    }
+
+    return (seed >= next_free_num) ? 0 : seed ;
+}
+
+extern Garb_nice
+cached_get_garb_nice (Conf_no conf_no)
+{
+    return small_conf_arr [ conf_no ]->nice;
+}
+
+
+extern Local_text_no
+cached_get_highest_local_no (Conf_no conf_no)
+{
+    return small_conf_arr[ conf_no ]->highest_local_no;
+}
+
+/* Lock a person struct in memory. Increase a referenc count. */
+void
+cached_lock_person(Pers_no pers_no)
+{
+    if ( pers_arr[ pers_no ]->s.exists == 0 )
+	restart_kom("cached_lock_person(%d): nonexistent.\n", pers_no);
+
+    if ( pers_arr[ pers_no ]->ptr == NULL )
+    {
+	Person *ptr;
+
+	ptr = cached_get_person_stat( pers_no );
+
+	if ( ptr == NULL )
+	    restart_kom("cached_lock_person(%d): couldn't read in person.\n",
+			pers_no);
+
+	if ( ptr != pers_arr[ pers_no ]->ptr )
+	    restart_kom("cached_lock_person(%d): ptr == %d, pers_arr[pers_no]"
+			"->ptr == %d.\n", pers_no, ptr,
+			pers_arr[ pers_no ]->ptr);
+    }
+
+    pers_arr[ pers_no ]->lock_cnt++;
+}
+
+/* Decrease reference count. If zero, unlock person. */
+void
+cached_unlock_person(Pers_no pers_no)
+{
+    if ( pers_arr[ pers_no ]->lock_cnt <= 0 )
+    {
+	log("cached_unlock_person(%d): lock_cnt == %d.\n",
+	    pers_no, pers_arr[ pers_no ]->lock_cnt);
+
+	pers_arr[ pers_no ]->lock_cnt = 0;
+    }
+    else
+	pers_arr[ pers_no ]->lock_cnt--;
+}
+
+
+/* Lock a conf struct in memory. Increase a referenc count. */
+void
+cached_lock_conf(Conf_no conf_no)
+{
+    if ( conf_arr[ conf_no ]->s.exists == 0 )
+	restart_kom("cached_lock_conf(%d): nonexistent.\n", conf_no);
+
+    if ( conf_arr[ conf_no ]->ptr == NULL )
+    {
+	Conference *ptr;
+
+	ptr = cached_get_conf_stat( conf_no );
+
+	if ( ptr == NULL )
+	    restart_kom("cached_lock_conf(%d): couldn't read in conf.\n",
+			conf_no);
+
+	if ( ptr != conf_arr[ conf_no ]->ptr )
+	    restart_kom("cached_lock_conf(%d): ptr == %d, conf_arr[conf_no]"
+			"->ptr == %d.\n", conf_no, ptr,
+			conf_arr[ conf_no ]->ptr);
+    }
+
+    conf_arr[ conf_no ]->lock_cnt++;
+}
+
+/* Decrease reference count. If zero, unlock conf. */
+void
+cached_unlock_conf(Conf_no conf_no)
+{
+    if ( conf_arr[ conf_no ]->lock_cnt <= 0 )
+    {
+	log("cached_unlock_conf(%d): lock_cnt == %d.\n",
+	    conf_no, conf_arr[ conf_no ]->lock_cnt);
+
+	conf_arr[ conf_no ]->lock_cnt = 0;
+    }
+    else
+	conf_arr[ conf_no ]->lock_cnt--;
+}
+
+
+
+/*
+ * And here comes some functions to deal with lookup_names.
+ */
+
+/* Free the _contents_ of a match_table. The table itself i _not_ freed. */
+
+static void
+free_match_table(Matching_info *match)
+{
+    if ( match == NULL )
+	return;
+    
+    while ( ! s_empty( match->name ))
+    {
+	free_tokens( match->tokens );
+	/* match->name is not freed since it points into conf_arr[]. */
+
+	++match;
+    }
+}    
+
+extern Success
+build_matching_info(void)
+{
+    Conf_no i;
+    Matching_info *match;
+    Conf_no	  *conf;
+
+    free_match_table(match_table);
+    
+    match_table = srealloc(match_table, next_free_num * sizeof(Matching_info));
+    conf_table = srealloc(conf_table, next_free_num * sizeof(Conf_no));
+
+    no_of_match_info = 0;
+
+    match = match_table;
+    conf = conf_table;
+    
+    for ( i = 1; i < next_free_num; i++ )
+    {
+	if ( small_conf_arr[ i ] != NULL
+	    && ! s_empty ( small_conf_arr[ i ]->name ) )
+	{
+	    match->name = small_conf_arr[ i ]->name;
+	    match->tokens = tokenize(match->name, s_fcrea_str(" \t"));
+	    match->priority = 7;
+	    *conf = i;
+	    ++match;
+	    ++conf;
+	    ++no_of_match_info;
+	}
+    }
+
+    match->name = EMPTY_STRING;
+    match->tokens = NULL;
+    match->priority = 0;
+    *conf = 0;
+    
+    return OK;
+}
+
+
+
+/* Map conference name to number */
+extern Success
+cached_lookup_name(const String name,
+		   Conf_list_old *result)
+{
+    Parse_info tmp;
+    int i;
+    
+    tmp = parse(name, match_table, FALSE, FALSE,
+		s_fcrea_str(" \t"), DEFAULT_COLLAT_TAB);
+
+    if ( tmp.no_of_matches == -1 )
+	return FAILURE;
+    
+    if ( tmp.no_of_matches == 1 && tmp.indexes[ 0 ] == -1 )
+    {
+	/* Return the entire list. */
+	result->no_of_conf_nos = no_of_match_info;
+	result->conf_nos = tmp_alloc(no_of_match_info * sizeof(Conf_no));
+	result->type_of_conf = tmp_alloc(no_of_match_info * sizeof(Conf_type));
+
+	for ( i = 0; i < no_of_match_info; i++ )
+	{
+	    result->conf_nos[ i ] = conf_table[ i ];
+	    result->type_of_conf[ i ] = small_conf_arr[ conf_table[i] ]->type;
+	}
+    }
+    else
+    {
+	/* Return the conferences whose conf_nos are in indexes[]. */
+	result->no_of_conf_nos = tmp.no_of_matches;
+	result->conf_nos = tmp_alloc(tmp.no_of_matches * sizeof(Conf_no));
+	result->type_of_conf = tmp_alloc(tmp.no_of_matches
+					 * sizeof(Conf_type));
+
+	for ( i = 0; i < tmp.no_of_matches; i++ )
+	{
+	    result->conf_nos[ i ] = conf_table[ tmp.indexes[ i ] ];
+	    result->type_of_conf[ i ]
+		= small_conf_arr[ conf_table[ tmp.indexes[ i ] ] ]->type;
+	}
+    }		
+    sfree(tmp.indexes);
+    return OK;
+}
+
+
+static Bool
+is_clean(const char *fn)
+{
+    FILE *fp;
+
+    if ( (fp = fopen(fn, "rb")) == NULL )
+	return FALSE;
+
+    if ( getc(fp) == 'C' && getc(fp) == 'L' && getc(fp) == 'E'
+	&& getc(fp) == 'A' && getc(fp) == 'N' )
+    {
+	fclose(fp);
+	return TRUE;
+    }
+    else
+    {
+	fclose(fp);
+	return FALSE;
+    }
+}
+
+
+extern void
+pre_sync(void)
+{
+    long i;
+    Cache_node *node;
+
+    async_sync_db();
+
+    /* Mark up what to save.*/
+
+    BUG(("Sync starting\n"));
+
+    highest_text_no = next_text_num;
+    highest_conf_no = next_free_num;
+
+    for ( i = 1; i < highest_conf_no; i++ )
+    {
+	node = conf_arr[i];
+
+	if ( node != NULL )
+	{
+	    node->s.exists_b = node->s.exists;
+
+	    if ( node->s.dirty != 0 && node->s.exists != 0)
+	    {
+		free_conference(node->snap_shot);
+
+		if ( node->lock_cnt == 0 )
+		{
+		    unlink_lru(node, &conf_lru, &conf_mru);
+		    node->snap_shot = node->ptr;
+		    node->ptr = NULL;
+		}
+		else
+		{
+		    node->snap_shot = copy_conf(node->ptr);
+		}
+		
+		node->s.dirty = 0;
+	    }
+	}
+    }
+
+    for ( i = 1; i < highest_conf_no; i++ )
+    {
+	node = pers_arr[i];
+
+	if ( node != NULL )
+	{
+	    node->s.exists_b = node->s.exists;
+
+	    if ( node->s.dirty != 0 && node->s.exists != 0)
+	    {
+		free_person(node->snap_shot);
+
+		if ( node->lock_cnt == 0 )
+		{
+		    unlink_lru(node, &pers_lru, &pers_mru);
+		    node->snap_shot = node->ptr;
+		    node->ptr = NULL;
+		}
+		else
+		{
+		    node->snap_shot
+			= copy_person(node->ptr);
+		}
+		
+		node->s.dirty = 0;
+	    }
+	}
+    }
+
+    for ( i = 1; i < highest_text_no; i++ )
+    {
+	node = text_arr[i];
+
+	if ( node != NULL )
+	{
+	    node->s.exists_b = node->s.exists;
+
+	    if ( node->s.dirty != 0 && node->s.exists != 0)
+	    {
+		free_text_stat(node->snap_shot);
+
+		if ( node->lock_cnt == 0 )
+		{
+		    unlink_lru(node, &text_lru, &text_mru);
+		    node->snap_shot = node->ptr;
+		    node->ptr = NULL;
+		}
+		else
+		{
+		    node->snap_shot
+			= copy_text_stat(node->ptr);
+		}
+		
+		node->s.dirty = 0;
+	    }
+	}
+    }
+
+    /* All marking is done. Now open file B. */
+    
+    if ( is_clean(datafilename) )
+    {
+	if ( rename(datafilename, backupfilename) != 0 )
+	    restart_kom("pre_sync: can't backup.\n");
+    }
+    else
+	log("pre_sync: datafile not clean. No backup taken.\n");
+    
+
+    if ( (file_b=fopen(datafilename, "wb") ) == NULL )
+    {
+	log("WARNING: pre_sync: can't open file to save in.\n");
+	sync_state = sync_wait;
+
+	return;
+    }
+
+    fprintf(file_b, "DIRTY\n");		 /* DIRTY-FLAG */
+
+    fprintf(file_b, "%d\n", highest_conf_no);	  /* NEXT_FREE_NUM */
+    sync_state = sync_save_conf;
+    sync_next = 1;
+}
+
+static void
+copy_file(FILE *from,
+	  FILE *to,
+	  long from_pos,
+	  long len)
+{
+    char *buf;
+    long result;
+
+    buf = smalloc(len);
+    if ( fseek(from, from_pos, SEEK_SET) == -1 )
+    {
+	sync_state = sync_error;
+	log("sync: copy_file(): fseek failed.\n");
+	return;
+    }
+    
+    if ( (result = fread(buf, 1, len, from)) != len )
+    {
+	log("sync: copy_file(): fread failed.\n"
+	    "from_pos = %d, len = %d, result = %d\n",
+	    from_pos, len, result);
+
+	sync_state = sync_error;
+	return;
+    }
+
+    if ( fseek(to, 0, SEEK_END) == -1 )
+    {
+	sync_state = sync_error;
+	log("sync: copy_file(): second fseek failed.\n");
+	return;
+    }
+
+    if ( fwrite(buf, 1, len, to) != len )
+    {
+	sync_state = sync_error;
+	log("sync: copy_file(): fwrite failed.\n");
+	return;
+    }
+
+    sfree(buf);
+}    
+
+static void
+save_one_conf(void)
+{
+    Cache_node *cn;
+    
+    if (sync_next < highest_conf_no)
+    {
+	cn = conf_arr[ sync_next ];
+
+	if ( cn == NULL )
+	{
+	    putc('@', file_b);
+	    putc('\n', file_b);
+	}
+	else 
+	{
+	    cn->pos_b = ftell(file_b);
+
+	    if ( cn->s.exists_b == 0 )
+		putc('@', file_b);
+	    else if ( cn->snap_shot != NULL )
+	    {
+		fprintf(file_b, "+ ");
+		foutput_conference(file_b, cn->snap_shot);
+	    }
+	    else if ( cn->s.dirty == 0 && cn->ptr != NULL )
+	    {
+		fprintf(file_b, "+ ");
+		foutput_conference(file_b, cn->ptr);
+	    }
+	    else
+	    {
+		copy_file(file_a, file_b, cn->pos, cn->size - 1);
+	    }
+
+	    putc('\n', file_b);
+	    cn->size_b = ftell(file_b) - cn->pos_b;
+	}
+	sync_next++;
+    }
+    else			/* All conferences are written. */
+    {
+	sync_next = 1;
+	sync_state = sync_save_pers;
+    }
+}
+
+static void
+write_pers(FILE *fp,
+	   Person *p)
+{
+    fprintf(fp, "+ %dH", PASSWD_LEN);
+    fwrite(p->pwd, PASSWD_LEN, 1, fp);
+    foutput_person(fp, p);
+}
+
+static void
+save_one_pers(void)
+{
+    Cache_node *cn;
+    
+    if (sync_next < highest_conf_no)
+    {
+	cn = pers_arr[ sync_next ];
+
+	if ( cn == NULL )
+	{
+	    putc('@', file_b);
+	    putc('\n', file_b);
+	}
+	else 
+	{
+	    cn->pos_b = ftell(file_b);
+
+	    if ( cn->s.exists_b == 0 )
+		putc('@', file_b);
+	    else if ( cn->snap_shot != NULL )
+	    {
+		write_pers(file_b, cn->snap_shot);
+	    }
+	    else if ( cn->s.dirty == 0 && cn->ptr != NULL )
+	    {
+		write_pers(file_b, cn->ptr);
+	    }
+	    else
+	    {
+		copy_file(file_a, file_b, cn->pos, cn->size - 1);
+	    }
+
+	    putc('\n', file_b);
+	    cn->size_b = ftell(file_b) - cn->pos_b;
+	}
+	sync_next++;
+    }
+    else			/* All persons are written. */
+    {
+	fprintf(file_b, "%d\n", highest_text_no);	/* NEXT_TEXT_NUM */
+	sync_next = 1;
+	sync_state = sync_save_text;
+    }
+}
+
+static void
+post_sync(void)
+{
+    int i;
+    Cache_node *node;
+
+    async_sync_db();
+
+    if ( file_a == NULL )
+	log("WARNING: post_sync(): file_a == NULL. This is only normal "
+	    "if this is the first sync ever on this data file.\n");
+    else
+	fclose(file_a);
+
+    if ( ( file_a = fopen(datafilename, "rb") ) == NULL )
+    {
+	log("post_sync: can't open the file I just saved.\n");
+	sync_state = sync_wait;
+	return;
+    }
+
+    for ( i = 1; i < highest_conf_no; i++ )
+    {
+	node = conf_arr[i];
+	if ( node != NULL )
+	{
+	    node->pos = node->pos_b;
+	    node->size = node->size_b;
+	    free_conference(node->snap_shot);
+	    node->snap_shot = NULL;
+	}
+    }
+
+    for ( i = 1; i < highest_conf_no; i++ )
+    {
+	node = pers_arr[i];
+	if ( node != NULL )
+	{
+	    node->pos = node->pos_b;
+	    node->size = node->size_b;
+	    free_person(node->snap_shot);
+	    node->snap_shot = NULL;
+	}
+    }
+
+    for ( i = 1; i < highest_text_no; i++ )
+    {
+	node = text_arr[i];
+	if ( node != NULL )
+	{
+	    node->pos = node->pos_b;
+	    node->size = node->size_b;
+	    free_text_stat(node->snap_shot);
+	    node->snap_shot = NULL;
+	}
+    }
+}
+
+    
+
+static void
+save_one_text(void)
+{
+    Cache_node *cn;
+    
+    if (sync_next < highest_text_no)
+    {
+	cn = text_arr[ sync_next ];
+
+	if ( cn == NULL )
+	{
+	    putc('@', file_b);
+	    putc('\n', file_b);
+	}
+	else 
+	{
+	    cn->pos_b = ftell(file_b);
+
+	    if ( cn->s.exists_b == 0 )
+		putc('@', file_b);
+	    else if ( cn->snap_shot != NULL )
+	    {
+		fprintf(file_b, "+ ");
+		foutput_text_stat(file_b, cn->snap_shot);
+	    }
+	    else if ( cn->s.dirty == 0 && cn->ptr != NULL )
+	    {
+		fprintf(file_b, "+ ");
+		foutput_text_stat(file_b, cn->ptr);
+	    }
+	    else
+	    {
+		copy_file(file_a, file_b, cn->pos, cn->size - 1);
+	    }
+
+	    putc('\n', file_b);
+	    cn->size_b = ftell(file_b) - cn->pos_b;
+	}
+	sync_next++;
+    }
+    else			/* All texts are written. */
+    {
+	if ( ferror(file_b) != 0 )
+	{
+	    sync_state = sync_error;
+	    return;
+	}
+	
+	rewind(file_b);
+	if ( ferror(file_b)  != 0 )
+	{
+	    sync_state = sync_error;
+	    return;
+	}
+	
+	fprintf(file_b, "CLEAN");
+	if ( ferror(file_b)  != 0 )
+	{
+	    sync_state = sync_error;
+	    return;
+	}
+	
+	if ( fclose(file_b) != 0 )
+	{
+	    file_b = NULL;
+
+	    log("Sync: fclose() failed in save_one_text. Deleting "
+		"file and retrying.\n");
+	    remove(datafilename);
+	    sync_state = sync_wait;
+	    return;
+	}
+
+	sync_state = sync_ready;
+
+	BUG(("Sync ready\n"));
+	post_sync();
+    }
+}
+
+/*
+ * Sync_part() should be called often as long as it returns FALSE.
+ * Sync_part() returns TRUE when everything is written to a file.
+ * Sync_part() should be called once in a while even when it returns
+ * TRUE (because sync() checks to see if it is time to start yet
+ * another save.)
+ */
+EXPORT Bool
+sync_part(void)
+{
+    static time_t last_sync_start = NO_TIME;
+
+    if ( last_sync_start == NO_TIME )
+    {
+	last_sync_start = time(NULL);
+	sync_state = sync_ready;
+    }
+
+    switch(sync_state)
+    {
+    case sync_save_conf:
+	save_one_conf();
+	break;
+
+    case sync_save_pers:
+	save_one_pers();
+	break;
+
+    case sync_save_text:
+	save_one_text();
+	break;
+
+    case sync_ready:
+	if ( difftime(time(NULL), last_sync_start) < 60 * SYNC_INTERVAL )
+	    return TRUE;
+
+	last_sync_start = time(NULL);
+    
+	pre_sync();
+	break;
+
+    case sync_wait:
+	if ( difftime(time(NULL), last_sync_start) < 60 * SYNC_RETRY_INTERVAL )
+	    return TRUE;
+
+	last_sync_start = time(NULL);
+
+	pre_sync();
+	break;
+
+    case sync_error:
+	log("sync: Error saving new file. Discarding file and "
+	    "trying again.\n");
+	fclose(file_b);
+	file_b = NULL;
+	remove(datafilename);
+	sync_state = sync_wait;
+	break;
+
+    default:
+	restart_kom("sync(): sync_state==%d", sync_state);
+    }
+
+    if ( ferror(file_b) != 0)
+	sync_state = sync_error;
+
+    return FALSE;
+}
+
+static Small_conf *
+alloc_small_conf(void)
+{
+    Small_conf *s;
+    s = smalloc(sizeof(Small_conf));
+    *s = EMPTY_SMALL_CONF;
+
+    return s;
+}
+
+static void
+setup_small_conf(Conf_no conf_no,
+		 Conference *conf_c)
+{
+    small_conf_arr[ conf_no ] = alloc_small_conf();
+    s_strcpy(&small_conf_arr[ conf_no ]->name, conf_c->name);
+    small_conf_arr[ conf_no ]->type = conf_c->type;
+    small_conf_arr[ conf_no ]->highest_local_no
+	= (conf_c->texts.first_local_no - 1
+	   + conf_c->texts.no_of_texts );
+    small_conf_arr[ conf_no ]->nice = conf_c->nice;
+}
+
+
+    
+extern Success
+init_cache(void)
+{
+    int i;
+    Cache_node *node;
+    Conference tmp_conf = EMPTY_CONFERENCE;
+    Person tmp_pers = EMPTY_PERSON;
+    Text_stat tmp_text = EMPTY_TEXT_STAT;
+
+    if ( (text_file = fopen(textfilename, "a+b")) == NULL )
+    {
+	restart_kom("ERROR: init_cache: can't open text file.\n");
+    }
+
+    if ( is_clean(datafilename) )
+    {
+	if ( (file_a = fopen(datafilename, "rb")) == NULL )
+	{
+	    log("WARNING: init_cache: can't open datafile.\n");
+	    return FAILURE;
+	}
+	log("MSG: init_cache: using datafile.\n");
+    }
+    else if ( is_clean(backupfilename) )
+    {
+	if ( (file_a = fopen(backupfilename, "rb")) == NULL )
+	{
+	    log("WARNING: init_cache: can't open backupfile.\n");
+	    return FAILURE;
+	}
+	log("MSG: init_cache: using backup file.\n");
+    }
+    else
+    {
+	log("WARNING: init_cache: can't find old data base.\n");
+	return FAILURE;
+    }
+        
+    fseek(file_a, 6, SEEK_SET);	/* skip clean/dirty flag. */
+
+    next_free_num = fparse_long(file_a);	  /* NEXT_FREE_NUM */
+
+    for ( i = 1; i < next_free_num; i++ ) /* CONFS */
+    {
+	fskipwhite(file_a);
+	switch(getc(file_a))
+	{
+	case '@':
+	    conf_arr[ i ] = NULL;
+	    break;
+
+	case '+':
+	    node = conf_arr[ i ] = alloc_cache_node();
+	    node->s.exists = 1;
+
+	    node->pos = ftell(file_a) - 1; /* Don't forget the '+' */
+
+	    if ( fparse_conference(file_a, &tmp_conf) != OK )
+		restart_kom("init_cache(): fparse_conference failed. "
+			    "i == %d\n", i);
+
+	    node->size = ftell(file_a) - node->pos;
+	    setup_small_conf(i, &tmp_conf);
+	    clear_conference(&tmp_conf);
+	    
+	    break;
+	}
+    }
+
+    build_matching_info();
+    
+    for ( i = 1; i < next_free_num; i++ ) /* PERSONS */
+    {
+	fskipwhite(file_a);
+	switch(getc(file_a))
+	{
+	case '@':
+	    pers_arr[ i ] = NULL;
+	    break;
+
+	case '+':
+	    node = pers_arr[ i ] = alloc_cache_node();
+	    node->s.exists = 1;
+	    node->pos = ftell(file_a) - 1; /* Don't forget the '+' */
+	    if ( fparse_person(file_a, &tmp_pers) != OK )
+		restart_kom("init_cache: fparse_person failed. i == %d\n", i);
+	    
+	    node->size = ftell(file_a) - node->pos;
+	    clear_person(&tmp_pers);
+
+	    break;
+	}
+    }
+
+    next_text_num = fparse_long(file_a);	/* NEXT_TEXT_NUM */
+
+    for ( i = 1; i < next_text_num; i++ ) /* TEXT_STATS */
+    {
+	fskipwhite(file_a);
+	switch(getc(file_a))
+	{
+	case '@':
+	    text_arr[ i ] = NULL;
+	    break;
+
+	case '+':
+	    node = text_arr[ i ] = alloc_cache_node();
+	    node->s.exists = 1;
+
+	    node->pos = ftell(file_a) - 1; /* Don't forget the '+' */
+
+	    if ( fparse_text_stat(file_a, &tmp_text) != OK )
+		restart_kom("init_cache(): fparse_text_stat failed. i == %d\n",
+			    i);
+
+	    node->size = ftell(file_a) - node->pos;
+	    clear_text_stat(&tmp_text);
+
+	    break;
+	}
+    }
+
+    log("Read %d confs/persons and %d texts\n",
+	next_free_num, next_text_num);
+
+    return OK;
+}
+
+/* ramkomd compatibility: */
+extern void
+cache_sync(void)
+{
+}
+
+extern void
+cache_sync_all(void)
+{
+    pre_sync();
+    while ( sync_part() != TRUE )
+	;
+}
diff --git a/src/server/text-garb.c b/src/server/text-garb.c
new file mode 100644
index 000000000..113ed41af
--- /dev/null
+++ b/src/server/text-garb.c
@@ -0,0 +1,125 @@
+/*
+ * This file contains the functions that deletes old texts.
+ *
+ * Author: Per Cederqvist.
+ */
+
+#include <time.h>
+#include <kom-types.h>
+#include "cache.h"
+#include "log.h"
+#include "internal-services.h"
+#include "lyskomd.h"
+#include <debug.h>
+
+
+/*
+ * Delete old texts.
+ *
+ * Return value is TRUE if there is nothing more to do right now,
+ * FALSE if this function should be called again as soon as the
+ * server has some time over.
+ */
+
+Bool
+garb_text(void)
+{
+    static Text_no last_checked = 0;
+    static time_t  last_start = NO_TIME;
+    static long	   deleted_texts = 0;
+    BUGDECL;
+
+    Text_stat	  *text_s;
+    Misc_info 	  *misc;
+    u_short 	   nmisc;
+    double	   age;			/* How many seconds is this text? */
+    double	   limit = 24 * 3600;
+                    
+    if ( last_checked == 0 )
+    {
+	if ( last_start != NO_TIME
+	    && difftime(time(NULL), last_start) < GARB_INTERVAL * 60 )
+	{
+	    return TRUE;
+	}
+	
+	log("MSG: garb started.\n");
+	time( &last_start );
+    }
+    
+    last_checked = traverse_text( last_checked );
+
+    if ( last_checked == 0 )
+    {
+	log("MSG: garb ready. %lu texts deleted.\n", (u_long)deleted_texts);
+	return FALSE;
+    }
+    
+    if ( (text_s = cached_get_text_stat( last_checked )) == NULL )
+    {
+	log("ERROR: garb_text(): Can't get text-stat.\n");
+	return FALSE;
+    }
+
+    age = difftime( time(NULL), text_s->creation_time );
+    
+    if ( text_s->no_of_marks > 0 || age < 24 * 3600 )
+    {
+	return FALSE;
+    }
+
+    for (nmisc = text_s->no_of_misc, misc = text_s->misc_items;
+	 nmisc > 0;
+	 --nmisc, ++misc )
+    {
+	switch ( misc->type )
+	{
+	case recpt:
+	    limit = 24 * 3600 * cached_get_garb_nice( misc->datum.recipient );
+	    if ( age < limit )
+		return FALSE;
+		
+	    break;
+
+	case cc_recpt:
+	    limit = ( 24 * 3600
+		     * cached_get_garb_nice( misc->datum.cc_recipient ));
+	    if ( age < limit )
+		return FALSE;
+
+	    break;
+
+	case comm_to:
+	case comm_in:
+	case footn_to:
+	case footn_in:
+	    limit = 24 * 3600;
+	    break;
+	    
+	case loc_no:
+	case rec_time:
+	case sent_by:
+	    break;
+	    
+	case sent_at:
+	    if ( difftime ( time(NULL), misc->datum.sent_at )
+		< limit )
+	    {
+		return FALSE;
+	    }
+	    		
+	    break;
+
+#ifndef COMPILE_CHECKS
+	default:
+	    restart_kom("garb_text(): Illegal misc-item.\n");
+#endif
+	}
+    }
+
+    VBUG(("garb_text: deleting %d\n", last_checked));
+    do_delete_text ( last_checked, text_s );
+    deleted_texts++;
+    return FALSE;
+}
+    
diff --git a/src/server/text-garb.h b/src/server/text-garb.h
new file mode 100644
index 000000000..a2a9d880d
--- /dev/null
+++ b/src/server/text-garb.h
@@ -0,0 +1,3 @@
+extern Bool
+garb_text(void);
+
diff --git a/src/server/text.c b/src/server/text.c
new file mode 100644
index 000000000..e8f519f9f
--- /dev/null
+++ b/src/server/text.c
@@ -0,0 +1,2103 @@
+/*
+ * text.c
+ *
+ * All atomic calls that deals with texts.
+ */
+#include <time.h>
+#include <stdlib.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include <services.h>
+#include "manipulate.h"
+#include <kom-errno.h>
+#include "smalloc.h"
+#include "cache.h"
+#include "log.h"
+#include "minmax.h"
+#include <config.h>
+#include "com.h"
+#include "isc.h"
+#include "connections.h"
+#include "send-async.h"
+#include "admin.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 < MAX_SUPER_CONF_LOOP; i++)
+    {
+	acc = access_perm( conf_no, conf_c );
+	
+	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(__FILE__ ": do_sub_recpt(): bad misc_item.\n");
+	    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 )
+	GET_T_STAT(text_s, comment, (void)0);
+
+    if ( parent_s == NULL )
+	GET_T_STAT(parent_s, comment_to, (void)0);
+    
+    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 )
+	GET_T_STAT(text_s, footnote, (void)0);
+
+    if ( parent_s == NULL )
+	GET_T_STAT(parent_s, footnote_to, (void)0);
+
+    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:
+	    return 0;		/* No sender. */
+
+	case loc_no:
+	case rec_time:
+	    break;		/* These may come before a sent_by. */
+
+	case sent_at:
+	    log("ERROR: sender(): Misc_item out of order.\n");
+	    return 0;
+
+#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;
+}
+
+#if 0
+/*
+ * Check if ACTPERS has sent this text as a footnote to parent.
+ */
+static Bool
+is_footn_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 footn_to:
+	    if ( text_s->misc_items[ i ].datum.footnote_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 comm_to:
+	case footn_in:
+	case sent_by:
+	case sent_at:
+	    break;
+
+#ifndef COMPILE_CHECKS
+	default:
+	    restart_kom("is_footn_sender(): Illegal misc_item found.\n");
+#endif
+	}
+    }
+
+    return FALSE;
+}
+#endif
+
+    
+/*
+ * 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 ) >= 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 ) >= 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))
+	    {
+		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))
+	    {
+		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: " __FILE__ ": do_delete_text(): 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;
+    }
+
+    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;
+}
+
+/*
+ * 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;
+    Misc_info	* orig;
+    Misc_info	* copy;		/* Censored Misc_infos */
+
+    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;
+    }
+
+    *result = *text_stat;
+    
+    /* Delete secret confs */
+
+    /* ^^^ Possible optimisation:
+       No need to copy unless there is a secret conf among the recipients. */
+
+    result->misc_items = tmp_alloc(result->no_of_misc * sizeof ( Misc_info));
+    result->no_of_misc = 0;
+    copy = result->misc_items;
+    orig = text_stat->misc_items;    
+    
+    while ( orig < text_stat->misc_items + text_stat->no_of_misc )
+    {
+	switch( orig->type )
+	{
+	case recpt:
+	    if ( fast_access_perm( orig->datum.recipient ) <= none 
+		&& !ENA(admin, 4))
+	    {
+		skip_recp ( &orig, text_stat );
+	    }
+	    else
+	    {
+		*copy++ = *orig++;
+		++result->no_of_misc;
+	    }
+	    break;
+
+	case cc_recpt:
+	    if ( fast_access_perm( orig->datum.cc_recipient ) <= none
+		&& !ENA(admin, 4))
+	    {
+		skip_recp ( &orig, text_stat );
+	    }
+	    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("get_text_stat() - illegal misc_item!\n");    
+#endif
+	}
+    }
+    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(__FILE__ "check_double_subm(): bad misc_item.\n");
+	    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;
+}
+
+/*
+ * 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(String	  message,
+	    u_short	  no_of_misc,
+	    Misc_info   * misc )
+{
+    Text_no text;
+    Text_stat * t_stat;
+    extern int errno;
+
+    CHK_LOGIN(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("create_text: can't get text-stat of "
+		    "newly created text.\n"
+		    "Text == %d, kom_errno == %d, errno == %d\n",
+		    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 );
+
+    async_new_text( text, t_stat );	/* Send asynchronous message. */
+
+    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) && !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);
+}
+
+/*
+ * 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 ) >= 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)
+	&& !is_supervisor(conf_no, conf_c)
+	&& !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 *text_s, *parent_s;
+
+    CHK_LOGIN(FAILURE);
+    GET_T_STAT(text_s, comment, FAILURE);
+    GET_T_STAT(parent_s, comment_to, FAILURE);
+    
+    if ( check_comm(parent_s) != OK )
+	return FAILURE;
+
+    if ( !text_read_access(comment, text_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 ( text_s->author != ACTPERS )
+	ADD_MISC(text_s, sent_by, sender, ACTPERS);
+
+    ADD_MISC(text_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)
+	&& !is_supervisor( parent_s->author, NULL )
+	&& !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);
+    
+    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);
+
+    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;
+}
-- 
GitLab