lima-1.0b5/
lima-1.0b5/driver/
lima-1.0b5/driver/ChangeLog.old/
lima-1.0b5/driver/Win32/
lima-1.0b5/driver/compat/
lima-1.0b5/driver/include/
lima-1.0b5/driver/testsuite/
lima-1.0b5/driver/testsuite/clone/
lima-1.0b5/driver/testsuite/command/
lima-1.0b5/driver/testsuite/data/
lima-1.0b5/driver/testsuite/etc/
lima-1.0b5/driver/testsuite/include/
lima-1.0b5/driver/testsuite/inherit/
lima-1.0b5/driver/testsuite/inherit/master/
lima-1.0b5/driver/testsuite/log/
lima-1.0b5/driver/testsuite/single/
lima-1.0b5/driver/testsuite/single/tests/compiler/
lima-1.0b5/driver/testsuite/single/tests/efuns/
lima-1.0b5/driver/testsuite/single/tests/operators/
lima-1.0b5/driver/testsuite/u/
lima-1.0b5/driver/tmp/
lima-1.0b5/etc/
lima-1.0b5/lib/WWW/help/
lima-1.0b5/lib/cmds/
lima-1.0b5/lib/cmds/create/
lima-1.0b5/lib/cmds/player/attic/
lima-1.0b5/lib/contrib/bboard/
lima-1.0b5/lib/contrib/boards/
lima-1.0b5/lib/contrib/marriage/
lima-1.0b5/lib/contrib/roommaker/
lima-1.0b5/lib/contrib/transient_effect/
lima-1.0b5/lib/daemons/channel/
lima-1.0b5/lib/daemons/imud/
lima-1.0b5/lib/data/
lima-1.0b5/lib/data/config/
lima-1.0b5/lib/data/links/
lima-1.0b5/lib/data/news/
lima-1.0b5/lib/data/players/
lima-1.0b5/lib/data/secure/
lima-1.0b5/lib/domains/
lima-1.0b5/lib/domains/std/2.4.5/maze1/
lima-1.0b5/lib/domains/std/2.4.5/npc/
lima-1.0b5/lib/domains/std/2.4.5/post_dir/
lima-1.0b5/lib/domains/std/2.4.5/sub/
lima-1.0b5/lib/domains/std/camera/
lima-1.0b5/lib/domains/std/config/
lima-1.0b5/lib/domains/std/cult/
lima-1.0b5/lib/domains/std/effects/
lima-1.0b5/lib/domains/std/misc/
lima-1.0b5/lib/domains/std/monsters/
lima-1.0b5/lib/domains/std/recorder/
lima-1.0b5/lib/domains/std/rooms/
lima-1.0b5/lib/domains/std/rooms/beach/
lima-1.0b5/lib/domains/std/rooms/labyrinth/
lima-1.0b5/lib/domains/std/school/
lima-1.0b5/lib/domains/std/school/O/
lima-1.0b5/lib/domains/std/spells/
lima-1.0b5/lib/domains/std/spells/stock-mage/
lima-1.0b5/lib/domains/std/spells/stock-priest/
lima-1.0b5/lib/help/
lima-1.0b5/lib/help/admin/
lima-1.0b5/lib/help/hints/General_Questions/
lima-1.0b5/lib/help/hints/Pirate_Quest/
lima-1.0b5/lib/help/player/
lima-1.0b5/lib/help/player/bin/
lima-1.0b5/lib/help/player/quests/
lima-1.0b5/lib/help/wizard/
lima-1.0b5/lib/help/wizard/coding/guilds/
lima-1.0b5/lib/help/wizard/coding/rooms/
lima-1.0b5/lib/help/wizard/lib/daemons/
lima-1.0b5/lib/help/wizard/lib/lfun/
lima-1.0b5/lib/help/wizard/lib/std/
lima-1.0b5/lib/help/wizard/mudos_doc/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/interactive/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/parsing/
lima-1.0b5/lib/help/wizard/mudos_doc/concepts/
lima-1.0b5/lib/help/wizard/mudos_doc/driver/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/arrays/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/buffers/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/compile/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/filesystem/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/floats/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/functions/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/general/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/mappings/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/mixed/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/numbers/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/parsing/
lima-1.0b5/lib/help/wizard/mudos_doc/lpc/constructs/
lima-1.0b5/lib/help/wizard/mudos_doc/lpc/types/
lima-1.0b5/lib/include/driver/
lima-1.0b5/lib/log/
lima-1.0b5/lib/obj/admtool/
lima-1.0b5/lib/obj/admtool/internal/
lima-1.0b5/lib/obj/admtool/mudinfo/
lima-1.0b5/lib/obj/admtool/secure/
lima-1.0b5/lib/obj/secure/
lima-1.0b5/lib/obj/secure/cmd/
lima-1.0b5/lib/obj/secure/mailers/
lima-1.0b5/lib/obj/secure/shell/
lima-1.0b5/lib/obj/secure/shell/classes/
lima-1.0b5/lib/obj/tasktool/
lima-1.0b5/lib/obj/tasktool/internal/
lima-1.0b5/lib/open/
lima-1.0b5/lib/secure/
lima-1.0b5/lib/secure/cgi/
lima-1.0b5/lib/secure/modules/
lima-1.0b5/lib/secure/simul_efun/
lima-1.0b5/lib/std/adversary/
lima-1.0b5/lib/std/adversary/advancement/
lima-1.0b5/lib/std/adversary/armor/
lima-1.0b5/lib/std/adversary/blows/
lima-1.0b5/lib/std/adversary/death/
lima-1.0b5/lib/std/adversary/formula/
lima-1.0b5/lib/std/adversary/health/
lima-1.0b5/lib/std/adversary/pulse/
lima-1.0b5/lib/std/adversary/wield/
lima-1.0b5/lib/std/classes/event_info/
lima-1.0b5/lib/std/container/
lima-1.0b5/lib/std/living/
lima-1.0b5/lib/std/modules/contrib/
lima-1.0b5/lib/std/patterns/
lima-1.0b5/lib/std/race/
lima-1.0b5/lib/std/race/restricted/
lima-1.0b5/lib/std/room/
lima-1.0b5/lib/tmp/
lima-1.0b5/lib/trans/
lima-1.0b5/lib/trans/admincmds/
lima-1.0b5/lib/trans/obj/
lima-1.0b5/lib/wiz/
/* Do not remove the headers from this file! see /USAGE for more info. */

