/* 
   users.c -- handles:
     testing and enforcing bans and ignores
     adding and removing bans and ignores
     listing bans and ignores
     auto-linking bots
     sending and receiving a userfile from a bot
     listing users ('.whois' and '.match')
     reading the user file

   dprintf'ized, 9nov95
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include "eggdrop.h"
#include "users.h"
#include "chan.h"
#include "proto.h"

extern char botname[];
extern char botuser[];
extern char botuserhost[];
extern int serv;
extern struct dcc_t dcc[];
extern int dcc_total;
extern int noshare;
extern struct userrec *userlist,*lastuser,*banu,*ignu;
extern char cx_file[];
extern int cx_line;
extern char origbotname[];
extern char SBUF[];
extern struct chanset_t *chanset;

/* where the user records are stored */
char userfile[121]="";
/* how many minutes will bans last? */
int ban_time=60;
/* how many minutes will ignores last? */
int ignore_time=10;


/* ignores are stored as 'hostmask:timer' */
int match_ignore(user)
char *user;
{
  struct userrec *u; struct eggqueue *q; char hst[121],s[161];
  u=get_user_by_handle(userlist,IGNORE_NAME);
  if (u==NULL) return 0;
  q=u->host; while (q!=NULL) {
    strcpy(s,q->item); splitc(hst,s,':');
    if (wild_match(hst,user)) return 1;
    q=q->next;
  }
  return 0;
}

/* returns 1 if temporary ban, 2 if permban, 0 if not a ban at all */
int u_equals_ban(u,user)
struct userrec *u; char *user;
{
  struct eggqueue *q; char hst[121],s[256];
  q=u->host; while (q!=NULL) {
    strcpy(s,q->item); splitc(hst,s,':');
    if (strcasecmp(hst,user)==0) {
      if (atol(s)>0) return 1;
      else return 2;
    }
    q=q->next;
  }
  return 0;   /* not equal */
}

int equals_ban(user)
char *user;
{
  struct userrec *u;
  u=get_user_by_handle(userlist,BAN_NAME);
  if (u==NULL) return 0;
  return u_equals_ban(u,user);
}

int u_match_ban(u,user)
struct userrec *u; char *user;
{
  struct eggqueue *q; char hst[121],s[256];
  q=u->host; while (q!=NULL) {
    strcpy(s,q->item); splitc(hst,s,':');
    if (wild_match(hst,user)) return 1;
    q=q->next;
  }
  return 0;
}

int match_ban(user)
char *user;
{
  struct userrec *u;
  u=get_user_by_handle(userlist,BAN_NAME);
  if (u==NULL) return 0;
  return u_match_ban(u,user);
}

/* if any bans match this wildcard expression, refresh them on the channel */
void refresh_ban_kick(chan,user,nick)
char *user,*nick; struct chanset_t *chan;
{
  struct userrec *u; struct eggqueue *q; char hst[121],s[256],ts[21],s1[161];
  time_t expire_time,time_added=(time_t)0L,last_active=(time_t)0L; char *p;
  static char lastban[161]=""; static time_t lastbantime=(unsigned long)0L;
  int cycle=0;
  u=get_user_by_handle(userlist,BAN_NAME);
  while (u!=NULL) {
    q=u->host; while ((q!=NULL) && (strcmp(q->item,"none")!=0)) {
      strcpy(s,q->item); splitc(hst,s,':');
      if (wild_match(hst,user)) {
	/* check if this is repetitive */
	if ((strcasecmp(hst,lastban)==0) && (lastbantime >= time(NULL)-60)) {
	  /* ban was recently made -- may not have propagated yet */
	  /* or, could be a desync, which can't be solved from here */
	}
	else {
	  strcpy(lastban,hst);
	  lastbantime=time(NULL);
	  add_mode(chan,'-','o',nick);   /* guess it can't hurt */
	  add_mode(chan,'+','b',hst);
	  flush_mode(chan,QUICK);   /* do it IMMEDIATELY */
	  splitc(ts,s,':'); expire_time=(time_t)atol(ts);
	  if (s[0]=='+') {
	    /* strip off new timestamps */
	    strcpy(s,&s[1]);
	    splitc(ts,s,':'); time_added=(time_t)atol(ts);
	    splitc(ts,s,':'); last_active=(time_t)atol(ts);
	    sprintf(s1,"%s:%lu:+%lu:%lu:%s",hst,expire_time,time_added,
		    time(NULL),s);	
	    chg_q(q,s1);
	    /* (update last-active timestamp) */
	  }
	  if ((s[0]) && (strchr(s,'~')!=NULL) && (strchr(s,':')!=NULL)) {
	    /* ban reason stored */
	    splitc(NULL,s,'~');  /* crafty Fred1 method to dispose of nick */
	    p=strchr(s,'~'); while (p!=NULL) { *p=' '; p=strchr(s,'~'); }
	    p=strchr(s,'`'); while (p!=NULL) { *p=','; p=strchr(s,'`'); }
	    mprintf(serv,"KICK %s %s :banned: %s\n",chan->name,nick,s);
	  }
	  else mprintf(serv,"KICK %s %s :You are banned.\n",chan->name,nick);
	}
      }
      q=q->next;
    }
    cycle++;
    if (cycle==1) u=chan->bans;
    else u=NULL;
  }
}

