diff --git a/src/server/admin.c b/src/server/admin.c
new file mode 100644
index 0000000000000000000000000000000000000000..287149dc7e9b924c5b9dbf355dcb2dfbacd174b2
--- /dev/null
+++ b/src/server/admin.c
@@ -0,0 +1,157 @@
+/*
+ * admin.c
+ *
+ * Administrative calls.
+ */
+#include <stdlib.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include <services.h>
+#include "manipulate.h"
+#include "smalloc.h"
+#include "cache.h"
+#include "log.h"
+#include "admin.h"
+#include <kom-errno.h>
+#include "com.h"
+#include "connections.h"
+#include "send-async.h"
+#include "config.h"
+
+Info kom_info = 
+{
+#include "version.incl"
+    ,				/* version */
+    1,				/* conf_pres_conf */
+    2,				/* pers_pres_conf */
+    3,				/* motd_conf */
+    4,				/* kom_news_conf */
+    0				/* motd_of_lyskom */
+};
+
+/*
+ * Return info about this server. This can (and should) be done
+ * before logging in. modt_of_lyskom should be displayed before
+ * prompting for username if it isn't 0.
+ */
+extern Success
+get_info( Info *result )
+{
+    *result = kom_info;
+    return OK;
+}
+
+/* /// */
+extern Success
+set_motd_of_lyskom (Text_no motd)
+{
+    Text_stat *old_motd = NULL;
+    Text_stat *new_motd = NULL;
+
+    CHK_LOGIN(FAILURE);
+
+    if ( !ENA(admin, 1) )
+    {
+	kom_errno = KOM_PERM;
+	return FAILURE;
+    }
+    
+    /* Check that the new motd exists before deleting the old*/
+
+    if ( motd != 0 )
+    {
+	GET_T_STAT(new_motd, motd, FAILURE);
+	if ( new_motd->no_of_marks >= MAX_MARKS_TEXT )
+	{
+	    log(
+	"LIMIT: set_motd_of_lyskom(): New motd very marked.\n");
+	    return FAILURE;
+	}
+    }
+    
+    /* Unmark the previous motd if it exists. */
+
+    if ( kom_info.motd_of_lyskom != 0
+	&& (old_motd = cached_get_text_stat(kom_info.motd_of_lyskom)) != NULL)
+    {
+	if ( old_motd->no_of_marks > 0 )
+	{
+	    --old_motd->no_of_marks;
+	    mark_text_as_changed( kom_info.motd_of_lyskom );
+	}
+	else
+	{
+	    log("ERROR: do_set_motd(): Old motd not marked\n");
+	}
+    }
+
+    /* Mark the new motd */
+
+    if ( motd != 0 )
+    {
+	++new_motd->no_of_marks;
+	mark_text_as_changed( motd );
+    }
+    
+    kom_info.motd_of_lyskom = motd;
+
+    return OK;
+}
+
+
+
+
+/*
+ * Force all clients to read a message.
+ * Sends an asynchronous message. This is obsoleted by send_message.
+ */
+extern Success
+broadcast (String message)
+{
+    CHK_LOGIN(FAILURE);
+
+    async_broadcast(ACTPERS, message);
+    return OK;
+}
+
+/*
+ * Send a message
+ */
+extern Success
+send_message (Pers_no recipient,
+	      const String message)
+{
+    CHK_LOGIN(FAILURE);
+
+    return async_send_message(recipient, ACTPERS, message);
+}
+
+
+
+/*
+ * Make LysKOM sync its files.
+ */
+extern Success
+sync (void) 
+{
+    cache_sync_all();
+    dump_statistics();
+    return OK;
+}
+
+/*
+ * Close LysKOM. exit_val is (currently) not used. The database is synced.
+ */
+extern Success
+shutdown (int exit_val) 
+{
+    CHK_LOGIN(FAILURE);
+    if ( !ENA(admin, 1) )
+    {
+	kom_errno = KOM_PERM;
+	return FAILURE;
+    }
+
+    go_and_die = TRUE;
+    return OK;
+}
diff --git a/src/server/admin.h b/src/server/admin.h
new file mode 100644
index 0000000000000000000000000000000000000000..ba4d0cd1a6bdc0cc6a3da1b4258d1bee209a07b4
--- /dev/null
+++ b/src/server/admin.h
@@ -0,0 +1,4 @@
+/*
+ * admin.h
+ */
+extern Info kom_info;
diff --git a/src/server/cache.h b/src/server/cache.h
new file mode 100644
index 0000000000000000000000000000000000000000..2e3f9c175bd98e08902c0c0d5346ab659adf8706
--- /dev/null
+++ b/src/server/cache.h
@@ -0,0 +1,212 @@
+/*
+ * This file contains the cached data that the server stores.
+ * .h file created by ceder 1990-04-18
+ */
+
+/* cache_sync_all only returns when everything is written to disk. */
+extern void
+cache_sync_all(void);
+
+
+/* First, some things which I want here. /ceder */
+
+extern struct matching_info *match_table;
+
+extern void
+cached_lock_conf(Conf_no conf_no);
+
+extern Success
+init_cache(void);
+
+extern void
+cached_unlock_conf(Conf_no conf);
+
+extern void
+cached_lock_person(Pers_no pers);
+
+extern void
+cached_unlock_person(Pers_no pers);
+
+extern Success
+cached_delete_person(Pers_no pres);
+
+extern Success
+cached_delete_text(Text_no text);
+
+extern Success
+build_matching_info(void);
+
+extern void			/* Write out everything. */
+cache_sync(void);
+
+/* S}d{r ja. Tack f|r mig. /ceder */
+
+#define	MAX_CACHED_PERSONS	5	/* To be increased after debugging */
+#define MAX_CACHED_WHATEVER		/* To be added	*/
+
+
+/*
+ * 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.
+ *
+ * cached_change_name(foo, EMPTY_STRING); should be used when a conference
+ * is deleted. (Eller ska det vara annorlunda? S} g|r jag nu... /ceder)
+ */
+extern void
+cached_change_name(Conf_no name_num,
+		   String  new_name );
+
+
+/* add_name adds a name to the list of cached conference names. It is only
+ * used when a new conference is created.
+ */
+extern void
+cached_add_name( char * new_name );
+
+/* load all the conference names from disk and store them in memory */
+extern void
+cached_load_names( void );
+
+#if 0
+/* get one name (I want this call! /ceder) */
+/* I no longer want it. */
+extern String
+cached_get_name( Conf_no conf_no );	/* Returns NULL if the conference */
+					/* doesn't exist. */
+#endif
+
+extern Bool
+cached_conf_exists( Conf_no conf_no );
+
+/*
+ * Calls for the cacheing of conf_type:
+ * cached_get_conf_type() returns the type, and
+ * cached_set_conf_type() sets the type.
+ */
+
+/* Get conference type */
+/* Undefined result if the conf doesn't exist. */
+
+extern Conf_type
+cached_get_conf_type (Conf_no conf_no);
+
+/*
+ * Set the type. This call is used whenever a conference is created or the
+ * conf_type is changed.
+ */
+
+extern void
+cached_set_conf_type (Conf_no   conf_no,
+		      Conf_type type);
+
+/*
+ * Get/set garb_nice for a certain conference.
+ */
+extern Garb_nice
+cached_get_garb_nice (Conf_no conf_no);
+
+extern void
+cached_set_garb_nice (Conf_no    conf_no,
+		      Garb_nice  nice);
+
+extern Local_text_no
+cached_get_highest_local_no (Conf_no conf_no);
+
+
+
+/*
+ * Person caching routines
+ */
+#if 0
+extern Person *
+cached_read_person( Pers_no pers_no );
+#endif
+/*
+ * Various function calls to tell the cache that something is changed.
+ */
+
+void
+mark_person_as_changed(Pers_no	pers_no);
+
+
+void
+mark_conference_as_changed(Conf_no	conf_no);
+
+
+void
+mark_text_as_changed(Text_no	text_no);
+
+
+
+
+/*
+ * Person-related calls
+ */
+
+extern Success
+cached_create_person( Pers_no person );
+
+
+
+extern Person *
+cached_get_person_stat( Pers_no	person );	/* Returns NULL if person
+						   doesn't exist */
+
+
+
+/*
+ * Conference-related calls
+ */
+extern Conf_no
+cached_create_conf( String  name );
+
+
+extern Success
+cached_delete_conf( Conf_no	conf );
+
+extern Success
+cached_lookup_name(const String   name,
+		   Conf_list_old *result);
+
+extern Conference *
+cached_get_conf_stat(	Conf_no		conf_no );
+
+
+/*
+ * Calls to handle texts
+ */
+
+extern String
+cached_get_text( Text_no text );/* The string should be free'd by the caller */
+
+extern Text_stat *
+cached_get_text_stat(	Text_no		text );
+
+
+extern Text_no
+cached_create_text( String message);
+
+/*
+ * traverse_text can be used to cycle through all existing texts. 0 will
+ * be returned once in the "cycle".
+ */
+extern Text_no
+traverse_text(Text_no seed);
+
+/*
+ * traverse_person can be used to cycle through all existing persons. 0 will
+ * be returned once in the "cycle".
+ */
+extern Pers_no
+traverse_person(Pers_no seed);
+
+/*
+ * traverse_conference can be used to cycle through all existing conferences.
+ * 0 will be returned once in the "cycle".
+ */
+extern Conf_no
+traverse_conference(Conf_no seed);
diff --git a/src/server/conference.c b/src/server/conference.c
new file mode 100644
index 0000000000000000000000000000000000000000..2bc4985d9d9eb7c7b1eec3e550b50cf5e48b69e0
--- /dev/null
+++ b/src/server/conference.c
@@ -0,0 +1,916 @@
+/*
+ * conference.c
+ *
+ * All atomic calls that deals with conferences.
+ */
+
+#include <time.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include <services.h>
+#include "manipulate.h"
+#include <kom-errno.h>
+#include "smalloc.h"
+#include "cache.h"
+#include "log.h"
+#include <config.h>
+#include "com.h"
+#include "connections.h"
+#include "send-async.h"
+#include <debug.h>
+#include "parser.h"
+#include "internal-connections.h"
+
+/* From ram-cache.h. This will go away when we use regexp-matching. This is
+ * only temporary. +++***
+ */
+extern Conf_no       *conf_table;
+
+/*
+ * Static functions
+ */
+
+/*
+ * Delete a conference. Delete all references to this conf.
+ */
+
+static void
+do_delete_conf (Conf_no      conf_no,
+		Conference * conf_c) /* Not NULL */
+{
+    int i;
+    
+    if ( do_set_presentation(conf_no, conf_c, 0) != OK )
+    {
+	log("ERROR: do_delete_conf() - couldn't unmark presentation.\n");
+    }
+    
+    if ( do_set_etc_motd(conf_no, conf_c, 0) != OK )
+    {
+	log("ERROR: do_delete_conf() - couldn't unmark motd.\n");
+    }
+
+    /* Delete all members */
+    /* Note that because of the way do_sub_member is written it is important */
+    /* that the loop is executed this way. */
+    for ( i = conf_c->members.no_of_members - 1; i >= 0; i-- )
+    {
+	do_sub_member( conf_no, conf_c, conf_c->members.members + i,
+		      conf_c->members.members[ i ].member, NULL, NULL);
+    }
+
+    sfree( conf_c->members.members );
+    conf_c->members.members = NULL;
+    
+    /* Delete the name */
+
+    s_clear( &conf_c->name );
+    cached_change_name( conf_no, EMPTY_STRING);
+    
+    /* texts */
+
+    /*
+     * The texts are not deleted at once, but since they now have no recipient
+     * they will not live long.
+     */
+
+    /* ??? Note that there will still be a recipient in the texts, but it
+       will not exist in reality. This might cause problems. A do_sub_recpt()
+       should maybe be used. */
+    /* Yes, do!  Also, send out asynchronous message about deleted conf. */
+
+    sfree( conf_c->texts.texts );
+    conf_c->texts.texts = NULL;
+
+    cached_delete_conf( conf_no );
+    
+    return;
+}
+
+/*
+ * Functions that are exported to the server.
+ */
+
+/*
+ * Return TRUE if NAME is not already used.
+ */
+
+Bool
+legal_name( String name )
+{
+    if (name.len == 0 || name.len > CONF_NAME_LEN )
+    {
+	return FALSE;
+    }
+
+    while( name.len-- )
+    {
+	if ( !isascii( *name.string ) || !isprint( *name.string )
+	    /*???|| *name.string == '"'*/ )
+	{
+	    return FALSE;
+	}
+	name.string++;
+    }
+
+    return TRUE;
+}
+
+/*
+ * Return TRUE if name is unique, or if the only match is conf_no. Use 0
+ * as conf_no if it should not be allowed to be changed.
+ */
+Bool
+unique_name( const String name, Conf_no conf_no )
+{
+    Parse_info   parse_info;
+    Parse_token *name_token;
+    Parse_token *existing_token;
+    Bool	 exact_match_found;
+    Bool	 diff_found;
+    int i;
+    int n;
+        
+    parse_info = parse(name, match_table, FALSE, TRUE,
+		       s_fcrea_str(" \t"), DEFAULT_COLLAT_TAB);
+
+    if ( parse_info.no_of_matches == 0 ) /* Doesn't match any name. */
+    {
+	sfree(parse_info.indexes);
+	return TRUE;
+    }
+
+    if ( parse_info.no_of_matches == -1 ) /* Error. */
+    {
+	log("unique_name(): parse returned error.\n");
+	sfree(parse_info.indexes);
+	return FALSE;
+    }
+    
+    if ( parse_info.no_of_matches == 1 && parse_info.indexes[ 0 ] == -1 )
+    {
+	/* Empty name is not allowed. */
+	sfree(parse_info.indexes);
+	return FALSE;
+    }
+    
+    /* The name matches some conference. Check if they are equal. */
+
+    name_token = tokenize(name, s_fcrea_str(" \t"));
+
+    exact_match_found = FALSE;
+    
+    for ( i = 0; !exact_match_found && i < parse_info.no_of_matches; i++ )
+    {
+	existing_token = match_table[ parse_info.indexes[ i ] ].tokens;
+	diff_found = FALSE;
+	
+	for ( n = 0;
+	     (!diff_found && !s_empty(existing_token[ n ].word)
+	      && !s_empty(name_token[ n ].word));
+	     ++n)
+	{
+	    if ( !s_usr_streq(existing_token[ n ].word,
+			      name_token[ n ].word,
+			      DEFAULT_COLLAT_TAB) )
+	    {
+		diff_found = TRUE;
+	    }
+	}
+
+	if (! s_empty(existing_token[ n ].word)
+	    || ! s_empty(name_token[ n ].word) )
+	{
+	    /* The length (number of words) differed. */
+	    diff_found = TRUE;
+	}
+
+	if ( !diff_found && conf_no != 0
+	    && conf_table[ parse_info.indexes[ i ] ] != conf_no )
+	    exact_match_found = TRUE;
+    }
+	    
+    sfree(parse_info.indexes);
+    free_tokens(name_token);
+    return exact_match_found ? FALSE : TRUE;		
+}
+
+/*
+ * Create a conference.
+ */
+Conf_no
+do_create_conf(String	 name,
+	       Pers_no	 creator,
+	       Conf_no	 supervisor,
+	       Conf_no	 super_conf,
+	       Conf_type type)
+{
+    Conf_no 	 conf_no;
+    Conference * conf_c;
+    
+    /* Allocate memory for conf_c */
+
+    conf_no = cached_create_conf( name );
+
+    if ( (conf_c = cached_get_conf_stat( conf_no ) ) == NULL)
+    {
+	restart_kom("create_conf() - can't get conf_stat");
+    }
+
+    conf_c->creator	= creator;
+    conf_c->creation_time = time(NULL);
+    conf_c->presentation= 0;		/* No presentation yet */
+    conf_c->supervisor	= supervisor;
+    conf_c->permitted_submitters = 0;
+    conf_c->super_conf	= super_conf;
+    conf_c->type	= type;
+    conf_c->last_written= conf_c->creation_time;
+    conf_c->msg_of_day	= 0;
+    conf_c->nice	= DEFAULT_NICE;
+    conf_c->members	= EMPTY_MEMBER_LIST;
+    conf_c->texts	= EMPTY_TEXT_LIST;
+
+    mark_conference_as_changed( conf_no );
+
+    cached_set_conf_type( conf_no, type );
+    
+    return conf_no;
+}
+
+
+/*
+ * Return TRUE if ACTPERS is a supervisor to CONF.
+ */
+
+Bool		
+is_supervisor(Conf_no      conf,
+	      Conference * conf_c)/* Conference status for CONF, can be NULL */
+{
+    if (!ACTPERS)
+	return FALSE;
+
+    if (ENA(wheel, 8))
+	return TRUE;
+    
+    if (ACTPERS == conf)	/* A person is ALWAYS supervisor to	*/
+	return TRUE;		/* his/her own mailbox!			*/
+
+
+    if ( conf_c == NULL )	/* Get conference if unknown to the caller */
+	GET_C_STAT( conf_c, conf, FALSE );
+    
+    if ( !conf_c->supervisor )
+        return FALSE;
+	
+    if (ACTPERS == conf_c->supervisor)
+        return TRUE;
+
+    if ( locate_membership(conf_c->supervisor, ACT_P) != NULL)
+	return TRUE;
+    
+    return FALSE;
+}
+
+
+/*
+ * Atomic functions
+ */
+
+/*
+ * Change name of a person or conference. You must be supervisor
+ * of the conference to use this call.
+ */
+extern Success
+change_name (Conf_no	conf_no,
+	     String	new_name)
+{
+    Conference * conf_c;
+    Access	 acc;
+
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    acc = access_perm(conf_no, conf_c);
+
+    if ( acc <= none )
+    {
+	kom_errno = KOM_UNDEF_CONF;
+	return FAILURE;
+    }
+
+    if ( !ACT_P->privileges.change_name
+	|| (acc != unlimited && !ENA(admin, 3)))
+    {
+	kom_errno = KOM_PERM;
+	return FAILURE;
+    }
+
+    if ( !legal_name( new_name ) )
+    {
+	kom_errno = KOM_BAD_NAME;
+	return FAILURE;
+    }
+
+    if ( !unique_name( new_name, conf_no ) )
+    {
+	kom_errno = KOM_CONF_EXISTS;
+	return FAILURE;
+    }
+
+    async_new_name(conf_no, conf_c->name, new_name);
+
+    s_strcpy(&conf_c->name, new_name);
+    mark_conference_as_changed(conf_no);
+
+    cached_change_name(conf_no, new_name);
+
+    return OK;
+}
+
+/*
+ * Create a conference: Default modes:
+ *	ACTPERS becomes supervisor and super_conf.
+ *	Anyone can submitt texts.
+ *	Noone (not even the creator) is a member in the conf.
+ *
+ * If ANYONE_CAN_CREATE_NEW_CONFS (#defined in config.h) is not true
+ * you must have the 'create_conf' capability.
+ *
+ * It is currently not allowed to have a conference that is secret
+ * and not rd_prot. This restriction might be lifted in the future
+ * (but I don't understand the use of such a conference...)
+ */
+
+extern Conf_no
+create_conf(String      name,
+	    Conf_type	type)
+{
+    Conf_no	 conf_no;
+    
+    CHK_LOGIN(0);
+
+    if (!ANYONE_CAN_CREATE_NEW_CONFS && !ENA(create_conf, 0) )
+    {
+	kom_errno = KOM_PERM;
+	return 0;
+    }
+
+    if ( !legal_name( name ) )
+    {
+	kom_errno = KOM_BAD_NAME;
+	return 0;
+    }
+
+    if ( !unique_name( name, 0 ) )
+    {
+	kom_errno = KOM_CONF_EXISTS;
+	return 0;
+    }
+
+    if ( type.letter_box )	/* A letter_box can only be created via */
+    {				/* create_person.			*/
+	kom_errno = KOM_PERM;
+	return 0;
+    }
+
+    if ( type.secret && !type.rd_prot )
+    {
+	kom_errno = KOM_SECRET_PUBLIC;
+	return 0;
+    }
+    
+    conf_no = do_create_conf(name, ACTPERS, ACTPERS, ACTPERS, type);
+
+    if ( conf_no != 0)
+    {
+	ACT_P->created_confs++;
+	mark_person_as_changed( ACTPERS );
+    }
+    
+    return conf_no;
+}
+
+/*
+ * Log out a person from any connection he might be logged on to.
+ */
+static void
+logout_person(Pers_no pers_no)
+{
+    Session_no i = 0;
+    Connection *real_active_connection;
+
+    real_active_connection = active_connection;
+
+    while ( (i = traverse_connections(i)) != 0)
+    {
+	active_connection = get_conn_by_number(i);
+
+	if ( active_connection->pers_no == pers_no )
+	    logout();
+    }
+
+    active_connection = real_active_connection;
+}
+
+/*
+ * Delete a conference or person. You must be supervisor of the
+ * conference to be allowed to delete it.
+ */
+extern Success
+delete_conf (Conf_no	conf_no )
+{
+    Conference	   * conf_c;
+    Access	     acc;
+    
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    acc = access_perm(conf_no, conf_c);
+    
+    if ( acc != unlimited )
+    {
+	kom_errno = (acc <= none) ? KOM_UNDEF_CONF : KOM_PERM ;
+	return FAILURE;
+    }
+
+    if ( conf_c->type.letter_box )
+    {
+	logout_person(ACTPERS);	/* Log out this person from any connection */
+
+	if ( do_delete_pers( conf_no ) != OK )
+	{
+	    log("ERROR: delete_conf(): can't delete person.\n");
+	}
+    }
+
+    do_delete_conf( conf_no, conf_c );
+
+    return OK;
+}
+
+/*
+ * Map conference name to number. Can be done without logging in.
+ * Secret conferences will not be returned unless ACTPERS is supervisor
+ * of, or member in, that conference.
+ */
+extern Success
+lookup_name (const String    name,
+	     Conf_list_old * result)
+{
+    Conf_no	* no, * no_copy;
+    Conf_type   * type, *type_copy;
+    int		i;
+    
+    if ( cached_lookup_name( name, result ) != OK )
+	return FAILURE;
+
+    no = no_copy = result->conf_nos;
+    type = type_copy = result->type_of_conf;
+
+    for ( i = result->no_of_conf_nos; i > 0; i-- )
+    {
+	if ( fast_access_perm( *no ) <= none )
+	    --result->no_of_conf_nos;
+	else
+	{
+	    *no_copy++ = *no;
+	    *type_copy++ = *type;
+	}
+
+	++no;
+	++type;
+    }
+    return OK;
+}
+
+
+/*
+ * Get status for a conference.
+ */
+extern  Success
+get_conf_stat (Conf_no		  conf_no,
+	       Conference	* result )
+{
+    Conference * conf_c;
+    Access	 acc;
+        
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    acc = access_perm( conf_no, conf_c);
+
+    if ( acc == error )
+	return FAILURE;
+
+    if ( acc == none )
+    {
+	kom_errno = KOM_UNDEF_CONF;
+	return FAILURE;
+    }
+
+    *result = *conf_c;
+
+    return OK;
+}
+
+extern  Success
+get_conf_stat_old (Conf_no	  conf_no,
+		   int		  mask,
+		   Conference	* result )
+{
+    Conference * conf_c;
+    Access	 acc;
+        
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    acc = access_perm( conf_no, conf_c);
+
+    if ( acc == error )
+	return FAILURE;
+
+    if ( acc == none )
+    {
+	kom_errno = KOM_UNDEF_CONF;
+	return FAILURE;
+    }
+
+    *result = *conf_c;
+
+    if ( !(mask & 1) )
+	result->name = EMPTY_STRING;	
+
+    return OK;
+}
+
+
+		
+/*
+ * Set or delete the presentation of a conference. If text_no == 0 there
+ * will be no presentation.
+ *
+ * The text's mark-count will be increased so that it will not be deleted
+ * when it gets old.
+ *
+ * BUGS: This should probably be a Info_type 'presentation to conf no #'
+ *	 for at least two reasons:
+ *		1) Given the text, there is no way to know that it
+ *		   is a presentation unless what the user writes
+ *		   tells you so.
+ *		2) Presentations should always be readable by everyone.
+ *		   But to be readable an open conference needs to be
+ *		   recipient of the text. Since all presentations should
+ *		   go to the presentation-confs (that is the clients
+ *		   responsibility!) this is no big problem, but it depends
+ *		   somewhat on how the text-garber is implemented.
+ */
+
+extern Success
+set_presentation (Conf_no	conf_no,
+		  Text_no	text_no )
+{
+    Conference * conf_c;
+    Access	 acc;
+    
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    acc = access_perm(conf_no, conf_c);
+    
+    if ( acc < unlimited )
+    {
+	kom_errno = (acc <= none) ? KOM_UNDEF_CONF : KOM_PERM;
+	return FAILURE;
+    }
+
+    return do_set_presentation(conf_no, conf_c, text_no);
+}
+
+/*
+ * Set a message-of-the-day for a conference. There should normally
+ * be no motd unless something extraordinary happens. If there is
+ * a motd the client should show it as soon as possible.
+ *
+ * Only the supervisor can change the motd.
+ *
+ * BUG: There is no asynchronous message that reports new motds.
+ */
+extern Success
+set_etc_motd(	Conf_no		conf_no,
+		Text_no		text_no )
+{
+    Conference * conf_c;
+    Access	 acc;
+    BUGDECL;
+    
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    if ( (acc = access_perm(conf_no, conf_c)) < unlimited )
+    {
+	kom_errno = (acc <= none ) ? KOM_UNDEF_CONF : KOM_PERM;
+	BUG(("set_etc_motd failed. Conf %ld Text %ld Acc %ld < %ld (%d).\n",
+	     (u_long)conf_no, (u_long)text_no, (u_long)acc,
+	     (u_long)unlimited, acc < unlimited));
+	return FAILURE;
+    }
+
+    return do_set_etc_motd(conf_no, conf_c, text_no);
+}
+
+/*
+ * Set a new supervisor for a conference. May only be used by
+ * the old supervisor.
+ *
+ * NEW_SUPER is either a person or a conference number. If it is a
+ * conference number it means that all the members in NEW_SUPER become
+ * supervisors to CONF_NO. (NEW_SUPER should normally be rd_prot to
+ * prevent anyone from makeing themselves supervisors).
+ */
+extern Success
+set_supervisor(	Conf_no	 conf_no,
+		Conf_no	 new_super )
+{
+    Conference * conf_c;
+    
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+    if (new_super != 0)
+	CHK_EXIST(new_super, FAILURE);
+
+    if ( !is_supervisor(conf_no, conf_c)
+	&& !ENA(admin, 6) )
+    {
+	kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_PERM;
+	return FAILURE;
+    }
+    
+    conf_c->supervisor = new_super;
+    mark_conference_as_changed( conf_no );
+	
+    return OK;
+}
+
+/*
+ * Allow certain users to submit texts to CONF_NO.
+ *
+ * If NEW_PERM_SUB == 0 anyone may submit texts.
+ * If it is a person only that person can submit texts to CONF_NO.
+ * If it is a conference only the members in that conference can
+ * submit texts.
+ *
+ * If anyone tries to submit a text to a conference he is not allowed
+ * to submit texts to the text will silently be redirected to the
+ * superconf. If he is not allowed to submit to that conference either
+ * it will be redirected again and so forth, but there is a limit
+ * (MAX_SUPER_CONF_LOOP) on how many redirections will be done.
+ *
+ * It is possible to have a secret conference as super_conf. Users
+ * will then be able to send texts to it, but they will not see
+ * where the text went...
+ */
+extern Success
+set_permitted_submitters (Conf_no  conf_no,
+			  Conf_no  new_perm_sub ) 
+{
+    Conference * conf_c;
+    
+    CHK_LOGIN(FAILURE);
+    if (new_perm_sub != 0)
+	CHK_EXIST(new_perm_sub, FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    if ( !is_supervisor(conf_no, conf_c)
+	&& !ENA(admin, 6) )
+    {
+	kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_PERM;
+	return FAILURE;
+    }
+	
+    conf_c->permitted_submitters = new_perm_sub;
+    mark_conference_as_changed( conf_no );
+	
+    return OK;
+}
+
+/*
+ * Set the super_conf of CONF_NO. This call may only be used of a
+ * supervisor of CONF_NO.
+ *
+ * See documentation for set_permitted_submitters().
+ */
+extern Success
+set_super_conf (Conf_no  conf_no,
+		Conf_no  new_super_conf )
+{
+    Conference * conf_c;
+    
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+    CHK_EXIST(new_super_conf, FAILURE);
+
+    if ( !is_supervisor(conf_no, conf_c)
+	&& !ENA(admin, 5) )
+    {
+	kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_PERM;
+	return FAILURE;
+    }
+
+    conf_c->super_conf = new_super_conf;
+    mark_conference_as_changed( conf_no );
+	
+    return OK;
+}
+
+/*
+ * Set the type of a conference. Only the supervisor of a conference can
+ * set the conf_type. The letter_box flag can not be changed.
+ *
+ * BUG: It is allowed to set the type to 'secret' for persons. I don't
+ * think we want it to be that way.
+ */
+extern Success
+set_conf_type (Conf_no    conf_no,
+	       Conf_type  type )
+{
+    Conference  * conf_c;
+    Access	  acc;
+        
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    if ( type.secret && !type.rd_prot )
+    {
+	kom_errno = KOM_SECRET_PUBLIC;
+	return 0;
+    }
+
+    acc = access_perm(conf_no, conf_c) ;
+    
+    if ( acc < unlimited )
+    {
+	kom_errno = ( acc <= none ) ? KOM_UNDEF_CONF : KOM_PERM ;
+	return FAILURE;
+    }
+    
+    if ( type.letter_box != conf_c->type.letter_box )
+    {
+	kom_errno = KOM_LETTER_BOX;
+	return FAILURE;
+    }
+
+    conf_c->type = type;
+    mark_conference_as_changed( conf_no );
+
+    cached_set_conf_type( conf_no, type );
+
+    return OK;
+}
+
+
+/*
+ * Set garb_nice for a conference. This controls how long messages
+ * to a conference will live. The argument is probably the number of
+ * days a message will live. Only the supervisor of the conference
+ * may change the garb_nice.
+ */
+extern Success
+set_garb_nice(	Conf_no		conf_no,
+		Garb_nice	nice	)	/* number of days */
+{
+    Conference * conf_c;
+    Access	 acc;
+        
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    acc = access_perm(conf_no, conf_c);
+    
+    if ( acc < unlimited )
+    {
+	kom_errno = (acc <= none) ? KOM_UNDEF_CONF : KOM_PERM;
+	return FAILURE;
+    }
+
+    conf_c->nice = nice;
+    mark_conference_as_changed( conf_no );
+
+    return OK;
+}
+
+/*
+ * Change presentation of a conference. If text_no is 0, there will be
+ * no presentation.
+ */
+
+Success
+do_set_presentation(Conf_no 	 conf_no,
+		    Conference * conf_c,
+		    Text_no	 text_no)
+{
+    Text_stat * old_pres;
+    Text_stat * new_pres = NULL; /* Initialized to stop gcc complaining
+				  * about this being used uninitialized.
+				  * This initialization is in fact not needed
+				  * since it would not be used uninitialized.*/
+
+    /* Check that the new presentation exists before deleting the old*/
+
+    if ( text_no != 0 )
+    {
+	GET_T_STAT(new_pres, text_no, FAILURE);
+	if ( new_pres->no_of_marks >= MAX_MARKS_TEXT )
+	{
+	    log(
+	"LIMIT: do_set_presentation(): New presentation very marked.\n");
+	    return FAILURE;
+	}
+    }
+    
+    /* Unmark the previous presentation if it exists. */
+
+    if ( conf_c->presentation != 0
+	&& (old_pres = cached_get_text_stat(conf_c->presentation)) != NULL)
+    {
+	if ( old_pres->no_of_marks > 0 )
+	{
+	    --old_pres->no_of_marks;
+	    mark_text_as_changed( conf_c->presentation );
+	}
+	else
+	{
+	    log("ERROR: do_set_presentation(): Old presentation not marked\n");
+	}
+    }
+
+    /* Mark the new presentation */
+
+    if ( text_no != 0 )
+    {
+	++new_pres->no_of_marks;
+	mark_text_as_changed( text_no );
+    }
+    
+    conf_c->presentation = text_no;
+    mark_conference_as_changed( conf_no );
+
+    return OK;
+}
+
+/*
+ * Change motd of a conference. If text_no is 0, there will be
+ * no motd.
+ */
+
+Success
+do_set_etc_motd(Conf_no      conf_no,
+		Conference * conf_c,
+		Text_no      text_no)
+{
+    Text_stat * old_motd;
+    Text_stat * new_motd = NULL; /* To stop gcc complaining. */
+
+    /* Check that the new motd exists before deleting the old*/
+
+    if ( text_no != 0 )
+    {
+	GET_T_STAT(new_motd, text_no, FAILURE);
+	if ( new_motd->no_of_marks >= MAX_MARKS_TEXT )
+	{
+	    log(
+	"LIMIT: do_set_motd(): New motd very marked.\n");
+	    return FAILURE;
+	}
+    }
+    
+    /* Unmark the previous motd if it exists. */
+
+    if ( conf_c->msg_of_day != 0
+	&& (old_motd = cached_get_text_stat(conf_c->msg_of_day)) != NULL)
+    {
+	if ( old_motd->no_of_marks > 0 )
+	{
+	    --old_motd->no_of_marks;
+	    mark_text_as_changed( conf_c->msg_of_day );
+	}
+	else
+	{
+	    log("ERROR: do_set_motd(): Old motd not marked\n");
+	}
+    }
+
+    /* Mark the new motd */
+
+    if ( text_no != 0 )
+    {
+	++new_motd->no_of_marks;
+	mark_text_as_changed( text_no );
+    }
+    
+    conf_c->msg_of_day = text_no;
+    mark_conference_as_changed( conf_no );
+
+    return OK;
+}
+
+
diff --git a/src/server/connections.c b/src/server/connections.c
new file mode 100644
index 0000000000000000000000000000000000000000..4c172f004e28aa7f53d1a5eb7359a091bd97e285
--- /dev/null
+++ b/src/server/connections.c
@@ -0,0 +1,811 @@
+/*
+ * connections.c
+ *
+ * Denna fil inneh}ller niv}n ovanf|r isc.
+ *
+ * Created by Willf|r 31/3-90. Mostly written by ceder.
+ */
+
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <kom-types.h>
+#include "lyskomd.h"
+#include "s-string.h"
+#include <config.h>
+#include <debug.h>
+#include "end-of-atomic.h"
+#include "log.h"
+#include <services.h>
+#include "isc.h"
+#include <kom-errno.h>
+#include "com.h"
+#include "connections.h"
+#include "prot-a-parse.h"
+#include "prot-a-output.h"
+#include "prot-a.h"
+#include "smalloc.h"
+#include "prot-a-parse-arg.h"
+#include "isc-parse.h"
+#include "cache.h"
+#include "send-async.h"
+#include "mux.h"
+#include "mux-parse.h"
+#include "internal-connections.h"
+
+ISCMCB         * kom_server_mcb    = NULL;
+Connection     * active_connection = NULL;
+
+Bool		 go_and_die = FALSE;
+Bool		 do_sync_db = FALSE;
+
+jmp_buf 	 parse_env;
+
+
+const Fnc_descriptor fnc_defs[]={
+#include "fnc-def-init.incl"
+};
+
+u_long service_statistics[sizeof (fnc_defs) / sizeof (Fnc_descriptor)];
+
+BUGDECL;
+
+
+
+void
+logout_client(Connection *cp)
+{
+    Connection *real_active_connection;
+
+    if ( active_connection != NULL )
+    {
+	log("BUGCHK: logout_client(%d): connection %d is active.\n",
+	    cp->session_no, active_connection->session_no);
+    }
+    
+    switch (cp->magic)
+    {
+    case CONN_MAGIC_ALLOC:
+        break;
+
+    case CONN_MAGIC_FREE:
+	log("LOGOUT_CLIENT: Trying to free freed Connection - ignored!\n");
+	return;
+
+    default:
+	restart_kom("LOGOUT_CLIENT: Bad magic number\n");
+    }
+
+    if ( cp->pers_no != 0 )
+    {
+	real_active_connection = active_connection; /* Shouldn't be needed +++ */
+	active_connection = cp;
+	logout();
+	active_connection = real_active_connection;
+    }
+
+    s_clear(&cp->unparsed);
+    s_clear(&cp->what_am_i_doing);
+    s_clear(&cp->username);
+
+    switch(cp->protocol)
+    {
+    case 0:			/* Hasn't yet allocated any protocol. */
+	break;
+	
+    case 'A':
+	prot_a_destruct(cp);
+	break;
+	
+    default:
+	restart_kom("logout_client(): Bad protocol.\n");
+    }
+
+    mux_close(cp);		/* Close fd's. Clear mux and isc structures. */
+
+    kill_client(cp);		/* Free the Connection */
+}
+
+    
+
+    
+/*
+ * Call a function in services.c. A pointer to the result is returned.
+ * The pointer points to static data which is overwritten on each call.
+ */
+static Success
+call_function (Connection  * client,
+	       Result_holder   * res)     /* This is a union. */
+{
+    Success	status=FAILURE;	/* OK if the call was successful. */
+
+    if ( active_connection != NULL )
+    {
+	log("call_function(%d): active_connection = %d",
+	    client->session_no, active_connection->session_no);
+    }
+    
+    active_connection = client;
+
+    service_statistics[client->function]++;
+
+#include "call-switch.incl"
+
+    active_connection = NULL;
+
+    return status;
+}
+
+
+static void
+parse_packet(Connection *client)
+{
+    if ( client->protocol == '\0' ) /* Not known yet. */
+    {
+	client->protocol = parse_char(client);
+	switch(client->protocol)
+	{
+	case 'A':
+	    prot_a_init(client);
+	    break;
+
+	default:
+	    client->protocol = '\0';
+	    mux_printf(client,
+		    "%%%%LysKOM unsupported protocol.\n");
+	    mux_flush(client);
+	    BUG(("%%%%Unsupported protocol.\n"));
+	    longjmp(parse_env, ISC_LOGOUT);
+	}
+    }
+
+    switch(client->protocol)
+    {
+    case 'A':
+	prot_a_parse_packet(client);
+	break;
+
+    default:
+	restart_kom("parse_packet(): Bad protocol.\n");
+	break;
+    }
+}
+
+/*
+ * Free all parsed areas which are no longer needed. Re-initialize all
+ * parse_pos fields so that the parse will expect a new function.
+ *
+ * This function is called
+ *	when a parse error occurs
+ *	when a parse is complete and the function has executed.
+ */
+static void
+free_parsed(Connection *client)
+{
+    s_clear(&client->c_string0);
+    s_clear(&client->c_string1);
+    client->string0 = EMPTY_STRING; /* So that no one frees it. */
+    sfree( client->c_misc_info_p);
+    client->c_misc_info_p = NULL;
+    sfree( client->c_local_text_no_p);
+    client->c_local_text_no_p = NULL;
+    client->parse_pos = 0;
+    client->fnc_parse_pos = 0;
+    client->array_parse_pos = 0;
+    client->struct_parse_pos = 0;
+    client->string_parse_pos = 0;
+}
+
+/*
+ * Send a reply to a call.
+ */
+static void
+reply(Connection *client,
+      Success	      status,
+      Result_holder  *result)
+{
+    switch(client->protocol)
+    {
+    case 'A':
+	prot_a_reply(client, status, result);
+	break;
+
+    default:
+	restart_kom("reply(): Bad protocol.\n");
+	break;
+    }
+}
+
+
+/*
+ * Try to parse enough data from client->unparsed to call a function.
+ * If more data is needed set client->more_to_parse to FALSE.
+ */
+static void
+parse_unparsed(Connection *client)
+{
+    String        tmp_str = EMPTY_STRING;
+    Success       status;
+    Result_holder result;
+        
+    switch ( setjmp(parse_env) )
+    {
+    case 0 :
+	/* Parse message. If message is complete call function and reply. */
+	parse_packet(client);
+	client->last_request = time(NULL);
+	status = call_function(client, &result);
+	reply(client, status, &result);
+	free_parsed(client);
+	end_of_atomic(FALSE);
+	break;
+
+    case ISC_PROTOCOL_ERR:
+	s_clear(&client->string0);
+	free_parsed(client);
+	mux_printf(client, "%% LysKOM protocol error.\n");
+	mux_flush(client);
+	BUG(("%%%% Protocol error.\n"));
+	s_clear(&client->unparsed);
+	client->first_to_parse = 0;
+	client->more_to_parse = FALSE;
+	end_of_atomic(FALSE);
+	break;
+
+    case ISC_MSG_INCOMPLETE:
+	client->more_to_parse = FALSE;
+	break;
+
+    case ISC_LOGOUT:
+	client->kill_me = TRUE;
+	break;
+    }
+    
+    /* Delete the parsed part of 'unparsed' */
+    
+    if ( s_substr(&tmp_str, client->unparsed,
+		  client->first_to_parse, END_OF_STRING) != OK )
+	restart_kom("parse_unparsed: s_substr\n");
+
+    s_clear(&client->unparsed);
+    client->unparsed = tmp_str;
+    client->first_to_parse = 0;
+}
+
+
+/*
+ * There is a message in the event. Parse it.
+ */
+static void
+parse_message(Connection *cp, String tmp_str)
+{
+    VBUGSTR(tmp_str);
+
+    cp->more_to_parse = TRUE;
+    
+    if ( s_strcat(&cp->unparsed, tmp_str) != OK )
+	restart_kom("parse_message(): s_strcat\n");
+
+    tmp_str = EMPTY_STRING;
+    
+    /* Parse this packet, but leave the last token if it isn't complete. */
+
+    parse_unparsed(cp);
+
+    return;
+}
+
+static void
+mux_handle_packet(Mux *mux)
+{
+    Mux_client *mcp = NULL;
+    Connection *cp = NULL;
+
+    switch(mux->parse.function)
+    {
+    case 0:			/* ping */
+	isc_printf(mux->scb, "0\n");
+	isc_flush(mux->scb);
+	break;
+
+    case 1:			/* login */
+
+	mcp = mux_getclientbyid(mux, mux->parse.num);
+	if (mcp)
+	{			/* ERROR %d: Already logged in\n */
+	    isc_printf(mux->scb, "4\n");
+	    isc_flush(mux->scb);
+	    return;
+	}
+    
+	cp = new_client();
+	cp->mux = mux;
+	cp->hostname = mux->parse.string;
+	mux->parse.string = EMPTY_STRING;
+	
+	mux_addclient(mux, mux->parse.num, cp);
+
+    
+	BUG(("\n[Client %d from %.*s via MUX %s",
+	     cp->session_no,
+	     cp->hostname.len,
+	     cp->hostname.string,
+	     mux->scb->info.tcp.hostname));
+	BUG((" is connecting]\n"));
+
+	break;
+    
+    case 2:			/* logout */
+	mcp = mux_getclientbyid(mux, mux->parse.num);
+	if (!mcp)
+	{			/* ERROR %d: No such mux-session */
+	    isc_printf(mux->scb, "4\n");
+	    isc_flush(mux->scb);
+	    return;
+	}
+
+	cp = mcp->conn;
+    
+	BUG(("\n[Client %d via MUX(%s)",
+	     cp->session_no,
+	     mux->scb->info.tcp.hostname));
+	BUG((" is logging out by request]\n"));
+
+#if 0
+	logout_client(cp);
+	end_of_atomic(FALSE);	/* ??? */
+#else
+	cp->kill_me = TRUE;
+#endif
+	break;
+
+    case 3:			/* msg */
+	mcp = mux_getclientbyid(mux, mux->parse.num);
+	if (!mcp)
+	{			/* ERROR %d: No such mux-session */
+	    isc_printf(mux->scb, "4\n");
+	    isc_flush(mux->scb);
+	    return;
+	}
+    
+	cp = mcp->conn;
+    
+	VBUG(("\n[Message client %d via MUX #%d]\n",
+	      cp->session_no, mux->scb));
+
+	parse_message(cp, mux->parse.string);
+	break;
+
+    default:
+	
+	VBUG(("\nMUX Protocol error from MUX #%d]\n",
+	      mux->scb));
+
+	isc_printf(mux->scb, "5\n");
+	isc_flush(mux->scb);
+	break;
+	
+    }
+}
+	
+void
+mux_logout(Mux *mp)
+{
+    int i;
+
+    for (i = 0; i < mp->client_c; i++)
+	if (mp->client_v[i].conn)
+	{
+	    BUG(("\n[Client %d via MUX(%s)",
+		 mp->client_v[i].conn->session_no,
+		 mp->scb->info.tcp.hostname));
+	    BUG((" is logging out by MUX shutdown]\n"));
+	    logout_client(mp->client_v[i].conn);
+	    end_of_atomic(FALSE);
+	}
+	      
+    isc_close(mp->scb);
+    mux_destruct(mp);
+}
+
+
+static void
+mux_parse_unparsed(Mux *mux)
+{
+    String        tmp_str;
+
+    while ( mux->parse.more_to_parse )
+    {
+	switch ( setjmp(mux_parse_env) )
+	{
+	case 0 :
+	    /* Parse message. Take apropriate action if message is complete. */
+	    mux_parse_packet(mux);
+	    mux_handle_packet(mux);
+	    mux_free_parsed(mux);
+	    break;
+
+	case MUX_PROTOCOL_ERR:
+	    s_clear(&mux->parse.string);
+	    mux_free_parsed(mux);
+	    isc_printf(mux->scb, "5\n");
+	    isc_flush(mux->scb);
+	    BUG(("%%%% Mux protocol error.\n"));
+	    s_clear(&mux->parse.unparsed);
+	    mux->parse.first_to_parse = 0;
+	    mux->parse.more_to_parse = FALSE;
+	    break;
+
+	case MUX_MSG_INCOMPLETE:
+	    mux->parse.more_to_parse = FALSE;
+	    break;
+
+	case MUX_LOGOUT:
+	    mux_logout(mux);
+	    return;
+	}
+    
+	/* Delete the parsed part of 'unparsed' */
+
+	tmp_str = EMPTY_STRING;
+
+	if ( s_substr(&tmp_str, mux->parse.unparsed,
+		      mux->parse.first_to_parse, END_OF_STRING) != OK )
+	    restart_kom("mux_parse_unparsed: s_substr\n");
+
+	s_clear(&mux->parse.unparsed);
+	mux->parse.unparsed = tmp_str;
+	mux->parse.first_to_parse = 0;
+    }
+}
+
+
+static void
+mux_parse_message(Mux *mp, String tmp_str)
+{
+    VBUGSTR(tmp_str);
+
+    BUG(("mux_parse_message(): mp->parse.unparsed = { len = %d, "
+	 "string = %p }\n", mp->parse.unparsed.len,
+	 mp->parse.unparsed.string));
+    
+    if ( s_strcat(&mp->parse.unparsed, tmp_str) != OK )
+	restart_kom("mux_parse_message(): s_strcat\n");
+
+    tmp_str = EMPTY_STRING;
+    
+    /* Parse this packet, but leave the last token if it isn't complete. */
+
+    mux_parse_unparsed(mp);
+
+    return;
+}
+
+/*
+ * parse data in client->unparsed and mux->unparsed if there
+ * is data that has not yet been taken care of. Return TRUE
+ * if there was no data to take care of.
+ */
+Bool
+parse_forgotten()
+{
+    Bool flg = FALSE;			/* Was there anything? */
+    Connection *ci;
+    Session_no session_no = 0;
+
+    if ( traverse_connections(0) == 0 )
+	return TRUE;
+
+    while ( ( session_no = traverse_connections( session_no ) ) != 0 )
+    {
+	ci = get_conn_by_number (session_no);
+
+	if ( ci->more_to_parse )
+	{
+	    parse_unparsed(ci);
+	    flg = TRUE;
+	}
+    }
+
+    return !flg;
+}
+
+
+/* Return 1 if the named file exists, 0 otherwise */
+static int
+fexists(char *filename)
+{
+    extern int stat(char *filename, struct stat *bufp);
+    struct stat buf;
+    int code;
+  
+    code = !stat(filename, &buf);
+    errno = 0;
+
+    return code;
+}
+
+
+void
+dump_statistics(void)
+{
+    static time_t last_dump = NO_TIME;
+    int i;
+    time_t now;
+    FILE *fp;
+    extern char statisticfile[1024]; /* Defined in ramkomd.c */
+
+    if ( (fp = fopen(statisticfile, "a")) == NULL )
+    {
+	log(__FILE__ ": dump_statistics(): can't open file %s\n",
+	    statisticfile);
+	return;
+    }
+
+    time(&now);
+
+    if (last_dump == NO_TIME)
+    {
+	fprintf(fp, "RESTART\n");
+	last_dump = now;
+    }
+    
+    fprintf(fp, "TIME: %s", ctime(&now));
+    fprintf(fp, "SECONDS: %d\n", (int)difftime(now, last_dump));
+    fprintf(fp, "STATISTICS:");
+    for ( i = 0; i < sizeof(service_statistics) / sizeof(u_long); i++)
+    {
+	fprintf(fp, " %lu", service_statistics[i]);
+	service_statistics[i]=0;
+    }
+
+    fprintf(fp, "\n");
+    fclose(fp);
+
+    last_dump = now;
+}
+
+
+/*
+ * check_kill_flg must NEVER be called inside an atomic call!
+ */
+static void
+check_kill_flg(void)
+{
+    Session_no  i = 0;
+    Connection *conn;
+
+    if ( active_connection != NULL )
+    {
+	restart_kom("check_kill_flg: active_connection == %d",
+		    active_connection->session_no);
+    }
+
+    while ( (i = traverse_connections (i)) != 0 )
+    {
+	conn = get_conn_by_number(i);
+
+	if ( conn->kill_me == TRUE )
+	{
+	    logout_client(conn);
+	    end_of_atomic(FALSE);
+	}
+    }
+}
+
+static void
+login_request(ISCECB  *event)
+{
+    Connection  * cp;
+
+    if (isc_sessions(kom_server_mcb) >= MAX_NO_OF_CONNECTIONS)
+    {
+	BUG(("Connection attempt rejected.\n"));
+	isc_printf(event->session, "%s", "%% No connections left.\n");
+	isc_flush(event->session);
+	isc_close(event->session);
+	      
+	async_rejected_connection();
+	return;
+    }
+
+    /* Supress logins if /etc/nologin exists */
+    if (fexists("/etc/nologin"))
+    {
+	isc_printf(event->session, "%s", "%% No logins allowed.\n");
+	isc_flush(event->session);
+	isc_close(event->session);
+	return;
+    }
+
+    if (event->session->info.tcp.listen == listen_mux)
+    {
+	/* Multiplexer requesting connection */
+
+	/*
+	 ** Create, and setup the MUX for the MUX case
+	 */
+	event->session->udg = mux_create(MUX_TYPE_MUX, event->session);
+	      
+	BUG(("MUX #%d at %s connecting.\n",
+	     event->session, event->session->info.tcp.hostname));
+    }
+    else
+    {
+	/* Client requesting connection */
+	      
+	/*
+	 ** Create, and setup the MUX for the CLIENT case
+	 */
+	event->session->udg = mux_create(MUX_TYPE_CLIENT,
+					 event->session);
+	      
+	cp = new_client();
+	cp->mux = event->session->udg;
+	cp->hostname = s_fcrea_str(event->session->info.tcp.hostname);
+	      
+	mux_addclient(event->session->udg, 0, cp);
+	      
+	BUG(("\n[Client %d from %s", cp->session_no,
+	     event->session->info.tcp.hostname));
+	BUG((" is connecting]\n"));
+    }	      
+}
+
+static void
+logout_request(ISCECB *event)
+{
+    Connection  * cp;
+    
+    if (event->session->udg->type == MUX_TYPE_MUX)
+    {
+	BUG(("\n[MUX #%d at %s", event->session,
+	     event->session->info.tcp.hostname));
+	BUG((" is logging out]\n"));
+
+	/*
+	 ** Logout any remaining clients
+	 */
+	mux_logout(event->session->udg);
+    }
+    else
+    {
+	cp = event->session->udg->client_v[0].conn;
+	      
+	BUG(("\n[Client %d from %s", cp->session_no,
+	     event->session->info.tcp.hostname));
+	BUG((" is logging out]\n"));
+	      
+	cp->kill_me = TRUE;
+    }
+}
+
+
+static void
+message_request(ISCECB *event)
+{
+    Connection *cp;
+    String      tmp_str;
+
+    if (event->session->udg->type == MUX_TYPE_MUX)
+    {
+	event->session->udg->parse.more_to_parse = TRUE;
+	tmp_str.string = event->msg->buffer;
+	tmp_str.len    = event->msg->length;
+	mux_parse_message(event->session->udg, tmp_str);
+    }
+    else
+    {
+	cp = event->session->udg->client_v[0].conn;
+	VBUG(("\n[Message from client %d]\n",
+	      cp->session_no));
+	/*
+	 ** Pass the message on to the parser
+	 */
+	tmp_str.string = event->msg->buffer;
+	tmp_str.len    = event->msg->length;
+	parse_message(cp, tmp_str);
+    }
+}
+
+static long
+timevaldiff(struct timeval a,
+	    struct timeval b)
+{
+    return 1000000 * (a.tv_sec - b.tv_sec) + a.tv_usec - b.tv_usec;
+}
+
+void
+toploop(void)
+{
+    ISCECB      * event;
+    Bool	pending_input = TRUE; /* Should be TRUE whenever there
+				         is or might be pending input
+					 from any client in the unparsed
+					 buffer. */
+    long	timeout = TIMEOUT;    /* In milliseconds. */
+    struct timeval before, after;
+    
+    while ( !go_and_die )
+    {
+        if (do_sync_db)
+	{
+	    cache_sync();	/* A noop in diskomd. Actually
+				   stops the server and dumps
+				   everything in ramkomd. */
+	    dump_statistics();
+	    do_sync_db = 0;
+	}
+
+	gettimeofday(&before, NULL);
+	event = isc_getnextevent(kom_server_mcb,
+				 pending_input ? 0 : timeout );
+	gettimeofday(&after, NULL);
+
+	timeout -= timevaldiff(after, before) / 1000;
+
+	if ( timeout < 0 )
+	    timeout = 0;
+	
+	if ( event == NULL )
+	    restart_kom("toploop(): Got NULL event - shouldn't happen...\n");
+
+	switch ( event->event )
+	{
+	case ISC_EVENT_ERROR:
+	    if (errno != EINTR)
+	    {
+		log("toploop: ISC_EVENT_ERROR (error = '%s'):\n   '%s'\n",
+		    strerror(errno), event->msg ? event->msg->buffer
+		    : "(unknown)");
+	    }
+	    break;
+	    
+	case ISC_EVENT_TIMEOUT:
+	    BUG((">"));
+#ifdef DEBUG
+	    fflush(stdout);
+#endif
+	    if ( pending_input == FALSE )
+		timeout = end_of_atomic(TRUE); /* Idle. Do some cleaning up. */
+	    else
+	    {
+		if ( timeout == 0 )
+		    end_of_atomic(FALSE);
+		pending_input = !parse_forgotten();
+	    }
+	    
+	    	    
+	    break;
+	    
+	case ISC_EVENT_LOGIN:
+	    pending_input = TRUE;
+	    login_request(event);
+	    break;
+
+	case ISC_EVENT_LOGOUT:
+	    pending_input = TRUE;
+	    logout_request(event);
+	    break;
+
+ 	case ISC_EVENT_MESSAGE:
+	    pending_input = TRUE;
+	    message_request(event);
+	    break;
+
+	default:
+	    pending_input = TRUE;
+	    log("ERROR: toploop(): Unknown ISC_EVENT\n");
+	    break;
+	}
+
+        isc_dispose(event);
+
+	check_kill_flg();
+    }
+}
diff --git a/src/server/connections.h b/src/server/connections.h
new file mode 100644
index 0000000000000000000000000000000000000000..815ef3afda3404d404451548a7a6d2d4620f70c2
--- /dev/null
+++ b/src/server/connections.h
@@ -0,0 +1,179 @@
+ /*
+  * connections.h -- The top level of the communication packet.
+  *
+  * Requires kom-types.h, com.h
+  */
+
+
+extern Bool go_and_die;		/* Set this to TRUE to kill the server. */
+extern Bool do_sync_db;         /* Set this to TRUE to force a sync via
+				   signal. */
+
+#define CONN_MAGIC_ALLOC	0x18F4AE74
+#define CONN_MAGIC_FREE         0x67A7B144
+
+typedef struct connection {
+    unsigned long       magic;           /* Hey, this is like magic! */
+    
+    struct connection * prev;
+    struct connection * next;
+    
+    /* The following are used by services.c */
+
+    Pers_no		pers_no;         /* 0 if not yet logged in. */
+    Person	      * person;	         /* NULL if not yet logged in. */
+    time_t		session_start;   /* the time when the current session
+					    was started */
+    Conf_no		cwc;	         /* Current working conference */
+    String		what_am_i_doing;
+    u_char		ena_level;       /* Enable level */
+    String		username;        /* Userid and hostname. */
+    String		hostname;        /* Real hostname  */
+
+    /* The following are used by server/connections.c */
+    
+    struct mux         *mux;
+    u_char		protocol;
+
+    int			parse_pos;
+    int			fnc_parse_pos;
+    int			array_parse_pos;
+    int			struct_parse_pos;
+    int			string_parse_pos;
+
+    int			ref_no;
+    Call_header		function; /* Function to call. */
+
+    /* Gather data in these variables. */
+    
+    long		num0;
+    long		num1;
+    long		num2;
+    long		num3;
+    String		c_string0; /* These strings are used for strings
+				    * that are *NOT* freed by services.c.
+				    * They are freed by free_parsed() in
+				    * connections.c */
+    String		c_string1;
+    String		string0; /* This string is used for strings that
+				   * are freed by the routines in services.c */
+    Misc_info	       *c_misc_info_p;	/* Freead by free_parsed(). */
+    Local_text_no      *c_local_text_no_p; /* Freead by free_parsed(). */
+    Priv_bits		priv_bits;
+    Conf_type		conf_type;
+
+    /* Protocol independent things. */
+    
+    String              unparsed;
+    String_size		first_to_parse; /* Index into unparsed. */
+    Bool		more_to_parse;	/* Any chance that there is anything
+					   left in unparsed? */
+    time_t		last_request;   /* Set every time a packet is
+					    received from the client. */
+    
+    Session_no		session_no;      /* A unique number. */
+    Bool                kill_me;        /* Yeah! */
+} Connection;
+/*
+ * It is guaranteed that in the Connection struct the pers_no and person 
+ * fields are either both 0/NULL, or both non-0/non-NULL.
+ */
+
+
+/*
+ * Data for the isc-server.
+ */
+extern struct isc_mcb *kom_server_mcb;
+
+#if 0
+extern Connection *all_connections;
+#endif
+
+/*
+ * active_connection points to the info about the client who initiated this
+ * call. This is set by connectins.c whenever a complete message is parsed
+ * and a function in services.c is called.
+ */
+Connection * active_connection;
+
+
+/*
+ * This enum describes the result of a function in services.c.
+ */
+typedef enum {
+    number,			/* E.g. Pers_no, Conf_no, Text_no, time_t.
+				   This is somewhat special in that the
+				   function indicates an error by returning
+				   0 and not FAILURE. */
+    success,			/* Only return a Success. */
+    /* Functions of the following types returns a Success _and_ data
+       of the indicated type. The corresponding types are found in
+       kom-types.h */
+    person,
+    membership,
+    conf_list,
+    conf_no_list,
+    conference,
+    string,
+    mark_list,
+    text_stat,
+    text_list,
+    who_info_list,
+    who_info_list_old,
+    info,
+    membership_list,
+    member_list,
+    time_date,
+    session_info,
+    session_no
+} Res_type;
+
+/*
+ * The result from a call is stored in this union.
+ */
+typedef union {
+        u_long			  number;
+    	Person			  person;
+	Membership		  membership;
+	Conf_list_old		  conf_list;
+	Conf_no_list		  conf_no_list;
+	Conference		  conference;
+	Mark_list		  mark_list;
+	String			  string;
+	Text_stat		  text_stat;
+	Text_list		  text_list;
+	Info			  info;
+	Who_info_list		  who_info_list;
+	Who_info_list_old	  who_info_list_old;
+	Membership_list		  membership_list;
+	Member_list		  member_list;
+	time_t			  time_date;
+	Session_info		  session_info;
+	Session_no		  session_no;
+} Result_holder;
+
+
+typedef struct {
+    Res_type  result;
+    /* The function that is used to parse args for this function: */
+    void      (*parser)(Connection *client);
+} Fnc_descriptor;
+
+extern const Fnc_descriptor fnc_defs[];
+
+/*
+ * This array holds number of calls to each service. It is dumped to the
+ * log file every time the server syncs.
+ */
+extern u_long service_statistics[];
+
+
+#ifdef _SETJMP_
+extern jmp_buf parse_env;
+#endif
+
+extern void
+toploop(void);
+
+extern void
+dump_statistics(void);
diff --git a/src/server/dbck-cache.c b/src/server/dbck-cache.c
new file mode 100644
index 0000000000000000000000000000000000000000..075087222c3a33decf40e9e86809e02e549d8475
--- /dev/null
+++ b/src/server/dbck-cache.c
@@ -0,0 +1,740 @@
+/*
+ * This module contains some very 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.
+ *   Also save time as a time_t instead of a struct tm.
+ */
+
+/*
+ * 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 "ram-output.h"
+#include "com.h"
+#include "connections.h"
+#include "send-async.h"
+#include "memory.h"
+
+#ifdef TIME_SYNC
+#  include <sys/resource.h>
+#endif
+    
+#define EXPORT
+
+#include "limits.h"
+
+Person		* pers_arr[ MAX_CONF ];
+Conference 	* conf_arr[ MAX_CONF ];
+Conf_type	  conf_type_arr[ MAX_CONF ];
+String		  name_list [ MAX_CONF ]; /* "cache" list */
+			        	  /* Global var auto init to NULL */
+int		  next_free_num = 1;
+
+Text_stat 	* text_arr[ MAX_TEXT ];
+int next_text_num = 1;
+
+/* Defined in ramkomd.c */
+extern char datafilename[1024];
+extern char backupfilename[1024];
+extern char textfilename[1024];
+
+
+static FILE          *text_file = NULL;
+static FILE	     *new_text_file = NULL; /* Used when garbage collecting. */
+
+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)
+#define INRANGE(str, num, retval) if ( num == 0 || num >= next_free_num ) \
+{			\
+    return retval;	\
+    printf("%s: conf_no %d out of range 1 ... %d ", str, num, next_free_num); \
+    fflush(stdout);	\
+    fflush(stderr);	\
+    abort();		\
+}
+
+#define TEXT_RANGE(str, num, retval) if ( num == 0 || num >= next_text_num ) \
+{	\
+    return retval;\
+    printf("Text_no out of range 1 ... %d ", next_text_num); \
+    printf(str);	\
+    fflush(stdout);	\
+    fflush(stderr);	\
+    abort(); 		\
+}
+
+
+extern void
+cached_set_conf_type (Conf_no   conf_no,
+		      Conf_type type)
+{
+    return;		/* No need to do anything in this simulation. */
+}
+
+
+extern Conf_type
+cached_get_conf_type (Conf_no conf_no)
+{
+    return conf_arr [ conf_no ]->type;
+}
+
+
+
+#define IMPL(retval) if (1) { fflush(stderr); fflush(stdout); abort(); }
+
+
+/*
+ * 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);
+    return;    
+}
+
+
+void
+mark_conference_as_changed(Conf_no	conf_no)
+{
+    TRACE2("Conf.  %d is changed\n", conf_no);
+    return;
+}
+
+void
+mark_text_as_changed( Text_no text_no )
+{
+    TRACE2("Text %d is changed.\n", text_no);
+    TEXT_RANGE("mark_text_as_changed\n", text_no, (void)0);
+}    
+
+
+/*
+ * Person-related calls
+ */
+
+
+extern Success
+cached_create_person( Pers_no person )
+{
+    TRACE2("Person %d is being created.\n", person);
+    INRANGE("cached_create_person\n", person, FAILURE);
+
+    if ( pers_arr[ person ] != NULL )
+    {
+	TRACE1("pers_arr not NULL");
+	fflush(stdout);
+	fflush(stderr);
+	abort();
+    }
+    
+    pers_arr[ person ] = alloc_person();
+    return OK;
+}
+
+
+extern Person *
+cached_get_person_stat( Pers_no	person )
+{
+    INRANGE("cached_get_person_stat\n", person, NULL);
+    TRACE2("cached_get_person_stat %d\n", person);
+    kom_errno = KOM_UNDEF_PERS;
+    
+    return pers_arr[ person ];
+}
+
+
+
+/*
+ * 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_c  = alloc_conference();
+
+    conf_c->name = EMPTY_STRING;
+    s_strcpy(&conf_c->name, name);
+
+    TRACE2(" number %d\n", conf_no);
+
+    conf_arr[ conf_no ] = conf_c;
+    pers_arr[ conf_no ] = NULL;
+    
+    return conf_no;
+}
+
+extern Success
+cached_delete_conf( Conf_no	conf )
+{
+    INRANGE("cached_delete_conf()", conf, FAILURE);
+    if ( conf_arr[ conf ] == NULL )
+    {
+	kom_errno = KOM_UNDEF_CONF;
+	return FAILURE;
+    }
+    free_conference(conf_arr[ conf ]);
+    conf_arr[ conf ] = NULL;
+    return OK;
+}
+
+Success
+cached_delete_person(Pers_no pers)
+{
+    INRANGE("cached_delete_person()", pers, FAILURE);
+    if ( pers_arr[ pers ] == NULL )
+    {
+	kom_errno = KOM_UNDEF_PERS;
+	return FAILURE;
+    }
+
+    pers_arr[ pers ] = NULL;
+    return OK;
+}
+
+Success
+cached_delete_text(Text_no text)
+{
+    TEXT_RANGE("cached_delete_text()", text, FAILURE);
+    if ( text_arr[ text ] == NULL )
+    {
+	kom_errno = KOM_NO_SUCH_TEXT;
+	return FAILURE;
+    }
+    free_text_stat(text_arr[ text ]);
+    text_arr[ text ] = NULL;
+    return OK;
+    
+}
+
+
+extern Conference *
+cached_get_conf_stat(	Conf_no		conf_no )
+{
+    INRANGE("cached_get_conf_stat\n", conf_no, NULL);
+    TRACE2("cached_get_conf_stat %d\n", conf_no);
+    kom_errno = KOM_UNDEF_CONF;
+    
+    return conf_arr[ conf_no ];
+}
+
+/*
+ * Return TRUE if conf_no exists.
+ */
+Bool
+cached_conf_exists(Conf_no conf_no)
+{
+    return conf_arr[ conf_no ] != NULL ? TRUE : FALSE;
+}
+
+    
+/*
+ * Calls to handle texts
+ */
+
+extern String
+cached_get_text( Text_no text )
+{
+    String  the_string;
+
+    TEXT_RANGE("cached_get_text\n", text, EMPTY_STRING);
+    TRACE2("cached_get_text %d\n", text);
+
+    if ( text_arr[ text ] == NULL )
+	return EMPTY_STRING;
+    else
+    {
+	the_string.string = tmp_alloc( text_arr[text]->no_of_chars );
+	the_string.len = text_arr[text]->no_of_chars;
+	fseek(text_file, text_arr[ text ]->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 the_string;
+    }
+}
+
+extern Text_stat *	/* NULL on error */
+cached_get_text_stat(	Text_no		text )
+{
+    kom_errno = KOM_NO_SUCH_TEXT;
+    TEXT_RANGE("cached_get_text_stat\n", text, NULL);
+    TRACE2("cached_get_text_stat %d\n", text);
+    
+    return text_arr[ text ];
+}
+
+
+
+/*
+ * The text is set up with an empty misc-field. The misc field is
+ * later initialized by create_text.
+ */
+
+#if 0
+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_text_stat();
+    text_arr[ tno ]->no_of_misc = 0;
+    text_arr[ tno ]->misc_items = NULL;
+    text_arr[ tno ]->no_of_marks = 0;
+    text_arr[ tno ]->no_of_lines = 0;
+    text_arr[ tno ]->no_of_chars = 0;
+    fseek(text_file, 0, SEEK_END);
+    text_arr[ tno ]->file_pos = ftell(text_file);
+
+    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;
+}
+#endif
+
+extern void
+cached_flush_text(Text_no text_no,
+		  String message)
+{
+    if ( text_no >= next_text_num )
+    {
+	log("cached_flush_text(%lu, string): only text %lu exists.",
+	    (u_long) text_no, (u_long) next_text_num);
+	return;
+    }
+        
+    fseek(new_text_file, 0, SEEK_END);
+    text_arr[ text_no ]->file_pos = ftell(new_text_file);
+
+    if ( fwrite(message.string, sizeof(char), message.len, new_text_file)
+	!= message.len )
+    {
+	log("WARNING: cached_flush_text: Couldn't write the text %d\n",
+	    text_no);
+    }
+
+    fflush(new_text_file);
+}
+
+
+Text_no
+traverse_text(Text_no seed)
+{
+    seed++;
+    
+    while ( seed < next_text_num && text_arr[ seed ] == NULL )
+	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 )
+	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 )
+	seed++;
+
+    return (seed == next_free_num) ? 0 : seed ;
+}
+
+extern Garb_nice
+cached_get_garb_nice (Conf_no conf_no)
+{
+    return conf_arr [ conf_no ]->nice;
+}
+
+extern Local_text_no
+cached_get_highest_local_no (Conf_no conf_no)
+{
+    return ( conf_arr [ conf_no ]->texts.first_local_no
+	    - 1 + conf_arr [ conf_no ]->texts.no_of_texts );
+}
+
+/* Lock a person struct in memory. Increase a referenc count. */
+void
+cached_lock_person(Pers_no pers_no)
+{}
+
+/* Decrease reference count. If zero, unlock person. */
+void
+cached_unlock_person(Pers_no pers_no)
+{}
+
+/* Lock a conf struct in memory. Increase a referenc count. */
+void
+cached_lock_conf(Conf_no conf_no)
+{}
+
+/* Decrease reference count. If zero, unlock conf. */
+void
+cached_unlock_conf(Conf_no conf_no)
+{}
+
+
+
+
+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;
+    }
+}
+
+#ifdef TIME_SYNC
+static long
+timerdiff(struct timeval a,
+	   struct timeval b)
+{
+    return a.tv_sec - b.tv_sec;
+}
+#endif
+
+extern void			/* Write out everything. */
+cache_sync(void)
+{
+    FILE *fp;
+    int i;
+
+#ifdef TIME_SYNC
+    struct rusage start, after_confs, after_persons, after_text_stats,
+    	after_text_masses;
+    time_t st, ac, ap, ats, atm;
+    void getrusage(int who, struct rusage *rusage);
+#endif
+    
+#ifdef TIME_SYNC
+    getrusage(0, &start);
+    time(&st);
+#endif
+
+    if ( is_clean(datafilename) )
+    {
+	if ( rename(datafilename, backupfilename) != 0 )
+	    log("WARNING: cache_sync: can't backup.\n");
+    }
+    else
+	log("cache_sync: datafile not clean. No backup taken.\n");
+    
+    if ( (fp=fopen(datafilename, "w") ) == NULL )
+    {
+	log("WARNING: cache_sync: can't open file to save in.\n");
+	return;
+    }
+
+    fprintf(fp, "DIRTY\n");		 /* DIRTY-FLAG */
+
+    fprintf(fp, "%d\n", next_free_num);	  /* NEXT_FREE_NUM */
+
+    for ( i = 1; i < next_free_num; i++ ) /* CONFS */
+    {
+	if ( conf_arr[ i ] == NULL )
+	    fprintf(fp, "@");
+	else
+	{
+	    fprintf(fp, "+ ");
+	    foutput_conference(fp, conf_arr[ i ]);
+	}
+	putc('\n', fp);
+    }
+#ifdef TIME_SYNC
+    getrusage(0, &after_confs);
+    time(&ac);
+#endif
+
+    for ( i = 1; i < next_free_num; i++ ) /* PERSONS */
+    {
+	if ( pers_arr[ i ] == NULL )
+	    fprintf(fp, "@");
+	else
+	{
+	    fprintf(fp, "+ %dH", PASSWD_LEN);
+	    fwrite(pers_arr[ i ]->pwd, PASSWD_LEN, 1, fp);
+	    foutput_person(fp, pers_arr[ i ]);
+	}
+	putc('\n', fp);
+    }
+#ifdef TIME_SYNC
+    getrusage(0, &after_persons);
+    time(&ap);
+#endif
+
+    fprintf(fp, "%d\n", next_text_num);	/* NEXT_TEXT_NUM */
+
+    for ( i = 1; i < next_text_num; i++ ) /* TEXT_STATS */
+    {
+	if ( text_arr[ i ] == NULL )
+	    fprintf(fp, "@");
+	else
+	{
+	    fprintf(fp, "+ ");
+	    foutput_text_stat(fp, text_arr[ i ]);
+	}
+	putc('\n', fp);
+    }
+
+#ifdef TIME_SYNC
+    getrusage(0, &after_text_stats);
+    time(&ats);
+
+    getrusage(0, &after_text_masses);
+    time(&atm);
+#endif
+
+    rewind(fp);
+    fprintf(fp, "CLEAN");
+    fclose(fp);
+
+#ifdef TIME_SYNC
+    log("Sync ready.\n"
+    	"User: %4ld sec (%4ld conf, %4ld pers, %4ld stat, %4ld text)\n"
+    	"Sys:  %4ld sec (%4ld conf, %4ld pers, %4ld stat, %4ld text)\n"
+	"Real: %4ld sec (%4ld conf, %4ld pers, %4ld stat, %4ld text)\n"
+	"Page faults: %4ld. Swapped: %4ld. Outblocked: %4ld.\n",
+	
+	timerdiff(after_text_masses.ru_utime, start.ru_utime),
+	timerdiff(after_confs.ru_utime, start.ru_utime),
+	timerdiff(after_persons.ru_utime, after_confs.ru_utime),
+	timerdiff(after_text_stats.ru_utime, after_persons.ru_utime),
+	timerdiff(after_text_masses.ru_utime, after_text_stats.ru_utime),
+	
+	timerdiff(after_text_masses.ru_stime, start.ru_stime),
+	timerdiff(after_confs.ru_stime, start.ru_stime),
+	timerdiff(after_persons.ru_stime, after_confs.ru_stime),
+	timerdiff(after_text_stats.ru_stime, after_persons.ru_stime),
+	timerdiff(after_text_masses.ru_stime, after_text_stats.ru_stime),
+
+	(u_long)difftime(atm, st),
+	(u_long)difftime(ac, st),
+	(u_long)difftime(ap, ac),
+	(u_long)difftime(ats, ap),
+	(u_long)difftime(atm, ats),
+
+	after_text_masses.ru_majflt - start.ru_majflt,
+	after_text_masses.ru_nswap - start.ru_nswap,
+	after_text_masses.ru_oublock - start.ru_oublock);
+#endif    
+}
+
+extern void
+cache_open_new_text_file(void)
+{
+    if ( ( new_text_file = fopen(textfilename, "w")) == NULL )
+    {
+	log("Can't open file to save new texts. Goodbye.\n");
+	exit(1);
+    }
+}
+
+extern Success
+init_cache(void)
+{
+    FILE *fp = NULL;
+    int i;
+    extern int vflag;		/* from dbck.c */
+
+    new_text_file = NULL;
+    
+    if ( (text_file = fopen(textfilename, "rb")) == NULL )
+    {
+	restart_kom("ERROR: init_cache: can't open text file.\n");
+    }
+
+    if ( is_clean(datafilename) )
+    {
+	if ( (fp = 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 ( (fp = 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(fp, 6, SEEK_SET);	/* skip clean/dirty flag. */
+
+    next_free_num = fparse_long(fp);	  /* NEXT_FREE_NUM */
+
+    if ( vflag )
+	log("Reading %d conferences, starting at pos %d.\n",
+	    next_free_num-1, ftell(fp));
+
+    for ( i = 1; i < next_free_num; i++ ) /* CONFS */
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '@':
+	    conf_arr[ i ] = NULL;
+	    break;
+
+	case '+':
+	    conf_arr[ i ] = alloc_conference();
+	    if ( fparse_conference(fp, conf_arr[ i ]) != OK )
+		restart_kom("init_cache(): fparse_conference failed. "
+			    "i == %d\n", i);
+
+	    name_list[i] = EMPTY_STRING;
+	    s_strcpy(&name_list[i], conf_arr[ i ]->name);
+	    
+	    break;
+	}
+    }
+
+    if ( vflag )
+	log("Reading %d persons, starting at pos %d.\n",
+	    next_free_num-1, ftell(fp));
+
+    for ( i = 1; i < next_free_num; i++ ) /* PERSONS */
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '@':
+	    pers_arr[ i ] = NULL;
+	    break;
+
+	case '+':
+	    pers_arr[ i ] = alloc_person();
+	    if ( fparse_person(fp, pers_arr[ i ]) != OK )
+		restart_kom("init_cache: fparse_person failed. i == %d\n", i);
+	    
+	    break;
+	}
+    }
+
+    next_text_num = fparse_long(fp);	/* NEXT_TEXT_NUM */
+
+    if ( vflag )
+	log("Reading %d texts, starting at pos %d.\n",
+	    next_text_num-1, ftell(fp));
+
+    for ( i = 1; i < next_text_num; i++ ) /* TEXT_STATS */
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '@':
+	    text_arr[ i ] = NULL;
+	    break;
+
+	case '+':
+	    text_arr[ i ] = alloc_text_stat();
+	    if ( fparse_text_stat(fp, text_arr[ i ]) != OK )
+		restart_kom("init_cache(): fparse_text_stat failed. i == %d\n",
+			    i);
+	    break;
+	}
+    }
+
+    log("Read %d confs/persons and %d texts, eof at %d\n",
+	next_free_num-1, next_text_num-1, ftell(fp));
+
+    fclose(fp);
+
+    return OK;
+}
diff --git a/src/server/dbck-cache.h b/src/server/dbck-cache.h
new file mode 100644
index 0000000000000000000000000000000000000000..0db145163b82898c714ec5033cfcb4bf1fa1d309
--- /dev/null
+++ b/src/server/dbck-cache.h
@@ -0,0 +1,8 @@
+extern void
+cached_flush_text(Text_no text_no,
+		  String message);
+
+extern void
+cache_open_new_text_file(void);
+
+
diff --git a/src/server/dbck.c b/src/server/dbck.c
new file mode 100644
index 0000000000000000000000000000000000000000..71dac1cd68a38d2b18cd2e8c439a5d547d102342
--- /dev/null
+++ b/src/server/dbck.c
@@ -0,0 +1,1223 @@
+/*
+ * dbck.c - A simple database checker and corrector.
+ *
+ * Author: Per Cederqvist.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <kom-types.h>
+#include <config.h>
+#include "cache.h"
+#include "log.h"
+#include "lyskomd.h"
+#include "misc-parser.h"
+#include "smalloc.h"
+#include <debug.h>
+#include "dbck-cache.h"
+
+char datafilename[1024];   /* Full pathname to the database file */
+char backupfilename[1024]; /* Full pathname to the backup file */
+char textfilename[1024];
+char textbackupfilename[1024]; /* unshrinked text-file. */
+static const char *dbase_dir = NULL;     /* Directory where database resides */
+
+#define TEXTBACKUPFILE_NAME "db/backup-texts"
+
+int vflag=0;			/* Verbose - list statistics also. */
+int iflag=0;			/* Interactive - prompt user and repair. */
+int rflag=0;			/* Repair simple error without confirmation. */
+int gflag=0;			/* Garbage collect text-file. */
+	
+int modifications = 0;
+
+typedef struct {
+    int	created_confs;
+} Person_scratchpad;
+
+static const Person_scratchpad   EMPTY_PERSON_SCRATCHPAD = { 0 };
+
+#include "limits.h"
+
+static Person_scratchpad *person_scratchpad[MAX_CONF];
+
+#ifdef DEBUG
+int	buglevel = 0;
+#endif
+
+extern void
+log (const char * format, ...)
+{
+    va_list AP;
+
+    va_start(AP, format);
+
+    vfprintf(stdout, format, AP);
+
+    va_end(AP);
+}
+
+extern void
+restart_kom (const char * format, ...)
+{
+    va_list AP;
+
+    va_start(AP, format);
+
+    vfprintf(stdout, format, AP);
+
+    va_end(AP);
+    exit(1);
+}
+
+static Person_scratchpad *
+alloc_person_scratchpad(void)
+{
+    Person_scratchpad *p;
+
+    p = smalloc(sizeof(Person_scratchpad));
+    *p = EMPTY_PERSON_SCRATCHPAD;
+    return p;
+}
+
+
+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;
+}
+
+static Bool
+is_commented_in(Text_no    parent,
+		Text_stat *child)
+{
+    int i;
+
+    for ( i = 0; i < child->no_of_misc; i++ )
+    {
+	switch( child->misc_items[ i ].type )
+	{
+	case comm_to:
+	    if ( child->misc_items[ i ].datum.comment_to == parent )
+		return TRUE;
+	    break;
+	default:
+	    break;
+	}
+    }
+
+    return FALSE;
+}
+	
+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;
+}
+
+static Bool
+is_footnoted_in(Text_no    parent,
+		Text_stat *child)
+{
+    int i;
+
+    for ( i = 0; i < child->no_of_misc; i++ )
+    {
+	switch( child->misc_items[ i ].type )
+	{
+	case footn_to:
+	    if ( child->misc_items[ i ].datum.footnote_to == parent )
+		return TRUE;
+	    break;
+	default:
+	    break;
+	}
+    }
+
+    return FALSE;
+}
+
+Member *
+locate_member(Pers_no      pers_no,
+	      Conference * conf_c)
+{
+    Member * member;
+    int      i;
+
+    for(member = conf_c->members.members, i = conf_c->members.no_of_members;
+	i > 0; i--, member++)
+    {
+	if ( member->member == pers_no )
+	{
+	    return member;
+	}
+    }
+
+    return NULL;
+}
+
+/*
+ * Delete a misc_info.
+ * 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
+delete_misc (Text_stat *tstat,
+	     Misc_info *misc)	/* Pointer to first misc_item to delete. */
+{
+    int del = 1;		/* Number of items to delete. */
+    				/* Always delete at least one item. */
+    Bool ready;
+    
+    /* Check range of misc */
+
+    if (misc < tstat->misc_items
+	|| misc >= tstat->misc_items + tstat->no_of_misc )
+    {
+	restart_kom("delete_misc() - misc out of range");
+    }
+
+    ready = FALSE;
+    
+    while (ready == FALSE
+	   && misc + del < tstat->misc_items + tstat->no_of_misc )
+    {
+	switch ( misc[ 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("delete_misc() - illegal misc found.\n");
+#endif
+	}
+    }
+
+    tstat->no_of_misc -= del;
+
+    /* Move items beyond the deleted ones. */
+
+    while ( misc < tstat->misc_items + tstat->no_of_misc )
+    {
+	misc[ 0 ] = misc[ del ];
+	misc++;
+    }
+}
+
+static int
+confirm(char *question)
+{
+    if ( iflag )
+    {
+	fputs(question, stdout);
+	fputs(" (y/n) ", stdout);
+	while(1)
+	    switch(getchar())
+	    {
+	    case 'y':
+	    case 'Y':
+		return 1;
+	    case 'n':
+	    case 'N':
+	    case EOF:
+		return 0;
+	    default:
+		break;
+	    }
+    }
+    else
+	return 0;
+}
+	
+    
+static long
+check_misc_infos(Text_no    tno,
+		 Text_stat *tstat)
+{
+    const Misc_info   * misc = tstat->misc_items;
+    Misc_info   * previous;
+    Misc_info_group group;
+    Conference *c;
+    Text_stat *t;
+
+    long error=0;
+
+    while (previous = (Misc_info *)misc,
+	   group = parse_next_misc(&misc,
+				   tstat->misc_items + tstat->no_of_misc),
+	   group.type != m_end_of_list && group.type != m_error )
+    {
+	switch ( group.type )
+	{
+	case m_recpt:
+	    c = cached_get_conf_stat (group.recipient);
+	    if ( c == NULL && group.recipient == 0 )
+	    {
+		log ("Conference 0 is recipient to text %lu.\n", (u_long)tno);
+		if (rflag || confirm("Repair by deleting misc_item? "))
+		{
+		    delete_misc(tstat, previous);
+		    mark_text_as_changed(tno);
+		    modifications++;
+		    log("Repaired: Conference 0 is no longer a recipient.\n");
+		    misc = previous;
+		}
+		else
+		    error++;
+		
+		break;
+	    }
+
+	    if ( c == NULL )
+		break;
+	    
+	    /* Check loc_no */
+	    if ( group.local_no < c->texts.first_local_no )
+	    {
+		log("Text %lu: Recipient %lu<%lu> loc_no is less than %lu\n",
+		    (u_long)tno, (u_long)group.recipient,
+		    (u_long)group.local_no,
+		    (u_long)c->texts.first_local_no);
+		error++;
+	    }
+	    else if ( c->texts.first_local_no + c->texts.no_of_texts - 1
+		     < group.local_no )
+	    {
+		log("Text %lu: Recipient %lu<%lu> loc_no"
+		    " is greater than %lu\n",
+		    (u_long)tno, (u_long)group.recipient,
+		    (u_long)group.local_no,
+		    (u_long)(c->texts.first_local_no
+			     + c->texts.no_of_texts - 1));
+		error++;
+	    }
+	    else if ( c->texts.texts[group.local_no
+				     - c->texts.first_local_no] != tno )
+	    {
+		log("Text %lu: Recipient %lu<%lu>: that local number "
+		    "is mapped to %lu.\n",
+		    (u_long)tno, (u_long)group.recipient,
+		    (u_long)group.local_no,
+		    (u_long)c->texts.texts[group.local_no
+					   - c->texts.first_local_no]);
+		error++;
+	    }
+
+	    break;
+
+	case m_cc_recpt:
+	    c = cached_get_conf_stat (group.cc_recipient);
+	    if ( c == NULL && group.cc_recipient == 0 )
+	    {
+		log ("Conference 0 is cc_recipient to text %lu.\n",
+		     (u_long)tno);
+		if (rflag || confirm("Repair by deleting misc_item? "))
+		{
+		    delete_misc(tstat, previous);
+		    mark_text_as_changed(tno);
+		    modifications++;
+		    log("Repaired: Conference 0 is no longer "
+			"a cc_recipient.\n");
+		    misc = previous;
+		}
+		else
+		    error++;
+		
+		break;
+	    }
+
+	    if ( c == NULL )
+		break;
+	    
+	    /* Check loc_no */
+	    if ( group.local_no < c->texts.first_local_no )
+	    {
+		log("Text %lu: CC_Recipient %lu<%lu> is less than %lu\n",
+		    (u_long)tno, (u_long)group.cc_recipient,
+		    (u_long)group.local_no,
+		    (u_long)c->texts.first_local_no);
+		error++;
+	    }
+	    else if ( c->texts.first_local_no + c->texts.no_of_texts - 1
+		     < group.local_no )
+	    {
+		log("Text %lu: CC_Recipient %lu<%lu> loc_no is "
+		    "greater than %lu\n",
+		    (u_long)tno, (u_long)group.cc_recipient,
+		    (u_long)group.local_no,
+		    (u_long)(c->texts.first_local_no
+			     + c->texts.no_of_texts - 1));
+		error++;
+	    }
+	    else if ( c->texts.texts[group.local_no
+				     - c->texts.first_local_no] != tno )
+	    {
+		log("Text %lu: CC_Recipient %lu<%lu>: that local "
+		    "number is mapped to %lu.\n",
+		    (u_long)tno, (u_long)group.cc_recipient,
+		    (u_long)group.local_no,
+		    (u_long)c->texts.texts[group.local_no
+					   - c->texts.first_local_no]);
+		error++;
+	    }
+
+	    break;
+
+	case m_comm_to:
+	    t = cached_get_text_stat(group.comment_to);
+
+	    if ( t == NULL )
+	    {
+		log("Text %lu is a comment to %lu, which doesn't exist.\n",
+		    (u_long)tno, (u_long)group.comment_to);
+
+		if (rflag || confirm("Repair by deleting misc_item? "))
+		{
+		    delete_misc(tstat, previous);
+		    mark_text_as_changed(tno);
+		    modifications++;
+		    log("Repaired: Comment-link deleted.\n");
+		    misc = previous;
+		}
+		else
+		    error++;
+		    
+		error++;
+	    }
+	    else if (!is_comment_to(tno, t))
+	    {
+		log("Text %lu is a comment to %lu, but not the reverse.\n",
+		    (u_long)tno, (u_long)group.comment_to);
+		error++;
+	    }
+
+	    break;
+
+	case m_comm_in:
+	    t = cached_get_text_stat(group.commented_in);
+
+	    if ( t == NULL )
+	    {
+		log("Text %lu is commented in %lu, which doesn't exist.\n",
+		    (u_long)tno, (u_long)group.commented_in);
+
+		if (rflag || confirm("Repair by deleting misc_item? "))
+		{
+		    delete_misc(tstat, previous);
+		    mark_text_as_changed(tno);
+		    modifications++;
+		    log("Repaired: Comment-link deleted.\n");
+		    misc = previous;
+		}
+		else
+		    error++;
+	    }
+	    else if (!is_commented_in(tno, t))
+	    {
+		log("Text %lu is a comment to %lu, but not the reverse.\n",
+		    (u_long)tno, (u_long)group.commented_in);
+		error++;
+	    }
+
+	    break;
+
+	case m_footn_to:
+	    t = cached_get_text_stat(group.footnote_to);
+
+	    if ( t == NULL )
+	    {
+		log("Text %lu is a footnote to %lu, which doesn't exist.\n",
+		    (u_long)tno, (u_long)group.footnote_to);
+
+		if (rflag || confirm("Repair by deleting misc_item? "))
+		{
+		    delete_misc(tstat, previous);
+		    mark_text_as_changed(tno);
+		    modifications++;
+		    log("Repaired: Footnote-link deleted.\n");
+		    misc = previous;
+		}
+		else
+		    error++;
+	    }
+	    else if (!is_footnote_to(tno, t))
+	    {
+		log("Text %lu is a footnote to %lu, but not the reverse.\n",
+		    (u_long)tno, (u_long)group.footnote_to);
+		error++;
+	    }
+
+	    break;
+	    
+	case m_footn_in:
+	    t = cached_get_text_stat(group.footnoted_in);
+
+	    if ( t == NULL )
+	    {
+		log("Text %lu is footnoted in %lu, which doesn't exist.\n",
+		    (u_long)tno, (u_long)group.footnoted_in);
+
+		if (rflag || confirm("Repair by deleting misc_item? "))
+		{
+		    delete_misc(tstat, previous);
+		    mark_text_as_changed(tno);
+		    modifications++;
+		    log("Repaired: Footnote-link deleted.\n");
+		    misc = previous;
+		}
+		else
+		    error++;
+	    }
+	    else if (!is_footnoted_in(tno, t))
+	    {
+		log("Text %lu is a footnot to %lu, but not the reverse.\n",
+		    (u_long)tno, (u_long)group.footnoted_in);
+		error++;
+	    }
+
+	    break;
+
+	default:
+	    log("check_misc_infos(): parse_next_misc returned type %lu\n",
+		(u_long)group.type);
+	    break;
+	}
+    }
+
+    if ( group.type == m_error )
+    {
+	log("Text %lu has a bad misc_info_list.\n", (u_long)tno);
+	error++;
+    }
+
+    return error;
+}
+
+		    
+		     
+		 
+static long
+check_texts(void)
+{
+    Text_no    ct = 0;
+    Text_stat *ctp=NULL;
+    long	errors = 0;
+    Text_no	number_of_texts = 0;
+    u_long	bytes=0;
+    u_long	max_bytes=0;
+    Text_no	max_text=0;
+
+    while ( ct = traverse_text(ct) )
+    {
+	number_of_texts++;
+	
+	ctp = cached_get_text_stat( ct );
+	if ( ctp == NULL )
+	{
+	    log("Text %lu nonexistent.\n", ct);
+	    errors++;
+	}
+	else
+	{
+	    bytes += ctp->no_of_chars;
+	    if ( ctp->no_of_chars > max_bytes )
+	    {
+		max_bytes = ctp->no_of_chars;
+		max_text = ct;
+	    }
+	    
+	    /* no_of_marks is not yet checked. */
+	    errors += check_misc_infos(ct, ctp);
+	}
+    }
+
+    if (vflag)
+    {
+	if ( number_of_texts == 0 )
+	    log("WARNING: No texts found.\n");
+	else
+	{
+	    log("Total of %lu texts (total %lu bytes, "
+		"average %lu bytes/text).\n",
+		(u_long)number_of_texts,
+		(u_long)bytes,
+		(u_long)(bytes/number_of_texts));
+	    log("Longest text is %lu (%lu bytes).\n",
+		(u_long)max_text, (u_long)max_bytes);
+	}
+    }
+    
+    return errors;
+}
+
+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;
+}
+    
+static int
+check_created_texts(Pers_no pno,
+		    Text_list *created)
+{
+    u_long i;
+    Text_stat *t;
+    int error=0;
+
+    for ( i=0; i < created->no_of_texts; i++ )
+    {
+	if (created->texts[i] != 0)
+	{
+	    t = cached_get_text_stat(created->texts[i]);
+	    if ( t != NULL && t->author != pno)
+	    {
+		log("Person %lu is author of text %lu whose author is %lu.\n",
+		    (u_long)pno, (u_long)created->texts[i],
+		    (u_long)t->author);
+		error++;
+	    }
+
+	    if ( t == NULL )
+	    {
+		log("Person %lu is author of text %lu, which doesn't exist.\n",
+		    (u_long)pno, (u_long)created->texts[i]);
+		if ( rflag ||
+		    confirm("Repair by setting to text_no to 0 in local map"))
+		{
+		    created->texts[i] = 0;
+		    mark_person_as_changed(pno);
+		    modifications++;
+		    log("Repaired: created_texts corrected.\n");
+		}
+		else
+		    error++;
+	    }
+	}
+    }
+
+    if ( created->no_of_texts > 0 && created->texts[0] == 0 )
+    {
+	log("Person %lu has a bad created_texts array. Starts with a 0.\n",
+	    (u_long)pno);
+	if ( rflag || confirm ("Repair by adjusting created_texts"))
+	{
+	    adjust_text_list(created);
+	    mark_person_as_changed(pno);
+	    modifications++;
+	    log("Repaired: created_texts adjusted.\n");
+	}
+	else
+	    error++;
+    }
+    
+    return error;
+}
+
+static int
+check_membership(Pers_no pno,
+		 const Membership *mship)
+{
+    int error=0;
+    Conference *conf;
+    int i;
+    Local_text_no last=0;
+    
+    conf = cached_get_conf_stat(mship->conf_no);
+    if ( conf == NULL )
+    {
+	log("Person %lu is a member in the non-existing conference %lu.\n",
+	    (u_long)pno, (u_long)mship->conf_no);
+	error++;
+    }
+    else
+    {
+	/* Check read texts */
+	if ( mship->last_text_read >
+	    conf->texts.first_local_no + conf->texts.no_of_texts - 1)
+	{
+	    log("Person %lu has read text %lu in conf %lu, "
+		"which only has %lu texts.\n",
+		(u_long)pno,
+		(u_long)mship->last_text_read,
+		(u_long)mship->conf_no,
+		(u_long)(conf->texts.first_local_no
+			 + conf->texts.no_of_texts - 1));
+	    error++;
+	}
+
+	for ( last = i = 0; i < mship->no_of_read; i++)
+	{
+	    if ( mship->read_texts[i] <= last )
+	    {
+		log("Person %lu's membership in %lu is corrupt:"
+		    " read text number %lu<%lu> <= %lu.\n",
+		    (u_long)pno, (u_long)mship->conf_no,
+		    (u_long)mship->read_texts[i], (u_long)i, (u_long)last);
+		error++;
+	    }
+
+	    last = mship->read_texts[i];
+	}
+
+	/* Check that he is a member */
+	if ( locate_member(pno, conf) == NULL )
+	{
+	    log("Person %lu is a member in %lu in which he isn't a member.\n",
+		(u_long)pno, (u_long)mship->conf_no);
+	    error++;
+	}
+    }
+
+    return error;
+}
+
+	    
+
+static int
+check_membership_list(Pers_no pno,
+		      const Membership_list *mlist)
+{
+    int errors=0;
+    int i;
+    
+    for (i = 0; i < mlist->no_of_confs; i++)
+	errors += check_membership(pno, &mlist->confs[i]);
+
+    return errors;
+}
+
+
+static int
+check_persons(void)
+{
+    Pers_no     cp = 0;
+    Person     *pstat=NULL;
+    Conference *cstat=NULL;
+    long	errors = 0;
+    Pers_no	number_of_persons=0;
+
+    while ( cp = traverse_person(cp) )
+    {
+	number_of_persons++;
+	pstat = cached_get_person_stat (cp);
+	cstat = cached_get_conf_stat (cp);
+	
+	if ( pstat == NULL )
+	{
+	    log("Person %lu nonexistent.\n", (u_long)cp);
+	    errors++;
+	}
+	else if (cstat == NULL)
+	{
+	    log("Person %lu has no conference.\n", (u_long)cp);
+	    errors++;
+	}
+	else if (!cstat->type.letter_box)
+	{
+	    log("Person %lu's conference is not a letter_box.\n",
+		(u_long)cp);
+	    errors++;
+	}
+	else
+	{
+	    errors += (check_created_texts(cp, &pstat->created_texts)
+		       + check_membership_list(cp, &pstat->conferences));
+	}
+    }
+
+    if (vflag)
+	log("Total of %lu persons.\n", number_of_persons);
+    
+    return errors;
+}
+
+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;
+}
+
+static int
+check_texts_in_conf(Conf_no cc,
+		    Text_list *tlist)
+{
+    u_long i;
+    Text_stat *t;
+    int error=0;
+
+    for ( i=0; i < tlist->no_of_texts; i++ )
+    {
+	if (tlist->texts[i] != 0)
+	{
+	    t = cached_get_text_stat(tlist->texts[i]);
+	    if ( t == NULL )
+	    {
+		log("Text %lu<%lu> in conference %lu is non-existent.\n",
+		    (u_long)tlist->texts[i],
+		    (u_long)i + tlist->first_local_no,
+		    (u_long)cc);
+
+		if (rflag
+		    || confirm("Repair by setting Text_no to 0 in the map?") )
+		{
+		    tlist->texts[i]=0;
+		    mark_conference_as_changed(cc);
+		    modifications++;
+		    log("Repaired: %lu is no longer a recipient.\n",
+			(u_long)cc);
+		}
+		else
+		    error++;
+	    }
+	    else
+	    {
+		if ( !is_recipient(cc, t) )
+		{
+		    log("Text %lu<%lu> in conference %lu doesn't "
+			"have the conference as recipient.\n",
+			(u_long)tlist->texts[i],
+			(u_long)i + tlist->first_local_no,
+			(u_long)cc);
+
+		    if (confirm("Repair by setting Text_no to 0 in the map?") )
+		    {
+			tlist->texts[i]=0;
+			mark_conference_as_changed(cc);
+			modifications++;
+			log("Repaired: %lu is no longer a recipient.\n",
+			    (u_long)cc);
+		    }
+		    else
+			error++;
+		}
+	    }
+	}
+    }
+
+    if ( tlist->no_of_texts > 0 && tlist->texts[0] == 0 )
+    {
+	log("Conference %lu has a bad Text_list. Starts with a 0.\n",
+	    (u_long)cc);
+	if ( rflag || confirm ("Repair by adjusting text_list"))
+	{
+	    adjust_text_list(tlist);
+	    mark_conference_as_changed(cc);
+	    modifications++;
+	    log("Repaired: text_list adjusted.\n");
+	}
+	else
+	    error++;
+    }
+    
+    return error;
+}
+
+Membership *
+locate_membership(Conf_no     conf_no,
+		  Person    * pers_p)
+{
+    Membership * confp;
+    int    i;
+
+    for(confp = pers_p->conferences.confs, i = pers_p->conferences.no_of_confs;
+	i > 0; i--, confp++)
+    {
+	if ( confp->conf_no == conf_no )
+	{
+	    return confp;
+	}
+    }
+
+    return NULL;
+}
+
+static int
+check_member(Conf_no cc,
+	     Member *memb)
+{
+    Person *pp;
+    int error=0;
+
+    pp = cached_get_person_stat(memb->member);
+    if ( pp == NULL )
+    {
+	log("Person %lu, who is supposed to be a member in conf %lu, "
+	    "is nonexistent.\n",
+	    (u_long)memb->member, (u_long)cc);
+	error++;
+    }
+    else
+    {
+	if ( locate_membership(cc, pp) == NULL )
+	{
+	    log("Person %lu is not a member in conf %lu.\n",
+		(u_long)memb->member,
+		(u_long)cc);
+	    error++;
+	}
+    }
+
+    return error;
+}
+
+static int
+check_member_list(Conf_no cc,
+		  const Member_list *mlist)
+{
+    int errors=0;
+    int i;
+    
+    for (i = 0; i < mlist->no_of_members; i++)
+	errors += check_member(cc, &mlist->members[i]);
+
+    return errors;
+}
+
+
+static int
+check_confs(void)
+{
+    Conf_no     cc = 0;
+    Person     *pstat=NULL;
+    Conference *cstat=NULL;
+    long	errors = 0;
+    Conf_no	number_of_confs = 0;
+
+    while ( (cc = traverse_conference(cc)) != 0 )
+    {
+	number_of_confs++;
+	cstat = cached_get_conf_stat (cc);
+	
+	if ( cstat == NULL )
+	{
+	    log("Conference %lu nonexistent.\n", (u_long)cc);
+	    errors++;
+	}
+	else
+	{
+	    if (cstat->type.letter_box)
+	    {
+		pstat = cached_get_person_stat(cc);
+		if (pstat == NULL)
+		{
+		    log("Mailbox %lu has no person.\n", (u_long)cc);
+		    errors++;
+		}
+	    }
+	    else		/* not letter_box */
+	    {
+		/* Remember that the creator might no longer exist. */
+		if ( person_scratchpad[ cstat->creator ] != NULL )
+		    ++person_scratchpad[ cstat->creator ]->created_confs;
+	    }
+
+	    errors += (check_texts_in_conf(cc, &cstat->texts)
+		       + check_member_list(cc, &cstat->members));
+	}
+    }
+
+    if ( vflag )
+	log("Total of %lu conferences.\n", (u_long)number_of_confs);
+
+    return errors;
+}
+
+static void
+init_person_scratch(void)
+{
+    Pers_no pno = 0;
+    
+    while( (pno = traverse_person(pno)) != 0 )
+    {
+	person_scratchpad[pno] = alloc_person_scratchpad();
+    }
+}
+
+static long
+post_check_persons(void)
+{
+    long errors = 0;
+    
+    Pers_no pers_no = 0;
+    Person *pstat;
+
+    while ( (pers_no = traverse_person(pers_no)) != 0 )
+    {
+	if ( (pstat = cached_get_person_stat(pers_no)) == NULL )
+	{
+	    log("INTERNAL DBCK ERROR: post_check_persons(): can't "
+		"cached_get_person_stat(%d).\n", pers_no);
+	}
+
+	if ( person_scratchpad[pers_no]->created_confs
+	    != pstat->created_confs )
+	{
+	    log("Person %d has created %d conferences, not %d (as said in "
+		"his person-stat).\n",
+		pers_no, person_scratchpad[pers_no]->created_confs,
+		pstat->created_confs);
+	    if ( rflag || confirm("Repair by altering person-stat? ") )
+	    {
+		pstat->created_confs
+		    = person_scratchpad[pers_no]->created_confs;
+		mark_person_as_changed(pers_no);
+		log("Person-stat corrected.\n");
+	    }
+	    else
+		errors++;
+	}
+    }
+
+    return errors;
+}
+
+		
+/*
+ * Returns 0 if the database seems to be correct.
+ */
+static long
+check_data_base(void)
+{
+    long errors;
+
+    init_person_scratch();
+    errors = check_texts() + check_persons() + check_confs();
+    return errors + post_check_persons();
+}
+
+static void
+init_data_base(void)
+{
+    if (dbase_dir == NULL)
+      dbase_dir = DEFAULT_DBASE_DIR;
+    
+    sprintf(datafilename,   "%s/%s", dbase_dir, DATAFILE_NAME);
+    sprintf(backupfilename, "%s/%s", dbase_dir, BACKUPFILE_NAME);
+    sprintf(textfilename, "%s/%s", dbase_dir, TEXTFILE_NAME);
+    sprintf(textbackupfilename, "%s/%s", dbase_dir, TEXTBACKUPFILE_NAME);
+
+    if ( vflag )
+    {
+	log("Database = %s\n", datafilename);
+	log("Backup   = %s\n", backupfilename);
+	log("Text     = %s\n", textfilename);
+	log("Textback = %s\n", textbackupfilename);
+    }
+    
+    if ( init_cache() == FAILURE )
+	restart_kom("Can't find database.\n");
+}
+
+void
+garb_text_file(void)
+{
+    Text_no tno = 0;
+    String text;
+
+    log("Renaming %s to %s", textfilename, textbackupfilename);
+    rename(textfilename, textbackupfilename);
+    log("Writing texts to (new) %s", textfilename);
+    fflush(stdout);
+    fflush(stderr);
+    cache_open_new_text_file();
+
+    while ( (tno = traverse_text(tno)) != 0 )
+    {
+	text = cached_get_text(tno);
+	cached_flush_text(tno, text);
+	free_tmp();
+    }
+    log("Writing datafile with new indexes.\n");
+    fflush(stdout);
+    fflush(stderr);
+    cache_sync();
+    log("Ready.");    
+}
+
+	
+	
+
+
+int
+main (int    argc,
+      char **argv)
+{
+    int i;
+    int errors;
+    BUGDECL;
+
+    for (i = 1; i < argc && argv[i][0] == '-'; i++)
+    {
+	switch (argv[i][1])
+	{
+#ifdef DEBUG
+	case 'd':
+	    buglevel++;
+	    break;
+#endif
+
+	case 'D':		/* Database directory */
+	    dbase_dir = argv[i]+2;
+	    break;
+
+	case 'i':		/* Running interactively. */
+	    iflag++;		/* Will ask user and try to repair. */
+	    break;
+
+	case 'r':		/* Repair simple errors wihtout asking. */
+	    rflag++;
+	    break;
+
+	case 'v':		/* Verbose: report more than errors. */
+	    vflag++;
+	    break;
+
+	case 'g':		/* Garbage collect: compress text-file. */
+	    gflag++;
+	    break;
+
+	default:
+	    restart_kom("usage: dbck [options]\n");
+	}
+    }
+    
+    s_set_storage_management(smalloc, srealloc, sfree);
+
+    init_data_base();
+    errors = check_data_base();
+    if ( iflag )
+	log("Total of %d error%s remains.\n", errors, errors == 1 ? "" : "s");
+    else if ( vflag && errors > 0 )
+	log("%d error%s found.\n", errors, errors == 1 ? "" : "s");
+
+    if ( modifications > 0 )
+    {
+	log("%d modification%s made. Syncing...\n",
+	    modifications, modifications == 1 ? "" : "s");
+	fflush(stdout);
+	fflush(stderr);
+	cache_sync();
+	log("ready.\n");
+    }
+
+    if ( modifications == 0 && errors == 0 && gflag )
+    {
+	log("No errors found. Compressing textfile.\n");
+	fflush(stdout);
+	fflush(stderr);
+	garb_text_file();
+	log("ready.\n");
+    }
+    
+    return errors != 0;
+}
diff --git a/src/server/disk-cache.h b/src/server/disk-cache.h
new file mode 100644
index 0000000000000000000000000000000000000000..ecc0e46c150f71e879c70f222d1ee8fb06950736
--- /dev/null
+++ b/src/server/disk-cache.h
@@ -0,0 +1,4 @@
+/* Returns TRUE when the sync is ready. */
+Bool
+sync_part(void);
+
diff --git a/src/server/disk-end-of-atomic.c b/src/server/disk-end-of-atomic.c
new file mode 100644
index 0000000000000000000000000000000000000000..76218d5018f4f032421aa442c01aac24916c8552
--- /dev/null
+++ b/src/server/disk-end-of-atomic.c
@@ -0,0 +1,36 @@
+/*
+ * end-of-atomic.c
+ *
+ * This is the magic function which is called at the end of every atomic
+ * call to the server. It is responisble for
+ *	Free:ing all tmp_alloc:ated memory
+ *	Throw out some cached data if necessary
+ *	Forget some old texts if necessary
+ *	Save some items to disk if saving
+ *
+ * idle is TRUE if the server has no pending calls. That might be a good time
+ * to forget old texts.
+ */
+
+#include <kom-types.h>
+#include "smalloc.h"
+#include "text-garb.h"
+#include "config.h"
+#include "disk-cache.h"
+
+long
+end_of_atomic( Bool  idle )
+{
+    int timeout = TIMEOUT;
+
+    free_tmp();
+
+    if ( idle )
+	if ( garb_text() == FALSE )
+	    timeout = GARBTIMEOUT;
+
+    if ( sync_part() == FALSE )
+	timeout = SYNCTIMEOUT;
+
+    return timeout;
+}
diff --git a/src/server/end-of-atomic.h b/src/server/end-of-atomic.h
new file mode 100644
index 0000000000000000000000000000000000000000..18877d0d7d9686585127a37ec9abd2b73ad846fe
--- /dev/null
+++ b/src/server/end-of-atomic.h
@@ -0,0 +1,10 @@
+/*
+ * end-of-atomic.c
+ */
+
+/*
+ * Return value X: end_of_atomic() should be called again within the
+ * next X milliseconds.
+ */
+long
+end_of_atomic( Bool  idle );
diff --git a/src/server/exp.h b/src/server/exp.h
new file mode 100644
index 0000000000000000000000000000000000000000..477e54a39f69d48f3174da5f8b024d430ce58712
--- /dev/null
+++ b/src/server/exp.h
@@ -0,0 +1,2 @@
+#define EXPORT
+#define INTERNAL static
diff --git a/src/server/internal-connections.c b/src/server/internal-connections.c
new file mode 100644
index 0000000000000000000000000000000000000000..a7e061390e07d2b5f165b9a44b976f1596030519
--- /dev/null
+++ b/src/server/internal-connections.c
@@ -0,0 +1,203 @@
+/*
+ * internal-connections.c
+ *
+ * Abstract routines on the data type Connection.
+ */
+
+#include "exp.h"
+#include <kom-types.h>
+#include "smalloc.h"
+#include "com.h"
+#include "connections.h"
+#include "internal-connections.h"
+#include "lyskomd.h"
+#include "config.h"
+
+INTERNAL  Connection  *all_connections   = NULL;
+
+/* Used in get_conn_by_number to speed things up. */
+
+INTERNAL  Connection  *last_conn = NULL;
+
+INTERNAL const Connection EMPTY_CONNECTION =
+    ((Connection){0, NULL, NULL, 0, NULL, NO_TIME, 0,
+		      EMPTY_STRING_i, 0, EMPTY_STRING_i, EMPTY_STRING_i,
+		      NULL, '\0',
+		      0, 0, 0, 0, 0, 0, 0,
+		      0, 0, 0, 0, EMPTY_STRING_i, EMPTY_STRING_i,
+		      EMPTY_STRING_i, NULL, NULL, DEFAULT_PRIV_BITS_i,
+		      NULL_CONF_TYPE_i, EMPTY_STRING_i, 0, FALSE, NO_TIME,
+		      0, FALSE});
+
+INTERNAL Connection *
+alloc_connection(void)
+{
+    Connection *res;
+    
+    res = smalloc ( sizeof(Connection) );
+    *res = EMPTY_CONNECTION;
+    return res;
+}
+
+EXPORT  Connection *
+new_client(void)
+{
+    Connection		*c;
+    static Session_no 	 no_of_connection_attempts = 0;
+
+    c = alloc_connection();
+
+    c->magic = CONN_MAGIC_ALLOC;
+    
+    if (all_connections != NULL)
+    {
+	all_connections->prev->next = c;
+	c->prev = all_connections->prev;
+	all_connections->prev = c;
+	c->next = all_connections;
+    }
+    else
+    {
+	c->prev = c;
+	c->next = c;
+    }
+
+    all_connections = c;
+    
+    c->session_no = ++no_of_connection_attempts;
+    c->protocol = '\0';		/* Reserved to mean 'no protocol specified yet' */
+    c->unparsed = EMPTY_STRING;
+    c->first_to_parse = 0;
+    c->more_to_parse = TRUE;
+    c->last_request = NO_TIME;
+    c->pers_no = 0;
+    c->person = NULL;
+    time( & c->session_start );
+    c->cwc = 0;
+    c->what_am_i_doing = EMPTY_STRING;
+    c->ena_level = 0;
+    c->username = EMPTY_STRING;
+    c->kill_me = FALSE;
+    
+    return c;
+}
+
+/*
+ * Only used from logout_client
+ */
+EXPORT  void
+kill_client(Connection *cp)    
+{
+    if ( all_connections == NULL )
+	restart_kom("kill_client(): No clients in all_connections list\n");
+    
+    if (all_connections == all_connections->next)
+    {
+	if (all_connections->prev != all_connections)
+	    restart_kom("kill_client(): all_connections corrupt (LINK)\n");
+
+	if (all_connections != cp)
+	    restart_kom("kill_client(): all_connections corrupt (SINGLE)\n");
+      
+	all_connections = NULL;
+    }
+    else
+    {
+	if (cp->prev == NULL || cp->next == NULL)
+	    restart_kom("kill_client(): all_connections corrupt (NULL)\n");
+      
+	cp->prev->next = cp->next;
+	cp->next->prev = cp->prev;
+	
+	if (all_connections == cp)
+	    all_connections = cp->next;
+    }
+
+    if ( last_conn == cp )
+	last_conn = NULL;
+
+    cp->magic = CONN_MAGIC_FREE;
+    sfree(cp);
+}
+
+#ifdef DEFENSIVE_CHECKS
+
+INTERNAL  void
+check_conn_exists(Connection *foo)
+{
+    /* Check that foo really is active. */
+
+    Connection *c = all_connections;
+    Bool found = FALSE;
+
+    do
+    {
+	if ( c == foo )
+	    found = TRUE;
+
+	c = c->next;
+    }
+    while ( c != all_connections && found == FALSE );
+
+    if ( found == FALSE )
+	restart_kom("get_conn_by_number: foo == %d not found in"
+		    " all_connections.\n", last_conn->session_no);
+}
+
+#endif
+
+/*
+ * Session_nos must NOT be recycled, or this code might break!
+ */
+EXPORT  Connection *
+get_conn_by_number (Session_no session_no)
+{
+    Connection *end;
+
+    if ( last_conn == NULL )
+	last_conn = all_connections;
+    else if ( all_connections == NULL )
+    {
+	restart_kom("get_conn_by_number(%d): last_conn = %d and "
+		    "all_connections == NULL", session_no,
+		    last_conn->session_no);
+    }
+#ifdef DEFENSIVE_CHECKS
+    else
+	check_conn_exists(last_conn);
+#endif
+
+    end = last_conn;
+
+    do
+    {
+	if ( last_conn->session_no == session_no )
+	    return last_conn;
+
+	last_conn = last_conn->next;
+    }
+    while ( last_conn != end);
+
+    return NULL;
+}
+
+
+EXPORT  Session_no
+traverse_connections (Session_no session_no)
+{
+    Connection *prev = NULL;
+
+    if ( all_connections == NULL )
+	return 0;
+
+    prev = get_conn_by_number ( session_no );
+
+    /* prev is NULL if session_no is 0, or if session_no was logged out. */
+
+    if ( prev == NULL )
+	return all_connections->session_no;
+    else if ( prev->next == all_connections )
+	return 0;		/* Full circle. */
+    else
+	return prev->next->session_no;
+}
diff --git a/src/server/internal-connections.h b/src/server/internal-connections.h
new file mode 100644
index 0000000000000000000000000000000000000000..a2885b2f95a68444b4654052a0f7deeb2d311a49
--- /dev/null
+++ b/src/server/internal-connections.h
@@ -0,0 +1,19 @@
+/*
+ * internal-connections.c
+ *
+ * Abstract routines on the data type Connection.
+ */
+
+extern Connection *
+new_client(void);
+
+extern void
+kill_client(Connection *cp);
+
+
+extern Connection *
+get_conn_by_number (Session_no session_no);
+
+
+extern Session_no
+traverse_connections (Session_no session_no);
diff --git a/src/server/internal-services.h b/src/server/internal-services.h
new file mode 100644
index 0000000000000000000000000000000000000000..82eb12352b6a4a6a36cf9092b06a898c791f38c5
--- /dev/null
+++ b/src/server/internal-services.h
@@ -0,0 +1,34 @@
+/*
+ * server/services.h
+ *
+ * F|rv{xla inte denna fil med services.h!
+ *
+ * I denna fil ligger en del funktioner typ do_delete_text som anropas
+ * b}de av servern (n{r en text blir f|r gammal) och av klienten (n{r
+ * en av{ndare kom p} att han inte ville ha texten kvar)
+ *
+ * Funktionerna i denna fil har kontrollerar inte att ACTPERS har r{tt att
+ * g|ra det som g|rs.
+ */
+
+
+extern Bool logins_allowed;	/* Defined in server/services.c */
+
+/*
+ * Create a conference.
+ *
+ * Returns (Conf_no) 0 if there is an error.
+ */
+
+extern Conf_no
+do_create_conf(String	 name,
+	       Pers_no	 creator,
+	       Conf_no	 supervisor,
+	       Conf_no	 super_conf,
+	       Conf_type type);
+
+
+
+extern Success
+do_delete_text(Text_no    text_no,
+	       Text_stat *text_s);
diff --git a/src/server/isc-interface.h b/src/server/isc-interface.h
new file mode 100644
index 0000000000000000000000000000000000000000..56d2b007a264d4648295486382b3a237d208a198
--- /dev/null
+++ b/src/server/isc-interface.h
@@ -0,0 +1,6 @@
+/*
+ * Wrapper around <isc.h> to ensure that ISC_UDGTYPE is always correct.
+ */
+
+#define ISC_UDGTYPE struct mux
+#include <isc.h>
diff --git a/src/server/isc-parse.c b/src/server/isc-parse.c
new file mode 100644
index 0000000000000000000000000000000000000000..1ca6b6e07acf2b53c841003f7275eef90542bce1
--- /dev/null
+++ b/src/server/isc-parse.c
@@ -0,0 +1,32 @@
+/*
+ * Generic parse routines.
+ */
+
+#include <setjmp.h>
+#include <string.h>
+#include <stdio.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include "com.h"
+#include "connections.h"
+#include "isc-parse.h"
+
+int
+parse_char(Connection *client)
+{
+    if ( client->unparsed.len <= client->first_to_parse )
+	longjmp(parse_env, ISC_MSG_INCOMPLETE);
+
+    return client->unparsed.string[ client->first_to_parse++ ];
+}
+	
+
+int
+parse_nonwhite_char(Connection *client)
+{
+    int c;
+
+    while ( strchr(" \t\n\r", c=parse_char(client) ) != NULL )
+	;
+    return c;
+}
diff --git a/src/server/isc-parse.h b/src/server/isc-parse.h
new file mode 100644
index 0000000000000000000000000000000000000000..254bf1a8a466871c5f4c7d52cb04e886785b5e8f
--- /dev/null
+++ b/src/server/isc-parse.h
@@ -0,0 +1,9 @@
+#define ISC_PROTOCOL_ERR 1
+#define ISC_MSG_INCOMPLETE 2
+#define ISC_LOGOUT 3
+
+int
+parse_char(Connection *client);
+
+int
+parse_nonwhite_char(Connection *client);
diff --git a/src/server/kom-types.c b/src/server/kom-types.c
new file mode 100644
index 0000000000000000000000000000000000000000..bf2f48ec5d6f03eb39a735861d06bc6a547ffc76
--- /dev/null
+++ b/src/server/kom-types.c
@@ -0,0 +1,69 @@
+/*
+ *  kom-types.c
+ *		Definition of constants and other thing that
+ *		can't or shouldn't be defined in header files.
+ *
+ *
+ *  Copyright (C) 1990  Lysator Computer Club
+ *			Linkoping University,  Sweden
+ *
+ *  Everyone is granted permission to copy, modify and redistribute
+ *  this code, provided the people they give it to can.
+ *
+ *
+ *  Author:	Thomas Bellman
+ *		Lysator Computer Club
+ *		Linkoping University
+ *		Sweden
+ *
+ *  email:	bellman@Lysator.LiU.SE
+ */
+
+
+#include <stdio.h>
+
+#include <kom-types.h>
+#include <config.h>
+
+#define	EXPORT
+
+
+EXPORT const Conf_list_old	EMPTY_CONF_LIST_OLD = EMPTY_CONF_LIST_OLD_i;
+
+EXPORT  const Pers_list		EMPTY_PERS_LIST = EMPTY_PERS_LIST_i;
+
+EXPORT  const Mark_list		EMPTY_MARK_LIST = EMPTY_MARK_LIST_i;
+
+EXPORT  const Conf_type		NULL_CONF_TYPE	= NULL_CONF_TYPE_i;
+
+EXPORT  const Membership	EMPTY_MEMBERSHIP = EMPTY_MEMBERSHIP_i;
+
+EXPORT  const Membership_list	EMPTY_MEMBERSHIP_LIST = EMPTY_MEMBERSHIP_LIST_i;
+
+EXPORT  const Text_list		EMPTY_TEXT_LIST =  EMPTY_TEXT_LIST_i;
+
+EXPORT  const Member_list	EMPTY_MEMBER_LIST = EMPTY_MEMBER_LIST_i;
+
+EXPORT  const Session_info	EMPTY_SESSION_INFO = EMPTY_SESSION_INFO_i;
+
+EXPORT  const Who_info_old	EMPTY_WHO_INFO_OLD = EMPTY_WHO_INFO_OLD_i;
+
+EXPORT  const Who_info_list_old	EMPTY_WHO_INFO_LIST_OLD =
+    					EMPTY_WHO_INFO_LIST_OLD_i;
+
+EXPORT  const Who_info		EMPTY_WHO_INFO = EMPTY_WHO_INFO_i;
+
+EXPORT  const Who_info_list 	EMPTY_WHO_INFO_LIST = EMPTY_WHO_INFO_LIST_i;
+
+EXPORT  const Priv_bits		DEFAULT_PRIV_BITS = DEFAULT_PRIV_BITS_i;
+
+EXPORT const Personal_flags DEFAULT_PERSONAL_FLAGS = DEFAULT_PERSONAL_FLAGS_i;
+
+
+EXPORT const Person EMPTY_PERSON = EMPTY_PERSON_i;
+
+EXPORT const Conference EMPTY_CONFERENCE = EMPTY_CONFERENCE_i;
+
+EXPORT const Small_conf EMPTY_SMALL_CONF = EMPTY_SMALL_CONF_i;
+
+EXPORT const Text_stat EMPTY_TEXT_STAT = EMPTY_TEXT_STAT_i;
diff --git a/src/server/log.c b/src/server/log.c
new file mode 100644
index 0000000000000000000000000000000000000000..51f90cb6aaf51718d2b797306fcf0dde21722c03
--- /dev/null
+++ b/src/server/log.c
@@ -0,0 +1,40 @@
+/*
+ * log.c
+ *
+ * File created by ceder 1990-05-25.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include "log.h"
+
+/*
+ * Add a string to the log file.
+ */
+
+extern void
+log (const char * format, ...)
+{
+    va_list AP;
+
+    va_start(AP, format);
+    logv(format, AP);
+    va_end(AP);
+}
+
+
+extern void
+logv (const char *format, va_list AP)
+{
+    time_t clock;
+
+    
+    time(&clock);
+
+    fprintf(stderr, "--LOGG-> %s: ", ctime(&clock));
+    vfprintf(stderr, format, AP);
+
+    fflush(stderr);
+}
diff --git a/src/server/log.h b/src/server/log.h
new file mode 100644
index 0000000000000000000000000000000000000000..8bf6d8dcc955dd53a20eeb942fa51c1dcb8d78d4
--- /dev/null
+++ b/src/server/log.h
@@ -0,0 +1,22 @@
+/*
+ * log.h
+ *
+ * File created by ceder 1990-05-25.
+ */
+
+#ifndef __LYSKOM__LOG_H__
+#define __LYSKOM__LOG_H__
+
+/*
+ * Add a string to the log file.
+ */
+
+extern void
+log (const char * format, ...);
+
+#ifdef _STDARG_H
+ extern void
+ logv (const char * format, va_list AP);
+#endif
+
+#endif
diff --git a/src/server/logII.c b/src/server/logII.c
new file mode 100644
index 0000000000000000000000000000000000000000..1f96c471497efa7e554971050f0442004dcbf543
--- /dev/null
+++ b/src/server/logII.c
@@ -0,0 +1,89 @@
+/*
+ * log.c
+ *
+ * File created by ceder 1990-05-25.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+enum 
+{
+    MESSAGE,			/* No action when message_counter is 0. */
+    WARNING,			/* Sync&Restart when message_counter is 0. */
+    RESTART,			/* Sync&Restart immediately. */
+    CRASH			/* Don't sync, but restart immediately. */
+    } log_class;
+
+typedef enum log_class Log_class;
+
+    
+extern void
+log (Log_class	 class,
+     const char *file,
+     const char *function,
+     int	*message_counter,
+     const char *format, ...)
+{
+    va_list AP;
+    time_t clock;
+    Bool log_it = TRUE;
+    Bool restart = FALSE;
+
+    va_start(AP, format);
+    time(&clock);
+
+    switch(log_class)
+    {
+    case MESSAGE:
+	if ( message_counter != NULL && message_counter > 0 )
+	    message_counter--;
+	else
+	    log_it = FALSE;
+	break;
+    case WARNING:
+	if ( message_counter != NULL )
+	    if ( *message_counter > 0 )
+		message_counter--;
+	    else
+		restart = TRUE;
+	break;
+    case RESTART:
+    case CRASH:
+	break;
+#ifndef COMPILE_CHECKS
+    default:
+	fprintf(stderr, "Deep Internal Error: log() called"
+		"with log_class %d.\n", log_class);
+#endif
+    }
+
+    if ( log_it == TRUE )
+    {
+	fprintf(stderr, "--> %-19s %-29s %s", ctime(&clock));
+	vfprintf(stderr, format, AP);
+    }
+
+    if ( log_class == RESTART || (log_class == WARNING
+				  && message_counter != NULL
+				  && *message_counter <= 0))
+    {
+	fprintf(stderr, "++> Syncing.\n");
+	cache_sync();
+    }
+
+    if ( restart == TRUE || log_class == RESTART || log_class == CRASH )
+    {
+	fprintf(stderr, "+++> Restarting LysKOM.\n");
+/* #include "CloseFileDescriptors" */
+	execl("/usr/lyskom/bin/ramkomd", "ramkomd", NULL);
+	fprintf(stderr, "+++> execl() failed.\n");
+	exit(1);
+    }
+
+    va_end(AP);
+}
+
+
diff --git a/src/server/lyskomd.h b/src/server/lyskomd.h
new file mode 100644
index 0000000000000000000000000000000000000000..420e3135abbd0e7259d6a4cbc67f5fc1063695b7
--- /dev/null
+++ b/src/server/lyskomd.h
@@ -0,0 +1,19 @@
+/*
+** Defined in ramkomd.c   /pen
+*/
+extern int listen_client;
+extern int listen_mux;
+
+
+/*
+ * restart_kom is used to try to recover from an impossible error.
+ * This function is in fact never called, unless some cosmic radiation
+ * changes some pointers or suchlike.
+ *
+ * The msg string is sent as a mail to kom@lysator.liu.se
+ * (At least one member in that group should not read his mail using kom...)
+ */
+
+
+extern void
+restart_kom(const char * format, ...);
diff --git a/src/server/manipulate.h b/src/server/manipulate.h
new file mode 100644
index 0000000000000000000000000000000000000000..bbb4d03c758b118ce8892aa7f104e541a57edd34
--- /dev/null
+++ b/src/server/manipulate.h
@@ -0,0 +1,653 @@
+
+/*
+ * manipulate.h
+ *
+ * Skapad av ceder n}gon g}ng efter 1990-05-25, men f|re 1990-07-05.
+ */
+
+/*
+ * Definition of macros used in services.c and manipulate.c
+ */
+
+/* pers_no of the connected person. (0    if not logged in) */
+#define ACTPERS (active_connection->pers_no)
+
+/* person  of the connected person. (NULL if not logged in) */
+#define ACT_P	(active_connection->person)
+
+/* many functions can only be used if ACTPERS is logged in */
+
+#define CHK_LOGIN(errortype)		\
+{					\
+    if ( ! ACTPERS )			\
+    {					\
+	kom_errno = KOM_LOGIN;		\
+	return errortype;		\
+    }					\
+}
+
+
+/* Conference 0 does never exist. */
+
+#define CONF_ZERO(conf, errortype)	\
+{					\
+    if ( (conf) == 0 )			\
+    {					\
+	kom_errno = KOM_CONF_ZERO;	\
+	return errortype;		\
+    }					\
+}
+    
+
+/* Check that a conference exists */
+
+#define CHK_EXIST(conf, errortype)	\
+{					\
+    CONF_ZERO(conf, errortype);		\
+    if ( !cached_conf_exists( conf ))	\
+    {					\
+	kom_errno = KOM_UNDEF_CONF;	\
+	return errortype;		\
+    }					\
+}
+
+
+/* do a cached_get_person_stat */
+
+#define GET_P_STAT(p_stat_p, pers_no, failure)				\
+{									\
+    if ( ((p_stat_p) = cached_get_person_stat( pers_no )) == NULL)	\
+    {									\
+	return (failure);						\
+    }									\
+}
+
+/* do a cached_get_conf_stat */
+
+#define GET_C_STAT(c_stat_p, conf_no, failure)				\
+{									\
+    if ( ((c_stat_p) = cached_get_conf_stat( conf_no )) == NULL)	\
+    {									\
+	return (failure);						\
+    }									\
+}
+
+/* do a cached_get_text_stat */
+
+#define GET_T_STAT(t_stat_p, text_no, failure)				\
+{									\
+    if ( ((t_stat_p) = cached_get_text_stat( text_no )) == NULL)	\
+    {									\
+	return (failure);						\
+    }									\
+}
+
+
+/*
+ * ENA returns TRUE if ACTPERS has his privtype bit set, and has
+ * enabled to at least req_lev.
+ */
+
+#define ENA(privtype, req_lev)	\
+(active_connection->ena_level >=(req_lev) && (ACT_P)->privileges.privtype)
+
+
+
+/*
+ * Add a misc item to a text_status.
+ * The item is put last on the list. Thus this macro should not be used
+ * to add rec_time.
+ *
+ *	Text_stat * text_stat_pointer	Textstatus to modify
+ *	Info_type   type_of_misc	Type om misc_item to add
+ *		    tag_name		Tagname in Info_datum
+ *		    value_of_misc	Value to set tag_name to.
+ */
+#define ADD_MISC(text_stat_pointer, type_of_misc, tag_name, value_of_misc) \
+{									\
+    /* Allocate space */						\
+    text_stat_pointer->misc_items					\
+    = srealloc(text_stat_pointer->misc_items,				\
+	       (++(text_stat_pointer->no_of_misc)) * sizeof(Misc_info));\
+									\
+    /* Set type */							\
+    text_stat_pointer->misc_items[ text_stat_pointer->no_of_misc-1 ]	\
+    .type = type_of_misc;						\
+									\
+    /* Set value */							\
+    text_stat_pointer->misc_items[ text_stat_pointer->no_of_misc-1 ]	\
+    .datum.tag_name = value_of_misc;					\
+}
+
+
+/*
+ * IMPL - returns with error "not implemented yet"
+ */
+
+#define IMPL(code)		\
+{				\
+    kom_errno = KOM_NOT_IMPL;	\
+    return code;		\
+}
+
+/*
+ * A value of the following type are returned from access_perm() and
+ * fast_access_perm(). They are used to see how much ACTPERS is allowed to
+ * read/modify the data of a given conference.
+ *
+ * access_perm() alway returns the "highest" from this list.
+ * fast_access_perm() avoids reading the conference struct. It will only
+ *		return "unlimited" if the conference is secret and ACTPERS
+ *		is not a member of the conference. fast_access_perm() is used
+ *		in lookup_name() which sometimes have to check the access
+ *		permissions of _all_ conferences. If access_perm() was used
+ *		_all_ conference statuses would have to be read in into the
+ *		cache in a single atomic call, and that would mean that the
+ *		cache would overflow if there are many conferences.
+ *		Now only the conference statuses for secret conferences of
+ *		which ACTPERS is not a member have to be read in.
+ */
+
+typedef enum {
+    error,			/* The conf doesn't exist or other error. */
+    none,			/* A secret conference. */
+    read_protected,		/* An rd_prot conference. */
+    limited,			/* An open conference. */
+    member,			/* ACTPERS is a member of the conference. */
+    unlimited			/* ACTPERS is supervisor of the conference.  */
+} Access;
+
+
+
+/*
+ * Functions which manipulate the CONFERENCE struct.
+ */
+
+extern Bool
+legal_passwd(const String pwd);
+
+extern Success
+do_set_passwd( Password        pwd,
+	      const String   new_pwd);
+
+
+extern Success
+chk_passwd( Password   pwd,
+	   const String     s );
+
+
+/*
+ * Locate the Member struct in CONF_C for person PERS_NO
+ */
+
+extern Member *
+locate_member(Pers_no      pers_no,
+	      Conference * conf_c);
+
+
+/*
+ * Return TRUE if ACTPERS is a supervisor to CONF.
+ */
+
+extern Bool		
+is_supervisor(Conf_no      conf,
+	      Conference * conf_c);/* Conference status for CONF or NULL */
+
+
+
+/*
+ * Add text_no to the list of texts in a conference. Return the local number
+ * the text gets in this conference.
+ */
+
+extern Local_text_no
+add_text_in_conf(Conference * conf_c,
+		 Text_no      text_no);
+
+
+/*
+ * 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.
+ */
+
+extern void
+set_loc_no (Conference    * conf_c,
+	    Local_text_no   loc_no,
+	    Text_no	    text_no);
+
+
+
+/*
+ * This function is called whenever a person leaves a conf,
+ * i e when he pepsi():s or logout():s.
+ */
+
+extern void
+leave_conf(void);
+
+
+/*
+ * Change presentation of a conference. If text_no is 0, there will be
+ * no presentation.
+ */
+
+extern Success
+do_set_presentation(Conf_no 	 conf_no,
+		    Conference * conf_c,
+		    Text_no	 text_no);
+
+
+/*
+ * Change motd of a conference. If text_no is 0, there will be
+ * no motd.
+ */
+
+extern Success
+do_set_etc_motd(Conf_no      conf_no,
+		Conference * conf_c,
+		Text_no      text_no);
+
+
+
+/*
+ * Delete a conference. Delete all references to this conf.
+ */
+
+extern void
+do_delete_conf (Conf_no      conf_no,
+		Conference * conf_c); /* Not NULL */
+
+
+/*
+ * Functions that manupulate the PERSON struct
+ */
+
+/*
+ * Find the data about PERS_P:s membership in CONF_NO.
+ * Return NULL if not found
+ */
+
+extern Membership *
+locate_membership(Conf_no     conf_no,
+		  Person    * pers_p);
+
+
+
+/*
+ * Change user_area of a person. If text_no is 0, there will be
+ * no user_area.
+ */
+
+extern Success
+do_set_user_area(Pers_no     pers_no,
+		 Person	   * pers_p,
+		 Text_no     text_no);
+
+/*
+ * Mark a text. No check is done if pers_p is really allowed to mark it.
+ * Use mark_type==0 to unmark the text.
+ */
+
+Success
+do_mark_text(Pers_no    pers_no,
+	     Person    *pers_p,		/* May be NULL */
+	     Text_no    text_no,
+	     Text_stat *text_s,		/* May be NULL */
+	     u_char     mark_type);
+
+/*
+ * Return a pointer to a Mark if pers_no has marked the text text_no.
+ * Otherwise, return NULL.
+ */
+Mark *
+locate_mark(Pers_no pers_no,
+	    Person *pers_p,	/* May be NULL. */
+	    Text_no text_no);
+
+/*
+ * Delete a person. (The mailbox is not deleted);.
+ */
+
+extern Success
+do_delete_pers (Pers_no pers_no);
+
+
+/*
+ * Functions which work on CONFERENCES and/or PERSONS.
+ */
+
+/*
+ * Add a member to a conference. All errorchecking should already
+ * be done when this function is called. The person must not already
+ * be a member of the conference. It is _not_ an error to make WHERE bigger
+ * than the number of conferences the person is a member in.
+ */
+
+extern void
+do_add_member(Conf_no	   conf_no,  /* Conference to add a new member to. */
+	      Conference * conf_c,   /* Conf. status. Must NOT be NULL.  */
+	      Pers_no	   pers_no,  /* Person to be added. */
+	      Person	 * pers_p,   /* Pers. status. Must NOT be NULL. */
+	      u_char	   priority, /* Prioritylevel to assign to this conf */
+	      u_short	   where);    /* Sequence number in the list */
+
+/*
+ * Return TRUE if ACTPERS has enough privileges to access VICTIM's data.
+ * VICTIM is a person or a conference.
+ * Meaning of return values:
+ *	unlimited: ACTPERS is supervisor of VICTIM, or ACTPERS is admin,
+ *		   or ACTPERS is VICTIM
+ *	none:	   VICTIM is secret, and ACTPERS is not a member
+ *	member:	   ACTPERS is a member in VICTIM, but doesn't have unlimited
+ *		   access.
+ *	limited:   otherwise.
+ *	error:	   see kom_errno
+ */
+
+extern Access
+access_perm(Conf_no 	  victim,
+	    Conference	* victim_c);	/* May be NULL */
+
+/*
+ * Fast version of access_perm. This function does not check if ATCPERS is a
+ * supervisor of the conference. This function should be used i calls where
+ * a lot of conferences are checked to avoid readin in conferences from the
+ * cache.
+ *
+ * BUG: If a person is supervisor of a secret conf he is not member in the
+ *	 result will be 'none'.
+ *
+ *	unlimited: ACTPERS is admin, or ACTPERS is VICTIM
+ *	none:	   VICTIM is secret, and ACTPERS is not a member
+ *	member:	   ACTPERS is a member in VICTIM, but doesn't have unlimited
+ *		   access.
+ *	limited:   otherwise.
+ *	error:	   see kom_errno
+ */
+Access
+fast_access_perm(Conf_no victim);
+
+
+/*
+ * Return TRUE if NAME is a legal name
+ */
+
+extern Bool
+legal_name( String name );
+
+
+/*
+ * Return TRUE if NAME is not already used
+ */
+
+extern Bool
+unique_name( const String name, Conf_no conf_no );
+
+
+
+/*
+ * Change the priority of a certain conference in a person.
+ */
+extern void
+do_change_priority (Membership * mship,
+		    u_char	 priority,
+		    u_short	 where,
+		    Pers_no	 pers_no,
+		    Person     * pers_p);
+
+
+/*
+ * Delete a member from a conference.
+ * No checks are made on the parameters.
+ * The dynamically allocated areas conf_c->members.members and
+ * pers_p->confs are NOT reallocated since they will anyhow sooner or later
+ * be flushed from core.
+ */
+
+extern Success
+do_sub_member(Conf_no	   conf_no, /* Conf to delete member from. */
+	      Conference * conf_c,  /* May be NULL */
+	      Member     * member,  /* May be NULL */
+	      Pers_no	   pers_no, /* Person to be deleted. */
+	      Person	 * pers_p,  /* May be NULL */
+	      Membership * mship);   /* Pointer to the persons membership in
+				       conf., or NULL if not known. */
+
+
+/*
+ * Functions which deal with TEXTS
+ */
+
+/*
+ * Count how many recipients and cc_recipients a text has.
+ */
+
+extern int
+count_recipients( Text_stat  *t_stat );
+
+
+/*
+ * Count how many footnotes a text has.
+ */
+
+extern int
+count_footn( Text_stat  *t_stat );
+
+
+/*
+ * Count how many commments a text has.
+ */
+
+extern int
+count_comment( Text_stat  *t_stat );
+
+
+/*
+ * Check if ACTPERS is allowed to read this text.
+ * Returns TRUE if he is allowed to read it.
+ */
+
+extern Bool
+text_read_access(Text_no     text_no,
+		 Text_stat * text_stat);
+
+
+
+/*
+ * Check if CONF_NO is a recipient of the text whose text_stat is given.
+ */
+
+extern Bool
+is_recipient(Conf_no	 conf_no,
+	     Text_stat * t_stat);
+
+
+
+/*
+ * Check if comment is a comment to parent.
+ */
+Bool
+is_comment_to(Text_no    comment,
+	      Text_stat *parent);
+
+
+/*
+ * Check if footnote is a footnote to parent.
+ */
+Bool
+is_footnote_to(Text_no    footnote,
+	       Text_stat *parent);
+
+
+/*
+ * 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.
+ */
+
+extern 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 */
+
+
+/*
+ * Insert a rec_time misc item to a text_status.
+ * The item is put at position POS on the list. (0 == first)
+ * Take no action if the misc_item at POS is a rec_time.
+ * This function is only used when a person marks his letters as read.
+ *
+ *	Text_stat * text_stat_pointer	Textstatus to modify
+ *	int	    pos			Where to insert rec_time
+ */
+
+extern void
+do_add_rec_time (Text_stat    * text_stat_ptr,
+		 int		pos);
+
+
+/*
+ * Say that FOOTNOTE is a footnote to TEXT.
+ */
+
+extern Success
+do_add_footnote(Text_no footnote,
+		Text_no text);
+
+
+
+/*
+ * Say that COMMENT is a comment to TEXT.
+ */
+
+extern Success
+do_add_comment(Text_no comment,
+	       Text_no text);
+
+
+    
+/*
+ * Say that RECEIVER is a recipient of TEXT.
+ */
+extern Success
+do_add_recpt(Text_no 	 text,
+	     Text_stat * text_s, /* May be NULL */
+	     Conf_no	 receiver);
+
+   
+
+extern Success
+do_add_cc_recpt(Text_no new_text,
+		Text_stat *text_s, /* May be NULL */
+		Conf_no receiver);
+
+    
+
+/*
+ * Return number of lines in a text
+ */
+
+extern u_short
+count_lines( String str );
+
+
+
+/*
+ * 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.
+ */
+
+extern void
+do_delete_misc (u_short	  * no_of_misc,
+		Misc_info * misc,
+		int	    loc);
+
+
+/*
+ * Delete a recipient from a text
+ */
+extern Success
+do_sub_recpt (Text_no	     text_no,
+	      Text_stat    * text_s,
+	      Conf_no	     conf_no,
+	      Conference   * conf_s );
+
+
+/*
+ * Delete the link between comment and comment_to.
+ */
+void
+do_sub_comment (Text_no	       comment,
+		Text_stat    * text_s,
+		Text_no	       comment_to,
+		Text_stat    * parent_s );
+
+/*
+ * Delete the link between footnote and footnote_to.
+ */
+void
+do_sub_footnote (Text_no       footnote,
+		Text_stat    * text_s,
+		Text_no	       footnote_to,
+		Text_stat    * parent_s );
+
+
+/*
+ * Check if ACTPERS has sent this text to conference CONF_NO
+ */
+extern Bool
+is_sender(Text_stat * text_s,
+	  Conf_no     conf_no);
+
+
+/*
+ * Check if ACTPERS has sent this text as a comment to parent.
+ */
+Bool
+is_comm_sender(Text_stat * text_s,
+	       Text_no     parent);
+
+
+/*
+ * Check if ACTPERS has sent this text as a footnote to parent.
+ */
+extern Bool
+is_footn_sender(Text_stat * text_s,
+		Text_no     parent);
+
+    
+/*
+ * Check if ACTPERS is allowed to add a footnote to a text. Sets errno if
+ * there is an error.
+ */
+
+extern Success
+check_footn(Text_stat * t_stat);
+
+
+/*
+ * 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.
+ */
+
+extern Success
+check_comm(Text_stat * t_stat);
+
+
+/*
+ * Check if pers_no is a member in any of the recipients or cc_recipients
+ * of text_s
+ */
+extern  Bool
+is_member_in_recpt(Person    * person,
+		   Text_stat * text_s);
+extern void
+forced_leave_conf(Pers_no pers_no,
+		  Conf_no conf_no);
+
diff --git a/src/server/membership.c b/src/server/membership.c
new file mode 100644
index 0000000000000000000000000000000000000000..8a682111fcdfeb163add046e0c939686f811eb03
--- /dev/null
+++ b/src/server/membership.c
@@ -0,0 +1,1169 @@
+/*
+ * membership.c
+ *
+ * All atomic calls that controlls who is a member in what.
+ * (The person/conf relation).
+ */
+#include <time.h>
+#include <stdlib.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include <services.h>
+#include "manipulate.h"
+#include "smalloc.h"
+#include "cache.h"
+#include "log.h"
+#include "minmax.h"
+#include "com.h"
+#include "isc.h"
+#include "connections.h"
+#include "send-async.h"
+#include <kom-errno.h>
+#include "internal-connections.h"
+
+#define DEBUG_MARK_AS_READ
+
+#ifdef DEBUG_MARK_AS_READ
+#  include <stdio.h>
+#  include <string.h>
+#  include "ram-output.h"
+#endif
+
+
+
+
+
+/*
+ * Copy all information that ACTPERS is authorized to know about ORIG_P's
+ * membership in all conferences to CENSOR_P.
+ *
+ * This function is used in get_membership().
+ */
+static void
+copy_public_confs (Person   * censor_p,	/* The censored Person-struct */
+		   Person   * orig_p,	/* The uncensored Person-struct */
+		   Bool	      want_read) /* Does ACTPERS want to know
+					  * which texts are read?*/
+{
+    int i;			/* Number of mships lefte in ORIG_P */
+    Membership * censor_m;		/* Pointer in CENSOR_P */
+    Membership * orig_m;	/* Pointer in ORIG_P */
+    
+
+    /* Copy all information except the secret. */
+		    
+    censor_p->conferences.confs
+	= tmp_alloc( orig_p->conferences.no_of_confs * sizeof(Membership));
+    censor_p->conferences.no_of_confs = 0;
+
+    censor_m = censor_p->conferences.confs;
+    orig_m   = orig_p->conferences.confs;
+
+    for ( i = 0; i < orig_p->conferences.no_of_confs; i++, orig_m++ )
+    {
+	if ( fast_access_perm( orig_m->conf_no ) > none )
+	{
+	    *censor_m = *orig_m;
+
+	    if ( orig_p->flags.unread_is_secret || !want_read )
+	    {
+		censor_m->no_of_read = 0;
+		censor_m->read_texts = NULL;
+	    }
+
+	    if ( orig_p->flags.unread_is_secret )
+	    {
+		censor_m->last_time_read = NO_TIME;
+		censor_m->last_text_read = 0;
+	    }
+	    
+	    ++censor_m;
+	    ++censor_p->conferences.no_of_confs;
+	}
+    }
+}
+
+/*
+ * Change the priority of a certain conference in a person.
+ */
+static void
+do_change_priority (Membership * mship,
+		    u_char	 priority,
+		    u_short	 where,
+		    Pers_no	 pers_no,
+		    Person     * pers_p)
+{
+    Membership tmp_conf;
+    
+    mship->priority = priority;
+    
+    /* Check range of where */
+    
+    if ( where >= pers_p->conferences.no_of_confs )
+    {
+	where = pers_p->conferences.no_of_confs - 1;
+    }
+    
+    /* And now move the conference to slot number 'where' */
+    
+    if ( mship < pers_p->conferences.confs + where )
+    {
+	tmp_conf = *mship;
+	while ( mship < pers_p->conferences.confs + where)
+	{
+	    *mship = *(mship + 1);
+	    mship++;
+	}
+	*mship = tmp_conf;
+    }
+    else
+    {
+	tmp_conf = *mship;
+	while ( mship > pers_p->conferences.confs + where)
+	{
+	    *mship = *(mship - 1);
+	    mship--;
+	}
+	*mship = tmp_conf;
+    }
+
+    mark_person_as_changed( pers_no );
+}
+
+
+/*
+ * Insert a rec_time misc item to a text_status.
+ * The item is put at position POS on the list. (0 == first)
+ * Take no action if the misc_item at POS is a rec_time.
+ * This function is only used when a person marks his letters as read.
+ *
+ *	Text_stat * text_stat_pointer	Textstatus to modify
+ *	int	    pos			Where to insert rec_time
+ */
+
+static void
+do_add_rec_time (Text_stat    * text_stat_ptr,
+		 int		pos)
+{
+    int i;
+
+    /* Defensive checks */
+    if ( pos < 0 || pos > text_stat_ptr->no_of_misc )
+    {
+	restart_kom("do_add_rec_time() - illegal pos\n");
+    }
+
+    /* Check that no rec_time exists */
+
+    if ( pos < text_stat_ptr->no_of_misc
+	&& text_stat_ptr->misc_items[ pos ].type == rec_time )
+    {
+	return;
+    }
+    
+    /* Allocate space */
+    text_stat_ptr->misc_items
+    = srealloc(text_stat_ptr->misc_items,
+	       (++(text_stat_ptr->no_of_misc)) * sizeof(Misc_info));
+
+    /* Move items. */
+
+    for ( i = text_stat_ptr->no_of_misc - 1; i > pos; i-- )
+    {
+	text_stat_ptr->misc_items[ i ] = text_stat_ptr->misc_items[ i - 1 ];
+    }
+
+    /* Set type */
+    text_stat_ptr->misc_items[ pos ].type = rec_time;
+
+    /* Set value */
+    time( & text_stat_ptr->misc_items[ pos ].datum.received_at);
+}
+
+/*
+ * add_rec_time adds a 'rec_time'  misc_item to text number LOC_NO in
+ * conference CONF_NO. The item will follow a recpt or cc_recpt to ACTPERS.
+ * No action is taken if ACTPERS is not a recipient of the text, or the text
+ * no longer exists, or the text has already been received.
+ */
+static void
+add_rec_time(Conf_no	  conf_no,
+	     Conference * conf_c,  /* The structure for conf_no */
+	     Local_text_no local_no)
+{
+    Bool 	  found;
+    Text_no	  text_no;
+    Text_stat 	* t_stat;
+    int		  i;
+    
+    if ( local_no >= conf_c->texts.first_local_no + conf_c->texts.no_of_texts
+	|| local_no < conf_c->texts.first_local_no )
+    {
+	return;			/* No longer exists in conf. */
+    }
+
+    text_no = conf_c->texts.texts[ local_no - conf_c->texts.first_local_no ];
+
+    if ( text_no == 0 )
+    {
+	return;			/* Text is deleted. */
+    }
+    
+    GET_T_STAT(t_stat, text_no, (void)0);
+
+    /* locate the misc_item which says that ACTPERS is a recipient */
+
+    for ( found = FALSE, i = 0; !found && i < t_stat->no_of_misc; i++ )
+    {
+	switch ( t_stat->misc_items[ i ].type )
+	{
+	case recpt:
+	    if ( t_stat->misc_items[ i ].datum.recipient == ACTPERS )
+	    {
+		do_add_rec_time( t_stat, i + 2 ); /* Add after loc_no */
+		found = TRUE;
+	    }
+	    break;
+
+	case cc_recpt:
+	    if ( t_stat->misc_items[ i ].datum.cc_recipient == ACTPERS )
+	    {
+		do_add_rec_time( t_stat, i + 2 );
+		found = 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;
+	}
+    }
+
+    if( found ==  FALSE )
+    {
+	log("ERROR: add_rec_time(): found==FALSE\n");
+    }
+    
+    mark_text_as_changed( text_no);
+    return;
+}
+
+/*
+ * Check if there are some texts immediately following last_text_read
+ * that are read or deleted. If so, update last_text_read and delete them
+ * from read_texts.
+ *
+ * This is only used from mark_as_read().
+ */
+static void
+adjust_read( Membership * m,
+	     const Conference * conf)
+{
+    u_short	i;
+    Local_text_no * locp;
+    Local_text_no   conf_max;	/* Highest used local_text_no in conf */
+    Local_text_no   conf_min;	/* Lowest used local_text_no in conf */
+    Bool ready;
+#ifdef DEFENSIVE_CHECKS
+    Local_text_no prev;
+#endif
+
+    i = 0;	/* Number of texts in read_texts in Membership which are
+		   included in last_text_read. */
+
+    /* (conf_min <= x <= conf_max) if x is an existing local_text_no */
+    conf_max = conf->texts.first_local_no + conf->texts.no_of_texts - 1;
+    conf_min = conf->texts.first_local_no;
+
+    do
+    {
+	ready = TRUE;
+
+	/*  Increase m->last_text_read if
+	 *   * the user has read the text (last_text_read+1)
+	 *   * text (last_text_read+1) is deleted.
+	 */
+
+	if ( m->last_text_read < conf_max) /* Does (last_text_read+1) exist? */
+	{
+	    if ( m->no_of_read > i	       /* Read? */
+		&& m->read_texts[ i ] == m->last_text_read + 1 )
+	    {
+		i++;
+		m->last_text_read++;
+		ready = FALSE;
+	    }
+	    else
+	    {
+		/* Is the text deleted? */
+		if ( m->last_text_read + 1 < conf_min /* Deleted and older
+							 than any text that
+							 exists in the
+							 conference. */
+		    || (conf->texts.texts	      /* Deleted? */
+				[ m->last_text_read + 1 - conf_min ]
+			== 0 ) )
+		{
+		    m->last_text_read++;
+		    ready = FALSE;
+		}
+	    }
+	}
+    } while ( ready == FALSE );
+
+    /* Delete the first i entries in read_texts */
+    if ( i > 0 )
+    {
+	m->no_of_read -= i;
+    
+	for (locp = m->read_texts;
+	     locp  < m->read_texts + m->no_of_read;
+	     locp++)
+	{
+	    *locp = *(locp + i);
+	}
+    }
+
+#ifdef DEFENSIVE_CHECKS
+    
+    /* Check that the items in read_texts really ARE sorted in ascending order.
+       If not, there is probably a bug in this routine or in mark_as_read */
+    
+    prev = m->last_text_read;
+    
+    for ( i = 0; i < m->no_of_read; i++)
+    {
+	if ( prev >= m->read_texts[ i ] )
+	{
+	    log("Bug in adjust_read. Conference %lu, Priority %lu\n"
+		"\tprev = %lu, i = %lu, m->read_texts[i] = %lu\n",
+		(u_long)m->conf_no, (u_long)m->priority,
+		(u_long)prev, (u_long)i, (u_long)m->read_texts[i]);
+	}
+
+	prev = m->read_texts[ i ];
+    }
+#endif
+}
+
+/*
+ * insert TEXT in the list of read_texts in M. The texts are sorted.
+ * m->no_of_read is updated. m->read_texts is never reallocated, and must
+ * thus be big enough to hold the new number.
+ *
+ * Returns FAILURE if the text is already read.
+ *
+ * This is only used from mark_as_read().
+ */
+
+static Success
+insert_loc_no(Local_text_no   text,
+	      Membership    * m)
+{
+    Local_text_no * seek, * move;
+    
+    
+    if ( text <= m->last_text_read )
+    {
+	return FAILURE;			/* This text was already read. */
+    }
+
+    for ( seek = m->read_texts; seek < m->read_texts + m->no_of_read;  seek++)
+    {
+	if ( text == *seek )
+	{
+	    return FAILURE;		/* This text was already read. */
+	}
+
+	if ( text < *seek )
+	{			/* The text should be entered here. */
+	    for ( move = m->read_texts + m->no_of_read; move > seek; move--)
+	    {
+		*move = *(move - 1);
+	    }
+
+	    *seek = text;
+	    ++(m->no_of_read);
+
+	    return OK;
+	}
+    }
+
+    *seek = text;		/* The text had a higher number than any */
+    ++(m->no_of_read);		/* previously read text.		 */
+    
+    return OK;
+}
+
+/*
+ * End of static functions
+ */
+
+/*
+ * Functions that are exported to the server.
+ */
+
+/*
+ * Add a member to a conference. All errorchecking should already
+ * be done when this function is called. The person must not already
+ * be a member of the conference. It is _not_ an error to make WHERE bigger
+ * than the number of conferences the person is a member in.
+ */
+
+void
+do_add_member(Conf_no	   conf_no,  /* Conference to add a new member to. */
+	      Conference * conf_c,   /* Conf. status. Must NOT be NULL.  */
+	      Pers_no	   pers_no,  /* Person to be added. */
+	      Person	 * pers_p,   /* Pers. status. Must NOT be NULL. */
+	      u_char	   priority, /* Prioritylevel to assign to this conf */
+	      u_short	   where)    /* Sequence number in the list */
+{
+    Membership  * mship;
+    Member	* member;
+
+    /* First add the conference in the person-struct.
+     * Make room for it.
+     */
+    
+    pers_p->conferences.confs = srealloc( pers_p->conferences.confs,
+					 ++(pers_p->conferences.no_of_confs)
+					 * sizeof(Membership));
+    
+    /* Fill in the room */
+
+    /* Find last slot */    
+    mship = pers_p->conferences.confs + pers_p->conferences.no_of_confs - 1 ;
+    
+    /* Move all data beyond WHERE */
+    while ( mship > pers_p->conferences.confs + where )
+    {
+	*mship = *(mship - 1);
+	mship--;
+    }
+
+    *mship = EMPTY_MEMBERSHIP;
+    
+    mship->conf_no = conf_no;
+    mship->priority = priority;
+    mship->last_time_read = time(NULL);
+    mship->last_text_read = 0;
+    mship->no_of_read = 0;
+    mship->read_texts = NULL;
+    
+    /* Make room for the person in the conference */
+    
+    conf_c->members.members = srealloc( conf_c->members.members,
+				       ++(conf_c->members.no_of_members)
+				       * sizeof(Member));
+
+    /* New members go to the end of the list */
+    
+    member = (conf_c->members.members
+	      + conf_c->members.no_of_members - 1);
+    member->member = pers_no;
+    
+    mark_conference_as_changed( conf_no );
+    mark_person_as_changed( pers_no );
+    
+    return;
+}
+
+/*
+ * Send an asynchronous message to person pers_no (if he is logged on)
+ * and tell him that he is no longer a member of conf_no. Also calls
+ * leave_conf(). 
+ */
+extern void
+forced_leave_conf(Pers_no pers_no,
+		  Conf_no conf_no)
+{
+    Connection *real_active_connection;
+    Session_no i = 0;
+    
+    real_active_connection = active_connection;
+
+    while ( (i = traverse_connections(i)) != 0 )
+    {
+	active_connection = get_conn_by_number(i);
+
+	if ( active_connection->pers_no == pers_no )
+	{
+	    async_forced_leave_conf(active_connection, conf_no);
+
+	    if ( active_connection->cwc == conf_no )
+		leave_conf();
+	}
+    }
+
+    active_connection = real_active_connection;
+}
+
+/*
+ * Delete a member from a conference.
+ * No checks are made on the parameters.
+ * The dynamically allocated areas conf_c->members.members and
+ * pers_p->confs are NOT reallocated since they will anyhow sooner or later
+ * be flushed from core.
+ */
+
+Success
+do_sub_member(Conf_no	   conf_no, /* Conf to delete member from. */
+	      Conference * conf_c,  /* May be NULL */
+	      Member     * member,  /* May be NULL */
+	      Pers_no	   pers_no, /* Person to be deleted. */
+	      Person	 * pers_p,  /* May be NULL */
+	      Membership * mship)   /* Pointer to the persons membership in
+				       conf., or NULL if not known. */
+{
+    if ( conf_c == NULL )
+	GET_C_STAT(conf_c, conf_no, FAILURE);
+    
+    if ( pers_p == NULL )
+	GET_P_STAT(pers_p, pers_no, FAILURE);
+    
+    if ( mship == NULL && (mship = locate_membership(conf_no, pers_p)) == NULL)
+	restart_kom("do_sub_member() - can't find mship\n");
+
+    if ( member == NULL && (member = locate_member(pers_no, conf_c)) == NULL)
+	restart_kom("do_sub_member() - can't find member.\n");
+
+    forced_leave_conf(pers_no, conf_no);
+    
+    /* Delete from Person */
+
+    sfree( mship->read_texts );
+    --pers_p->conferences.no_of_confs;
+    while ( mship
+	   < pers_p->conferences.confs + pers_p->conferences.no_of_confs )
+    {
+	*mship = *(mship + 1);
+	++mship;
+    }
+
+    /* Delete from Conference */
+
+    --conf_c->members.no_of_members;
+    while ( member < conf_c->members.members + conf_c->members.no_of_members )
+    {
+	*member = *(member + 1);
+	++member;
+    }
+
+    mark_person_as_changed( pers_no );
+    mark_conference_as_changed( conf_no );
+    
+    return OK;    
+}
+
+
+/*
+ * VICTIM is a person or a conference.
+ * Meaning of return values:
+ *	unlimited: ACTPERS is supervisor of VICTIM, or ACTPERS is admin,
+ *		   or ACTPERS is VICTIM
+ *	none:	   VICTIM is secret, and ACTPERS is not a member
+ *	member:	   ACTPERS is a member in VICTIM, but doesn't have unlimited
+ *		   access.
+ *	read_protected: The conference is rd_prot and ACTPERS is not a member.
+ *	limited:   otherwise.
+ *	error:	   see kom_errno
+ */
+
+Access
+access_perm(Conf_no 	  victim,
+	    Conference	* victim_c)	/* May be NULL */
+{
+    if (victim == ACTPERS)
+    	return unlimited;
+
+    if (victim_c == NULL)
+	GET_C_STAT(victim_c, victim, error);
+    
+    if ( ACTPERS != 0 && ENA(admin, 2) )
+    	return unlimited;
+
+    if ( is_supervisor(victim, victim_c))
+    	return unlimited;
+
+    if ( ACTPERS != 0 && locate_membership( victim, ACT_P ) != NULL )
+    	return member;
+
+    if ( victim_c->type.secret )
+        return none;
+
+    if ( victim_c->type.rd_prot )
+	return read_protected;
+    
+    return limited;
+}
+
+
+/*
+ * Fast version of access_perm. See comment in file server/manipulate.h
+ * where Access is defined.
+ */
+Access
+fast_access_perm(Conf_no victim)
+{
+    Conf_type conf_type;
+    
+    if ( ACTPERS != 0 && (ENA(admin, 2) || ENA(wheel,8) || ACTPERS == victim) )
+    	return unlimited;
+
+    if ( ACTPERS != 0 && locate_membership( victim, ACT_P ) != NULL )
+    	return member;
+
+    if ( (conf_type=cached_get_conf_type( victim )).secret )
+	return access_perm(victim, NULL); /* Only read in conference struct
+					   * when really necessary. */
+
+    if ( conf_type.rd_prot )
+	return read_protected;
+    
+    return limited;
+}
+
+/*
+ * Locate the Member struct in CONF_C for person PERS_NO
+ */
+
+Member *
+locate_member(Pers_no      pers_no,
+	      Conference * conf_c)
+{
+    Member * member;
+    int      i;
+
+    for(member = conf_c->members.members, i = conf_c->members.no_of_members;
+	i > 0; i--, member++)
+    {
+	if ( member->member == pers_no )
+	{
+	    return member;
+	}
+    }
+
+    return NULL;
+}
+
+
+/*
+ * Find the data about PERS_P:s membership in CONF_NO.
+ * Return NULL if not found
+ */
+
+Membership *
+locate_membership(Conf_no     conf_no,
+		  Person    * pers_p)
+{
+    Membership * confp;
+    int    i;
+
+    for(confp = pers_p->conferences.confs, i = pers_p->conferences.no_of_confs;
+	i > 0; i--, confp++)
+    {
+	if ( confp->conf_no == conf_no )
+	{
+	    return confp;
+	}
+    }
+
+    return NULL;
+}
+
+/*
+ * Atomic functions.
+ */
+
+/*
+ * Unsubscribe from a conference.
+ *
+ * You must be supervisor of either conf_no or pers_no to be allowed to
+ * do this.
+ *
+ * BUGS: There is no passive membership.
+ */
+extern Success
+sub_member(	Conf_no		conf_no,
+		Pers_no		pers_no )
+{
+    Conference  * conf_c;
+    Membership	* mship;
+    Person	* pers_p;
+    
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+    GET_P_STAT(pers_p, pers_no, FAILURE);
+
+    if( (mship = locate_membership(conf_no, pers_p) ) == NULL)
+    {
+	kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_NOT_MEMBER;
+	return FAILURE;
+    }
+
+    if ( !is_supervisor(conf_no, conf_c) && !is_supervisor(pers_no, NULL)
+	&& !ENA(admin, 4) )
+    {
+	kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_PERM;
+	return FAILURE;
+    }
+
+    return do_sub_member(conf_no, conf_c, NULL, pers_no, pers_p, mship);    
+}
+
+
+/*
+ * Add a member to a conference (join a conference) or
+ * Change the priority of a conference.
+ *
+ * Anyone may add anyone as a member as long as the new member is not
+ * secret and the conference is not rd_prot. This might be a bug.
+ *
+ * PRIORITY is the assigned priority for the conference. WHERE says
+ * where on the list the person wants the conference. 0 is first. WHERE
+ * is automatically set to the number of conferences that PERS_NO is member
+ * in if WHERE is too big, so it is not an error to give WHERE == ~0 as
+ * a parameter.
+ *
+ * You can only re-prioritize if you are supervisor of pers_no.
+ */
+extern Success
+add_member(Conf_no	conf_no,
+	   Pers_no	pers_no,
+	   u_char	priority,
+	   u_short	where)		/* Range of where is [0..] */
+{
+    Conference  * conf_c, * pers_c;
+    Person	* pers_p;
+    Membership	* mship;
+    
+
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+    GET_P_STAT(pers_p, pers_no, FAILURE);
+
+    if ( access_perm(conf_no, conf_c) < limited && !ENA(admin, 4) )
+    {
+	kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_ACCESS;
+	return FAILURE;
+    }
+
+    /* Is he already a member? */
+
+    if ( (mship = locate_membership( conf_no, pers_p )) != NULL)
+    {
+	/* He is already a member. Only change the priority. */
+
+	GET_C_STAT(pers_c, pers_no, FAILURE);
+	if( !is_supervisor( pers_no, pers_c ) )	/* Noone else can change */
+						/* one's priorities. */
+	{					 
+	    kom_errno = KOM_PERM;
+	    return FAILURE;
+	}
+
+	do_change_priority( mship, priority, where, pers_no, pers_p);
+    }
+    else
+    {
+	do_add_member(conf_no, conf_c, pers_no, pers_p, priority, where);
+    }
+
+    return OK;
+}
+
+#ifdef DEBUG_MARK_AS_READ
+static int
+check_membership(Pers_no pno,
+		 const Conference *conf,
+		 const Membership *mship)
+{
+    int error=0;
+    int i;
+    Local_text_no last=0;
+    int log_no=0;
+    
+    /* Check read texts */
+    if ( mship->last_text_read >
+	conf->texts.first_local_no + conf->texts.no_of_texts - 1)
+    {
+	if ( log_no++ < 80 )
+	    log("membership.c: check_membership():"
+		"(%d) Person %lu has read text %lu in conf %lu,"
+		"which only has %lu texts.\n",
+		log_no,
+		(u_long)pno,
+		(u_long)mship->last_text_read,
+		(u_long)mship->conf_no,
+		(u_long)(conf->texts.first_local_no
+			 + conf->texts.no_of_texts - 1));
+	error++;
+    }
+
+    last = mship->last_text_read;
+
+    for ( i = 0; i < mship->no_of_read; i++)
+    {
+	if ( mship->read_texts[i] <= last )
+	{
+	    error++;
+	}
+
+	last = mship->read_texts[i];
+    }
+
+    return error;
+}
+#endif
+
+/*
+ * mark_as_read() is used to tell LysKOM which texts you have read.
+ * You can mark several texts in one chunk, but the chunk should not
+ * be too big to prevent users from having to re-read texts in case of
+ * a [server/client/network]-crash.
+ *
+ * The texts are marked per conference. If there are several recipients
+ * to a text it should be mark_as_read() in all the recipients.
+ *
+ * If conference is ACTPERS mailbox it will add a rec_time item in the
+ * misc_items field.
+ *
+ * It is only possible to mark texts as read in a conference you are
+ * member in.
+ *
+ * Attempts to mark non-existing texts as read are ignored if the text
+ * has existed. If the text has not yet been created KOM_NO_SUCH_LOCAL_TEXT
+ * will be returned in kom_errno.
+ *
+ * If CONFERENCE is the current working conference of ACTPERS, and the text
+ * has not previously been marked as read, ACT_P->read_texts will be
+ * increased. If the client cooperates this will be correct. If the
+ * client pepsi()s to all recipients of a text before marking it as read
+ * the read_texts field will be too big. (If anyone cares about that,
+ * feel free to rewrite this code as long as it doen't get too CPU-
+ * intensive.)
+ */
+extern Success
+mark_as_read (Conf_no		      conference,
+	      int		      no_of_texts,
+	      const Local_text_no  * text_arr )
+{
+    int		 i;
+    Membership * m;
+    int		 allocflg = 0;	/* read_texts is not re-allocated yet */
+    Conference * conf_c;
+    Success	 retval = OK;
+#ifdef DEBUG_MARK_AS_READ
+    const Local_text_no  * const text_arr_start = text_arr;
+    Membership   original;
+    int		 loop;
+    static int   log_no = 0;
+#endif
+
+    
+    CHK_LOGIN(FAILURE);
+    GET_C_STAT(conf_c, conference, FAILURE);
+
+    if ( (m = locate_membership( conference, ACT_P)) == NULL)
+    {
+	kom_errno = KOM_NOT_MEMBER;
+	return FAILURE;
+    }
+
+#ifdef DEBUG_MARK_AS_READ
+    if ( m->read_texts == NULL && m->no_of_read != 0 )
+    {
+	log("mark_as_read(): m->read_texts == NULL && m->no_of_read == %lu (corrected).",
+	    (u_long)m->no_of_read);
+	m->no_of_read = 0;
+    }
+    
+    original = *m;
+    original.read_texts = smalloc(m->no_of_read * sizeof(Local_text_no));
+    memcpy(original.read_texts, m->read_texts,
+	   m->no_of_read * sizeof(Local_text_no));
+#endif
+    
+    for( i = no_of_texts; i > 0; i--, text_arr++ )
+    {
+	if ( *text_arr >= ( conf_c->texts.first_local_no
+			   + conf_c->texts.no_of_texts ))
+	{
+	    kom_errno = KOM_NO_SUCH_LOCAL_TEXT;
+	    err_stat = no_of_texts - i;
+	    retval = FAILURE;
+	    break;		/* Exit for-loop */
+	}
+
+	if ( *text_arr == 0 )
+	{
+	    kom_errno = KOM_LOCAL_TEXT_ZERO;
+	    err_stat = no_of_texts - i;
+	    retval = FAILURE;
+	    break;		/* Exit for-loop */
+	}
+	
+	/* Is it a letter to ACTPERS? If so, add a rec_time item. */
+
+	if ( conference == ACTPERS )
+	    add_rec_time( conference, conf_c, *text_arr );
+	
+	/* Update the Membership struct */
+
+	if ( *text_arr == m->last_text_read + 1 )
+	{
+	    ++m->last_text_read;
+	    if ( active_connection->cwc == conference )
+		++ACT_P->read_texts;
+	}
+	else
+	{
+	    if ( allocflg == 0 )
+	    {
+		/* Realloc as much as is needed, and probably more. */
+		/* Better than to execute srealloc 100 times... */
+		
+		m->read_texts = srealloc( m->read_texts,
+					 (m->no_of_read + i)
+					 * sizeof(Local_text_no));
+		allocflg = 1;
+	    }
+
+	    if ( insert_loc_no( *text_arr, m ) == OK
+		&& active_connection->cwc == conference )
+	    {
+		++ACT_P->read_texts;
+	    }
+	}
+    }
+
+    adjust_read( m, conf_c ); /* Delete initial part
+				 of read_texts in the membership. */
+    /* Realloc to correct size */
+    
+    m->read_texts = srealloc( m->read_texts,
+			     (m->no_of_read) * sizeof(Local_text_no));
+    
+    mark_conference_as_changed ( conference );
+
+    if ( active_connection->cwc == conference )
+	mark_person_as_changed( ACTPERS );
+#ifdef DEBUG_MARK_AS_READ
+    /* Check that the membership is correct. Otherwise log all info. */
+    if ( check_membership(ACTPERS, conf_c, m) > 0 && log_no++ < 40 )
+    {
+	log("mark_as_read(): (Msg no %d) Person %lu "
+	    "has a corrupt membership:\n",
+	    log_no, (u_long)ACTPERS);
+	log("Dump of data follows: <original membership>"
+	    " <updated membership> <texts to mark>\n");
+	foutput_membership(stderr, &original);
+	putc('\n', stderr);
+	foutput_membership(stderr, m);
+	fprintf(stderr, "\n%lu { ", (u_long)no_of_texts);
+	for ( loop = 0; loop < no_of_texts; loop++ )
+	{
+	    fprintf(stderr, "\n%lu ", (u_long)text_arr_start[loop]);
+	}
+	fprintf(stderr, "}\n");
+    }
+
+    sfree(original.read_texts);
+#endif
+    return retval;
+}
+
+/*
+ * Ask what conferences a person is a member of.
+ *
+ * /// The following might be slightly misleading: /// /ceder
+ * Only ask for information about at most NO_OF_CONFS conferences
+ * starting with conferece FIRST. WANT_READ_TEXTS has the same
+ * function as GETP_READ_TEXTS in the get_person_stat call.
+ * ///
+ */
+extern  Success
+get_membership (Pers_no		  pers_no,
+		u_short		  first,
+		u_short		  no_of_confs,
+		Bool		  want_read_texts,
+		Membership_list	* memberships )
+{
+    Person		* p_orig;
+    Person		  temp_pers;
+    Conference		* pers_c;
+    Access		  acc;
+    int			  i;
+            
+    CHK_LOGIN (FAILURE);
+    
+    GET_P_STAT (p_orig, pers_no, FAILURE);
+    GET_C_STAT (pers_c, pers_no, FAILURE);
+    
+    acc = access_perm (pers_no, pers_c);
+  
+    if (acc == error)
+	return  FAILURE;
+
+    if (acc == none)
+    {
+	kom_errno = KOM_UNDEF_PERS;
+	return  FAILURE;
+    }
+
+    /* Make a copy of the struct. */
+    
+    temp_pers = *p_orig;
+    
+    /* Delete all secret information. */
+
+    if ( acc != unlimited )
+	copy_public_confs (&temp_pers, p_orig, want_read_texts);
+    else if ( !want_read_texts )
+    {
+	/* Delete info about read texts. */
+	temp_pers.conferences.confs
+	    = tmp_alloc(temp_pers.conferences.no_of_confs
+			* sizeof(Membership));
+
+	memcpy(temp_pers.conferences.confs,
+	       p_orig->conferences.confs,
+	       (temp_pers.conferences.no_of_confs
+		* sizeof(Membership) ));
+	
+	for ( i = 0; i < temp_pers.conferences.no_of_confs; i++ )
+	    temp_pers.conferences.confs[ i ].read_texts = NULL;
+    }
+    
+
+    *memberships = temp_pers.conferences;
+
+    if ( first >= memberships->no_of_confs )
+    {
+	kom_errno = KOM_INDEX_OUT_OF_RANGE;
+	return FAILURE;
+    }
+    
+    memberships->confs += first;
+    memberships->no_of_confs = min( memberships->no_of_confs - first,
+				   no_of_confs);
+
+    return OK;
+}
+
+/*
+ * +++///
+ *
+ * first starts at 0.
+ */
+extern  Success
+get_members (Conf_no	  conf_no,
+	     u_short	  first,
+	     u_short	  no_of_members,
+	     Member_list * members	)
+{
+    Conference * conf_c;
+    Access	 acc;
+        
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+
+    acc = access_perm( conf_no, conf_c);
+
+    if ( acc == error )
+	return FAILURE;
+
+    if ( acc == none )
+    {
+	kom_errno = KOM_UNDEF_CONF;
+	return FAILURE;
+    }
+
+    *members = conf_c->members;
+
+    if ( first >= members->no_of_members )
+    {
+	kom_errno = KOM_INDEX_OUT_OF_RANGE;
+	return FAILURE;
+    }
+
+    members->members += first;
+    members->no_of_members = min (no_of_members,
+				  members->no_of_members - first);
+    
+    return OK;
+}
+
+/*
+ * Get a list of all conferences where it is possible that a person has
+ * unread articles.
+ */
+
+Success
+get_unread_confs(Pers_no       pers_no,
+		 Conf_no_list *result)
+{
+    Person *pers_p;
+    Membership *confs;
+    u_short n;
+
+    GET_P_STAT(pers_p, pers_no, FAILURE);
+
+    result->conf_nos = tmp_alloc (pers_p->conferences.no_of_confs
+				  * sizeof(Conf_no));
+    result->no_of_confs = 0;
+        
+    for ( n = 0, confs = pers_p->conferences.confs;
+    	 n < pers_p->conferences.no_of_confs;
+	 n++, confs++ )
+    {
+	if ( confs->last_text_read
+	    < cached_get_highest_local_no( confs->conf_no ) )
+	{
+	    result->conf_nos[ result->no_of_confs++ ] = confs->conf_no;
+	}
+    }
+    return OK;
+}
+
+
+/*
+ * Tell the server that I want to mark/unmark texts as read so that I
+ * get (approximately) no_of_unread unread texts in conf_no.
+ */
+extern  Success
+set_unread (Conf_no   conf_no,
+	    Text_no   no_of_unread)
+{
+    Membership  *mship;
+    Conference  *conf_c;
+    Local_text_no highest;
+    
+    CHK_LOGIN(FAILURE);
+
+    GET_C_STAT(conf_c, conf_no, FAILURE);
+    
+    if ( (mship = locate_membership(conf_no, ACT_P)) == NULL )
+    {
+	kom_errno = KOM_NOT_MEMBER;
+	return FAILURE;
+    }
+
+    highest = conf_c->texts.first_local_no + conf_c->texts.no_of_texts - 1;
+    
+    mship->last_text_read = ((highest > no_of_unread)
+			     ? (highest - no_of_unread) : 0);
+    
+    sfree(mship->read_texts);
+    mship->read_texts = NULL;
+    mship->no_of_read = 0;
+
+    mark_person_as_changed(ACTPERS);
+    return OK;
+}
+
diff --git a/src/server/memory.c b/src/server/memory.c
new file mode 100644
index 0000000000000000000000000000000000000000..79b43da82be5a93e77ac546b0d431080134df427
--- /dev/null
+++ b/src/server/memory.c
@@ -0,0 +1,385 @@
+/*
+ * Memory allocators/deallocators.
+ *
+ * These functions should be used instead of smalloc/srealloc.
+ */
+
+#include <kom-types.h>
+#include <string.h>
+#include "smalloc.h"
+#include "memory.h"
+#include "exp.h"
+#include "lyskomd.h"
+
+static int text_list_cnt = 0;
+static int mark_list_cnt = 0;
+static int membership_list_cnt = 0;
+static int membership_cnt = 0;
+static int person_cnt = 0;
+static int member_list_cnt = 0;
+static int conference_cnt = 0;
+static int text_stat_cnt = 0;
+
+EXPORT Text_list *
+alloc_text_list(void)
+{
+    Text_list *t;
+
+    text_list_cnt++;
+    
+    t = smalloc(sizeof(Text_list));
+    *t = EMPTY_TEXT_LIST;
+    return t;
+}
+
+EXPORT Mark_list *
+alloc_mark_list(void)
+{
+    Mark_list *m;
+
+    mark_list_cnt++;
+    
+    m = smalloc(sizeof(Mark_list));
+    *m = EMPTY_MARK_LIST;
+    return m;
+}
+    
+EXPORT Membership_list *
+alloc_membership_list(void)
+{
+    Membership_list *m;
+
+    membership_list_cnt++;
+    
+    m = smalloc(sizeof(Membership_list));
+    *m = EMPTY_MEMBERSHIP_LIST;
+    return m;
+}
+
+EXPORT Membership *
+alloc_membership(void)
+{
+    Membership *m;
+
+    membership_cnt++;
+    
+    m = smalloc(sizeof(Membership));
+    *m = EMPTY_MEMBERSHIP;
+
+    return m;
+}
+
+EXPORT Person *
+alloc_person(void)
+{
+    Person *p;
+
+    person_cnt++;
+    
+    p = smalloc(sizeof(Person));
+    *p = EMPTY_PERSON;
+    return p;
+}
+
+EXPORT Member_list *
+alloc_member_list(void)
+{
+    Member_list *m;
+
+    member_list_cnt++;
+    
+    m = smalloc(sizeof(Member_list));
+    *m = EMPTY_MEMBER_LIST;
+
+    return m;
+}
+
+EXPORT Conference *
+alloc_conference(void)
+{
+    Conference *c;
+
+    conference_cnt++;
+    
+    c = smalloc(sizeof(Conference));
+    *c = EMPTY_CONFERENCE;
+    return c;
+}
+
+EXPORT Text_stat *
+alloc_text_stat(void)
+{
+    Text_stat *t;
+
+    text_stat_cnt++;
+    
+    t = smalloc(sizeof(Text_stat));
+    *t = EMPTY_TEXT_STAT;
+    return t;
+}
+
+EXPORT void
+free_text_list(Text_list *text_list)
+{
+    if ( text_list == NULL )
+	return;
+
+    text_list_cnt--;
+
+    sfree(text_list->texts);
+    text_list->texts = NULL;
+    text_list->no_of_texts = 0;
+}
+
+EXPORT void
+free_mark_list(Mark_list *mark_list)
+{
+    if ( mark_list == NULL )
+	return;
+
+    mark_list_cnt--;
+
+    sfree(mark_list->marks);
+    mark_list->marks = NULL;
+    mark_list->no_of_marks = 0;
+}
+    
+EXPORT void
+free_membership_list(Membership_list *mlist)
+{
+    int i;
+
+    if ( mlist == NULL )
+	return;
+
+    membership_list_cnt--;
+
+    for ( i = 0; i < mlist->no_of_confs; i++ )
+    {
+	free_membership(&mlist->confs[i]);
+    }
+
+    mlist->confs = NULL;
+    mlist->no_of_confs = 0;
+}
+
+EXPORT void
+free_membership(Membership *mship)
+{
+    if ( mship == NULL )
+	return;
+
+    membership_cnt--;
+
+    sfree(mship->read_texts);
+    mship->read_texts = NULL;
+    mship->no_of_read = 0;
+}
+
+EXPORT void
+clear_person(Person *person)
+{
+    s_clear(&person->username);
+    free_text_list(&person->created_texts);
+    free_mark_list(&person->marks);
+    free_membership_list(&person->conferences);
+    *person = EMPTY_PERSON;
+}
+
+EXPORT void
+free_person(Person *person)
+{
+    if ( person == NULL )
+	return;
+
+    person_cnt--;
+
+    clear_person(person);
+
+    sfree(person);
+}
+
+
+EXPORT void 
+free_member_list(Member_list *m)
+{
+    if ( m == NULL )
+	return;
+
+    member_list_cnt--;
+    
+    sfree(m->members);
+    m->members = NULL;
+    m->no_of_members = 0;
+}
+
+EXPORT void
+clear_conference(Conference *confp)
+{
+    s_clear(&confp->name);
+    free_member_list(&confp->members);
+    free_text_list(&confp->texts);
+    *confp = EMPTY_CONFERENCE;
+}
+
+EXPORT void
+free_conference(Conference *confp)
+{
+    if ( confp == NULL )
+	return;
+
+    conference_cnt--;
+    clear_conference(confp);
+    sfree(confp);
+}
+
+EXPORT void
+clear_text_stat(Text_stat *t)
+{
+    int i;
+
+    for ( i = 0; i < t->no_of_misc; i++ )
+    {
+	switch ( t->misc_items[ i ].type )
+	{
+	case recpt:
+	case cc_recpt:
+	case comm_to:
+	case comm_in:
+	case footn_to:
+	case footn_in:
+	case loc_no:
+	case rec_time:
+	case sent_by:
+	case sent_at:
+	    /* No need to free anything for these. */
+	    break;
+
+	default:
+	    restart_kom(__FILE__ ": free_text_stat: unknown Info_type %d.",
+			t->misc_items[ i ].type );
+	}
+    }
+
+    sfree(t->misc_items);
+    *t = EMPTY_TEXT_STAT;
+}
+
+EXPORT void
+free_text_stat(Text_stat *t)
+{
+    if ( t == NULL )
+	return;
+
+    text_stat_cnt--;
+    clear_text_stat(t);
+    sfree(t);
+}
+
+
+EXPORT Person *
+copy_person(Person *p)
+{
+    Person *c;
+
+    c = alloc_person();
+    *c = *p;
+    c->username = EMPTY_STRING;
+    s_strcpy(&c->username, p->username);
+
+    c->created_texts = copy_text_list(p->created_texts);
+    c->marks = copy_mark_list(p->marks);
+    c->conferences = copy_membership_list(p->conferences);
+    return c;
+}
+
+EXPORT Conference *
+copy_conf(Conference *o)
+{
+    Conference *c;
+
+    c = alloc_conference();
+    *c = *o;
+    c->name = EMPTY_STRING;
+    s_strcpy(&c->name, o->name);
+    c->members = copy_member_list(o->members);
+    c->texts = copy_text_list(o->texts);
+    return c;
+}
+
+EXPORT Text_stat *
+copy_text_stat(Text_stat *t)
+{
+    Text_stat *c;
+
+    c = alloc_text_stat();
+    *c = *t;
+    c->misc_items = smalloc(c->no_of_misc * sizeof(Misc_info));
+    memcpy(c->misc_items, t->misc_items, c->no_of_misc * sizeof(Misc_info));
+    return c;
+}
+
+
+EXPORT Text_list
+copy_text_list(Text_list tl)
+{
+    Text_list r;
+
+    r = tl;
+    r.texts = smalloc(r.no_of_texts * sizeof(Text_no));
+    memcpy(r.texts, tl.texts, r.no_of_texts * sizeof(Text_no));
+
+    return r;
+}
+
+EXPORT Mark_list
+copy_mark_list(Mark_list ml)
+{
+    Mark_list r;
+
+    r.no_of_marks = ml.no_of_marks;
+    r.marks = smalloc(r.no_of_marks * sizeof(Mark));
+    memcpy(r.marks, ml.marks, r.no_of_marks * sizeof(Mark));
+    return r;
+}
+
+static Membership
+copy_membership(Membership m)
+{
+    Membership res;
+
+    res = m;
+    res.read_texts = smalloc(m.no_of_read * sizeof(Local_text_no));
+    memcpy(res.read_texts, m.read_texts, m.no_of_read * sizeof(Local_text_no));
+    return res;
+}
+
+EXPORT Membership_list
+copy_membership_list(Membership_list ml)
+{
+    Membership_list r;
+    int i;
+
+    r.no_of_confs = ml.no_of_confs;
+    r.confs = smalloc(ml.no_of_confs * sizeof(Membership));
+
+    for ( i = 0; i < r.no_of_confs; i++ )
+    {
+	r.confs[i] = copy_membership(ml.confs[i]);
+    }
+
+    return r;
+}
+
+EXPORT Member_list
+copy_member_list(Member_list ml)
+{
+    Member_list res;
+
+    res.no_of_members = ml.no_of_members;
+    res.members = smalloc(res.no_of_members * sizeof ( Member ));
+    memcpy(res.members, ml.members, res.no_of_members * sizeof ( Member ));
+    return res;
+}
+
+    
diff --git a/src/server/memory.h b/src/server/memory.h
new file mode 100644
index 0000000000000000000000000000000000000000..2953425616d67666e0ec6b4e33f3f770d43b95ca
--- /dev/null
+++ b/src/server/memory.h
@@ -0,0 +1,82 @@
+
+extern Text_list *
+alloc_text_list(void);
+
+extern Mark_list *
+alloc_mark_list(void);
+
+extern Membership_list *
+alloc_membership_list(void);
+
+extern Membership *
+alloc_membership(void);
+
+extern Person *
+alloc_person(void);
+
+extern Member_list *
+alloc_member_list(void);
+
+extern Conference *
+alloc_conference(void);
+
+extern Text_stat *
+alloc_text_stat(void);
+
+extern Small_conf *
+alloc_small_conf(void);
+
+extern void
+free_text_list(Text_list *text_list);
+
+extern void
+free_mark_list(Mark_list *mark_list);
+
+extern void
+free_membership_list(Membership_list *mlist);
+
+extern void
+free_membership(Membership *mship);
+
+extern void
+free_person(Person *person);
+
+extern void 
+free_member_list(Member_list *m);
+
+extern void
+free_conference(Conference *confp);
+
+extern void
+free_text_stat(Text_stat *t);
+
+extern Person *
+copy_person(Person *p);
+
+extern Conference *
+copy_conf(Conference *c);
+
+extern Text_stat *
+copy_text_stat(Text_stat *c);
+
+
+extern void
+clear_conference(Conference *c);
+
+extern void
+clear_person(Person *p);
+
+extern void
+clear_text_stat(Text_stat *t);
+
+extern Text_list
+copy_text_list(Text_list tl);
+
+extern Mark_list
+copy_mark_list(Mark_list ml);
+
+extern Membership_list
+copy_membership_list(Membership_list ml);
+
+extern Member_list
+copy_member_list(Member_list ml);
diff --git a/src/server/minmax.h b/src/server/minmax.h
new file mode 100644
index 0000000000000000000000000000000000000000..57ad9ccf091781b6d55705aa9ec6cb350a333e36
--- /dev/null
+++ b/src/server/minmax.h
@@ -0,0 +1,6 @@
+/*
+ * I can't believe that this really doesn't exist in a standard library!
+ */
+
+#define min(a, b) ((a) > (b) ? (b) : (a))
+#define max(a, b) ((a) > (b) ? (a) : (b))
diff --git a/src/server/missing-ansi.c b/src/server/missing-ansi.c
new file mode 100644
index 0000000000000000000000000000000000000000..d3acdff9c389bb60ef8905cb6cd1a651c0ad97f7
--- /dev/null
+++ b/src/server/missing-ansi.c
@@ -0,0 +1,83 @@
+/*
+ * Some missing functions that are needed.
+ *
+ * Written by ceder.
+ */
+
+#include <string.h>
+#include <time.h>
+
+#if defined(__sequent__) && !defined(DEBUG_MALLOC)
+
+void *
+memcpy (void * s1, const void * s2, size_t n)
+{
+    extern bcopy(const char *b1, char *b2, unsigned int length);
+
+    /* bcopy takes the parameters the other way round. */
+    
+    bcopy(s2, s1, n);
+    return s1;			/* Since ANSI says so */
+}
+
+#endif
+
+#if defined(__sequent__) || defined(__sun__)
+
+extern double
+difftime(time_t t1, time_t t2)
+{
+    return (double) t1 - (double) t2;
+}
+
+#endif
+
+#ifdef __sequent__
+
+/*
+ * This version in NOT 100 % ANSI-conformant, but it is enough to run LysKOM
+ * (I hope) /ceder
+ */
+
+extern time_t
+mktime (const struct tm  * temeptr )
+{
+    int nyears;
+    int nleaps;
+    int ndays;
+
+    nyears = temeptr->tm_year - 70;
+    nleaps = (nyears+2) / 4;
+
+    ndays = 365 * nyears + nleaps + temeptr->tm_yday;
+    
+    return (time_t) ( temeptr->tm_sec
+		     + 60 * ( temeptr->tm_min
+			     + 60 * ( temeptr->tm_hour 
+				     + 24 * ndays )));
+}
+
+#endif
+
+
+#ifdef __sequent__
+#include <errno.h>
+#include <stdio.h>
+
+extern char *sys_errlist[];
+extern int sys_nerr;
+
+const char *strerror(int eno)
+{
+  static char buf[200];
+
+  
+  if (eno < 0 || eno >= sys_nerr)
+  {
+    sprintf(buf, "error #%d", eno);
+    return buf;
+  }
+  else
+    return sys_errlist[eno];
+}
+#endif
diff --git a/src/server/mux-parse.c b/src/server/mux-parse.c
new file mode 100644
index 0000000000000000000000000000000000000000..20502d7980207403b68220b8fbcfa5e1a2229947
--- /dev/null
+++ b/src/server/mux-parse.c
@@ -0,0 +1,226 @@
+/*
+ ** mux-parse.c                                    Handle the MUX protocol
+ * This is a hack. Clean this mess up with lacgen.
+ */
+ 
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include "com.h"
+#include "connections.h"
+#include "isc.h"
+#include "mux.h"
+#include "smalloc.h"
+#include <setjmp.h>
+#include <string.h>
+#include <stdio.h>
+#include "mux-parse.h"
+#include "minmax.h"
+#include "config.h"
+#include <s-string.h>
+
+jmp_buf mux_parse_env;
+
+static String
+mux_get_token(Mux *mux)
+{
+    String result;
+    String_size old_first;
+
+    old_first = mux->parse.first_to_parse;
+    
+    result = s_strtok(mux->parse.unparsed, &mux->parse.first_to_parse,
+		      s_fcrea_str(" \t\n\r"));
+
+    /* Check that there was at least one trailing blank. */
+    
+    if ( mux->parse.first_to_parse >= s_strlen(mux->parse.unparsed) )
+    {
+	mux->parse.first_to_parse = old_first;
+	longjmp(mux_parse_env, MUX_MSG_INCOMPLETE);
+    }
+
+    return result;
+}
+
+static long
+mux_parse_long(Mux *mux)
+{
+    String	token;
+    String_size end;
+    long	res;
+
+    token = mux_get_token(mux);
+    res = s_strtol(token, &end, PROTOCOL_NUMBER_BASE);
+    if (end != s_strlen(token))
+	longjmp(mux_parse_env, MUX_PROTOCOL_ERR);
+    
+    return res;
+}
+
+
+/*
+ * Parse a string. At most 'maxlen' characters are allowed. If the
+ * mux sends a longer string only the first 'maxlen+1' characters
+ * are read. The following characters are discarded.
+ */
+static void
+mux_parse_string(Mux	    *mux,
+		 String	    *result,
+		 int	     maxlen)
+{
+    String_size hptr;		/* Pointer to 'H' */
+    String_size mux_len;	/* The len the mux is sending. */
+    String_size truncated_len;	/* How much the server will receive. */
+    String_size to_skip;
+    String tmp;
+
+    switch ( mux->parse.string_parse_pos )
+    {
+    case 0:
+	/* Get number and discard trailing 'H' */
+	result->len = s_strtol(s_fsubstr(mux->parse.unparsed,
+					 mux->parse.first_to_parse,
+					 END_OF_STRING),
+			       &hptr, PROTOCOL_NUMBER_BASE);
+
+	if ( hptr == -1
+	    || mux->parse.first_to_parse + hptr
+	    >= s_strlen(mux->parse.unparsed) )
+	{
+	    longjmp(mux_parse_env, MUX_MSG_INCOMPLETE);
+	}
+	
+	/* Check that
+	      a) there is a trailing H
+	      b) there was at least one digit before the H */
+
+	if ( mux->parse.unparsed.string[ mux->parse.first_to_parse
+					  + hptr ] != 'H'
+	    || hptr <= 0 )
+	{
+	    longjmp(mux_parse_env, MUX_PROTOCOL_ERR);
+	}
+
+	mux->parse.first_to_parse += 1 + hptr;
+	mux->parse.string_parse_pos = 1;
+	/* Fall through */
+    case 1:
+	/* Check that the entire string is transmitted. */
+	/* (Don't care about the trailing part that will be skipped if the
+	 *  string is longer than maxlen) */
+	truncated_len = min(maxlen + 1, result->len);
+	mux_len = result->len;
+	
+	if ( mux->parse.first_to_parse + truncated_len
+	    > s_strlen(mux->parse.unparsed) )
+	{
+	    longjmp(mux_parse_env, MUX_MSG_INCOMPLETE);
+	}
+
+	result->string = (mux->parse.unparsed.string
+			  + mux->parse.first_to_parse);
+	result->len = truncated_len;
+	tmp = EMPTY_STRING;
+	s_strcpy(&tmp, *result); /* Copy the string. */
+	*result = tmp;
+	result->len = mux_len;
+
+	mux->parse.first_to_parse += truncated_len;
+	mux->parse.string_parse_pos = 2;
+	/* Fall through */
+    case 2:
+	/* Was the string too long? If so, skip the truncated data. */
+
+	mux_len = result->len;
+	truncated_len = min(maxlen+1, result->len);
+	
+	if ( mux_len > truncated_len )
+	{
+	    to_skip = min(mux_len - truncated_len,
+			   mux->parse.unparsed.len - mux->parse.first_to_parse);
+	    mux_len -= to_skip;
+	    mux->parse.first_to_parse += to_skip;
+	}
+
+	result->len = mux_len;
+	
+	if ( mux_len > truncated_len )
+	    longjmp(mux_parse_env, MUX_MSG_INCOMPLETE);
+	/* Fall through */
+    default:
+	mux->parse.string_parse_pos = 0;
+    }
+}
+
+
+
+
+
+static Bool
+mux_is_legal_fnc(int fnc)
+{
+    switch(fnc)
+    {
+    case 0:
+    case 1:
+    case 2:
+    case 3:
+	return TRUE;
+
+    default:
+	return FALSE;
+    }
+}    
+
+static void
+mux_parse_client_message(Mux *mux)
+{
+    switch(mux->parse.parse_pos_2)
+    {
+    case 0:
+	mux->parse.num = mux_parse_long(mux);
+	mux->parse.parse_pos_2 = 1;
+	/* Fall through */
+    case 1:
+	mux_parse_string(mux, &mux->parse.string, 10000);
+	/* Fall through */
+    default:
+	mux->parse.parse_pos_2 = 0;
+    }
+}
+
+void
+mux_parse_packet(Mux *mux)
+{
+    switch(mux->parse.parse_pos)
+    {
+    case 0:			/* Get fnc_no */
+	mux->parse.function = mux_parse_long(mux);
+	if ( !mux_is_legal_fnc(mux->parse.function) )
+	    longjmp(mux_parse_env, MUX_PROTOCOL_ERR);
+	mux->parse.parse_pos = 1;
+	/* Fall through */
+    case 1:
+	/* Call the function that parses the arguments for this call. */
+	switch(mux->parse.function)
+	{
+	case 0:
+	    break;
+
+	case 2:
+	    mux->parse.num = mux_parse_long(mux);
+	    break;
+
+	case 1:
+	case 3:
+	    mux_parse_client_message(mux);
+	    break;
+	}
+	/* Fall through */
+    default:
+	mux->parse.parse_pos = 0;
+    }
+}
diff --git a/src/server/mux-parse.h b/src/server/mux-parse.h
new file mode 100644
index 0000000000000000000000000000000000000000..5e09d796b6b564e763dd08e6467c00bfa231bf64
--- /dev/null
+++ b/src/server/mux-parse.h
@@ -0,0 +1,15 @@
+/*
+ ** mux-parse.c                                    Handle the MUX protocol
+ * This is a hack.
+ */
+ 
+#ifdef _SETJMP_
+extern jmp_buf mux_parse_env;
+#endif
+
+#define MUX_PROTOCOL_ERR 1
+#define MUX_MSG_INCOMPLETE 2
+#define MUX_LOGOUT 3
+
+extern void
+mux_parse_packet(Mux *mux);
diff --git a/src/server/mux.c b/src/server/mux.c
new file mode 100644
index 0000000000000000000000000000000000000000..16b7ee5c3d21d40aab248cbef67b6b9da3d15aa9
--- /dev/null
+++ b/src/server/mux.c
@@ -0,0 +1,401 @@
+/*
+** mux.c
+*/
+ 
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include "s-string.h"
+#include "lyskomd.h"
+#include <kom-types.h>
+#include "com.h"
+#include "connections.h"
+#include "isc.h"
+#include "mux.h"
+#include "smalloc.h"
+
+
+extern _printf( int (*sputc)(int chr), const char *fmt, va_list AP);
+
+
+/*
+** MUX support functions
+*/
+
+Mux *
+mux_create(Mux_type type, ISCSCB *scb)
+{
+  Mux *mp;
+
+
+  mp = (Mux *) smalloc(sizeof(Mux));
+
+  mp->magic    = MUX_MAGIC_ALLOC;
+  mp->type     = type;
+  mp->scb      = scb;
+  mp->client_c = 0;
+  mp->client_v = NULL;
+  
+  mp->parse.parse_pos = 0;
+  mp->parse.parse_pos_2 = 0;
+  mp->parse.string_parse_pos = 0;
+  mp->parse.first_to_parse = 0;
+  mp->parse.more_to_parse = TRUE;
+  mp->parse.function = 0;
+  mp->parse.string = EMPTY_STRING;
+  mp->parse.unparsed = EMPTY_STRING;
+  
+  return mp;
+}
+
+void
+mux_free_parsed(Mux *mp)
+{
+    s_clear(&mp->parse.string);
+    mp->parse.parse_pos = 0;
+    mp->parse.parse_pos_2 = 0;
+    mp->parse.string_parse_pos = 0;
+} 
+
+void
+mux_destruct(Mux *mp)
+{
+  switch (mp->magic)
+  {
+    case MUX_MAGIC_ALLOC:
+      break;
+
+    case MUX_MAGIC_FREE:
+      restart_kom("MUX_DESTRUCT: Trying to destruct freed Mux block\n");
+
+    default:
+      restart_kom("MUX_DESTRUCT: Bad magic number\n");
+  }
+
+  mp->magic = MUX_MAGIC_FREE;
+
+  mux_free_parsed(mp);
+  s_clear(&mp->parse.unparsed);
+  sfree(mp->client_v);
+  sfree(mp);
+}
+
+
+
+int
+mux_clients(Mux *mp)
+{
+  int i;
+  int c;
+
+
+  switch (mp->magic)
+  {
+    case MUX_MAGIC_ALLOC:
+      break;
+
+    case MUX_MAGIC_FREE:
+      restart_kom("MUX_CLIENTS: Trying to work on freed Mux block\n");
+
+    default:
+      restart_kom("MUX_CLIENTS: Bad magic number\n");
+  }
+
+  for (i = 0, c = 0; i < mp->client_c; i++)
+    if (mp->client_v[i].id != -1)
+      c++;
+
+  return c;
+}
+
+
+void
+mux_addclient(Mux *mp, int id, Connection *cp)
+{
+  int i;
+
+
+  switch (mp->magic)
+  {
+    case MUX_MAGIC_ALLOC:
+      break;
+
+    case MUX_MAGIC_FREE:
+      restart_kom("MUX_ADDCLIENT: Trying to work on freed Mux block\n");
+
+    default:
+      restart_kom("MUX_ADDCLIENT: Bad magic number\n");
+  }
+
+  for (i = 0; i < mp->client_c; i++)
+    if (mp->client_v[i].id == -1)
+      break;
+
+  if (i == mp->client_c)
+  {
+    mp->client_c++;
+    if (mp->client_v == NULL)
+      mp->client_v = (Mux_client *) smalloc(sizeof(Mux_client));
+    else
+      mp->client_v = (Mux_client *) srealloc(mp->client_v,
+					      sizeof(Mux_client) *
+					      (mp->client_c));
+  }
+  
+  mp->client_v[i].conn   = cp;
+  mp->client_v[i].id     = id;
+  mp->client_v[i].outmsg = isc_allocmsg(MUX_OUTMSGSIZE);
+}
+
+
+
+void
+mux_delclient(Mux *mp, int id)
+{
+  int i;
+
+
+  switch (mp->magic)
+  {
+    case MUX_MAGIC_ALLOC:
+      break;
+
+    case MUX_MAGIC_FREE:
+      restart_kom("MUX_DELCLIENT: Trying to work on freed Mux block\n");
+
+    default:
+      restart_kom("MUX_DELCLIENT: Bad magic number\n");
+  }
+
+  for (i = 0; i < mp->client_c; i++)
+    if (mp->client_v[i].id == id)
+    {
+      mp->client_v[i].id   = -1;
+      mp->client_v[i].conn = NULL;
+      isc_freemsg(mp->client_v[i].outmsg);
+      return;
+    }
+}
+
+
+Mux_client *
+mux_getclientbyid(Mux *mp, int id)
+{
+  int i;
+  
+
+  switch (mp->magic)
+  {
+    case MUX_MAGIC_ALLOC:
+      break;
+
+    case MUX_MAGIC_FREE:
+      restart_kom("MUX_GETCLIENTBYID: Trying to work on freed Mux block\n");
+
+    default:
+      restart_kom("MUX_GETCLIENTBYID: Bad magic number\n");
+  }
+
+
+  for (i = 0; i < mp->client_c; i++)
+    if (mp->client_v[i].id == id)
+      return &mp->client_v[i];
+
+  return NULL;
+}
+
+
+
+Mux_client *
+mux_getclientbyconn(Mux *mp, Connection *cp)
+{
+  int i;
+
+
+  switch (mp->magic)
+  {
+    case MUX_MAGIC_ALLOC:
+      break;
+
+    case MUX_MAGIC_FREE:
+      restart_kom("MUX_GETCLIENTBYCONN: Trying to work on freed Mux block\n");
+
+    default:
+      restart_kom("MUX_GETCLIENTBYCONN: Bad magic number\n");
+  }
+
+  for (i = 0; i < mp->client_c; i++)
+    if (mp->client_v[i].conn == cp)
+      return &mp->client_v[i];
+
+  return NULL;
+}
+
+
+/*
+** MUX output functions
+*/
+
+
+int
+mux_write(Connection  * cp,
+	  const char  * buf,
+	  size_t        size)
+{
+  Mux         * mp = cp->mux;
+  Mux_client  * mcp;
+  size_t        osize;
+  size_t        wlen;
+  size_t        clen;
+  
+
+  mcp = mux_getclientbyconn(mp, cp);
+  if (mcp == NULL)
+    /* BETTER ERROR HANDLING SHOULD GO HERE */
+    restart_kom("mux_write: STALE CLIENT ID\n");
+
+  osize = size;
+  while (size > 0)
+  {
+    while ((wlen = mcp->outmsg->size - mcp->outmsg->length) == 0)
+      mux_flush(cp);
+    
+    clen = size > wlen ? wlen : size;
+    memcpy(mcp->outmsg->buffer + mcp->outmsg->length, buf, clen);
+    mcp->outmsg->length += clen;
+    size -= clen;
+    buf  += clen;
+  }
+
+  return osize;
+}
+
+
+
+
+int
+mux_putc(int           chr,
+	 Connection  * cp)
+{
+  Mux         * mp = cp->mux;
+  Mux_client  * mcp;
+
+  
+  mcp = mux_getclientbyconn(mp, cp);
+  if (mcp == NULL)
+    /* BETTER ERROR HANDLING SHOULD GO HERE */
+    restart_kom("mux_putc: STALE CLIENT ID\n");
+
+  while (mcp->outmsg->length == mcp->outmsg->size)
+    mux_flush(cp);
+
+  mcp->outmsg->buffer[mcp->outmsg->length++] = chr;
+
+  return chr;
+}
+
+
+
+static Connection *send_cp;
+
+static int
+send_putc(int chr)
+{
+  return mux_putc(chr, send_cp);
+}
+
+
+
+int
+mux_printf(Connection  * cp,
+	   const char  * format,
+	   ...)
+{
+  va_list       AP;
+  int           retval = -1;
+
+  
+  va_start(AP, format);
+
+  send_cp = cp;
+  retval = _printf(&send_putc, format, AP);
+
+  va_end(AP);
+
+  return retval;
+}
+
+
+void
+mux_flush(Connection  * cp)
+{
+  Mux         * mp = cp->mux;
+  Mux_client  * mcp;
+
+
+  mcp = mux_getclientbyconn(mp, cp);
+  if (mcp == NULL)
+    /* BETTER ERROR HANDLING SHOULD GO HERE */
+    restart_kom("mux_flush: STALE CLIENT ID\n");
+
+  if (mcp->outmsg->length == 0)
+    return;
+  
+  switch (mp->type)
+  {
+    case MUX_TYPE_CLIENT:
+      isc_send(mp->scb, mcp->outmsg);
+      isc_flush(mp->scb);
+      mcp->outmsg = isc_allocmsg(MUX_OUTMSGSIZE);
+      break;
+
+    case MUX_TYPE_MUX:
+      isc_printf(mp->scb, "3 %d %dH",
+		 mcp->id, mcp->outmsg->length);
+      isc_write(mp->scb, mcp->outmsg->buffer, mcp->outmsg->length);
+      isc_printf(mp->scb, "\n");
+      isc_flush(mp->scb);
+      mcp->outmsg->length = 0;
+      break;
+      
+    default:
+       restart_kom("mux_flush: ILLEGAL MUX TYPE: %d\n", mp->type);
+  }
+}
+
+
+void
+mux_close(Connection  * cp)
+{
+  Mux         * mp = cp->mux;
+  Mux_client  * mcp;
+  
+
+  mcp = mux_getclientbyconn(mp, cp);
+  if (mcp == NULL)
+    /* BETTER ERROR HANDLING SHOULD GO HERE */
+    restart_kom("mux_close: STALE CLIENT ID\n");
+  
+  mux_flush(cp);
+  
+  switch (mp->type)
+  {
+    case MUX_TYPE_CLIENT:
+      isc_close(mp->scb);
+      mux_destruct(mp);
+      cp->mux = NULL;
+      break;
+
+    case MUX_TYPE_MUX:
+      isc_printf(mp->scb, "2 %d\n", mcp->id);
+      isc_flush(mp->scb);
+
+      mux_delclient(mp, mcp->id);
+      cp->mux = NULL;
+      break;
+      
+    default:
+       restart_kom("mux_close: ILLEGAL MUX TYPE: %d\n", mp->type);
+  }
+}
+
diff --git a/src/server/mux.h b/src/server/mux.h
new file mode 100644
index 0000000000000000000000000000000000000000..f4e469d608218bc5f9886f152d273fea605d3dfb
--- /dev/null
+++ b/src/server/mux.h
@@ -0,0 +1,113 @@
+/*
+** mux.h
+*/
+
+#ifndef __LYSKOM__MUX_H__
+#define __LYSKOM__MUX_H__
+
+#define MUX_OUTMSGSIZE	   1024
+
+#define MUX_MAGIC_ALLOC    0x56AE17DB
+#define MUX_MAGIC_FREE     0xF24A6BE7
+
+typedef enum
+{
+  MUX_TYPE_CLIENT,
+  MUX_TYPE_MUX
+} Mux_type;
+
+
+
+typedef struct
+{
+  int             id;     /* MUX client id */
+  Connection    * conn;   /* LysKOM virtual Connection */
+
+  struct isc_msg * outmsg;
+} Mux_client;
+
+typedef struct
+{
+    int			parse_pos;
+    int			parse_pos_2;
+    int			string_parse_pos;
+    int			function; /* Function to call. */
+
+    /* Gather data in these variables. */
+    
+    long		num;
+    String		string;
+    String              unparsed;
+    String_size		first_to_parse; /* Index into unparsed. */
+    Bool		more_to_parse;	/* Any chance that there is anything
+					   left in unparsed? */
+} Mux_parse;
+
+typedef struct mux
+{
+  unsigned long     magic;    /* Magic number :-) */
+  Mux_type          type;
+  struct isc_scb  * scb;
+
+  Mux_parse         parse;
+
+  int               client_c;
+  Mux_client      * client_v;
+} Mux;
+
+
+/*
+** MUX support functions
+*/
+
+extern Mux *
+mux_create(Mux_type type, IscSession *scb);
+  
+extern void
+mux_destruct(Mux *mp);
+
+extern int
+mux_clients(Mux *mp);
+
+extern void
+mux_addclient(Mux *mp, int id, Connection *cp);
+
+extern void
+mux_delclient(Mux *mp, int id);
+
+extern Mux_client *
+mux_getclientbyid(Mux *mp, int id);
+
+extern Mux_client *
+mux_getclientbyconn(Mux *mp, Connection *cp);
+
+
+
+/*
+** MUX output functions
+*/
+
+extern int
+mux_write(Connection  * cp,
+	  const char  * buf,
+	  size_t        size);
+
+extern int
+mux_printf(Connection  * cp,
+	   const char  * format,
+	   ...);
+
+extern int
+mux_putc(int           chr,
+	 Connection  * cp);
+
+extern void
+mux_flush(Connection  * cp);
+
+extern void
+mux_close(Connection  * cp);
+
+extern void
+mux_free_parsed(Mux *mp);
+
+#endif
diff --git a/src/server/person.c b/src/server/person.c
new file mode 100644
index 0000000000000000000000000000000000000000..e1e79c1e9aa946f7af768caca4d94998b6111609
--- /dev/null
+++ b/src/server/person.c
@@ -0,0 +1,715 @@
+/*
+ * person.c
+ *
+ * All atomic calls that deals with persons.
+ */
+#include <time.h>
+#include <string.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 "parser.h"
+#include "com.h"
+#include "connections.h"
+#include "send-async.h"
+#include <debug.h>
+/*
+ * Static functions.
+ */
+static Bool
+legal_passwd(const String pwd)
+{
+    return TRUE;		/* ??? */
+}
+
+static Success
+do_set_passwd( Password        pwd,
+	       const String   new_pwd)
+{
+    if ( !legal_passwd(new_pwd) )
+	return FAILURE;
+	
+    *pwd++ = new_pwd.len;		/*** Maybe too easy crypt... */
+    strncpy(pwd, new_pwd.string, min( PASSWD_LEN-1, new_pwd.len ));
+    return OK;
+}
+
+
+
+/*
+ * Mark a text. No check is done if pers_p is really allowed to mark it.
+ * Use mark_type==0 to unmark the text.
+ */
+
+static Success
+do_mark_text(Pers_no    pers_no,
+	     Person    *pers_p,		/* May be NULL */
+	     Text_no    text_no,
+	     Text_stat *text_s,		/* May be NULL */
+	     u_char     mark_type)
+{
+    Mark      *markp;
+    Mark      *old_mark = NULL;
+    int        i;
+    BUGDECL;
+
+    if ( pers_p == NULL )
+	GET_P_STAT(pers_p, pers_no, FAILURE);
+	
+    /* Locate the mark. */
+    
+    for ( i = pers_p->marks.no_of_marks, markp = pers_p->marks.marks;
+	 i > 0 && old_mark == NULL;
+	 i--, markp++ )
+    {
+	if ( markp->text_no == text_no )
+	    old_mark = markp;
+    }
+
+    if (old_mark != NULL && mark_type != 0)
+    {
+	/* Change marktype of an already marked text. (No need to check
+	   that the text exists). */
+	BUG(("do_mark_text(): Change type of mark.\n"));
+	
+	old_mark->mark_type = mark_type;
+	mark_person_as_changed(pers_no);
+    }
+    else if (old_mark != NULL)
+    {
+	/* Delete this mark. */
+	BUG(("do_mark_text(): Delete mark.\n"));
+	
+	while ( old_mark < ( pers_p->marks.marks
+			    + pers_p->marks.no_of_marks - 1 ))
+	{
+	    *old_mark = *(old_mark + 1);
+	    old_mark++;
+	}
+
+	pers_p->marks.no_of_marks--;
+	mark_person_as_changed(pers_no);
+	
+	/* ...and don't forget to decrease the ref_cnt in the text_stat... */
+
+	if ( (text_s = cached_get_text_stat(text_no)) != NULL )
+	{
+	    if ( text_s->no_of_marks == 0 )
+	    {
+		log("WNG: do_mark_text(): Text %d has no_of_marks==0,\
+ 		     but person %d had marked the text.",
+		    text_no, pers_no);
+	    }
+	    else
+	    {
+		text_s->no_of_marks--;
+		mark_text_as_changed(text_no);
+	    }
+	}
+	/* ...but it is not an error if someone has deleted the text. */
+    }
+    else if ( mark_type != 0 )
+    {
+	/* A new mark. Check that the text exists. */
+	BUG(("do_mark_text(): new mark.\n"));
+
+	if ( text_s == NULL )
+	    GET_T_STAT(text_s, text_no, FAILURE);
+
+	if (text_s->no_of_marks >= MAX_MARKS_TEXT
+	    || pers_p->marks.no_of_marks >= MAX_MARKS_PERSON)
+	{
+	    kom_errno = KOM_MARK_LIMIT;
+	    return FAILURE;
+	}
+
+	text_s->no_of_marks++;
+	mark_text_as_changed(text_no);
+	
+	pers_p->marks.marks = srealloc(pers_p->marks.marks,
+			       ++pers_p->marks.no_of_marks * sizeof(Mark));
+	pers_p->marks.marks[pers_p->marks.no_of_marks - 1].text_no = text_no;
+	pers_p->marks.marks[pers_p->marks.no_of_marks - 1].mark_type
+	    = mark_type;
+	mark_person_as_changed(pers_no);
+    }
+    else
+    {
+	/* An attempt to delete a non-existent mark. This is a noop. */
+	BUG(("do_mark_text(): delete non-existent mark.\n"));
+    }
+    
+    return OK;
+}
+
+
+/*
+ * Change user_area of a person. If text_no is 0, there will be
+ * no user_area.
+ */
+
+static Success
+do_set_user_area(Pers_no     pers_no,
+		 Person	   * pers_p,
+		 Text_no     text_no)
+{
+    Text_stat * old_user_area;
+    Text_stat * new_user_area = NULL; /* To stop gcc complaining. */
+
+    /* Check that the new user_area exists before deleting the old */
+
+    if ( text_no != 0 )
+    {
+	GET_T_STAT(new_user_area, text_no, FAILURE);
+	if ( new_user_area->no_of_marks >= MAX_MARKS_TEXT )
+	{
+	    log("LIMIT: do_set_user_area(): New user_area very marked.\n");
+	    return FAILURE;
+	}
+    }
+    
+    /* Unmark the previous user_area if it exists. */
+
+    if ( pers_p->user_area != 0
+	&& (old_user_area = cached_get_text_stat(text_no)) != NULL)
+    {
+	if ( old_user_area->no_of_marks > 0 )
+	{
+	    --old_user_area->no_of_marks;
+	    mark_text_as_changed( text_no );
+	}
+	else
+	{
+	    log("ERROR: do_set_user_area(): Old user_area not marked\n");
+	}
+    }
+
+    /* Mark the new user_area */
+
+    if ( text_no != 0 )
+    {
+	++new_user_area->no_of_marks;
+	mark_text_as_changed( text_no );
+    }
+    
+    pers_p->user_area = text_no;
+    mark_person_as_changed( pers_no );
+
+    return OK;
+}
+/*
+ * End of static functions.
+ */
+
+/*
+ * Functions that are exported to the server.
+ */
+
+/*
+ * Delete a person. (The mailbox is not deleted).
+ */
+
+Success
+do_delete_pers (Pers_no pers_no)
+{
+    Person * pers_p;
+    int	     i;
+    
+    GET_P_STAT(pers_p, pers_no, FAILURE);
+
+    /* Cancel membership in all confs. */
+    /* Note that because of the way do_sub_member is written the */
+    /* loop must be executed this way. */
+    for ( i = pers_p->conferences.no_of_confs - 1; i >= 0; i-- )
+    {
+	if ( do_sub_member( pers_p->conferences.confs[ i ].conf_no, NULL, NULL,
+			   pers_no, pers_p,
+			   pers_p->conferences.confs + i) != OK)
+	{
+	    log("ERROR: do_delete_pers(): can't sub_member\n");
+	}
+    }
+
+    for ( i = pers_p->marks.no_of_marks; i > 0; i-- )
+    {
+	if ( do_mark_text(pers_no, pers_p,
+			  pers_p->marks.marks[0].text_no, NULL, 0) != OK )
+	{
+	    log("WNG: do_delete_pers(): can't unmark text %d (i == %d)\n",
+		pers_p->marks.marks[0].text_no, i);
+	}
+    }
+    
+    if ( do_set_user_area(pers_no, pers_p, 0) != OK )
+    {
+	log("WNG: do_delete_pers(): can't unmark user_area\n");
+    }
+
+    s_clear( &pers_p->username );
+    sfree( pers_p->created_texts.texts );
+    pers_p->created_texts.texts = NULL;
+    pers_p->created_texts.no_of_texts = 0;
+
+    cached_delete_person(pers_no);
+    /* ??? Eller r{cker det med cached_delete_conf() ??? */
+    
+    /* ASYNC */
+    return OK;    
+}
+
+/*
+ * Check a password
+ */
+Success
+chk_passwd( Password   pwd,
+	    const String     s )
+{
+    int i;
+    
+    if ( s.len != *pwd )
+	return FAILURE;
+
+    for ( i = 0; i < s.len; i++ )
+	if ( pwd[ i + 1 ] != s.string[ i ] )
+	    return FAILURE;
+
+    return OK;
+}
+
+
+/*
+ * Mark a text.
+ *
+ * If the you already have marked the text the mark-type will be changed. 
+ * If mark_type == 0 the mark will be deleted. You can only mark texts
+ * that you are allowed to read. You are always allowed to read all
+ * texts that you have marked even if you are no longer a recipient
+ * of the text.
+ */
+
+extern Success
+mark_text( Text_no	text_no,	/* Will fail if the user is not */
+	   u_char	mark_type )	/* allowed to read the text.	*/
+{
+    Text_stat *text_s = NULL;
+    BUGDECL;
+        
+    CHK_LOGIN(FAILURE);
+
+    if ( mark_type != 0 )
+    {
+	/* Check that the user is allowed to read the text. */
+	GET_T_STAT(text_s, text_no, FAILURE);
+	if ( !text_read_access(text_no, text_s) )
+	{
+	    kom_errno = KOM_PERM;
+	    return FAILURE;
+	}
+    }
+
+    BUG(("Person %d markerar text %d med typ %d\n",
+	 ACTPERS, text_no, mark_type));
+    
+    return do_mark_text(ACTPERS, ACT_P, text_no, text_s, mark_type);
+}
+
+/*
+ * Create a new person.
+ *
+ * If CREATE_PERSON_BEFORE_LOGIN (#define'd in config.h) is true
+ * anyone can create themself. Otherwise you must be logged in to
+ * create a new person.
+ *
+ * If you are not logged in an auto-login as the created person is
+ * performed. (This is so that it is impossible to create a million
+ * persons (and so destroy the database) anonymously. This way it will
+ * at least be possible to see from which machine the attack came).
+ *
+ * If you are logged in you will be supervisor and creator of the new
+ * person, otherwise the new person will be supervisor and creator.
+ *
+ * The new person will be rd_prot, but not secret. His privileges will
+ * be as defined in DEFAULT_PRIV_BITS in kom-types.c.
+ *
+ * This function returns the Pers_no of the new person, or 0 if an error
+ * occured. (0 is an illegal Pers_no).
+ */
+extern Pers_no
+create_person (String         name,
+	       const String  passwd )
+{
+    Pers_no 	  new_user;
+    Conference	* mailbox;
+    Person	* pers_p;
+    
+
+    if (!CREATE_PERSON_BEFORE_LOGIN && ACTPERS == 0 )
+    {
+	kom_errno = KOM_LOGIN;
+	return 0;
+    }
+
+#if 0
+    if ( !logins_allowed  && !ACTPERS ) /* A new user can't create himself */
+    {					/* if he isn't allowed to login. */
+	kom_errno = KOM_LOGIN_DISALLOWED;
+	return 0;
+    }
+#endif
+
+    if ( !legal_name( name ) )
+    {
+	kom_errno = KOM_BAD_NAME;
+	return 0;
+    }
+
+    if ( !unique_name( name, 0 ) )
+    {
+	kom_errno = KOM_CONF_EXISTS;
+	return 0;
+    }
+
+    if ( !legal_passwd( passwd ) )
+    {
+	kom_errno = KOM_PWD;
+	return 0;
+    }
+
+    new_user = cached_create_conf( name );
+
+    if ( (mailbox = cached_get_conf_stat( new_user ) ) == NULL)
+    {
+	restart_kom("create_person() - can't get conf_stat");
+    }
+    
+    
+    mailbox->creator		= ACTPERS ? ACTPERS : new_user;
+    mailbox->creation_time 	= time(NULL);
+    mailbox->presentation	= 0;		/* No presentation yet */
+    mailbox->supervisor		= ACTPERS ? ACTPERS : new_user;
+    mailbox->permitted_submitters = 0;
+    mailbox->super_conf		= ACTPERS;
+    mailbox->type.rd_prot	= 1;
+    mailbox->type.original	= 0;
+    mailbox->type.secret	= 0;
+    mailbox->type.letter_box	= 1;
+    mailbox->last_written	= mailbox->creation_time;
+    mailbox->msg_of_day		= 0;
+    mailbox->nice		= DEFAULT_NICE;
+    mailbox->members		= EMPTY_MEMBER_LIST;
+    mailbox->texts		= EMPTY_TEXT_LIST;
+
+    mark_conference_as_changed( new_user );
+
+    cached_set_conf_type(new_user, mailbox->type);
+
+    /* allocate Person */
+
+    if ( cached_create_person( new_user ) == FAILURE )
+    {
+	cached_delete_conf( new_user );
+	mark_conference_as_changed( new_user ); /*&*/
+	return 0;
+    }
+
+    if ( ( pers_p = cached_get_person_stat( new_user )) == NULL )
+    {
+	cached_delete_conf( new_user );
+	mark_conference_as_changed( new_user ); /*&*/
+	restart_kom("create_person() - can't get pers_stat");
+    }
+
+	
+    /* Fill in Person */
+    *pers_p = EMPTY_PERSON;
+    if ( do_set_passwd( pers_p->pwd, passwd) != OK )
+	restart_kom("create_person(): can't set passwd\n");
+
+    mark_person_as_changed( new_user );
+
+    do_add_member( new_user, mailbox, new_user, pers_p, UCHAR_MAX, 0 );
+
+    if ( ACTPERS )
+    {
+	ACT_P->created_persons++;
+	mark_person_as_changed( ACTPERS );
+    }
+    else
+    {
+	/* Auto login */
+	login( new_user, passwd);
+    }
+
+    return new_user;
+}
+
+
+/*
+ * Get the person status of PERSON.
+ */
+extern  Success
+get_person_stat (Pers_no	  person,
+ 		 Person		* result)
+{
+    Person *p_orig;
+    Conference *pers_c;
+    Access acc;
+        
+    CHK_LOGIN(FAILURE);
+    
+    GET_P_STAT(p_orig, person, FAILURE);
+    GET_C_STAT(pers_c, person, FAILURE);
+    
+    acc = access_perm(person, pers_c);
+  
+    if ( acc == error )
+	return FAILURE;
+
+    if ( acc == none )
+    {
+	kom_errno = KOM_UNDEF_PERS;
+	return FAILURE;
+    }
+
+    *result = *p_orig;	/* Make a copy of the struct. */
+    
+    /* The user area is normally secret. */
+
+    if( acc != unlimited )
+	result->user_area	= 0;
+    
+    return OK;
+}
+
+/* As get_person_stat, but only return the name if bit 0 in mask is set. */
+
+extern  Success
+get_person_stat_old (Pers_no	  person,
+		     int 	  mask,
+		     Person	* result)
+{
+    Person *p_orig;
+    Conference *pers_c;
+    Access acc;
+        
+    CHK_LOGIN(FAILURE);
+    
+    GET_P_STAT(p_orig, person, FAILURE);
+    GET_C_STAT(pers_c, person, FAILURE);
+    
+    acc = access_perm(person, pers_c);
+  
+    if ( acc == error )
+	return FAILURE;
+
+    if ( acc == none )
+    {
+	kom_errno = KOM_UNDEF_PERS;
+	return FAILURE;
+    }
+
+    *result = *p_orig;	/* Make a copy of the struct. */
+    
+    /* The user area is normally secret. */
+
+    if( acc != unlimited )
+	result->user_area	= 0;
+
+    if ( !(mask & 1 ) )
+	result->username = EMPTY_STRING;
+    
+    return OK;
+}
+
+
+/* /// */
+extern  Success
+get_created_texts(Pers_no       pers_no,
+		  Local_text_no first,
+		  u_long        len,
+		  Text_list   * result)
+{
+    Person *pers_p;
+    Access acc;
+    Local_text_no new_first;
+
+    CHK_LOGIN(FAILURE);
+
+    GET_P_STAT(pers_p, pers_no, FAILURE);
+
+    acc = access_perm(pers_no, NULL);
+
+    if ( acc == error )
+	return FAILURE;
+
+    if ( acc == none )
+    {
+	kom_errno = KOM_UNDEF_PERS;
+	return FAILURE;
+    }
+
+    *result = pers_p->created_texts;
+    new_first = max(result->first_local_no, first);
+
+    if ( new_first >= result->first_local_no + result->no_of_texts)
+    {
+	kom_errno = KOM_NO_SUCH_LOCAL_TEXT;
+	return FAILURE;
+    }
+
+    result->texts += new_first - result->first_local_no;
+    result->no_of_texts = min(len, (result->no_of_texts
+				    - (new_first - result->first_local_no)));
+    result->first_local_no = new_first;
+
+    return OK;
+}
+
+
+
+/*
+ * Set privilege bits of a person. You must have the wheel bit set
+ * to be allowed to do this.
+ */
+extern Success
+set_priv_bits(	Pers_no		person,
+		Priv_bits	privileges )
+{
+    Person *p;
+    
+    CHK_LOGIN(FAILURE);
+
+    GET_P_STAT(p, person, FAILURE);
+
+    if ( ENA(wheel, 6) )
+    {
+	p->privileges = privileges;
+	mark_person_as_changed( person );
+	return OK;
+    }
+    else
+    {
+	kom_errno = KOM_PERM;
+	return FAILURE;
+    }
+}
+
+/*
+ * Set password. You may set the password of yourself and all persons that
+ * you are supervisor of. OLD_PWD is your password. 
+ */
+extern Success 
+set_passwd (Pers_no	    person,
+	    const String   old_pwd,	/* of the one who is changing the pwd,
+					   not necessarily the person whose pwd
+					   is changed. */
+	    const String   new_pwd)	/* of person */
+{
+    Person *p;
+    
+    CHK_LOGIN(FAILURE);
+
+    GET_P_STAT(p, person, FAILURE);
+
+    if ( person != ACTPERS && !ENA(wheel, 7)
+	&& !is_supervisor(person, NULL) )
+    {
+	kom_errno = KOM_PERM;
+	return FAILURE;	   /* Not allowed to change the other persons pwd */
+    }
+
+    if ( chk_passwd( ACT_P->pwd, old_pwd ) == FAILURE )     
+    {
+	kom_errno = KOM_PWD;
+	return FAILURE;
+    }
+
+    if ( do_set_passwd( p->pwd, new_pwd ) != OK)
+    {
+	kom_errno = KOM_PWD;
+	return FAILURE;
+    }
+
+    mark_person_as_changed( person );
+
+    return OK;
+}
+
+/*
+ * Set user_area. The text is a text which has previously been created
+ * with create_text(). It typically contains option-settings for the
+ * clients, e. g. if you want to be interrupted when new mail arrives.
+ * The format of this text is not yet defined.
+ *
+ * set_user_area(0) to clear the user area.
+ */
+Success
+set_user_area(Text_no text_no)
+{
+    return do_set_user_area(ACTPERS, ACT_P, text_no);
+}
+
+/*
+ * Ask which texts a person has read in a certain conference.
+ * Can be done before loggin. Will return EMPTY_MEMBERSHIP if VICTIM
+ * has his unread_is_secret-flag set. This can not be distinguished
+ * from the case when VICTIM has not read any texts in CONF_NO. This
+ * is a feature, not a bug. (At least I think so. /ceder+++)
+ *
+ * (*** Well, I've changed my mind. This call is full of security holes.
+ *	/ceder)
+ *
+ * Will fail if VICTIM is not a member of CONF_NO.
+ */
+extern Success
+query_read_texts(Pers_no	  victim,
+		 Conf_no	  conf_no,
+		 Membership * result)	/* Points to area to store result in */
+{
+    Person	* pers_p;
+    Membership  * membp;
+    Access	  victim_acc;
+
+    GET_P_STAT( pers_p, victim, FAILURE);
+
+    victim_acc = access_perm(victim, NULL);
+    if ( victim_acc <= none )
+    {
+	kom_errno = KOM_UNDEF_PERS;
+	return FAILURE;
+    }
+
+    if ( access_perm(conf_no, NULL) <= none )
+    {
+	kom_errno = KOM_UNDEF_CONF;
+	return FAILURE;
+    }
+    
+    if ( (membp = locate_membership( victim, pers_p )) == NULL)
+    {
+	kom_errno = KOM_NOT_MEMBER;
+	return FAILURE;
+    }
+
+    *result = *membp;
+
+    if ( pers_p->flags.unread_is_secret &&
+	victim_acc != unlimited)
+    {
+	result->last_time_read = NO_TIME;
+	result->last_text_read = 0;
+	result->no_of_read = 0;
+	result->read_texts = NULL;
+    }
+
+    return OK;
+}
+
diff --git a/src/server/prot-a-output.c b/src/server/prot-a-output.c
new file mode 100644
index 0000000000000000000000000000000000000000..1eb461aad5a55fa51f2b5c950e6b8f2df010d5a3
--- /dev/null
+++ b/src/server/prot-a-output.c
@@ -0,0 +1,501 @@
+/*
+ * prot-a-output.c  -  write objects through a mux connection.
+ *
+ * Written by ceder 1990-07-13
+ */
+
+#include <kom-types.h>
+#include <stdio.h>
+#include <sys/file.h>
+#include <time.h>
+#include "lyskomd.h"
+#include "com.h"
+#include "connections.h"
+#include "prot-a-output.h"
+#include "isc.h"
+#include "mux.h"
+
+void
+prot_a_output_person (Connection *fp,
+		      Person *person)
+{
+    prot_a_output_string (fp, person->username);
+    prot_a_output_priv_bits (fp, person->privileges);
+    prot_a_output_personal_flags (fp, person->flags);
+
+    prot_a_output_time(fp, person->last_login);
+
+    mux_printf (fp, " %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
+	     (u_long) person -> user_area,
+	     (u_long) person -> total_time_present, /* This is not a time,
+						     * but a number of seconds.
+						     */
+	     (u_long) person -> sessions,
+	     (u_long) person -> created_lines,
+	     (u_long) person -> created_bytes,
+	     (u_long) person -> read_texts,
+	     (u_long) person -> no_of_text_fetches,
+	     (u_long) person -> created_persons,
+	     (u_long) person -> created_confs,
+	     (u_long) person -> created_texts.first_local_no,
+	     (u_long) person -> created_texts.no_of_texts,
+	     (u_long) person -> marks.no_of_marks,
+	     (u_long) person -> conferences.no_of_confs);
+}
+
+void
+prot_a_output_membership(Connection *fp,
+			 const Membership *mship)
+{
+    int i;
+    
+    prot_a_output_time(fp, mship->last_time_read );
+    
+    mux_printf(fp, " %lu %lu %lu %lu",
+	    (u_long)mship->conf_no,
+	    (u_long)mship->priority,
+	    (u_long)mship->last_text_read,
+	    (u_long)mship->no_of_read);
+    
+    if ( mship->read_texts != NULL && mship->no_of_read > 0)
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < mship->no_of_read; i++)
+	    mux_printf(fp, " %lu", (u_long)mship->read_texts[ i ]);
+	
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}
+
+	
+void
+prot_a_output_membership_list (Connection	* fp,
+			       Membership_list	  mlist)
+{
+    int i;
+    
+    mux_printf(fp, " %lu", (u_long)mlist.no_of_confs);
+
+    if ( mlist.confs != NULL && mlist.no_of_confs > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < mlist.no_of_confs; i++)
+	    prot_a_output_membership(fp, mlist.confs + i);
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}
+
+
+void
+prot_a_output_conf_list(Connection *fp,
+			Conf_list_old conf_list)
+{
+    int i;
+
+    mux_printf(fp, " %lu", (u_long)conf_list.no_of_conf_nos);
+    if ( conf_list.conf_nos != NULL && conf_list.no_of_conf_nos > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < conf_list.no_of_conf_nos; i++ )
+	    mux_printf(fp, " %lu", (u_long)conf_list.conf_nos[ i ]);
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+
+    if ( conf_list.type_of_conf != NULL && conf_list.no_of_conf_nos > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < conf_list.no_of_conf_nos; i++ )
+	    prot_a_output_conf_type(fp, conf_list.type_of_conf[ i ]);
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}
+
+void
+prot_a_output_conf_no_list(Connection *fp,
+			   Conf_no_list conf_no_list)
+{
+    int i;
+
+    mux_printf(fp, " %lu", (u_long)conf_no_list.no_of_confs);
+    if ( conf_no_list.conf_nos != NULL && conf_no_list.no_of_confs > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < conf_no_list.no_of_confs; i++ )
+	    mux_printf(fp, " %lu", (u_long)conf_no_list.conf_nos[ i ]);
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}
+    
+
+void
+prot_a_output_conference (Connection *fp,
+			  Conference *conf_c)
+{
+    prot_a_output_string(fp, conf_c->name);
+
+    prot_a_output_conf_type(fp, conf_c->type);
+
+    prot_a_output_time(fp, conf_c -> creation_time );
+    prot_a_output_time(fp, conf_c -> last_written );
+
+    mux_printf (fp, " %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
+	     (u_long) conf_c -> creator,
+	     (u_long) conf_c -> presentation,
+	     (u_long) conf_c -> supervisor,
+	     (u_long) conf_c -> permitted_submitters,
+	     (u_long) conf_c -> super_conf,
+	     (u_long) conf_c -> msg_of_day,
+	     (u_long) conf_c -> nice,
+	     (u_long) conf_c -> members.no_of_members,
+	     (u_long) conf_c -> texts.first_local_no,
+	     (u_long) conf_c -> texts.no_of_texts);
+}
+
+
+void
+prot_a_output_mark_list(Connection *fp,
+			Mark_list mark_list)
+{
+    int i;
+
+    mux_printf(fp, " %lu", (u_long)mark_list.no_of_marks);
+
+    if ( mark_list.marks != NULL && mark_list.no_of_marks > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < mark_list.no_of_marks; i++ )
+	    prot_a_output_mark(fp, mark_list.marks[ i ]);
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+    
+}
+
+
+void
+prot_a_output_text_stat(Connection *fp,
+			Text_stat *t_stat)
+{
+    int i;
+
+    prot_a_output_time(fp, t_stat->creation_time);
+    
+    mux_printf(fp, " %lu %lu %lu %lu %lu",
+	    (u_long)t_stat->author,
+	    (u_long)t_stat->no_of_lines,
+	    (u_long)t_stat->no_of_chars,
+	    (u_long)t_stat->no_of_marks,
+	    (u_long)t_stat->no_of_misc);
+
+    if ( t_stat->misc_items != NULL && t_stat->no_of_misc > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < t_stat->no_of_misc; i++ )
+	    prot_a_output_misc_info(fp, t_stat->misc_items[ i ]);
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}	
+
+
+void
+prot_a_output_text_list(Connection *fp,
+			Text_list text_list)
+{
+    int i;
+
+    mux_printf(fp, " %lu %lu",
+	    (u_long)text_list.first_local_no,
+	    (u_long)text_list.no_of_texts);
+
+    if ( text_list.texts != NULL && text_list.no_of_texts > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < text_list.no_of_texts; i++ )
+	    mux_printf(fp, " %lu", (u_long)text_list.texts[ i ]);
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}
+    
+void
+prot_a_output_who_info(Connection *fp,
+		       Who_info *info)
+{
+    mux_printf(fp, " %lu %lu %lu",
+	    (u_long)info->person,
+	    (u_long)info->working_conference,
+	    (u_long)info->session_no);
+
+    prot_a_output_string(fp, info->what_am_i_doing);
+    prot_a_output_string(fp, info->username);
+}
+
+
+void
+prot_a_output_who_info_list(Connection *fp,
+			    Who_info_list info)
+{
+    int i;
+    
+    mux_printf(fp, " %lu", (u_long)info.no_of_persons);
+
+    if ( info.info != NULL && info.no_of_persons > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < info.no_of_persons; i++ )
+	{
+	    prot_a_output_who_info(fp, &info.info[ i ]);
+	}
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}
+
+void
+prot_a_output_who_info_list_old(Connection *fp,
+				Who_info_list_old info)
+{
+    int i;
+    
+    mux_printf(fp, " %lu", (u_long)info.no_of_persons);
+
+    if ( info.info != NULL && info.no_of_persons > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < info.no_of_persons; i++ )
+	{
+	    mux_printf(fp, " %lu %lu",
+		    (u_long)info.info[ i ].person,
+		    (u_long)info.info[ i ].working_conference);
+	    
+	    prot_a_output_string(fp, info.info[ i ].what_am_i_doing);
+	}
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}
+
+void
+prot_a_output_session_info(Connection *fp,
+			   Session_info *info)
+{
+    mux_printf(fp, " %lu %lu %lu",
+	    (u_long)info->person,
+	    (u_long)info->working_conference,
+	    (u_long)info->session);
+
+    prot_a_output_string(fp, info->what_am_i_doing);
+    prot_a_output_string(fp, info->username);
+
+    mux_printf(fp, " %lu", (u_long)info->idle_time);
+    prot_a_output_time(fp, info->connection_time);
+}
+    
+void
+prot_a_output_info(Connection *fp,
+		   Info *info)
+{
+    mux_printf(fp, " %lu %lu %lu %lu %lu %lu",
+	    (u_long)info->version,
+	    (u_long)info->conf_pres_conf,
+	    (u_long)info->pers_pres_conf,
+	    (u_long)info->motd_conf,
+	    (u_long)info->kom_news_conf,
+	    (u_long)info->motd_of_lyskom);
+}
+
+
+extern void
+prot_a_output_string(Connection *fp,
+		     String str)
+{
+    mux_printf(fp, " %luH", (u_long)str.len);
+    mux_write(fp, str.string, str.len);
+}
+
+
+extern void
+prot_a_output_priv_bits(Connection *fp,
+			Priv_bits bits)
+{
+    mux_putc(' ', fp);
+    mux_putc(bits.wheel + '0', fp);
+    mux_putc(bits.admin + '0', fp);
+    mux_putc(bits.statistic + '0', fp);
+    mux_putc(bits.create_pers + '0', fp);
+    mux_putc(bits.create_conf + '0', fp);
+    mux_putc(bits.change_name + '0', fp);
+    mux_putc(bits.flg7 + '0', fp);
+    mux_putc(bits.flg8 + '0', fp);
+    mux_putc(bits.flg9 + '0', fp);
+    mux_putc(bits.flg10 + '0', fp);
+    mux_putc(bits.flg11 + '0', fp);
+    mux_putc(bits.flg12 + '0', fp);
+    mux_putc(bits.flg13 + '0', fp);
+    mux_putc(bits.flg14 + '0', fp);
+    mux_putc(bits.flg15 + '0', fp);
+    mux_putc(bits.flg16 + '0', fp);
+}
+
+
+extern void
+prot_a_output_personal_flags(Connection *fp,
+			     Personal_flags flags)
+{
+    mux_putc(' ', fp);
+    mux_putc(flags.unread_is_secret + '0', fp);
+    mux_putc(flags.flg2 + '0', fp);
+    mux_putc(flags.flg3 + '0', fp);
+    mux_putc(flags.flg4 + '0', fp);
+    mux_putc(flags.flg5 + '0', fp);
+    mux_putc(flags.flg6 + '0', fp);
+    mux_putc(flags.flg7 + '0', fp);
+    mux_putc(flags.flg8 + '0', fp);
+}	
+
+extern void
+prot_a_output_conf_type(Connection *fp,
+			Conf_type type)
+{
+    mux_putc(' ', fp);
+    mux_putc(type.rd_prot + '0', fp);
+    mux_putc(type.original + '0', fp);
+    mux_putc(type.secret + '0', fp);
+    mux_putc(type.letter_box + '0', fp);
+}
+
+
+extern void
+prot_a_output_member_list(Connection *fp,
+			  Member_list m_list)
+{
+    int i;
+
+    mux_printf(fp, " %lu", (u_long)m_list.no_of_members);
+    if ( m_list.members != NULL && m_list.no_of_members > 0 )
+    {
+	mux_printf(fp, " {");
+	for ( i = 0; i < m_list.no_of_members; i++ )
+	    prot_a_output_member(fp, m_list.members[ i ]);
+	mux_printf(fp, " }");
+    }
+    else
+	mux_printf(fp, " *");
+}
+
+
+void
+prot_a_output_member(Connection *fp,
+		     Member member)
+{
+    mux_printf(fp, " %lu", (u_long)member.member);
+}
+
+    
+extern void
+prot_a_output_mark(Connection *fp,
+		   Mark mark)
+{
+    mux_printf(fp, " %lu %lu", (u_long)mark.text_no, (u_long)mark.mark_type);
+}
+
+
+extern void
+prot_a_output_misc_info(Connection *fp, 
+			Misc_info misc)
+{
+    mux_printf(fp, " %lu", (u_long)misc.type);
+    
+    switch(misc.type)
+    {
+    case recpt:
+	mux_printf(fp, " %lu", (u_long)misc.datum.recipient);
+	break;
+	
+    case cc_recpt:
+	mux_printf(fp, " %lu", (u_long)misc.datum.cc_recipient);
+	break;
+	
+    case loc_no:
+	mux_printf(fp, " %lu", (u_long)misc.datum.local_no);
+	break;
+	
+    case rec_time:
+	prot_a_output_time(fp, misc.datum.received_at);
+	break;
+	
+    case comm_to:
+	mux_printf(fp, " %lu", (u_long)misc.datum.comment_to);
+	break;
+	
+    case comm_in:
+	mux_printf(fp, " %lu", (u_long)misc.datum.commented_in);
+	break;
+	
+    case footn_to:
+	mux_printf(fp, " %lu", (u_long)misc.datum.footnote_to);
+	break;
+	
+    case footn_in:
+	mux_printf(fp, " %lu", (u_long)misc.datum.footnoted_in);
+	break;
+	
+    case sent_by:
+	mux_printf(fp, " %lu", (u_long)misc.datum.sender);
+	break;
+	
+    case sent_at:
+	prot_a_output_time(fp, misc.datum.sent_at);
+	break;
+
+#ifndef COMPILE_CHECKS
+    default:
+	restart_kom("prot_a_output_misc_info: Illegal misc\n");
+#endif
+    }
+}
+
+
+void
+prot_a_output_time(Connection *fp,
+		   time_t clock)
+{
+    struct tm *time;
+
+    time = localtime( &clock );
+
+    mux_printf(fp, " %lu %lu %lu %lu %lu %lu %lu %lu %lu",
+	    (u_long) time -> tm_sec,
+	    (u_long) time -> tm_min,
+	    (u_long) time -> tm_hour,
+	    (u_long) time -> tm_mday,
+	    (u_long) time -> tm_mon,
+	    (u_long) time -> tm_year,
+	    (u_long) time -> tm_wday,
+	    (u_long) time -> tm_yday,
+	    (u_long) time -> tm_isdst);
+}
+
+
+void
+prot_a_output_session_no(Connection *fp,
+			 Session_no session_no)
+{
+    mux_printf(fp, " %lu", (u_long) session_no);
+}
diff --git a/src/server/prot-a-output.h b/src/server/prot-a-output.h
new file mode 100644
index 0000000000000000000000000000000000000000..bde9cd429803a060fdf7edc3beb92a3698499483
--- /dev/null
+++ b/src/server/prot-a-output.h
@@ -0,0 +1,99 @@
+
+extern void
+prot_a_output_person(Connection *fp,
+		     Person *person);
+
+
+extern void
+prot_a_output_membership(Connection *fp,
+			 const Membership *mship);
+
+extern void
+prot_a_output_membership_list(Connection *fp,
+			      Membership_list mlist);
+
+extern void
+prot_a_output_conf_list(Connection *fp,
+			Conf_list_old conf_list);
+
+extern void
+prot_a_output_conf_no_list(Connection *fp,
+			   Conf_no_list conf_no_list);
+
+extern void
+prot_a_output_conference(Connection *fp,
+			 Conference *conf_c);
+
+extern void
+prot_a_output_mark_list(Connection *fp,
+			Mark_list mark_list);
+
+extern void
+prot_a_output_text_stat(Connection *fp,
+			Text_stat *t_stat);
+
+extern void
+prot_a_output_text_list(Connection *fp,
+			Text_list text_list);
+
+extern void
+prot_a_output_info(Connection *fp,
+		   Info *info);
+
+extern void
+prot_a_output_who_info(Connection *fp,
+		       Who_info *info);
+
+extern void
+prot_a_output_who_info_list(Connection *fp,
+			    Who_info_list info);
+
+extern void
+prot_a_output_who_info_list_old(Connection *fp,
+				Who_info_list_old info);
+
+void
+prot_a_output_session_info(Connection *fp,
+			   Session_info *info);
+
+extern void
+prot_a_output_string(Connection *fp,
+		     String str);
+
+extern void
+prot_a_output_priv_bits(Connection *fp,
+			Priv_bits bits);
+
+extern void
+prot_a_output_personal_flags(Connection *fp,
+			     Personal_flags flags);
+
+
+extern void
+prot_a_output_conf_type(Connection *fp,
+			Conf_type type);
+
+extern void
+prot_a_output_member_list(Connection *fp,
+			  Member_list m_list);
+
+extern void
+prot_a_output_mark(Connection *fp,
+		   Mark mark);
+
+extern void
+prot_a_output_misc_info(Connection *fp, 
+			Misc_info misc);
+
+extern void
+prot_a_output_member(Connection *fp,
+		     Member member);
+
+void
+prot_a_output_time(Connection *fp,
+		   time_t clock);
+
+void
+prot_a_output_session_no(Connection *fp,
+			 Session_no session_no);
+
diff --git a/src/server/prot-a-parse.c b/src/server/prot-a-parse.c
new file mode 100644
index 0000000000000000000000000000000000000000..34a0f45063c2b2e94fffd9260a8c75b8a6ef4f86
--- /dev/null
+++ b/src/server/prot-a-parse.c
@@ -0,0 +1,249 @@
+/*
+ * prot-a-parse.c - parse protocol-A messages.
+ *
+ * BUG: Not all functions are used, I think. /ceder
+ */
+
+#include <setjmp.h>
+#include <string.h>
+
+#include <kom-types.h>
+#include "s-string.h"
+#include "lyskomd.h"
+#include "com.h"
+#include "connections.h"
+#include "prot-a-parse.h"
+#include "isc-parse.h"
+#include "smalloc.h"
+#include "minmax.h"
+#include "prot-a.h"
+#include "config.h"
+
+long
+prot_a_parse_long(Connection *client)
+{
+    String	token;
+    String_size end;
+    long	res;
+
+    token = prot_a_get_token(client);
+    res = s_strtol(token, &end, PROTOCOL_NUMBER_BASE);
+    if (end != s_strlen(token))
+	longjmp(parse_env, ISC_PROTOCOL_ERR);
+    
+    return res;
+}
+
+void
+prot_a_parse_priv_bits(Connection *client,
+		       Priv_bits      *res)
+{
+    String token;
+
+    token = prot_a_get_token(client);
+
+    if ( s_strlen(token) != 16 )
+	longjmp(parse_env, ISC_PROTOCOL_ERR);
+    
+    res->wheel = token.string[ 0 ] != '0';
+    res->admin = token.string[ 1 ] != '0';
+    res->statistic = token.string[ 2 ] != '0';
+    res->create_pers = token.string[ 3 ] != '0';
+    res->create_conf = token.string[ 4 ] != '0';
+    res->change_name = token.string[ 5 ] != '0';
+    res->flg7 = token.string[ 6 ] != '0';
+    res->flg8 = token.string[ 7 ] != '0';
+    res->flg9 = token.string[ 8 ] != '0';
+    res->flg10 = token.string[ 9 ] != '0';
+    res->flg11 = token.string[ 10 ] != '0';
+    res->flg12 = token.string[ 11 ] != '0';
+    res->flg13 = token.string[ 12 ] != '0';
+    res->flg14 = token.string[ 13 ] != '0';
+    res->flg15 = token.string[ 14 ] != '0';
+    res->flg16 = token.string[ 15 ] != '0';
+}
+
+void
+prot_a_parse_conf_type(Connection *client,
+		Conf_type      *res)
+{
+    String token;
+
+    token = prot_a_get_token(client);
+    
+    if ( s_strlen(token) != 4 )
+	longjmp(parse_env, ISC_PROTOCOL_ERR);
+  
+    res->rd_prot = token.string[ 0 ] != '0';
+    res->original = token.string[ 1 ] != '0';
+    res->secret = token.string[ 2 ] != '0';
+    res->letter_box = token.string[ 3 ] != '0';
+}
+
+/*
+ * Parse a string. At most 'maxlen' characters are allowed. If the
+ * client sends a longer string only the first 'maxlen+1' characters
+ * are read. The following characters are discarded.
+ */
+void
+prot_a_parse_string(Connection  *client,
+		    String	    *result,
+		    int		     maxlen)
+{
+    String_size hptr;		/* Pointer to 'H' */
+    String_size client_len;	/* The len the client is sending. */
+    String_size truncated_len;	/* How much the server will receive. */
+    String_size to_skip;
+    String tmp;
+
+    switch ( client->string_parse_pos )
+    {
+    case 0:
+	/* Get number and discard trailing 'H' */
+	result->len = s_strtol(s_fsubstr(client->unparsed,
+					 client->first_to_parse,
+					 END_OF_STRING),
+			       &hptr, PROTOCOL_NUMBER_BASE);
+
+	if ( hptr == -1
+	    || client->first_to_parse + hptr
+	    >= s_strlen(client->unparsed) )
+	{
+	    longjmp(parse_env, ISC_MSG_INCOMPLETE);
+	}
+	
+	/* Check that
+	      a) there is a trailing H
+	      b) there was at least one digit before the H */
+
+	if ( client->unparsed.string[ client->first_to_parse
+					  + hptr ] != 'H'
+	    || hptr <= 0 )
+	{
+	    longjmp(parse_env, ISC_PROTOCOL_ERR);
+	}
+
+	client->first_to_parse += 1 + hptr;
+	client->string_parse_pos = 1;
+	/* Fall through */
+    case 1:
+	/* Check that the entire string is transmitted. */
+	/* (Don't care about the trailing part that will be skipped if the
+	 *  string is longer than maxlen) */
+	truncated_len = min(maxlen + 1, result->len);
+	client_len = result->len;
+	
+	if ( client->first_to_parse + truncated_len
+	    > s_strlen(client->unparsed) )
+	{
+	    longjmp(parse_env, ISC_MSG_INCOMPLETE);
+	}
+
+	result->string = client->unparsed.string + client->first_to_parse;
+	result->len = truncated_len;
+	tmp = EMPTY_STRING;
+	s_strcpy(&tmp, *result); /* Copy the string. */
+	*result = tmp;
+	result->len = client_len;
+
+	client->first_to_parse += truncated_len;
+	client->string_parse_pos = 2;
+	/* Fall through */
+    case 2:
+	/* Was the string too long? If so, skip the truncated data. */
+
+	client_len = result->len;
+	truncated_len = min(maxlen+1, result->len);
+	
+	if ( client_len > truncated_len )
+	{
+	    to_skip = min(client_len - truncated_len,
+			   client->unparsed.len - client->first_to_parse);
+	    client_len -= to_skip;
+	    client->first_to_parse += to_skip;
+	}
+
+	result->len = client_len;
+	
+	if ( client_len > truncated_len )
+	    longjmp(parse_env, ISC_MSG_INCOMPLETE);
+	/* Fall through */
+    default:
+	client->string_parse_pos = 0;
+    }
+}
+
+
+extern void
+prot_a_parse_misc_info(Connection *client,
+		       Misc_info      *result)
+{
+    switch ( client->struct_parse_pos )
+    {
+    case 0:
+	result->type = prot_a_parse_long(client);
+	client->struct_parse_pos = 1;
+	/* Fall through */
+    case 1:
+	switch( result->type )
+	{
+	case recpt:
+	    result->datum.recipient = prot_a_parse_long(client);
+	    break;
+
+	case cc_recpt:
+	    result->datum.cc_recipient = prot_a_parse_long(client);
+	    break;
+	    
+	case loc_no:
+	    result->datum.local_no = prot_a_parse_long(client);
+	    break;
+	    
+	case comm_to:
+	    result->datum.comment_to = prot_a_parse_long(client);
+	    break;
+	    
+	case footn_to:
+	    result->datum.footnote_to = prot_a_parse_long(client);
+	    break;
+
+	case comm_in:
+	case footn_in:
+	case rec_time:
+	case sent_by:
+	case sent_at:
+	    /* Fall through */
+	    
+#ifndef COMPILE_CHECKS
+	default:
+#endif
+	    longjmp(parse_env, ISC_PROTOCOL_ERR);
+	}
+    default:
+	client->struct_parse_pos = 0;
+    }
+}
+
+
+String
+prot_a_get_token(Connection *client)
+{
+    String result;
+    String_size old_first;
+
+    old_first = client->first_to_parse;
+    
+    result = s_strtok(client->unparsed, &client->first_to_parse,
+		      s_fcrea_str(" \t\n\r"));
+
+    /* Check that there was at least one trailing blank. */
+    
+    if ( client->first_to_parse >= s_strlen(client->unparsed) )
+    {
+	client->first_to_parse = old_first;
+	longjmp(parse_env, ISC_MSG_INCOMPLETE);
+    }
+
+    return result;
+}
+
diff --git a/src/server/prot-a-parse.h b/src/server/prot-a-parse.h
new file mode 100644
index 0000000000000000000000000000000000000000..888ac02c128808c2fb5a370c4f93eea44dcbe1f7
--- /dev/null
+++ b/src/server/prot-a-parse.h
@@ -0,0 +1,28 @@
+extern long
+prot_a_parse_long(Connection *client);
+
+extern void
+prot_a_parse_priv_bits(Connection *client,
+		       Priv_bits  *result);
+
+extern void
+prot_a_parse_conf_type(Connection *client,
+		       Conf_type      *result);
+
+
+extern void
+prot_a_parse_string(Connection *client,
+	     String	    *result,
+	     int	     maxlen);
+
+extern void
+prot_a_parse_misc_info(Connection *client,
+		Misc_info      *result);
+
+/*
+ * Return next token from the input stream. Note that the String returned
+ * by this call points into data that might be freed by the next call to
+ * get_token or any function which reads from the stream.
+ */
+extern String
+prot_a_get_token(Connection *client);
diff --git a/src/server/prot-a-send-async.c b/src/server/prot-a-send-async.c
new file mode 100644
index 0000000000000000000000000000000000000000..d7490fc0b4a824b4d2e3900fba30f7d9898cd7e0
--- /dev/null
+++ b/src/server/prot-a-send-async.c
@@ -0,0 +1,148 @@
+/*
+ * Asynchronous messages in protocol A.
+ */
+
+#include <stdio.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include <services.h>
+#include "async.h"
+#include "com.h"
+#include "connections.h"
+#include "prot-a-send-async.h"
+#include "prot-a-output.h"
+#include "manipulate.h"
+#include "log.h"
+#include "isc.h"
+#include "mux.h"
+
+static void
+async_header(Connection *fp,
+	     int   no_of_tokens,
+	     Async fnc)
+{
+    mux_printf(fp, ":%lu %lu", (u_long)no_of_tokens, (u_long)fnc);
+}
+
+
+
+    
+static void
+async_trailer(Connection *fp)
+{
+    mux_putc('\n', fp);
+    mux_flush(fp);
+}
+
+
+void
+prot_a_async_new_text(Connection *cptr,
+		      Text_no	      text_no,
+		      Text_stat      *text_s)
+{
+    async_header(cptr, 16, ay_new_text);
+    mux_printf(cptr, " %lu", text_no);
+    prot_a_output_text_stat(cptr, text_s);
+    async_trailer(cptr);
+}
+
+
+void
+prot_a_async_i_am_on(Connection *cptr,
+		     Who_info info)
+{
+    async_header(cptr, 5, ay_i_am_on);
+    prot_a_output_who_info(cptr, &info);
+    async_trailer(cptr);
+}
+
+void
+prot_a_async_i_am_off(Connection *cptr,
+		      Pers_no pers_no)
+{
+    async_header(cptr, 1, ay_i_am_off);
+    mux_printf(cptr, " %lu", pers_no);
+    async_trailer(cptr);
+}
+
+
+void
+prot_a_async_logout(Connection *cptr,
+		    Pers_no pers_no,
+		    Session_no session_no)
+{
+    async_header(cptr, 2, ay_logout);
+    mux_printf(cptr, " %lu %lu", (u_long)pers_no, (u_long)session_no);
+    async_trailer(cptr);
+}
+
+void
+prot_a_async_new_name(Connection *cptr,
+		      Conf_no conf_no,
+		      String old_name,
+		      String new_name)
+{
+    async_header(cptr, 3, ay_new_name);
+    mux_printf(cptr, " %lu", (u_long)conf_no);
+    prot_a_output_string(cptr, old_name);
+    prot_a_output_string(cptr, new_name);
+    async_trailer(cptr);
+}
+
+void
+prot_a_async_sync_db(Connection *cptr)
+{
+    async_header(cptr, 0, ay_sync_db);
+    async_trailer(cptr);
+}
+
+void
+prot_a_async_forced_leave_conf(Connection *cptr,
+			       Conf_no         conf_no)
+{
+    async_header(cptr, 1, ay_leave_conf);
+    mux_printf(cptr, " %lu", (u_long)conf_no);
+    async_trailer(cptr);
+}
+
+void
+prot_a_async_login(Connection *cptr,
+		   Pers_no	   pers_no,
+		   int		   session_no)
+{
+    async_header(cptr, 2, ay_login);
+    mux_printf(cptr, " %lu %lu", (u_long)pers_no, (u_long)session_no);
+    async_trailer(cptr);
+}
+    
+
+
+void
+prot_a_async_broadcast(Connection *cptr,
+		       Pers_no	       pers_no,
+		       String	       message)
+{
+    async_header(cptr, 2, ay_broadcast);
+    mux_printf(cptr, " %lu", (u_long)pers_no);
+    prot_a_output_string(cptr, message);
+    async_trailer(cptr);
+}
+    
+void
+prot_a_async_rejected_connection(Connection *cptr)
+{
+    async_header(cptr, 0, ay_rejected_connection);
+    async_trailer(cptr);
+}
+    
+void
+prot_a_async_send_message(Connection *cptr,
+			  Pers_no     recipient,
+			  Pers_no     sender,
+			  String      message)
+{
+    async_header(cptr, 3, ay_send_message);
+    mux_printf(cptr, " %lu %lu", (u_long)recipient, (u_long)sender);
+    prot_a_output_string(cptr, message);
+    async_trailer(cptr);
+}
diff --git a/src/server/prot-a-send-async.h b/src/server/prot-a-send-async.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ad5a91a1903a122c1acc7d486c244dde188de4f
--- /dev/null
+++ b/src/server/prot-a-send-async.h
@@ -0,0 +1,52 @@
+/*
+ * Asynchronous messages in protocol A.
+ */
+
+void
+prot_a_async_new_text(Connection *cptr,
+		      Text_no	      text_no,
+		      Text_stat      *text_s);
+
+void
+prot_a_async_i_am_on(Connection *cptr,
+		     Who_info info);
+void
+prot_a_async_i_am_off(Connection *cptr,
+		      Pers_no pers_no);
+void
+prot_a_async_logout(Connection *cptr,
+		    Pers_no pers_no,
+		    Session_no session_no);
+
+void
+prot_a_async_new_name(Connection *cptr,
+		      Conf_no conf_no,
+		      String old_name,
+		      String new_name);
+
+void
+prot_a_async_sync_db(Connection *cptr);
+
+void
+prot_a_async_forced_leave_conf(Connection *cptr,
+			       Conf_no	       conf_no);
+
+void
+prot_a_async_login(Connection *cptr,
+		   Pers_no	   pers_no,
+		   int		   session_no);
+
+
+void
+prot_a_async_broadcast(Connection *cptr,
+		       Pers_no	     pers_no,
+		       String	     message);
+
+void
+prot_a_async_rejected_connection(Connection *cptr);
+
+void
+prot_a_async_send_message(Connection *cptr,
+			  Pers_no     recipient,
+			  Pers_no     sender,
+			  String      message);
diff --git a/src/server/prot-a.c b/src/server/prot-a.c
new file mode 100644
index 0000000000000000000000000000000000000000..c0ad2848dda58952ebaf85b3f3ee24b093551a31
--- /dev/null
+++ b/src/server/prot-a.c
@@ -0,0 +1,317 @@
+/*
+ * Protocol A.
+ */
+
+#include <stdio.h>
+#include <setjmp.h>
+
+#include <kom-errno.h>
+#include <kom-types.h>
+#include "smalloc.h"
+#include "lyskomd.h"
+#include "com.h"
+#include "connections.h"
+#include "isc-parse.h"
+#include "prot-a.h"
+#include "prot-a-output.h"
+#include "prot-a-parse.h"
+#include <debug.h>
+#include "isc.h"
+#include "mux.h"
+
+BUGDECL;
+
+void
+prot_a_reply(Connection *client,
+	     Bool status,
+	     Result_holder *res)
+{
+    /*
+     * The function is called. Now return the answer.
+     */
+
+    if ( status == OK )
+    {
+	mux_printf(client, "=%d", client->ref_no);
+
+	switch ( fnc_defs[ client->function ].result )
+	{
+	case number:
+	    mux_printf(client, " %ld", (u_long)res->number);
+	    BUG(("=%d\n", res->number));
+	    break;
+
+	case success:
+	    BUG(("=Success\n"));
+	    break;
+	    
+	case person:
+	    prot_a_output_person(client, &res->person);
+	    BUG(("={Person struct not listed}\n"));
+	    break;
+
+	case membership:
+	    prot_a_output_membership(client, &res->membership);
+	    BUG(("={Membership struct not listed}\n"));
+	    break;
+
+	case conf_list:
+	    prot_a_output_conf_list(client, res->conf_list);
+	    BUG(("={Conf_list not listed}\n"));
+	    break;
+
+	case conf_no_list:
+	    prot_a_output_conf_no_list(client, res->conf_no_list);
+	    BUG(("={Conf_no_list not listed}\n"));
+	    break;
+	    	    
+	case conference:
+	    prot_a_output_conference(client, &res->conference);
+	    BUG(("={Conference struct not listed}\n"));
+	    break;
+
+	case string:
+	    prot_a_output_string(client, res->string);
+	    BUG(("={%dH", res->string.len));
+	    BUGSTR(res->string);
+	    BUG(("}\n"));
+	    break;
+	    
+	case mark_list:
+	    prot_a_output_mark_list(client, res->mark_list);
+	    BUG(("={Mark_list not listed}\n"));
+	    break;
+	    
+	case text_stat:
+	    prot_a_output_text_stat(client, &res->text_stat);
+	    BUG(("={Text_stat struct not listed}\n"));
+	    break;
+	    
+	case text_list:
+	    prot_a_output_text_list(client, res->text_list);
+	    BUG(("={Text_list not listed}\n"));
+	    break;
+
+	case who_info_list:
+	    prot_a_output_who_info_list(client, res->who_info_list);
+	    BUG(("={Who_info_list not listed}\n"));
+	    break;
+
+	case who_info_list_old:
+	    prot_a_output_who_info_list_old(client,
+					    res->who_info_list_old);
+	    BUG(("={Old who_info_list not listed}\n"));
+	    break;
+
+	case session_info:
+	    prot_a_output_session_info(client,
+				       &res->session_info);
+	    BUG(("={Session_info not listed}\n"));
+	    break;
+    
+	case info:
+	    prot_a_output_info(client, &res->info);
+	    BUG(("={Who_info struct not listed}\n"));
+	    break;
+
+	case membership_list:
+	    prot_a_output_membership_list (client, res->membership_list);
+	    BUG (("={Membership_list not listed}\n"));
+	    break;
+	    
+	case member_list:
+	    prot_a_output_member_list (client, res->member_list);
+	    BUG(("={Member_list not listed}\n"));
+	    break;
+
+	case time_date:
+	    prot_a_output_time (client, res->time_date);
+	    BUG(("=(time_t)%d\n", res->time_date));
+	    break;
+
+	case session_no:
+	    prot_a_output_session_no (client, res->session_no);
+	    BUG(("=(Session_no)%d\n", res->session_no));
+	    break;
+	}
+	mux_putc('\n', client);
+    }
+    else
+    {
+	/* Failure. Give a reply with the error message. */
+	mux_printf(client, "%%%d %d %d\n",
+		client->ref_no, kom_errno, err_stat);
+	BUG(("%%Err %d\n", kom_errno));
+    }
+
+    mux_flush(client);
+}
+
+/*
+ * Check if it is a legal function.
+ *
+ * BUG: This should be generated from fncdef.txt.
+ */
+
+
+/*
+ * Set up all data structures that are private to protocol A. This
+ * function is called from connections.c whenever a connection says
+ * that it is of type A.
+ */
+void
+prot_a_init(Connection *conn)
+{
+    conn->parse_pos = 0;
+    conn->fnc_parse_pos = 0;
+    conn->array_parse_pos = 0;
+    conn->struct_parse_pos = 0;
+    conn->string_parse_pos = 0;
+    conn->ref_no = 0;
+    conn->function = 0;
+    conn->num0 = 0;
+    conn->num1 = 0;
+    conn->num2 = 0;
+    conn->num3 = 0;
+    conn->c_string0 = EMPTY_STRING;
+    conn->c_string1 = EMPTY_STRING;
+    conn->string0 = EMPTY_STRING;
+    conn->c_misc_info_p = NULL;
+    conn->c_local_text_no_p = NULL;
+    conn->priv_bits = DEFAULT_PRIV_BITS;
+    conn->conf_type = NULL_CONF_TYPE;
+}
+
+void
+prot_a_destruct(Connection *conn)
+{
+
+    /*
+     * All strings et c should already by free:d - that is done at the
+     * end of each atomic call. But free them anyhow, just in case
+     * this client is forced of.
+     */
+
+    s_clear(&conn->string0);
+    s_clear(&conn->c_string0);
+    s_clear(&conn->c_string1);
+    sfree(conn->c_misc_info_p);
+    sfree(conn->c_local_text_no_p);
+}
+
+
+static Bool
+prot_a_is_legal_fnc(Call_header fnc)
+{
+    switch(fnc)
+    {
+    case call_fnc_login: 
+    case call_fnc_logout: 
+    case call_fnc_pepsi: 
+    case call_fnc_change_name: 
+    case call_fnc_change_what_i_am_doing: 
+    case call_fnc_create_person: 
+    case call_fnc_get_person_stat_old:
+    case call_fnc_set_priv_bits:
+    case call_fnc_set_passwd: 
+    case call_fnc_query_read_texts: 
+    case call_fnc_create_conf: 
+    case call_fnc_delete_conf: 
+    case call_fnc_lookup_name: 
+    case call_fnc_get_conf_stat_old: 
+    case call_fnc_add_member: 
+    case call_fnc_sub_member: 
+    case call_fnc_set_presentation: 
+    case call_fnc_set_etc_motd: 
+    case call_fnc_set_supervisor: 
+    case call_fnc_set_permitted_submitters: 
+    case call_fnc_set_super_conf: 
+    case call_fnc_set_conf_type: 
+    case call_fnc_set_garb_nice: 
+    case call_fnc_get_marks: 
+    case call_fnc_mark_text: 
+    case call_fnc_get_text: 
+    case call_fnc_get_text_stat: 
+    case call_fnc_mark_as_read: 
+    case call_fnc_create_text: 
+    case call_fnc_delete_text: 
+    case call_fnc_add_recipient: 
+    case call_fnc_sub_recipient: 
+    case call_fnc_add_comment: 
+    case call_fnc_sub_comment: 
+    case call_fnc_get_map: 
+    case call_fnc_get_time: 
+    case call_fnc_get_info:
+    case call_fnc_add_footnote:
+    case call_fnc_sub_footnote:
+    case call_fnc_who_is_on_old:
+    case call_fnc_set_unread:
+    case call_fnc_set_motd_of_lyskom:
+    case call_fnc_enable:
+    case call_fnc_sync:
+    case call_fnc_shutdown:
+    case call_fnc_broadcast:
+    case call_fnc_get_membership:
+    case call_fnc_get_created_texts:
+    case call_fnc_get_members:
+    case call_fnc_get_person_stat:
+    case call_fnc_get_conf_stat:
+    case call_fnc_who_is_on:
+    case call_fnc_get_unread_confs:
+    case call_fnc_send_message:
+    case call_fnc_get_session_info:
+    case call_fnc_disconnect:
+    case call_fnc_who_am_i:
+	return TRUE;
+
+    default:
+	return FALSE;
+    }
+}    
+
+
+void
+prot_a_parse_packet(Connection *client)
+{
+    if ( s_empty(client->username) )
+    {
+	/* Connection not established yet */
+
+	prot_a_parse_string(client, &client->c_string0, USERNAME_LEN);
+
+	client->username = client->c_string0; /* Kludge to deal with */
+	client->c_string0 = EMPTY_STRING;     /* "A5B" as first input. */
+				/* Protokoll B will not suffer from this... */
+	
+	if ( s_strcat(&client->username, s_fcrea_str("@")) != OK )
+	    restart_kom("prot_a_parse_packet: s_strcat\n");
+
+	if ( s_strcat(&client->username, client->hostname) != OK )
+	    restart_kom("prot_a_parse_packet: s_strcat II\n");
+
+	mux_printf(client, "LysKOM\n");
+	mux_flush(client);
+	BUG(("[Client %d is logged on]\n", client->session_no));
+    }	
+
+    switch(client->parse_pos)
+    {
+    case 0:			/* Get ref_no */
+	client->ref_no = prot_a_parse_long(client);
+	client->parse_pos = 1;
+	/* Fall through */
+    case 1:			/* Get fnc_no */
+	client->function = prot_a_parse_long(client);
+	if ( !prot_a_is_legal_fnc(client->function) )
+	    longjmp(parse_env, ISC_PROTOCOL_ERR);
+	client->parse_pos = 2;
+	/* Fall through */
+    case 2:
+	/* Call the function that parses the arguments for this call. */
+	fnc_defs[client->function].parser(client);
+	/* Fall through */
+    default:
+	client->parse_pos = 0;
+    }
+
+}
diff --git a/src/server/prot-a.h b/src/server/prot-a.h
new file mode 100644
index 0000000000000000000000000000000000000000..d9b3dd5d709df632f61a783eca9cba5c38e438a3
--- /dev/null
+++ b/src/server/prot-a.h
@@ -0,0 +1,14 @@
+
+void
+prot_a_init(Connection *conn);
+
+void
+prot_a_destruct(Connection *conn);
+
+void
+prot_a_reply(Connection *client,
+	     Bool status,
+	     Result_holder *result);
+
+void
+prot_a_parse_packet(Connection *client);
diff --git a/src/server/ram-output.c b/src/server/ram-output.c
new file mode 100644
index 0000000000000000000000000000000000000000..bc409fca6d31b01af02b24e5aa4c1a4c6dc4aa56
--- /dev/null
+++ b/src/server/ram-output.c
@@ -0,0 +1,383 @@
+/*
+ * ram-output.c  -  write objects to disk.
+ *
+ * This is a hack. It shouldn't be used except for debugging and as a
+ * temporary substitute for what Willf|r is (or should:-) be doing.
+ *
+ * Written by ceder 1990-07-13. Rewritten 1990-08-31.
+ * Some functions rewritten for speed by Inge Wallin.
+ * (It worked - now saving is twice as fast.)
+ */
+
+#include <kom-types.h>
+#include <stdio.h>
+#include <sys/file.h>
+#include <time.h>
+#include "lyskomd.h"
+#include "ram-output.h"
+#include "com.h"
+#include "isc.h"
+#include "connections.h"
+#include "prot-a-output.h"
+#include "log.h"
+
+void
+foutput_person (FILE *fp,
+		Person *person)
+{
+    foutput_string (fp, person->username);
+    foutput_priv_bits (fp, person->privileges);
+    foutput_personal_flags (fp, person->flags);
+
+    foutput_text_list (fp, person->created_texts);
+    foutput_mark_list (fp, person->marks);
+    foutput_membership_list (fp, person->conferences);
+
+    foutput_time(fp, person->last_login);
+
+    fprintf (fp, " %lu %lu %lu %lu %lu %lu %lu %lu %lu",
+	     (u_long) person -> user_area,
+	     (u_long) person -> total_time_present, /* This is not a time,
+						     * but a number of seconds.
+						     */
+	     (u_long) person -> sessions,
+	     (u_long) person -> created_lines,
+	     (u_long) person -> created_bytes,
+	     (u_long) person -> read_texts,
+	     (u_long) person -> no_of_text_fetches,
+	     (u_long) person -> created_persons,
+	     (u_long) person -> created_confs);
+}
+
+
+
+void
+foutput_conference (FILE *fp,
+		    Conference *conf_c)
+{
+    foutput_string(fp, conf_c->name);
+    foutput_member_list(fp, conf_c->members);
+    foutput_text_list(fp, conf_c->texts);
+    foutput_conf_type(fp, conf_c->type);
+
+    foutput_time(fp, conf_c -> creation_time );
+    foutput_time(fp, conf_c -> last_written );
+
+    fprintf (fp, " %lu %lu %lu %lu %lu %lu %lu",
+	     (u_long) conf_c -> creator,
+	     (u_long) conf_c -> presentation,
+	     (u_long) conf_c -> supervisor,
+	     (u_long) conf_c -> permitted_submitters,
+	     (u_long) conf_c -> super_conf,
+	     (u_long) conf_c -> msg_of_day,
+	     (u_long) conf_c -> nice);
+}
+
+
+void
+foutput_text_stat(FILE *fp,
+		  Text_stat *t_stat)
+{
+    int i;
+
+    foutput_time(fp, t_stat->creation_time);
+    
+    foutput_ulong((u_long) t_stat->author, fp);
+    foutput_ulong((u_long) t_stat->file_pos, fp);
+    foutput_ulong((u_long) t_stat->no_of_lines, fp);
+    foutput_ulong((u_long) t_stat->no_of_chars, fp);
+    foutput_ulong((u_long) t_stat->no_of_marks, fp);
+    foutput_ulong((u_long) t_stat->no_of_misc, fp);
+
+    if ( t_stat->misc_items != NULL && t_stat->no_of_misc > 0 )
+    {
+	fputs(" {", fp);
+	for ( i = 0; i < t_stat->no_of_misc; i++ )
+	    foutput_misc_info(fp, t_stat->misc_items[ i ]);
+	fputs(" }", fp);
+    }
+    else
+	fputs(" *", fp);
+}	
+
+
+extern void
+foutput_string(FILE *fp,
+	       String str)
+{
+    foutput_ulong((u_long)str.len, fp);
+    putc('H', fp);
+
+    if (str.len)
+	fwrite(str.string, str.len, 1, fp);
+}
+
+extern void
+foutput_priv_bits(FILE *fp,
+		  Priv_bits bits)
+{
+    putc(' ', fp);
+    putc(bits.wheel + '0', fp);
+    putc(bits.admin + '0', fp);
+    putc(bits.statistic + '0', fp);
+    putc(bits.create_pers + '0', fp);
+    putc(bits.create_conf + '0', fp);
+    putc(bits.change_name + '0', fp);
+    putc(bits.flg7 + '0', fp);
+    putc(bits.flg8 + '0', fp);
+    putc(bits.flg9 + '0', fp);
+    putc(bits.flg10 + '0', fp);
+    putc(bits.flg11 + '0', fp);
+    putc(bits.flg12 + '0', fp);
+    putc(bits.flg13 + '0', fp);
+    putc(bits.flg14 + '0', fp);
+    putc(bits.flg15 + '0', fp);
+    putc(bits.flg16 + '0', fp);
+}
+
+extern void
+foutput_personal_flags(FILE *fp,
+		       Personal_flags flags)
+{
+    putc(' ', fp);
+    putc(flags.unread_is_secret + '0', fp);
+    putc(flags.flg2 + '0', fp);
+    putc(flags.flg3 + '0', fp);
+    putc(flags.flg4 + '0', fp);
+    putc(flags.flg5 + '0', fp);
+    putc(flags.flg6 + '0', fp);
+    putc(flags.flg7 + '0', fp);
+    putc(flags.flg8 + '0', fp);
+}	
+
+
+void
+foutput_text_list(FILE *fp,
+		  Text_list text_list)
+{
+    int i;
+
+    foutput_ulong((u_long)text_list.first_local_no, fp);
+    foutput_ulong((u_long)text_list.no_of_texts, fp);
+
+    if ( text_list.texts != NULL && text_list.no_of_texts > 0 )
+    {
+	fputs(" {", fp);
+	for ( i = 0; i < text_list.no_of_texts; i++ )
+	    foutput_ulong((u_long)text_list.texts[ i ], fp);
+	fputs(" }", fp);
+    }
+    else
+	fprintf(fp, " *");
+}
+
+
+void
+foutput_mark_list(FILE *fp,
+		  Mark_list mark_list)
+{
+    int i;
+
+    fprintf(fp, " %lu", (u_long)mark_list.no_of_marks);
+
+    if ( mark_list.marks != NULL && mark_list.no_of_marks > 0 )
+    {
+	fprintf(fp, " {");
+	for ( i = 0; i < mark_list.no_of_marks; i++ )
+	    foutput_mark(fp, mark_list.marks[ i ]);
+	fprintf(fp, " }");
+    }
+    else
+	fprintf(fp, " *");
+    
+}
+
+
+extern void
+foutput_mark(FILE *fp,
+	     Mark mark)
+{
+    fprintf(fp, " %lu %lu", (u_long)mark.text_no, (u_long)mark.mark_type);
+}
+
+void
+foutput_membership_list (FILE		* fp,
+			 Membership_list  mlist)
+{
+    int i;
+    
+    fprintf(fp, " %lu", (u_long)mlist.no_of_confs);
+
+    if ( mlist.confs != NULL && mlist.no_of_confs > 0 )
+    {
+	fprintf(fp, " {");
+	for ( i = 0; i < mlist.no_of_confs; i++)
+	    foutput_membership(fp, mlist.confs + i);
+	fprintf(fp, " }");
+    }
+    else
+	fprintf(fp, " *");
+}
+
+
+
+void
+foutput_membership(FILE *fp,
+		   Membership *mship)
+{
+    int i;
+    
+    foutput_time(fp, mship->last_time_read );
+    
+    if ( mship->read_texts == NULL && mship->no_of_read != 0 )
+    {
+	log("foutput_membership(): no_of_read forced to 0"
+	    " in someones membership in %lu.\n",
+	    mship->conf_no);
+	mship->no_of_read = 0;
+    }
+    
+    fprintf(fp, " %lu %lu %lu %lu",
+	    (u_long)mship->conf_no,
+	    (u_long)mship->priority,
+	    (u_long)mship->last_text_read,
+	    (u_long)mship->no_of_read);
+    
+    if ( mship->read_texts != NULL && mship->no_of_read > 0)
+    {
+	fprintf(fp, " {");
+	for ( i = 0; i < mship->no_of_read; i++)
+	    fprintf(fp, " %lu", (u_long)mship->read_texts[ i ]);
+	
+	fprintf(fp, " }");
+    }
+    else
+	fprintf(fp, " *");
+}
+
+void
+foutput_time(FILE *fp,
+	     time_t clock)
+{
+    foutput_ulong((u_long) clock, fp);
+}
+
+
+extern void
+foutput_member_list(FILE *fp,
+		    Member_list m_list)
+{
+    int i;
+
+    fprintf(fp, " %lu", (u_long)m_list.no_of_members);
+    if ( m_list.members != NULL && m_list.no_of_members > 0 )
+    {
+	fprintf(fp, " {");
+	for ( i = 0; i < m_list.no_of_members; i++ )
+	    foutput_member(fp, m_list.members[ i ]);
+	fprintf(fp, " }");
+    }
+    else
+	fprintf(fp, " *");
+}
+
+
+void
+foutput_member(FILE *fp,
+	       Member member)
+{
+    fprintf(fp, " %lu", (u_long)member.member);
+}
+
+extern void
+foutput_conf_type(FILE *fp,
+		  Conf_type type)
+{
+    putc(' ', fp);
+    putc(type.rd_prot + '0', fp);
+    putc(type.original + '0', fp);
+    putc(type.secret + '0', fp);
+    putc(type.letter_box + '0', fp);
+}
+
+
+extern void
+foutput_misc_info(FILE *fp, 
+		  Misc_info misc)
+{
+    foutput_ulong((u_long)misc.type, fp);
+    
+    switch(misc.type)
+    {
+    case recpt:
+	foutput_ulong((u_long)misc.datum.recipient, fp);
+	break;
+	
+    case cc_recpt:
+	foutput_ulong((u_long)misc.datum.cc_recipient, fp);
+	break;
+	
+    case loc_no:
+	foutput_ulong((u_long)misc.datum.local_no, fp);
+	break;
+	
+    case rec_time:
+	foutput_time(fp, misc.datum.received_at);
+	break;
+	
+    case comm_to:
+	foutput_ulong((u_long)misc.datum.comment_to, fp);
+	break;
+	
+    case comm_in:
+	foutput_ulong((u_long)misc.datum.commented_in, fp);
+	break;
+	
+    case footn_to:
+	foutput_ulong((u_long)misc.datum.footnote_to, fp);
+	break;
+	
+    case footn_in:
+	foutput_ulong((u_long)misc.datum.footnoted_in, fp);
+	break;
+	
+    case sent_by:
+	foutput_ulong((u_long)misc.datum.sender, fp);
+	break;
+	
+    case sent_at:
+	foutput_time(fp, misc.datum.sent_at);
+	break;
+
+#ifndef COMPILE_CHECKS
+    default:
+	restart_kom("prot_a_output_misc_info: Illegal misc\n");
+#endif
+    }
+}
+
+
+/* 
+ * Output the unsigned long L in the fastest way possible to the file
+ * FP. Ok, it's ugly, but it's fast (or is it?).
+ */
+
+void
+foutput_ulong (u_long l,
+	       FILE *fp)
+{
+    static char   buf[20];
+    char         *cp;
+
+    putc(' ', fp);
+    if (l < 10)
+	putc("0123456789"[l], fp);
+    else {
+	cp = buf + 19;
+	while (l > 0) {
+	    *cp-- = (l % 10) + '0';
+	    l /= 10;
+	}
+	fwrite(cp + 1, buf + 19 - cp, 1, fp);
+    }
+}
diff --git a/src/server/ram-output.h b/src/server/ram-output.h
new file mode 100644
index 0000000000000000000000000000000000000000..994f104243b85204a21891779e0b890b874c2e38
--- /dev/null
+++ b/src/server/ram-output.h
@@ -0,0 +1,72 @@
+/*
+ * ram-output.c  -  write objects to disk.
+ *
+ * This is a hack. It shouldn't be used except for debugging and as a
+ * temporary substitute for what Willf|r is (or should:-) be doing.
+ *
+ * Written by ceder 1990-07-13. Rewritten 1990-08-31.
+ */
+
+void
+foutput_person (FILE *fp,
+		Person *person);
+
+
+void
+foutput_conference (FILE *fp,
+		    Conference *conf_c);
+
+void
+foutput_text_stat(FILE *fp,
+		  Text_stat *t_stat);
+
+extern void
+foutput_string(FILE *fp,
+		     String str);
+extern void
+foutput_priv_bits(FILE *fp,
+		  Priv_bits bits);
+extern void
+foutput_personal_flags(FILE *fp,
+		       Personal_flags flags);
+
+void
+foutput_text_list(FILE *fp,
+		  Text_list text_list);
+
+void
+foutput_mark_list(FILE *fp,
+		  Mark_list mark_list);
+
+extern void
+foutput_mark(FILE *fp,
+	     Mark mark);
+void
+foutput_membership_list (FILE		* fp,
+			 Membership_list  mlist);
+
+void
+foutput_membership(FILE *fp,
+		   Membership *mship);
+void
+foutput_time(FILE *fp,
+	     time_t clock);
+
+extern void
+foutput_member_list(FILE *fp,
+		    Member_list m_list);
+
+void
+foutput_member(FILE *fp,
+	       Member member);
+extern void
+foutput_conf_type(FILE *fp,
+		  Conf_type type);
+
+extern void
+foutput_misc_info(FILE *fp, 
+		  Misc_info misc);
+
+extern void
+foutput_ulong (u_long l,
+	       FILE *fp);
diff --git a/src/server/ram-parse.c b/src/server/ram-parse.c
new file mode 100644
index 0000000000000000000000000000000000000000..f1e47b29a0ae3b65b99d29ce909e7a2ed69e193c
--- /dev/null
+++ b/src/server/ram-parse.c
@@ -0,0 +1,1173 @@
+#define DISKERR			/* Some corrections
+				   that are needed by diskomd 0.29.
+				   */
+/*
+ * ram-parse.c -- parse objects from disk file.
+ */
+
+/*
+ * BUGS: Not all functions are needed.
+ *	 The method for checking errors in fparse_long is ugly.
+ *	 Errors in fparse_long are not always checked for.
+ */
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "lyskomd.h"
+#include <kom-types.h>
+#include <kom-errno.h>
+#include <services.h>
+#include "ram-parse.h"
+#include "log.h"
+#include "smalloc.h"
+#include "com.h"
+#include "connections.h"
+
+#define REALLOC(ptr, size)  srealloc(ptr, size)
+
+static int fparse_long_errors = 0;
+
+void
+fskipwhite(FILE *fp)
+{
+    int c;
+
+    while ( (c = getc(fp)) != EOF && isascii(c) && isspace(c) )
+	;
+
+    ungetc(c, fp);
+}
+
+extern u_long
+fparse_long(FILE *fp)
+{
+    u_long res = 0;
+    int foo = 0;
+    int c;
+
+    fskipwhite(fp);
+    while ( (c = getc(fp)) != EOF && isascii(c) && isdigit(c))
+    {
+	foo = 1;
+	res = 10 * res + c - '0';
+    }
+
+    if ( foo == 0 )
+    {
+	log("fparse_long() failed at pos %d.\n", ftell(fp));
+	++fparse_long_errors;
+    }
+    
+    ungetc(c, fp);
+
+    return res;
+}
+
+extern time_t
+fparse_time(FILE *fp)
+{
+    return fparse_long(fp);
+}
+
+    
+
+extern Success
+fparse_conference(FILE *fp,
+		 Conference *result)
+{
+    if ( fparse_long_errors != 0 )
+    {
+	log("fparse_conference(): fparse_long_errors == %d on entry. Reset.\n",
+	    fparse_long_errors);
+	fparse_long_errors = 0;
+    }
+
+    if ( fparse_string(fp, &result->name) != OK )
+    {
+	log("fparse_conference(): Can't parse name.\n");
+	return FAILURE;
+    }
+
+    if ( fparse_member_list(fp, &result->members) != OK
+	|| fparse_text_list(fp, &result->texts) != OK
+	|| fparse_conf_type(fp, &result->type) != OK )
+    {
+	log("fparse_conference: file is corrupt.\n");
+	return FAILURE;
+    }
+    
+    result->creation_time = fparse_time(fp);
+    result->last_written = fparse_time(fp);
+
+    result->creator = fparse_long(fp);
+    result->presentation = fparse_long(fp);
+    result->supervisor = fparse_long(fp);
+    result->permitted_submitters = fparse_long(fp);
+    result->super_conf = fparse_long(fp);
+    result->msg_of_day = fparse_long(fp);
+
+    if ( fparse_long_errors != 0 )
+    {
+	log("fparse_conference(): %d fparse_long_errors before 'nice'. "
+	    "Reset.\n",
+	    fparse_long_errors);
+	fparse_long_errors = 0;
+	return FAILURE;
+    }
+
+    result->nice = fparse_long(fp);
+
+#ifdef DISKERR
+    if ( fparse_long_errors != 0 )
+    {
+	log("fparse_conference(): Error parsing 'nice' at pos %d. "
+	    "nice set to 77 and error ignored.\n", ftell(fp));
+	fparse_long_errors = 0;
+	result->nice = 77;
+    }
+
+    if ( result->nice < 10 )
+    {
+	char *name;
+	int new_nice;
+
+	name = s_crea_c_str(result->name);
+	new_nice = 10 * result->nice + 7;
+
+	if ( name == NULL )
+	{
+	    log("fparse_conference(): nice in (null) was %d, set to %d.\n",
+		result->nice, new_nice);
+	}
+	else
+	{
+	    log("fparse_conference(): nice in %s was %d, set to %d.\n",
+		name, result->nice, new_nice);
+	    sfree(name);
+	}
+
+	result->nice = new_nice;
+    }
+#endif
+
+    fskipwhite(fp);
+
+    return OK;
+}
+
+
+Success
+fparse_person(FILE *fp,
+	      Person *person)
+{
+    String pwd = EMPTY_STRING;
+
+    if ( fparse_long_errors != 0 )
+    {
+	log("fparse_person(): fparse_long_errors == %d on entry. Reset.\n",
+	    fparse_long_errors);
+	fparse_long_errors = 0;
+    }
+
+    if ( fparse_string(fp, &pwd) != OK )
+    {
+	log("fparse_person(): Failed to parse password.\n");
+	return FAILURE;
+    }
+
+    memcpy(person->pwd, pwd.string, PASSWD_LEN);
+    s_clear(&pwd);
+    	
+    if ( fparse_string(fp, &person->username) != OK
+	|| fparse_priv_bits(fp, &person->privileges) != OK
+	|| fparse_personal_flags(fp, &person->flags) != OK
+	|| fparse_text_list(fp, &person->created_texts) != OK
+	|| fparse_mark_list(fp, &person->marks) != OK 
+	|| fparse_membership_list(fp, &person->conferences) != OK )
+    {
+	log("fparse_person(): parse error.\n");
+	return FAILURE;
+    }
+
+    person->last_login = fparse_time(fp);
+
+    person->user_area = fparse_long(fp);
+    person->total_time_present = fparse_long(fp);
+    person->sessions = fparse_long(fp);
+    person->created_lines = fparse_long(fp);
+    person->created_bytes = fparse_long(fp);
+    person->read_texts = fparse_long(fp);
+    person->no_of_text_fetches = fparse_long(fp);
+    person->created_persons = fparse_long(fp);
+
+    if ( fparse_long_errors != 0 )
+    {
+	log("fparse_person(): %d fparse_long_errors before 'created_"
+	    "confs'. Reset.\n",
+	    fparse_long_errors);
+	fparse_long_errors = 0;
+	return FAILURE;
+    }
+
+    person->created_confs = fparse_long(fp);
+
+#ifdef DISKERR
+    if ( fparse_long_errors != 0 )
+    {
+	log("fparse_person(): Error parsing 'created_confs' at pos %d. "
+	    "created_confs set to 0 and error ignored.\n", ftell(fp));
+	fparse_long_errors = 0;
+	person->created_confs = 0;
+    }
+#endif
+
+    fskipwhite(fp);
+
+    return OK;
+}
+
+Success
+fparse_membership(FILE *fp,
+		 Membership *mship)
+{
+    int i;
+    
+    mship->last_time_read = fparse_time(fp);
+
+    mship->conf_no = fparse_long(fp);
+    mship->priority = fparse_long(fp);
+    mship->last_text_read = fparse_long(fp);
+    mship->no_of_read = fparse_long(fp);
+
+    if ( mship->no_of_read > 0)
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '{':
+	    mship->read_texts
+		= REALLOC(mship->read_texts, (mship->no_of_read
+					      * sizeof(Local_text_no)));
+	    if ( mship->read_texts == NULL )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+
+	    for ( i = 0; i < mship->no_of_read; i++)
+		mship->read_texts[ i ] = fparse_long(fp);
+
+	    fskipwhite(fp);
+	    if ( getc(fp) != '}' )
+	    {
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		log("fparse_membership(): expected '}' at pos %d.\n",
+		    ftell(fp));
+		return FAILURE;
+	    }
+	    break;
+	    
+	case '*':
+	    if ( mship->read_texts != NULL )
+	    {
+		sfree(mship->read_texts);
+		mship->read_texts = NULL;
+	    }
+	    log("fparse_membership(): empty read_texts "
+		"with %lu elements (corrected)\n",
+		mship->no_of_read);
+	    mship->no_of_read = 0;
+	    break;
+	default:
+	    log("fparse_membership(): expected '*' or '{' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( getc(fp) != '*' ) 
+	{
+	    log("fparse_membership(): expected '*' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+
+	if ( mship->read_texts != NULL )
+	{
+	    sfree(mship->read_texts);
+	    mship->read_texts = NULL;
+	}
+    }
+
+    return OK;
+}
+
+	
+extern Success
+fparse_membership_list(FILE *fp,
+		       Membership_list *result)
+{
+    int i;
+    
+    /* First free all the read_texts. */
+
+    if ( result->confs != NULL )
+    {
+	for ( i = 0; i < result->no_of_confs; i++)
+	    sfree(result->confs[ i ].read_texts);
+    }
+    
+    result->no_of_confs = fparse_long(fp);
+
+    if ( result->no_of_confs > 0 )
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '{':
+	    result->confs = REALLOC(result->confs,
+					  (result->no_of_confs
+					   * sizeof(Membership)));
+	    if ( result->confs == NULL && result->no_of_confs > 0 )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+
+	    for ( i = 0; i < result->no_of_confs; i++)
+	    {
+		result->confs[ i ] = EMPTY_MEMBERSHIP;
+		if ( fparse_membership(fp, &result->confs[i]) != OK )
+		    return FAILURE;
+	    }
+	    
+	    fskipwhite(fp);
+	    if ( getc(fp) != '}' )
+	    {
+		log("fparse_membership_list(): expected '}' at pos %d.\n",
+		    ftell(fp));
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		return FAILURE;
+	    }
+	    
+	    break;
+	case '*':
+	    if ( result->confs != NULL )
+	    {
+		sfree(result->confs);
+		result->confs = NULL;
+	    }
+	    log("fparse_membership_list(): empty list with %lu "
+		"elements (corrected).\n",
+		(u_long)result->no_of_confs);
+	    
+	    result->no_of_confs = 0;
+	    break;
+	default:
+	    log("fparse_membership_list(): expected '*' or '{' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( getc(fp) != '*' ) 
+	{
+	    log("fparse_membership_list(): expected '*' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+	if ( result->confs != NULL )
+	{
+	    sfree(result->confs);
+	    result->confs = NULL;
+	}
+    }
+    return OK;
+}
+
+
+extern Success
+fparse_conf_list(FILE *fp,
+		Conf_list_old *result)
+{
+    int i;
+
+    result->no_of_conf_nos = fparse_long(fp);
+    if ( result->no_of_conf_nos > 0 )
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '{':
+	    result->conf_nos = REALLOC(result->conf_nos,
+				       (result->no_of_conf_nos
+					* sizeof(Conf_no)));
+	    if ( result->conf_nos == NULL )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+	    
+	    for ( i = 0; i < result->no_of_conf_nos; i++)
+		result->conf_nos[ i ] = fparse_long(fp);
+	    
+	    fskipwhite(fp);
+	    if ( getc(fp) != '}' )
+	    {
+		log("fparse_conf_list(): expected '}' at pos %d.\n",
+		    ftell(fp));
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		return FAILURE;
+	    }
+	    
+	    break;
+	case '*':
+	    if ( result->conf_nos != NULL )
+	    {
+		sfree(result->conf_nos);
+		result->conf_nos = NULL;
+	    }
+	    break;
+	default:
+	    log("fparse_conf_list(): expected '*' or '{' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( getc(fp) != '*' ) 
+	{
+	    log("fparse_conf_list(): expected '*' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+	if ( result->conf_nos != NULL )
+	{
+	    sfree(result->conf_nos);
+	    result->conf_nos = NULL;
+	}
+    }
+
+    if ( result->no_of_conf_nos > 0 )
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '{':
+	    result->type_of_conf = REALLOC(result->type_of_conf,
+					   (result->no_of_conf_nos
+					    * sizeof(Conf_type)));
+
+	    if ( result->type_of_conf == NULL )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+	    
+	    for ( i = 0; i < result->no_of_conf_nos; i++)
+		if ( fparse_conf_type(fp, &result->type_of_conf[i]) != OK )
+		{
+		    return FAILURE;
+		}
+	    
+	    fskipwhite(fp);
+	    if ( getc(fp) != '}' )
+	    {
+		log("fparse_conf_list(): expected '}' at pos %d.\n",
+		    ftell(fp));
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		return FAILURE;
+	    }
+	    
+	    break;
+	case '*':
+	    if ( result->type_of_conf != NULL )
+	    {
+		sfree(result->type_of_conf);
+		result->type_of_conf = NULL;
+	    }
+	    break;
+	default:
+	    log("fparse_conf_list(): expected '*' or '+' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( getc(fp) != '*' ) 
+	{
+	    log("fparse_conf_list(): expected '*' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+
+	if ( result->type_of_conf != NULL )
+	{
+	    sfree(result->type_of_conf);
+	    result->type_of_conf = NULL;
+	}
+    }
+    return OK;
+}
+
+
+extern Success
+fparse_mark_list(FILE *fp,
+		Mark_list *result)
+{
+    int i;
+
+    result->no_of_marks = fparse_long(fp);
+
+    if ( result->no_of_marks > 0 )
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '{':
+	    result->marks = REALLOC(result->marks,
+				    (result->no_of_marks
+				     * sizeof(Mark)));
+
+	    if ( result->marks == NULL )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+	    
+	    for ( i = 0; i < result->no_of_marks; i++)
+		if ( fparse_mark(fp, &result->marks[ i ] ) != OK )
+		{
+		    return FAILURE;
+		}
+	    
+	    fskipwhite(fp);
+	    if ( getc(fp) != '}' )
+	    {
+		log("fparse_mark_list(): expected '}' at pos %d.\n",
+		    ftell(fp));
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		return FAILURE;
+	    }
+	    
+	    break;
+
+	case '*':
+	    if ( result->marks != NULL )
+	    {
+		sfree(result->marks);
+		result->marks = NULL;
+	    }
+	    break;
+
+	default:
+	    log("fparse_mark_list(): expected '*' or '{' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( getc(fp) != '*' ) 
+	{
+	    log("fparse_mark_list(): expected '*' at pos %d.\n", ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+	if ( result->marks != NULL )
+	{
+	    sfree(result->marks);
+	    result->marks = NULL;
+	}
+    }
+    return OK;
+}
+
+
+extern Success
+fparse_text_stat(FILE *fp,
+		 Text_stat *result)
+{
+    int i;
+    int c;
+
+    if ( fparse_long_errors != 0 )
+    {
+	log("fparse_text_stat(): fparse_long_errors == %d on entry. Reset.\n",
+	    fparse_long_errors);
+	fparse_long_errors = 0;
+    }
+
+    result->creation_time = fparse_time(fp);
+
+    result->author = fparse_long(fp);
+    result->file_pos = fparse_long(fp);
+    result->no_of_lines = fparse_long(fp);
+    result->no_of_chars = fparse_long(fp);
+    result->no_of_marks = fparse_long(fp);
+    result->no_of_misc = fparse_long(fp);
+
+    if ( fparse_long_errors != 0 )
+    {
+	log("fparse_text_stat(): %d fparse_long_errors before 'misc_items'. "
+	    "Reset.\n",
+	    fparse_long_errors);
+	fparse_long_errors = 0;
+	return FAILURE;
+    }
+
+    if ( result->no_of_misc > 0 )
+    {
+	fskipwhite(fp);
+	switch( c = getc(fp) )
+	{
+	case '{':
+	    result->misc_items = REALLOC(result->misc_items,
+					 (result->no_of_misc
+					  * sizeof(Misc_info)));
+
+	    if ( result->misc_items == NULL )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+	    
+	    for ( i = 0; i < result->no_of_misc; i++)
+		if ( fparse_misc_info(fp, &result->misc_items[ i ]) != OK )
+		    return FAILURE;
+	    
+	    fskipwhite(fp);
+	    if ( (c = getc(fp)) != '}' )
+	    {
+#ifdef DISKERR
+		if ( c == '@' || c == '+' )
+		{
+		    ungetc(c, fp);
+		    log("fparse_text_stat(): got '%c' when expecting '}'.\n."
+			"Character ungetc'd and interpreted as a '}'."
+			" (pos %d).\n", c, ftell(fp));
+		}
+		else
+#endif
+		{
+		    log("fparse_text_stat(): expected '}' at pos %d.\n",
+			ftell(fp));
+		    kom_errno = KOM_SERVER_IS_CRAZY;
+		    return FAILURE;
+		}
+	    }
+
+	    break;
+
+	case '@':
+	case '+':
+	    ungetc(c, fp);
+	    log("fparse_text_stat(): got '%c' when expecting '{' or '*'\n."
+		"Character ungetc'd and interpreted as a '*'. (pos %d).\n",
+		c, ftell(fp));
+	    /* Fall through */
+	case '*':
+	    if ( result->misc_items != NULL )
+	    {
+		sfree(result->misc_items);
+		result->misc_items = NULL;
+	    }
+	    break;
+
+	default:
+	    log("fparse_text_stat(): expected '*' or '}' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( (c = getc(fp)) != '*' ) 
+	{
+#ifdef DISKERR
+	    if ( c == '@' || c == '+' )
+	    {
+		ungetc(c, fp);
+		log("fparse_text_stat(): got '%c' when expecting '*'.\n."
+		    "Character ungetc'd and interpreted as a '*'."
+		    " (pos %d).\n", c, ftell(fp));
+	    }
+	    else
+#endif
+	    {
+		log("fparse_text_stat(): expected '*' at pos %d.\n",
+		    ftell(fp));
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		return FAILURE;
+	    }
+	}
+
+	if ( result->misc_items != NULL )
+	{
+	    sfree(result->misc_items);
+	    result->misc_items = NULL;
+	}
+    }
+
+    fskipwhite(fp);
+
+    return OK;
+}	
+
+
+extern Success
+fparse_text_list(FILE *fp,
+		Text_list *result)
+{
+    int i;
+
+    result->first_local_no = fparse_long(fp);
+    result->no_of_texts = fparse_long(fp);
+
+    if ( result->no_of_texts > 0 )
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '{':
+	    result->texts = REALLOC(result->texts,
+				    (result->no_of_texts
+				     * sizeof(Text_no)));
+
+	    if ( result->texts == NULL )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+	    
+	    for ( i = 0; i < result->no_of_texts; i++)
+		result->texts[ i ] = fparse_long(fp);
+	    
+	    fskipwhite(fp);
+	    if ( getc(fp) != '}' )
+	    {
+		log("fparse_text_list(): expected '}' at pos %d.\n",
+		    ftell(fp));
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		return FAILURE;
+	    }
+	    
+	    break;
+
+	case '*':
+	    if ( result->texts != NULL )
+	    {
+		sfree(result->texts);
+		result->texts = NULL;
+	    }
+	    break;
+
+	default:
+	    log("fparse_text_list(): expected '*' or '{' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( getc(fp) != '*' ) 
+	{
+	    log("fparse_text_list(): expected '*' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+	if ( result->texts != NULL )
+	{
+	    sfree(result->texts);
+	    result->texts = NULL;
+	}
+    }
+    return OK;
+}
+    
+
+extern Success
+fparse_info(FILE *fp,
+	   Info *result)
+{
+    result->version = fparse_long(fp);
+    result->conf_pres_conf = fparse_long(fp);
+    result->pers_pres_conf = fparse_long(fp);
+    result->motd_conf = fparse_long(fp);
+    result->kom_news_conf = fparse_long(fp);
+    result->motd_of_lyskom = fparse_long(fp);
+    return OK;
+}
+
+
+extern Success
+fparse_string(FILE *fp,
+	     String *result)
+{
+#if 0
+    String_size i;
+    int c;
+#endif
+    
+    result->len = fparse_long(fp);
+
+    if ( getc(fp) != 'H' )
+    {
+	log("fparse_string(): expected 'H' at pos %d.\n",
+	    ftell(fp));
+	kom_errno = KOM_SERVER_IS_CRAZY;
+	return FAILURE;
+    }
+
+    result->string = REALLOC(result->string, result->len);
+
+    if ( result->string == NULL )
+    {
+	kom_errno = KOM_OUT_OF_MEMORY;
+	return FAILURE;
+    }
+    
+    if ( fread(result->string, sizeof(char), result->len, fp)
+	!= result->len )
+    {
+	log("fparse_string(): unexpected eof at pos %d.\n", ftell(fp));
+	kom_errno = KOM_SERVER_IS_CRAZY;
+	return FAILURE;
+    }
+    
+    return OK;
+}
+
+extern Success
+fparse_member_list(FILE *fp,
+		  Member_list *result)
+{
+    int i;
+
+    result->no_of_members = fparse_long(fp);
+    if ( result->no_of_members > 0 )
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '{':
+	    result->members = REALLOC(result->members,
+				      (result->no_of_members
+				       * sizeof(Member)));
+
+	    if ( result->members == NULL )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+	    
+	    for ( i = 0; i < result->no_of_members; i++)
+		if ( fparse_member(fp, &result->members[ i ]) != OK )
+		{
+		    return FAILURE;
+		}
+	    
+	    
+	    fskipwhite(fp);
+	    if ( getc(fp) != '}' )
+	    {
+		log("fparse_member_list(): expected '}' at pos %d.\n",
+		    ftell(fp));
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		return FAILURE;
+	    }
+	    
+	    break;
+
+	case '*':
+	    if ( result->members != NULL )
+	    {
+		sfree(result->members);
+		result->members = NULL;
+	    }
+	    break;
+
+	default:
+	    log("fparse_member_list(): expected '*' or '{' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( getc(fp) != '*' ) 
+	{
+	    log("fparse_member_list(): expected '*' at pos %d.\n", ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+	if ( result->members != NULL )
+	{
+	    sfree(result->members);
+	    result->members = NULL;
+	}
+    }
+    return OK;
+}
+
+
+extern Success
+fparse_member(FILE *fp,
+	     Member *result)
+{
+    result->member = fparse_long(fp);
+    return OK;
+}
+
+extern Success
+fparse_mark(FILE *fp,
+	   Mark *result)
+{
+    result->text_no = fparse_long(fp);
+    result->mark_type = fparse_long(fp);
+    return OK;
+}
+
+
+extern Success
+fparse_priv_bits(FILE *fp,
+		Priv_bits *result)
+{
+    fskipwhite(fp);
+
+    result->wheel = getc(fp) != '0';
+    result->admin = getc(fp) != '0';
+    result->statistic = getc(fp) != '0';
+    result->create_pers = getc(fp) != '0';
+    result->create_conf = getc(fp) != '0';
+    result->change_name = getc(fp) != '0';
+    result->flg7 = getc(fp) != '0';
+    result->flg8 = getc(fp) != '0';
+    result->flg9 = getc(fp) != '0';
+    result->flg10 = getc(fp) != '0';
+    result->flg11 = getc(fp) != '0';
+    result->flg12 = getc(fp) != '0';
+    result->flg13 = getc(fp) != '0';
+    result->flg14 = getc(fp) != '0';
+    result->flg15 = getc(fp) != '0';
+    result->flg16 = getc(fp) != '0';
+
+    return OK;
+}
+
+
+extern Success
+fparse_personal_flags(FILE *fp,
+		     Personal_flags *result)
+{
+    fskipwhite(fp);
+    
+    result->unread_is_secret = getc(fp) != '0';
+    result->flg2 = getc(fp) != '0';
+    result->flg3 = getc(fp) != '0';
+    result->flg4 = getc(fp) != '0';
+    result->flg5 = getc(fp) != '0';
+    result->flg6 = getc(fp) != '0';
+    result->flg7 = getc(fp) != '0';
+    result->flg8 = getc(fp) != '0';
+
+    return OK;
+}	
+
+extern Success
+fparse_conf_type(FILE *fp,
+		Conf_type *result)
+{
+    fskipwhite(fp);
+    
+    result->rd_prot = getc(fp) != '0';
+    result->original = getc(fp) != '0';
+    result->secret = getc(fp) != '0';
+    result->letter_box = getc(fp) != '0';
+
+    return OK;
+}
+
+
+extern Success
+fparse_who_info(FILE *fp,
+	       Who_info *result)
+{
+    result->person = fparse_long(fp);
+    result->working_conference = fparse_long(fp);
+    if ( fparse_string(fp, &result->what_am_i_doing) != OK )
+    {
+	log("fparse_who_info(): parse error.\n");
+	kom_errno = KOM_SERVER_IS_CRAZY;
+	return FAILURE;
+    }
+
+    return OK;
+}
+
+    
+extern Success
+fparse_who_info_list(FILE *fp,
+		     Who_info_list *result)
+{
+    int i;
+    
+    fskipwhite(fp);
+
+    result->no_of_persons = fparse_long(fp);
+    
+    if ( result->no_of_persons > 0 )
+    {
+	fskipwhite(fp);
+	switch(getc(fp))
+	{
+	case '{':
+	    result->info = REALLOC(result->info,
+				   (result->no_of_persons
+				    * sizeof(Who_info)));
+
+	    if ( result->info == NULL )
+	    {
+		kom_errno = KOM_OUT_OF_MEMORY;
+		return FAILURE;
+	    }
+	    
+	    for ( i = 0; i < result->no_of_persons; i++)
+	    {
+		result->info[ i ] = EMPTY_WHO_INFO;
+		if ( fparse_who_info(fp, &result->info[ i ]) != OK )
+		    return FAILURE;
+	    }
+	    
+	    
+	    fskipwhite(fp);
+	    if ( getc(fp) != '}' )
+	    {
+		log("fparse_who_info_list(): expected '}' at pos %d.\n",
+		    ftell(fp));
+		kom_errno = KOM_SERVER_IS_CRAZY;
+		return FAILURE;
+	    }
+	    
+	    break;
+
+	case '*':
+	    if ( result->info != NULL )
+	    {
+		sfree(result->info);
+		result->info = NULL;
+	    }
+	    break;
+
+	default:
+	    log("fparse_who_info_list(): expected '*' or '{' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+    }
+    else
+    {
+	fskipwhite(fp);
+	if ( getc(fp) != '*' ) 
+	{
+	    log("fparse_who_info_list(): expected '*' at pos %d.\n",
+		ftell(fp));
+	    kom_errno = KOM_SERVER_IS_CRAZY;
+	    return FAILURE;
+	}
+	if ( result->info != NULL )
+	{
+	    sfree(result->info);
+	    result->info = NULL;
+	}
+    }
+    return OK;
+}
+
+extern Success
+fparse_misc_info(FILE *fp, 
+		 Misc_info *result)
+{
+    result->type = fparse_long(fp);
+    
+    switch(result->type)
+    {
+    case recpt:
+	result->datum.recipient = fparse_long(fp);
+	break;
+	
+    case cc_recpt:
+	result->datum.cc_recipient = fparse_long(fp);
+	break;
+	
+    case loc_no:
+	result->datum.local_no = fparse_long(fp);
+	break;
+	
+    case rec_time:
+	result->datum.received_at = fparse_time(fp);
+	break;
+	
+    case comm_to:
+	result->datum.comment_to = fparse_long(fp);
+	break;
+	
+    case comm_in:
+	result->datum.commented_in = fparse_long(fp);
+	break;
+	
+    case footn_to:
+	result->datum.footnote_to = fparse_long(fp);
+	break;
+	
+    case footn_in:
+	result->datum.footnoted_in = fparse_long(fp);
+	break;
+	
+    case sent_by:
+	result->datum.sender = fparse_long(fp);
+	break;
+	
+    case sent_at:
+	result->datum.sent_at = fparse_time(fp);
+	break;
+
+    default:
+	log("fparse_misc_info(): illegal info_type %d at pos %d.\n",
+	    result->type, ftell(fp));
+	kom_errno = KOM_SERVER_IS_CRAZY;
+	return FAILURE;
+    }
+
+    return OK;
+}
diff --git a/src/server/ram-parse.h b/src/server/ram-parse.h
new file mode 100644
index 0000000000000000000000000000000000000000..4f704ded083f1b23e617a7b266fe46e662000273
--- /dev/null
+++ b/src/server/ram-parse.h
@@ -0,0 +1,102 @@
+/*
+ * ram-parse.h -- parse objects from disk file.
+ */
+
+extern u_long
+fparse_long(FILE *fp);
+
+extern void
+fskipwhite(FILE *fp);
+
+extern time_t
+fparse_time(FILE *fp);
+
+extern Success
+fparse_conference(FILE *fp,
+		 Conference *result);
+
+
+Success
+fparse_person(FILE *fp,
+	     Person *person);
+
+Success
+fparse_membership(FILE *fp,
+		 Membership *mship);
+
+	
+extern Success
+fparse_membership_list(FILE *fp,
+		      Membership_list *result);
+
+
+extern Success
+fparse_conf_list(FILE *fp,
+		Conf_list_old *result);
+
+
+extern Success
+fparse_mark_list(FILE *fp,
+		Mark_list *result);
+
+
+extern Success
+fparse_text_stat(FILE *fp,
+		Text_stat *result);
+
+
+extern Success
+fparse_text_list(FILE *fp,
+		Text_list *result);
+    
+
+extern Success
+fparse_info(FILE *fp,
+	   Info *result);
+
+
+extern Success
+fparse_string(FILE *fp,
+	     String *result);
+
+extern Success
+fparse_member_list(FILE *fp,
+		  Member_list *result);
+
+
+extern Success
+fparse_member(FILE *fp,
+	     Member *result);
+
+extern Success
+fparse_mark(FILE *fp,
+	   Mark *result);
+
+
+extern Success
+fparse_priv_bits(FILE *fp,
+		Priv_bits *result);
+
+
+extern Success
+fparse_personal_flags(FILE *fp,
+		     Personal_flags *result);
+
+extern Success
+fparse_conf_type(FILE *fp,
+		Conf_type *result);
+
+
+extern Success
+fparse_who_info(FILE *fp,
+	       Who_info *result);
+
+    
+extern Success
+fparse_who_info_list(FILE *fp,
+		     Who_info_list *result);
+
+extern Success
+fparse_misc_info(FILE *fp, 
+		 Misc_info *result);
+
diff --git a/src/server/ram-smalloc.c b/src/server/ram-smalloc.c
new file mode 100644
index 0000000000000000000000000000000000000000..0efbb45598fe09b85905ce024cb4c9f55ca7729e
--- /dev/null
+++ b/src/server/ram-smalloc.c
@@ -0,0 +1,177 @@
+/*
+ * smalloc.c
+ *
+ * Contains memory allocator routines
+ *
+ *	TEST VERSION by ceder
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <malloc.h>
+#include <stdlib.h>
+#include "smalloc.h"
+#include "lyskomd.h"
+#include "log.h"
+
+static u_long no_of_allocated_blocks = 0;
+
+/*
+ * "safe" malloc. Handles the case when malloc returns NULL.
+ * smalloc cannot fail.
+ */
+void *
+smalloc(size_t size)
+{
+   unsigned int *p;
+
+   
+   p = (unsigned int *) malloc(size + 2*sizeof(unsigned int) + 2);
+   if (p == NULL)
+       restart_kom("Can't allocate %lu bytes.\n", (u_long)size);
+
+   ++no_of_allocated_blocks;
+
+   *p++ = SMALLOC_MAGIC_ALLOC;
+   *p++ = size;
+   ((unsigned char *) p)[size]   = 0x89;
+   ((unsigned char *) p)[size+1] = 0xA7;
+   
+   return (void *) p;
+}
+
+
+void
+sfree(void * ptr)	/* it is legal to sfree a NULL pointer */
+{
+    unsigned int *ip;
+
+#if 0
+    if ( ptr == NULL)
+        log("SFREE: Freeing a NULL pointer - should be bad! /pen\n");
+#endif
+    
+    if ( ptr != NULL )
+    {
+        ip = (unsigned int *) ptr;
+	ip -= 2;
+	switch (*ip)
+	{
+	    case SMALLOC_MAGIC_ALLOC:
+		if (((unsigned char *) (ip+2))[ip[1]] != 0x89 ||
+		    ((unsigned char *) (ip+2))[ip[1]+1] != 0xA7)
+		  restart_kom("SFREE: Buffer overflow, bsize = %ul\n", ip[1]);
+	        --no_of_allocated_blocks;
+		*ip = SMALLOC_MAGIC_FREE;
+		free( ip );
+		break;
+
+	    case SMALLOC_MAGIC_FREE:
+		restart_kom("SFREE: Trying to free already freed block\n");
+
+	    default:
+		restart_kom("SFREE: Illegal magic number\n");
+	}
+    }
+}
+
+extern void *
+srealloc(void * ptr, size_t size) /* Never fails. It is legal to */
+{				    /* realloc the NULL ptr. */
+    unsigned int   * ip;
+    unsigned int   * new_ptr;
+
+    if ( ptr == NULL )
+	return smalloc(size);
+
+    ip = (unsigned int *) ptr;
+    ip -= 2;
+    switch (*ip)
+    {
+        case SMALLOC_MAGIC_ALLOC:
+            break;
+
+	case SMALLOC_MAGIC_FREE:
+	    restart_kom("SREALLOC: Trying to realloc freed block\n");
+
+	default:
+	    restart_kom("SREALLOC: Illegal magic number\n");
+    }
+
+    if (((unsigned char *) (ip+2))[ip[1]] != 0x89 ||
+	((unsigned char *) (ip+2))[ip[1]+1] != 0xA7)
+      restart_kom("SREALLOC: Buffer overflow, osize = %ul, nsize = %ul\n",
+		  ip[1], size);
+
+    *ip = SMALLOC_MAGIC_FREE;
+    if ( (new_ptr = (unsigned int *) realloc((void *) ip,
+				size+2*sizeof(unsigned int)+2) ) == NULL )
+    {
+	restart_kom("Out of memory - can't realloc. ptr = %d size = %d. ",
+		    (int)ptr, size);
+    }
+
+    *new_ptr++ = SMALLOC_MAGIC_ALLOC;
+    *new_ptr++ = size;
+
+    ((unsigned char *) new_ptr)[size]   = 0x89;
+    ((unsigned char *) new_ptr)[size+1] = 0xA7;
+   
+    return (void *) new_ptr;
+}
+
+
+/*
+ * Allocate temporary memory, which is automatically freed after this
+ * atomic call.
+ */
+
+static void **tmp_alloc_table   = NULL;
+static int tmp_alloc_table_size = 0; /* Size */
+static int tmp_alloc_table_use  = 0; /* Used size */
+
+void *
+tmp_alloc(u_long size)
+{
+    if ( tmp_alloc_table_size <= tmp_alloc_table_use )
+    {
+	/* Need to increas table. */
+	tmp_alloc_table = srealloc (tmp_alloc_table,
+				    ((++tmp_alloc_table_size)
+				     * sizeof (void *)));
+	log("tmp_alloc: internal table size now %d elements.\n",
+	    tmp_alloc_table_size);
+    }
+    
+    return (tmp_alloc_table[ tmp_alloc_table_use++ ]
+	    = smalloc (size));
+}
+
+
+/*
+ * Free all core which is allocated with tmp_alloc(). This is called from
+ * end_of_atomic().
+ */
+void
+free_tmp(void)
+{
+    int i;
+
+    for ( i = 0; i < tmp_alloc_table_use; i++ )
+    {
+	sfree ( tmp_alloc_table[ i ] );
+	tmp_alloc_table[ i ] = NULL;
+    }
+
+    tmp_alloc_table_use = 0;
+}
+
+void
+free_all_tmp(void)
+{
+    free_tmp();
+    sfree( tmp_alloc_table );
+    tmp_alloc_table = NULL;
+    tmp_alloc_table_size = 0;
+}
diff --git a/src/server/ramkomd.c b/src/server/ramkomd.c
new file mode 100644
index 0000000000000000000000000000000000000000..214008968518893a4c698b6bb3172d0e63461ab2
--- /dev/null
+++ b/src/server/ramkomd.c
@@ -0,0 +1,269 @@
+/*
+ * Detta {r serverns huvudprogram. Det kommer f|rhoppningsvis bli st|rre
+ * {n det {r just nu...
+ *
+ * Created by Willf|r 31/3-90
+ *
+ * It has grown! /ceder
+ */
+
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include "std-disclaimer.h"
+#include <kom-types.h>
+#include <kom-errno.h>
+#include "s-string.h"
+#include "internal-services.h"
+#include "lyskomd.h"
+#include "isc.h"
+#include "smalloc.h"
+#include "cache.h"
+#include "log.h"
+#include "com.h"
+#include "connections.h"
+#include "config.h"
+
+Kom_err		  kom_errno;
+u_long		  err_stat;
+Bool		  send_async_messages		= TRUE;
+
+
+#ifdef DEBUG
+int	buglevel = 0;
+Bool	never_save = FALSE;
+#endif
+
+
+extern	int	chdir (char	* path);
+
+
+char datafilename[1024];   /* Full pathname to the database file */
+char backupfilename[1024]; /* Full pathname to the backup file */
+char textfilename[1024];	/* Full pathname to the text file. */
+char statisticfile[1024];	/* Dito to statistics file. */
+char pidfile[1024];		/* Dito pid-file. */
+
+char ip_client_port[80];        /* Port to listen to for clients */
+char ip_mux_port[80];           /* Port to listen to for mux:es */
+
+int listen_client = -1;		/* ISC listen identifier */
+int listen_mux    = -1;         /*          -"-          */
+
+
+static char *dbase_dir = NULL;  /* Directory where database resides */
+
+static void
+server_init( char * client_port, char * mux_port)
+{
+    /*
+    ** Setup some parameters here
+    */
+    isc_setlogfn(&logv);
+    isc_setallocfn(&smalloc, &srealloc, &sfree);
+    /* isc_setabortfn(&restart_kom); */
+
+    kom_server_mcb  = isc_initialize(NULL);
+    
+    if ( kom_server_mcb == NULL )
+	restart_kom("server_init: can't isc_setup()\n");
+    
+    if ((listen_client = isc_listentcp(kom_server_mcb, client_port)) < 0)
+        restart_kom("server_init: can't isc_listen(CLIENT)\n");
+
+    if ((listen_mux = isc_listentcp(kom_server_mcb, mux_port)) < 0)
+        restart_kom("server_init: can't isc_listen(MUX)\n");
+    
+
+    /*
+     * Ignore SIGPIPE, which the server gets if it tries to write to a
+     * socket and the client has died. The server will anyhow handle
+     * this situation correct.
+     */
+    signal(SIGPIPE, SIG_IGN);
+}
+
+static void
+init_data_base(const char *dbase_dir)
+{
+    String a = EMPTY_STRING, b = EMPTY_STRING, c = EMPTY_STRING,
+	    d = EMPTY_STRING;
+
+    if (dbase_dir == NULL)
+      dbase_dir = DEFAULT_DBASE_DIR;
+    
+    sprintf(datafilename,   "%s/%s", dbase_dir, DATAFILE_NAME);
+    sprintf(backupfilename, "%s/%s", dbase_dir, BACKUPFILE_NAME);
+    sprintf(textfilename, "%s/%s", dbase_dir, TEXTFILE_NAME);
+    sprintf(statisticfile, "%s/%s", dbase_dir, STATISTIC_NAME);
+    sprintf(pidfile, "%s/%s", dbase_dir, PID_NAME);
+
+    log("Database = %s\n",  datafilename);
+    log("Backup = %s\n", backupfilename);
+    
+    if ( init_cache() == FAILURE )
+    {
+	log ( "Setting up first four conferences.\n");
+
+	if ( s_crea_str(&a, "Presentation (av nya) m|ten") != OK
+	    || s_crea_str(&b, "Presentation (av nya) medlemmar") != OK
+	    || s_crea_str(&c, "Lappar (p}) d|rren") != OK
+	    || s_crea_str(&d, "Nyheter om LysKOM") != OK)
+	{
+	    restart_kom("init_data_base: can't create strings\n");
+	}
+	
+	if ( ! (   do_create_conf(a, 0, 0, 0, NULL_CONF_TYPE)
+		&& do_create_conf(b, 0, 0, 0, NULL_CONF_TYPE)
+		&& do_create_conf(c, 0, 0, 0, NULL_CONF_TYPE)
+		&& do_create_conf(d, 0, 0, 0, NULL_CONF_TYPE)))
+	{
+	    restart_kom("init_data_base: Kan ej skapa m|ten\n");
+	}
+    }
+}
+
+void
+sighandler_hup (void)
+{
+    log ("Signal HUP received. Shutting down server.\n");
+    go_and_die = TRUE;
+}
+
+void
+sighandler_quit (void)
+{
+    log ("Signal QUIT received - syncing...\n");
+    cache_sync_all();
+    log ("Dumping core now.\n");
+    chdir ("/usr/lyskom/etc");
+    abort();
+}
+
+void
+sighandler_usr1 (void)
+{
+    do_sync_db = TRUE;
+}
+
+void
+sighandler_usr2 (void)
+{
+    int		  child;
+    extern int	  fork(void);
+
+    log ("Signal USR2 received - will dump core now.  (Check that child dies.)\n");
+    if ((child = fork()) == 0)
+    {
+	chdir ("/usr/lyskom/etc");
+	abort();
+	log ("Abort() failed!!!");
+	exit(1);
+    }
+    else if (child < 0)
+    {
+	log ("Couldn't fork.\n");
+    }
+    else
+    {
+	wait (NULL);
+    }
+}
+
+void
+save_pid(void)
+{
+    FILE *fp;
+
+    if ( (fp = fopen(pidfile, "w")) == NULL )
+	return;
+
+    fprintf(fp, "%d", getpid());
+    fclose(fp);
+}
+
+	
+int
+main (int    argc,
+      char **argv)
+{
+    int i;
+
+
+    strcpy(ip_client_port, DEFAULT_CLIENT_SERVICE);
+    strcpy(ip_mux_port,    DEFAULT_MUX_SERVICE);
+
+    for (i = 1; i < argc && argv[i][0] == '-'; i++)
+	switch (argv[i][1])
+	{
+	case 'd':
+	    buglevel++;
+	    break;
+
+	case 'q':
+	    never_save = TRUE;
+	    break;
+	    
+	case 'D':		/* Database directory */
+	    dbase_dir = argv[i]+2;
+	    break;
+
+	case 'p':		/* TCP/IP port number for clients */
+	    strcpy(ip_client_port, argv[i]+2);
+	    break;
+
+	case 'P':		/* TCP/IP port number for MUXes */
+	    strcpy(ip_mux_port, argv[i]+2);
+	    break;
+
+	case 'a':
+	    send_async_messages = FALSE;
+	    break;
+
+	default:
+	    restart_kom("usage: ramkomd [options]\n");
+	}
+    
+    if (i < argc)
+    {
+	restart_kom("usage: ramkomd\n");
+    }
+
+    s_set_storage_management(smalloc, srealloc, sfree);
+
+    signal(SIGHUP, sighandler_hup);
+    signal(SIGQUIT, sighandler_quit);
+    signal(SIGUSR1, sighandler_usr1);
+    signal(SIGUSR2, sighandler_usr2);
+  
+    server_init(ip_client_port, ip_mux_port);
+    init_data_base(dbase_dir);
+    save_pid();
+    toploop();
+    cache_sync_all();
+    free_all_tmp();
+  
+    log("ramkomd terminated normally.\n");
+    return 0;
+}
+
+void
+restart_kom(const char * format, ...)
+{
+    va_list AP;
+
+    va_start(AP, format);
+    logv(format, AP);
+    va_end(AP);
+
+    log("Previous message is fatal. Will dump core now.\n");
+    abort();
+}
diff --git a/src/server/server-config.c b/src/server/server-config.c
new file mode 100644
index 0000000000000000000000000000000000000000..8d83113b5770b2d9e73c21d6bf66184325862574
--- /dev/null
+++ b/src/server/server-config.c
@@ -0,0 +1,100 @@
+/*
+ *  config.c
+ *
+ *  This is in a .c file to make it possible to change a value without having
+ *  to recompile the entire server.
+ */
+
+
+/* Where to save things. These are used by ramkomd. */
+
+const char * DEFAULT_DBASE_DIR = "/usr/lyskom";
+
+const char *DATAFILE_NAME   = "db/ramkomd-data";
+const char *BACKUPFILE_NAME = "db/ramkomd-backup";
+const char *TEXTFILE_NAME   = "db/ramkomd-texts";
+const char *STATISTIC_NAME  = "etc/ramkomd-logg";
+const char *PID_NAME   = "etc/pid";
+
+/* Communications */
+
+const char * DEFAULT_CLIENT_SERVICE = "lyskom-client";  /* Can be number! */
+const char * DEFAULT_MUX_SERVICE    = "lyskom-mux";     /* Can be number! */
+
+#if 0
+const int IN_CLIENT_PORT = 4896;	/* Which port does LysKOM listen to? */
+const int IN_MUX_PORT = 4895;		/* Which port does LysKOM listen to? */
+#endif
+
+/*
+ * The following should always be true:
+ * 0 <= SYNCTIMEOUT <= GARBTIMEOUT <= TIMEOUT
+ * Times in milliseconds.
+ *
+ * GARBTIMEOUT and SYNCTIMEOUT are only used in diskomd.
+ */
+const int TIMEOUT = 100;	/* Timeout to select() when totally idle.
+				 * Should be much larger. Is this small
+				 to work around the famous select bug
+				 in isc. */
+const int GARBTIMEOUT = 100;	/* Timeout to select() when garbing texts
+				   but not syncing. */
+const int SYNCTIMEOUT = 10;	/* Timeout to select() when syncing. */
+
+/* Times in minutes. */
+const int GARB_INTERVAL = 60*24; /* Once a day. */
+const int SYNC_INTERVAL = 30;	/* 30 = Twice per hour. */
+const int SYNC_RETRY_INTERVAL = 5; /* 5 = Wait 5 minutes and retry after
+				      an error. */
+
+/* String limits */
+
+const int CONF_NAME_LEN = 60;	/* Conference (and Person) name */
+const int PWD_LEN	= 128;	/* Password. It is not guaranteed that all
+				   chars are significant. */
+const int WHAT_DO_LEN	=  60;	/* what_am_i_doing */
+const int USERNAME_LEN	= 128;	/* Max login-id from clients */
+const int TEXT_LEN      =131072;/* Max len of a text. */
+const int BROADCAST_LEN = 1024;	/* Max len of a broadcast message */
+
+/* Text_stat limits */
+
+const int MAX_MARKS_PERSON = 2048; /* Max marks per person */
+const int MAX_MARKS_TEXT   = 1024; /* Max marks per text */
+const int MAX_RECIPIENTS   =  256; /* Max recipients/cc_recipients per text */
+const int MAX_COMM	   =  128; /* Max number of comments to a text */
+const int MAX_FOOT	   =   32; /* Max number of footnotes to a text */
+const int MAX_CREA_MISC    =  512; /* Sum of recipients, cc_recipients, comm_to
+				   * and footn_to must not exceed MAX_CREA_MISC
+				    * when the text is created. */
+
+/*
+ * Some other limits
+ */
+
+#ifdef __sequent__
+const int MAX_NO_OF_CONNECTIONS = 12;
+#else
+const int MAX_NO_OF_CONNECTIONS = 32;	/* the maximum number of persons
+					 * that can use Kom simultaneously */
+#endif
+
+const int MARK_AS_READ_CHUNK    = 128;	/* You can't mark more than this many
+					 * texts as read in one call. */
+
+/*
+ * Max number of nested super_confs a message will be forwarded before
+ * the server gives up.
+ */
+const int MAX_SUPER_CONF_LOOP = 17;
+
+
+const int DEFAULT_NICE = 77;	/* Number of days a text normally lives. */
+
+
+
+/* Max entries in the per-client transmit queue */
+const int MAXQUEUEDSIZE = 300;
+
+/* Max entries in the per-client transmit queue to send at any one time */
+const int MAXDEQUEUELEN = 10;
diff --git a/src/server/tmp-limits.h b/src/server/tmp-limits.h
new file mode 100644
index 0000000000000000000000000000000000000000..0b14f1c6b27c662aa044a4eb4bb7273dc004a6d3
--- /dev/null
+++ b/src/server/tmp-limits.h
@@ -0,0 +1,8 @@
+/*
+ * limits.h
+ *
+ * These arbitrary limits should be lifted in future versions.
+ */
+
+#define MAX_CONF 1026
+#define MAX_TEXT 99999