/* Copyright 1994 - Tim Hollebeek
 *
 * Permission is granted to copy and use this code elsewhere, provided
 * that all code derived from this code retain this header and credit
 * the author (Tim Hollebeek) for the source, that all improvements
 * to this code be communicated to the above mentioned author for possible
 * inclusion in this code, that all derived works are made publicly
 * available to whoever wants them, and no profit is made off of this
 * code or any other derived works or any other package or system this
 * is used in without express written permission of the author.
 */
/* 
 *  General news daemon, by Beek
 *  Oct 10, 1994
 *
 * Interface:
 *
 * id NEWS_D->post(group, subject, message)
 * id NEWS_D->followup(group, id, message)
 * message NEWS_D->get_message(group, id)
 * id array NEWS_D->get_messages(group)
 * id array NEWS_D->get_thread(group, thread)
 * NEWS_D->get_groups()
 * varargs id NEWS_D->system_post(group, subject, message, poster)  (poster optional)
 *
 *
 * 941224, Deathblade: GUElib conversion
 * 950707, Rust: added system_post on July 7, 1995
 * ??????, Beek: modified the system to be automatic based on group prefixes.
 * 950811, Deathblade: converted to classes. added remove_post().
 * 9606xx, Ohara: added move_post() and reply_post.
 * 961205, Ohara: added change_subject at DB's grovel^H^H^H^H^H^Hrequest.
 * 9711xx, Tigran: minor change, added a couple of threading functions, and 
 *                 prevented removed posts from being completely deleted, as 
 *                 other data in the post is needed for threading information.
 * 971112, Tigran: Improved newsgroup restrictions, and placed calls to the
 *                 restriction functions in many places they previously 
 *                 weren't.  Also improved the mail_forward interface.
 *                 Restricted dump() to Mudlib:daemons
 * 990110, Tigran: Unstagnated the removed mapping.  I must have been high 
 *                 before.  I can find no reason that the original threaded
 *                 article is still necessary.  Anyways removed is used again
 */