int u_unprog_ban(u,who)
struct userrec *u; char *who;
{
  int i,j; struct eggqueue *q; char s[256],hst[121];
  context;
  i=0;
  if (atoi(who)) {
    j=atoi(who); q=u->host;
    while ((j>0) && (q!=NULL)) {
      if (strcmp(q->item,"none")!=0) j--;
      if (j>0) q=q->next;
    }
    if (q!=NULL) {
      strcpy(s,q->item); splitc(who,s,':');
      if (!who[0]) strcpy(who,s);
      u->host=del_q(q->item,u->host,&i);
    }
    else return j-atoi(who);
  }
  else {
    /* find matching host, if there is one */
    q=u->host; while ((q!=NULL) && (!i)) {
      strcpy(s,q->item); splitc(hst,s,':');
      if (!hst[0]) strcpy(hst,s);
      if (strcasecmp(who,hst)==0) u->host=del_q(q->item,u->host,&i);
      q=q->next;
    }
  }
  if (i) {
    if (!noshare) {
      /* distribute chan bans differently */
      if (strcasecmp(u->handle,BAN_NAME)==0)
	shareout("-ban %s\n",who);
      else
	shareout("-banchan %s %s\n",u->info,who);
    }
  }
  return i;
}

int unprog_ban(who)
char *who;
{
  struct userrec *u; int i;
  u=get_user_by_handle(userlist,BAN_NAME);
  if (u==NULL) return 0;
  i=u_unprog_ban(u,who);
  if (u->host==NULL) deluser(BAN_NAME);
  return i;
}

int unprog_ignore(who)
char *who;
{
  struct userrec *u; int i,j; struct eggqueue *q; char s[161],hst[121];
  context;
  u=get_user_by_handle(userlist,IGNORE_NAME); i=0;
  if (u==NULL) return 0;
  if (atoi(who)) {
    j=atoi(who)-1; q=u->host; 
    while (j>0) {
      if (q!=NULL) q=q->next;
      j--;
    }
    if (q!=NULL) {
      strcpy(s,q->item); splitc(who,s,':');
      u->host=del_q(q->item,u->host,&i);
    }
  }
  else {
    /* find the matching host, if there is one */
    q=u->host; while ((q!=NULL) && (!i)) {
      strcpy(s,q->item); splitc(hst,s,':'); context;
      if (strcasecmp(who,hst)==0) u->host=del_q(q->item,u->host,&i);
      q=q->next;
    }
  }
  if (i) {
    if (u->host == NULL) deluser(IGNORE_NAME);
    if (!noshare) shareout("-ignore %s\n",who);
  }
  return i;
}

void u_prog_ban(u,whom,t,note)
struct userrec *u; char *whom,*note; time_t t;
{
  char s[161],who[161],*p,oldnote[121]; time_t now=time(NULL);
  context;
  sprintf(s,"%s!%s",botname,botuserhost); strcpy(who,whom);
  /* choke check: fix broken bans (must have '!' and '@') */
  if ((strchr(who,'!')==NULL) && (strchr(who,'@')==NULL)) {
    strcat(who,"!*@*");
  }
  else if (strchr(who,'@')==NULL) {
    strcat(who,"@*");
  }
  else if (strchr(who,'!')==NULL) {
    char *p=strchr(who,'@'),s1[161];
    strcpy(s1,p); *p=0; strcat(who,"!*"); strcat(who,s1);
  }
  if (wild_match(who,s)) {
    putlog(LOG_MISC,"*","Wanted to ban myself: deflected.");
    return;
  }
  if (u_equals_ban(u,who)) u_unprog_ban(u,who);  /* remove old ban */
  /* new format: */
  sprintf(s,"%s:%lu:+%lu:%lu",who,t,now,now);
  if (note[0]) {
    strcpy(oldnote,note);
    /* remove spaces & commas */
    p=strchr(note,' '); while (p!=NULL) { *p='~'; p=strchr(note,' '); }
    p=strchr(note,','); while (p!=NULL) { *p='`'; p=strchr(note,','); }
    strcat(s,":"); strcat(s,note);
  }
  else oldnote[0]=0;
  if (t!=0) { t=(now-t); if (t==0) t=1; }
  u->host=add_q(s,u->host);
  if (!noshare) {
    if (strcasecmp(u->handle,BAN_NAME)==0)
      shareout("+ban %s %lu %s\n",who,t,oldnote);
    else
      shareout("+banchan %s %lu %s %s\n",who,t,u->info,oldnote);
  }
  strcpy(note,oldnote);
}