#include <mudlib.h>
#include <security.h>
#include <commands.h>
#include <classes.h>
#include <socket.h>

inherit M_ACCESS;

inherit CLASS_NEWSMSG;

#define SAVE_FILE "/data/news/news_d"
#define RECENT_FILE "/data/news/recent"

#define ARCHIVE_DIR "/data/news/archive"

void archive_posts();
nomask string * get_groups();
nomask void update_threads(string group);
nomask void update_all_threads();

private mapping data = ([]);
private mapping removed = ([]);
private nosave mapping recent_changes = ([]);
private nosave mapping threading_data=([]);
// No info on a group means never archive.
private mapping archive_info = ([ ]);

/* The purpose of this variable is for placing additional restrictions to 
 * newsgroup hierarchies.  These should all be fpointers */
private nosave mapping additional_restrictions = ([]);

private mapping restrictions=([]);

private mapping mail_forward =([]);

#define is_group(x) (member_array(x,get_groups()) != -1)

nomask void save_recent() {
    unguarded(1, (: rm, RECENT_FILE :));
    unguarded(1, (: write_file, RECENT_FILE, save_variable(recent_changes) :));
}

nomask void save_me()
{
    unguarded(1, (: save_object, SAVE_FILE :));
    save_recent();
}

/*
** Set the archive time on a group.
*/
nomask void set_archive_time(string group, int numDays)
{
    if (!check_privilege("Mudlib:daemons"))
    {
        error("Insufficient privs");
    }
    if (!is_group(group))
    {
        error("First arg not a valid newsgroup");
    }
    if(!intp(numDays))
    {
        error("Number of days not an integer.");
    }
    if(numDays < 0)
    {
        error("Number of days must be greater than or equal to 0.");
    }
    if(!numDays) 
    {
        /* Never expire */
        map_delete(archive_info, group);
    }
    else
    {
        archive_info[group] = numDays;
    }
    save_me();
}

nomask mapping get_archive_info()
{    
  if (!check_privilege("Mudlib:daemons"))
    error("Insufficient privs");
  return archive_info;
}

nomask void create()
{
    string rec;

    set_privilege(1);
    if ( clonep(this_object()) )
    {
        destruct(this_object());
        return;
    }

    restore_object(SAVE_FILE, 1);
    if (rec = read_file(RECENT_FILE))
        recent_changes = restore_variable(rec);
    else
        recent_changes = 0;
    if (!recent_changes) {
        recent_changes = ([]);
        foreach (string key in keys(data))
            recent_changes[key] = ([]);
    } else {
        foreach (string key, mixed value in recent_changes) {
            if (value == "#removed#") {
	      map_delete(data, key);
	      map_delete(recent_changes, key);
	      map_delete(removed, key);
            } 
	    else {
	      /* possible for new groups */
	      if (!data[key])
		data[key] = ([]);
	      if (!removed[key])
		removed[key] = ({});
	      
	      recent_changes[key] = ([]);
	      foreach (string key2 in keys(value)) {
		data[key][key2] = value[key2];
		if (classp(value[key2]) &&
		    ((class news_msg)value[key2])->body == 0)
		  removed[key] += ({ key2 });
	      }
            }
        }
    }
    foreach (string key, mixed value in data) {
        if (mapp(value)) {
            foreach (string key2, class news_msg msg in value) {
                if (classp(msg) && !msg->body)
                    map_delete(value, key2);
            }
        }
    }
    update_all_threads();
    save_me();
    archive_posts();
}

private nomask int get_new_id(string group)
{
    int id = data[group]["next_id"]++;

    recent_changes[group]["next_id"] = data[group]["next_id"];

    return id;
}

private nomask void notify_users(string group, class news_msg msg)
{
    CHANNEL_D->deliver_channel("news",
      sprintf("%s: %s [%s]",
        group,
        msg->subject[0..39],
        msg->poster));
}

nomask mapping get_mail_forward()
{
  if(!check_privilege("Mudlib:daemons"))
    return 0;
  return mail_forward;
}

nomask string array query_mail_forward(string group)
{
  if(!check_privilege("Mudlib:daemons"))
    return 0;
  if(member_array(group,
		  keys(mail_forward) )==-1)
    return ({ });
  return mail_forward[group];
}

nomask void add_mail_forward(string group,string destination)
{
  if(!check_privilege("Mudlib:daemons"))
    return;
  if(member_array(group,
		  keys(mail_forward) )==-1 )
    {
      mail_forward+=([group:({destination})]);
      return;
    }
  if(member_array(destination,mail_forward[group])>-1)
    return;
  mail_forward[group]+=({destination});
  return;
}

nomask varargs void remove_mail_forward(string group,string destination)
{
  if(!check_privilege("Mudlib:daemons"))
    return;
  if(member_array(group,
		  keys(mail_forward) ) == -1 )
    return;
  if(!destination)
    map_delete(mail_forward,group);
  else
    mail_forward[group]-=({destination});
  return;
}

private nomask void do_mail_forward(string group,class news_msg msg ) 
{  
  string body;
  body=sprintf("Posted by %s to %s on %s:\n\n%s\n",
	       msg->poster,
	       group,
	       MUD_NAME,
	       replace_string(msg->body,"\n.\n","\n..\n") );
  foreach(string destination in mail_forward[group])
    unguarded(1, (:SMTP_D->send_mail($(destination),$(msg->subject),$(body)) :) );
}

nomask mapping list_restrictions()
{
  if(!check_privilege("Mudlib:daemons"))
    return 0;
  return restrictions+additional_restrictions;
}

nomask int is_write_restricted(string group)
{
  string closest="";
  mixed array restrict;
  string whoami=this_user()->query_userid();
  string rest;
  mixed array priv;
  /* Never ever restrict the administration from being able to access a
   * newsgroup */
  if(adminp(whoami))
    return 0;
  foreach(rest,priv in restrictions)
    {
      int i;
      if(rest!=group[0..((i=strlen(rest))-1)]||i<strlen(closest))
	continue;
      closest=rest;
      restrict=priv;
    }
  foreach(rest,priv in additional_restrictions)
    {
      int i;
      if(rest!=group[0..((i=strlen(rest))-1)]||i<strlen(closest))
	continue;
      closest=rest;
      restrict=priv;
    }
  /* No restriction */
  if(closest==""||restrict[0]==""||!restrict[0])
    return 0;
  /* If the restriction is a function pointer evaluate it and return 
   * 1 if it returns nonzero */
  if(functionp(restrict[0]))
    {
      if(evaluate(restrict[0],whoami))
	return 1;
      return 0;
    }
  /* Check the status of the user here, if they aren't a wizard, no 
   * sense checking any farther */
  if(!wizardp(whoami) )
    return 1;
   /* Handle special case "wiz", which really should be wizardp() but is the
   * only obvious contingency which isn't part of an existing dmoain (like 
   * admin). */
  if(restrict[0]=="wiz")
    return 0;
  /* If the domains doesn't exist that is in the restriction, we might 
   * as well stop here because the rest of the calls will fail, */
  if(member_array(restrict[0],SECURE_D->query_domains())==-1)
    return 0;
  /* If the first character of the restriction is a capital letter the 
   * user must be a lord of the domain */
  if('A'<=closest[0]&&closest[0]<='Z')
    {
      if(member_array(lower_case(restrict[0]),
		      SECURE_D->query_domains(whoami) ) >-1&& 
	 member_array(whoami,SECURE_D->query_domain_lords(lower_case(restrict[0]) ) ) >-1 )
	return 0;
      else
	return 1;
    }
  if(member_array(restrict[0],
		  SECURE_D->query_domains(whoami) ) >-1)
    return 0;
  return 1;
}