void prog_ban(whom,t,note)
char *whom,*note; time_t t;
{
  struct userrec *u;
  u=get_user_by_handle(userlist,BAN_NAME);
  if (u==NULL) {
    userlist=adduser(userlist,BAN_NAME,"none","-",0);
    u=get_user_by_handle(userlist,BAN_NAME);
  }
  u_prog_ban(u,whom,t,note);
}

void prog_ignore(who,t,from)
char *who; time_t t; char *from;
{
  struct userrec *u; char s[161]; time_t now=time(NULL);
  context;
  u=get_user_by_handle(userlist,IGNORE_NAME);
  sprintf(s,"%s:%lu:%s",who,t,from);
  if (t!=0) { t=(now-t); if (t==0) t=1; }
  if (u==NULL) {
    userlist=adduser(userlist,IGNORE_NAME,s,"-",0);
    if (!noshare) shareout("+ignore %s %lu\n",who,t);
    return;
  }
  u->host=add_q(s,u->host);
  if (!noshare) shareout("+ignore %s %lu\n",who,t);
}

/* bans:
   <banmask>:<expire-time>:[+<time-added>:<last-active>]:<encoded-desc>
     expire-time: timestamp when the ban was made, or 0 for permanent
     time-added: when the ban was first created
     last-active: last time ban was enforced
*/

/* grabs and translates the note from a ban (in host form) */
void getbannote(host,note)
char *host,*note;
{
  char *p;
  /* scratch off ban and timestamps */
  splitc(NULL,host,':'); splitc(NULL,host,':');
  if (host[0]=='+') {
    splitc(NULL,host,':'); splitc(NULL,host,':');
  }
  if (host[0]) {
    /* fix spaces & commas */
    p=strchr(host,'~'); while (p!=NULL) { *p=' '; p=strchr(host,'~'); }
    p=strchr(host,'`'); while (p!=NULL) { *p=','; p=strchr(host,'`'); }
  }
  strcpy(note,host);
}

/* take host entry from ban list and display it ban-style */
void display_ban(idx,number,host,chan,show_inact)
int idx,number,show_inact; char *host; struct chanset_t *chan;
{
  char ban[UHOSTLEN],ts[21],note[121],dates[81],s[41],*p; int i,j;
  time_t expire_time,time_added,last_active,now;
  now=time(NULL);
  /* split off ban and expire-time */
  splitc(ban,host,':'); splitc(ts,host,':'); expire_time=(time_t)atol(ts);
  if (host[0]=='+') {
    /* extended format */
    strcpy(host,&host[1]);
    splitc(ts,host,':'); time_added=(time_t)atol(ts);
    splitc(ts,host,':'); last_active=(time_t)atol(ts);
    daysago(now,time_added,note);
    sprintf(dates,"Created %s",note);
    if (time_added < last_active) {
      strcat(dates,", last used ");
      daysago(now,last_active,note);
      strcat(dates,note);
    }
  }
  else { time_added=(time_t)0L; last_active=(time_t)0L; dates[0]=0; }
  strcpy(note,host);
  if (expire_time==0) strcpy(s,"(perm)");
  else {
    i=now-expire_time; j=(i/60); i=i%60; s[0]=0;
    sprintf(s,"(active %d:%02d)",j,i);
  }
  if (note[0]) {
    /* fix spaces & commas */
    p=strchr(note,'~'); while (p!=NULL) { *p=' '; p=strchr(note,'~'); }
    p=strchr(note,'`'); while (p!=NULL) { *p=','; p=strchr(note,'`'); }
  }
  if ((chan==NULL) || (isbanned(chan,ban))) {
    if (number>=0) dprintf(idx,"  [%3d] %s %s\n",number,ban,s);
    else dprintf(idx,"  [BAN] %s %s\n",ban,s);
    dprintf(idx,"        %s\n",note);
    if (dates[0]) dprintf(idx,"        %s\n",dates);
  }
  else if (show_inact) {
    if (number>=0) dprintf(idx,"! [%3d] %s %s\n",number,ban,s);
    else dprintf(idx,"! [BAN] %s %s\n",ban,s);
    dprintf(idx,"        %s\n",note);
    if (dates[0]) dprintf(idx,"        %s\n",dates);
  }
}