nomask int is_read_restricted(string group)
{
  string closest="";
  mixed array restrict;
  string whoami;
  string rest;
  mixed array priv;
  /* Never ever restrict the administration from being able to access a
   * newsgroup */
  if(!this_user())
    return 0;
  whoami=this_user()->query_userid();
  if(adminp(whoami))
    return 0;
  foreach(rest,priv in restrictions)
    {
      int i;
      if(rest!=group[0..((i=strlen(rest))-1)]||i<strlen(closest))
	continue;
      closest=rest;
      restrict=restrictions[rest];
    }
  foreach(rest,priv in additional_restrictions)
    {
      int i;
      if(rest!=group[0..((i=strlen(rest))-1)]||i<strlen(closest))
	continue;
      closest=rest;
      restrict=priv;
    }
  /* No restriction */
  if(closest==""||restrict[1]==""||!restrict[1])
    return 0;
  /* If the restriction is a function pointer evaluate it and return 
   * 1 if it returns nonzero */
  if(functionp(restrict[1]))
    {
      if(evaluate(restrict[1],whoami))
	return 0;
      return 1;
    }
  /* Check the status of the user here, if they aren't a wizard, no 
   * sense checking any farther */
  if(!wizardp(whoami) )
     return 1;
   /* Handle special case "wiz", which really should be wizardp() but is the
   * only obvious contingency which isn't part of an existing dmoain (like 
   * admin). */
  if(restrict[1]=="wiz")
    return 0;
  /* If the domains doesn't exist that is in the restriction, we might 
   * as well stop here because the rest of the calls will fail, */
  if(member_array(restrict[1],SECURE_D->query_domains())==-1)
    return 1;
  /* If the first character of the restriction is a capital letter the 
   * user must be a lord of the domain */
  if('A'<=closest[0]&&closest[0]<='Z')
    {
      if(member_array(lower_case(restrict[1]),
		      SECURE_D->query_domains(whoami) ) >-1&& 
	 member_array(whoami,
		      SECURE_D->query_domain_lords(lower_case(restrict[1]) ) ) >-1 )
	return 0;
      else
	return 1;
    }
  if(member_array(restrict[1],
		  SECURE_D->query_domains(whoami) ) >-1)
    return 0;
  return 1;
}

nomask string query_write_restriction(string prefix)
{
  if(restrictions[prefix])
    return restrictions[prefix][0];
  if(additional_restrictions[prefix])
    return sprintf("%O",additional_restrictions[prefix][0]);
}

nomask string query_read_restriction(string prefix)
{
  if(restrictions[prefix])
    return restrictions[prefix][1];
  if(additional_restrictions[prefix])
    return sprintf("%O",additional_restrictions[prefix][1]);
}

varargs nomask void set_write_restriction(string prefix,string restriction)
{
  if(!check_privilege("Mudlib:daemons"))
    return;
  if(restriction=="0"||restriction=="")
    restriction=0;
  if(!restrictions[prefix])
    restrictions[prefix]=({restriction,0});
  else 
    restrictions[prefix][0]=restriction;
  if(restrictions[prefix][0]==0&&restrictions[prefix][1]==0)
    map_delete(restrictions,prefix);
  save_me();
}

varargs nomask void set_read_restriction(string prefix,string restriction)
{
  if(!check_privilege("Mudlib:daemons"))
    return;  
  if(restriction=="0"||restriction=="")
    restriction=0;
  if(!restrictions[prefix])
    restrictions[prefix]=({0,restriction});
  else 
    restrictions[prefix][1]=restriction;
  if(restrictions[prefix][0]==0&&restrictions[prefix][1]==0)
    map_delete(restrictions,prefix);
  save_me();
}