void tell_bans(idx,show_inact,match)
int idx,show_inact; char *match;
{
  struct userrec *u; struct eggqueue *q; int k=1,cycle;
  char s[256],hst[UHOSTLEN],note[121]; struct chanset_t *chan;

  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) chan=chanset;   /* pick arbitrary channel to view */
  if (chan==NULL) return;  /* i give up then. */
  if (show_inact)
    dprintf(idx,"Global bans:   (! = not active on %s)\n",chan->name);
  else dprintf(idx,"Global bans:\n");
  u=get_user_by_handle(userlist,BAN_NAME); cycle=0;
  while (u!=NULL) {
    if (cycle==1) {
      if (show_inact)
	dprintf(idx,"Channel bans for %s:   (! = not active, * = not placed by bot)\n",
		chan->name);
      else dprintf(idx,"Channel bans for %s:  (* = not placed by bot)\n",
		   chan->name);
    }
    q=u->host; while ((q!=NULL) && (strcasecmp(q->item,"none")!=0)) {
      strcpy(s,q->item); getbannote(s,note);
      splitc(hst,s,':'); strcpy(s,q->item);
      if (match[0]) {
	if ((wild_match(match,hst)) || (wild_match(match,note)))
	  display_ban(idx,k,s,chan,1);
	k++;
      }
      else display_ban(idx,k++,s,chan,show_inact);
      q=q->next;
    }
    if (cycle==0) { u=chan->bans; cycle++; }
    else u=NULL;
  }
  tell_chanbans(chan,idx,k,match);
  if ((!show_inact) && (!match[0]))
    dprintf(idx,"Use '.bans all' to see the total list.\n");
}

/* list the ignores and how long they've been active */
void tell_ignores(idx)
int idx;
{
  struct userrec *u; struct eggqueue *q; int i,j,k=1; time_t tt,ti;
  char s[161],hst[121],s1[81];
  u=get_user_by_handle(userlist,IGNORE_NAME); tt=time(NULL);
  if (u==NULL) {
    dprintf(idx,"No ignores.\n");
    return;
  }
  q=u->host;
  if (q==NULL) dprintf(idx,"No ignores.\n");
  dprintf(idx,"Currently ignoring:\n");
  while (q!=NULL) {
    strcpy(s,q->item); splitc(hst,s,':'); ti=atol(s);
    if (ti==0) strcpy(s1,"(perm)");
    else {
      i=tt-ti; j=(i/60); i=i%60; s1[0]=0;
      sprintf(s1,"(active %d:%02d)",j,i);
    }
    splitc(NULL,s,':');
    dprintf(idx,"  [%2d] (%-9s) %s %s\n",k++,s,hst,s1);
    q=q->next;
  }
}

/* check for expired timed-ignores */
void check_expired_ignores()
{
  struct userrec *u; struct eggqueue *q; char s[161],hst[121]; time_t tt,ti;
  context;
  u=get_user_by_handle(userlist,IGNORE_NAME); tt=time(NULL);
  if (u==NULL) return;
  q=u->host; if (q==NULL) return;
  while (q!=NULL) {
    strcpy(s,q->item); splitc(hst,s,':'); ti=atol(s);
    if (ti!=0L) if (tt-ti >= 60*ignore_time) {
      /* expired */
      putlog(LOG_MISC,"*","No longer ignoring %s (automatically expired)",hst);
      unprog_ignore(hst); u=get_user_by_handle(userlist,IGNORE_NAME);
      if (u!=NULL) q=u->host;  /* start over, check for more */
    }
    if ((u!=NULL) && (q!=NULL)) q=q->next; else q=NULL;
  }
}

/* check for expired timed-bans */
void check_expired_bans()
{
  struct userrec *u; struct eggqueue *q; char s[256],hst[121]; time_t tt,ti;
  char note[41]; struct chanset_t *chan;
  context;
  u=get_user_by_handle(userlist,BAN_NAME); tt=time(NULL); if (u==NULL) return;
  q=u->host; if (q==NULL) return;
  while (q!=NULL) {
    strcpy(s,q->item); splitc(hst,s,':');
    splitc(note,s,':'); ti=atoi(note);
    if ((ti!=0L) && (tt-ti >= 60*ban_time)) {
      /* expired: 1 hr later */
      putlog(LOG_MISC,"*","No longer banning %s (automatically expired)",hst);
      chan=chanset; while (chan!=NULL) {
	add_mode(chan,'-','b',hst); chan=chan->next;
      }
      unprog_ban(hst); u=get_user_by_handle(userlist,BAN_NAME);
      if (u!=NULL) q=u->host;  /* start over, check for more */
      else q=NULL;
    }
    else q=q->next;
  }
  /* check for specific channel-domain bans expiring */
  chan=chanset; while (chan!=NULL) {
    q=chan->bans->host; while ((q!=NULL) && (strcmp(q->item,"none")!=0)) {
      strcpy(s,q->item); splitc(hst,s,':');
      splitc(note,s,':'); ti=atoi(note);
      if ((ti!=0L) && (tt-ti >= 60*ban_time)) {
	putlog(LOG_MISC,chan->name,"No longer banning %s on %s (expired)",
	       hst,chan->name);
	add_mode(chan,'-','b',hst);
	u_unprog_ban(chan->bans,hst); q=chan->bans->host;
      }
      else q=q->next;
    }
    chan=chan->next;
  }
}