nomask int post(string group, string subject, string message)
{
    int post_id;
    class news_msg msg;

    if (!data[group]) return 0;
    if(is_write_restricted(group))
      {
	write("You cannot post messages to this group.\n");
	return 0;
      }
    post_id = get_new_id(group);
    msg = new(class news_msg,
              time :            time(),
              thread_id :       post_id,
              subject :         subject,
              userid :          this_user()->query_userid(),
              body :            message);
    msg->poster          = capitalize( msg->userid );

    data[group][post_id] = msg;
    update_threads(group);
    recent_changes[group][post_id] = msg;
    save_recent();

    notify_users(group, msg);

    if (mail_forward[group])
        do_mail_forward(group,msg);

    return post_id;
}

varargs nomask int system_post(string group,
  string subject,
  string message,
  string poster)
{
    int post_id;
    class news_msg msg;

//### need to think on this. I don't think we want to require priv 1
//### ... especially since even *this* object doesn't have that.  And
//### just checking the previous object is a no-no.
//### 
//### Set up to check for Mudlib:daemons because otherwise this is WIDE
//### open and I don't believe this is the desired effect -- Tigran
//### check for Mudlib:daemons maybe?
    if(is_write_restricted(group))
      {
	write("Invalid group to post.\n");
	return 0;
      }
    if(!check_privilege("Mudlib:daemons"))
      return 0;
    if ( !data[group] )
        return 0;
    post_id = get_new_id(group);
    msg = new(class news_msg,
      time : time(),
      thread_id : post_id,
      subject : subject);
    if ( poster )
    {
        msg->poster = poster;
        msg->userid = base_name(previous_object());
    }
    else if ( this_body() )
    {
        msg->userid = this_user()->query_userid();
        msg->poster = capitalize( msg->userid );
    }
    else if ( this_user() )
    {
        msg->userid = this_user()->query_userid();
        msg->poster = capitalize( msg->userid);
    }
    else
    {
        msg->poster = msg->userid = mud_name();
    }
    msg->body           = message;

    data[group][post_id] = msg;
    recent_changes[group][post_id] = msg;
    update_threads(group);
    save_recent();

    notify_users(group, msg);

    if (mail_forward[group])
        do_mail_forward(group,msg);

    return post_id;
}

varargs nomask void add_group(string group)
{
    if (!check_privilege("Mudlib:daemons")) error("Permission denied.\n");

    data[group] = (["next_id":1]);
    recent_changes[group] = (["next_id":1]);
    removed[group] = ({});
    update_all_threads();
    save_recent();
}

nomask void remove_group(string group)
{
    if (!check_privilege("Mudlib:daemons")) error("Permission denied.\n");
    map_delete(data, group);
    recent_changes[group] = "#removed#";
    update_all_threads();
    save_recent();
}

nomask int followup(string group, int id, string message)
{
    string subject;
    int post_id;
    class news_msg msg;

    if (!data[group]) return 0;
    if (!data[group][id]) return 0;
    if(is_write_restricted(group)) return 0;
    post_id = get_new_id(group);
    subject = ">" + ((class news_msg)data[group][id])->subject;

    id = ((class news_msg)data[group][id])->thread_id;

    msg = new(class news_msg,
      time : time(),
      thread_id : id, /* link to original thread_id */
      subject : subject,
      userid : this_user()->query_userid(),
      body : message);
    msg->poster       = capitalize( msg->userid );

    data[group][post_id] = msg;
    recent_changes[group][post_id] = msg;
    save_recent();
    update_threads(group);
    notify_users(group, msg);

    if (mail_forward[group])
        do_mail_forward(group,msg);

    return post_id;
}