/* see if there are any bots that need to be linked */
/* priority is: +sh (highest), +h, +a (lowest) */
/* only link ONE +h bot at a time */
/* only link ONE +a bot at a time, and only if the +h are unreachable,
   and then keep trying for the +h bot(s) */
void auto_link_tandem(start)
char *start;
{
  struct userrec *u=userlist,*autc=NULL; static int cycle=0;
  int got_hub=0,got_alt=0,linked,ready=0,i;
  context;
  if (start==NULL) { ready=1; cycle=0; }   /* new run through the user list */
  while (u!=NULL) {
    if ((flags_eq(USER_BOT|BOT_HUB,u->flags)) ||
	(flags_eq(USER_BOT|BOT_ALT,u->flags))) {
      linked=0;
      for (i=0; i<dcc_total; i++) {
	if (strcasecmp(dcc[i].nick,u->handle)==0) {
	  if (dcc[i].type==DCC_BOT) linked=1;
	  if (dcc[i].type==DCC_BOT_NEW) linked=1;
	  if (dcc[i].type==DCC_FORK) if (dcc[i].u.fork->type==DCC_BOT)
	    linked=1;
	}
      }
      /* check for possibility that share-bot IS linked in... but not to me */
      if ((!linked) && (in_chain(u->handle)) && (u->flags&BOT_SHARE) &&
	  (strcasecmp(origbotname,u->handle)!=0)) {
	int q=nextbot(u->handle);
	tprintf(dcc[q].sock,"unlink %s %s %s\n",origbotname,lastbot(u->handle),
		u->handle);
	/* once unlinking is done, i'll try to autoconnect later */
      }
      /* is this a HUB we have linked? */
      if ((u->flags&BOT_HUB) && (linked)) {
	got_hub=1;
	if ((autc!=NULL) && (autc->flags&BOT_HUB) &&
	    !(autc->flags&BOT_SHARE)) {
	  /* great... already have a hub bot in line to be linked */
	  /* start over from there */
	  u=autc; linked=1; autc=NULL;
	}
      }
      /* is this an ALT we have linked? */
      if ((u->flags&BOT_ALT) && (linked)) got_alt=1;
      /* don't link anything if we have our +h bot */
      if ((got_hub) && !(u->flags&BOT_SHARE)) linked=1;
      /* if we've got an alt linked, don't link another one */
      if ((got_alt) && (u->flags&BOT_ALT) && !(u->flags&BOT_SHARE)) linked=1;
      if ((!linked) && (u->flags&BOT_ALT) && (in_chain(u->handle))) linked=1;
      if ((!linked) && (ready)) {
	if (autc==NULL) autc=u;
	else if ((u->flags&BOT_HUB) && !(autc->flags&BOT_HUB) && (cycle==0))
	  autc=u;  /* hub priority over alt (except 2nd cycle) */
	else if ((u->flags&BOT_SHARE) && !(autc->flags&BOT_SHARE))
	  autc=u;  /* share bots get priority */
      }
      /* did we make it where we're supposed to start?  yay! */
      if (!ready) if (strcasecmp(u->handle,start)==0) { ready=1; autc=NULL; }
    }
    if ((u->flags&USER_BOT) && (u->flags&BOT_REJECT))
      if (in_chain(u->handle)) {
	/* get rid of nasty reject bot */
	reject_bot(u->handle);
      }
    u=u->next;
    if ((u==NULL) && (autc==NULL) && (cycle==0)) {
      /* end of 1st run of userlist -- no succeses */
      /* try again, this time go for +a */
      cycle++; u=userlist;
    }
  }
  if ((got_hub) && (cycle==0)) autc=NULL;
  if (((got_hub) || (got_alt)) && (cycle==1)) autc=NULL;
  if (autc!=NULL) tandem_link("",-1,autc->handle);   /* try autoconnect */
}