nomask class news_msg get_message(string group, int id)
{
    class news_msg msg;
    
    if ( !data[group] )
        return 0;

    if(is_read_restricted(group))
      return 0;
    msg = data[group][id];
    if ( !msg )
        return 0;

    /* sigh */
//### oops... can't do this yet... leave this unsafe for now
//    return copy(msg);
    return msg;
}

nomask void remove_post(string group, int id)
{
    class news_msg msg;

    if ( !data[group] )
        return;

    msg = data[group][id];
    if ( !msg || !msg->body )
        return;
    if ((this_user() && !adminp(this_user()) && msg->userid != this_user()->query_userid()) &&
      (msg->userid != base_name(previous_object())))
    {
        error("* illegal attempt to remove post\n");
    }

    msg->body = 0;
    recent_changes[group][id] = msg;
    removed[group]+=({id});
    update_threads(group);
    save_recent();
}

varargs nomask int * get_messages(string group, int no_removed)
{
    array ret = keys(data[group]) - ({ "next_id" });
    
    if(is_read_restricted(group))
      return 0;
    if (no_removed)
        ret -= removed[group];
    
    return ret;
}

nomask int get_thread_id(string group,int id)
{
  if(!id||!group)
    return 0;
  if(is_read_restricted(group))
    return 0;
  /* Check to make sure that the article exists */
  if(!data[group][id])
    return 0;
  return ((class news_msg)data[group][id])->thread_id;
}

nomask int * get_thread(string group, int thread)
{
  if(is_read_restricted(group))
    return 0;
  return filter_array(keys(data[group]),
		      (: $1 != "next_id" &&
		       ((class news_msg)data[$(group)][$1])->thread_id
		       == $(thread) :) );
}

nomask int array get_thread_ids(string group)
{
  if(is_read_restricted(group))
     return 0;
  return filter_array(get_messages(group,1),(: $1!=get_thread_id($(group),$1) :) );
}

nomask string * get_groups()
{
    string array ret;

    // filter before sorting; the func is typically pretty cheap, and
    // and calling them all is O(n).  Sorting the list first is more
    // expensive
    ret = filter(keys(data),(: is_read_restricted($1)?0:1 :) );
    return sort_array(ret, 1);
}

nomask int get_group_last_id(string group)
{
    return data[group]["next_id"] - 1;
}

nomask void dump_to_file(string group, string fname)
{
    mapping contents = data[group];
    int id;
    if(is_read_restricted(group))
      return;
    foreach ( id in sort_array(keys(contents) - ({ "next_id" }), 1) )
    {
        class news_msg msg = contents[id];

        if ( !msg->body )
            continue;

        write_file(fname,
          sprintf("---\nposter: %s\nsubject: %s\ndate: %s\n\n%s\n",
            msg->poster, msg->subject,
            intp(msg->time) ? ctime(msg->time) : msg->time,
            msg->body));
    }
}

/* these functions need work IMHO, first off a post should never be
 * archived if the thread is still active.  I also need to look into 
 * how the newsreader should handle things like this because it will 
 * currently choke on anything that doesn't exist...  -- Tigran*/
private  nomask void archive_post(string group, int id)
{
    class news_msg msg = data[group][id];

    unguarded(1, (: write_file, 
		  sprintf("%s/%s", ARCHIVE_DIR, group),
		  sprintf("---\nposter: %s\nsubject: %s\ndate: %s\n%s\n\n",
			  msg->poster, msg->subject,
			  intp(msg->time) ? ctime(msg->time) : msg->time,
			  msg->body)
		  :) );

    /* This doesn't give w/ the newsreader. */
    /* the newsreader will learn */
#if 0
    map_delete(data[group], id);
#else
    /* Do this instead. */
    remove_post(group, id);
#endif
}

private nomask void archive_posts()
{
    int archive_days;
    int archive_time;
    string group;
    mapping contents;

    foreach ( group, contents in data )
    {
        mixed id;
        class news_msg msg;

        archive_days = archive_info[group];
        if(!archive_days)
        {
            continue;
        }
        archive_time = time() - archive_days * 24 * 60 * 60;
        foreach ( id, msg in contents )
        {
            if ( id == "next_id" )
                continue;
            if ( intp(msg->time) && msg->time < archive_time ) 
                archive_post(group, id);
        }
    }
    update_all_threads();
}