/* erase old user list, switch to new one */
void finish_share(idx)
int idx;
{
  struct userrec *u; int i,j=0;
  for (i=0; i<dcc_total; i++)
    if ((strcasecmp(dcc[i].nick,dcc[idx].host)==0) &&
	(dcc[i].type==DCC_BOT))
      j=i;
  if (j==0) return;   /* oh well. */
  dcc[j].u.bot->status&=~STAT_GETTING;
  /* copy the bots over */
  u=dup_userlist(1);
  /* read the rest in */
  if (!readuserfile(dcc[idx].u.xfer->filename,&u)) {
    putlog(LOG_MISC,"*","CAN'T READ NEW USERFILE");
    return;
  }
  reaffirm_owners();   /* make sure my owners are +n */
  unlink(dcc[idx].u.xfer->filename);   /* done with you! */
  putlog(LOG_MISC,"*","Userlist transfer complete; switched over.");
  clear_userlist(userlist);
  userlist=u;
  clear_chanlist();
  lastuser=banu=ignu=NULL;
}

/* begin the user transfer process */
void start_sending_users(idx)
int idx;
{
  struct userrec *u; char s[121]; int i;
  context;
  sprintf(s,".share.user%lu",time(NULL));
  u=dup_userlist(0);  /* only non-bots */
  write_tmp_userfile(s,u);
  clear_userlist(u);
  i=raw_dcc_send(s,"*users","(users)",s);
  if (i>0) {   /* abort */
    unlink(s);
    tprintf(dcc[idx].sock,"error Can't send userfile to you (internal error)\n");
    dcc[idx].u.bot->status&=~STAT_SHARE;
    return;
  }
  dcc[idx].u.bot->status|=STAT_SENDING;
  i=dcc_total-1;
  strcpy(dcc[i].host,dcc[idx].nick);  /* store bot's nick */
  tprintf(dcc[idx].sock,"ufsend %lu %d %lu\n",ntohl(getmyip()),dcc[i].port,
	  dcc[i].u.xfer->length);
  /* start up a tbuf to queue outgoing changes for this bot until the */
  /* userlist is done transferring */
  new_tbuf(dcc[idx].nick);
  /* wish could unlink the file here to avoid possibly leaving it lying */
  /* around, but that messes up NFS clients. */
}

/* update a user's last signon, by host */
void update_laston(host)
char *host;
{
  struct userrec *u;
  u=get_user_by_host(host);
  if (u==NULL) return;
  u->laston=time(NULL);
}

/* return laston time */
void get_handle_laston(nick,n)
char *nick; time_t *n;
{
  struct userrec *u;
  u=get_user_by_handle(userlist,nick);
  if (u==NULL) *n=0L;
  else *n = u->laston;
}

void set_handle_laston(nick,n)
char *nick; time_t n;
{
  struct userrec *u;
  u=get_user_by_handle(userlist,nick);
  if (u==NULL) return;
  u->laston=n;
}

void set_handle_laston2(bu,nick,n)
struct userrec *bu; char *nick; time_t n;
{
  struct userrec *u;
  u=get_user_by_handle(bu,nick);
  if (u==NULL) return;
  u->laston=n;
}

/* since i was getting a ban list, i assume i'm chop */
/* recheck_bans makes sure that all who are 'banned' on the userlist are
   actually in fact banned on the channel */
void recheck_bans(chan)
struct chanset_t *chan;
{
  struct userrec *u; struct eggqueue *q; char s[256],hst[121]; int i;
  context;
  if (chan->stat&CHAN_DYNAMICBANS) return;
  for (i=0; i<2; i++) {
    if (i==0) u=get_user_by_handle(userlist,BAN_NAME);
    else u=chan->bans;
    if (u!=NULL) {
      q=u->host; while ((q!=NULL) && (strcmp(q->item,"none")!=0)) {
	strcpy(s,q->item); splitc(hst,s,':');
	if (!hst[0]) strcpy(hst,s);
	if (!isbanned(chan,hst)) add_mode(chan,'+','b',hst);
	q=q->next;
      }
    }
  }
}

/* find info line for a user and display it if there is one */
void showinfo(chan,who,nick)
char *who,*nick; struct chanset_t *chan;
{
  char s[121];
  if (get_attr_handle(who) & USER_BOT) return;
  get_handle_info(who,s);
  if (s[0]=='@') strcpy(s,&s[1]);
  if (s[0]) mprintf(serv,"PRIVMSG %s :[%s] %s\n",chan->name,nick,s);
}