// Blame --OH. for this code :)

nomask void move_post( string curr_group, int curr_id, string to_group )
{
    class  news_msg msg;
    int new_id;
    msg = copy( data[curr_group][curr_id]);
    if( !adminp(this_user()) && msg->userid != this_user()->query_userid())
    {
        write( "You cannot move posts which don't belong to you.\n");
        return;
    }
    if( curr_group == to_group )
    {
        write( "Same group. Post not moved.\n");
        return;
    }
    if( is_write_restricted(to_group))
    {
        write( "You don't have permission to move posts to " + to_group + "\n");
        return;
    }
    new_id = get_new_id(to_group);
    msg->body = "(Originally in " + curr_group + ")\n" + msg->body;
    msg->thread_id = new_id;
    data[to_group][new_id] = msg;
    recent_changes[to_group][new_id] = msg;
    remove_post( curr_group, curr_id );
    write( "Post moved.\n");
    update_threads(to_group);
    update_threads(curr_group);
    save_recent();
}

void remove() {
    save_me();
}


nomask void change_header( string group, int id, string header )
{
    class news_msg msg;
    object tu = this_user();

    msg = data[group][id];
    if( !adminp( tu) && tu->query_userid() != msg->userid)
    {
        write( "You cannot change the subjects of posts that don't belong to you\n");
    }
    else if( sizeof( header ))
        ((class news_msg)data[group][id])->subject = header;
    else
        write( "Subject not changed.\n");
    save_recent();
    return;
}

/* This should probably be protected in some way. */
void dump(string path) {
    string ret = "";
    if(!check_privilege("Mudlib:daemons"))
       return;
    foreach (string group in get_groups()) {
        foreach (int id in get_messages(group, 1)) {
            class news_msg post = get_message(group, id);

            ret += "Group: " + group + "\n" +
                   "Subject: " + post->subject + "\n" +
                   "Date: " + ctime(post->time) + "\n" +
                   "Poster: " + post->poster + "\n\n" +
                   post->body + "\n---------------------------------------------------------------------------\n\n";
        }
    }
    write_file(path, ret);
}

array search_for(string what) {
    array ret = ({});
    
    foreach (string group, mapping contents in data) {
        foreach (mixed id, class news_msg post in contents) {
            if (id == "next_id" || !post->body)
                continue;

            if (regexp(post->body, what) && !is_read_restricted(group))
                ret += ({ ({ group, id }) });
        }
    }
    return ret;
}

array search_for_author(string who) {
    array ret = ({});
    
    foreach (string group, mapping contents in data) {
        foreach (mixed id, class news_msg post in contents) {
            if (id == "next_id" || !post->body || !post->poster)
                continue;

            if (lower_case(post->poster) == lower_case(who) && !is_read_restricted(group))
                ret += ({ ({ group, id }) });
        }
    }

    return ret;
}

private nomask int sort_messages_by_thread(int first,int second,string group)
{
  int i1=get_thread_id(group,first);
  int i2=get_thread_id(group,second);
  if(i1<i2)
    return -1;
  if(i1>i2)
    return 1;
  if(first<second)
    return -1;
  return 1;
}

nomask string array get_threads(string group)
{
  return threading_data[group];
}

nomask mapping get_all_thread_data()
{
  return threading_data;
}

private nomask void update_threads(string group)
{
  threading_data[group]=sort_array(get_messages(group,1),
			    (: sort_messages_by_thread :), group);
}

private nomask void update_all_threads()
{
  threading_data=([]);
  foreach(string group in get_groups())
    {
      reset_eval_cost();
      threading_data[group]=sort_array( get_messages(group,1), 
					(: sort_messages_by_thread :), 
					group);
   }
}