void tell_user(idx,u,master)
int idx; struct userrec *u; int master;
{
  char s[81],s1[81]; struct eggqueue *q; time_t now; int n;
  if (strcmp(u->handle,BAN_NAME)==0) return;
  if (strcmp(u->handle,IGNORE_NAME)==0) return;
  flags2str(u->flags,s);
  n=num_notes(u->handle);
  if (u->laston == 0L) strcpy(s1,"never");
  else {
    now=time(NULL)-(u->laston); strcpy(s1,ctime(&(u->laston)));
    if (now>86400) { s1[7]=0; strcpy(&s1[11],&s1[4]); strcpy(s1,&s1[8]); }
    else { s1[16]=0; strcpy(s1,&s1[11]); }
  }
  dprintf(idx,"%-10s%-5s%-25s%5d %s\n",u->handle,u->pass[0]=='-'?"no":"yes",
	  s,n,s1);
  s[0]=0; q=u->host;
  while (q!=NULL) {
    if (!s[0]) sprintf(s,"          %s",q->item);
    else {
      if (strlen(s)+strlen(q->item)+2>60) {
	dprintf(idx,"%s\n",s);
	sprintf(s,"          %s",q->item);
      }
      else {
	strcat(s,", "); strcat(s,q->item);
      }
    }
    q=q->next;
  }
  if (s[0]) dprintf(idx,"%s\n",s);
  if ((master) && (u->comment!=NULL))
    dprintf(idx,"          COMMENT: %s\n",u->comment);
  if (u->email!=NULL) dprintf(idx,"          EMAIL: %s\n",u->email);
  if (u->flags&USER_BOT) {
    if (u->info!=NULL) dprintf(idx,"          ADDRESS: %s\n",u->info);
  }
  else {
    if (u->info!=NULL) dprintf(idx,"          INFO: %s\n",u->info);
  }
}

/* show user by ident */
void tell_user_ident(idx,id,master)
int idx; char *id; int master;
{
  struct userrec *u;
  u=get_user_by_handle(userlist,id);
  if (u==NULL) u=get_user_by_host(id);
  if (u==NULL) {
    dprintf(idx,"Can't find anyone matching that.\n");
    return;
  }
  dprintf(idx,"HANDLE    PASS FLAGS                    NOTES LAST\n");
  tell_user(idx,u,master);
}

/* match string: wildcard to match nickname or hostmasks */
/*               +attr to find all with attr */
void tell_users_match(idx,mtch,start,limit,master)
int idx; char *mtch; int start,limit,master;
{
  struct userrec *u=userlist; int fnd=0,cnt; struct eggqueue *q;
  char s[UHOSTLEN];
  dprintf(idx,"*** Matching '%s':\n",mtch); cnt=0;
  dprintf(idx,"HANDLE    PASS FLAGS                    NOTES LAST\n");
  if (start>1) dprintf(idx,"(skipping first %d)\n",start-1);
  while (u!=NULL) {
    if (mtch[0]=='+') {
      fnd=str2flags(&mtch[1]);
      if (((u->flags&fnd)==fnd) && (fnd!=0)) {
	cnt++;
	if ((cnt<=limit) && (cnt>=start)) tell_user(idx,u,master);
	if (cnt==limit+1)
	  dprintf(idx,"(more than %d matches; list truncated)\n",limit);
      }
    }
    else if (wild_match(mtch,u->handle)) {
      cnt++;
      if ((cnt<=limit) && (cnt>=start)) tell_user(idx,u,master);
      if (cnt==limit+1)
	dprintf(idx,"(more than %d matches; list truncated)\n",limit);
    }
    else {
      fnd=0; q=u->host; while (q!=NULL) {
	if ((wild_match(mtch,q->item)) && (!fnd)) {
	  cnt++; fnd=1;
	  if ((cnt<=limit) && (cnt>=start)) {
	    if (strcmp(u->handle,BAN_NAME)==0) {
	      strcpy(s,q->item);
	      display_ban(idx,-1,s,NULL,1);
	    }
	    else if (strcmp(u->handle,IGNORE_NAME)==0) {
	      dprintf(idx,"[ignore list] -> use '.ignores'\n");
	    }
	    else tell_user(idx,u,master);
	  }
	  if (cnt==limit+1)
	    dprintf(idx,"(more than %d matches; list truncated)\n",limit);
	}
	q=q->next;
      }
    }
    u=u->next;
  }
  dprintf(idx,"--- Found %d match%s.\n",cnt,cnt==1?"":"es");
}

/*
   tagged lines in the user file:
   #  (comment)
   ;  (comment)
   -  hostmask(s)
   +  email
   *  dcc directory
   =  comment
   :  info line
   .  xtra (Tcl)
   !  channel-specific
*/
      
int readuserfile(file,ret)
char *file; struct userrec **ret;
{
  char *p,s[181],lasthand[181],host[181],attr[181],pass[181],code[181];
  FILE *f; unsigned int flags; struct userrec *bu; int convpw=0;
  char ignored[512];
  context;
  bu=(*ret); ignored[0]=0;
  if (bu==userlist) {
    clear_chanlist(); lastuser=banu=ignu=NULL;
  }
  lasthand[0]=0; f=fopen(file,"r");
  if (f==NULL) return 0;
  noshare=1;
  context;
  /* read opening comment */
  fgets(s,180,f);
  if (s[1]<'2') {
    convpw=1;
    putlog(LOG_MISC,"*","* Old userfile (unencrypted passwords)");
    putlog(LOG_MISC,"*","* Encrypting as I load ...");
  }
  if (s[1]>'2') fatal("Don't understand userfile encoding!");
  while (!feof(f)) {
    fgets(s,180,f);
    if (!feof(f)) {
      rmspace(s);
      if ((s[0]!='#') && (s[0]!=';') && (s[0])) {
	nsplit(code,s); rmspace(code); rmspace(s);
	if (strcasecmp(code,"-")==0) {
	  if (lasthand[0]) {
	    p=strchr(s,','); while (p!=NULL) {
	      splitc(code,s,','); rmspace(code); rmspace(s);
	      if (code[0]) addhost_by_handle2(bu,lasthand,code);
	      p=strchr(s,',');
	    }
	    /* channel bans are never stacked with , */
	    if (s[0]) {
	      if ((lasthand[0]=='#') || (lasthand[0]=='+'))
		restore_chanban(lasthand,s);
	      else addhost_by_handle2(bu,lasthand,s);
	    }
	  }
	}
	else if (strcasecmp(code,"+")==0) {
	  if (lasthand[0]) {
	    set_handle_email(bu,lasthand,s);
	  }
	}
	else if (strcasecmp(code,"*")==0) {
	  if (lasthand[0]) {
	    set_handle_dccdir(bu,lasthand,s);
	  }
	}
	else if (strcasecmp(code,"=")==0) {
	  if (lasthand[0]) {
	    set_handle_comment(bu,lasthand,s);
	  }
	}
	else if (strcasecmp(code,":")==0) {
	  if (lasthand[0]) {
	    set_handle_info(bu,lasthand,s);
	  }
	}
	else if (strcasecmp(code,".")==0) {
	  if (lasthand[0]) {
	    add_handle_xtra(bu,lasthand,s);
	  }
	}
	else if (strcasecmp(code,"!")==0) {
	  /* ! #chan laston flags */
	  /* not implemented yet */
	  putlog(LOG_MISC,"*","* Channel-specific user data for '%s'! %s",
		 lasthand,"(not supported yet)");
	}
	else if (strncmp(code,"::",2)==0) {
	  /* channel-specific bans */
	  strcpy(lasthand,&code[2]);
	  if (!defined_channel(lasthand)) {
	    strcat(ignored,lasthand); strcat(ignored," ");
	    lasthand[0]=0;
	  }
	}
	else {
	  if (convpw) {
	    nsplit(host,s); rmspace(host); rmspace(s);  /* unused */
	  }
	  nsplit(pass,s); rmspace(pass); rmspace(s);
	  nsplit(attr,s); rmspace(attr); rmspace(s);
	  if ((!s[0]) || (!attr[0]) || (!pass[0]) ||
	      ((!host[0]) && convpw)) {
	    putlog(LOG_MISC,"*","* Corrupt user record '%s'!",code);
	    lasthand[0]=0;
	  }
	  else if (is_user2(bu,code)) {
	    putlog(LOG_MISC,"*","* Duplicate user record '%s'!",code);
	    lasthand[0]=0;
	  }
	  else {
	    flags=str2flags(attr); strcpy(lasthand,code);
	    if (convpw) {
	      if (strcasecmp(host,"$placeholder$")==0) host[0]=0;
	      if (strcasecmp(host,"none")==0) host[0]=0;
	    }
	    else host[0]=0;
	    if (strlen(code)>9) code[9]=0;
	    if (strlen(pass)>20) {
	      putlog(LOG_MISC,"*","* Corrupted password for %s; reset.",
		     code);
	      strcpy(pass,"-");
	    }
	    if (convpw) {
	      if (strcmp(pass,"nopass")==0) strcpy(pass,"-");
	      else if (!(flags & USER_BOT)) encrypt_pass(pass,pass);
	    }
	    bu=adduser(bu,code,host,pass,flags);
	    set_handle_laston2(bu,code,atol(s));
	  }
	}
      }
    }
  }
  noshare=0;
  context;
  fclose(f);
  (*ret)=bu;
  if (ignored[0]) {
    putlog(LOG_MISC,"*","Ignored bans for channel(s): %s",ignored);
  }
  return 1;
}
