{*********************************************************
 SlavaNap source code.

 Copyright 2001,2002 by SlavaNap development team
 Released under GNU General Public License

 Latest version is available at
 http://www.slavanap.org

**********************************************************

 Unit: handler

 Handlers for all client-server and server-server messages

*********************************************************}
unit handler;

interface

uses
 SysUtils, Classes2, graphics, zlibex, winsock, windows, constants, users,
 servers, stypes, blcksock, synsock, localusers, registered, slavastrings,
 class_cmdlist, class_cmd2list, class_cmdexlist, mmsystem, ips, keywords,
 keywords2, share2, blocks;

function ProcessCommand(usr: TLocalUser; q: TQuery=queryNormal): Boolean;
procedure ProcessServerCommand(srv: TServer);
procedure DisconnectUser(usr: TLocalUser; reason, text, sender: String;close_socket: Boolean);
procedure KickUser(user: POnlineUser;msg: String);
procedure DisconnectServer(srv: TServer; inform_servers, do_wallop: Boolean; sender: String);
procedure Exec(usr: POnlineUser; id: Integer; cmd: String);
procedure WriteAllServersEx(srv: TServer;id: Integer; sender,cmd: String);
function WriteAllServers(id: Integer; sender,cmd: String; ignored: TServer=nil): Integer;
function FormatString(user: POnlineUser; str: String; strip_quotes: Boolean): String;
procedure Wallop(id: Integer; wid: TWallopType; cmd: String; local_only: Boolean);
procedure Error(str: String; check_permission: Boolean=false);
procedure PermissionDenied(func: String; check_permission: Boolean=false);
procedure CountStats;
procedure CompleteSyncUser(srv: TServer; user: POnlineUser);
procedure Handler_JoinChannel;
procedure UpdateUser(user: POnlineUser; ignored: TServer=nil);
procedure Handler_ServerConnect(srv: TServer; timer: Boolean);
procedure BanUser(ban,server: String;time: Integer; reason: String; write_all: Boolean);
procedure AddReconnector(ip: String);
procedure DoCloseSocket(s: TSocket);
//function FindLocalUser(data: POnlineUser): TLocalUser;
function FindLocalUser(nick: String): TLocalUser;
function CheckWinMX(usr: TLocalUser): Boolean;
function isLogged: Boolean;
function CheckLevel(func: String;lev: TNapUserLevel): Boolean;
function CheckParams(count: Integer; show_error: Boolean=true): Boolean;
procedure Handler_MsgServ;

var
 gcmd: TNapCmd;
 query: TQuery;
 user: POnlineUser;
 server: TServer;

implementation

{$I defines.pas}

uses
 lang, vars, share, thread, channels, bans, config, md5, memory_manager, browseform;

var
 hlist,hlst: TMyStringList;
 local: TLocalUser;
 query_channel: String;
 search_data: TSearchStruct;
 search_data_old: TSearchStruct;
 compressed: Boolean;

function FindLocalUser(nick: String): TLocalUser;
var
 i, len: Integer;
begin
 Result:=nil;
 nick:=AnsiLowerCase(nick);
 len:=Length(nick);
 try
   if (local<>nil) and (AnsiLowerCase(local.nick)=nick) then Result:=local
   else for i:=0 to db_local.Count-1 do
   if Length(TLocalUser(db_local.Items[i]).nick)=len then
   if AnsiLowerCase(TLocalUser(db_local.Items[i]).nick)=nick then
   begin
    Result:=db_local.Items[i];
    exit;
   end;
  except
 end
end;

procedure Exec(usr: POnlineUser; id: Integer; cmd: String);
begin
 if usr=nil then exit;
 try
   if usr^.server=nil then
    TLocalUser(usr.local).Exec(id,cmd)
   else
    usr^.server.Exec(MSG_CLIENT_RELAY,IntToStr(id)+' '+usr^.username+' '+cmd);
  except
 end;  
end;

function CanSendError(usr: POnlineUser;q: TQuery): Boolean;
begin
 case q of
  queryServer: Result:=false;
  queryOperServ, queryChanServ, queryNickServ, queryMsgServ, queryChannel: Result:=true;
  else if usr=nil then Result:=true
       else Result:=not (userHideErrors in usr^.state);
 end;
end;

procedure Error(str: String; check_permission: Boolean=false);
begin
 if user=nil then exit;
 if check_permission then
  if not CanSendError(user,query) then exit;
 case query of
   queryOperServ:  Exec(user,MSG_SERVER_PRIVMSG,'OperServ '+str);
   queryNickServ:  Exec(user,MSG_SERVER_PRIVMSG,'NickServ '+str);
   queryChanServ:  Exec(user,MSG_SERVER_PRIVMSG,'ChanServ '+str);
   queryMsgServ:   Exec(user,MSG_SERVER_PRIVMSG,'MsgServ '+str);
   queryChannel:   Exec(user,MSG_SERVER_PUBLIC,query_channel+' Server '+str);
   queryRemoteUser,
   queryServer: begin end;
   else Exec(user,MSG_SERVER_NOSUCH,str);
 end;
end;

procedure PermissionDenied(func: String; check_permission: Boolean=false);
begin
 if func='' then Error('アクセス拒否',check_permission)
 else Error(func+': permission denied',check_permission);
end;

procedure UserIsOffline(str: String; check_permission: Boolean=false);
begin
 Error(GetLangT(LNG_OFFLINE2,str),check_permission);
end;

procedure NoSuchUser(check_permission: Boolean=false);
begin
 Error(GetLangT(LNG_NOUSER),check_permission);
end;

function CheckRange(num: String; min,max: Integer): Boolean;
var
 n: Integer;
begin
 n:=StrToIntDef(num,min-1);
 Result:=(n>=min) and (n<=max);
end;

function CheckRangeEx(num: String; min,max: Integer): Boolean;
var
 n: Integer;
begin
 n:=StrToIntDef(num,min-1);
 Result:=(n>=min) and (n<=max);
 if Result=false then
  Error(GetLangT(LNG_INVALIDARGS),true);
end;

function CheckParams(count: Integer; show_error: Boolean=true): Boolean;
begin
 SplitString(gcmd.cmd,hlist);
 if hlist.Count<count then
 begin
  Result:=false;
  if query<>queryServer then
   if show_error then
    Error(GetLangT(LNG_INVALIDARGS),true);
 end
 else
  Result:=true;
end;

function CheckLevel(func: String;lev: TNapUserLevel): Boolean;
begin
 if user=nil then
 begin
   Result:=false;
   exit;
 end;
 if user^.level<lev then
 begin
   Result:=false;
   PermissionDenied(func,true);
 end
 else
   Result:=true;
end;

function isLogged: Boolean;
begin
 Result:=user<>nil;
end;

function isLocal: Boolean;
begin
 Result:=local<>nil;
end;

procedure AddSoftware(str: String);
var
 i, crc: Integer;
 str1: String;
begin
  str:=trim(str);
  str1:=lowercase(str);
  crc:=StringCRC(str1,true);
  if db_software=nil then exit;
  for i:=0 to db_software.Count-1 do
  if PNapCmd2(db_software.Items[i])^.id2=crc then
   if lowercase(PNapCmd2(db_software.Items[i])^.cmd)=str1 then
   begin
    inc(PNapCmd2(db_software.Items[i])^.id1);
    exit;
   end;
  if db_software.count>MAX_SOFTWARE_INDEX then
  begin
    i:=0;
    while i<db_software.count do
    begin
     if PNapCmd2(db_software.Items[i])^.id1<2 then db_software.Delete(i)
     else inc(i);
    end;
    exit;
  end;
  if db_software.count>MAX_SOFTWARE_INDEX then exit;
  db_software.AddCmd(1,crc,str);
end;

procedure BlockCQEXChat(var str: String);
begin
  if pos('<',str)<1 then exit;
  if pos('>',str)<1 then exit;
  ReplaceString(str,'<b>','');
  ReplaceString(str,'</b>','');
  ReplaceString(str,'<c>','');
  ReplaceString(str,'</c>','');
  ReplaceString(str,'<u>','');
  ReplaceString(str,'</u>','');
  ReplaceString(str,'<inverse>','');
  ReplaceString(str,'</inverse>','');
  ReplaceString(str,'<underline>','');
  ReplaceString(str,'</underline>','');
  ReplaceString(str,'<bold>','');
  ReplaceString(str,'</bold>','');
  ReplaceString(str,'<white>','');
  ReplaceString(str,'<black>','');
  ReplaceString(str,'<navy>','');
  ReplaceString(str,'<green>','');
  ReplaceString(str,'<red>','');
  ReplaceString(str,'<maroon>','');
  ReplaceString(str,'<purple>','');
  ReplaceString(str,'<orange>','');
  ReplaceString(str,'<yellow>','');
  ReplaceString(str,'<lime>','');
  ReplaceString(str,'<teal>','');
  ReplaceString(str,'<aqua>','');
  ReplaceString(str,'<blue>','');
  ReplaceString(str,'<fuchsia>','');
  ReplaceString(str,'<gray>','');
  ReplaceString(str,'<silver>','');
end;

function CheckWinMX(usr: TLocalUser): Boolean;
// detects winmx. returns false if handler should be interrupted
begin
 Result:=true;
 if usr=nil then exit;
 if usr.data=nil then exit; // not logged in
 case usr.softwareID of
   softWinMXNormal,softWinMXJap,softWinMXHidden,softNapchan: exit;
 end;
 if locPingable in usr.localstate then exit;
 // potential WinMX with changed signature detected
 usr.softwareID:=softWinMXHidden;
 inc(mx_users);
 if blocked_clients[softWinMXHidden] then
  if usr.data^.level<napUserModerator then
   if not StrHash_FindString(db_friends,usr.data^.username,true) then
   begin
     usr.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_BLOCKEDWINMX));
     if ban_fakemx then
      BanUser(decode_ip(usr.ip),servername_t,mx_ban,GetLangT(LNG_REASON_WINMXBAN,serveralias),true);
     DisconnectUser(usr,'',GetLangT(LNG_DISCONNECT_WINMX,usr.nick,usr.software),'CheckWinMX',false);
     Result:=false;
     exit;
   end;
end;

function WriteAllServers(id: Integer; sender,cmd: String; ignored: TServer=nil): Integer;
var
 i,num: Integer;
 srv: TServer;
 str: String;
begin
 tmp_pos:=100;
 if sender='' then
  str:=cmd
 else if cmd<>'' then
  str:=sender+' '+cmd
 else
  str:=sender;
 str:=IntToStr(id)+' '+IntToStr(myserverhandle)+' '+str;
 num:=0;
 ignored:=GetServerLink(ignored);
 tmp_pos:=101;
 for i:=0 to db_servers.Count-1 do
 begin
   srv:=db_servers.Items[i];
   if srv<>ignored then
   if srv.logged then
   if srv.hub=nil then
   begin
    srv.Exec(MSG_SRV_FORWARDALL,str);
    inc(num);
   end;
 end;
 Result:=num;
 tmp_pos:=102;
end;

procedure WriteAllServersEx(srv: TServer;id: Integer; sender,cmd: String);
var
 str: String;
begin
 tmp_pos:=110;
 if sender='' then
  str:=cmd
 else if cmd<>'' then
  str:=sender+' '+cmd
 else
  str:=sender;
 str:=IntToStr(id)+' '+IntToStr(myserverhandle)+' '+str;
 tmp_pos:=111;
 srv:=GetServerLink(srv);
 tmp_pos:=112;
 if srv.logged then
 if srv.hub=nil then
  srv.Exec(MSG_SRV_FORWARDALL,str);
 tmp_pos:=113; 
end;

procedure Handler_Stats;
begin
 if user<>nil then
  Exec(user,MSG_SERVER_STATS,IntToStr(total_users)+' '+IntToStr(total_files)+' '+IntToStr(total_bytes div 1073741824)); // +' GB '+IntToStr(user^.shared)
end;

procedure Wallop(id: Integer; wid: TWallopType; cmd: String; local_only: Boolean);
var
 i: Integer;
 user2: TLocalUser;
 b: Boolean;
begin
 tmp_pos:=120;
 for i:=0 to db_local.count-1 do
 begin
   user2:=db_local.Items[i];
   if user2.logged then
    if user2.data^.level>napUserUser then
    begin
     b:=false;
     case wid of
       wallopWallop:   b:=userHideWallop in user2.data^.state;
       wallopMBan:     b:=userHideMBans in user2.data^.state;
       wallopSBan:     b:=userHideSBans in user2.data^.state;
       wallopMBanConn: b:=userHideMBanConn in user2.data^.state;
       wallopSBanConn: b:=userHideSBanConn in user2.data^.state;
       wallopChange:   b:=userHideChange in user2.data^.state;
       wallopKill:     b:=userHideKill in user2.data^.state;
       wallopLevel:    b:=userHideLevel in user2.data^.state;
       wallopServer:   b:=userHideServer in user2.data^.state;
       wallopMuzzle:   b:=userHideMuzzle in user2.data^.state;
       wallopPort:     b:=userHidePort in user2.data^.state;
       wallopCloak:    b:=userHideCloak in user2.data^.state;
       wallopFlood:    b:=userHideFlood in user2.data^.state;
       wallopPing:     b:=userHidePing in user2.data^.state;
       wallopWhois:    b:=userHideWhois in user2.data^.state;
       wallopFriends:  b:=userHideFriends in user2.data^.state;
       wallopAnnouncement: b:=userHideAnnouncements in user2.data^.state;
       wallopChannel:  b:=userHideChannel in user2.data^.state;
       wallopRegister: b:=userHideRegister in user2.data^.state;
       wallopVar:      b:=userHideVar in user2.data^.state;
       wallopMotd:     b:=userHideMotd in user2.data^.state;
       wallopLeech:    b:=userHideLeech in user2.data^.state;
     end;
     if not b then
      user2.Exec(id,cmd);
    end;
 end;
 tmp_pos:=121;
 if not local_only then WriteAllServers(MSG_SRV_WALLOP,IntToStr(id)+' '+IntToStr(Ord(wid)),cmd);
end;

procedure RegisterUser(user: POnlineUser; setby: String);
var
 reg: TRegisteredUser;
 preg: PRegisteredUser;
begin
 reg.nick:=user^.username;
 reg.password:=user^.password;
 reg.level:=user^.level;
 reg.downloads:=user^.total_down;
 reg.uploads:=user^.total_up;
 reg.last_ip:=user^.ip;
 reg.last_seen:=current_time_t;
 reg.state:=user^.state - [userChatting];
 preg:=db_registered.FindUser(user^.username);
 if preg<>nil then
 begin
  reg.created:=preg.created;
  reg.createdby:=preg.createdby;
  if setby<>'server' then reg.lastsetby:=setby;
  db_registered.Delete(user^.username);
 end else
 begin
  reg.created:=current_time_t;
  reg.createdby:=setby;
 end;
 db_registered.Add(reg);
end;

procedure UserDisconnected(user: POnlineUser);
var
 str: String;
 i: Integer;
 cmd: TNapCmdEx;
begin
 tmp_pos:=1253;
 str:=AnsiLowerCase(user^.username);
 i:=db_whowas.FindByCmd(str);
 if i<>-1 then db_whowas.Delete(i);
 tmp_pos:=1254;
 cmd.cmd:=str;
 cmd.data:=AddStr(user^.username)+' '+AddStr(Level2Str(user^.level))+' '+IntToStr(current_time_t)+' '+
    IntToStr(user^.ip)+' '+AddStr(GetServerName(user^.server))+' '+AddStr(user^.software);
 tmp_pos:=1255;
 cmd.id:=user^.ip;
 tmp_pos:=1256;
 db_whowas.Add(cmd);
end;

procedure UserOnline(usr: TLocalUser; mail: String='');
var
 i: Integer;
 user2: TLocalUser;
 l: TNapUserLevel;
 str: String;
begin
 tmp_pos:=130;
 if usr.data=nil then exit;
 usr.last_seen:=current_time;
 usr.data^.last_seen_t:=current_time_t;
 CompleteSyncUser(nil,usr.data);
 {$I checksync.pas}
 tmp_pos:=131;
 if running and log_login then
 begin
  if mail='' then
   begin
   log(slOnline,GetLangT(LNG_USERONLINE,usr.nick,usr.software,decode_ip(usr.ip)));
   if StrHash_FindString(db_friends,usr.nick,true) then
   begin
    Wallop(MSG_SERVER_NOSUCH,wallopFriends,GetLangT(LNG_FRIENDLOGIN,user^.username,usr.software,decode_ip(usr.ip),str),true);
    if friend_sound then PlaySound(PChar(ApplicationDir+'friendlogin.wav'), 0, SND_FILENAME or SND_ASYNC);
    end;
   end
  else
   log(slOnline,GetLangT(LNG_USERREGISTER,usr.nick,usr.software,decode_ip(usr.ip),mail));
 end;
 tmp_pos:=132;
 inc(users_per_minute);
 if usr.level>napUserUser then
 begin
   usr.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_LEVEL,servername_t,Level2Str(usr.level),Ord(usr.level)));
   l:=usr.level;
   usr.data^.level:=napUserUser; // to avoid sending this message to usr
   Wallop(MSG_SERVER_NOSUCH,wallopLevel,GetLangT(LNG_LEVEL1,servername_t,usr.nick,Level2Str(l),IntToStr(Ord(l))),false);
   usr.data^.level:=l;
 end;
 if usr.level=napUserLeech then Wallop(MSG_SERVER_NOSUCH,wallopLeech,GetLangT(LNG_LEVEL1,servername_t,usr.nick,Level2Str(usr.level),IntToStr(Ord(usr.level))),false);
 usr.Exec(MSG_SERVER_PING,'server');
 if (usr.SoftwareID=softWinMXNormal) or (usr.SoftwareID=softWinMXJap) or (usr.SoftwareID=softWinMXHidden) then inc(mx_users);
 tmp_pos:=133;
 inc(local_users);
 local_users_max:=max(local_users,local_users_max);
 inc(total_connections);
 inc(total_users);
 if total_users>total_users_max then total_users_max:=total_users;
 tmp_pos:=134;
 for i:=0 to db_local.count-1 do
 begin
  user2:=db_local.Items[i];
  if user2<>usr then
  begin
   str:=StrHash_FindStringEx(user2.hotlist,usr.nick,true);
   if str<>'' then
    user2.Exec(MSG_SERVER_USER_SIGNON,str+' '+IntToStr(Ord(usr.data^.speed)));
  end;
 end;
 tmp_pos:=135;
 usr.localstate:=usr.localstate-[locNeedsUpdate];
 Handler_Stats;
 if userCloaked in usr.data^.state then
 begin
  local.Exec(MSG_SERVER_NOSUCH,'You are now cloaked.');
  Wallop(MSG_SERVER_NOSUCH,wallopCloak,GetLangT(LNG_CLOAKED,local.nick),false);
  if user^.server=nil then WriteAllServers(MSG_CLIENT_CLOAK,local.nick,'1');
 end;
end;

procedure UserOffline(usr: TLocalUser; reason: String; off_message: String; sender: String);
var
 preg: PRegisteredUser;
 i: Integer;
 b,cloaked: Boolean;
 user2: TLocalUser;
 ch: TChannel;
 str: String;
begin
 try
   tmp_pos:=140;
   if usr=cons then
    if running then
     exit;
   tmp_pos:=141;
   if (usr.data<>nil) and (usr.nick<>'') then
   begin
    dec(total_users);
    dec(local_users);
    if (usr.SoftwareID=softWinMXNormal) or (usr.softwareID=softWinMXJap) or (usr.softwareID=softWinMXHidden) then dec(mx_users);
    tmp_pos:=1250;
    UserDisconnected(usr.data);
    tmp_pos:=1251;
    WriteAllServers(MSG_SRV_USEROFFLINE,usr.nick,'');
    tmp_pos:=142;
    if db_channels<>nil then
    for i:=db_channels.count-1 downto 0 do
    begin
      ch:=db_channels.Items[i];
      if ch.FindUser(usr.data)<>-1 then
      begin
       ch.Part(usr.data);
       if ch.users.count=0 then
        if not (chRegistered in ch.state) then
        begin
         db_channels.Delete(i);
         ch.Free;
        end;
      end;
    end;
    cloaked:=userCloaked in usr.data^.state;
    tmp_pos:=143;
    if usr.shared<>nil then
    begin
      tmp_pos:=1204;
      dec(local_files,usr.data^.shared);
      if nocount_text then dec(local_files,usr.shared_text);
      dec(local_bytes,usr.shared_size);
      dec(total_files,usr.data^.shared);
      if nocount_text then dec(total_files,usr.shared_text);
      dec(total_bytes,usr.shared_size);
      tmp_pos:=1205;
      usr.shared_size:=0;
      usr.shared_mp3:=0;
      usr.shared_audio:=0;
      usr.shared_video:=0;
      usr.shared_images:=0;
      usr.shared_apps:=0;
      usr.shared_text:=0;
      usr.shared_cd:=0;
      usr.shared_blocked:=0;
      tmp_pos:=1206;
      usr.shared.Clear;
      tmp_pos:=1207;
      usr.shared.Free;
      tmp_pos:=1208;
      usr.shared:=nil;
    end;
    tmp_pos:=144;
    b:=registered_only;
    if b=false then
    begin
      if userMuzzled in usr.data^.state then b:=true;
      if usr.level<>napUserUser then b:=true;
    end;
    preg:=db_registered.FindUser(usr.nick);
    if preg<>nil then b:=true;
    if usr.data^.level=napUserConsole then
    begin
     if preg<>nil then db_registered.Delete(usr.data^.username);
     b:=false;
    end;
    tmp_pos:=145;
    if b then RegisterUser(usr.data,'server');
    tmp_pos:=146;
    if running and log_login then
    begin
     if off_message<>'' then
      log(slOffline,off_message)
     else
     begin
       if reason='' then
        log(slOffline,GetLangT(LNG_USEROFFLINE2,usr.nick,usr.software))
       else
        log(slOffline,GetLangT(LNG_USEROFFLINE,usr.nick,usr.software,reason,sender));
     end;
    end;
    tmp_pos:=147;
    for i:=0 to db_local.count-1 do
    begin
     user2:=db_local.Items[i];
     if user2.logged then
     if user2<>usr then
     if (not cloaked) or (user2.level>napUserUser) then
     begin
      str:=StrHash_FindStringEx(user2.hotlist,usr.nick,true);
      if str<>'' then
       user2.Exec(MSG_SERVER_USER_SIGNOFF,str);
     end;
    end;
    tmp_pos:=148;
    StrHash_Clear(usr.hotlist);
    StrHash_Clear(usr.ignored);
   end;
   tmp_pos:=149;
   if usr.data<>nil then
    db_online.DeleteRecord(usr.data);
  except
   on E:Exception do
    DebugLog('Exception in UserOffline (pos='+IntToStr(tmp_pos)+') : '+E.Message);
  end;
end;

procedure DisconnectUser(usr: TLocalUser; reason, text, sender: String;close_socket: Boolean);
begin
 tmp_pos:=150;
 if usr=cons then
 begin
   DebugLog('Warning: someone tried to kick console user. (DisconnectUser arguments: reason="'+reason+'" text="'+text+'" sender="'+sender+'" cmd.id='+IntToStr(gcmd.id)+' cmd.cmd="'+gcmd.cmd+'" query='+IntToStr(Ord(query))+')');
   exit;
 end;
 tmp_pos:=151;
 UserOffline(usr, reason, text, 'DisconnectUser-'+sender);
 tmp_pos:=152;
 if usr.socket<>INVALID_SOCKET then
 begin
   if close_socket then
   begin
     synsock.Shutdown(usr.socket,SD_BOTH);
     synsock.closesocket(usr.socket);
     dec(sockets_count);
   end
   else
   begin
     usr.Flush(false);
     DoCloseSocket(usr.socket);
   end;
   usr.socket:=INVALID_SOCKET;
 end;
 usr.last_seen:=0;
 tmp_pos:=153;
end;

procedure KickUser(user: POnlineUser;msg: String);
var
 preg: PRegisteredUser;
 i: Integer;
 b: Boolean;
 user2: TLocalUser;
 ch: TChannel;
 str: String;
begin
 tmp_pos:=160;
 if user=nil then exit;
 if user^.server=nil then
 begin
   user2:=user^.local;
   if user2=nil then exit;
   if msg<>'' then
    user2.Exec(MSG_SERVER_NOSUCH,msg);
   DisconnectUser(user2,msg,'','KickUser',false);
   exit;
 end;
 tmp_pos:=161;
 UserDisconnected(user);
 if db_channels<>nil then
 for i:=db_channels.count-1 downto 0 do
 begin
  ch:=db_channels.Items[i];
  if ch.FindUser(user)<>-1 then
  begin
   ch.Part(user);
   if ch.users.count=0 then
    if not (chRegistered in ch.state) then
    begin
     db_channels.Delete(i);
     ch.Free;
    end;
  end;
 end;
 tmp_pos:=162;
 b:=registered_only;
 if b=false then
 begin
   if userMuzzled in user^.state then b:=true;
   if user^.level<>napUserUser then b:=true;
 end;
 tmp_pos:=163;
 preg:=db_registered.FindUser(user^.username);
 if preg<>nil then b:=true;
 if user^.level=napUserConsole then
 begin
   b:=false;
   db_registered.Delete(user^.username);
 end;
 if b then RegisterUser(user,'server');
 tmp_pos:=164;
 for i:=0 to db_local.count-1 do
 begin
   user2:=db_local.Items[i];
   str:=StrHash_FindStringEx(user2.hotlist,user^.username,true);
   if str<>'' then
    user2.Exec(MSG_SERVER_USER_SIGNOFF,str);
 end;
 tmp_pos:=165;
 db_online.DeleteRecord(user);
 tmp_pos:=166;
end;

function FormatString(user: POnlineUser; str: String; strip_quotes: Boolean): String;
begin
 // variables:
 //
 // $user$       - user's name
 // $server$     - server's name (server user is connected to)
 // $level$      - user's level
 // $levelnum$   - user's level as digit (0..4)
 // $client$     - user's software
 // $speed$      - user's connection speed
 // $minshare$   - minimum shared files
 // $maxshare$   - maximum shared files
 // $minsharesize$ - minimum MB to share
 // $maxusers$   - user's limit
 // $maxusers_total$ - total user's limit (on all linked servers)
 // $version$    - server version
 // $build$      - server build
 // $console$    - console user's name
 // $users$      - number of users online
 // $users_local$ - number of local users
 // $files$      - total number of shared files (by all users)
 // $files_local$ - number of shared files only on this server
 // $files_ex$   - total number of shared files (by all users), but thousands are separated by dots (example: 12.345.678 instead of 12345678)
 // $files_local_ex$
 // $gb$         - size of shared files in Gb. (by all users)
 // $gb_ex$      - size of shared files in Gb using dots
 // $gb_local$   - size of shared files on this server
 // $gb_local_ex$ -
 // $servers$    - number of linked servers
 // $connections$ - number of connections
 // $memory$     - number of bytes used by application
 // $memory_ex$  - same as memory, but thousands are separated by dot.
 // $avgfiles$   - avg # of shared files/user (by all users)
 // $avggig$     - avg # of shared gb/user (by all users)
 // $avgfiles_local$ - avg # of shared files/user (by local users)
 // $avggig_local$ - avg # of shared gb/user (by local users)
 // $transfers$
 // $searches$

 tmp_pos:=170;
 if strip_quotes then
  ReplaceString(str,'"','`');
 // $user$
 if pos('$user$',str)>0 then
 if user<>nil then
  ReplaceString(str,'$user$',user^.username)
 else
  ReplaceString(str,'$user$','<unknown>');
 // $server$
 if pos('$server$',str)>0 then
  if user=nil then
   ReplaceString(str,'$server$',serveralias)
  else
   ReplaceString(str,'$server$',GetServerAlias(user^.server));
 // $level$
 if pos('$level$',str)>0 then
 if user<>nil then
  ReplaceString(str,'$level$',Level2Str(user^.level))
 else
  ReplaceString(str,'$level$','<unknown>');
 // $levelnum$
 if pos('$levelnum$',str)>0 then
 if user<>nil then
  ReplaceString(str,'$levelnum$',IntToStr(Ord(user^.level)))
 else
  ReplaceString(str,'$levelnum$','<unknown>');
 tmp_pos:=171;
 // $client$
 if pos('$client$',str)>0 then
 if user<>nil then
  ReplaceString(str,'$client$',user^.software)
 else
  ReplaceString(str,'$client$','<unknown>');
 // $speed$
 if pos('$speed$',str)>0 then
 if user<>nil then
  ReplaceString(str,'$speed$',Speed2Str(user^.speed))
 else
  ReplaceString(str,'$speed$','<unknown>');
 // $minshare$
 if pos('$minshare$',str)>0 then
  ReplaceString(str,'$minshare$',IntToStr(minshare));
 // $minsharesize$
 if pos('$minsharesize$',str)>0 then
  ReplaceString(str,'$minsharesize$',IntToStr(minshare_size div MegaByte)+' MB');
 // $maxshare$
 if pos('$maxshare$',str)>0 then
  ReplaceString(str,'$maxshare$',IntToStr(maxshare));
 // $maxusers$
 if pos('$maxusers$',str)>0 then
  ReplaceString(str,'$maxusers$',IntToStr(max_users));
 // $maxusers_total$
 if pos('$maxusers_total$',str)>0 then
  ReplaceString(str,'$maxusers_total$',IntToStr(total_users_limit));
 tmp_pos:=172;
 // $version$
 if pos('$version$',str)>0 then
  ReplaceString(str,'$version$',SLAVANAP_VERSION_SHORT);
 // $build$
 if pos('$build$',str)>0 then
  ReplaceString(str,'$build$',SLAVANAP_BUILD);
 // $console$
 if pos('$console$',str)>0 then
  ReplaceString(str,'$console$',cons.data^.username);
 // $users$
 if pos('$users$',str)>0 then
  ReplaceString(str,'$users$',IntToStr(total_users));
 // $users_local$
 if pos('$users_local$',str)>0 then
  ReplaceString(str,'$users_local$',IntToStr(local_users));
 // $files$
 if pos('$files$',str)>0 then
  ReplaceString(str,'$files$',IntToStr(total_files));
 // $files_local$
 if pos('$files_local$',str)>0 then
  ReplaceString(str,'$files_local$',IntToStr(local_files));
 // $gb$
 if pos('$gb$',str)>0 then
  ReplaceString(str,'$gb$',IntToStr(total_bytes div 1073741824));
 // $gb_local$
 if pos('$gb_local$',str)>0 then
  ReplaceString(str,'$gb_local$',IntToStr(local_bytes div 1073741824));
 // $files_ex$
 if pos('$files_ex$',str)>0 then
  ReplaceString(str,'$files_ex$',IntToStrDot(total_files));
 // $files_local_ex$
 if pos('$files_local_ex$',str)>0 then
  ReplaceString(str,'$files_local_ex$',IntToStrDot(local_files));
 // $gb_ex$
 if pos('$gb_ex$',str)>0 then
  ReplaceString(str,'$gb_ex$',IntToStrDot(total_bytes div 1073741824));
 // $gb_local_ex$
 if pos('$gb_local_ex$',str)>0 then
  ReplaceString(str,'$gb_local_ex$',IntToStrDot(local_bytes div 1073741824));
 tmp_pos:=173;
 // $servers$
 if pos('$servers$',str)>0 then
  ReplaceString(str,'$servers$',IntToStr(num_servers));
 // $connections$
 if pos('$connections$',str)>0 then
  ReplaceString(str,'$connections$',IntToStr(total_connections));
 // $memory$
 if pos('$memory$',str)>0 then
  ReplaceString(str,'$memory$',IntToStr(AllocMemSize));
 // $memory_ex$
 if pos('$memory_ex$',str)>0 then
  ReplaceString(str,'$memory_ex$',IntToStrDot(AllocMemSize));
 // $avgfiles$
 if pos('$avgfiles_local$',str)>0 then
  ReplaceString(str,'$avgfiles_local$',IntToStrDot(local_files div local_users));
 // $avggig$
 if pos('$avggig_local$',str)>0 then
  ReplaceString(str,'$avggig_local$',IntToStrDot((local_bytes div GigaByte) div local_users));
 // $avgfiles$
 if pos('$avgfiles$',str)>0 then
  ReplaceString(str,'$avgfiles$',IntToStrDot(total_files div total_users));
 // $avggig$
 if pos('$avggig$',str)>0 then
  ReplaceString(str,'$avggig$',IntToStrDot((total_bytes div GigaByte) div total_users));
 if pos('$transfers$',str)>0 then
  ReplaceString(str,'$transfers$',IntToStrDot(total_transfers+num_transfers));
 if pos('$searches$',str)>0 then
  ReplaceString(str,'$searches$',IntToStrDot(total_searches+num_searches));
 Result:=str;
 tmp_pos:=174;
end;

{* * * * * handlers * * * * *}

procedure Handler_Motd;
var
 str: String;
 t: PStringHashItem;
begin
 tmp_pos:=180;
 if local=nil then exit;
 if query<>queryNormal then exit;
 if user=nil then exit;
 if userHideMotd in user^.state then exit;
 //
 // !!!!!  DO NOT translate or modify the following
 // strings - some clients use it to detect server version
 //
 local.Exec(MSG_SERVER_MOTD,'VERSION SlavaNap '+SLAVANAP_VERSION+'.'+SLAVANAP_BUILD+
                            '.'+SLAVANAP_SHUUSEI+' 非公式日本語バージョン');
 local.Exec(MSG_SERVER_MOTD,'SERVER '+serveralias);
 //{$IFDEF }
// local.Exec(MSG_SERVER_MOTD,'このサーバーにはのべ'+IntToStr(total_connections)+'回の接続がありました。');
// {$ENDIF}
 tmp_pos:=181;
 t:=db_motd.first;
 while t<>nil do
 try
   str:=FormatString(user,t^.data,false);
   if (Length(str)>0) and (str[1]=';') then
    // do nothing
   else
    local.Exec(MSG_SERVER_MOTD,str);
   t:=t^.next;
  except
   on E:Exception do
   begin
    DebugLog('Exception in Handler_Motd : '+E.Message);
    exit;
   end;
 end;
 tmp_pos:=182;
end;

procedure AddReconnector(ip: String);
begin
 if not reconnect_delay then exit;
 if GetIndex(db_reconnect,ip,false)=-1 then
  db_reconnect.Add(ip);
end;

procedure DoCloseSocket(s: TSocket);
begin
 if s=INVALID_SOCKET then exit;
 if not running then
 begin // if slavanap isn't running - close socket regardless of waiting data
  synsock.shutdown(s,SD_BOTH);
  synsock.closesocket(s);
  dec(sockets_count);
  exit;
 end;
 db_closed.AddCmd(current_time,s,'');
end;

procedure LoginError(str: String);
begin
 if local=nil then exit;
 AddReconnector(decode_ip(local.ip));
 local.Exec(MSG_SERVER_ERROR,str);
 DisconnectUser(local,'','','LoginError',false);
end;

procedure RedirectUser(def: String);
var
 srv: TServer;
 i,j,k: Integer;
begin
 if local=nil then exit;
 if num_servers<1 then
 begin
   LoginError(def);
   exit;
 end;
 AddReconnector(decode_ip(local.ip));
 k:=0;
 for i:=0 to db_servers.Count-1 do
 begin
   srv:=db_servers.Items[i];
   if srv.logged then
    if srv.num_users<(srv.max_users-10) then
     inc(k,srv.max_users-srv.num_users);
 end;
 if k=0 then
 begin
   LoginError(def);
   exit;
 end;
 k:=random(k); // selecting random server
 j:=0;
 for i:=0 to db_servers.Count-1 do
 begin
   srv:=db_servers.Items[i];
   if srv.logged then
    if srv.num_users<(srv.max_users-10) then
    begin
      inc(j,srv.max_users-srv.num_users);
      if j>=k then
      begin
       local.Exec(MSG_CLIENT_REDIRECT,srv.host+' '+IntToStr(srv.port));
       LoginError(def);
       exit;
      end;
    end;
 end;
 LoginError(def);
end;

procedure Handler_Login;
var
 rec: TOnlineUser;
 user2: POnlineUser;
 reg: PRegisteredUser;
 str, bandata, tempnick, loginims: String;
 i: Integer;
 //sin: TSockAddrIn;
 ban: PBan;
 b: Boolean;
begin
 tmp_pos:=190;
 if local=nil then exit;
 if (user<>nil) or (local.socket=INVALID_SOCKET) then
 begin
   LoginError(GetLangT(LNG_LOGGED));
   exit;
 end;
 if query<>queryNormal then
 begin
   LoginError('アクセス拒否');
   exit;
 end;
 SplitString(gcmd.cmd,hlist);
 if hlist.Count<5 then
 begin
   LoginError(GetLangT(LNG_INVALIDARGS));
   exit;
 end;
 tmp_pos:=191;
 if not CheckRange(hlist.Strings[4],0,10) then
 begin
   LoginError(GetLangT(LNG_INVSPEED));
   exit;
 end;
 tempnick:=hlist.Strings[0];
 if not check_name(tempnick) then
 begin
   LoginError(GetLangT(LNG_INVALIDNICK2,tempnick));
   exit;
 end;
 if IsDigit(tempnick) then
 begin
   LoginError(GetLangT(LNG_INVALIDNICK));
   exit;
 end;
 if not check_software(hlist.Strings[3]) then
 begin
   LoginError(GetLangT(LNG_INVALIDSOFTWARE));
   exit;
 end;
 if check_loginpass and (hlist.Strings[1]<>loginpass) then
 if db_registered.FindUser(tempnick)=nil then
 begin
   LoginError('そのパスワードではこのサーバーにログインできません。');
   exit;
 end;
 if not check_port(StrToIntDef(hlist.Strings[2],-1)) then
 begin
   LoginError('当サーバではポート'+hlist.Strings[2]+'の使用は禁止しています。');
   If (log_login) then
     Log(slDebugData,'申告ポート'+hlist.Strings[2]+'のため、'
       +hlist.Strings[0]+'のログインをブロックしました.');
   exit;
 end;
 tmp_pos:=12262;
 for i:=0 to MAX_LISTEN_TRAPSOCKET do
 begin
   if trap_port[i]=0 then continue;
   if TCPSocket_GetLocalSin(local.socket).sin_port
     <>TCPSocket_GetLocalSin(trap_socket[i]).sin_port then continue;
   LoginError('不正なポートにアクセスしようとしています');
   BanUser(tempnick,servername_t,7*86400,'不正なポートへのアクセス ID:'+tempnick,true);
   exit;
 end;
 tmp_pos:=192;
 local.SoftwareId:=GetSoftware(hlist.Strings[3]);
 ResetOnlineRec(rec);
 tmp_pos:=193;
 rec.username:=tempnick;
 rec.namecrc:=StringCRC(rec.username,true);
 rec.ip:=CheckIP(local.socket);
 rec.dataport:=StrToIntDef(hlist.Strings[2],0);
 rec.software:=Copy(trim(hlist.Strings[3]),1,max_software);
 rec.local:=local;
 tmp_pos:=194;
 bandata:=JoinBan(rec.username,decode_ip(rec.ip));
 user2:=db_online.FindUser(rec.username);
 if user2<>nil then
 begin
   if user2.level<napUserModerator then
    Exec(user2,MSG_SERVER_GHOST,'')
   else 
    if not (userHideSameNic in user2^.state) then
     Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_SAMENIC,rec.software,decode_ip(rec.ip)));
   LoginError(GetLangT(LNG_GHOST));
   exit;
 end;
 tmp_pos:=195;
 reg:=db_registered.FindUser(rec.username);
 if (reg=nil) or ((reg<>nil) and (reg^.level<napUserModerator)) then
 if not StrHash_FindString(db_friends,rec.username,true) then
// if not StrHash_FindString(db_friends,decode_ip(rec.ip),false) then
 begin
   tmp_pos:=196;
   if reconnect_delay then
   if GetIndex(db_reconnect,decode_ip(rec.ip),false)<>-1 then
   begin // reconnector
     RedirectUser(GetLangT(LNG_RECONNECT));
     local.localstate:=local.localstate+[locWriteOnly];
     exit;
   end;
   if network_hub then
   begin
     //if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser('アクセス拒否')
     LoginError('アクセス拒否');
     exit;
   end;
   if linking then
   begin
     if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_LINKING))
     else LoginError(GetLangT(LNG_LINKING));
     exit;
   end;
   if bandwidth_limited then
   begin
     if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_BANDWIDTHLIMIT))
     else LoginError(GetLangT(LNG_BANDWIDTHLIMIT));
     inc(num_rejects);
     exit;
   end;
   if local_users>=max_users then
   begin
     if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_USERSLIMIT,local_users))
     else LoginError(GetLangT(LNG_USERSLIMIT,local_users));
     inc(num_rejects);
     exit;
   end;
   if (memory_limit>0) and win98 then
    if AllocMemSize>memory_limit then
    begin
      if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_MEMORYLIMIT))
      else LoginError(GetLangT(LNG_MEMORYLIMIT));
      inc(num_rejects);
      exit;
    end;
   if max_users_per_minute>0 then
   if users_per_minute>=max_users_per_minute then
   begin
     if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_USERSLIMIT,local_users))
     else LoginError(GetLangT(LNG_USERSLIMIT,local_users));
     inc(num_rejects);
     exit;
   end;
   tmp_pos:=197;
   if db_bans.Banned(rec.username,decode_ip(rec.ip),ban) then
   begin
     if banmail<>'' then LoginError(GetLangT(LNG_BANNED3,banmail,ban^.reason))
     else LoginError(GetLangT(LNG_BANNED2,ban^.reason));
     if Copy(ban^.admin,1,7)='server:' then
      Wallop(MSG_SERVER_NOSUCH,wallopSBanConn,GetLangEx('$1からの接続: $2はBanされています: $3',bandata,JoinBan(ban^.user,ban^.ip),ban^.reason),true)
     else
      Wallop(MSG_SERVER_NOSUCH,wallopMBanConn,GetLangEx('$1からの接続: $2はBanされています: $3',bandata,JoinBan(ban^.user,ban^.ip),ban^.reason),true);
     WriteAllServers(MSG_SRV_UPDATEBAN,'',rec.username+' '+decode_ip(rec.ip));
     inc(ban^.tries);
     ban^.lastattempt:=GetTickCountT;
     if ban^.ip='*' then
      ban^.using:=decode_ip(rec.ip)
     else
      ban^.using:=rec.username;
     exit;
   end;
   if registered_only then
   if db_registered.FindUser(rec.username)=nil then
   begin
     LoginError(GetLangT(LNG_REGISTEREDONLY));
     exit;
   end;
   tmp_pos:=198;
   str:=lowercase(rec.software);
   b:=true;
   for i:=0 to max_custom_block do
     if blocked_custom[i]<>'' then
     if Copy(str,1,Length(blocked_custom[i]))=blocked_custom[i] then
     begin
       b:=false;
       break;
     end;
   tmp_pos:=199;
   if blocked_clients[local.SoftwareID] then b:=false;
   if not b then
   begin
     case blocked_messagetype of
      blckNone:   begin
                    AddReconnector(decode_ip(local.ip));
                    DisconnectUser(local,'','','LoginError',false);
                  end;
      blckCustom: LoginError(FormatString(@rec,blocked_message,false))
      else LoginError(GetLangT(LNG_CLIENTBLOCK,hlist.Strings[3]));
     end;
     exit;
   end;
   if ((local.softwareID=softWinMXNormal) or (local.softwareID=softWinMXJap)) and limit_mx then
   if (mx_users*100 div max_users)>=quota_mx then
   begin
     loginError(GetLangT(LNG_TOOMUCHMX,quota_mx));
     exit;
   end;
   tmp_pos:=200;
   if max_clones>0 then
   if db_online.CountClones(rec.ip,false)>=max_clones then
   begin
     LoginError(GetLangT(LNG_MAXCLONES));
     exit;
   end;
  end;
 tmp_pos:=201;
 rec.password:=encode(hlist.Strings[1]);
 if not db_registered.PasswordOk(rec.username,rec.password) then
 begin
   LoginError(GetLangT(LNG_INVALIDPASS));
   exit;
 end;
 if TCPSocket_GetRemoteSin(local.socket).sin_addr.S_addr<>TCPSocket_GetLocalSin(local.socket).sin_addr.S_addr then
   AddSoftware(rec.software);
 tmp_pos:=202;
 rec.speed:=TNapSpeed(StrToIntDef(hlist.Strings[4],0));
 if reg<>nil then
 begin
   rec.total_up:=reg^.uploads;
   rec.total_down:=reg^.downloads;
   rec.level:=reg^.level;
   rec.state:=reg^.state;
   if userChatting in rec.state then
    rec.state:=rec.state-[userChatting];
   if userCloaked in rec.state then
    if rec.level<napUserModerator then
     rec.state:=rec.state-[userCloaked];
   if userMuzzled in rec.state then
    if rec.level>napUserUser then
     rec.state:=rec.state-[userMuzzled];  
 end;
 if network_hub and (rec.level<napUserAdmin) then
 begin
   LoginError('アクセス拒否');
   exit;
 end;
 tmp_pos:=203;
 rec.local:=local;
 local.data:=db_online.AddUser(rec);
 user:=local.data;
 local.Exec(MSG_SERVER_EMAIL,'anon@'+serveralias);
 inc(num_login);
 tmp_pos:=204;
 Handler_Motd;
 UserOnline(local);
 tmp_pos:=12263;
 if enable_loginim then
 begin
   if Pos('WinMX',local.software)<>0 then
   begin
     for i:=0 to max_loginim do
       if loginim[i]<>'' then
         loginims:=loginims+loginim[i]+#13#10;
     local.Exec(MSG_CLIENT_PRIVMSG,loginimbot+' '+loginims);
   end
   else
     for i:=0 to max_loginim do
       if loginim[i]<>'' then
         local.Exec(MSG_CLIENT_PRIVMSG,loginimbot+' '+loginim[i]);
 end;
 tmp_pos:=12264;
 if enable_msgserv then
 begin
   gcmd.cmd:='msgserv read';
   Handler_MsgServ;
 end;
 tmp_pos:=12265;
 if force_enter then
 begin
   local.join_delay := INITIAL_JOIN_DELAY;
   db_login.Add(local);
 end;
 tmp_pos:=205;
 local.Flush;
end;

procedure Handler_LoginRegister;
var
 rec: TOnlineUser;
 user2: POnlineUser;
 str, bandata: String;
 i: Integer;
 ban: PBan;
 b: Boolean;
begin
 tmp_pos:=210;
 if local=nil then exit;
 if (user<>nil) or (local.socket=INVALID_SOCKET) then
 begin
   LoginError(GetLangT(LNG_LOGGED));
   exit;
 end;
 if query<>queryNormal then
 begin
   LoginError(GetLangT(LNG_ACCESS));
   exit;
 end;
 if registered_only then
 if not allow_register then
 begin
   LoginError(GetLangT(LNG_REGISTEREDONLY));
   exit;
 end;
 tmp_pos:=211;
 SplitString(gcmd.cmd,hlist);
 if hlist.Count<5 then
 begin
   LoginError(GetLangT(LNG_INVALIDARGS));
   exit;
 end;
 if not CheckRange(hlist.Strings[4],0,10) then
 begin
   LoginError(GetLangT(LNG_INVSPEED));
   exit;
 end;
 if not check_name(hlist.Strings[0]) then
 begin
   LoginError(GetLangT(LNG_INVALIDNICK2,hlist.Strings[0]));
   exit;
 end;
 if not check_software(hlist.Strings[3]) then
 begin
   LoginError(GetLangT(LNG_INVALIDSOFTWARE));
   exit;
 end;
 tmp_pos:=212;
 local.SoftwareId:=GetSoftware(hlist.Strings[3]);
 ResetOnlineRec(rec);
 rec.username:=hlist.Strings[0];
 rec.namecrc:=StringCRC(rec.username,true);
 //sin:=TCPSocket_GetRemoteSin(local.socket);
 //rec.ip:=sin.sin_addr.S_addr;
 rec.ip:=CheckIP(local.socket);
 rec.password:=encode(hlist.Strings[1]);
 rec.dataport:=StrToIntDef(hlist.Strings[2],0);
 rec.software:=Copy(trim(hlist.Strings[3]),1,max_software);
 rec.speed:=TNapSpeed(StrToIntDef(hlist.Strings[4],0));
 tmp_pos:=213;
 bandata:=JoinBan(rec.username,decode_ip(rec.ip));
 user2:=db_online.FindUser(rec.username);
 if user2<>nil then
 begin
   Exec(user2,MSG_SERVER_GHOST,'');
   LoginError(GetLangT(LNG_GHOST));
   exit;
 end;
 if reconnect_delay then
 if GetIndex(db_reconnect,decode_ip(rec.ip),false)<>-1 then
 begin // reconnector
   RedirectUser(GetLangT(LNG_RECONNECT));
   local.localstate:=local.localstate+[locWriteOnly];
   exit;
 end;
 if db_registered.FindUser(rec.username)<>nil then
 begin
   LoginError(GetLangT(LNG_REGISTERED));
   exit;
 end;
 tmp_pos:=214;
 if network_hub then
 begin
   if redirect_cqex then RedirectUser(GetLangT(LNG_ACCESS))
   else LoginError(GetLangT(LNG_ACCESS));
   exit;
 end;
 if linking then
 begin
   if redirect_cqex then RedirectUser(GetLangT(LNG_LINKING))
   else LoginError(GetLangT(LNG_LINKING));
   exit;
 end;
 if bandwidth_limited then
 begin
   if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_BANDWIDTHLIMIT))
   else LoginError(GetLangT(LNG_BANDWIDTHLIMIT));
   inc(num_rejects);
   exit;
 end;
 if local_users>=max_users then
 begin
   if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_USERSLIMIT,local_users))
   else LoginError(GetLangT(LNG_USERSLIMIT,local_users));
   inc(num_rejects);
   exit;
 end;
 if (memory_limit>0) and win98 then
  if AllocMemSize>memory_limit then
  begin
    if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_MEMORYLIMIT))
    else LoginError(GetLangT(LNG_MEMORYLIMIT));
    inc(num_rejects);
    exit;
  end;
 if max_users_per_minute>0 then
 if users_per_minute>=max_users_per_minute then
 begin
   if redirect_cqex and (local.softwareID=softCQEX) then RedirectUser(GetLangT(LNG_USERSLIMIT,local_users))
   else LoginError(GetLangT(LNG_USERSLIMIT,local_users));
   inc(num_rejects);
   exit;
 end;
 tmp_pos:=215;
 if db_bans.Banned(rec.username,decode_ip(rec.ip),ban) then
 begin
   if banmail<>'' then LoginError(GetLangT(LNG_BANNED3,banmail,ban^.reason))
   else LoginError(GetLangT(LNG_BANNED2,ban^.reason));
   if Copy(ban^.admin,1,7)='server:' then
    Wallop(MSG_SERVER_NOSUCH,wallopSBanConn,GetLangEx('$1からの接続: $2はBanされています: $3',bandata,JoinBan(ban^.user,ban^.ip),ban^.reason),true)
   else
    Wallop(MSG_SERVER_NOSUCH,wallopMBanConn,GetLangEx('$1からの接続: $2はBanされています: $3',bandata,JoinBan(ban^.user,ban^.ip),ban^.reason),true);
   WriteAllServers(MSG_SRV_UPDATEBAN,'',rec.username+' '+decode_ip(rec.ip));
   inc(ban^.tries);
   ban^.lastattempt:=GetTickCountT;
   if ban^.ip='*' then
    ban^.using:=decode_ip(rec.ip)
   else
    ban^.using:=rec.username;
   exit;
 end;
 if max_clones>0 then
  if db_online.CountClones(rec.ip,false)>=max_clones then
  begin
    LoginError(GetLangT(LNG_MAXCLONES));
    exit;
  end;
 tmp_pos:=216;
 str:=lowercase(rec.software);
 b:=true;
 for i:=0 to max_custom_block do
   if blocked_custom[i]<>'' then
   if Copy(str,1,Length(blocked_custom[i]))=blocked_custom[i] then
   begin
     b:=false;
     break;
   end;
 tmp_pos:=199;
 if blocked_clients[local.SoftwareID] then b:=false;
 if not b then
 begin
   case blocked_messagetype of
    blckNone:   begin
                  AddReconnector(decode_ip(local.ip));
                  DisconnectUser(local,'','','LoginError',false);
                end;
    blckCustom: LoginError(FormatString(@rec,blocked_message,false))
    else LoginError(GetLangT(LNG_CLIENTBLOCK,hlist.Strings[3]));
   end;
   exit;
 end;
 if ((local.softwareID=softWinMXNormal) or (local.softwareID=softWinMXJap)) and limit_mx then
 if (mx_users*100 div max_users)>=quota_mx then
 begin
   loginError(GetLangT(LNG_TOOMUCHMX,quota_mx));
   exit;
 end;
 tmp_pos:=217;
 if TCPSocket_GetRemoteSin(local.socket).sin_addr.S_addr<>TCPSocket_GetLocalSin(local.socket).sin_addr.S_addr then
  AddSoftware(rec.software);
 rec.local:=local;
 local.data:=db_online.AddUser(rec);
 user:=local.data;
 local.Exec(MSG_SERVER_EMAIL,'anon@'+serveralias);
 inc(num_login);
 tmp_pos:=218;
 Handler_Motd;
 UserOnline(local);
 if force_enter then
 begin
   local.join_delay := INITIAL_JOIN_DELAY;
   db_login.Add(local);
 end;
 tmp_pos:=219;
 local.Flush;
end;

procedure Handler_CheckNick;
begin
 tmp_pos:=220;
 if local=nil then exit;
 if query<>queryNormal then exit;
 if user<>nil then exit;
 if (user<>nil) or (local.socket=INVALID_SOCKET) then
 begin
   local.Exec(MSG_SERVER_REGISTER_FAIL,'');
   Error(GetLangT(LNG_LOGGED),true);
   exit;
 end;
 if not check_name(gcmd.cmd) then
 begin
   local.Exec(MSG_SERVER_BAD_NICK,'');
   exit;
 end;
 tmp_pos:=221;
 if db_registered.FindUser(gcmd.cmd)<>nil then
 begin
   local.Exec(MSG_SERVER_REGISTER_FAIL,'');
   exit;
 end;
 if db_online.FindUser(gcmd.cmd)<>nil then
 begin
   local.Exec(MSG_SERVER_REGISTER_FAIL,'');
   exit;
 end;
 tmp_pos:=222;
 local.Exec(MSG_SERVER_REGISTER_OK,'');
end;

procedure Handler_PassCheck;
begin
 tmp_pos:=230;
 if not isLocal then exit;
 if query<>queryNormal then exit;
 if (user<>nil) or (local.socket=INVALID_SOCKET) then
 begin
   Error(GetLangT(LNG_LOGGED),true);
   exit;
 end;
 if not CheckParams(2) then exit;
 if not check_name(hlist.Strings[0]) then
 begin
  LoginError(GetLangT(LNG_INVALIDPASS));
  exit;
 end;
 tmp_pos:=231;
 if db_online.FindUser(hlist.Strings[0])<>nil then
 begin
  LoginError(GetLangT(LNG_INVALIDPASS));
  exit;
 end;
 if db_registered.FindUser(hlist.Strings[0])=nil then
 begin
   local.Exec(MSG_SERVER_PASS_OK,'');
   exit;
 end;
 tmp_pos:=232;
 if db_registered.PasswordOk(hlist.Strings[0],encode(hlist.Strings[1])) then
  local.Exec(MSG_SERVER_PASS_OK,'')
 else
  LoginError(GetLangT(LNG_INVALIDPASS));
end;

procedure Handler_Echo;
begin
 Exec(user,gcmd.id,gcmd.cmd);
end;

{procedure Handler_ServerLinks;
var
 i: Integer;
 str: String;
 srv: TServer;
 moderator: Boolean;
begin
 tmp_pos:=240;
 moderator:=user^.level>napUserUser;
 if query<>queryNormal then
 begin
  if num_servers=0 then
  begin
    Error('no linked servers');
    exit;
  end;
  if not moderator then str:=dispsrvname else str:=servername_t;
  Error('Servers linked to '+str);
 end;
 tmp_pos:=241;
 for i:=0 to db_servers.Count-1 do
 begin
   srv:=db_servers.Items[i];
   if srv.logged then
   begin
    if not moderator then
    begin
      str:=srv.alias;
      if srv.hub<>nil then str:=str+' (->'+srv.hub.alias+')';
      str:=str+#9'   id='+IntToStr(srv.server_handle);
      str:=str+', users='+IntToStr(srv.num_users)+'/'+IntToStr(srv.max_users);
      Error(str);
    end else if query<>queryNormal then
    begin
      str:=srv.host+':'+srv.port;
      if srv.hub<>nil then str:=str+' (->'+srv.hub.host+')';
      str:=str+#9'   id='+IntToStr(srv.server_handle);
      str:=str+', users='+IntToStr(srv.num_users)+'/'+IntToStr(srv.max_users);
      Error(str);
    end else 
      Exec(user,gcmd.id,GetServerName(srv.hub)+' 0 '+GetServerName(srv)+' '+IntToStr(srv.port)+' 1');
   end;
 end;
 tmp_pos:=242;
 if (query<>queryNormal) or (not moderator) then
  Error('Total: '+IntToStr(num_servers)+' linked servers.')
 else
  Exec(user,gcmd.id,'');
end;
}

procedure Handler_ServerLinks;  
var  
 i: Integer;  
 str: String;  
 srv: TServer;  
 moderator: Boolean;  
begin  
 tmp_pos:=240;  
 moderator:=user^.level>napUserUser;  
 if query<>queryNormal then  
 begin  
  if num_servers=0 then  
  begin  
    Error('リンクしているサーバーがありません。');
    exit;
  end;
  if not moderator then
   str:=serveralias
  else
   str:=servername_t;
  Error(str+'にリンクしているサーバー');
 end
 else  
  if num_servers=0 then  
  begin  
   Exec(user,gcmd.id,'');  
   exit;
  end;  
 tmp_pos:=241;  
 for i:=0 to db_servers.Count-1 do  
 begin  
   srv:=db_servers.Items[i];  
   if srv.logged then  
   begin  
    if query<>queryNormal then  
    begin  
      if not moderator then  
      begin  
        str:=GetServerAlias(srv);  
        if srv.hub<>nil then str:=str+' (->'+GetServerAlias(srv.hub)+')';  
        str:=str+#9'   users='+IntToStr(srv.num_users)+'/'+IntToStr(srv.max_users);  
        Error(str);  
      end else  
      begin  
        str:=srv.host+':'+IntToStr(srv.port);  
        if srv.hub<>nil then str:=str+' (->'+srv.hub.host+')';  
        str:=str+#9'   id='+IntToStr(srv.server_handle);  
        str:=str+', users='+IntToStr(srv.num_users)+'/'+IntToStr(srv.max_users);  
        Error(str);
      end  
    end else   
    begin  
      if (current_time-local.last_seen)>10000 then // if user requested stats 10 seconds after login it is probably auto-request by client (like aG) to avoid connecting to linked servers
      begin  
        if srv.hub=nil then
          Exec(user,gcmd.id,AddStr(serveralias)+' 0 '+GetServerAlias(srv)+' '+IntToStr(srv.port)+' 1')  
     else
          Exec(user,gcmd.id,GetServerAlias(srv.hub)+' 0 '+GetServerAlias(srv)+' '+IntToStr(srv.port)+' 1')  
      end       
      else       
        Exec(user,gcmd.id,GetServerName(srv.hub)+' 0 '+GetServerName(srv)+' '+IntToStr(srv.port)+' 1');
    end;
   end;  
 end;  
 tmp_pos:=242;  
 if query<>queryNormal then  
 begin  
   if moderator then  
     Error('合計: '+IntToStr(num_servers)+'個のリンクサーバー')
   else
     Error('.')  
 end  
 else
  Exec(user,gcmd.id,'');  
end;

procedure Handler_HotList;
var
 user2: POnlineUser;
begin
 tmp_pos:=250;
 if not isLocal then exit;
 if query<>queryNormal then exit;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 if gcmd.id=MSG_CLIENT_ADD_HOTLIST_SEQ then
 if GetTickCount-local.last_seen<30000 then
   local.detector:=local.detector+[loc208];
 tmp_pos:=251;
 case gcmd.id of
    MSG_CLIENT_ADD_HOTLIST,
    MSG_CLIENT_ADD_HOTLIST_SEQ:  begin
                                   if not check_name(gcmd.cmd) then
                                   begin
                                     if gcmd.id=MSG_CLIENT_ADD_HOTLIST then
                                       local.Exec(MSG_SERVER_HOTLIST_ERROR,gcmd.cmd);
                                     exit;
                                   end;
                                   user2:=db_online.FindUser(gcmd.cmd);
                                   tmp_pos:=252;
                                   if max_hotlist>0 then
                                    if local.level<napUserModerator then
                                     if local.hotlist.Count>=max_hotlist then exit;
                                   if StrHash_FindString(local.hotlist,gcmd.cmd,true) then exit;
                                   tmp_pos:=253;
                                   StrHash_Add(local.hotlist,gcmd.cmd);
                                   if gcmd.id=MSG_CLIENT_ADD_HOTLIST then
                                    local.Exec(MSG_SERVER_HOTLIST_ACK,gcmd.cmd);
                                   if user2<>nil then
                                    local.Exec(MSG_SERVER_USER_SIGNON,gcmd.cmd+' '+IntToStr(Ord(user2^.speed)));
                                   tmp_pos:=254;
                                 end;
    MSG_CLIENT_REMOVE_HOTLIST:   StrHash_Delete(local.hotlist,gcmd.cmd,true);
 end;
end;

procedure Handler_IgnoreList;
var
 i: Integer;
 item: PStringHashItem;
begin
 tmp_pos:=260;
 if not isLocal then exit;
 if query<>queryNormal then exit;
 if not isLogged then exit;
 tmp_pos:=261;
 case gcmd.id of
    MSG_CLIENT_IGNORE_LIST: begin
                              tmp_pos:=262;
                              item:=local.ignored.first;
                              while item<>nil do
                              begin
                                local.Exec(MSG_SERVER_IGNORE_ENTRY,item^.data);
                                item:=item^.next;
                              end;
                              local.Exec(MSG_CLIENT_IGNORE_LIST,IntToStr(local.ignored.Count));
                            end;
    MSG_CLIENT_IGNORE_USER: begin
                              if not CheckParams(1) then exit;
                              if StrHash_FindString(local.ignored,AnsiLowerCase(gcmd.cmd),false) then
                              begin
                                local.Exec(MSG_SERVER_ALREADY_IGNORED,gcmd.cmd);
                                exit;
                              end;
                              tmp_pos:=263;
                              if max_ignorelist>0 then
                               if local.level<napUserModerator then
                                 if local.ignored.count>=max_ignorelist then
                                 begin
                                   local.Exec(MSG_SERVER_NOT_IGNORED,gcmd.cmd);
                                   exit;
                                 end;
                              StrHash_add(local.ignored,AnsiLowerCase(gcmd.cmd));
                              local.Exec(gcmd.id,gcmd.cmd);
                            end;
    MSG_CLIENT_UNIGNORE_USER: begin
                              if not CheckParams(1) then exit;
                              if StrHash_Delete(local.ignored,AnsiLowerCase(gcmd.cmd),false) then
                                local.Exec(gcmd.id,gcmd.cmd)
                              else
                                local.Exec(MSG_SERVER_NOT_IGNORED,gcmd.cmd);
                            end;
    MSG_CLIENT_CLEAR_IGNORE: begin
                              if GetTickCount-local.last_seen<30000 then
                                local.detector:=local.detector+[loc326];
                              i:=local.ignored.Count;
                              StrHash_Clear(local.ignored);
                              local.Exec(gcmd.id,IntToStr(i));
                            end;
  end;
end;

procedure Handler_OperServ;
var
 action: String;
begin
 query:=queryOperServ;
 if not isLogged then exit;
 if not isLocal then exit;
 if hlist.count<2 then hlist.Add('help');
 action:=lowercase(hlist.Strings[1]);
 gcmd.cmd:=NextParamEx(gcmd.cmd,2);
 gcmd.id:=0;
 if action='links' then gcmd.id:=MSG_CLIENT_LINKS
 else if action='block' then gcmd.id:=MSG_CLIENT_BLOCK
 else if action='blocklist' then gcmd.id:=MSG_CLIENT_BLOCKLIST
 else if action='unblock' then gcmd.id:=MSG_CLIENT_UNBLOCK
 else if action='stats' then gcmd.id:=MSG_CLIENT_USAGE_STATS
 else if action='nuke' then gcmd.id:=MSG_CLIENT_NUKE
 else if action='register' then gcmd.id:=MSG_CLIENT_REGISTER_USER
 else if action='chanlevel' then gcmd.id:=MSG_CLIENT_SET_CHAN_LEVEL
 else if action='chanlimit' then gcmd.id:=MSG_CLIENT_CHANNEL_LIMIT
 else if action='kick' then gcmd.id:=MSG_CLIENT_KICK
 else if action='config' then gcmd.id:=MSG_CLIENT_SERVER_CONFIG
 else if action='reconfig' then gcmd.id:=MSG_CLIENT_SERVER_RECONFIG
 else if action='cban' then gcmd.id:=MSG_CLIENT_CHANNEL_BAN
 else if action='cunban' then gcmd.id:=MSG_CLIENT_CHANNEL_UNBAN
 else if action='cbanlist' then gcmd.id:=MSG_CLIENT_CHANNEL_BAN_LIST
 else if action='cbanclear' then gcmd.id:=MSG_CLIENT_CHANNEL_CLEAR_BANS
 else if action='clearchan' then gcmd.id:=MSG_CLIENT_CLEAR_CHANNEL
 else if action='cloak' then gcmd.id:=MSG_CLIENT_CLOAK
 else if action='op' then gcmd.id:=MSG_CLIENT_OP
 else if action='deop' then gcmd.id:=MSG_CLIENT_DEOP
 else if action='redirect' then gcmd.id:=MSG_CLIENT_REDIRECT
 else if action='cycle' then gcmd.id:=MSG_CLIENT_CYCLE
 else if action='whowas' then gcmd.id:=MSG_CLIENT_WHO_WAS
 else if action='restart' then gcmd.id:=MSG_CLIENT_RESTART
 else if action='setvar' then gcmd.id:=MSG_CLIENT_SETVAR
 else if action='userlist' then gcmd.id:=MSG_CLIENT_USERLIST
 else if action='connect' then gcmd.id:=MSG_CLIENT_CONNECT
 else if action='disconnect' then gcmd.id:=MSG_CLIENT_DISCONNECT
 else if action='killserver' then gcmd.id:=MSG_CLIENT_KILL_SERVER
 else if action='removeserver' then gcmd.id:=MSG_CLIENT_REMOVE_SERVER
 else if (action='usermode') or (action='um') then gcmd.id:=MSG_CLIENT_USER_MODE
 else if action='muzzle' then gcmd.id:=MSG_CLIENT_MUZZLE
 else if action='unmuzzle' then gcmd.id:=MSG_CLIENT_UNMUZZLE
 else if action='help' then
 begin
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ OperServのコマンドリスト :');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ --------------------------------');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ connect <server>[:port] - サーバーに接続');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ block <expression> [<expression2> ...] - ファイルをブロック');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ blocklist - ブロックファイルを一覧');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ chanlevel <channel> <level> - チャンネルの最低入室レベルを設定');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ chanlimit <channel> <limit> - チャンネルの定員を設定');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ clearchan <channel> - すべてのユーザーをチャンネルから追放');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ cloak - 一般ユーザーに自分を表示/非表示');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ cycle <nick> <host> - メタサーバー <host> に再接続するようにクライアントに要求');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ deop <channel> <user> [<user2> ...] - チャンネルオペレーターからはずす');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ disconnect <server> - サーバーから切断');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ help - このヘルプメッセージを表示');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ kick <nick> - ユーザーを追放');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ killserver <server> - サーバーを切断');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ muzzle <user> [reason] - ユーザーを発言禁止');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ nuke <nick> - ユーザーを追放してその名前でのログインを禁止');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ op <channel> <user> [<user2> ...] - チャンネルオペレーターを任命');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ redirect <nick> <host> <port> - サーバー<host>:<port>へ接続するようにクライアントに要求');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ removeserver <server> - サーバーを一覧から削除');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ restart - サーバーを再起動');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ setvar [<variable=value>] - サーバー変数を変更');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ stats - サーバー統計を表示');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ unblock <expression> [<expression2> ...] - 特定のファイルブロックを解除');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ unmuzzle <user> [reason] - ユーザーの発言禁止を解除');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ userlist - すべてのユーザーを一覧');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ usermode [+|-mode1 ...] - モード設定("usermode help"でモード一覧)');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ whowas <nick> - 現在ログアウト済みのクライアントの情報を表示');
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ --------------------------------');
   exit;
 end
 else
 begin
   local.Exec(MSG_CLIENT_PRIVMSG,'OperServ '+GetLangT(LNG_INVALIDCOMMAND,action));
   exit;
 end;
 if gcmd.id<>0 then
  ProcessCommand(local,queryOperServ);
end;

procedure Handler_MsgServ_ReadAll;
var
 t: PStringHashItem;
begin
 if not isLogged then exit;
 t:=db_msgserv.first;//receiver (new|old) date time <sender> "msg"
 while t<>nil do
 try
   if FirstParam(t^.data)=AnsiLowerCase(local.nick) then
   begin
     local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ '+NextParam(t^.data,2));
     if FirstParam(NextParam(t^.data))='new' then
       t^.data:=FirstParam(t^.data)+' old '+NextParam(t^.data,2);
   end;
   t:=t^.next;
  except
 end;
end;

procedure Handler_MsgServ_ReadNew;//伝言を送信者からのIMみたいに表示
var
 t: PStringHashItem;
begin
 if not isLogged then exit;
 t:=db_msgserv.first;//receiver (new|old) date time <sender> "msg"
 while t<>nil do
 try
   if FirstParam(t^.data)=AnsiLowerCase(local.nick) then
   if FirstParam(NextParam(t^.data))='new' then
   begin
     local.Exec(MSG_CLIENT_PRIVMSG,FirstParam(NextParam(t^.data,4))
       +' [伝言] '+FirstParam(NextParam(t^.data,2))
       +' '+FirstParam(NextParam(t^.data,3))+' '+NextParam(t^.data,5));
     t^.data:=FirstParam(t^.data)+' old '+NextParam(t^.data,2);
   end;
   t:=t^.next;
  except
 end;
end;

procedure Handler_MsgServ_Write;//write sender msg(空白含む)
begin
 if not isLogged then exit;
 if not CheckParams(2) then exit;
 StrHash_AddEx(db_msgserv,AnsiLowerCase(FirstParam(gcmd.cmd))+' new '+DateTimeToStr(now)
   +' '+AnsiLowerCase(user^.username)+' "'+NextParam(gcmd.cmd)+'"');
 //t^.data:='receiver yyyy/mm/dd hh:mm:ss sender "msg"';
 if user^.server=nil then WriteAllServers(MSG_CLIENT_MSGSERV_WRITE,user^.username,gcmd.cmd);
 if local<> nil then local.Exec(MSG_SERVER_NOSUCH,'<MsgServ> 伝言メッセージを記憶しました。');
end;

procedure Handler_MsgServ_Delete;//delete date time <sender> "msg"
var
  b: Boolean;
begin
 if not isLogged then exit;
 if not CheckParams(4) then exit;
 b:=StrHash_Delete(db_msgserv,AnsiLowerCase(user^.username)+' old '+gcmd.cmd,false);
 if user^.server=nil then WriteAllServers(MSG_CLIENT_MSGSERV_DELETE,user^.username,gcmd.cmd);
 if local<>nil then
   if b then
     local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ 伝言メッセージを1件削除しました')
   else local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ そのような伝言はないようです');
end;

procedure Handler_MsgServ_Clear;//clear
var
 t,next_t: PStringHashItem;
begin
 if not isLogged then exit;
 t:=db_msgserv.first;
 while t<>nil do
 try
   next_t:=t^.next;
   if FirstParam(t^.data)=AnsiLowerCase(user^.username) then
     StrHash_Delete(db_msgserv,t^.data,false);
   t:=next_t;
  except
 end;
 if user^.server=nil then WriteAllServers(MSG_CLIENT_MSGSERV_CLEAR,user^.username,gcmd.cmd);
 if local<>nil then local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ 伝言メッセージをすべて削除しました');
end;

procedure Handler_MsgServ;
var
  action: String;
begin
  query:=queryMsgServ;
  if not isLogged then exit;
  if not isLocal then exit;
  if not enable_msgserv then
  begin
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ 停止中！');
    exit;
  end;
  SplitString(gcmd.cmd,hlist);
  if hlist.count<2 then hlist.Add('help');
  action:=lowercase(hlist.Strings[1]);
  gcmd.cmd:=NextParamEx(gcmd.cmd,2);
  gcmd.id:=0;
  if (action='readall')     or (action='a') then gcmd.id:=MSG_CLIENT_MSGSERV_READALL
  else if (action='read')   or (action='r') then gcmd.id:=MSG_CLIENT_MSGSERV_READNEW
  else if (action='write')  or (action='w') then gcmd.id:=MSG_CLIENT_MSGSERV_WRITE
  else if (action='delete') or (action='d') then gcmd.id:=MSG_CLIENT_MSGSERV_DELETE
  else if action='clear'                    then gcmd.id:=MSG_CLIENT_MSGSERV_CLEAR
  else if action='help' then
  begin
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ MsgServのコマンドリスト :');
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ --------------------------------');
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ readall  - 伝言をぜんぶ読む');
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ (read|r) - 新しい伝言を読む');
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ (write|w) <user> <message> - 伝言を書き残す');
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ (delete|d) <date> <time> <user> <message> - 伝言を消去');
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ clear    - 伝言をすべて消去');
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ help     - このヘルプメッセージを表示');
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ --------------------------------');
    exit;
  end
  else
  begin
    local.Exec(MSG_CLIENT_PRIVMSG,'MsgServ '+GetLangT(LNG_INVALIDCOMMAND,action));
    exit;
  end;
  case gcmd.id of
    MSG_CLIENT_MSGSERV_READNEW:  Handler_MsgServ_ReadNew;
    MSG_CLIENT_MSGSERV_READALL:  Handler_MsgServ_ReadAll;
    MSG_CLIENT_MSGSERV_WRITE:    Handler_MsgServ_Write;
    MSG_CLIENT_MSGSERV_DELETE:   Handler_MsgServ_Delete;
    MSG_CLIENT_MSGSERV_CLEAR:    Handler_MsgServ_Clear;
  end;
end;

procedure Handler_NickServ;
var
 action: String;
begin
 query:=queryNickServ;
 if not isLogged then exit;
 if not isLocal then exit;
 if hlist.count<2 then hlist.Add('help');
 action:=lowercase(hlist.Strings[1]);
 gcmd.cmd:=NextParamEx(gcmd.cmd,2);
 gcmd.id:=0;
 if action='ghost'                            then gcmd.id:=MSG_CLIENT_GHOST
 else if action='pass'                        then gcmd.id:=MSG_CLIENT_CHANGE_PASS
 else if (action='usermode') or (action='um') then gcmd.id:=MSG_CLIENT_USER_MODE
 else if action='help' then
 begin
   local.Exec(MSG_CLIENT_PRIVMSG,'NickServ NickServのコマンドリスト :');
   local.Exec(MSG_CLIENT_PRIVMSG,'NickServ --------------------------------');
   local.Exec(MSG_CLIENT_PRIVMSG,'NickServ ghost [<user> <pass>] - ゴーストを表示/切断');
   local.Exec(MSG_CLIENT_PRIVMSG,'NickServ help - このヘルプメッセージを表示');
   local.Exec(MSG_CLIENT_PRIVMSG,'NickServ pass <pass> - パスワードを変更');
   local.Exec(MSG_CLIENT_PRIVMSG,'NickServ usermode [+|-mode1 ...] - モード設定("usermode help"でモード一覧)');
   local.Exec(MSG_CLIENT_PRIVMSG,'NickServ --------------------------------');
   exit;
 end
 else
 begin
   local.Exec(MSG_CLIENT_PRIVMSG,'NickServ '+GetLangT(LNG_INVALIDCOMMAND,action));
   exit;
 end;
 if gcmd.id<>0 then
  ProcessCommand(local,queryNickServ);
end;

procedure Handler_ChanServ;
var
 action: String;
begin
 query:=queryChanServ;
 if not isLogged then exit;
 if not isLocal then exit;
 if hlist.count<2 then hlist.Add('help');
 action:=lowercase(hlist.Strings[1]);
 gcmd.cmd:=NextParamEx(gcmd.cmd,2);
 gcmd.id:=0;
 if action='ban' then gcmd.id:=MSG_CLIENT_CHANNEL_BAN
 else if action='unban' then gcmd.id:=MSG_CLIENT_CHANNEL_UNBAN
 else if action='banclear' then gcmd.id:=MSG_CLIENT_CHANNEL_CLEAR_BANS
 else if action='banlist' then gcmd.id:=MSG_CLIENT_CHANNEL_BAN_LIST
 else if action='clear' then gcmd.id:=MSG_CLIENT_CLEAR_CHANNEL
 else if action='kick' then gcmd.id:=MSG_CLIENT_KICK
 else if action='topic' then gcmd.id:=MSG_SERVER_TOPIC
 else if action='limit' then gcmd.id:=MSG_CLIENT_CHANNEL_LIMIT
 else if action='drop' then gcmd.id:=MSG_CLIENT_DROP_CHANNEL
 else if action='op' then gcmd.id:=MSG_CLIENT_OP
 else if action='deop' then gcmd.id:=MSG_CLIENT_DEOP
 else if action='wallop' then gcmd.id:=MSG_CLIENT_CHANNEL_WALLOP
 else if action='invite' then gcmd.id:=MSG_CLIENT_CHANNEL_INVITE_OPENNAP
 else if action='mode' then gcmd.id:=MSG_CLIENT_CHANNEL_MODE
 else if action='muzzle' then gcmd.id:=MSG_CLIENT_CHANNEL_MUZZLE
 else if action='unmuzzle' then gcmd.id:=MSG_CLIENT_CHANNEL_UNMUZZLE
 else if action='unvoice' then gcmd.id:=MSG_CLIENT_CHANNEL_UNVOICE
 else if action='voice' then gcmd.id:=MSG_CLIENT_CHANNEL_VOICE
 else if action='level' then gcmd.id:=MSG_CLIENT_SET_CHAN_LEVEL
 else if action='help' then
 begin
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ ChanServのコマンドリスト :');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ --------------------------------');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ ban <channel> <user> - チャンネルのユーザーを入室禁止');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ banclear <channel> - チャンネルの入室禁止をすべて解除');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ banlist <channel> - チャンネルの入室禁止者を一覧');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ clear <channel> - チャンネルの入室者をすべて追放');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ deop <channel> [user [user ...]] - チャンネルオペレーターからはずす');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ drop <channel> - チャンネルを削除');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ help - このヘルプメッセージを表示');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ invite <channel> <user> - ユーザーをチャンネルに招待');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ kick <channel> <user> [reason] - ユーザーをチャンネルから追放');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ level <channel> [level] - 入室に必要な最低ユーザーレベルを表示/設定');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ limit <channel> [number] - チャンネルの定員を設定');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ mode <channel> [mode [mode ...]] - チャンネルモードを表示/設定');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ muzzle <channel> <user> - ユーザーを発言禁止にする');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ op <channel> [user [user ...] - チャンネルオペレーターを表示/設定');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ topic <channel> [topic] - チャンネルトピックの表示/設定');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ unban <channel> <user> - 入室禁止を解除');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ unmuzzle <channel> <user> - 発言禁止を解除');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ unvoice <channel> [user [user ...]] - 発言権を取り消す');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ voice <channel> [user [user ...]] - 発言権を与える');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ wallop <channel> <text> - すべてのチャンネルオペレーターにメッセージを送る');
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ --------------------------------');
   exit;
 end
 else
 begin
   local.Exec(MSG_CLIENT_PRIVMSG,'ChanServ '+GetLangT(LNG_INVALIDCOMMAND,action));
   exit;
 end;
 if gcmd.id<>0 then
  ProcessCommand(local,queryChanServ);
end;

procedure Handler_PrivateMessage;
var
 user2: POnlineUser;
 l2: TLocalUser;
 str,msg: String;
begin
 tmp_pos:=270;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 str:=AnsiLowerCase(hlist.Strings[0]);
 tmp_pos:=271;
 if str='operserv' then
 begin
   if user^.level>napUserUser then
    Handler_OperServ
   else
    Exec(user,MSG_SERVER_PRIVMSG,'OperServ '+GetLangT(LNG_ACCESS));
   exit;
 end;
 if str='nickserv' then
 begin
   Handler_NickServ;
   exit;
 end;
 if str='chanserv' then
 begin
   Handler_ChanServ;
   exit;
 end;
 if str='msgserv' then
 begin
   Handler_MsgServ;
   exit;
 end;
 tmp_pos:=272;
 msg:=Copy(NextParamEx(gcmd.cmd),1,max_privmsg_len);
 if trim(msg)='' then exit;
 if not allow_rtf_code and (Pos('{\rtf', msg) > 0) then Exit;
 if (msg='//WantQueue') then
 begin
   tmp_pos:=122621;
   if user^.level=napUserLeech then
   begin
     Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
     Exec(user,MSG_SERVER_NOSUCH,'あなたは'+levels[0]+'です。'
       +'このサーバーでは、'+levels[0]+'はキュー入れできません');
     exit;
   end;
   tmp_pos:=122622;
   if (local<>nil) then
   begin
     if not CheckWinMX(local) then exit;
     //-----------------------------------wantqueueカウント.mods+,friendもやる
     tmp_pos:=122623;
     user2:=db_online.FindUser(str);
     tmp_pos:=122624;
     if user2=nil then
     begin
       UserIsOffline(str,true);
       exit;
     end;
     tmp_pos:=122625;
     if user2^.server=nil then
       l2:=FindLocalUser(user2^.username)
     else
       l2:=nil;
     tmp_pos:=122626;
     inc(local.wantqueuep3m);//WQ元がローカルならとりあえずカウント
     tmp_pos:=122627;
     if (l2<>nil) and StrHash_FindString(l2.hotlist,AnsiLowerCase(local.nick),true) then
       dec(local.wantqueuep3m);//WQ元とWQ先がともにローカルでホットリストにWQ元がいるときは戻す
     //-----------------------------------連キューブロック
     tmp_pos:=122628;
     if local.level<napUserModerator then
     if not StrHash_FindString(db_friends,local.nick,true) then
     if (wqfloodblock_count<>0) and (local.wantqueuep3m>wqfloodblock_count) then
     begin
       //local.Exec(MSG_SERVER_NOSUCH,'一度に連キューしすぎです！');
       case wqfloodblock_method of
         //block: local.Exec(MSG_SERVER_NOSUCH,'連キューをブロックしました:WantQueue/上限(3分間で)='
         //       +IntToStr(local.wantqueuep3m)+'/'+IntToStr(wqfloodblock_count));
         leech:
         begin
           local.Exec(MSG_SERVER_NOSUCH,'あなたのユーザーレベルを'+levels[0]+'にします。');
           local.data^.level:=napUserLeech;
           RegisterUser(user,servername_t);
           Wallop(MSG_SERVER_NOSUCH,wallopLevel,GetLangT(LNG_LEVEL1,servername_t,
             local.nick,Level2Str(napUserLeech),IntToStr(Ord(napUserLeech))),false);
         end;
         ban:
         begin
           BanUser(local.nick,servername_t,wqfloodblock_bantime,'連Q ID:'+local.nick,true);
           AddReconnector(decode_ip(local.ip));
           DisconnectUser(local,'',GetLangT(LNG_KILLED,local.nick,local.software,
             'Server '+servername_t)+' - 連Q','Handler_PrivateMessage',false);
         end;
       end;
       exit;
     end;
   end;
 end;
 tmp_pos:=273;
 user2:=db_online.FindUser(str);
 if user2=nil then
 begin
   UserIsOffline(str,true);
   if enable_msgserv then
   begin
     gcmd.cmd:='msgserv write '+str+' '+msg;
     SplitString(gcmd.cmd,hlist);
     Handler_MsgServ;
   end;
   exit;
 end;
 if userHidePM in user2^.state then exit;
 if (user^.server=nil) and (userMuzzled in user^.state) then
 if user2^.level<napUserModerator then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if user2^.level>napUserUser then
  if userCloaked in user2^.state then
   if user^.level<napUserModerator then
   begin
    UserIsOffline(str,true);
    exit;
   end;
 tmp_pos:=274;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   user2^.server.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
   exit;
 end;
 if user^.level<napUserModerator then
 begin
   l2:=user2^.local;
   if l2<>nil then
    if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
    begin
      UserIsOffline(hlist.Strings[0],true);
      exit;
    end;
 end;
 tmp_pos:=275;
 Exec(user2,gcmd.id,user^.username+' '+msg);
end;

procedure Handler_PrivateMessageAnonymous;
var
 user2: POnlineUser;
 str,msg: String;
begin
 tmp_pos:=7270;
 if user^.level<napUserElite then
 begin
  PermissionDenied('',true);
  exit;
 end;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 str:=AnsiLowerCase(hlist.Strings[0]);
 tmp_pos:=271;
 msg:=Copy(NextParamEx(gcmd.cmd),1,max_privmsg_len);
 if trim(msg)='' then exit;
 tmp_pos:=7271;
 user2:=db_online.FindUser(str);
 if user2=nil then
 begin
   UserIsOffline(str,true);
   exit;
 end;
 if user2^.level>napUserUser then
 begin
  PermissionDenied('',true);
  exit;
 end;
 tmp_pos:=7272;
 gcmd.id:=MSG_CLIENT_PRIVMSG;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   user2^.server.Exec(gcmd.id,'Server'+' '+gcmd.cmd);
   exit;
 end;
 Exec(user2,gcmd.id,'Server'+' '+msg);
end;


procedure Handler_Resume;
begin
 if not isLocal then exit;
 local.Exec(MSG_SERVER_RESUME_MATCH_END,'');
end;

procedure Handler_GetData;
var
 user2: POnlineUser;
begin
 if not isLogged then exit;
 case gcmd.id of
   MSG_CLIENT_USERSPEED:            begin
                                     if not CheckParams(1) then exit;
                                     user2:=db_online.FindUser(gcmd.cmd);
                                     if user2=nil then UserIsOffline(gcmd.cmd,true)
                                     else Exec(user,MSG_SERVER_USER_SPEED,user2^.username+' '+IntToStr(Ord(user2^.speed)));
                                    end;
 end;
end;

function ShareFile(filename, folder: String; rec:TShare; mime: Integer):Boolean;
var
 item: PShare;
 id, index: Integer;
 sharing: Boolean;
begin
 tmp_pos:=280;
 Result:=true;
 if not allow_share then exit;
 if mime=TYPE_INVALID then
 begin
  Result:=false;
  exit;
 end;
 {$I checksync.pas}
 tmp_pos:=281;
 if share_nomodem then
  if (user^.speed<napSpeed64ISDN) and (user^.speed<>napSpeedUnknown) then
   exit; // modem sharing disabled
 case mime of
   TYPE_AUDIO:     id:=SHARED_AUDIO;
   TYPE_VIDEO:     id:=SHARED_VIDEO;
   TYPE_TEXT:      id:=SHARED_TEXT;
   TYPE_IMAGE:     id:=SHARED_IMAGE;
   TYPE_APP:       id:=SHARED_APP;
   TYPE_CD:        id:=SHARED_CD;
   else // MP3/WMA/OGG
    case opBitrate(rec.options) of
       320:     if not share_320 then id:=SHARED_INVALID
                else id:=SHARED_320;
       256:     if not share_256 then id:=SHARED_INVALID
                else id:=SHARED_256;
       224:     if not share_224 then id:=SHARED_INVALID
                else id:=SHARED_224;
       192:     if not share_192 then id:=SHARED_INVALID
                else id:=SHARED_192;
       160:     if not share_160 then id:=SHARED_INVALID
                else id:=SHARED_160;
       128:     if not share_128 then id:=SHARED_INVALID
                else id:=SHARED_128;
       112:     if not share_112 then id:=SHARED_INVALID
                else id:=SHARED_112;
       96:      if not share_96 then id:=SHARED_INVALID
                else id:=SHARED_OTHER;
       80:      if not share_80 then id:=SHARED_INVALID
                else id:=SHARED_OTHER;
       64:      if not share_64 then id:=SHARED_INVALID
                else id:=SHARED_64;
       56:      if not share_56 then id:=SHARED_INVALID
                else id:=SHARED_OTHER;
       48:      if not share_48 then id:=SHARED_INVALID
                else id:=SHARED_OTHER;
       40:      if not share_40 then id:=SHARED_INVALID
                else id:=SHARED_OTHER;
       32:      if not share_32 then id:=SHARED_INVALID
                else id:=SHARED_OTHER;
       24:      if not share_24 then id:=SHARED_INVALID
                else id:=SHARED_OTHER;
       else     if not share_unknown then id:=SHARED_INVALID
                else id:=SHARED_OTHER;
    end;
    if id=SHARED_INVALID then
    begin
      if shareinform and CanSendError(user,query) then
       local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_NOSHARE_BITRATE,opBitrate(rec.options)));
      exit;
    end;
 end;
 if user^.dataport=0 then
  inc(id,SHARED_FIREWALL);
 tmp_pos:=282;
 try
   // checking duplicate
   if local.shared=nil then local.shared:=TShareList.Create;
   index:=local.shared.AddFolder(folder);
   if index>32767 then
   begin // too many directories
     local.shared.dirs.Delete(index);
     Result:=false;
     exit;
   end;
   rec.index:=index;
   if share_checkdup then
   if local.shared.FindFile(rec.index,rec.name)<>-1 then
   begin
     Result:=false;
     exit;
   end;
   tmp_pos:=283;
   // extracting keywords
   item:=CreateShareItem;
   item^.name:=rec.name;
   item^.crc:=StringCRC(folder+rec.name,false);
   item^.size:=rec.size;
   item^.options:=rec.options;
   item^.index:=rec.index;
   item^.user:=local;
   tmp_pos:=1262;
   if not GetKeywords(filename,id,local,item) then
   begin // error extracting keywords
     FreeShareItem(item);
     exit;
   end;
   tmp_pos:=1263;
   // adding item
   local.shared.AddEx(item);
   sharing:=opIsShared(item^.options);
   tmp_pos:=284;
   if sharing then
   begin
    inc(user^.shared);
    case mime of
     TYPE_MP3: inc(local.shared_mp3);
     TYPE_AUDIO: inc(local.shared_audio);
     TYPE_VIDEO: inc(local.shared_video);
     TYPE_IMAGE: inc(local.shared_images);
     TYPE_APP: inc(local.shared_apps);
     TYPE_CD: inc(local.shared_cd);
     TYPE_TEXT:
     begin
      if nocount_text then dec(user^.shared);
      inc(local.shared_text);
     end;
    end;
    if auto_friendadd then
     if user^.shared=share_auto_friendadd_minimum then
      if not StrHash_FindString(db_friends,user^.username,true) then
       StrHash_Add(db_friends,user^.username);
    if force_enter then
      local.join_delay := INITIAL_JOIN_DELAY;
    inc(total_files);
    inc(local_files);
    inc(total_bytes, rec.size);
    inc(local_bytes, rec.size);
    inc(local.shared_size, rec.size);
    local.localstate:=local.localstate+[locNeedsUpdate];
    if local_files_max<local_files then local_files_max:=local_files;
    if total_files_max<total_files then total_files_max:=total_files;
    if local_bytes_max<local_bytes then local_bytes_max:=local_bytes;
    if total_bytes_max<total_bytes then total_bytes_max:=total_bytes;
   end
   else
    inc(local.shared_blocked);
   tmp_pos:=285;
  except
   on E:Exception do
    DebugLog('Exception in ShareFile (pos='+IntToStr(tmp_pos)+') : '+E.Message);
 end;
end;

procedure Handler_Share(dir: String; valid_dir: Boolean);
var
 rec: TShare;
 folder_name, str, filename, file_ext, ext_fake: String;
 i,j,mime: Integer;
 k,l,fsize: Int64;
 op_bitrate, op_freq, op_time: Integer;
begin
 tmp_pos:=290;
 // valid_dir=false if this proc is called from Handler_ShareDir
 if not allow_share then exit;
 if network_hub then exit;
 if not isLocal then exit;
 if not isLogged then exit;
 if query<>queryNormal then exit;
 if not valid_dir then SplitString(gcmd.cmd,hlist);
 if hlist.Count<6 then
 begin
  Error(GetLangT(LNG_INVALIDARGS),true);
  exit;
 end;
 if (dir='') and not valid_dir then local.detector:=local.detector+[loc100];
 if hlist.Strings[1]='0' then local.detector:=local.detector+[locMD5Zero]
 else if hlist.Strings[1]='00000000000000000000000000000000' then local.detector:=local.detector+[locMD5Zeros]
 else local.detector:=local.detector+[locMD5NonZero];
 tmp_pos:=12262;
 if autoban_incomplete and (Pos('__INCOMPLETE___',hlist.Strings[0])<>0) then
 begin
   Inc(local.blocked_incomplete);
   //if shareinform and CanSendError(user,query) then
     //local.Exec(MSG_SERVER_NOSUCH,'ファイル"'+hlist.Strings[0]+'"は共有されません。'
     //  +' : INCOMPLETEファイル');
   exit;
 end;
 ext_fake:=ExtractFileExt(ChangeFileExt(hlist.Strings[0],''))+ExtractFileExt(hlist.Strings[0]);
 if disable_fakeext and StrHash_FindString(fakeext_list,ext_fake,true) then
 begin
   Inc(local.blocked_fakeext);
   //if shareinform and CanSendError(user,query) then
     //local.Exec(MSG_SERVER_NOSUCH,'ファイル"'+hlist.Strings[0]+'"は共有されません。'
     //  +' : 拡張子偽装ファイル');
   exit;
 end;
 tmp_pos:=291;
 filename:=dir+hlist.Strings[0];
 SplitFileName(filename,folder_name,rec.name);
 // checking FileNavigator
 if copy(filename,1,3)='|||' then
 begin
   str:=ExtractFileName(Copy(filename,4,Length(filename)));
   i:=Length(ExtractFileExt(str));
   str:=Copy(str,1,Length(str)-i);
   i:=pos('|',str);
   while i>0 do
   begin
     str[i]:='.';
     i:=pos('|',str);
   end;
   filename:=str;
 end;
 tmp_pos:=292;
 if maxshare>0 then
  if (user^.shared>=maxshare) or
     (nocount_text and (user^.shared+local.shared_text>=maxshare)) then
  begin
    Inc(local.blocked_toomany);
    //if shareinform and CanSendError(user,query) then
    // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_LIMIT,filename,maxshare));
    exit;
  end;
 tmp_pos:=293;
 if maxshare_total>0 then
  if local_files>=maxshare_total then
  begin
   if shareinform and CanSendError(user,query) then
    local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_TOTAL_LIMIT,filename));
   exit;
  end;
 if (memory_limit>0) and win98 then
  if AllocMemSize>=memory_limit then
  begin
   if shareinform and CanSendError(user,query) then
    local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_MEMORY_LIMIT,filename));
   exit;
  end;
 tmp_pos:=294;
 if Length(filename)<minfilename then
 begin
   Inc(local.blocked_tooshort);
   //if shareinform and CanSendError(user,query) then
   // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_SHORT,filename));
   exit;
 end;
 fsize:=StrToInt64Def(hlist.Strings[2],0);
 rec.size:=StrToInt64Def(hlist.Strings[2],0);
 op_bitrate:=StrToIntDef(hlist.Strings[3],WINMX_BITRATE);
 op_freq:=StrToIntDef(hlist.Strings[4],WINMX_FREQ);
 op_time:=StrToIntDef(hlist.Strings[5],WINMX_TIME);
 tmp_pos:=295;
 if (op_bitrate=0) or (op_freq=0) then // Fixing bug in SunshineUE
 begin
   op_bitrate:=WINMX_BITRATE;
   op_freq:=WINMX_FREQ;
   op_time:=WINMX_TIME;
 end;
 file_ext:=lowercase(ExtractFileExt(filename));
 if Length(file_ext)>0 then if file_ext[1]='.' then file_ext:=Copy(file_ext,2,Length(file_ext));
 tmp_pos:=296;
 mime:=GetType(file_ext);
 if mime=TYPE_INVALID then
 begin
   Inc(local.blocked_other);
   if shareinform and CanSendError(user,query) then
    local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_ERROR,filename));
   exit;
 end;
 if mime=TYPE_MP3 then
 if op_time<minduration then
 begin
   Inc(local.blocked_tooshortmp3);
   //if shareinform and CanSendError(user,query) then
   // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_TIME,filename));
   exit;
 end;
 tmp_pos:=297;
 case mime of
   TYPE_AUDIO: begin i:=maxshare_audio; j:=local.shared_audio; end;
   TYPE_VIDEO: begin i:=maxshare_video; j:=local.shared_video; end;
   TYPE_IMAGE: begin i:=maxshare_image; j:=local.shared_images; end;
   TYPE_APP:   begin i:=maxshare_app; j:=local.shared_apps; end;
   TYPE_CD:    begin i:=maxshare_cd; j:=local.shared_cd; end;
   TYPE_TEXT:  begin i:=maxshare_text; j:=local.shared_text; end;
   else i:=maxshare_mp3; j:=local.shared_mp3;
 end;
 if j>=i then
 begin
   Inc(local.blocked_toomany);
   //if shareinform and CanSendError(user,query) then
   // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_LIMIT,filename,i));
   exit;
 end;
 tmp_pos:=298;
 case mime of
   TYPE_AUDIO: begin k:=minfilesize_audio; l:=maxfilesize_audio; end;
   TYPE_VIDEO: begin k:=minfilesize_video; l:=maxfilesize_video; end;
   TYPE_IMAGE: begin k:=minfilesize_image; l:=maxfilesize_image; end;
   TYPE_APP: begin k:=minfilesize_app; l:=maxfilesize_app; end;
   TYPE_CD: begin k:=minfilesize_cd; l:=maxfilesize_cd; end;
   TYPE_TEXT: begin k:=minfilesize_text; l:=maxfilesize_text; end;
   else k:=minfilesize_mp3; l:=maxfilesize_mp3;
 end;
 if k>0 then
 if fsize<k then
 begin
   Inc(local.blocked_toosmall);
   //if shareinform and CanSendError(user,query) then
   // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_SMALL,filename));
   exit;
 end;
 if l>0 then
 if fsize>l then
 begin
   Inc(local.blocked_toolarge);
   //if shareinform and CanSendError(user,query) then
   // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_LARGE,filename));
   exit;
 end;
 rec.options:=SetOption(op_bitrate,op_freq,op_time,0,true);
 tmp_pos:=299;
 if not ShareFile(filename,folder_name,rec,mime) then
 begin
  Inc(local.blocked_other);
  if shareinform and CanSendError(user,query) then
   local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_ERROR,filename));
 end;
end;

procedure Handler_ShareDir;
var
 i:Integer;
 dir: String;
begin
 tmp_pos:=300;
 if not allow_share then exit;
 if network_hub then exit;
 if not isLocal then exit;
 if not isLogged then exit;
 //if user^.level=napUserLeech then exit;
 if query<>queryNormal then exit;
 tmp_pos:=301;
 if not CheckParams(7,false) then
 begin
   if hlist.count=1 then // possible winmx
    CheckWinMX(local);
   exit;
 end;
 local.detector:=local.detector+[loc870];
 tmp_pos:=302;
 dir:=hlist.Strings[0];
 if not (locMacDir in local.detector) then
 begin
   if AnsiLowerCase(dir)[1] in ['a'..'z'] then
   begin
     if Copy(dir,2,2)<>':\'  then
       local.detector:=local.detector+[locMacDir];
   end else
   if not ((AnsiLowerCase(dir)[1] in ['\']) and (AnsiLowerCase(dir)[2] in ['\'])) then
     if not (AnsiLowerCase(dir)[1] in ['/']) then
       local.detector:=local.detector+[locMacDir];
 end;
 hlist.Delete(0);
 tmp_pos:=303;
 while hlist.Count>5 do
 begin
   if not local.logged then exit;
   if not running then exit;
   if maxshare>0 then if user^.shared>=maxshare then exit;
   tmp_pos:=304;
   Handler_Share(dir,true);
   tmp_pos:=305;
   for i:=0 to 5 do
    hlist.Delete(0);
  {$I checksync.pas}
 end;
end;

procedure Handler_ShareFile;
var
 rec: TShare;
 i,j,mime: Integer;
 k,l,fsize: Int64;
 folder,file_ext,ext_fake: String;
begin
 tmp_pos:=310;
 if not allow_share then exit;
 if network_hub then exit;
 if not isLocal then exit;
 if not isLogged then exit;
 if query<>queryNormal then exit;
 //if user^.level=napUserLeech then exit;
 if not CheckParams(4) then exit;
 local.detector:=local.detector+[loc10300];
 if hlist.Strings[1]='0' then local.detector:=local.detector+[locMD5Zero]
 else if hlist.Strings[1]='00000000000000000000000000000000' then local.detector:=local.detector+[locMD5Zeros]
 else local.detector:=local.detector+[locMD5NonZero];
 tmp_pos:=12262;
 if autoban_incomplete and (Pos('__INCOMPLETE___',hlist.Strings[0])<>0) then
 begin
   Inc(local.blocked_incomplete);
   exit;
 end;
 ext_fake:=ExtractFileExt(ChangeFileExt(hlist.Strings[0],''))+ExtractFileExt(hlist.Strings[0]);
 if disable_fakeext and StrHash_FindString(fakeext_list,ext_fake,true) then
 begin
   Inc(local.blocked_fakeext);
   exit;
 end;
 tmp_pos:=292;
 if maxshare>0 then
  if (user^.shared>=maxshare) or
     (nocount_text and (user^.shared+local.shared_text>=maxshare)) then
  begin
    Inc(local.blocked_toomany);
    exit;
  end;
 tmp_pos:=293;
 if maxshare_total>0 then
  if local_files>=maxshare_total then
  begin
   if shareinform and CanSendError(user,query) then
    local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_TOTAL_LIMIT,hlist.Strings[0]));
   exit;
  end;
 if (memory_limit>0) and win98 then
  if AllocMemSize>=memory_limit then
  begin
   if shareinform and CanSendError(user,query) then
    local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_MEMORY_LIMIT,hlist.Strings[0]));
   exit;
  end;
 tmp_pos:=294;
 if Length(hlist.Strings[0])<minfilename then
 begin
   Inc(local.blocked_tooshort);
   exit;
 end;
 tmp_pos:=311;
 mime:=StrToType(hlist.Strings[3]);
 if mime=TYPE_INVALID then exit;
 if mime=TYPE_MP3 then
 begin
   if shareinform and CanSendError(user,query) then
    local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_10300));
   exit;
 end;
 case mime of
   TYPE_AUDIO: begin i:=maxshare_audio; j:=local.shared_audio; end;
   TYPE_VIDEO: begin i:=maxshare_video; j:=local.shared_video; end;
   TYPE_IMAGE: begin i:=maxshare_image; j:=local.shared_images; end;
   TYPE_APP:   begin i:=maxshare_app; j:=local.shared_apps; end;
   TYPE_CD:    begin i:=maxshare_cd; j:=local.shared_cd; end;
   TYPE_TEXT:  begin i:=maxshare_text; j:=local.shared_text; end;
   else i:=maxshare_mp3; j:=local.shared_mp3;
 end;
 tmp_pos:=312;
 if j>=i then
 begin
   Inc(local.blocked_toomany);
   //if shareinform and CanSendError(user,query) then
   // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_LIMIT,hlist.Strings[0],i));
   exit;
 end;
 case mime of
   TYPE_AUDIO: begin k:=minfilesize_audio; l:=maxfilesize_audio; end;
   TYPE_VIDEO: begin k:=minfilesize_video; l:=maxfilesize_video; end;
   TYPE_IMAGE: begin k:=minfilesize_image; l:=maxfilesize_image; end;
   TYPE_APP: begin k:=minfilesize_app; l:=maxfilesize_app; end;
   TYPE_CD: begin k:=minfilesize_cd; l:=maxfilesize_cd; end;
   TYPE_TEXT: begin k:=minfilesize_text; l:=maxfilesize_text; end;
   else k:=minfilesize_mp3; l:=maxfilesize_mp3;
 end;
 tmp_pos:=313;
 rec.size:=StrToInt64Def(hlist.Strings[1],0);
 fsize:=StrToInt64Def(hlist.Strings[1],0);
 if k>0 then
 if fsize<k then
 begin
   Inc(local.blocked_toosmall);
   //if shareinform and CanSendError(user,query) then
   // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_SMALL,hlist.Strings[0]));
   exit;
 end;
 if l>0 then
 if fsize>l then
 begin
   Inc(local.blocked_toolarge);
   //if shareinform and CanSendError(user,query) then
   // local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_LARGE,hlist.Strings[0]));
   exit;
 end;
 tmp_pos:=314;
 // let's make these values compatible with WinMX and some other clients
 rec.options:=SetOption(WINMX_BITRATE,WINMX_FREQ,WINMX_TIME,0,true);
// if Length(hlist.Strings[0])<minfilename then
// begin
//   if shareinform and CanSendError(user,query) then
//    local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHARE_SHORT,hlist.Strings[0]));
//   exit;
// end;
 tmp_pos:=315;
 file_ext:=lowercase(ExtractFileExt(hlist.Strings[0]));
 if Length(file_ext)>0 then if file_ext[1]='.' then file_ext:=Copy(file_ext,2,Length(file_ext));
 tmp_pos:=316;
 SplitFileName(hlist.Strings[0],folder,rec.name);
 ShareFile(hlist.Strings[0],folder,rec,mime);
end;

procedure Handler_Unshare(filename: String='');
var
 i,j:Integer;
 n: Int64;
 p: PShare;
 kw: PKeyword;
 shared: Boolean;
begin
 tmp_pos:=320;
 if not isLocal then exit;
 if not isLogged then exit;
 if GetTickCount-local.last_seen<30000 then local.detector:=local.detector+[loc110];
 if local.shared=nil then exit;
 if user^.shared=0 then
   if not nocount_text then exit
   else if local.shared_text=0 then exit;
 tmp_pos:=321;
 if filename='' then
 begin
  if local.shared<>nil then
  begin
   dec(total_bytes,local.shared_size);
   dec(local_bytes,local.shared_size);
   dec(total_files,local.shared.count);
   dec(local_files,local.shared.count);
   if nocount_text then
   begin
     dec(total_files,local.shared_text);
     dec(local_files,local.shared_text);
   end;
   local.shared.Clear;
   local.localstate:=local.localstate+[locNeedsUpdate];
  end;
  tmp_pos:=322;
  user^.shared:=0;
  local.shared_mp3:=0;
  local.shared_audio:=0;
  local.shared_video:=0;
  local.shared_cd:=0;
  local.shared_images:=0;
  local.shared_text:=0;
  local.shared_apps:=0;
  local.shared_size:=0;
  local.shared_blocked:=0;
  exit;
 end;
 tmp_pos:=323;
 if Length(filename)>2 then
  if filename[1]='"' then filename:=Copy(filename,2,length(filename)-2);
 if local.shared=nil then exit;
 j:=local.shared.FindFile(filename);
 tmp_pos:=324;
 if j<>-1 then
 begin
   n:= PShare(local.shared.Items[j])^.size;
   p:=local.shared.Items[j];
   shared:=opIsShared(p^.options);
   kw:=p^.keywords^[0];
   i:=ID2Mime(kw^.id);
   local.shared.Delete(j);
   local.shared.reindex:=true;
   if shared then
   begin
    dec(total_bytes,n);
    dec(local_bytes,n);
    dec(local.shared_size,n);
    dec(total_files);
    dec(local_files);
    dec(user^.shared);
    local.localstate:=local.localstate+[locNeedsUpdate];
    case i of
     TYPE_AUDIO: dec(local.shared_audio);
     TYPE_VIDEO: dec(local.shared_video);
     TYPE_TEXT:  dec(local.shared_text);
     TYPE_IMAGE: dec(local.shared_images);
     TYPE_APP:   dec(local.shared_apps);
     TYPE_CD:    dec(local.shared_cd);
     else        dec(local.shared_mp3);
    end;
   end
   else
    dec(local.shared_blocked);
 end;
end;

function Search(inc_list, exc_list: TNapCmdList): Integer;
var
 i,j,mime,sync: Integer;
 loop_b, loop_item: Integer;
 b: Array[0..SHARED_ARRAY-1] of Boolean;
 match, match2, complete: Boolean;
 str: String;
 sh: PShare;
 user2: TLocalUser;
 inc_list2, exc_list2: TMyList;
 kwindex, kwlength: Integer;
 kw, kw_min: PKeyword;
 p: PKeywordItem;
 sh_bitrate, sh_time, sh_numwords: Word;
 sh_freq: LongWord;
 sh_shared: Boolean;
 matchcount_per_user: Integer; //1ユーザーが連続してヒットした数をカウント
begin
 tmp_pos:=330;
 result:=0;
 {$I checksync.pas}
 if local_users<1 then exit;
 for i:=0 to SHARED_MAX do b[i]:=false;
 // checking file types
 tmp_pos:=1235;
 case search_data.mime of
   TYPE_AUDIO:    b[SHARED_AUDIO]:=true;
   TYPE_VIDEO:    b[SHARED_VIDEO]:=true;
   TYPE_TEXT:     b[SHARED_TEXT]:=true;
   TYPE_IMAGE:    b[SHARED_IMAGE]:=true;
   TYPE_APP:      b[SHARED_APP]:=true;
   else
       if search_data.nonmp3=false then
       begin
         tmp_pos:=1236;
         if search_data.mime=TYPE_INVALID then
         begin
           b[SHARED_AUDIO]:=true;
           b[SHARED_VIDEO]:=true;
           b[SHARED_TEXT]:=true;
           b[SHARED_IMAGE]:=true;
           b[SHARED_APP]:=true;
         end;
         b[SHARED_320]:=compare(search_data.bitrate_cmp,search_data.bitrate,320);
         b[SHARED_256]:=compare(search_data.bitrate_cmp,search_data.bitrate,256);
         b[SHARED_224]:=compare(search_data.bitrate_cmp,search_data.bitrate,224);
         b[SHARED_192]:=compare(search_data.bitrate_cmp,search_data.bitrate,192);
         b[SHARED_160]:=compare(search_data.bitrate_cmp,search_data.bitrate,160);
         b[SHARED_128]:=compare(search_data.bitrate_cmp,search_data.bitrate,128);
         b[SHARED_112]:=compare(search_data.bitrate_cmp,search_data.bitrate,112);
         b[SHARED_64]:=compare(search_data.bitrate_cmp,search_data.bitrate,64);
         tmp_pos:=1237;
         if search_data.bitrate_cmp<>napEqual then b[SHARED_OTHER]:=true
         else
          if search_data.bitrate<>320 then
          if search_data.bitrate<>256 then
          if search_data.bitrate<>224 then
          if search_data.bitrate<>192 then
          if search_data.bitrate<>160 then
          if search_data.bitrate<>128 then
          if search_data.bitrate<>112 then
          if search_data.bitrate<>64 then
           b[SHARED_OTHER]:=true;
         tmp_pos:=1238;
         for i:=inc_list.count-1 downto 0 do
         begin // allow user to type file extension in search string
           j:=GetType(PNapCmd(inc_list.Items[i])^.cmd);
           tmp_pos:=1239;
           case j of
             TYPE_AUDIO:  b[SHARED_AUDIO]:=true;
             TYPE_VIDEO:  b[SHARED_VIDEO]:=true;
             TYPE_IMAGE:  b[SHARED_IMAGE]:=true;
             TYPE_APP:    b[SHARED_APP]:=true;
             TYPE_CD:     b[SHARED_CD]:=true;
             TYPE_TEXT:   b[SHARED_TEXT]:=true;
             else j:=-1;
           end;
           if j<>-1 then
           begin
            inc_list.Delete(i);
            if inc_list.count<1 then exit;
           end;
         end;
       end;
 end;
 tmp_pos:=1240;
 if search_data.nonmp3 then // if searching all non-mp3s
 begin
   for i:=0 to SHARED_MAX do
     if i<SHARED_MP3_MIN then b[i]:=true
     else b[i]:=false;
 end;
 for i:=0 to SHARED_MAX do
 begin
  if user^.dataport>0 then // can search firewalled users
   b[i+SHARED_FIREWALL]:=b[i]
  else
   b[i+SHARED_FIREWALL]:=false;
 end;
 tmp_pos:=332;
 {$I checksync.pas}
 inc_list2:=TMyList.Create;
 exc_list2:=TMyList.Create;
 sync:=0;
 matchcount_per_user:=0;
 for loop_b:=0 to SHARED_ARRAY-1 do
  if b[loop_b] then
  begin
    // clearing variables
    inc_list2.Clear;
    exc_list2.Clear;
    kw_min:=nil;
    match:=true;
    mime:=ID2Mime(loop_b);
    // checking if all keywords are in database
    for i:=0 to inc_list.count-1 do
    if match then
    begin
      kwindex:=GetKeywordIndex(PNapCmd(inc_list.Items[i])^.cmd);
      kwlength:=Length(PNapCmd(inc_list.Items[i])^.cmd);
      if kwindex=KEYWORDS_NOINDEX then kw:=nil
      else kw:=KWList_FindItem(db_keywords[loop_b,kwindex,kwlength],PNapCmd(inc_list.Items[i])^.cmd);
      if kw=nil then match:=false
      else
      begin
        if (kw_min=nil) or (kw^.count<kw_min^.count) then
        begin // found new minimum item
          if kw_min<>nil then inc_list2.Add(kw_min);
          kw_min:=kw;
        end
        else
          inc_list2.Add(kw);
      end;
    end;
    if kw_min=nil then match:=false; // possible error???
    // checking all excluded keywords
    if match then
     for i:=0 to exc_list.count-1 do
     begin
       kwindex:=GetKeywordIndex(PNapCmd(exc_list.Items[i])^.cmd);
       kwlength:=Length(PNapCmd(exc_list.Items[i])^.cmd);
       if kwindex=KEYWORDS_NOINDEX then kw:=nil
       else kw:=KWList_FindItem(db_keywords[loop_b,kwindex,kwlength],PNapCmd(exc_list.Items[i])^.cmd);
       if kw<>nil then exc_list2.Add(kw);
     end;
    {$I checksync.pas}
    if match then
    begin // all keywords found. checking each item in smallest list for all keywords
      complete:=true;
      p:=kw_min^.complete;
      if p=nil then
      begin
        p:=kw_min^.incomplete;
        complete:=false;
      end;
      while p<>nil do
      begin
        for loop_item:=0 to ITEMS_PER_KEYWORD-1 do
        if p^.share[loop_item]<>nil then
        begin
          inc(sync);
          sh:=p^.share[loop_item];
          SplitOption(sh^.options,sh_bitrate,sh_freq,sh_time,sh_numwords,sh_shared);
          // checking item 'sh'
          match:=sh_shared or (user^.level>napUserModerator);
          if (sh^.user=local) or (sh^.user=nil) then match:=false;
          for i:=0 to inc_list2.Count-1 do
           if match then // checking if all keywords are persent
           begin
             match:=false;
             for j:=0 to sh_numwords-1 do
              if sh^.keywords^[j*2]=inc_list2.Items[i] then
               match:=true;
           end;
          if match then // checking excluded keywords
          for i:=0 to exc_list2.count-1 do
           if match then
            for j:=0 to sh_numwords-1 do
             if sh^.keywords^[j*2]=exc_list2.Items[i] then
              match:=false;
          if (sync mod 100)=30 then
          begin
           {$I checksync.pas}
          end;
          if match then
          begin
            // all keywords match - checking bitrate/freq/size,etc...
            if not compare(search_data.size_cmp,search_data.size,sh^.size) then match:=false
            else if (mime=TYPE_AUDIO) or (mime=TYPE_VIDEO) or (mime=TYPE_MP3) then
            begin
             if not compare(search_data.bitrate_cmp,search_data.bitrate,sh_bitrate) then match:=false
             else if not compare(search_data.frequency_cmp,search_data.frequency,sh_freq) then match:=false
             else if not compare(search_data.time_cmp,search_data.time,sh_time) then match:=false;
            end;
            user2:=TLocalUser(sh^.user);
            if match then
             if not compare(search_data.speed_cmp,Ord(search_data.speed),Ord(user2.data^.speed)) then match:=false;
            if match then
            begin // found a match !!!
              if restrict_hitperuser then
                if (loop_item=0) or
                   ((loop_item>0) and (p^.share[loop_item-1]<>nil) and ((p^.share[loop_item-1])^.user=sh^.user)) then
                begin
                  inc(matchcount_per_user);
                  if matchcount_per_user>maxhitperuser then continue;
                end
                else matchcount_per_user:=1;
              tmp_pos:=1247;
              i:=sh^.index;
              if i<>-1 then str:=PNapCmd(user2.shared.dirs.Items[i])^.cmd
              else str:='';
              if disable_nullip or (user.level>=NapUserModerator) then
                str:='"'+str+sh^.name+'" '+null_md5+' '+IntToStr(sh^.size)+' '+
                  IntToStr(sh_bitrate)+' '+IntToStr(sh_freq)+' '+IntToStr(sh_time)+
                  ' '+user2.nick+' '+IntToStr(user2.ip)+' '+IntToStr(Ord(user2.data^.speed))
              else
                str:='"'+str+sh^.name+'" '+null_md5+' '+IntToStr(sh^.size)+' '+
                  IntToStr(sh_bitrate)+' '+IntToStr(sh_freq)+' '+IntToStr(sh_time)+
                  ' '+user2.nick+' '+null_ip+' '+IntToStr(Ord(user2.data^.speed));
              tmp_pos:=1248;
              if search_data.ext_queue then
              begin
                if user2.data^.queue=-1 then str:=str+' n/a'
                else str:=str+' '+IntToStr(user2.data^.uploads+user2.data^.queue-user2.data^.max_up);
              end;
              tmp_pos:=340;
              if search_data.ext_soft then str:=str+' '+AddStr(user2.data^.software);
              tmp_pos:=1249;
              if local=nil then user^.server.Exec(MSG_SRV_SEARCH_RESULT,user^.username+' '+str)
              else Exec(user,MSG_SERVER_SEARCH_RESULT,str);
              inc(result);
              tmp_pos:=341;
              if result>=search_data.max then
              begin
                inc_list2.Free;
                exc_list2.Free;
                exit;
              end;
            end;
          end;
        end;
        p:=p^.next;
        if (p=nil) and complete then
        begin
          p:=kw_min^.incomplete;
          complete:=false;
        end;
      end;
    end;
 end;
 inc_list2.Free;
 exc_list2.Free;
end;

procedure Handler_Search;
var
 i,j,k:Integer;
 dec: Integer;
 err: Boolean;
 srv: TServer;
 inc_list, exc_list: TNapCmdList;
begin
{
Exec(user,MSG_SERVER_SEARCH_END,'');
Exec(user,MSG_SERVER_NOSUCH,'This is a debug server version. Search is temporary disabled.');
exit;
}
 if not isLogged then exit;
 Inc(user^.searchreqs);
 // block searches by leeches
 if user^.level=napUserLeech then
 begin
   if user^.server=nil then
     local.Exec(MSG_SERVER_SEARCH_END,'')
   else
     user^.server.Exec(MSG_SRV_SEARCH_END,user^.username);
   exit;
 end;
 tmp_pos:=360;
 if local<>nil then
 begin // local user
   tmp_pos:=1232;
   if not allow_share then
   begin
     local.Exec(MSG_SERVER_SEARCH_END,'');
     exit;
   end;
   if network_hub then
    if not IPisLocal(decode_ip(user^.ip)) then
    begin
     local.Exec(MSG_SERVER_SEARCH_END,'');
     exit;
    end;
   // block search by local ppl not meeting min_share
   if user^.level=napUserUser then
   begin
     if blocked_clients[softWinMXHidden] and (not StrHash_FindString(db_friends,user^.username,true))
     and (local.softwareID=softWinMXHidden) and (not (locPingable in local.localstate)) then
     begin // possible winmx (don't block it here, because other clients can execute query too
       local.Exec(MSG_SERVER_SEARCH_END,'');
       exit;
     end;
     if not minshare_noblockact then
     begin
       if local.shared_size<minshare_size then
       begin
         if not StrHash_FindString(db_friends,user^.username,true) then
         begin
           //local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHAREMEGSFIRST,IntToStr(minshare_size div MegaByte),server_alias));
           local.Exec(MSG_SERVER_SEARCH_END,'');
           exit;
         end;
       end
       else
       if user^.shared<minshare then
        if not StrHash_FindString(db_friends,user^.username,true) then
        begin
          //local.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_SHAREFILESFIRST,IntToStr(minshare),server_alias));
          local.Exec(MSG_SERVER_SEARCH_END,'');
          exit;
        end;
     end;
   end;
   tmp_pos:=361;
   if not CheckParams(3)then
   begin
     local.Exec(MSG_SERVER_SEARCH_END,'');
     exit;
   end;
   tmp_pos:=1233;
   if not local.BufferEmpty then
   begin
     local.Exec(MSG_SERVER_NOSUCH,'検索失敗: 実行中の検索が多すぎます');
     local.Exec(MSG_SERVER_SEARCH_END,'');
     local.searches_count:=0;
     exit;
   end;
   tmp_pos:=362;
   if local.searchespm>=flood_max_searches then
   begin
     //local.Exec(MSG_SERVER_NOSUCH,'検索失敗: 実行中の検索が多すぎます');
     local.Exec(MSG_SERVER_SEARCH_END,'');
     inc(local.searchespm);
     exit;
   end;
   tmp_pos:=1234;
   if local.searches_count>0 then
   begin
     local.Exec(MSG_SERVER_NOSUCH,'検索失敗: 実行中の検索が多すぎます');
     local.Exec(MSG_SERVER_SEARCH_END,'');
     inc(local.searchespm);
     local.searches_count:=0;
     exit;
   end;
   tmp_pos:=363;
   if pos('FILENAME CONTAINS',uppercase(gcmd.cmd))=0 then
   begin
     local.Exec(MSG_SERVER_NOSUCH,'検索失敗: 有効な単語が含まれていません');
     local.Exec(MSG_SERVER_SEARCH_END,'');
     exit;
   end;
   tmp_pos:=122621;
   if local.level<napUserModerator then
   if not StrHash_FindString(db_friends,local.nick,true) then
   begin
     if searchblock_chkfiles and searchblock_chksize then
     begin
       if searchblock_hard then
       begin
         if local.shared.Count<searchblock_files then
         begin
           local.Exec(MSG_SERVER_NOSUCH,'共有ファイル数がたりないので検索できません。('
             +IntToStr(local.shared.Count)+'/'+IntToStr(searchblock_files)+'ファイル)');
           local.Exec(MSG_SERVER_SEARCH_END,'');
           exit;
         end;
         if local.shared_size<searchblock_size then
         begin
           local.Exec(MSG_SERVER_NOSUCH,'共有ファイルサイズがたりないので検索できません。('
             +IntToStr(local.shared_size div Megabyte)+'/'
             +IntToStr(searchblock_size div Megabyte)+'Mb)');
           local.Exec(MSG_SERVER_SEARCH_END,'');
           exit;
         end;
       end
       else
       if (local.shared.Count<searchblock_files) and
          (local.shared_size<searchblock_size) then
       begin
         local.Exec(MSG_SERVER_NOSUCH,'検索できません。:ファイル数/サイズが両方たりません('
           +IntToStr(local.shared.Count)+'/'+IntToStr(searchblock_files)+'ファイル)('
           +IntToStr(local.shared_size div Megabyte)+'/'
           +IntToStr(searchblock_size div Megabyte)+'Mb)');
         local.Exec(MSG_SERVER_SEARCH_END,'');
         exit;
       end;
     end
     else if searchblock_chkfiles and (local.shared.Count<searchblock_files) then
     begin
       local.Exec(MSG_SERVER_NOSUCH,'共有ファイル数がたりないので検索できません。('
         +IntToStr(local.shared.Count)+'/'+IntToStr(searchblock_files)+'ファイル)');
       local.Exec(MSG_SERVER_SEARCH_END,'');
       exit;
     end
     else if searchblock_chksize and (local.shared_size<searchblock_size) then
     begin
       local.Exec(MSG_SERVER_NOSUCH,'共有ファイルサイズがたりないので検索できません。('
         +IntToStr(local.shared_size div Megabyte)+'/'
         +IntToStr(searchblock_size div Megabyte)+'Mb)');
       local.Exec(MSG_SERVER_SEARCH_END,'');
       exit;
     end;
   end;
   inc(local.searchespm);
 end
 else
 begin
   tmp_pos:=364;
   if (not allow_share) or disableremotesearch or (local_files<1) then
   begin
     user^.server.Exec(MSG_SRV_SEARCH_END,user^.username);
     exit;
   end;
   if search_noforward_results and CheckLag(user^.server) then
   begin
     user^.server.Exec(MSG_SRV_SEARCH_END,user^.username);
     exit;
   end;
   tmp_pos:=122622;
   if searchblock_chkfiles then
   if user^.shared<searchblock_files then
   if user^.level<napUserModerator then
   if not StrHash_FindString(db_friends,user^.username,true) then
   begin
     Exec(user,MSG_SERVER_NOSUCH,'<'+servername_t
       +'> 共有ファイル数がたりないのでリモート検索できません。('
       +IntToStr(user^.shared)+'/'+IntToStr(searchblock_files)+'ファイル)');
     user^.server.Exec(MSG_SERVER_SEARCH_END,user^.username);
     exit;
   end;
   tmp_pos:=365;
   SplitString(gcmd.cmd,hlist);
 end;
 tmp_pos:=366;
 with search_data do
 begin
    include:='';
    exclude:='';
    max:=defsearchresults;
    mime:=TYPE_MP3;
    speed_cmp:=napNoCompare;
    bitrate_cmp:=napNoCompare;
    frequency_cmp:=napNoCompare;
    size_cmp:=napNoCompare;
    time_cmp:=napNoCompare;
    local:=false;
    nonmp3:=false;
    ext_soft:=false;
    ext_queue:=false;
 end;
 err:=false;
 tmp_pos:=367;
 while hlist.Count>0 do
 begin
   hlist.Strings[0]:=uppercase(hlist.Strings[0]);
   dec:=0;
   if hlist.Strings[0]='FILENAME' then
   begin
     if hlist.Count<3 then err:=true
     else
     begin
       for i := 1 to Length(hlist.Strings[2]) do
         if hlist.Strings[2][i] in [Chr(0)..Chr(31), Chr(127)] then
           err := true;
       if not err then
       begin
         dec:=3;
         if uppercase(hlist.Strings[1])='CONTAINS' then
           search_data.include:=search_data.include+AnsiLowerCase(hlist.Strings[2])+' '
         else if uppercase(hlist.Strings[1])='EXCLUDES' then
           search_data.exclude:=search_data.exclude+AnsiLowerCase(hlist.Strings[2])+' '
         else err:=true;
       end;
     end;
   end; // end FILENAME
   if hlist.Strings[0]='MAX_RESULTS' then
   begin
     if hlist.Count<2 then err:=true
     else search_data.max:=StrToIntDef(hlist.Strings[1],search_data.max);
     dec:=2;
   end; // end MAX_RESULTS
   if hlist.Strings[0]='TYPE' then
   begin
     if hlist.Count<2 then err:=true
     else search_data.mime:=StrToType(hlist.Strings[1]);
     dec:=2;
   end; // end TYPE
   if hlist.Strings[0]='LINESPEED' then
   begin
     if hlist.Count<3 then err:=true
     else
     begin
       search_data.speed_cmp:=Str2Compare(hlist.Strings[1]);
       search_data.speed:=TNapSpeed(StrToIntDef(hlist.Strings[2],0));
       dec:=3;
     end;
   end; // end LINESPEED
   if hlist.Strings[0]='BITRATE' then
   begin
     if hlist.Count<3 then err:=true
     else
     begin
       search_data.bitrate_cmp:=Str2Compare(hlist.Strings[1]);
       search_data.bitrate:=StrToIntDef(hlist.Strings[2],0);
       if search_data.bitrate=0 then search_data.bitrate:=24;
       dec:=3;
     end;
   end; // end BITRATE
   if hlist.Strings[0]='FREQ' then
   begin
     if hlist.Count<3 then err:=true
     else
     begin
       search_data.frequency_cmp:=Str2Compare(hlist.Strings[1]);
       search_data.frequency:=StrToIntDef(hlist.Strings[2],WINMX_FREQ);
       dec:=3;
     end;
   end; // end FREQ
   if (hlist.Strings[0]='SIZE') or (hlist.Strings[0]='FILESIZE') then
   begin
     if hlist.Count<3 then err:=true
     else
     begin
       search_data.size_cmp:=Str2Compare(hlist.Strings[1]);
       search_data.size:=StrToInt64Def(hlist.Strings[2],0);
       dec:=3;
     end;
   end; // end SIZE
   if hlist.Strings[0]='DURATION' then
   begin
     if hlist.Count<3 then err:=true
     else
     begin
       search_data.time_cmp:=Str2Compare(hlist.Strings[1]);
       search_data.time:=StrToIntDef(hlist.Strings[2],0);
       dec:=3;
     end;
   end; // end DURATION
   if (hlist.Strings[0]='LOCAL') or (hlist.Strings[0]='LOCAL_ONLY') then
   begin // local only
     if local<>nil then
      search_data.local:=true;
     dec:=1;
   end;
   if hlist.Strings[0]='SHOW_SOFTWARE' then
   begin
     search_data.ext_soft:=true;
     dec:=1;
   end;
   if hlist.Strings[0]='SHOW_QUEUE' then
   begin
     search_data.ext_queue:=true;
     dec:=1;
   end;
   if (hlist.Strings[0]='WMA-FILE') or (hlist.Strings[0]='WMA_FILE') then
   begin // WMA-FILE
     //search_data.mime:=napTypeAudio;
     dec:=1;
   end;
   if dec=0 then err:=true;
   if err then
   begin
     tmp_pos:=368;
     if local=nil then user^.server.Exec(MSG_SRV_SEARCH_END,user^.username)
     else Exec(user,MSG_SERVER_SEARCH_END,'');
     exit;
   end;
   for i:=1 to dec do
    if hlist.count>0 then
     hlist.Delete(0);
 end;
 tmp_pos:=369;
 // checking parsed parameters
 inc_list:=TNapCmdList.Create;
 exc_list:=TNapCmdList.Create;
 SplitToKeywordsEx(search_data.include,search_data.exclude,inc_list,exc_list,KEYWORD_MAX_SEARCH);
 if inc_list.count<1 then
 begin // empty keywords list
   inc_list.Free;
   exc_list.Free;
   if user^.server<>nil then
     user^.server.Exec(MSG_SRV_SEARCH_END,user^.username)
   else
   begin
     local.Exec(MSG_SERVER_NOSUCH,'検索失敗: 有効な単語が含まれていません');
     local.Exec(MSG_SERVER_SEARCH_END,'');
   end;
   exit;
 end;
 if isSearchBlocked(user,gcmd.cmd,search_data.include,inc_list) then
 begin
   if user^.server<>nil then
     user^.server.Exec(MSG_SRV_SEARCH_END,user^.username)
   else
     local.Exec(MSG_SERVER_SEARCH_END,'');
   exit;
 end;
 tmp_pos:=370;
 if user^.server<>nil then
 begin
   if maxremotesearchresults>0 then
    if search_data.max>maxremotesearchresults then
     search_data.max:=maxremotesearchresults;
 end
 else
  if maxsearchresults>0 then
   if search_data.max>maxsearchresults then
    search_data.max:=maxsearchresults;
 tmp_pos:=372;
 with search_data do // checking for WinMX/FileNavigator command
 begin
   nonmp3:=false;
   if bitrate_cmp=napEqual then
    if bitrate=WINMX_BITRATE then
     if frequency_cmp=napEqual then
      if frequency=WINMX_FREQ then
      begin
       bitrate_cmp:=napNoCompare;
       frequency_cmp:=napNoCompare;
       time_cmp:=napNoCompare;
       nonmp3:=true;
      end;
 end;
 inc(num_searches);
 {$I checksync.pas}
 search_data_old:=search_data; // saving old data, cos "search" changes .include strings
 if network_hub or (not allow_share) then i:=0
 else i:=Search(inc_list, exc_list);
 inc_list.Free;
 exc_list.Free;
 search_data:=search_data_old;
 if i>0 then
 begin
  inc(successful_searches);
  total_search_results:=total_search_results+i;
 end;
 if log_search then
 begin
  if local=nil then Log(slSearch,GetLangT(LNG_SEARCHLOG4,user^.username,user^.software,gcmd.cmd,IntToStr(i)))
  else Log(slSearch,GetLangT(LNG_SEARCHLOG3,local.nick,local.software,gcmd.cmd,IntToStr(i)));
 end;
 tmp_pos:=373;
 if local=nil then
 begin // that was remote search
   user^.server.Exec(MSG_SRV_SEARCH_END,user^.username);
   exit;
 end;
 local.last_search_time:=GetTickCount;
 local.searches_count:=0;
 tmp_pos:=374;
 k:=0;
 if num_servers>0 then
  for j:=0 to db_servers.Count-1 do
  begin
    srv:=db_servers.Items[j];
    if srv.logged then
    if srv.hub=nil then
     if not srv.lag then
      inc(k);
  end;
 if  (i<search_data.max) and (num_servers>0) and (search_data.local=false) and (k>0) then
 begin
   WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
   local.searches_count:=num_servers;
 end
 else
  local.Exec(MSG_SERVER_SEARCH_END,'');
end;

procedure Handler_RemoteSearchResult;
var
 user: POnlineUser;
 loc: TLocalUser;
begin
 tmp_pos:=380;
 user:=db_online.FindUser(FirstParam(gcmd.cmd));
 if user=nil then exit;
 if user^.server<>nil then exit;
 tmp_pos:=381;
 loc:=user^.local;
 if loc=nil then exit;
 if loc.searches_count<1 then exit;
 tmp_pos:=382;
 loc.Exec(MSG_SERVER_SEARCH_RESULT,NextParamEx(gcmd.cmd));
end;

procedure Handler_RemoteSearchEnd;
var
 user: POnlineUser;
 loc: TLocalUser;
begin
 tmp_pos:=390;
 user:=db_online.FindUser(gcmd.cmd);
 if user=nil then exit;
 if user^.server<>nil then exit;
 tmp_pos:=391;
 loc:=user^.local;
 if loc=nil then exit;
 if loc.searches_count<1 then exit;
 dec(loc.searches_count);
 tmp_pos:=392;
 if loc.searches_count=0 then
  loc.Exec(MSG_SERVER_SEARCH_END,'');
end;

procedure Handler_Download;
var
 user2: POnlineUser;
 l2: TLocalUser;
begin
 tmp_pos:=400;
 if not isLogged then exit;
 if not CheckParams(2) then exit;
 if (query<>queryNormal) and (query<>queryRemoteUser) then exit;
 if user=nil then exit;
 tmp_pos:=122621;
 if user^.level=napUserLeech then
 begin
   Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
   Exec(user,MSG_SERVER_NOSUCH,'あなたは'+levels[0]+'です。このサーバーでは、'+levels[0]+'はDL要求できません');
   exit;
 end;
 tmp_pos:=122622;
 if local<> nil then
 begin
   //-----------------------------------DLカウント.mods+,friendもやる
   tmp_pos:=122623;
   user2:=db_online.FindUser(hlist.Strings[0]);
   tmp_pos:=122624;
   if user2=nil then
   begin
     Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
     UserIsOffline(hlist.Strings[0],true);
     exit;
   end;
   tmp_pos:=122625;
   if user2^.server=nil then
     l2:=FindLocalUser(user2^.username)
   else
     l2:=nil;
   tmp_pos:=122626;
   inc(local.dlrequestsp3m);//DL元がローカルならとりあえずカウント
   inc(user^.dlrequests);
   inc(user2^.ulrequests);
   tmp_pos:=122627;
   if (l2<>nil) and StrHash_FindString(l2.hotlist,AnsiLowerCase(local.nick),true) then
   begin
     dec(local.dlrequestsp3m);//DL元とDL先がともにローカルでホットリストにDL元がいるときは戻す
     dec(user^.dlrequests);
     dec(user2^.ulrequests);
   end;
   //--------------------------------------------投網ブロック
   tmp_pos:=122628;
   if local.level<NapUserModerator then
   if not StrHash_FindString(db_friends,local.nick,true) then
   if (dlfloodblock_count<>0) and (local.dlrequestsp3m>dlfloodblock_count) then
   begin
     //local.Exec(MSG_SERVER_NOSUCH,'一度にDL要求しすぎです！');
     case dlfloodblock_method of
       //block: local.Exec(MSG_SERVER_NOSUCH,'DL要求をブロックしました:DL要求/上限(3分間で)='
       //         +IntToStr(local.dlrequestsp3m)+'/'+IntToStr(dlfloodblock_count));
       leech:
       begin
         local.Exec(MSG_SERVER_NOSUCH,'あなたのユーザーレベルを'+levels[0]+'にします。');
         local.data^.level:=napUserLeech;
         RegisterUser(user,servername_t);
         Wallop(MSG_SERVER_NOSUCH,wallopLevel,GetLangT(LNG_LEVEL1,servername_t,
           local.nick,Level2Str(napUserLeech),IntToStr(Ord(napUserLeech))),false);
       end;
       ban:
       begin
         BanUser(local.nick,servername_t,dlfloodblock_bantime,'投網 ID:'+local.nick,true);
         AddReconnector(decode_ip(local.ip));
         DisconnectUser(local,'',GetLangT(LNG_KILLED,local.nick,local.software,
           'Server '+servername_t)+' - 投網','Handler_Download',false);
       end;
     end;
     exit;
   end;
 end;
 tmp_pos:=122629;
 if user^.level<NapUserModerator then
 if not StrHash_FindString(db_friends,user^.username,true) then
 begin
   if domblock_chkfiles and domblock_chksize then
   begin
     if domblock_hard then
     begin
       if user^.shared<domblock_files then
       begin
         Error('共有ファイル数がたりないのでDLできません。('
           +IntToStr(user^.shared)+'/'+IntToStr(domblock_files)+'ファイル)',true);
         exit;
       end;
       if (local<>nil) and (local.shared_size<=domblock_size) then
       begin
         local.Exec(MSG_SERVER_NOSUCH,'共有ファイルサイズがたりないのでDLできません。('
           +IntToStr(local.shared_size div Megabyte)+'/'
           +IntToStr(domblock_size div Megabyte)+'Mb)');
         exit;
       end;
     end
     else
     if (user^.shared<domblock_files) and
        (local<>nil) and (local.shared_size<=domblock_size) then
     begin
       local.Exec(MSG_SERVER_NOSUCH,'DLできません。:ファイル数/サイズが両方たりません('
         +IntToStr(local.shared.Count)+'/'+IntToStr(domblock_files)+'ファイル)('
         +IntToStr(local.shared_size div Megabyte)+'/'
         +IntToStr(domblock_size div Megabyte)+'Mb)');
       exit;
     end;
   end
   else
   if domblock_chkfiles and (user^.shared<domblock_files) then
   begin
     Error('共有ファイル数がたりないのでDLできません。('
       +IntToStr(user^.shared)+'/'+IntToStr(domblock_files)+'ファイル)',true);
     exit;
   end
   else
   if domblock_chksize and (local<>nil) and (local.shared_size<=domblock_size) then
   begin
     local.Exec(MSG_SERVER_NOSUCH,'共有ファイルサイズがたりないのでDLできません。('
       +IntToStr(local.shared_size div Megabyte)+'/'
       +IntToStr(domblock_size div Megabyte)+'Mb)');
     exit;
   end;
 end;
 tmp_pos:=401;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   user2^.server.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
   exit;
 end;
 tmp_pos:=402;
 l2:=user2^.local;
 if l2<>nil then
  if user^.level<napUserModerator then
   if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
   begin
     Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
     UserIsOffline(hlist.Strings[0],true);
     exit;
   end;
 tmp_pos:=403;
 if (user^.dataport=0) and (user2^.dataport=0) then
 begin
   Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
   Error(GetLangT(LNG_FIREWALL),true);
   exit;
 end;
 tmp_pos:=404;
 if l2=nil then
  Exec(user2,MSG_SERVER_UPLOAD_REQUEST,user^.username+' "'+hlist.Strings[1]+'" '+IntToStr(Ord(user^.speed)))
 else
  l2.Exec(MSG_SERVER_UPLOAD_REQUEST,user^.username+' "'+hlist.Strings[1]+'" '+IntToStr(Ord(user^.speed)));
end;

procedure Handler_DownloadFirewall;
var
 str: String;
 user2: POnlineUser;
 l2: TLocalUser;
begin
 tmp_pos:=410;
 if not isLogged then exit;
 if (query<>queryNormal) and (query<>queryRemoteUser) then exit;
 if user=nil then exit;
 if not CheckParams(2) then exit;
 tmp_pos:=411;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   user2^.server.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
   exit;
 end;
 tmp_pos:=412;
 l2:=user2^.local;
 if l2<>nil then
  if user^.level<napUserModerator then
   if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
   begin
     Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
     UserIsOffline(hlist.Strings[0],true);
     exit;
   end;
 if (user^.dataport=0) and (user2^.dataport=0) then
 begin
   Exec(user,MSG_SERVER_UPLOAD_FAILED,gcmd.cmd);
   Error(GetLangT(LNG_FIREWALL),true);
   exit;
 end;
 tmp_pos:=413;
 str:=user^.username+' '+IntToStr(user^.ip)+' '+IntToStr(user^.dataport)+' "'+hlist.Strings[1]+
  '" '+null_md5+' '+IntToStr(Ord(user^.speed));
 tmp_pos:=414;
 if l2=nil then
  Exec(user2,MSG_SERVER_UPLOAD_FIREWALL,str)
 else
  l2.Exec(MSG_SERVER_UPLOAD_FIREWALL,str)
end;

procedure Handler_Upload;
var
 str: String;
 user2: POnlineUser;
 l2: TLocalUser;
begin
 tmp_pos:=420;
 if not isLogged then exit;
 if (query<>queryNormal) and (query<>queryRemoteUser) then exit;
 if user=nil then exit;
 if not CheckParams(2) then exit;
 tmp_pos:=421;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 tmp_pos:=422;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   if gcmd.id<>MSG_SERVER_UPLOAD_FAILED then
   begin
    if log_transfers then
     Log(slTransfer,GetLangT(LNG_TRANSFERLOG5,hlist.Strings[1],user2^.username,GetServerName(user2^.server),user^.username));
    inc(num_transfers);
   end;
   user2^.server.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
   exit;
 end;
 tmp_pos:=423;
 l2:=user2^.local;
 if l2<>nil then
  if user^.level<napUserModerator then
   if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
   begin
     Error(GetLangT(LNG_OFFLINE2,hlist.Strings[0]),true);
     exit;
   end;
 if (user^.dataport=0) and (user2^.dataport=0) then
 begin
   Error(GetLangT(LNG_FIREWALL),true);
   exit;
 end;
 tmp_pos:=424;
 if gcmd.id=MSG_SERVER_UPLOAD_FAILED then
 begin
   if l2<>nil then
    l2.Exec(gcmd.id,user^.username+' "'+NextParamEx(gcmd.cmd) + '"')
   else
    Exec(user2,gcmd.id,user^.username+' "'+NextParamEx(gcmd.cmd) + '"');
   exit;
 end;
 tmp_pos:=425;
 if log_transfers then
 begin
  if user^.server<>nil then
    Log(slTransfer,GetLangT(LNG_TRANSFERLOG4,hlist.Strings[1],user2^.username,user^.username,GetServerName(user^.server)))
  else
    Log(slTransfer,GetLangT(LNG_TRANSFERLOG3,hlist.Strings[1],user2^.username,user^.username));
 end;
 inc(num_transfers);
 tmp_pos:=427;
 str:=user^.username+' '+IntToStr(user^.ip)+' '+IntToStr(user^.dataport)+' "'+hlist.Strings[1]+'" '+
 null_md5+' '+IntToStr(Ord(user^.speed));
 if l2=nil then
  Exec(user2,MSG_SERVER_FILE_READY,str)
 else
  l2.Exec(MSG_SERVER_FILE_READY,str);
end;

procedure Handler_Browse; // 211
var
 user2: POnlineUser;
 l2: TLocalUser;
 i,j,limit: Integer;
 str: String;
 sh: PShare;
 sh_bitrate, sh_time, sh_numwords: Word;
 sh_freq: LongWord;
 sh_shared: Boolean;
begin
 tmp_pos:=430;
 if not isLogged then exit;
 if (query<>queryNormal) and (query<>queryRemoteUser) then exit;
 if user=nil then exit;
 tmp_pos:=431;
 if user^.level=napUserLeech then
 begin
   Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd);
   exit;
 end;
 if network_hub then
  if not IPisLocal(decode_ip(user^.ip)) then
  begin
   Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd);
   exit;
  end;
 if not minshare_noblockact then
 if user^.level=napUserUser then
 begin
  if not StrHash_FindString(db_friends,user^.username,true) then
  begin
    if local<>nil then
    begin
      if local.shared_size<minshare_size then
      begin
        //exec(user,MSG_SERVER_NOSUCH,GetLangT(LNG_SHAREMEGSFIRST,IntToStr(minshare_size div MegaByte),server_alias));
        local.Exec(MSG_SERVER_BROWSE_END,gcmd.cmd);
        exit;
      end;
      if user^.shared<minshare then
      begin
        //exec(user,MSG_SERVER_NOSUCH,GetLangT(LNG_SHAREFILESFIRST,IntToStr(minshare),server_alias));
        local.Exec(MSG_SERVER_BROWSE_END,gcmd.cmd);
        exit;
      end;
    end;
  end;
 end;
 if local<>nil then
 begin
   if not local.BufferEmpty then
   begin
     local.Exec(MSG_SERVER_BROWSE_END,gcmd.cmd);
     exit;
   end;
   if local.searchespm>=flood_max_searches then
   begin
     local.Exec(MSG_SERVER_BROWSE_END,gcmd.cmd);
     inc(local.searchespm);
     exit;
   end;
 end;
 tmp_pos:=12262;
 if user^.username<>gcmd.cmd then
 if user^.level<napUserModerator then
 if not StrHash_FindString(db_friends,user^.username,true) then
 begin
   if domblock_chkfiles and domblock_chksize then
   begin
     if domblock_hard then
     begin
       if (user^.shared<domblock_files) then
       begin
         Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd);
         Exec(user,MSG_SERVER_NOSUCH,'<'+servername_t
           +'> 共有ファイル数がたりないので参照できません。('
           +IntToStr(user^.shared)+'/'+IntToStr(domblock_files)+'ファイル)');
         exit;
       end;
       if (local<>nil) and (local.shared_size<=domblock_size) then
       begin
         local.Exec(MSG_SERVER_BROWSE_END,gcmd.cmd);
         local.Exec(MSG_SERVER_NOSUCH,'共有ファイルサイズがたりないので参照できません。('
           +IntToStr(local.shared_size div Megabyte)+'/'
           +IntToStr(domblock_size div Megabyte)+'Mb)');
         exit;
       end;
     end
     else
     if (user^.shared<domblock_files) and
        (local<>nil) and (local.shared_size<=domblock_size) then
     begin
       local.Exec(MSG_SERVER_BROWSE_END,gcmd.cmd);
       local.Exec(MSG_SERVER_NOSUCH,'参照できません。:ファイル数/サイズが両方たりません('
         +IntToStr(local.shared.Count)+'/'+IntToStr(domblock_files)+'ファイル)('
         +IntToStr(local.shared_size div Megabyte)+'/'+IntToStr(domblock_size div Megabyte)+'Mb)');
       exit;
     end;
   end
   else if domblock_chkfiles and (user^.shared<domblock_files) then
   begin
     Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd);
     Exec(user,MSG_SERVER_NOSUCH,'<'+servername_t+'> 共有ファイル数がたりないので参照できません。('
       +IntToStr(user^.shared)+'/'+IntToStr(domblock_files)+'ファイル)');
     exit;
   end
   else if domblock_chksize and (local<>nil) and (local.shared_size<=domblock_size) then
   begin
     local.Exec(MSG_SERVER_BROWSE_END,gcmd.cmd);
     local.Exec(MSG_SERVER_NOSUCH,'共有ファイルサイズがたりないので参照できません。('
       +IntToStr(local.shared_size div Megabyte)+'/'
       +IntToStr(domblock_size div Megabyte)+'Mb)');
     exit;
   end;
 end;
 tmp_pos:=432;
 if not CheckParams(1) then exit;
 if user^.server<>nil then
  if browse_noforward_results then
   if CheckLag(GetServerLink(user^.server)) then
   begin // block browse
     Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd);
     exit;
   end;
 tmp_pos:=433;
 user2:=db_online.FindUser(gcmd.cmd);
 if user2=nil then
 begin
   Error(GetLangT(LNG_BROWSEFAIL),true);
   Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd);
   exit;
 end;
 tmp_pos:=434;
 if local<>nil then inc(local.searchespm);
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   if browse_noforward_requests then
   if CheckLag(GetServerLink(user2^.server)) then
   begin // block browse
     Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd);
     exit;
   end;
   user2^.server.Exec(MSG_CLIENT_BROWSE,user^.username+' '+gcmd.cmd);
   exit;
 end;
 tmp_pos:=435;
 l2:=user2^.local;
 if l2=nil then exit; // weird error
  if user^.level<napUserModerator then
   if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
   begin
     Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd);
     exit;
   end;
 tmp_pos:=436;
 if user2^.level>napUserUser then
 if user<>user2 then
  if not (userHideBrowse in user2^.state) then
   Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_BROWSEMOD,AddStr(Level2Str(user^.level)),user^.username,decode_ip(user^.ip)));
 j:=0;
 if user^.level<napUserElite then 
 begin
  limit:=maxbrowseresults;
  if user^.server<>nil then
   if maxremotebrowse<limit then
    limit:=maxremotebrowse;
 end else
  limit:=65535;
 inc(num_browses);
 if suggest_dbrowse then
   Exec(user,MSG_SERVER_PRIVMSG,user^.username+' なるべく直接参照を使ってください。');
 tmp_pos:=437;
 if l2.shared<>nil then
  for i:=0 to l2.shared.count-1 do
  begin
   sh:=l2.shared.Items[i];
   SplitOption(sh^.options,sh_bitrate,sh_freq,sh_time,sh_numwords,sh_shared);
   if sh_shared or (user^.level>napUserModerator) then
   begin
     str:=user2^.username+' "'+l2.shared.GetFileName(i)+'" '+null_md5+' '+IntToStr(sh^.size)+' '+IntToStr(sh_bitrate)+' '+IntToStr(sh_freq)+' '+IntToStr(sh_time);
     Exec(user,MSG_SERVER_BROWSE_RESPONSE,str);
     inc(j);
     if limit>0 then
      if j>=limit then
      begin
        if disable_nullip or (user.level>=NapUserModerator) then
          Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd+' '+IntToStr(user2^.ip))
        else
          Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd+' '+null_ip);
        total_browse_results:=total_browse_results+j;
        if log_browse then
        begin
          if local=nil then Log(slBrowse,GetLangT(LNG_BROWSELOG2,user^.username,user^.software,GetServerName(user^.server),l2.nick,IntToStr(j)))
          else Log(slBrowse,GetLangT(LNG_BROWSELOG1,local.nick,local.software,l2.nick,IntToStr(j)));
        end;
        exit;
      end;
    end;  
  end;
 if disable_nullip or (user.level>=NapUserModerator) then
   Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd+' '+IntToStr(user2^.ip))
 else
   Exec(user,MSG_SERVER_BROWSE_END,gcmd.cmd+' '+null_ip);
 total_browse_results:=total_browse_results+j;
 if log_browse then
 begin
  if local=nil then Log(slBrowse,GetLangT(LNG_BROWSELOG2,user^.username,user^.software,GetServerName(user^.server),l2.nick,IntToStr(j)))
  else Log(slBrowse,GetLangT(LNG_BROWSELOG1,local.nick,local.software,l2.nick,IntToStr(j)));
 end;
end;

procedure Handler_Queue;
var
 rec: PShare;
 str: String;
 size: Integer;
 user2: POnlineUser;
 l2: TLocalUser;
begin
 tmp_pos:=440;
 if not isLogged then exit;
 if (query<>queryNormal) and (query<>queryRemoteUser) then exit;
 if user=nil then exit;
 if not CheckParams(2) then exit;
 tmp_pos:=441;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 tmp_pos:=442;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   user2^.server.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
   exit;
 end;
 l2:=user2^.local;
 tmp_pos:=443;
 if (l2=nil) or (l2.shared=nil) then
 begin
  str:=hlist.Strings[1];
  size:=0;
 end
 else
 begin
  rec:=l2.shared.FindRec(hlist.Strings[1]);
  if rec=nil then
  begin
   str:=hlist.Strings[1];
   size:=0;
  end
  else
  begin
   str:=hlist.Strings[1];
   size:=rec^.size;
  end;
 end;
 tmp_pos:=444;
 str:=user^.username+' "'+str+'" '+IntToStr(size)+' ';
 if hlist.Count<3 then str:=str+'0'
 else str:=str+hlist.Strings[2];
 if l2=nil then
  Exec(user2,MSG_SERVER_LIMIT,str)
 else
  l2.Exec(MSG_SERVER_LIMIT,str)
end;

procedure Handler_Friends;
var
 str: String;
 p: PStringHashItem;
 user2: POnlineUser;
begin
  tmp_pos:=450;
  if not isLogged then exit;
  if not CheckLevel('',napUserAdmin) then exit;
  if not CheckParams(1) then
  begin
   if query=queryChannel then Error(STR_CHSTR_A_FRIENDS);
   exit;
  end;
  tmp_pos:=451;
  str:=lowercase(FirstParam(gcmd.cmd));
  if str='add' then
  begin
    str:=trim(AnsiLowerCase(NextParamEx(gcmd.cmd)));
    if str<>'' then
     if not StrHash_FindString(db_Friends,str,false) then
     begin
       StrHash_Add(db_friends,str);
       Wallop(MSG_SERVER_NOSUCH,wallopFriends,GetLangT(LNG_ADDFRIEND,user^.username,str),true);
     end;
  end else if str='remove' then
  begin
    str:=trim(AnsiLowerCase(NextParamEx(gcmd.cmd)));
    if StrHash_Delete(db_friends,str,false) then
     Wallop(MSG_SERVER_NOSUCH,wallopFriends,GetLangT(LNG_REMOVEFRIEND,user^.username,str),true);
  end else if str='list' then
  begin
    p:=db_friends.first;
    while p<>nil do
    begin
     Error('Friend:        '#9#9+'Time online:');
     p:=db_friends.first;
     while p<>nil do
     begin
      str:=p^.data;
      user2:=db_online.FindUser(p^.data);
      if user2<>nil then str:=str+'    '#9#9+(Time2Str(current_time_t-user2^.last_seen_t));
      Error(str);
      p:=p^.next;
     end;
     Error('.');
    end;
  end else
  begin
   if query=queryChannel then
    Error(STR_CHSTR_A_FRIENDS)
   else
    Error(GetLangT(LNG_INVALIDARGS),true);
  end;
end;

procedure Handler_JoinChannel;
var
 i,j: Integer;
 ch: TChannel;
 str: String;
 s: PNapCmdEx;
begin
 tmp_pos:=460;
 if db_channels=nil then exit;
 if not isLogged then exit;
 if user=nil then exit;
 if local<>nil then
 begin
   tmp_pos:=461;
   str:=gcmd.cmd;
   if isDigit(str) then // fixing Napster 9.6+ bug
   for i:=db_invitations.count-1 downto 0 do
   begin
     s:=db_invitations.Items[i];
     if s^.data=local.nick then
     begin
       str:=s^.cmd;
       db_invitations.Delete(i);
     end
     else
      if (GetTickCount-s^.id)>EXPIRE_INVITATION then
       db_invitations.Delete(i);
   end;
   str:=channelname(str);
   tmp_pos:=462;
   if (str='') or (str='#') then // invalid channel
   begin
     Error(GetLangT(LNG_INVALIDCHANNEL),true);
     exit;
   end;
   if userMuzzled in user^.state then
   begin
     Error(GetLangT(LNG_CANTTALK),true);
     exit;
   end;
   tmp_pos:=463;
   ch:=FindChannel(str);
   if ch=nil then
   begin
     tmp_pos:=464;
     if (user^.level<napUserModerator) and (allow_create_channels=false) then
     begin
       Error(GetLangT(LNG_NOCREATE),true);
       exit;
     end;
     if db_channels.count>=max_channels_total then
     begin
       Error(GetLangT(LNG_CHANNELLIMIT),true);
       exit;
     end;
     tmp_pos:=465;
     if user^.level<napUserModerator then
     begin
       j:=0;
       for i:=0 to db_channels.count-1 do
       begin
         ch:=db_channels.Items[i];
         if ch.FindUser(user)<>-1 then inc(j);
       end;
       if j>=max_channels then
       begin
         Error(GetLangT(LNG_CHANNELLIMIT),true);
         exit;
       end;
     end;
     tmp_pos:=466;
     ch:=TChannel.Create(str);
     db_channels.Add(ch);
   end
   else
   begin
     tmp_pos:=467;
     if ch.level>user^.level then
     begin
       PermissionDenied('',true);
       exit;
     end;
     tmp_pos:=468;
     if user^.level<napUserModerator then
      if ch.Banned(user^.username,decode_ip(user^.ip)) then
      begin
        PermissionDenied('',true);
        exit;
      end;
     tmp_pos:=469;
     if not ch.Operator(user) then
     begin
       if ch.users.count>=ch.limit then
       begin
         Error(GetLangT(LNG_CHANNELLIMIT2),true);
         exit;
       end;
       j:=0;
       tmp_pos:=470;
       for i:=0 to db_channels.count-1 do
         if TChannel(db_channels.Items[i]).FindUser(user)<>-1 then inc(j);
       if j>=max_channels then
       begin
         Error(GetLangT(LNG_CHANNELLIMIT),true);
         exit;
       end;
     end;
   end;
   tmp_pos:=471;
 end
 else
 begin
   tmp_pos:=472;
   ch:=FindChannel(gcmd.cmd);
   if ch=nil then
   begin
     ch:=TChannel.Create(gcmd.cmd);
     db_channels.Add(ch);
   end;
 end;
 tmp_pos:=473;
 ch.Join(user);
end;

procedure Handler_PartChannel;
var
 ch: TChannel;
 i: Integer;
begin
 tmp_pos:=480;
 ch:=FindChannel(gcmd.cmd);
 if ch<>nil then
 begin
   tmp_pos:=481;
   ch.Part(user);
   tmp_pos:=482;
   if ch.users.count=0 then
    if not (chRegistered in ch.state) then
     for i:=db_channels.count-1 downto 0 do
      if db_channels.Items[i]=ch then
      begin
        db_channels.Delete(i);
        ch.Free;
      end;
   tmp_pos:=483;
 end;
end;

procedure Handler_ChannelServerCommand(args: String;count: Integer);
var
 moderator, admin, elite, console: Boolean;
 list: TMyStringList;
 action, str, str2, last_bw_up, last_bw_down: String;
 i,j: Integer;
 user2: POnlineUser;
 srv: TServer;
begin
 moderator:=user^.level>napUserUser;
 if not moderator then
 begin
  PermissionDenied('',true);
  exit;
  end;
 admin:=user^.level>napUserModerator;
 elite:=user^.level>napUserAdmin;
 //console:=user^.level=napUserConsole;
 list:=CreateStringList;
 SplitString(args,list);
 if list.count<1 then list.Add('help');
 count:=list.Count-1;
 action:=lowercase(list.Strings[0]);
 args:=NextParamEx(args);
 gcmd.cmd:=args;
 FreeStringList(list);
 if action='version' then
 begin
   if num_servers=0 then Error(SLAVANAP_FULL)
   else begin
     Error(serveralias+'      '#9+SLAVANAP_FULL);
     for i:=0 to db_servers.count-1 do
     begin
       srv:=db_servers.Items[i];
       if srv.logged then
        Error(srv.host+'      '#9+srv.version);
     end;
     Error('.');
   end;
 end else if action='alias' then
  gcmd.id:=MSG_CLIENT_ALIAS 
 else if action='lag' then
 begin
   for i:=0 to db_servers.count-1 do
   begin
     srv:=db_servers.Items[i];
     if srv.logged then
      if srv.hub=nil then
       Error(srv.host+'      '#9+GetLangT(LNG_SLIST_LAGSEC,srv.CountLag));
   end;
   Error('.');
 end else if action='allowed' then
  gcmd.id:=MSG_CLIENT_ALLOWEDLIST
 else if action='list' then
  begin
   if not elite then
   begin
    PermissionDenied('',true);
    exit;
  end;
  for i:=0 to db_servers.Count-1 do
  begin
    srv:=db_servers.Items[i];
    str:=srv.host+':'+IntToStr(srv.port);
    if srv.authentication=authResolve then str2:=GetLangI(LNG_SRV_RESOLVE) else str2:=GetLangI(LNG_SRV_PASSWORD);
    Error(str+'           '#9+str2+'       '#9+srv.alias);
  end;
 end else if (action='connect') or (action='link') then
 begin
   if count<1 then
    Error(STR_CHSTR_S_CONNECT)
   else
    gcmd.id:=MSG_CLIENT_CONNECT;
 end else if (action='remove') or (action='kill') then
 begin
   if count<1 then
    Error(STR_CHSTR_S_REMOVE)
   else
    gcmd.id:=MSG_CLIENT_REMOVE_SERVER;
 end else if action='setpasswords' then
  gcmd.id:=MSG_CLIENT_SETSERVERPASSWORDS
 else if action='restart' then
  gcmd.id:=MSG_CLIENT_RESTART
 else if action='reboot' then
 begin
   if count<1 then exit;
   gcmd.id:=MSG_CLIENT_REBOOT;
 end else if (action='disconnect') or (action='delink') then
 begin
   if count<1 then
    Error(STR_CHSTR_S_DISCONNECT)
   else
    gcmd.id:=MSG_CLIENT_DISCONNECT;
 end else if action='links' then
   gcmd.id:=MSG_CLIENT_LINKS
 else if action='console' then
  gcmd.id:=MSG_CLIENT_GETCONSOLE
 else if action='reconnectors' then
 begin
   Error('db_reconnect:');
   for i:=0 to db_reconnect.count-1 do
    Error('  '+db_reconnect.Strings[i]);
   Error('.');
 end else if action='stats' then
 begin
   if not moderator then
     PermissionDenied('',true)
   else
   begin
     Error(servername_t+' - '+SLAVANAP_VERSION_SHORT+' (b'+SLAVANAP_BUILD+') の統計');
     Error('ローカル: '+IntToStr(local_users)+'/'+IntToStr(max_users)+'ユーザー, '+IntToStrDot(local_files)+'ファイル, '+IntToStrDot(local_bytes div GigaByte)+' GB');
     Error('ローカル平均: '+IntToStr(local_files div local_users)+'ファイル/ユーザー, '+IntToStrDot((local_bytes div GigaByte) div local_users)+' GB/ユーザー');
     Error('ネットワーク: '+IntToStr(total_users)+'/'+IntToStr(total_users_limit)+'ユーザー, '+IntToStrDot(total_files)+'ファイル, '+IntToStrDot(total_bytes div GigaByte)+' GB');
     Error('ネットワーク平均: '+IntToStr(total_files div total_users)+'ファイル/ユーザー, '+IntToStrDot((total_bytes div GigaByte) div total_users)+' GB/ユーザー');
     Error('ユーザー数の最高記録: '+IntToStr(local_users_max)+'(ローカル), '+IntToStr(total_users_max)+'(ネットワーク)');
     Error('ファイル数の最高記録: '+IntToStrDot(local_files_max)+'(ローカル), '+IntToStrDot(total_files_max)+'(ネットワーク)');
     Error('サイズ(GB)の最高記録: '+IntToStrDot(local_bytes_max div GigaByte)+'(ローカル), '+IntToStrDot(total_bytes_max div GigaByte)+'(ネットワーク)');
     Error('接続回数'+IntToStrDot(total_connections)+', 転送回数'+IntToStrDot(total_transfers+num_transfers));
     Error('検索回数'+IntToStrDot(total_searches+num_searches)+', 有効検索回数'+IntToStrDot(successful_searches)+', 合計ヒット数'+IntToStrDot(total_search_results));
     Error('参照回数'+IntToStrDot(total_browses)+', 参照ファイル数'+IntToStrDot(total_browse_results)+'ファイル');
     Error('使用中のソケット'+IntToStr(sockets_count)+'個');
     Error('リンクサーバー:'+IntToStr(num_servers)+'台');
     if win98 then
      Error('メモリ使用量: '+IntToStrDot(AllocMemSize)+'バイト');
     j:=(GetTickCount-start_time) div 60000;
     str:=IntToStr(j mod 60)+'分.';
     if j>59 then
     begin
       j:=j div 60;
       str:=IntToStr(j mod 24)+'時間, '+str;
       if j>23 then
         str:=IntToStr(j div 24)+'日, '+str;
     end;
     Error('稼働時間: '+str);
     Error('帯域使用量の合計: '+IntToStrDot(total_bytes_in+bytes_in)+'バイト受信, '+IntToStrdot(total_bytes_out+bytes_out)+'バイト送信.');
     last_bw_up:=IntToStr((last_bytes_out div Kilobyte) div 60)+'.'+IntToStr(((last_bytes_out div KiloByte) div 6) mod 10);
     last_bw_down:=IntToStr((last_bytes_in div KiloByte) div 60)+'.'+IntToStr(((last_bytes_in div Kilobyte) div 6) mod 10);
     Error('最後の1分間で: '+last_bw_down+' KB/s受信, '+last_bw_up+' KB/s送信');
     Error('.');
   end;
 end else if action='ts' then
 begin
   if not admin then
    PermIssionDenied('',true)
   else
    begin
     if true_stats=true then str:='yes' else str:='no';
     Error(servername_t+'    '+str);
     for i:=0 to db_servers.count-1 do
     begin
      srv:=db_servers.Items[i];
      if srv.truestats=true then str:='yes' else str:='no';
      Error(srv.host+'    '+str);
     end;
    end;
 end else if action='pingall' then
 begin
   if not moderator then
     PermissionDenied('',true)
   else
   begin
     str:=user^.username+' pingall '+query_channel+' '+IntToStr(GetTickCount);
     cons.Exec(MSG_SERVER_PING,str);
     Error(servername_t+'にpingしています');
     str:=IntToStr(myserverhandle)+' '+IntToStr(GetTickCount)+' pingall '+user^.username+' '+query_channel;
     for i:=0 to db_servers.count-1 do
     begin
       srv:=db_servers.Items[i];
       if srv.logged then
       begin
        srv.Exec(MSG_CLIENT_PING_ALL_SERVERS,str);
        Error(srv.host+'にpingしています');
       end;
     end;
     {for i:=0 to db_servers.count-1 do
     begin
       srv:=db_servers.Items[i];
       if srv.logged then
       begin
         user2:=db_online.FindUser(srv.console);
         if user2<>nil then
         begin
          Exec(user2,MSG_SERVER_PING,str);
          Error(srv.host+'にpingしています');
         end;
       end;
     end;}
   end;
 end else if action='ping' then
 begin
   if not moderator then
     PermissionDenied('',true)
   else if count<1 then
     Error(STR_CHSTR_S_PING)
   else
   begin
     SplitString(lowercase(args),hlist);
     str:=user^.username+' pingall '+query_channel+' '+IntToStr(GetTickCount);
     for j:=0 to hlist.count-1 do
     begin
       if MatchesMaskEx(servername_t,hlist.Strings[j]) or MatchesMaskEx(serveralias,hlist.Strings[j]) then
       begin
         cons.Exec(MSG_SERVER_PING,str);
         Error(servername_t+'にpingしています');
       end;
       for i:=0 to db_servers.count-1 do
       begin
         srv:=db_servers.Items[i];
         if srv.logged then
         if MatchesMaskEx(srv.host,hlist.Strings[j]) or MatchesMaskEx(srv.alias,hlist.Strings[j]) then
         begin
           user2:=db_online.FindUser(srv.console);
           if user2<>nil then
           begin
            Exec(user2,MSG_SERVER_PING,str);
            Error(srv.host+'にpingしています');
           end;
         end;
       end;
     end;
   end;
 end else if action='help' then
 begin
   if local<>cons then
    Error(STR_CHHLP_MAIN);
   Error(STR_CHHLP_HELPLISTSERVER);
   if admin then Error(STR_CHHLP_S_ALLOWED);
   if admin then Error(STR_CHHLP_S_CONNECT);
   Error(STR_CHHLP_S_CONSOLE);
   if admin then Error(STR_CHHLP_S_DISCONNECT);
   Error(STR_CHHLP_S_LAG); // they must be mod now
   Error(STR_CHHLP_S_LINKS);
   if elite then Error(STR_CHHLP_S_LIST);
   Error(STR_CHHLP_S_PING);
   Error(STR_CHHLP_S_PINGALL);
   if elite then Error(STR_CHHLP_S_REBOOT);
   if elite then Error(STR_CHHLP_S_REMOVE);
   if elite then Error(STR_CHHLP_S_RESTART);
   if local=cons then Error(STR_CHHLP_S_SETPASSWORDS);
   Error(STR_CHHLP_S_STATS);
   Error(STR_CHHLP_S_VERSION);
   Error('.');
 end
 else
   Error(Format(STR_CHSTR_UNKNOWNSERVER,[action]));
end;

procedure Handler_ChannelAdmin(args: String;count: Integer);
var
 moderator, admin, elite, console: Boolean;
 list: TMyStringList;
 action, str: String;
 i,j: Integer;
 user2: POnlineUser;
 l1: TLocalUser;
 reg: PRegisteredUser;
begin
 moderator:=user^.level>napUserUser;
 admin:=user^.level>napUserModerator;
 elite:=user^.level>napUserAdmin;
 //console:=user^.level=napUserConsole;
 list:=CreateStringList;
 SplitString(args,list);
 if list.count<1 then list.Add('help');
 count:=list.Count-1;
 action:=lowercase(list.Strings[0]);
 args:=NextParamEx(args);
 gcmd.cmd:=args;
 FreeStringList(list);
 if not moderator then
 begin
  PermissionDenied('',true);
  exit;
  end;
 if action='debug' then
 begin
   //if not moderator then
   //  PermissionDenied('',true)
   //else
   //begin
   //l1:=user^.local;
     Error('db_online items = '+IntToStr(db_online.CountItems));
     Error('db_local.count = '+IntToStr(db_local.count));
     Error('db_local.capacity = '+IntToStr(db_local.capacity));
     Error('db_servers.count = '+IntToStr(db_servers.count));
     Error('db_registered items = '+IntToStr(db_registered.CountUsers));
     Error('db_bans.count = '+IntToStr(db_bans.count));
     Error('cmd_list.count = '+IntToStr(cmd_list.count));
     Error('sync_reply_list.count = '+IntToStr(sync_reply_list.count));
     Error('num_servers = '+IntToStr(num_servers));
     Error('direct_links = '+IntToStr(direct_links));
     str:=decode_ip(user^.ip);
     Error(str);
     if IPisLocal(str) then error('yep') else error('nope');
     Error(':-b');
   //end;
 end else if action='register' then
 begin
   if count<1 then
    Error(STR_CHSTR_A_REGISTER)
   else
    gcmd.id:=MSG_CLIENT_REGISTER_USER;
 end else if action='wallop' then
   gcmd.id:=MSG_CLIENT_WALLOP
 else if action='announce' then
   gcmd.id:=MSG_CLIENT_ANNOUNCE
 else if (action='clientblocks') or (action='cb') then
   gcmd.id:=MSG_CLIENT_CLIENTBLOCKS
 else if action='ban' then
 begin
   if count<1 then
     Error(STR_CHSTR_A_BAN)
   else
     gcmd.id:=MSG_CLIENT_BAN;
 end else if action='unban' then
 begin
   if count<1 then
     Error(STR_CHSTR_A_UNBAN)
   else
     gcmd.id:=MSG_CLIENT_UNBAN;
 end else if action='kill' then
 begin
   if count<1 then
     Error(STR_CHSTR_A_KILL)
   else
     gcmd.id:=MSG_CLIENT_KILL;
 end else if (action='unregister') or (action='unreg') then
 begin
   if count<1 then
     Error(STR_CHSTR_A_UNREGISTER)
   else
     gcmd.id:=MSG_CLIENT_UNREGISTER;
 end else if action='nuke' then
 begin
   if count<1 then
     Error(STR_CHSTR_A_NUKE)
   else
     gcmd.id:=MSG_CLIENT_NUKE;
 end else if action='level' then
 begin
   if count<2 then
     Error(STR_CHSTR_A_LEVEL)
   else
     gcmd.id:=MSG_CLIENT_SETUSERLEVEL;
 end else if (action='reglist') or (action='rl') then
 begin
  if not admin then
   PermissionDenied('',true)
  else
  for i:=0 to USERS_NAME_ARRAY-1 do
   for j:=0 to db_registered.list[i].Count-1 do
   begin
     reg:=db_registered.list[i].Items[j];
     //Error(reg^.nick+'          '#9+(Level2Str(reg^.level)));
     str:=reg^.nick+'          '#9+(Level2Str(reg^.level));
     user2:=db_online.FindUser(reg^.nick);
     if user2<>nil then str:=str+'    '#9#9+GetServerName(user2^.server);
     Error(str);
    end;
    Error('.');
 end else if action='muzzle' then
 begin
   if count<1 then
     Error(STR_CHSTR_A_MUZZLE)
   else
     gcmd.id:=MSG_CLIENT_MUZZLE;
 end else if action='unmuzzle' then
 begin
   if count<1 then
     Error(STR_CHSTR_A_UNMUZZLE)
   else
     gcmd.id:=MSG_CLIENT_UNMUZZLE;
 end else if action='cloak' then
   gcmd.id:=MSG_CLIENT_CLOAK
 else if action='banlist' then
   gcmd.id:=MSG_CLIENT_BANLIST
 else if action='software' then
   gcmd.id:=MSG_CLIENT_VERSION_STATS
 else if action='set' then
   gcmd.id:=MSG_CLIENT_SERVER_CONFIG
 else if action='setremote' then
   gcmd.id:=MSG_CLIENT_SHOWSETTING
 else if action='setallremote' then
   gcmd.id:=MSG_CLIENT_SHOWALLSETTINGS
 else if action='redirect' then
 begin
   if not elite then
     PermissionDenied('',true)
   else if count<2 then
     Error(STR_CHSTR_A_REDIRECT)
   else
     for j:=0 to USERS_NAME_ARRAY-1 do
     for i:=0 to db_online.names[j].Count-1 do
     begin
       user2:=db_online.names[j].Items[i];
        if user2^.server=nil then
         if user2^.level<>napUserConsole then
          Exec(user2,MSG_CLIENT_REDIRECT,gcmd.cmd);
     end;
 end else if action='cycle' then
 begin
   if not elite then
     PermissionDenied('',true)
   else if count<1 then
     Error(STR_CHSTR_A_CYCLE)
   else
     for j:=0 to USERS_NAME_ARRAY-1 do
     for i:=0 to db_online.names[j].Count-1 do
     begin
       user2:=db_online.names[j].Items[i];
       if user2^.server=nil then
        if user2^.level<>napUserConsole then
         Exec(user2,MSG_CLIENT_CYCLE,gcmd.cmd);
     end;
 end else if action='ip' then
 begin
 //  user2:=db_online.FindUser(gcmd.cmd);
//   if user2^.level>napUserUser then exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_IPREQMOD,AddStr(Level2Str(user^.level)),user^.nick,decode_ip(user^.ip)));
   if not moderator then
     PermissionDenied('',true)
   else if count<1 then
     Error(STR_CHSTR_A_IP)
   else
   begin
     user2:=db_online.FindUser(gcmd.cmd);
     if user2=nil then
       UserIsOffline(gcmd.cmd)
     else
       Error(user2^.username+'のIPは '+decode_ip(user2^.ip)+' です。');
   end;
 end else if action='servermsg' then
  gcmd.id:=MSG_CLIENT_PRIVMSG_ANON
 else if action='friends' then
 begin
   if not admin then
     PermissionDenied('',true)
   else if count<1 then
     Error(STR_CHSTR_A_FRIENDS)
   else
     gcmd.id:=MSG_CLIENT_FRIENDS;
 end else if action='block' then
   gcmd.id:=MSG_CLIENT_BLOCK
 else if action='blocks' then
   gcmd.id:=MSG_CLIENT_BLOCKLIST
 else if action='unblock' then
   gcmd.id:=MSG_CLIENT_UNBLOCK
 else if action='blocked' then
 begin
  if user^.level<napUserAdmin then
   PermissionDenied('')
  else
  begin
   if not block_allowshare then
    Error('注意: ユーザーは除外されたファイルを共有できません。除外ファイルはカウントされません。');
   if not block_allowshare_admins then
    Error('注意: mods+は除外されたファイルを共有できません。除外ファイルはカウントされません。');
   j:=0;
   for i:=0 to db_local.count-1 do
   begin
     l1:=db_local.Items[i];
     if l1.logged then
      if l1.shared_blocked>0 then
      begin
       str:=Level2Str(l1.level)+' '+l1.nick+' ('+decode_ip(l1.ip)+') が'+IntToStr(l1.shared_blocked)+'個の除外ファイルを共有しようとしました。';
       inc(j,l1.shared_blocked);
       Error(str);
      end;
   end;
   Error(GetLangT(LNG_ITEMSLISTED,IntToStr(j)));
  end;
 end else if action='help' then
 begin
   if local<>cons then
    Error(STR_CHHLP_MAIN);
   Error(STR_CHHLP_HELPLISTADMIN);
   if admin then Error(STR_CHHLP_A_ANNOUNCE);
   if moderator then Error(STR_CHHLP_A_BAN);
   if moderator then Error(STR_CHHLP_A_BANLIST);
   if admin then Error(STR_CHHLP_A_BLOCK);
   if admin then Error(STR_CHHLP_A_BLOCKED);
   if admin then Error(STR_CHHLP_A_BLOCKS);
   if moderator then Error(STR_CHHLP_A_CLIENTBLOCKS);
   if moderator then Error(STR_CHHLP_A_CLOAK);
   if elite then Error(STR_CHHLP_A_CYCLE);
   if admin then Error(STR_CHHLP_A_FRIENDS);
   if moderator then Error(STR_CHHLP_A_IP);
   if moderator then Error(STR_CHHLP_A_KILL);
   if moderator then Error(STR_CHHLP_A_LEVEL);
   if moderator then Error(STR_CHHLP_A_MUZZLE);
   if moderator then Error(STR_CHHLP_A_NUKE);
   if elite then Error(STR_CHHLP_A_REDIRECT);
   if moderator then Error(STR_CHHLP_A_REGISTER);
   if admin then Error(STR_CHHLP_A_REGLIST);
   if elite then Error(STR_CHHLP_A_SERVERMSG);
   if elite then Error(STR_CHHLP_A_VARIABLE)
   else if admin then Error(STR_CHHLP_A_VARIABLE2);
   if elite then Error(STR_CHHLP_A_REMOTEVARIABLE)
   else if admin then Error(STR_CHHLP_A_REMOTEVARIABLE2);
   if elite then Error(STR_CHHLP_A_ALLREMOTEVARIABLE)
   else if admin then Error(STR_CHHLP_A_ALLREMOTEVARIABLE2);
   if moderator then Error(STR_CHHLP_A_SOFTWARE);
   if moderator then Error(STR_CHHLP_A_UNBAN);
   if admin then Error(STR_CHHLP_A_UNBLOCK);
   if moderator then Error(STR_CHHLP_A_UNMUZZLE);
   if moderator then Error(STR_CHHLP_A_UNREGISTER);
   if moderator then Error(STR_CHHLP_A_WALLOP);
   Error('.');
 end
 else
   Error(Format(STR_CHSTR_UNKNOWNADMIN,[action]));
end;

procedure Handler_ChannelCommand(command: String);
var
 action, args, str: String;
 t,i,j,k,count: Integer;
 admin,moderator,operator,add_ch: Boolean;
 ch: TChannel;
 p: PStringHashItem;
 user2: POnlineUser;
 kw: PKeyword;
 kwl: PKeywordList;
begin
 ch:=Findchannel(query_channel);
 if ch=nil then exit;
 operator:=ch.Operator(user);
 moderator:=user^.level>napUserUser;
 admin:=user^.level>napUserModerator;
 SplitString(command,hlist);
 if hlist.count<1 then
  action:='>help'
 else
  action:=lowercase(hlist.Strings[0]);
 if Length(action)<2 then action:='>help';
 if hlist.count>1 then
  args:=NextParamEx(command)
 else
  args:='';
 count:=hlist.Count-1;
 if count<0 then count:=0;
 gcmd.id:=0;
 gcmd.cmd:=args;
 add_ch:=true;
 action:=copy(action,2,length(action));
 if isdigit(action) then
 begin
   args:=action+' '+args;
   inc(count);
   action:='raw';
 end;
 if action='raw' then
 begin
   SplitString(args,hlist);
   if hlist.count>0 then
   begin
     gcmd.id:=StrToIntDef(hlist.Strings[0],0);
     if gcmd.id<1 then exit;
     gcmd.cmd:=NextParamEx(args);
     ProcessCommand(local,queryChannel);
     exit;
   end;
   action:='help';
 end;
 if (action='admin') or (action='a') then
 begin
   Handler_ChannelAdmin(args,count);
   add_ch:=false;
 end else if (action='server') or (action='s') then
 begin
   Handler_ChannelServerCommand(args,count);
   add_ch:=false;
 end else if action='kw' then // keywords debug
 begin
   if user^.level<napUserModerator then exit;
   if Length(args)<2 then exit;
   j:=GetKeywordIndex(args);
   k:=Length(args);
   if j=-1 then exit;
   for i:=0 to SHARED_ARRAY-1 do
   begin
     kwl:=db_keywords[i,j,k];
     kw:=kwl^.first;
     while kw<>nil do
     begin
       if kw^.keyword=args then
        Error('i='+IntToStr(i)+' keyword='+kw^.keyword+' count='+IntToStr(kw^.count)+' total='+IntToStr(kw^.total)+' available='+IntToStr(kw^.available));
       kw:=kw^.next;
     end;
    end;
   Error('.');
 end else if (action='opsay') or (action='o') then
   gcmd.id:=MSG_CLIENT_CHANNEL_WALLOP
 else if (action='opme') or (action='opemote') then
   gcmd.id:=MSG_CLIENT_CHANNEL_OPEMOTE
 else if (action='me') or (action='emote') then
   gcmd.id:=MSG_CLIENT_EMOTE
 else if action='say' then
   gcmd.id:=0 // do nothing
 else if action='wallop' then
 begin
   gcmd.id:=MSG_CLIENT_WALLOP;
   add_ch:=false;
 end else if (action='listfriends') or (action='lf') then
 begin
  if user^.level<napUserAdmin then
  begin
    PermissionDenied('',true);
    exit;
  end;
  Error('友人:        '#9#9+'オンライン時間:');
  p:=db_friends.first;
  while p<>nil do
  begin
    str:=p^.data;
    user2:=db_online.FindUser(p^.data);
    if user2<>nil then str:=str+'    '#9#9+(Time2Str(current_time_t-user2^.last_seen_t));
    Error(str);
    p:=p^.next;
  end;
  Error('.');
 end else if action='announce' then
 begin
   gcmd.id:=MSG_CLIENT_ANNOUNCE;
   add_ch:=false;
 end else if (action='usermode') or (action='um') then
 begin
   gcmd.id:=MSG_CLIENT_USER_MODE;
   add_ch:=false;
 end else if (action='showserver') or (action='ss') then
 begin
   if count<1 then
    Error(STR_CHSTR_SHOWSERVER)
   else
    gcmd.id:=MSG_CLIENT_WHICH_SERVER;
   add_ch:=false;
 end else if action='banlist' then
   gcmd.id:=MSG_CLIENT_CHANNEL_BAN_LIST
 else if action='ban' then
 begin
   if count<1 then
     Error(STR_CHSTR_BAN)
   else
     gcmd.id:=MSG_CLIENT_CHANNEL_BAN;
 end else if action='banip' then
 begin
   if count<1 then
     Error(STR_CHSTR_BANIP)
   else
     gcmd.id:=MSG_CLIENT_CHANNEL_BANIP;
 end else if action='unban' then
 begin
   if count<1 then
     Error(STR_CHSTR_UNBAN)
   else
     gcmd.id:=MSG_CLIENT_CHANNEL_UNBAN;
 end else if action='banclear' then
   gcmd.id:=MSG_CLIENT_CHANNEL_CLEAR_BANS
 else if action='banlist' then
   gcmd.id:=MSG_CLIENT_CHANNEL_BAN_LIST
 else if action='op' then
 begin
   if count<1 then
     Error(STR_CHSTR_OP)
   else
     gcmd.id:=MSG_CLIENT_OP;
 end else if action='deop' then
 begin
   if count<1 then
     Error(STR_CHSTR_DEOP)
   else
     gcmd.id:=MSG_CLIENT_DEOP;
 end else if action='oplist' then
 begin
  if not moderator then
    PermissionDenied('',true)
  else
  begin
    p:=ch.ops.first;
    while p<>nil do
    begin
      Error(p^.data);
      p:=p^.next;
    end;
    Error('.');
  end;
 end else if action='voice' then
 begin
   if count<1 then
     Error(STR_CHSTR_VOICE)
   else
     gcmd.id:=MSG_CLIENT_CHANNEL_VOICE;
 end else if action='unvoice' then
 begin
   if count<1 then
     Error(STR_CHSTR_UNVOICE)
   else
     gcmd.id:=MSG_CLIENT_CHANNEL_UNVOICE;
 end else if action='kick' then
 begin
   if count<1 then
     Error(STR_CHSTR_KICK)
   else
     gcmd.id:=MSG_CLIENT_KICK;
 end else if action='mode' then
   gcmd.id:=MSG_CLIENT_CHANNEL_MODE
 else if (action='whois') or (action='w') then
 begin
   gcmd.id:=MSG_CLIENT_WHOIS;
   add_ch:=false;
 end else if action='topic' then
   gcmd.id:=MSG_SERVER_TOPIC
 else if action='drop' then
   gcmd.id:=MSG_CLIENT_DROP_CHANNEL
 else if action='remove' then
 begin
   if count<1 then
     Error(STR_CHSTR_REMOVE)
   else
     gcmd.id:=MSG_CLIENT_DROP_CHANNEL;
   add_ch:=false;
 end else if action='clear' then
   gcmd.id:=MSG_CLIENT_CLEAR_CHANNEL
 else if action='part' then
   gcmd.id:=MSG_CLIENT_PART
 else if action='msg' then
 begin
   gcmd.id:=MSG_CLIENT_PRIVMSG;
   add_ch:=false;
 end else if action='limit' then
 begin
   if count<1 then
     Error(GetLangT(LNG_CHLIMIT,ch.limit))
   else
     gcmd.id:=MSG_CLIENT_CHANNEL_LIMIT;
 end else if action='level' then
 begin
   if count<1 then
     Error(GetLangT(LNG_CHLEVEL2,Level2Str(ch.level)))
   else
     gcmd.id:=MSG_CLIENT_SET_CHAN_LEVEL
 end else if action='invite' then
 begin
   if count<1 then
     Error(STR_CHSTR_INVITE)
   else
     gcmd.id:=MSG_CLIENT_CHANNEL_INVITE_OPENNAP;
 end else if action='motd' then
 begin
   p:=ch.motd.first;
   while p<>nil do
   begin
     if Length(p^.data)<1 then
      Exec(user,MSG_SERVER_EMOTE,ch.channel+' Server ""')
     else
      if p^.data[1]<>';' then
       Exec(user,MSG_SERVER_EMOTE,ch.channel+' Server "'+FormatString(user,p^.data,true)+'"');
     p:=p^.next;
     end;
 end else if action='console' then
   Error(cons.data^.username)
 else if action='ghost' then
 begin
  gcmd.id:=MSG_CLIENT_GHOST;
  add_ch:=false;
 end else if action='version' then
  Error(SLAVANAP_FULL)
 else if action='help' then
 begin
   if local<>cons then
    Error(STR_CHHLP_MAIN);
   Error(STR_CHHLP_HELPLIST);
   //if admin then Error(STR_CHHLP_ADDFRIENDS);
   if moderator then Error(STR_CHHLP_ADMIN);
   if operator then Error(STR_CHHLP_BAN);
   if operator then Error(STR_CHHLP_BANCLEAR);
   if operator then Error(STR_CHHLP_BANIP);
   if operator then Error(STR_CHHLP_BANLIST);
   if moderator then Error(STR_CHHLP_CLEAR);
   if local=cons then Error(STR_CHHLP_CLS);
   Error(STR_CHHLP_CONSOLE);
   if moderator then Error(STR_CHHLP_DEOP);
   if moderator then Error(STR_CHHLP_DROP);
   Error(STR_CHHLP_EMOTE);
   Error(STR_CHHLP_GHOST);
   Error(STR_CHHLP_HELP);
   Error(STR_CHHLP_INVITE);
   if operator then Error(STR_CHHLP_KICK);
   if moderator then Error(STR_CHHLP_LEVEL);
   if admin then Error(STR_CHHLP_LISTFRIENDS);
   if moderator then Error(STR_CHHLP_LIMIT);
   if moderator then Error(STR_CHHLP_MODE);
   Error(STR_CHHLP_MSG);
   if moderator then Error(STR_CHHLP_OP);
   if moderator then Error(STR_CHHLP_OPLIST);
   if operator then Error(STR_CHHLP_OPEMOTE);
   if operator then Error(STR_CHHLP_OPSAY);
   Error(STR_CHHLP_PART);
   if moderator then Error(STR_CHHLP_RAW);
   //if admin then Error(STR_CHHLP_REMFRIENDS);
   if moderator then Error(STR_CHHLP_REMOVE);
   if moderator then Error(STR_CHHLP_SERVER);
   if moderator then Error(STR_CHHLP_SHOWSERVER);
   if (chTopic in ch.state) or operator then Error(STR_CHHLP_TOPIC);
   if operator then Error(STR_CHHLP_UNBAN);
   if operator then Error(STR_CHHLP_UNVOICE);
   Error(STR_CHHLP_USERMODE);
   Error(STR_CHHLP_VERSION);
   if operator then Error(STR_CHHLP_VOICE);
   Error(STR_CHHLP_WHOIS);
   Error('.');
 end
 else
   Error(Format(STR_CHSTR_UNKNOWN,[action]));
 if gcmd.id<>0 then
 begin
   if add_ch then gcmd.cmd:=Trim(ch.channel+' '+gcmd.cmd);
   ProcessCommand(local,queryChannel);
 end;
end;

function CheckFlood(ch: TChannel; text: String): Boolean;
var
 i,user_message, same_message: Integer;
 t: Cardinal;
 str: String;
 data: TNapCmdEx;
 usr: POnlineUser;
begin
 t:=GetTickCount;
 Result:=true;
 if not flood_enable then exit;
 if ch=nil then exit;
 if local=nil then exit;
 if user^.level>napUserUser then exit; // mods+ can bypass flood control
 user_message:=0;
 same_message:=0;
 ch.history.AddCmd(t,local.nick,text);
 for i:=ch.history.Count-1 downto 0 do
 begin
   data:=ch.history.CmdEx(i);
   if (t-data.id)>10000 then
    ch.history.Delete(i)
   else
    if data.cmd=local.nick then
    begin
      inc(user_message);
      if data.data=text then inc(same_message);
    end;
 end;
 if same_message>flood_max_same_message then Result:=false
 else if user_message>flood_max_user_message then
 begin
   if flood_warning then
    if not (locFloodWarning in local.localstate) then
    begin
      local.localstate:=local.localstate+[locFloodWarning];
      local.Exec(MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_FLOODWARN));
      for i:=ch.history.count-1 downto 0 do
       if ch.history.CmdEx(i).cmd=local.nick then
        ch.history.Delete(i);
      exit;
    end;
   Result:=false;
 end;
 if Result=false then
 begin
   for i:=ch.history.count-1 downto 0 do
    if ch.history.CmdEx(i).cmd=local.nick then
     ch.history.Delete(i);
   local.localstate:=local.localstate-[locFloodWarning];
   local.Exec(MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_FLOODMUZZLE1));
   str:=ch.channel+' Server '+GetLangT(LNG_FLOODMUZZLE2,user^.username);
   for i:=0 to ch.users.count-1 do
   begin
     usr:=ch.users.Items[i];
     if (usr^.server=nil) and (usr<>user) then
      Exec(usr,MSG_SERVER_PUBLIC,str);
   end;
   user^.state:=user^.state+[userMuzzled];
   Wallop(MSG_SERVER_NOSUCH,wallopFlood,GetLangT(LNG_FLOODMUZZLE3,user^.username,serveralias,ch.channel),true);
   WriteAllServers(MSG_SRV_FLOODER,'',ch.channel+' '+user^.username);
   CompleteSyncUser(nil,user);
 end;
end;

procedure Handler_Flooder;
var
 usr: POnlineUser;
 ch: TChannel;
 str: String;
 i: Integer;
begin
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then exit;
 str:=ch.channel+' Server '+GetLangT(LNG_FLOODMUZZLE2,hlist.Strings[1]);
 for i:=0 to ch.users.count-1 do
 begin
   usr:=ch.users.Items[i];
   if usr^.server=nil then
    Exec(usr,MSG_SERVER_PUBLIC,str);
 end;
end;

procedure Handler_Public;
var
 ch: TChannel;
 usr: POnlineUser;
 str: String;
 vis: Boolean;
 i: Integer;
begin
 tmp_pos:=490;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 if query=queryChannel then exit;
 tmp_pos:=491;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then exit;
 if ch.FindUser(user)=-1 then exit;
 str:=NextParamEx(gcmd.cmd);
 if not allow_rtf_code and (Pos('{\rtf', str) > 0) then Exit;
 tmp_pos:=492;
 if trim(str)='' then exit;
 if prevent_shouting and (user^.level<napUserModerator) then
  if AnsiUpperCase(str)=str then
  begin
    str:=AnsiLowerCase(str);
    gcmd.cmd:=ch.channel+' '+str;
  end;
 if Length(str)>0 then
  if (str[1]='>') or (str[1]='~') then
  if query=queryNormal then
  begin
   query_channel:=ch.channel;
   query:=queryChannel;
   Handler_ChannelCommand(str);
   exit;
  end;
 tmp_pos:=493;
 if (user^.server=nil) and (userMuzzled in user^.state) then
 begin
   Error(GetLangT(LNG_CANTTALK),true);
   exit;
 end;
 if user^.server=nil then
  if not ch.Operator(user) then
   if chModerated in ch.state then
   if not StrHash_FindString(ch.voices,user^.username,true) then
   begin
     Error(GetLangT(LNG_CANTTALK),true);
     exit;
   end;
 tmp_pos:=494;
 if block_cqex_chat then BlockCQEXChat(str);
 if Length(str)>max_channelmsg_len then
  str:=Copy(str,1,max_channelmsg_len);
 if local<>nil then
  if not CheckFlood(ch,str) then exit;
 tmp_pos:=495;
 vis:=ch.Visible(user);
 for i:=0 to ch.users.count-1 do
 begin
   usr:=ch.users.Items[i];
   if usr^.server=nil then
   begin
    {if vis or (usr^.level>napUserUser) then
     Exec(usr,MSG_SERVER_PUBLIC,ch.channel+' '+user^.username+' '+str)
    else
     Exec(usr,MSG_SERVER_PUBLIC,ch.channel+' Operator '+str);}
    if vis then
     Exec(usr,MSG_SERVER_PUBLIC,ch.channel+' '+user^.username+' '+str)
    else if usr^.level<napUserModerator then
     Exec(usr,MSG_SERVER_PUBLIC,ch.channel+' Operator '+str)
    else
     Exec(usr,MSG_SERVER_PUBLIC,ch.channel+' Cloaked/'+user^.username+' '+str);
   end;
 end;
 tmp_pos:=496;
 if user^.server=nil then
  ch.WriteChannelServers(gcmd.id,user^.username+' '+gcmd.cmd);
end;

procedure Handler_Emote;
var
 ch: TChannel;
 usr: POnlineUser;
 str: String;
 vis: Boolean;
 i: Integer;
begin
 tmp_pos:=500;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 if (user^.server=nil) and (userMuzzled in user^.state) then
 begin
   Error(GetLangT(LNG_CANTTALK),true);
   exit;
 end;
 tmp_pos:=501;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then exit;
 if ch.FindUser(user)=-1 then exit;
 if hlist.count=1 then str:='""'
 else if hlist.count=2 then str:='"'+hlist.Strings[1]+'"'
 else str:='"'+NextParamEx(gcmd.cmd)+'"';
 if Length(str)=2 then exit;
 // if AnsiUpperCase(str)=AnsiLowerCase(str) then exit;
 tmp_pos:=502;
 if block_cqex_chat then BlockCQEXChat(str);
 if not allow_rtf_code and (Pos('{\rtf', str) > 0) then Exit;
 if Length(str)>max_channelmsg_len then
  str:=Copy(str,1,max_channelmsg_len);
 if prevent_shouting and (user^.level<napUserModerator) then
  if AnsiUpperCase(str)=str then
  begin
   str:=AnsiLowerCase(str);
   gcmd.cmd:=ch.channel+' '+str;
  end;
 if local<>nil then
  if not CheckFlood(ch,str) then exit;
 vis:=ch.Visible(user);
 tmp_pos:=503;
 for i:=0 to ch.users.count-1 do
 begin
   usr:=ch.users.Items[i];
   if usr^.server=nil then
   begin
    {if vis or (usr^.level>napUserUser) then
     Exec(usr,MSG_SERVER_EMOTE,ch.channel+' '+user^.username+' '+str)
    else
     Exec(usr,MSG_SERVER_EMOTE,ch.channel+' Operator '+str);}
    if vis then
     Exec(usr,MSG_SERVER_EMOTE,ch.channel+' '+user^.username+' '+str)
    else if usr^.level<napUserModerator then
     Exec(usr,MSG_SERVER_EMOTE,ch.channel+' Operator '+str)
    else
     Exec(usr,MSG_SERVER_EMOTE,ch.channel+' Cloaked/'+user^.username+' '+str);
   end;
 end;
 tmp_pos:=504;
 if user^.server=nil then
  ch.WriteChannelServers(gcmd.id,user^.username+' '+gcmd.cmd);
end;

procedure Handler_ListChannels;
var
 ch: TChannel;
 i: Integer;
begin
 tmp_pos:=510;
 if not isLogged then exit;
 if user=nil then exit;
 tmp_pos:=511;
 for i:=0 to db_channels.count-1 do
 begin
   ch:=db_channels.Items[i];
   if (not (chPrivate in ch.state)) or (user^.level>napUserUser) then
   if ch.level<=user^.level then
   case query of
     queryChannel,
     queryOperServ,
     queryNickServ,
     queryMsgServ,
     queryChanServ: Error('['+IntToStr(ch.users.count)+'] '+ch.channel);
     else Exec(user,MSG_SERVER_CHANNEL_LIST,ch.channel+' '+IntToStr(ch.users.count)+' '+ch.topic);
   end;
 end;
 tmp_pos:=512;
 case query of
  queryChannel,
  queryOperServ,
  queryNickServ,
  queryMsgServ,
  queryChanServ: Error('.');
  else Exec(user,MSG_SERVER_CHANNEL_LIST_END,'');
 end;
 tmp_pos:=513;
end;

procedure Handler_ListAllChannels;
var
 ch: TChannel;
 i: Integer;
begin
 tmp_pos:=520;
 if not isLogged then exit;
 if user=nil then exit;
 if not listall_for_users and (user.level<napUserModerator) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=521;
 for i:=0 to db_channels.count-1 do
 begin
   ch:=db_channels.Items[i];
   case query of
     queryChannel,
     queryOperServ,
     queryNickServ,
     queryMsgServ,
     queryChanServ: Error('['+IntToStr(ch.users.count)+'] '+ch.channel);
     else Exec(user,MSG_SERVER_FULL_CHANNEL_INFO,ch.channel+' '+IntToStr(ch.users.count)+' '+IntToStr(Ord(chPrivate in ch.state))+' '+IntToStr(Ord(ch.level))+' '+IntToStr(ch.limit)+' '+AddStr(ch.topic));
   end;
 end;
 tmp_pos:=522;
 case query of
  queryChannel,
  queryOperServ,
  queryNickServ,
  queryMsgServ,
  queryChanServ: Error('.');
  else Exec(user,MSG_CLIENT_FULL_CHANNEL_LIST,'');
 end;
end;

procedure Handler_ChannelTopic;
var
 ch: TChannel;
begin
 tmp_pos:=530;
 if query=queryServer then
 begin
  if not CheckParams(2) then exit;
  ch:=FindChannel(hlist.Strings[0]);
  if ch=nil then exit;
  ch.SetTopic(NextParamEx(gcmd.cmd));
  exit;
 end;
 tmp_pos:=531;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 tmp_pos:=532;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then exit;
 if ch.FindUser(user)=-1 then
 begin
   if user.level>napUserUser then
   if hlist.count>1 then
   begin
    ch.SetTopic(NextParamEx(gcmd.cmd));
    if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
   end;
   exit;
 end;
 tmp_pos:=533;
 if hlist.count=1 then
 begin
   Exec(user,MSG_SERVER_TOPIC,ch.channel+' '+ch.topic);
   exit;
 end;
 if not ch.Operator(user) then
 if not (chTopic in ch.state) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=534;
 ch.SetTopic(NextParamEx(gcmd.cmd));
 Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangT(LNG_CHTOPIC,Level2Str(user^.level),user^.username,ch.channel,ch.topic),true);
 if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_ChanBanList;
var
 ch: TChannel;
 p: PStringHashItem;
begin
 tmp_pos:=540;
 if not isLogged then exit;
 if user=nil then exit;
 if not CheckParams(1) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=541;
 if not ch.Operator(user) then
  if user^.level<napUserModerator then
  begin
    PermissionDenied('',true);
    exit;
  end;
 tmp_pos:=542;
 p:=ch.bans.first;
 while p<>nil do
 begin
   case query of
     queryOperServ,
     queryNickServ,
     queryChanServ,
     queryMsgServ,
     queryChannel: Error(p^.data);
     else Exec(user,MSG_SERVER_CHANNEL_BAN_LIST,ch.channel+' '+p^.data);
   end;
   p:=p^.next;
 end;
 tmp_pos:=543;
 case query of
   queryOperServ,
   queryNickServ,
   queryChanServ,
   queryMsgServ,
   queryChannel: Error('.');
   else Exec(user,MSG_CLIENT_CHANNEL_BAN_LIST,ch.channel);
 end;
 tmp_pos:=544;
end;

procedure Handler_ChanBan; // 422
var
 ch: TChannel;
 user2: POnlineUser;
 ban,name,ip: String;
begin
 tmp_pos:=550;
 if query<>queryServer then
  if not isLogged then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 tmp_pos:=551;
 if ch=nil then
 begin
   Error('チャンネル入室禁止に失敗しました: そのようなチャンネルはありません',true);
   exit;
 end;
 tmp_pos:=552;
 if query<>queryServer then
 if not ch.Operator(user) then
  if user^.level<napUserModerator then
  begin
   PermissionDenied('',true);
   exit;
  end;
 tmp_pos:=553;
 SplitBan(hlist.Strings[1],name,ip);
 ban:=JoinBan(name,ip);
 if gcmd.id=MSG_CLIENT_CHANNEL_BANIP then
 begin
   user2:=db_online.FindUser(name);
   if user2=nil then
   begin
     UserIsOffline(hlist.Strings[1],true);
     exit;
   end;
   ip:=decode_ip(user2^.ip);
   ban:=JoinBan(name,ip);
 end;
 tmp_pos:=554;
 if StrHash_FindString(ch.bans,ban,true) then
 begin
   Error(GetLangT(LNG_CHALREADYBANNED),true);
   exit;
 end;
 StrHash_Add(ch.bans,ban);
 tmp_pos:=555;
 if query<>queryServer then
 begin
  Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangT(LNG_CHBANNED,user^.username,ban,ch.channel),true);
  if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 end; 
end;

procedure Handler_ChanUnban; // 423
var
 ch: TChannel;
begin
 tmp_pos:=560;
 if query<>queryServer then
  if not isLogged then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error('チャンネル入室禁止の解除に失敗しました: そのようなチャンネルはありません',true);
   exit;
 end;
 tmp_pos:=561;
 if query<>queryServer then
 if not ch.Operator(user) then
  if user^.level<napUserModerator then
  begin
    PermissionDenied('',true);
    exit;
  end;
 tmp_pos:=562;
 if not StrHash_Delete(ch.bans,hlist.Strings[1],true) then
 begin
   Error('チャンネル入室禁止の解除に失敗しました: そのようなBanはありません',true);
   exit;
 end;
 tmp_pos:=563;
 if query<>queryServer then
 begin
   Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangT(LNG_CHUNBANNED,user^.username,hlist.Strings[1],ch.channel),true);
   if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 end;
end;

procedure Handler_ChanBanClear;
var
 ch: TChannel;
begin
 tmp_pos:=564;
 if query<>queryServer then
  if not isLogged then exit;
 if not CheckParams(1) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=565;
 if query<>queryServer then
 if not ch.Operator(user) then
  if user^.level<napUserModerator then
  begin
    PermissionDenied('',true);
    exit;
  end;
 if ch.bans.Count<1 then
 begin
   Error('Banはありません。',true);
   exit;
 end;
 tmp_pos:=566;
 StrHash_Clear(ch.bans);
 if query<>queryServer then
 begin
  Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangEx('<$1> $2チャンネルのBanリストをクリアしました',user^.username,ch.channel),true);
  if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 end; 
end;

procedure Handler_Whois;
var
 user2: POnlineUser;
 rec: PRegisteredUser;
 l2: TLocalUser;
 i,j: Integer;
 state, str, display_str: String;
 ch: TChannel;
 cloaked: Boolean;
 p: PNapCmdEx;
begin
 tmp_pos:=570;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(gcmd.cmd);
 tmp_pos:=571;
 cloaked:=false;
 if user2<>nil then
  if userCloaked in user2^.state then
   if user^.level<napUserModerator then
    cloaked:=true;
 tmp_pos:=1224;
 if (user2=nil) or cloaked then
 begin
   rec:=db_registered.FindUser(gcmd.cmd);
   tmp_pos:=1225;
   if rec=nil then
   begin
     i:=db_whowas.FindByCmd(AnsiLowerCase(gcmd.cmd));
     str:=gcmd.cmd+' "User" 0';
     tmp_pos:=1226;
     if (i<>-1) and (not cloaked) then
     begin
       p:=db_whowas.Items[i];
       SplitString(p.data,hlst);
       tmp_pos:=1227;
       if hlst.count>2 then
        str:=gcmd.cmd+' "'+hlst.Strings[1]+'" '+hlst.Strings[2];
     end;
     tmp_pos:=1228;
     //Exec(user,MSG_SERVER_WHOWAS,str);
     Error('ユーザー'+gcmd.cmd+'は未登録のユーザーです。', true);
     Error('ユーザーはオフラインです。',true);
     exit;
   end;
   tmp_pos:=572;
   str:=gcmd.cmd+' "'+Level2Str(rec^.level)+'" '+IntToStr(rec^.last_seen);
   Exec(user,MSG_SERVER_WHOWAS,str);
   Error('ユーザーはオフラインです。',true);
   exit;
 end;
 tmp_pos:=1229;
 j:=current_time_t-user2^.last_seen_t;
 if j<0 then j:=0;
 tmp_pos:=573;
 str:=gcmd.cmd+' "'+Level2Str(user2^.level)+'" '+IntToStr(j)+' "';
 for i:=0 to db_channels.Count-1 do
 begin
   ch:=db_channels.Items[i];
   if ch.FindUser(user2)<>-1 then
    str:=str+ch.channel+' ';
 end;
 tmp_pos:=574;
 str:=str+'" ';
 if user2^.server=nil then state:='Active'
 else state:='Remote';
 if (user^.level>napUserUser) and (userCloaked in user2^.state) then state:=state+' (cloaked)'; 
 //-----------------------------------stateの拡張
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2^.server=nil then l2:=FindLocalUser(user2^.username) else l2:=nil;
 if (l2<>nil) and (local<>nil) and StrHash_FindString(l2.hotlist,AnsiLowerCase(local.nick),true) then
   state:=state+' Friend';//whois先がローカルでホットリストにwhois元がいるときは情報追加
 str:=str+AddStr(state)+' ';
 str:=str+IntToStr(user2^.shared)+' '+IntToStr(user2^.downloads)+' '+
 IntToStr(user2^.uploads)+' '+IntToStr(Ord(user2^.speed))+' "'+
 user2^.software;
 l2 := user2^.local;
 if l2 <> nil then
   case l2.SoftwareID of
     softNapchan: str:=str+#13#10' Size      : ' + FormatFloat('0,', l2.shared_size shr 10) + ' KB"';
     else str:=str+#13#10'Size'#9#9': ' + FormatFloat('0,', l2.shared_size shr 10) + ' KB"';
   end
 else
   str:=str+'"';
 i:=0;
 tmp_pos:=1230;
 if user^.level>napUserUser then i:=1;
 if user=user2 then i:=1;
 tmp_pos:=575;
 if i=1 then
 begin // complete whois reply
   str:=str+' '+IntToStr(user2^.total_down)+' '+IntToStr(user2^.total_up)+' '+decode_ip(user2^.ip)+'('+GetHostByIP(decode_ip(user2^.ip))+') ';
   l2:=user2^.local;
   tmp_pos:=1231;
   if (l2<>nil) and (l2.socket<>INVALID_SOCKET) then
     str:=str+IntToStr(TCPSocket_GetRemoteSin(l2.socket).sin_port)+' '
   else
     str:=str+'0 ';
   str:=str+IntToStr(user2^.dataport)+' anon@'+GetServerAlias(user2^.server);
   if user^.level=napUserUser then
    display_str:=GetServerAlias(user2^.server)
   else
    display_str:=GetServerName(user2^.server);
   str:=str+' '+display_str;
 end;
 tmp_pos:=576;
 Exec(user,MSG_SERVER_WHOIS_RESPONSE,str);
 if user2^.level>napUserUser then
  if user<>user2 then // do not inform self
   if not (userHideWhois in user2^.state) then
    Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_WHOISMOD,AddStr(Level2Str(user^.level)),user^.username,decode_ip(user^.ip)));
 tmp_pos:=577;
 if (user2=user) and (query=queryNormal) then  // probably Napster asking info about itself. For 10.3+ it would be better to receive stats.
  if user^.server=nil then
   Handler_Stats;
end;

procedure BanUser(ban,server: String;time: Integer; reason: String; write_all: Boolean);
begin
  tmp_pos:=580;
  if time<1 then exit;
  ban:=AnsiLowerCase(ban);
  tmp_pos:=581;
  if db_bans.FindRec(ban)<>-1 then exit;
  db_bans.Ban('server: '+server,ban,reason,time);
  tmp_pos:=582;
  if wallop_serverban then
  begin
   if time=0 then
     Wallop(MSG_SERVER_NOSUCH,wallopSBan,GetLangEx('<Server $1> $2をBanしました: $3',server,ban,reason),true)
   else
     Wallop(MSG_SERVER_NOSUCH,wallopSBan,GetLangEx('<Server $1> $2を$3間Banしました: $4',server,ban,BanTime2Str(time),reason),true);
  end;
  if write_all then WriteAllServers(MSG_SRV_SERVERBAN,'',AddStr(ban)+' '+AddStr(server)+' '+IntToStr(time)+' '+AddStr(reason));
end;

procedure Ban(ban,reason: String;timeout: time_t; write_all: Boolean);
begin
  tmp_pos:=583;
  ban:=AnsiLowerCase(ban);
  if ban='' then exit;
  if ban='*' then exit;
  if ban='*!*' then exit;
  tmp_pos:=584;
  if db_bans.FindRec(ban)<>-1 then
  begin
   if user^.server=nil then
    Exec(user,MSG_SERVER_NOSUCH,'すでにBanされています');
   exit;
  end;
  tmp_pos:=585;
  db_bans.Ban(user^.username,ban,reason,timeout);
  if timeout=0 then
   Wallop(MSG_SERVER_NOSUCH,wallopMBan,GetLangEx('<$1> $2をBanしました: $3',user^.username,ban,reason),true)
  else
   Wallop(MSG_SERVER_NOSUCH,wallopMBan,GetLangEx('<$1> $2を$3間Banしました: $4',user^.username,ban,BanTime2Str(timeout),reason),true);
  tmp_pos:=586;
  if write_all then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_BanEx; // 8116
var
 timeout: time_t;
 reason: String;
begin
 tmp_pos:=600;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 tmp_pos:=601;
 if hlist.count<2 then hlist.Add(IntToStr(def_ban_timeout));
 tmp_pos:=602;
 timeout:=StrToIntDef(hlist.Strings[1],def_ban_timeout);
 if (timeout<60) and (timeout<>0) then exit;
 if hlist.count=3 then
  reason:=hlist.Strings[2]
 else
  reason:=NextParamEx(gcmd.cmd,2);
 Ban(hlist.Strings[0],reason,timeout,user^.server=nil);
end;

procedure Handler_Ban; // 612
var
 reason: String;
begin
 tmp_pos:=590;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 if hlist.count=3 then
  if isDigit(hlist.Strings[2]) then
  begin
    gcmd.id:=MSG_CLIENT_BANEX;
    gcmd.cmd:=AddStr(hlist.Strings[0])+' '+AddStr(hlist.Strings[2])+' '+AddStr(hlist.Strings[1]);
    Handler_BanEx;
  end;
 tmp_pos:=591;
 if hlist.count=2 then
  reason:=hlist.Strings[1]
 else
  reason:=NextParamEx(gcmd.cmd);
 Ban(hlist.Strings[0],reason,def_ban_timeout,user^.server=nil);
end;

procedure Handler_Unban; // 614
var
 ban: String;
 i: Integer;
 b: PBan;
begin
 tmp_pos:=610;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 ban:=AnsiLowerCase(hlist.Strings[0]);
 tmp_pos:=612;
 i:=db_bans.FindRec(ban);
 while i<>-1 do
 begin
  b:=db_bans.Items[i];
  Wallop(MSG_SERVER_NOSUCH,wallopMBan,GetLangEx('<$1> $2 ($3) のBanを解除しました: $4',user^.username,ban,b^.reason,NextParamEx(gcmd.cmd)),true);
  db_bans.Delete(i);
  i:=db_bans.FindRec(ban);
 end;
 tmp_pos:=613;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_Banlist; // 615
var
 str: String;
 i: Integer;
 b: PBan;
begin
 tmp_pos:=620;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if Length(gcmd.cmd)>2 then
 begin // ????? TekNap sends 615 to ban user ???
   gcmd.id:=MSG_CLIENT_BAN;
   Handler_Ban;
 end;
 tmp_pos:=621;
 for i:=0 to db_bans.Count-1 do
 begin
   b:=db_bans.Items[i];
   case query of
     queryChannel,
     queryOperServ,
     queryNickServ,
     queryMsgServ,
     queryChanServ: begin
                    str:=b^.user+'!'+b^.ip+' '+b^.admin+' '+UnixTimeToStr(b^.time)+'-';
                    if b^.expires>0 then
                     str:=str+UnixTimeToStr(b^.expires)
                    else
                     str:=str+'無期限';
                    str:=str+': '+b^.reason;
                    Error(str);
                   end;
     else
      str:=b^.user+' '+b^.ip+' "'+b^.reason+'" '+IntToStr(b^.time)+' ';
      if b^.expires>0 then
       str:=str+IntToStr(b^.expires-b^.time)
      else
       str:=str+'0';
      Exec(user,MSG_SERVER_IP_BANLIST,str);
   end;
 end;
 tmp_pos:=622;
 case query of
      queryChannel,
      queryOperServ,
      queryNickServ,
      queryMsgServ,
      queryChanServ: Error(GetLangT(LNG_ITEMSLISTED,IntToStr(db_bans.count)));
      else Exec(user,MSG_CLIENT_BANLIST,'');
 end;
end;

procedure Handler_ChangeBan;
var
 b: PBan;
 exp: time_t;
 str,reason: String;
 i: Integer;
 name,ip: String;
 a: Boolean;
begin
 tmp_pos:=623;
 if not isLogged then exit;
 if not CheckParams(2) then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if hlist.count=3 then hlist.Add('');
 tmp_pos:=624;
 SplitBan(hlist.Strings[0],name,ip);
 for i:=0 to db_bans.Count-1 do
 begin
   b:=db_bans.Items[i];
   if (b^.user=name) and (b^.ip=ip) then
   begin
     exp:=StrToIntDef(hlist.Strings[1],b^.expires);
     reason:=hlist.Strings[2];
     a:=false;
     if exp<>b^.expires then
     begin
       b^.expires:=exp;
       if b^.expires>0 then
         str:=UnixTimeToStr(b^.expires)
       else
         str:=GetLangT(LNG_BANS_NEVER_EXPIRE);
       Wallop(MSG_SERVER_NOSUCH,wallopMBan,GetLangT(LNG_BANEXPCHANGE,user^.username,hlist.Strings[0],str),true);
       a:=true;
     end;
     tmp_pos:=625;
     if (reason<>'') and (b^.reason<>reason) then
     begin
       b^.reason:=reason;
       Wallop(MSG_SERVER_NOSUCH,wallopMBan,GetLangT(LNG_BANREASONCHANGE,user^.username,hlist.Strings[0],reason),true);
       a:=true;
     end;
     if a then
      if user^.server=nil then
       WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
     exit;
   end;
 end;
end;

procedure Handler_ChannelDeclineInvite;
var
 i: Integer;
 s: PNapCmdEx;
begin
 if local=nil then exit;
 for i:=db_invitations.count-1 downto 0 do
 begin
   s:=db_invitations.Items[i];
   if s^.data=local.nick then
    db_invitations.Delete(i)
   else
    if (GetTickCount-s^.id)>EXPIRE_INVITATION then
     db_invitations.Delete(i);
 end;
end;

procedure Handler_ChannelInvite;
var
 ch: TChannel;
 str: String;
 user2: POnlineUser;
 l2: TLocalUser;
 s: PNapCmdEx;
 i: Integer;
begin
 tmp_pos:=630;
 if not isLogged then exit;
 if not CheckParams(2) then exit;
 if gcmd.id=MSG_CLIENT_CHANNEL_INVITE_OPENNAP then
 begin // swap arguments
   str:=hlist.Strings[1];
   hlist.Strings[1]:=hlist.Strings[0];
   hlist.Strings[0]:=str;
 end;
 tmp_pos:=631;
 ch:=FindChannel(hlist.Strings[1]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 if (user^.level<napUserModerator) and (ch.FindUser(user)=-1) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=632;
 if user^.level<ch.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   NoSuchUser(true);
   exit;
 end;
 tmp_pos:=633;
 if ch.FindUser(user2)<>-1 then
 begin
   Error(GetLangT(LNG_CHJOINED,user2^.username,ch.channel),true);
   exit;
 end;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   user2^.server.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
   exit;
 end;
 tmp_pos:=634;
 if user2^.server<>nil then
 begin
   user2^.server.Exec(MSG_SRV_SETLASTINV,user2^.username+' '+hlist.Strings[1]);
 end
 else
 begin
   l2:=user2^.local;
   if l2=nil then exit; // weird error
   for i:=db_invitations.count-1 downto 0 do
   begin
     s:=db_invitations.Items[i];
     if s^.data=l2.nick then
      db_invitations.Delete(i)
     else
      if (GetTickCount-s^.id)>EXPIRE_INVITATION then
       db_invitations.Delete(i);
   end;
   db_invitations.AddCmd(GetTickCount,hlist.Strings[1],l2.nick);
 end;
 tmp_pos:=635;
 Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_INVITE,ch.channel,user^.username));
 Exec(user2,MSG_CLIENT_CHANNEL_INVITE,user^.username+' '+ch.channel+' "'+ch.topic+'" 0 0');
end;

procedure Handler_Muzzle;
var
 user2: POnlineUser;
 reg: PRegisteredUser;
 str: String;
 l: TNapUserLevel;
 b: Boolean;
begin
 tmp_pos:=640;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2^.level>=user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 reg:=db_registered.FindUser(hlist.Strings[0]);
 tmp_pos:=641;
 if (user2=nil) and (reg=nil) then
 begin
   NoSuchUser(true);
   exit;
 end;
 if user2<>nil then
 begin
   l:=user2^.level;
   b:=userMuzzled in user2^.state;
 end
 else
 begin
   l:=reg^.level;
   b:=userMuzzled in reg^.state;
 end;
 tmp_pos:=642;
 //if l>napUserUser then // old routine- no muzzling any staff
 //begin
 //  PermissionDenied('',true);
 //  exit;
 //end;
 if b then
 begin
   Error(GetLangT(LNG_MUZZLE1),true);
   exit;
 end;
 tmp_pos:=643;
 if user2<>nil then user2^.state:=user2^.state+[userMuzzled];
 if reg<>nil then reg^.state:=reg^.state+[userMuzzled];
 case hlist.Count of
  1:  str:='';
  2:  str:=' : '+hlist.Strings[1];
  else str:=' : '+Trim(NextParamEx(gcmd.cmd));
 end;
 Wallop(MSG_SERVER_NOSUCH,wallopMuzzle,GetLangT(LNG_MUZZLE,hlist.Strings[0],user^.username)+str,true);
 tmp_pos:=644;
 if user^.server=nil then
  WriteAllServers(MSG_CLIENT_MUZZLE,user^.username,gcmd.cmd);
 if user2<>nil then
  if user2^.server=nil then
   Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_MUZZLE3,NextParamEx(gcmd.cmd)));
end;

procedure Handler_Unmuzzle;
var
 user2: POnlineUser;
 reg: PRegisteredUser;
 str: String;
 l: TNapUserLevel;
 b: Boolean;
begin
 tmp_pos:=645;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 reg:=db_registered.FindUser(hlist.Strings[0]);
 if (user2=nil) and (reg=nil) then
 begin
   NoSuchUser(true);
   exit;
 end;
 tmp_pos:=646;
 if user2<>nil then
 begin
   l:=user2^.level;
   b:=userMuzzled in user2^.state;
 end
 else
 begin
   l:=reg^.level;
   b:=userMuzzled in reg^.state;
 end;
{ if l>user^.level then
 begin
   PermissionDenied(true);
   exit;
 end;} // mods+ can unmuzzle all other mods+ if accedently got muzzled
 tmp_pos:=647;
 if b=false then
 begin
   Error(GetLangT(LNG_UNMUZZLE1),true);
   exit;
 end;
 if user<>nil then user2^.state:=user2^.state-[userMuzzled];
 if reg<>nil then reg^.state:=reg^.state-[userMuzzled];
 case hlist.Count of
  1:  str:='';
  2:  str:=' : '+hlist.Strings[1];
  else str:=' : '+Trim(NextParamEx(gcmd.cmd));
 end;
 tmp_pos:=648;
 Wallop(MSG_SERVER_NOSUCH,wallopMuzzle,GetLangT(LNG_UNMUZZLE,hlist.Strings[0],user^.username)+str,true);
 if user^.server=nil then
  WriteAllServers(MSG_CLIENT_UNMUZZLE,user^.username,gcmd.cmd);
 if user2<>nil then
  if user2^.server=nil then
   Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_UNMUZZLE3));
end;

procedure Handler_MuzzleInv;
var
 user2: POnlineUser;
 reg: PRegisteredUser;
 b: Boolean;
 l: TNapUserLevel;
begin
 tmp_pos:=650;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 reg:=db_registered.FindUser(hlist.Strings[0]);
 tmp_pos:=651;
 if user2=nil then
 begin
   NoSuchUser(true);
   exit;
 end;
 if user2<>nil then
 begin
   b:=userMuzzled in user2^.state;
   l:=user2^.level;
 end
 else
 begin
   b:=userMuzzled in reg^.state;
   l:=reg^.level;
 end;
 tmp_pos:=652;
{ if l>=user^.level then
 begin
   PermissionDenied(true);
   exit;
 end;}
 if b then
 begin
   gcmd.id:=MSG_CLIENT_UNMUZZLE;
   Handler_Unmuzzle;
 end
 else
 begin
   gcmd.id:=MSG_CLIENT_MUZZLE;
   Handler_Muzzle;
 end;
end;

procedure Handler_AlterPort; // 613
var
 user2: POnlineUser;
 i: Integer;
begin
 if not isLogged then exit;
 if not CheckLevel('alter_port()',napUserAdmin) then exit;
 if not CheckParams(2) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 if user2^.level>=user^.level then
 begin
   PermissionDenied('ポート変更失敗',true);
   exit;
 end;
 i:=StrToIntDef(hlist.Strings[1],-1);
 if (i<0) or (i>65535) then
 begin
   Error(hlist.Strings[1]+'は不正なポートです',true);
   exit;
 end;
 if user2^.dataport<>i then
 begin
   Wallop(MSG_SERVER_NOSUCH,wallopChange,'<'+user^.username+'> '+user2^.username+'のデータポートを'+IntToStr(i)+'に変更しました: '+NextParamEx(gcmd.cmd,2),true);
   user2^.dataport:=i;
 end;
 if user2^.server=nil then Exec(user2,gcmd.id,hlist.Strings[1]);
 if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_AlterSpeed; // 625
var
 user2: POnlineUser;
 i: Integer;
begin
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(2) then exit;
 i:=StrToIntDef(hlist.Strings[1],-1);
 if (i<0) or (i>10) then
 begin
   Error('不正な回線速度です',true);
   exit;
 end;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 if user2^.level>=user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if Ord(user2^.speed)=i then
 begin
   if user^.server=nil then
    Error(hlist.Strings[0]+'の回線速度はすでに'+hlist.Strings[1]+'です',true);
   exit;
 end;
 user2^.speed:=TNapSpeed(i);
 Wallop(MSG_SERVER_NOSUCH,wallopChange,GetLangT(LNG_ALTERSPEED,user^.username,user2^.username,hlist.Strings[1]),true);
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_Relay;
var
 user2: POnlineUser;
begin
 tmp_pos:=660;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   if AnsiLowerCase(hlist.Strings[0])<>'server' then
    UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 tmp_pos:=661;
 if hlist.Count>1 then
  Exec(user2,gcmd.id,user^.username+' '+NextParamEx(gcmd.cmd))
 else
  Exec(user2,gcmd.id,user^.username);
end;

procedure Handler_Relay2;
var
 user2: POnlineUser;
 l2: TLocalUser;
begin
 tmp_pos:=662;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   if AnsiLowerCase(hlist.Strings[0])<>'server' then
    UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 tmp_pos:=663;
 l2:=user2^.local;
 if l2<>nil then
  if user^.level<napUserModerator then
   if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
   begin
     UserIsOffline(hlist.Strings[0],true);
     exit;
   end;
 tmp_pos:=664;
 if hlist.Count>1 then
  Exec(user2,gcmd.id,user^.username+' '+NextParamEx(gcmd.cmd))
 else
  Exec(user2,gcmd.id,user^.username);
end;

procedure Handler_Pong;
begin
 if user^.server=nil then
  if (not (locPingable in local.localstate)) and (AnsiLowerCase(gcmd.cmd)='server') then
  begin
   local.localstate:=local.localstate+[locPingable];
   exit;
  end;
 Handler_Relay;
end;

procedure Handler_UserMode;
var
 st: TUserState;
 str: String;
begin
 if not isLogged then exit;
 SplitString(uppercase(gcmd.cmd),hlist);
 if hlist.count=0 then
 begin
   str:='';
   if query<>queryNormal then
   begin
     if userHideErrors in user^.state then str:='-ERROR '
     else str:='+ERROR ';
     if userHideAnnouncements in user^.state then str:=str+'-ANNOUNCE'
     else str:=str+'+ANNOUNCE';
     if user^.level>napUserUser then
     begin
       //if userHideBans in user^.state then str:=str+' -BAN '
       //else str:=str+' +BAN ';
       if userHideMBans in user^.state then str:=str+' -MBAN '
       else str:=str+' +MBAN ';
       if userHideMBanConn in user^.state then str:=str+'-MBANCONN '
       else str:=str+'+MBANCONN ';
       if userHideSBans in user^.state then str:=str+'-SBAN '
       else str:=str+'+SBAN ';       
       if userHideSBanConn in user^.state then str:=str+'-SBANCONN '
       else str:=str+'+SBANCONN ';       
       if userHideChange in user^.state then str:=str+'-CHANGE '
       else str:=str+'+CHANGE ';
       if userHideKill in user^.state then str:=str+'-KILL '
       else str:=str+'+KILL ';
       if userHideLevel in user^.state then str:=str+'-LEVEL '
       else str:=str+'+LEVEL ';
       if userHideServer in user^.state then str:=str+'-SERVER '
       else str:=str+'+SERVER ';
       if userHideMuzzle in user^.state then str:=str+'-MUZZLE '
       else str:=str+'+MUZZLE ';
       if userHidePort in user^.state then str:=str+'-PORT '
       else str:=str+'+PORT ';
       if userHideWallop in user^.state then str:=str+'-WALLOP '
       else str:=str+'+WALLOP ';
       if userHideCloak in user^.state then str:=str+'-CLOAK '
       else str:=str+'+CLOAK ';
       if userHideFlood in user^.state then str:=str+'-FLOOD '
       else str:=str+'+FLOOD ';
       if userHidePM in user^.state then str:=str+'-MSG '
       else str:=str+'+MSG ';
       if userHideWhois in user^.state then str:=str+'-WHOIS '
       else str:=str+'+WHOIS ';
       if userHideBrowse in user^.state then str:=str+'-BROWSE '
       else str:=str+'+BROWSE ';
       if userHideMotd in user^.state then str:=str+'-MOTD '
       else str:=str+'+MOTD ';
       if userHideFriends in user^.state then str:=str+'-FRIEND '
       else str:=str+'+FRIEND ';
       if userHideChannel in user^.state then str:=str+'-CHANNEL '
       else str:=str+'+CHANNEL ';
       if userHideRegister in user^.state then str:=str+'-REGISTER '
       else str:=str+'+REGISTER ';
       if userHideVar in user^.state then str:=str+'-VAR '
       else str:=str+'+VAR ';
       if userHidePing in user^.state then str:=str+'-PING '
       else str:=str+'+PING ';
       if userHideLeech in user^.state then str:=str+'-LEECH '
       else str:=str+'+LEECH ';
       if userHideSameNic in user^.state then str:=str+'-SAMENIC '
       else str:=str+'+SAMENIC ';
     end;
     Error(str);
   end
   else
   begin
     if not (userHideErrors in user^.state) then str:='ERROR ';
     if not (userHideAnnouncements in user^.state) then str:=str+'ANNOUNCE ';
     if user^.level>napUserUser then
     begin
       //if not (userHideBans in user^.state) then str:=str+'BAN ';
       if not (userHideMBans in user^.state) then str:=str+'MBAN ';
       if not (userHideMBanConn in user^.state) then str:=str+'MBANCONN ';
       if not (userHideSBans in user^.state) then str:=str+'SBAN ';
       if not (userHideSBanConn in user^.state) then str:=str+'SBANCONN ';
       if not (userHideChange in user^.state) then str:=str+'CHANGE ';
       if not (userHideKill in user^.state) then str:=str+'KILL ';
       if not (userHideLevel in user^.state) then str:=str+'LEVEL ';
       if not (userHideServer in user^.state) then str:=str+'SERVER ';
       if not (userHideMuzzle in user^.state) then str:=str+'MUZZLE ';
       if not (userHidePort in user^.state) then str:=str+'PORT ';
       if not (userHideWallop in user^.state) then str:=str+'WALLOP ';
       if not (userHideCloak in user^.state) then str:=str+'CLOAK ';
       if not (userHideFlood in user^.state) then str:=str+'FLOOD ';
       if not (userHidePM in user^.state) then str:=str+'MSG ';
       if not (userHideWhois in user^.state) then str:=str+'WHOIS ';
       if not (userHideBrowse in user^.state) then str:=str+'BROWSE ';
       if not (userHideMotd in user^.state) then str:=str+'MOTD ';
       if not (userHideFriends in user^.state) then str:=str+'FRIEND ';
       if not (userHideChannel in user^.state) then str:=str+'CHANNEL ';
       if not (userHideRegister in user^.state) then str:=str+'REGISTER ';
       if not (userHideVar in user^.state) then str:=str+'VAR ';
       if not (userHidePing in user^.state) then str:=str+'PING ';
       if not (userHideLeech in user^.state) then str:=str+'LEECH ';
       if not (userHideSameNic in user^.state) then str:=str+'SAMENIC ';
     end;
     Exec(user,gcmd.id,trim(str));
   end;
   exit;
 end;
 st:=user^.state;
 if uppercase(gcmd.cmd)='HELP' then
 begin
   Error('ユーザーモードのヘルプ - 利用できるモードの一覧 :');
   Error('ERROR    '#9' - エラーメッセージを表示/非表示');
   Error('ANNOUNCE '#9' - アナウンスを表示/非表示');
   if user^.level<napUserModerator then
    Error('その他のモードはmods+のみ利用できます。')
   else
   begin
    Error('MBAN    '#9#9' - mod+によるbanの告知を表示/非表示');
    Error('MBANCONN'#9' - mod+がbanしたユーザーの接続試行の告知を表示/非表示');
    Error('SBAN    '#9#9' - サーバーによるbanの告知を表示/非表示 (共有関連と詐称MX)');
    Error('SBANCONN'#9' - サーバーがbanしたユーザーの接続試行の告知を表示/非表示');
    Error('CHANGE  '#9' - ユーザー情報の変更メッセージを表示/非表示');
    Error('KILL    '#9#9' - 切断/アカウント削除の告知を表示/非表示');
    Error('LEVEL   '#9#9' - ユーザーのレベル変更告知を表示/非表示');
    Error('SERVER  '#9' - サーバーリンクの告知を表示/非表示');
    Error('MUZZLE  '#9' - 発言禁止/禁止解除の告知を表示/非表示');
    Error('PORT    '#9#9' - データポート変更の告知を表示/非表示');
    Error('WALLOP  '#9' - wallopメッセージを表示/非表示');
    Error('CLOAK   '#9' - クローク/解除の告知を表示/非表示');
    Error('FLOOD   '#9' - flooderの告知を表示/非表示');
    Error('MSG     '#9#9' - 他のユーザーからあなたに送られるIMを表示/非表示');
    Error('WHOIS   '#9' - 情報をみられたときの告知を表示/非表示');
    Error('BROWSE  '#9' - 参照されたときの告知を表示/非表示');
    Error('MOTD    '#9' - MOTDを表示/非表示(登録済みユーザーにのみ有効)');
    Error('FRIEND  '#9' - 友人リストの変更告知を表示/非表示');
    Error('CHANNEL '#9' - チャンネルへの入室禁止、追放、削除、オペレーター追加/削除...などのメッセージを表示/非表示');
    Error('REGISTER '#9' - サーバーへの新規登録の告知を表示/非表示');
    Error('VAR     '#9#9' - サーバー変数の変更を表示/非表示');
    Error('PING    '#9#9' - サーバーping/pongの表示/非表示');
    Error('LEECH   '#9#9' - Leechのログインの告知を表示/非表示');
    Error('SAMENIC '#9' - あなたのIDでログインしようとするユーザーの告知を表示/非表示');
   end;
   Error('.');
 end;
 while hlist.count>0 do
 begin
   str:=hlist.Strings[0];
   hlist.Delete(0);
   //if str='NONE' then st:=st+[userHideErrors,userHideAnnouncements,userHideBans,
     if str='NONE' then st:=st+[userHideErrors,userHideAnnouncements,userHideMBans,
      userHideMBanConn,userHideSBans,userHideSBanConn,
      userHideChange,userHideKill,userHideLevel,userHideServer,userHideMuzzle,
      userHidePort,userHideWallop,userHideCloak,userHideFlood,userHidePM,
      userHideWhois,userHideBrowse,userHideMotd,userHideFriends,userHideChannel,
      userHideRegister,userHideVar,userHidePing,userHideLeech,userHideSameNic]
   //else if str='ALL' then st:=st-[userHideErrors,userHideAnnouncements,userHideBans,
      else if str='ALL' then st:=st-[userHideErrors,userHideAnnouncements,userHideMBans,
      userHideMBanConn,userHideSBans,userHideSBanConn,
      userHideChange,userHideKill,userHideLevel,userHideServer,userHideMuzzle,
      userHidePort,userHideWallop,userHideCloak,userHideFlood,userHidePM,
      userHideWhois,userHideBrowse,userHideMotd,userHideFriends,userHideChannel,
      userHideRegister,userHideVar,userHidePing,userHideLeech,userHideSameNic]
   else if str='-ERROR' then st:=st+[userHideErrors]
   else if str='+ERROR' then st:=st-[userHideErrors]
   else if str='-ANNOUNCE' then st:=st+[userHideAnnouncements]
   else if str='+ANNOUNCE' then st:=st-[userHideAnnouncements]
   else if user^.level>napUserUser then
   begin
     //if str='-BAN' then st:=st+[userHideBans]
     //else if str='+BAN' then st:=st-[userHideBans]
     if str='-MBAN' then st:=st+[userHideMBans]
     else if str='+MBAN' then st:=st-[userHideMBans]
     else if str='-MBANCONN' then st:=st+[userHideMBanConn]
     else if str='+MBANCONN' then st:=st-[userHideMBanConn]
     else if str='-SBAN' then st:=st+[userHideSBans]
     else if str='+SBAN' then st:=st-[userHideSBans]
     else if str='-SBANCONN' then st:=st+[userHideSBanConn]
     else if str='+SBANCONN' then st:=st-[userHideSBanConn]
     else if str='-CHANGE' then st:=st+[userHideChange]
     else if str='+CHANGE' then st:=st-[userHideChange]
     else if str='-KILL' then st:=st+[userHideKill]
     else if str='+KILL' then st:=st-[userHideKill]
     else if str='-LEVEL' then st:=st+[userHideLevel]
     else if str='+LEVEL' then st:=st-[userHideLevel]
     else if str='-SERVER' then st:=st+[userHideServer]
     else if str='+SERVER' then st:=st-[userHideServer]
     else if str='-MUZZLE' then st:=st+[userHideMuzzle]
     else if str='+MUZZLE' then st:=st-[userHideMuzzle]
     else if str='-PORT' then st:=st+[userHidePort]
     else if str='+PORT' then st:=st-[userHidePort]
     else if str='-WALLOP' then st:=st+[userHideWallop]
     else if str='+WALLOP' then st:=st-[userHideWallop]
     else if str='-CLOAK' then st:=st+[userHideCloak]
     else if str='+CLOAK' then st:=st-[userHideCloak]
     else if str='-FLOOD' then st:=st+[userHideFlood]
     else if str='+FLOOD' then st:=st-[userHideFlood]
     else if str='-MSG' then st:=st+[userHidePM]
     else if str='+MSG' then st:=st-[userHidePM]
     else if str='-WHOIS' then st:=st+[userHideWhois]
     else if str='+WHOIS' then st:=st-[userHideWhois]
     else if str='-BROWSE' then st:=st+[userHideBrowse]
     else if str='+BROWSE' then st:=st-[userHideBrowse]
     else if str='-MOTD' then st:=st+[userHideMotd]
     else if str='+MOTD' then st:=st-[userHideMotd]
     else if str='-FRIEND' then st:=st+[userHideFriends]
     else if str='+FRIEND' then st:=st-[userHideFriends]
     else if str='-CHANNEL' then st:=st+[userHideChannel]
     else if str='+CHANNEL' then st:=st-[userHideChannel]
     else if str='-REGISTER' then st:=st+[userHideRegister]
     else if str='+REGISTER' then st:=st-[userHideRegister]
     else if str='-VAR' then st:=st+[userHideVar]
     else if str='+VAR' then st:=st-[userHideVar]
     else if str='-PING' then st:=st+[userHidePing]
     else if str='+PING' then st:=st-[userHidePing]
     else if str='-LEECH' then st:=st+[userHideLeech]
     else if str='+LEECH' then st:=st-[userHideLeech]
     else if str='-SAMENIC' then st:=st+[userHideSameNic]
     else if str='+SAMENIC' then st:=st-[userHideSameNic];

   end;
 end;
 if user^.state=st then exit;
 user^.state:=st;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_Wallop; // 627
begin
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if gcmd.cmd='' then exit;
 tmp_pos:=665;
 Wallop(MSG_SERVER_WALLOP,wallopWallop,user^.username+' '+gcmd.cmd,true);
 if user^.server=nil then
   WriteAllServers(MSG_CLIENT_WALLOP,user^.username,gcmd.cmd);
end;

procedure Handler_Announce; // 627
var
 i: Integer;
 l: TLocalUser;
begin
 if not isLogged then exit;
 if not CheckLevel('',napUserAdmin) then exit;
 if gcmd.cmd='' then exit;
 tmp_pos:=666;
 for i:=0 to db_local.Count-1 do
 begin
   l:=db_local.Items[i];
   if l.logged then
    if not (userHideWallop in l.data^.state) then
     l.Exec(MSG_SERVER_ANNOUNCE,user^.username+' '+gcmd.cmd);
 end;
 if user^.server=nil then
   WriteAllServers(MSG_CLIENT_ANNOUNCE,user^.username,gcmd.cmd);
end;

procedure Handler_DirectBrowse;
var
 str: String;
 user2: POnlineUser;
 l2: TLocalUser;
begin
 tmp_pos:=670;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 if isLocal and (GetTickCount-local.last_seen<30000) then local.detector:=local.detector+[loc640];
 if user^.level=napUserLeech then
 begin
   Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_BROWSELEECH)+'"');
   exit;
 end;
  // block browse by ppl not meeting min_share
 if not minshare_noblockact then
 if user^.level=napUserUser then
 begin
  if not StrHash_FindString(db_friends,user^.username,true) then
  begin
    if local<>nil then
    begin
      if local.shared_size<minshare_size then
      begin
        //exec(user,MSG_SERVER_NOSUCH,GetLangT(LNG_SHAREMEGSFIRST,IntToStr(minshare_size div MegaByte),server_alias));
        Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_BROWSELEECH)+'"');
        exit;
      end;
      if user^.shared<minshare then
      begin
       //exec(user,MSG_SERVER_NOSUCH,GetLangT(LNG_SHAREFILESFIRST,IntToStr(minshare),server_alias));
       Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_BROWSELEECH)+'"');
       exit;
      end;
    end;
   end;
 end;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_OFFLINE2,hlist.Strings[0])+'"');
   exit;
 end;
 tmp_pos:=671;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   user2^.server.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
   exit;
 end;
 tmp_pos:=672;
 l2:=user2^.local;
 if l2<>nil then
  if user^.level<napUserModerator then
   if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
   begin
     Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_OFFLINE2,hlist.Strings[0])+'"');
     exit;
   end;
 tmp_pos:=673;
 if (user^.dataport=0) and (user2^.dataport=0) then
 begin
   Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_BROWSEFIREWALL,user2^.username)+'"');
   exit;
 end;
 str:=user^.username;
 if user2^.dataport=0 then
  str:=str+' '+IntToStr(user^.ip)+' '+IntToStr(user^.dataport);
 Exec(user2,gcmd.id,str);
end;

procedure Handler_DirectBrowseOK;
var
 str: String;
 user2: POnlineUser;
 l2: TLocalUser;
begin
 tmp_pos:=674;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_OFFLINE2,hlist.Strings[0])+'"');
   exit;
 end;
 tmp_pos:=675;
 if user2^.server<>nil then
 begin
   if local=nil then exit;
   user2^.server.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
   exit;
 end;
 tmp_pos:=676;
 l2:=user2^.local;
 if l2<>nil then
  if user^.level<napUserModerator then
   if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
   begin
     Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_OFFLINE2,hlist.Strings[0])+'"');
     exit;
   end;
 tmp_pos:=677;
 if (user^.dataport=0) and (user2^.dataport=0) then
 begin
   Exec(user,MSG_SERVER_BROWSE_DIRECT_ERR,hlist.Strings[0]+' "'+GetLangT(LNG_BROWSEFIREWALL)+'"');
   exit;
 end;
 str:=user^.username+' '+IntToStr(user^.ip)+' '+IntToStr(user^.dataport);
 Exec(user2,gcmd.id,str);
end;

procedure Handler_Cloak; // 652
var
 i,j: Integer;
 b: Boolean;
 ch: TChannel;
 user2: POnlineUser;
 loc: TLocalUser;
 str: String;
begin
 tmp_pos:=680;
 if not isLogged then exit;
 if not CheckLevel('['+serveralias+'] cloak失敗',napUserModerator) then exit;
 b:=userCloaked in user.state;
 if gcmd.cmd='1' then
 begin
   if b then
   begin
     Error(GetLangT(LNG_ERRCLOAKED),true);
     exit;
   end;
   b:=true;
 end else if gcmd.cmd='0' then
 begin
   if not b then
   begin
     Error(GetLangT(LNG_ERRUNCLOAK),true);
     exit;
   end;
   b:=false;
 end else b:=not b;
 tmp_pos:=681;
 if b then
 begin
   user^.state:=user^.state+[userCloaked];
   Wallop(MSG_SERVER_NOSUCH,wallopCloak,GetLangT(LNG_CLOAKED,user^.username),true);
   if user^.server=nil then
   begin
     local.Exec(MSG_SERVER_NOSUCH,'あなたは隠れました');
     WriteAllServers(MSG_CLIENT_CLOAK,user^.username,'1');
   end;
 end
 else
 begin
   user^.state:=user^.state-[userCloaked];
   Wallop(MSG_SERVER_NOSUCH,wallopCloak,GetLangT(LNG_UNCLOAKED,user^.username),true);
   if user^.server=nil then
   begin
     local.Exec(MSG_SERVER_NOSUCH,'あなたはもう隠れていません');
     WriteAllServers(MSG_CLIENT_CLOAK,user^.username,'0');
   end;
 end;
 tmp_pos:=682;
 for i:=0 to db_channels.Count-1 do
 begin
   ch:=db_channels.Items[i];
   j:=ch.FindUser(user);
   if j<>-1 then
   for j:=0 to ch.users.Count-1 do
   try
     user2:=ch.users.Items[j];
     if user2<>user then
     if user2^.server=nil then
     if user2^.level<napUserModerator then
     begin
       if not b then
        Exec(user2,MSG_SERVER_JOIN,ch.channel+' '+user^.username+' '+IntToStr(user^.shared)+' '+IntToStr(Ord(user^.speed)))
       else
        Exec(user2,MSG_SERVER_PART,ch.channel+' '+user^.username+' '+IntToStr(user^.shared)+' '+IntToStr(Ord(user^.speed)));
     end;
    except
     on E:Exception do
      DebugLog('Handler_Cloak : exception '+E.Message);
   end;
  end;
  for i:=0 to db_local.count-1 do
  begin
   loc:=db_local.Items[i];
   if loc.logged then
   if loc<>local then
   if loc.level<napUserModerator then
   begin
    str:=StrHash_FindStringEx(loc.hotlist,user^.username,true);
    if str<>'' then
    begin
     if not b then
      loc.Exec(MSG_SERVER_USER_SIGNON,str+' '+IntToStr(Ord(user^.speed)))
     else
      loc.Exec(MSG_SERVER_USER_SIGNOFF,str);
    end;
   end;
  end;
end;

procedure Handler_SetSpeed; // 700
var
 i: Integer;
begin
 tmp_pos:=690;
 if not isLogged then exit;
 i:=StrToIntdef(gcmd.cmd,-1);
 if (i<0) or (i>10) then
 begin
   Error('不正な回線速度です');
   exit;
 end;
 if (Ord(user^.speed)=i) and (query=queryNormal) and (local<>nil) then
  if not CheckWinMX(local) then exit;
 tmp_pos:=691;
 user^.speed:=TNapSpeed(i);
 if user^.server=nil then
 begin
   if local=nil then
     WriteAllServers(gcmd.id,user^.username,gcmd.cmd)
   else
     local.localstate:=local.localstate+[locNeedsUpdate];
 end;
end;

procedure Handler_SetPass; // 701
var
 str: String;
 reg: PRegisteredUser;
begin
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 str:=encode(hlist.Strings[0]);
 if user^.password=str then exit;
 user^.password:=str;
 reg:=db_registered.FindUser(user^.username);
 if reg<>nil then reg^.password:=str;
 if user^.server=nil then
 begin
   Error('パスワードが変更されました',true);
   WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 end;
end;

procedure Handler_SetDataPort; // 703
var
 i: Integer;
begin
 if not isLogged then exit;
 if not CheckRangeEx(gcmd.cmd,0,65535) then exit;
 i:=StrToIntDef(gcmd.cmd,-1);
 if (i<0) or (i>65535) then
 begin
   Error('不正なデータポートです',true);
   exit;
 end;
 if i=user^.dataport then exit;
 user^.dataport:=i;
 if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_AlterPass; // 753
var
 user2: POnlineUser;
 reg: PRegisteredUser;
 str: String;
 l: TNapUserLevel;
 i: Integer;
 srv: TServer;
begin
 if not isLogged then exit;
 if not CheckLevel('',napUserAdmin) then exit;
 if not CheckParams(2) then exit;
 str:=AnsiLowerCase(hlist.Strings[0]);
 if num_servers>0 then
  for i:=0 to db_servers.count-1 do
  begin
    srv:=db_servers.Items[i];
    if srv.logged then
     if AnsiLowerCase(srv.reg_user)=str then
     begin
       PermissionDenied('',true);
       exit;
     end;
  end;
 user2:=db_online.FindUser(hlist.Strings[0]);
 reg:=db_registered.FindUser(hlist.Strings[0]);
 if (user2=nil) and (reg=nil) then
 begin
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 if user2<>nil then l:=user2^.level
 else l:=reg^.level;
 if l>=user^.level then
 begin
   Error('パスワードの変更に失敗しました: アクセス拒否',true);
   exit;
 end;
 str:=encode(hlist.Strings[1]);
 if user2<>nil then user2^.password:=str;
 if reg<>nil then reg^.password:=str;
 Wallop(MSG_SERVER_NOSUCH,wallopChange,GetLangT(LNG_CHPASS2,user^.username,hlist.Strings[0]),true);
 if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_Restart;
begin
 if not isLogged then exit;
 if not CheckLevel('',napUserElite) then exit;
 restart_user:=user^.username;
 sync_reply_list.AddDoubleCmd(MSG_SR_RESTART,0,'','');
end;

procedure Handler_ServerVersion;
var
 srv: TServer;
 i: Integer;
 display_name: String;
 moderator: Boolean;
begin
 moderator:=user^.level>napUserUser;
 if gcmd.cmd<>'' then
 begin
   gcmd.cmd:=lowercase(gcmd.cmd);
   for i:=0 to db_servers.Count-1 do
   begin
     srv:=db_servers.Items[i];
     if srv.logged then
     if (srv.host=gcmd.cmd) or (srv.alias=gcmd.cmd) then
     begin
       Error('--');
       if not moderator then
        display_name:=GetServerAlias(srv)
       else
        display_name:=srv.host;
       Error('Server '+display_name);
       Error(srv.version);
       Error('--');
       exit;
     end;
   end;
 end;
 Error('--');
 Error(SLAVANAP_FULL);
 Error('--');
end;

procedure Handler_ClearChannel;
var
 ch: TChannel;
 i: Integer;
begin
 tmp_pos:=700;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 tmp_pos:=701;
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 if ch.level>=user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=702;
 while ch.users.count>0 do
  ch.Part(ch.users.Items[0]);
 Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangT(LNG_CLEARCHANNEL,user^.username,ch.channel,NextParamEx(gcmd.cmd)),true);
 tmp_pos:=703;
 if not (chRegistered in ch.state) then
  for i:=db_channels.count-1 downto 0 do
  if db_channels.Items[i]=ch then
  begin
    db_channels.Delete(i);
    ch.Free;
  end;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_DropChannel;
var
 ch: TChannel;
 i: Integer;
 motd_filename: String;
begin
 tmp_pos:=710;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=711;
 if ch.level>=user^.level then
 if ch.level<>napUserConsole then
 begin
   PermissionDenied('',true);
   exit;
 end;
 while ch.users.count>0 do
  ch.Part(ch.users.Items[0]);
 Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangT(LNG_DROPCHANNEL,user^.username,ch.channel,NextParamEx(gcmd.cmd)),true);
 motd_filename:=(ApplicationDir+'chmotd.'+ch.channel);
 if FileExists(motd_filename) then
  DeleteFile(PChar(motd_filename));
 tmp_pos:=712;
 for i:=db_channels.count-1 downto 0 do
 if db_channels.Items[i]=ch then
 begin
   db_channels.Delete(i);
   ch.Free;
 end;
 tmp_pos:=713;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_Redirect;
var
 user2: POnlineUser;
begin
 tmp_pos:=720;
 if not isLogged then exit;
 if not CheckLevel('',napUserElite) then exit;
 case gcmd.id of
   MSG_CLIENT_REDIRECT: if not CheckParams(3) then exit;
   else if not CheckParams(2) then exit;
 end;
 tmp_pos:=721;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 tmp_pos:=722;
 if user2^.level>=user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 Exec(user2,gcmd.id,NextParamEx(gcmd.cmd));
end;

procedure Handler_ChannelLevel;
var
 ch: TChannel;
 l: TNapUserLevel;
 i: Integer;
 user2: POnlineUser;
 b: Boolean;
begin
 tmp_pos:=730;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=731;
 if ch.level>user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if not isLevel(hlist.Strings[1]) then
 begin
   Error(GetLangT(LNG_INVALIDLEVEL,hlist.Strings[1]),true);
   exit;
 end;
 l:=Str2Level(hlist.Strings[1]);
 if ch.level=l then exit;
 if l>user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=732;
 ch.level:=l;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 b:=userCloaked in user^.state;
 tmp_pos:=733;
 for i:=0 to ch.users.count-1 do
 begin
   user2:=ch.users.Items[i];
   if user2^.server=nil then
   begin
     if (user2^.level>napUserUser) or (b=false) then
      Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_CHLEVEL,user^.username,ch.channel,Level2Str(ch.level)))
     else
      Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_CHLEVEL,'Operator',ch.channel,Level2Str(ch.level)))
   end;
 end;
end;

procedure Handler_ChannelLimit;
var
 ch: TChannel;
 i: Integer;
 user2: POnlineUser;
 b: Boolean;
begin
 tmp_pos:=734;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=735;
 if ch.level>user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if not isDigit(hlist.Strings[1]) then
 begin
   Error(GetLangT(LNG_INVALIDARGS),true);
   exit;
 end;
 tmp_pos:=736;
 i:=StrToIntDef(hlist.Strings[1],-1);
 if (i<2) or (i>65535) then
 begin
  Error(GetLangT(LNG_INVALIDARGS),true);
  exit;
 end;
 if ch.limit=i then exit;
 ch.limit:=i;
 tmp_pos:=737;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 b:=userCloaked in user^.state;
 tmp_pos:=738;
 for i:=0 to ch.users.count-1 do
 begin
   user2:=ch.users.Items[i];
   if user2^.server=nil then
   begin
     if (user2^.level>napUserUser) or (b=false) then
      Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_LIMIT,user^.username,ch.channel,IntToStr(ch.limit)))
     else
      Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_CHLEVEL,'Operator',ch.channel,IntToStr(ch.limit)))
   end;
 end;
end;

procedure Handler_Nuke; // 611
var
 user2: POnlineUser;
 user3: TLocalUser;
 b: Boolean;
 preg: PRegisteredUser;
 l: TNapUserLevel;
 i: Integer;
 srv: TServer;
 str: String;
begin
 tmp_pos:=740;
 if not isLogged then exit;
 if not CheckLevel('['+serveralias+'] アカウント削除失敗',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 b:=false;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=user then user2:=nil;
 tmp_pos:=741;
 str:=AnsiLowerCase(hlist.Strings[0]);
 if user2<>nil then
 begin
   if user2^.level>=user^.level then
   begin
     PermissionDenied('['+serveralias+'] アカウント削除失敗',true);
     exit;
   end;
   if user^.server=nil then
    if user2^.level>napUserModerator then
    for i:=0 to db_servers.count-1 do
    begin
      srv:=db_servers.Items[i];
      if srv.logged then
       if AnsiLowerCase(srv.reg_user)=str then
       begin
         Error(GetLangT(LNG_ACCESSREG,Level2Str(user2^.level),user2^.username,srv.host,srv.console),true);
         exit;
       end;
    end;
   tmp_pos:=742;
   if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
   user3:=user2^.local;
   if user3<>nil then
    if user3<>cons then
    begin
      user3.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_NUKEMSG));
      DisconnectUser(user3,'',GetLangT(LNG_NUKED,user2^.username,user2^.software,user^.username),'Handler_Nuke',false);
    end;
   tmp_pos:=743;
   b:=true;
 end;
 tmp_pos:=744;
 preg:=db_registered.FindUser(hlist.Strings[0]);
 if preg<>nil then
 begin
   l:=preg^.level;
   if l>=user^.level then
   begin
     PermissionDenied('',true);
     exit;
   end;
   if user^.server=nil then
    if l>napUserModerator then
    for i:=0 to db_servers.count-1 do
    begin
      srv:=db_servers.Items[i];
      if srv.logged then
       if srv.reg_user=str then
       begin
         Error(GetLangT(LNG_ACCESSREG,Level2Str(l),preg^.nick,srv.host,srv.console),true);
         exit;
       end;
    end;
   tmp_pos:=745;
   if not b then
    if user^.server=nil then
     WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
   db_registered.Delete(hlist.Strings[0]);
 end
 else
 if not b then
 begin
   tmp_pos:=746;
   if user^.server<>nil then
    NoSuchUser(true);
   exit;
 end;
 tmp_pos:=747;
 Wallop(MSG_SERVER_NOSUCH,wallopKill,GetLangT(LNG_NUKE,user^.username,hlist.Strings[0],NextParamEx(gcmd.cmd)),true);
end;

procedure Handler_UnRegister; // 8124
var
 user2: POnlineUser;
 b: Boolean;
 preg: PRegisteredUser;
 l: TNapUserLevel;
 i: Integer;
 srv: TServer;
 str: String;
begin
 tmp_pos:=7658;
 if not isLogged then exit;
 if not CheckLevel('['+serveralias+'] 登録解除失敗',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 b:=false;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=user then user2:=nil;
 tmp_pos:=7659;
 str:=AnsiLowerCase(hlist.Strings[0]);
 if user2<>nil then
 begin
   if user2^.level>=user^.level then
   begin
     PermissionDenied('['+serveralias+'] 登録解除失敗',true);
     exit;
   end;
   if user^.server=nil then
    if user2^.level>napUserModerator then
    for i:=0 to db_servers.count-1 do
    begin
      srv:=db_servers.Items[i];
      if srv.logged then
       if AnsiLowerCase(srv.reg_user)=str then
       begin
         Error(GetLangT(LNG_ACCESSREG,Level2Str(user2^.level),user2^.username,srv.host,srv.console),true);
         exit;
       end;
    end;
   tmp_pos:=7660;
   if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
   tmp_pos:=7661;
   b:=true;
 end;
 tmp_pos:=7662;
 preg:=db_registered.FindUser(hlist.Strings[0]);
 if preg<>nil then
 begin
   l:=preg^.level;
   if l>=user^.level then
   begin
     PermissionDenied('',true);
     exit;
   end;
   if user^.server=nil then
    if l>napUserModerator then
    for i:=0 to db_servers.count-1 do
    begin
      srv:=db_servers.Items[i];
      if srv.logged then
       if srv.reg_user=str then
       begin
         Error(GetLangT(LNG_ACCESSREG,Level2Str(l),preg^.nick,srv.host,srv.console),true);
         exit;
       end;
    end;
   tmp_pos:=7663;
   if not b then
    if user^.server=nil then
     WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
   db_registered.Delete(hlist.Strings[0]);
 end
 else
 if not b then
 begin
   tmp_pos:=7664;
   if user^.server<>nil then
    NoSuchUser(true);
   exit;
 end;
 tmp_pos:=7665;
 if user2<>nil then
  if user2^.level<>napUserUser then user2^.level:=napUserUser;
 Wallop(MSG_SERVER_NOSUCH,wallopLevel,GetLangT(LNG_UNREGISTER,user^.username,hlist.Strings[0],NextParamEx(gcmd.cmd)),true);
end;

procedure Handler_SetUserLevel;
var
 l,l1: TNapUserLevel;
 b: Boolean;
 user2, user3: POnlineUser;
 preg: PRegisteredUser;
 ch: TChannel;
 srv: TServer;
 i,j,k: Integer;
 str: String;
begin
 tmp_pos:=750;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(2) then exit;
 if not isLevel(hlist.Strings[1]) then
 begin
   Error(GetLangT(LNG_INVALIDLEVEL,hlist.Strings[1]),true);
   exit;
 end;
 tmp_pos:=751;
 l:=Str2Level(hlist.Strings[1]);
 if l>=user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 b:=false;
 str:=AnsiLowerCase(hlist.Strings[0]);
 user2:=db_online.FindUser(hlist.Strings[0]);
 tmp_pos:=752;
 if user2<>nil then
 begin
   b:=true;
   if user2^.level>=user^.level then
   begin
     PermissionDenied('',true);
     exit;
   end;
   if user^.server=nil then
    if user2^.level>napUserModerator then
    if l<>napUserElite then
    for i:=0 to db_servers.count-1 do
    begin
      srv:=db_servers.Items[i];
      if srv.logged then
       if AnsiLowerCase(srv.reg_user)=str then
       begin
         Error(GetLangT(LNG_ACCESSREG,Level2Str(user2^.level),user2^.username,srv.host,srv.console),true);
         exit;
       end;
    end;
   tmp_pos:=753;
   l1:=user2^.level;
   j:=0; if l>napUserUser then j:=1;
   if l1>napUserUser then inc(j,2);
   tmp_pos:=754;
   if (l<napUserModerator) or (l1<napUserModerator) then
   if userCloaked in user2^.state then
   if (j=1) or (j=2) then
   for i:=0 to db_channels.count-1 do
   begin
     ch:=db_channels.Items[i];
     tmp_pos:=755;
     if ch.FindUser(user2)<>-1 then
     begin
       case j of
         1: // hide user
            for k:=0 to ch.users.count-1 do
            begin
              user3:=ch.users.Items[i];
              if not ch.Operator(user3) then
               Exec(user3,MSG_SERVER_PART,ch.channel+' '+user2^.username+' '+IntToStr(user2^.shared)+' '+IntToStr(Ord(user2^.speed)));
            end;
         2: // show user
            for k:=0 to ch.users.count-1 do
            begin
              user3:=ch.users.Items[i];
              if not ch.Operator(user3) then
               Exec(user3,MSG_SERVER_JOIN,ch.channel+' '+user2^.username+' '+IntToStr(user2^.shared)+' '+IntToStr(Ord(user2^.speed)));
            end;
       end;
     end;
   end;
   tmp_pos:=756;
   user2^.level:=l;
   RegisterUser(user2,user^.username);
 end;
 tmp_pos:=757;
 preg:=db_registered.FindUser(hlist.Strings[0]);
 if preg<>nil then
 begin
   if b=false then
   if preg^.level>=user^.level then
   begin
     PermissionDenied('',true);
     exit;
   end;
   if user^.server=nil then
    if preg^.level>napUserModerator then
    if l<>napUserElite then
    for i:=0 to db_servers.count-1 do
    begin
      srv:=db_servers.Items[i];
      if srv.logged then
       if AnsiLowerCase(srv.reg_user)=str then
       begin
         Error(GetLangT(LNG_ACCESSREG,Level2Str(preg^.level),preg^.nick,srv.host,srv.console),true);
         exit;
       end;
    end;
   preg^.level:=l;
   b:=true;
   if user2=nil then
    preg^.lastsetby:=user^.username;
 end;
 tmp_pos:=758;
 if b then
 begin
   if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
   Wallop(MSG_SERVER_NOSUCH,wallopLevel,GetLangT(LNG_LEVEL2,user^.username,hlist.Strings[0],Level2Str(l),IntToStr(Ord(l))),true);
   if preg=nil then
    if user2<>nil then
     RegisterUser(user2,user^.username);
 end
 else
  NoSuchUser(true);
end;

procedure Handler_Kill;
var
 user2: POnlineUser;
 user3: TLocalUser;
 srv: TServer;
 i: Integer;
begin
 tmp_pos:=760;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 tmp_pos:=761;
 if user2=nil then
 begin
   UserIsOffline(hlist.Strings[0],true);
   exit;
 end;
 tmp_pos:=762;
 if user2^.level>=user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if num_servers>0 then
  if user2^.level=napUserElite then
   for i:=0 to db_servers.count-1 do
   begin
     srv:=db_servers.Items[i];
     if srv.logged then
      if AnsiLowerCase(srv.reg_user)=AnsiLowerCase(user2^.username) then
      begin
        PermissionDenied('',true);
        exit;
      end;
   end;
 tmp_pos:=763;
 Wallop(MSG_SERVER_NOSUCH,wallopKill,GetLangT(LNG_KICK,user^.username,user2^.username,NextParamEx(gcmd.cmd)),true);
 if user^.server=nil then WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 user3:=user2^.local;
 tmp_pos:=764;
 if user3<>nil then
  if user3<>cons then
  begin
   user3.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_KICKMSG));
   if user2^.level<napUserModerator then
    AddReconnector(decode_ip(user3.ip));
   DisconnectUser(user3,'',GetLangT(LNG_KILLED,user2^.username,user2^.software,user^.username),'Handler_Kill',false);
  end;
end;

procedure Handler_Kick;
var
 ch: TChannel;
 user2: POnlineUser;
 str,str1: String;
 i: Integer;
begin
 tmp_pos:=770;
 if not isLogged then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=771;
 if not ch.Operator(user) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 user2:=db_online.FindUser(hlist.Strings[1]);
 if user2=nil then
 begin
   NoSuchUser(true);
   exit;
 end;
 tmp_pos:=772;
 if ch.FindUser(user2)=-1 then
 begin
   NoSuchUser(true);
   exit;
 end;
 if user2^.level>=user^.level then
 begin
  if user^.level<>napUserUser then
  begin
    PermissionDenied('',true);
    exit;
  end;
  tmp_pos:=773;
  if ch.Operator(user2) then
  begin
    PermissionDenied('',true);
    exit;
  end;
 end;
 ch.Part(user2);
 case hlist.Count of
   2: str1:='';
   3: str1:=hlist.Strings[2];
   else str1:=NextParamEx(gcmd.cmd,2);
 end;
 tmp_pos:=774;
 str:=GetLangT(LNG_CHKICK,user^.username,user2^.username,ch.channel,str1);
 str1:=GetLangT(LNG_CHKICK,'Operator',user2^.username,ch.channel,str1);
 if not (userCloaked in user^.state) then str1:=str;
 for i:=0 to ch.users.count-1 do
 begin
   user2:=ch.users.Items[i];
   if user2^.server=nil then
   begin
     if user2^.level>napUserUser then
      Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+str)
     else
      Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+str1);
   end;
 end;
 tmp_pos:=775;
 if ch.users.count=0 then
  if not (chRegistered in ch.state) then
   for i:=db_channels.count-1 downto 0 do
   if db_channels.Items[i]=ch then
   begin
     db_channels.Delete(i);
     ch.Free;
   end;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_ChannelUsersList;
var
 i: Integer;
 user2: POnlineUser;
 ch: TChannel;
begin
 tmp_pos:=780;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=781;
 if user^.level<napUserModerator then
  if ch.FindUser(user)=-1 then
  begin
    PermissionDenied('',true);
    exit;
  end;
 if user^.level<ch.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=782;
 for i:=0 to ch.users.Count-1 do
 begin
   user2:=ch.users.Items[i];
   if (not (userCloaked in user2^.state)) or (user^.level>napUserUser) then
     Exec(user,MSG_SERVER_NAMES_LIST,ch.channel+' '+user2^.username+' '+IntToStr(user2^.shared)+' '+IntToStr(Ord(user2^.speed)));
  end;
 Exec(user,gcmd.id,'');
end;

procedure Handler_UserList;
var
 str,ip_mask: String;
 server_name: String;
 soft_mask: String;
 user2: POnlineUser;
 i,j,k: Integer;
 b,b_console, b_elite, b_admin, b_mod, b_user, b_leech, b_all, b_mask, b_server_mask,
 b_cloaked, b_notshare, b_softmask, b_showsoft, b_showshared, b_showserver: Boolean;
begin
 tmp_pos:=790;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 b_all:=true; ip_mask:='*'; soft_mask:='';
 b_console:=false; b_elite:=false; b_admin:=false; b_mod:=false;
 b_user:=false; b_leech:=false; b_cloaked:=false; b_notshare:=false;
 b_showsoft:=false; b_showshared:=false; b_softmask:=false; b_showserver:=false;
 SplitString(lowercase(gcmd.cmd),hlist);
 j:=0;
 if hlist.count>0 then server_name:=lowercase(hlist.strings[0])
 else server_name:='*';
 tmp_pos:=791;
 if hlist.count>1 then
 while j<hlist.count do
 begin
   if hlist.strings[j]='c' then b_console:=true
   else if hlist.strings[j]='e' then b_elite:=true
   else if hlist.strings[j]='a' then b_admin:=true
   else if hlist.strings[j]='m' then b_mod:=true
   else if hlist.strings[j]='u' then b_user:=true
   else if hlist.strings[j]='l' then b_leech:=true
   else if hlist.strings[j]='-c' then b_console:=true
   else if hlist.strings[j]='-e' then b_elite:=true
   else if hlist.strings[j]='-a' then b_admin:=true
   else if hlist.strings[j]='-m' then b_mod:=true
   else if hlist.strings[j]='-u' then b_user:=true
   else if hlist.strings[j]='-l' then b_leech:=true
   else if hlist.strings[j]='-cl' then b_cloaked:=true
   else if hlist.strings[j]='-cloak' then b_cloaked:=true
   else if hlist.strings[j]='-console' then b_console:=true
   else if hlist.strings[j]='-elite' then b_elite:=true
   else if hlist.strings[j]='-admin' then b_admin:=true
   else if hlist.strings[j]='-administrator' then b_admin:=true
   else if hlist.strings[j]='-mod' then b_mod:=true
   else if hlist.strings[j]='-moderator' then b_mod:=true
   else if hlist.strings[j]='-user' then b_user:=true
   else if hlist.strings[j]='-leech' then b_leech:=true
   else if hlist.strings[j]='-notshare' then b_notshare:=true
   else if hlist.strings[j]='-showshared' then b_showshared:=true
   else if hlist.strings[j]='-showsoftware' then b_showsoft:=true
   else if hlist.strings[j]='-showclient' then b_showsoft:=true
   else if hlist.strings[j]='-showserver' then b_showserver:=true
   else if hlist.Strings[j]='-mods' then
   begin
     b_mod:=true;
     b_admin:=true;
     b_elite:=true;
     b_console:=true;
   end
   else if (hlist.strings[j]='i') or (hlist.strings[j]='-ip') then
   begin
     if hlist.count>(j+1) then
      ip_mask:=hlist.strings[j+1];
     inc(j);
   end
   else if (hlist.strings[j]='-client') or (hlist.strings[j]='-software') then
   begin
     if hlist.count>(j+1) then
      soft_mask:=hlist.strings[j+1];
     inc(j);
   end;
   inc(j);
 end;
 tmp_pos:=792;
 if b_console or b_elite or b_admin or b_mod or b_user or b_leech then b_all:=false;
 b_mask:=ip_mask<>'*';
 b_server_mask:=server_name<>'*';
 if soft_mask<>'' then b_softmask:=true;
 if soft_mask='*' then b_softmask:=false;
 if b_softmask then soft_mask:=AnsiLowerCase(soft_mask);
 for k:=0 to USERS_NAME_ARRAY-1 do
 for i:=0 to db_online.names[k].Count-1 do
 begin
   tmp_pos:=793;
   if ((i mod 30)=5) then
   begin
    {$I checksync.pas}
   end;
   user2:=db_online.names[k].Items[i];
   b:=true;
   if not b_all then
   case user2^.level of
     napUserLeech: b:=b_leech;
     napUserUser: b:=b_user;
     napUserModerator: b:=b_mod;
     napUserAdmin: b:=b_admin;
     napUserElite: b:=b_elite;
     napUserConsole: b:=b_console;
   end;
   if (userCloaked in user2^.state) then b:=b_cloaked;
   if b and b_notshare then
    if user2^.shared>0 then b:=false;
   tmp_pos:=794;
   if b and b_mask then
    b:=MatchesMaskEx(decode_ip(user2^.ip),ip_mask);
   if b and b_server_mask then
    b:=MatchesMaskEx(lowercase(GetServerName(user2^.server)),server_name);
   if b and b_softmask then
    b:=MatchesMaskEx(AnsiLowerCase(user2^.software),soft_mask);
   if b then
   begin
    str:=user2^.username+' '+decode_ip(user2^.ip);
    if b_showshared then str:=str+' '+IntToStr(user2^.shared);
    if b_showsoft then str:=str+' '+AddStr(user2^.software);
    if b_showserver then str:=str+' '+AddStr(GetServerName(user2^.server));
    if query=queryNormal then
     Exec(user,MSG_SERVER_GLOBAL_USER_LIST,str)
    else
     Error(str);
   end;
 end;
 if query=queryNormal then
  Exec(user,MSG_CLIENT_GLOBAL_USER_LIST,'')
 else
  Error('.'); 
 tmp_pos:=795;
 {$I checksync.pas}
end;

procedure Handler_SetConfig;//
begin
 if not isLogged then exit;
 if not CheckLevel('',napUserAdmin) then exit;
 SetConfig(user,gcmd.cmd);
end;

procedure Handler_RelayMessage;
var
 user2: POnlineUser;
 i: Integer;
begin
 tmp_pos:=800;
 if query=queryServer then
  SplitString(gcmd.cmd,hlist)
 else
 begin
  if not isLogged then exit;
  if not checkParams(2) then exit;
  if not CheckLevel('',napUserAdmin) then exit;
 end;
 tmp_pos:=801;
 if not isDigit(hlist.Strings[0]) then
 begin
   Error(GetLangT(LNG_INVALIDARGS),true);
   exit;
 end;
 user2:=db_online.FindUser(hlist.Strings[1]);
 tmp_pos:=802;
 if user2=nil then
 begin
   NoSuchUser(true);
   exit;
 end;
 i:=StrToIntDef(hlist.Strings[0],MSG_SERVER_NOSUCH);
 if query<>queryServer then
 if user2^.level>napUserUser then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=803;
 Exec(user2,i,NextParamEx(gcmd.cmd,2));
end;

procedure Handler_RelayAll;
var
 i,j,k: Integer;
 str: String;
 user2: POnlineUser;
begin
 tmp_pos:=804;
 if query<>queryServer then exit;
 if not CheckParams(1) then exit;
 if not isDigit(hlist.Strings[0]) then
 begin
   Error(GetLangT(LNG_INVALIDARGS),true);
   exit;
 end;
 tmp_pos:=805;
 i:=StrToIntDef(hlist.Strings[0],404);
 str:=NextParamEx(gcmd.cmd);
 for k:=0 to USERS_NAME_ARRAY-1 do
 for j:=0 to db_online.names[k].Count-1 do
 begin
   if ((j mod 50)=5) then
   begin
    {$I checksync.pas}
   end;
   user2:=db_online.names[k].Items[j];
   if (user2<>user) and (user2^.server=nil) then
    Exec(user2,i,str);
 end;
 tmp_pos:=806;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_UsageStats;
var
 str: String;
 i,j: Integer;
 srv2: TServer;
begin
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 j:=0;
 for i:=0 to db_servers.Count-1 do
 begin
  srv2:=db_servers.Items[i];
  if srv2.logged and (srv2.hub=nil) then inc(j);
 end;
 str:=Format('%d %d %d %d %d %d %d %d %d %d %.2f %.2f %.2f %d %d 0',[
      local_users,
      j,
      total_users,
      total_files,
      total_bytes div 1073741824,
      db_channels.count,
      start_time_t,
      DateTimeToUnixTime(now)-start_time_t,
      AllocMemCount,
      db_registered.CountUsers,
      last_bytes_in / 60.0 / 1024.0,
      last_bytes_out / 60 / 1024,
      last_searches / 60,
      total_bytes_in,
      total_bytes_out
 ]);
 Exec(user,gcmd.id,str);
 case query of
  queryOperServ, queryNickServ, queryChanServ, queryMsgServ, queryChannel: Error('stats: '+str);
 end;
end;

procedure Handler_SoftwareStats;
var
 i: Integer;
 a: PNapCmd2;
begin
 if not isLogged then exit;
 tmp_pos:=820;
 if db_software<>nil then
 begin
   db_software.SortByID1;
   tmp_pos:=821;
   case query of
    queryOperServ, queryNickServ, queryMsgServ, queryChanServ, queryChannel:
                   begin
                    Error('クライアントソフトウェアの統計:');
                    Error('形式: <number of users> <software name>');
                   end;
   end;
   for i:=0 to db_software.count-1 do
   begin
     if (i mod 100)=50 then
     begin
      {$I checksync.pas}
     end;
     a:=db_software.Items[i];
     case query of
       queryOperServ, queryNickServ, queryMsgServ, queryChanServ,
       queryChannel:  Error(IntToStr(a^.id1)+'        '+#9+' '+a^.cmd);
       else Exec(user,MSG_CLIENT_VERSION_STATS,AddStr(a^.cmd)+' '+IntToStr(a^.id1));
     end;
   end;
 end;
 tmp_pos:=822;
 case query of
   queryOperServ, queryNickServ, queryMsgServ, queryChanServ,
   queryChannel: Error(GetLangT(LNG_ITEMSLISTED,IntToStr(db_software.count)));
   else Exec(user,MSG_CLIENT_VERSION_STATS,'');
 end;
end;

procedure Handler_WhichServer;
var
 user2: POnlineUser;
 l2: TLocalUser;
begin
 if not isLogged then exit;
 //if (user^.level=napUserLeech) or (userMuzzled in user^.state) then
 if user^.level<napUserModerator then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=823;
 user2:=db_online.FindUser(gcmd.cmd);
 if user2=nil then
 begin
   UserIsOffline(gcmd.cmd,true);
   exit;
 end;
 if userCloaked in user2.state then
  if user^.level<napUserModerator then
  begin
    UserIsOffline(gcmd.cmd,true);
    exit;
  end;
 l2:=user2^.local;
 tmp_pos:=824;
 if l2<>nil then
 begin
   if user^.level<napUserModerator then
    if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
    begin
      UserIsOffline(gcmd.cmd,true);
      exit;
    end;
 end;
 tmp_pos:=825;
 Error(GetLangT(LNG_USERSERVER,user2^.username,GetServerName(user2^.server)));
end;

procedure Handler_WhoWas; // 10121
var
 user2: POnlineUser;
 reg: PRegisteredUser;
 l2: TLocalUser;
 i: Integer;
 p: PNapCmdEx;
 str: String;
begin
 tmp_pos:=830;
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 user2:=db_online.FindUser(gcmd.cmd);
 if user2<>nil then
 begin
   l2:=user2^.local;
   if l2<>nil then
    if user^.level<napUserModerator then
     if StrHash_FindString(l2.ignored,AnsiLowerCase(user^.username),false) then
      user2:=nil;
 end;
 tmp_pos:=831;
 if user=nil then
 begin
   reg:=db_registered.FindUser(gcmd.cmd);
   if reg<>nil then
   begin
     if user^.level>napUserUser then
      Exec(user,gcmd.id,reg^.nick+' '+IntToStr(reg^.last_ip)+' '+servername_t+' '+IntToStr(reg^.last_seen)+' "n/a"')
     else
      Exec(user,gcmd.id,reg^.nick+' 0 '+serveralias+' '+IntToStr(reg^.last_seen)+' "n/a"');
     exit;
   end
   else
   begin
     i:=db_whowas.FindByCmd(AnsiLowerCase(gcmd.cmd));
     if i<>-1 then
     begin
       p:=db_whowas.Items[i];
       SplitString(p^.data,hlst);
       if hlst.count>5 then
       begin
         str:=gcmd.cmd+' ';
         if user^.level>napUserUser then
           str:=str+hlst.Strings[3]+' '
         else
           str:=str+'0 ';
         str:=str+AddStr(hlst.Strings[4])+' '+hlst.Strings[2]+' '+AddStr(hlst.Strings[5]);
         Exec(user,gcmd.id,str);
       end;
     end;
   end;
   tmp_pos:=832;
   UserIsOffline(gcmd.cmd,true);
   exit;
 end;
 tmp_pos:=833;
 if user^.level>napUserUser then
  Exec(user,gcmd.id,user2^.username+' '+IntToStr(user2^.ip)+' '+GetServerName(user2^.server)+' '+IntToStr(user2^.last_seen_t)+' "'+user2^.software+'"')
 else
  Exec(user,gcmd.id,user2^.username+' 0 '+GetServerAlias(user2^.server)+' '+IntToStr(user2^.last_seen_t)+' "'+user2^.software+'"');
end;

procedure Handler_AdminRegister;
var
 l: TNapUserLevel;
 reg: TRegisteredUser;
 preg: PRegisteredUser;
 usr: POnlineUser;
begin
 if not isLogged then exit;
 if not CheckParams(1) then exit;
 if hlist.count=1 then
 begin
   usr:=db_online.FindUser(hlist.Strings[0]);
   if usr=nil then UserIsOffline(hlist.Strings[0],true)
   else
   begin
     preg:=db_registered.FindUser(hlist.Strings[0]);
     if preg<>nil then Error(GetLangT(LNG_ALREADYREGISTERED,Level2Str(preg^.level),preg^.nick),true)
     else
     begin
       RegisterUser(usr,user^.username);
       Wallop(MSG_SERVER_NOSUCH,wallopRegister,GetLangT(LNG_REGOK2,user^.username,usr^.username,Level2Str(usr^.level)),true);
       if user^.server=nil then
        WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
     end;
   end;
   exit;
 end;
 if hlist.Count=2 then hlist.Add('anon@'+serveralias);
 if hlist.Count=3 then hlist.Add('1');
 tmp_pos:=840;
 if not isLevel(hlist.Strings[3]) then
 begin
   Error(GetLangT(LNG_INVALIDLEVEL,hlist.Strings[3]),true);
   exit;
 end;
 l:=Str2Level(hlist.Strings[3]);
 tmp_pos:=841;
 if (l>=user^.level) or (l<napUserUser) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if not check_name(hlist.Strings[0]) then
 begin
   Error(GetLangT(LNG_INVALIDNICK2,hlist.Strings[0]),true);
   exit;
 end;
 tmp_pos:=842;
 if db_registered.FindUser(hlist.Strings[0])<>nil then
 begin
   Error(GetLangT(LNG_REGEXISTS,hlist.Strings[0]),true);
   exit;
 end;
 tmp_pos:=843;
 ResetRegistered(reg);
 reg.nick:=hlist.Strings[0];
 reg.password:=encode(hlist.Strings[1]);
 reg.level:=l;
 reg.last_seen:=946702800;
 reg.created:=current_time_t;
 reg.createdby:=user^.username;
 db_registered.Add(reg);
 tmp_pos:=844;
 Wallop(MSG_SERVER_NOSUCH,wallopRegister,GetLangT(LNG_REGOK2,user^.username,reg.nick,Level2Str(reg.level)),true);
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_ChannelOp;
var
 ch: TChannel;
 user2: POnlineUser;
 str: String;
begin
 tmp_pos:=850;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=851;
 if ch.level>napUserUser then
 begin
   Error(GetLangT(LNG_CHNOOPS),true);
   exit;
 end;
 hlist.Delete(0);
 tmp_pos:=852;
 while hlist.Count>0 do
 begin
   str:=hlist.Strings[0];
   hlist.Delete(0);
   tmp_pos:=853;
   if not check_name(str) then
   begin
    if user^.server=nil then
     Error(GetLangT(LNG_INVALIDNICK2,str),true);
   end
   else if StrHash_FindString(ch.ops,str,true) then
   begin
     if user^.server=nil then
      Error(GetLangT(LNG_CHANOP3,str,ch.channel),true);
   end
   else
   begin
     user2:=db_online.FindUser(str);
     tmp_pos:=854;
     if (user2<>nil) and (user2^.level>napUserUser) then
     begin
       if user^.server=nil then
        Error(GetLangT(LNG_CHANOP3,str,ch.channel),true);
     end
     else
     begin
       tmp_pos:=855;
       StrHash_AddEx(ch.ops,str);
       Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangT(LNG_CHANOP,user^.username,str,ch.channel),true);
       if user2<>nil then
       if user2^.server=nil then
       begin
         if ch.FindUser(user2)<>-1 then
           Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_CHANOP2,ch.channel))
         else
          if user^.server=nil then
           Error(GetLangT(LNG_CHANOP2,ch.channel),true);  
       end;
       tmp_pos:=856;
     end;
   end;
 end;
 tmp_pos:=857;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_ChannelDeop;
var
 ch: TChannel;
 user2: POnlineUser;
 str: String;
begin
 tmp_pos:=860;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   if user^.server=nil then
    Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=861;
 if ch.level>napUserUser then
 begin
   Error(GetLangT(LNG_CHNOOPS),true);
   StrHash_Clear(ch.ops);
   exit;
 end;
 hlist.Delete(0);
 tmp_pos:=862;
 while hlist.Count>0 do
 begin
   str:=hlist.Strings[0];
   hlist.Delete(0);
   tmp_pos:=863;
   if not check_name(str) then
   begin
     if user^.server=nil then
      Error(GetLangT(LNG_INVALIDNICK2,str),true);
   end
   else
   begin
     tmp_pos:=864;
     if not StrHash_FindString(ch.ops,str,true) then
     begin
       if user^.server=nil then
        Error(GetLangT(LNG_CHANDEOP3,str,ch.channel),true);
     end
     else
     begin
       user2:=db_online.FindUser(str);
       if (user2<>nil) and (user2^.level>napUserUser) then
       begin
         if user^.server=nil then
           Error(GetLangT(LNG_CHANDEOP4,str),true);
       end
       else
       begin
         tmp_pos:=865;
         StrHash_Delete(ch.ops,str,true);
         Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangT(LNG_CHANDEOP,user^.username,str,ch.channel),true);
         if user2<>nil then
         if user2^.server=nil then
         begin
           tmp_pos:=866;
           if ch.FindUser(user2)<>-1 then
             Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_CHANDEOP2,ch.channel))
           else
             Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_CHANDEOP2,ch.channel));
         end;
       end;
     end;
   end;
 end;
 tmp_pos:=867;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_ChannelOpEmote;
var
 ch: TChannel;
 str, tempnic: String;
 i: Integer;
 user2: POnlineUser;
begin
 tmp_pos:=870;
 if not isLogged then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=871;
 if not ch.Operator(user) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 str:=NextParamEx(gcmd.cmd);
 if AnsiUpperCase(str)=AnsiLowerCase(str) then exit;
 //if Length(str)>0 then
  if str[1]<>'"' then
   str:='"'+str+'"';
 //if prevent_shouting then
 // if AnsiUpperCase(str)=str then
 // begin
 //  str:=AnsiLowerCase(str);
 //  gcmd.cmd:=ch.channel+' '+str;
 // end;
 if userCloaked in user^.state then
  tempnic:='Cloaked/'+user^.username
 else
  tempnic:=user^.username;
 str:=ch.channel+' ops/'+tempnic+' '+str;
 tmp_pos:=872;
 for i:=0 to ch.users.Count-1 do
 begin
   user2:=ch.users.Items[i];
   if user2^.server=nil then
    if ch.Operator(user2) then
    begin
     if old_opsay then
      Exec(user2,MSG_SERVER_NOSUCH,str)
     else
      Exec(user2,MSG_SERVER_EMOTE,str);
    end;
 end;
 tmp_pos:=873;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_ChannelWallop; // 10208
var
 ch: TChannel;
 str, tempnic: String;
 i: Integer;
 user2: POnlineUser;
begin
 tmp_pos:=870;
 if not isLogged then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=871;
 if not ch.Operator(user) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 str:=NextParamEx(gcmd.cmd);
 //if prevent_shouting then
 // if AnsiUpperCase(str)=str then
 // begin
 //  str:=AnsiLowerCase(str);
 //  gcmd.cmd:=ch.channel+' '+str;
 // end;
 if userCloaked in user^.state then
  tempnic:='Cloaked/'+user^.username
 else
  tempnic:=user^.username;
 if old_opsay then
  str:=tempnic+' [ops/'+ch.channel+']: '+str
 else
  str:=ch.channel+' ops/'+tempnic+' '+str;
 tmp_pos:=872;
 for i:=0 to ch.users.Count-1 do
 begin
   user2:=ch.users.Items[i];
   if user2^.server=nil then
    if ch.Operator(user2) then
    begin
     if old_opsay then
      Exec(user2,MSG_SERVER_NOSUCH,str)
     else
      Exec(user2,MSG_SERVER_PUBLIC,str);
    end;
 end;
 tmp_pos:=873;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_ChannelMode; // 10209
var
 ch: TChannel;
 st,old_st: TChannelState;
 str: String;
begin
 tmp_pos:=880;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(1) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=881;
 if ch.level>user^.level then
 begin
   PermissionDenied('',true);
   exit;
 end;
 hlist.Delete(0);
 tmp_pos:=882;
 old_st:=ch.state;
 st:=ch.state;
 while hlist.Count>0 do
 begin
   str:=lowercase(hlist.Strings[0]);
   hlist.Delete(0);
   if (str='+private') or (str='+p') then st:=st+[chPrivate]
   else if (str='-private') or (str='-p') then st:=st-[chPrivate]
   else if (str='+moderated') or (str='+m') then st:=st+[chModerated]
   else if (str='-moderated') or (str='-m') then st:=st-[chModerated]
   else if (str='+topic') or (str='+t') then st:=st+[chTopic]
   else if (str='-topic') or (str='-t') then st:=st-[chTopic]
   else if (str='+registered') or (str='+r') then st:=st+[chRegistered]
   else if (str='-registered') or (str='-r') then st:=st-[chRegistered]
   else Error(GetLangT(LNG_UNKNOWNCHMODE,str));
 end;
 tmp_pos:=883;
 ch.state:=st;
 if st<>old_st then
 begin
   str:='';
   if (chPrivate in st) and (not (chPrivate in old_st)) then str:=str+' +PRIVATE';
   if (chPrivate in old_st) and (not (chPrivate in st)) then str:=str+' -PRIVATE';
   if (chModerated in st) and (not (chModerated in old_st)) then str:=str+' +MODERATED';
   if (chModerated in old_st) and (not (chModerated in st)) then str:=str+' -MODERATED';
   if (chTopic in st) and (not (chTopic in old_st)) then str:=str+' +TOPIC';
   if (chTopic in old_st) and (not (chTopic in st)) then str:=str+' -TOPIC';
   if (chRegistered in st) and (not (chRegistered in old_st)) then str:=str+' +REGISTERED';
   if (chRegistered in old_st) and (not (chRegistered in st)) then str:=str+' -REGISTERED';
   Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangEx('<$1> $2チャンネルのモードを変更しました:$3',user^.username,ch.channel,str),true);
   if user^.server=nil then
    WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 end;
 if user^.server<>nil then exit;
 tmp_pos:=884;
 str:='';
 if chRegistered in st then str:='+REGISTERED';
 if chPrivate in st then
 begin
  if str<>'' then str:=str+' ';
  str:=str+'+PRIVATE';
 end;
 if chModerated in st then
 begin
  if str<>'' then str:=str+' ';
  str:=str+'+MODERATED';
 end;
 if chTopic in st then
 begin
  if str<>'' then str:=str+' ';
  str:=str+'+TOPIC';
 end;
 if str='' then str:='<empty>';
 tmp_pos:=885;
 if (local<>cons) or (query<>queryNormal) then
  Error(GetLangEx('$1チャンネルのモードは $2',ch.channel,str));
end;

procedure Handler_ChannelVoice;
var
 ch: TChannel;
 user2: POnlineUser;
 str,str1: String;
begin
 tmp_pos:=890;
 if not isLogged then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=891;
 if not ch.Operator(user) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if (not (chModerated in ch.state)) or (ch.level>napUserUser) then
 begin
   Error(GetLangT(LNG_NOMODERATE,ch.channel),true);
   exit;
 end;
 hlist.Delete(0);
 tmp_pos:=892;
 str1:=user^.username;
 if userCloaked in user^.state then str1:='Operator';
 while hlist.Count>0 do
 begin
   str:=hlist.Strings[0];
   hlist.Delete(0);
   tmp_pos:=893;
   if not check_name(str) then
     Error(GetLangT(LNG_INVALIDNICK2,str),true)
   else if StrHash_FindString(ch.voices,str,true) then
     Error(GetLangT(LNG_VOICE3,str,ch.channel),true)
   else
   begin
     tmp_pos:=894;
     user2:=db_online.FindUser(str);
     if (user2<>nil) and (user2^.level>napUserUser) then
       Error(GetLangT(LNG_VOICE3,str,ch.channel),true)
     else
     begin
       tmp_pos:=895;
       StrHash_AddEx(ch.voices,str);
       if user^.server=nil then
        ch.Wallop(GetLangT(LNG_VOICE4,user^.username,str));
       if user2<>nil then
       if user2^.server=nil then
       begin
         if ch.FindUser(user2)<>-1 then
           Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_VOICE,str1,ch.channel))
         else
           Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_VOICE,str1,ch.channel));
       end;
     end;
   end;
 end;
 tmp_pos:=896;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_ChannelUnvoice;
var
 ch: TChannel;
 user2: POnlineUser;
 str: String;
begin
 tmp_pos:=900;
 if not isLogged then exit;
 if not CheckLevel('',napUserModerator) then exit;
 if not CheckParams(2) then exit;
 ch:=FindChannel(hlist.Strings[0]);
 if ch=nil then
 begin
   Error(GetLangT(LNG_NOCHANNEL),true);
   exit;
 end;
 tmp_pos:=901;
 if not ch.Operator(user) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if (not (chModerated in ch.state)) or (ch.level>napUserUser) then
 begin
   Error(GetLangT(LNG_NOMODERATE,ch.channel),true);
   StrHash_Clear(ch.voices);
   exit;
 end;
 hlist.Delete(0);
 tmp_pos:=902;
 while hlist.Count>0 do
 begin
   str:=hlist.Strings[0];
   hlist.Delete(0);
   tmp_pos:=903;
   if not check_name(str) then
     Error(GetLangT(LNG_INVALIDNICK2,str),true)
   else
   begin
     if not StrHash_FindString(ch.voices,str,true) then
       Error(GetLangT(LNG_DEVOICE3,str,ch.channel),true)
     else
     begin
       tmp_pos:=904;
       user2:=db_online.FindUser(str);
       if (user2<>nil) and (user2^.level>napUserUser) then
         Error(GetLangT(LNG_DEVOICE4,str),true)
       else
       begin
         tmp_pos:=905;
         StrHash_Delete(ch.voices,str,true);
         if user^.server=nil then
          ch.Wallop(GetLangT(LNG_DEVOICE5,user^.username,str));
         if user2<>nil then
         if user2^.server=nil then
         begin
           tmp_pos:=906;
           if ch.FindUser(user2)<>-1 then
             Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_DEVOICE,ch.channel))
           else
             Exec(user2,MSG_SERVER_NOSUCH,GetLangT(LNG_DEVOICE,ch.channel));
         end;
       end;
     end;
   end;
 end;
 tmp_pos:=907;
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
end;

procedure Handler_GetConsole;
var
 i,num: Integer;
 srv: TServer;
 display_name: String;
 moderator: Boolean;
begin
 if user^.level<napUserModerator then
 begin
  PermissionDenied('',true);
  exit;
 end;
 if query=queryNormal then
   Error(servername_t+' '+cons.data^.username)
 else
   Error(cons.data^.username+'  '#9+servername_t);
 if num_servers=0 then
 begin
  if query=queryNormal then
   local.Exec(gcmd.id,gcmd.cmd)
  else
   Error(GetLangT(LNG_ITEMSLISTED,'1'));
  exit;
 end;
 num:=1;
 for i:=0 to db_servers.count-1 do
 begin
   srv:=db_servers.Items[i];
   if srv.logged then
   begin
    if query=queryNormal then
     Error(srv.host+' '+srv.console)
    else
     Error(srv.console+'  '#9+srv.host);
    inc(num);
   end;
 end;
 if query=queryNormal then
  local.Exec(gcmd.id,gcmd.cmd)
 else
  Error(GetLangT(LNG_ITEMSLISTED,IntToStr(num)));
end;

procedure Handler_Transfer;
begin
 if not isLogged then exit;
 if user^.level=napUserLeech then exit;
 if (query<>queryNormal) and (query<>queryRemoteUser) then exit;
 case gcmd.id of
   MSG_CLIENT_DOWNLOAD_START: begin inc(user^.downloads); inc(user^.total_down); end;
   MSG_CLIENT_DOWNLOAD_END: begin dec(user^.downloads); if user^.downloads<0 then user^.downloads:=0; end;
   MSG_CLIENT_UPLOAD_START: begin inc(user^.uploads); inc(user^.total_up); end;
   MSG_CLIENT_UPLOAD_END: begin dec(user^.uploads); if user^.uploads<0 then user^.uploads:=0; end;
 end;
 if local<>nil then local.localstate:=local.localstate+[locNeedsUpdate];
end;

procedure Handler_SetTransfers;
begin
  if not isLogged then exit;
 if not CheckParams(4) then exit;
 if user=nil then exit;
 user^.downloads:=StrToIntDef(hlist.Strings[0],0);
 user^.uploads:=StrToIntDef(hlist.Strings[1],0);
 user^.max_up:=StrToIntDef(hlist.Strings[2],-1);
 user^.queue:=StrToIntDef(hlist.Strings[3],-1);
 if user^.server=nil then
 begin
  if local=nil then
    WriteAllServers(gcmd.id,user^.username,gcmd.cmd)
  else
    local.localstate:=local.localstate+[locNeedsUpdate];
 end;
end;

procedure Handler_GetTransfers;
var
 user2: POnlineUser;
begin
 if not isLogged then exit;
 user2:=db_online.FindUser(gcmd.cmd);
 if user2=nil then
 begin
   UserIsOffline(gcmd.cmd,true);
   exit;
 end;
 Exec(user,gcmd.id,user2^.username+' '+IntToStr(user2^.downloads)+' '+IntToStr(user2^.uploads)+' '+IntToStr(user2^.max_up)+' '+IntToStr(user2^.queue));
end;

procedure Handler_GhostKiller;
var
 i: Integer;
 user2: POnlineUser;
 users: TMyList;
begin
 if not isLogged then exit;
 if user^.server<>nil then exit;
 if gcmd.cmd='' then
 begin // list users with that IP
   users:=TMyList.Create;
   db_online.GetClones(user^.ip,users);
   for i:=0 to users.count-1 do
   if users.Items[i]<>user then
   begin
    user2:=users.Items[i];
    if query=queryNormal then
     Error(user2^.username+' '+GetServerAlias(user2^.server))
    else
     Error('ユーザー名: '+user2^.username+'  サーバー: '+GetServerAlias(user2^.server));
   end;
   if query=queryNormal then
    local.Exec(gcmd.id,gcmd.cmd)
   else
    Error(GetLangT(LNG_ITEMSLISTED,IntToStr(users.count-1)));
   exit;
 end;
 if not CheckParams(2) then exit;
 user2:=db_online.FindUser(hlist.Strings[0]);
 if user2=nil then
 begin
  UserIsOffline(hlist.Strings[0],true);
  exit;
 end;
 if user2=user then
 begin
  PermissionDenied('');
  exit;
 end;
 if user2^.level=napUserConsole then
 begin
  PermissionDenied('');
  exit;
 end;
 if user^.ip<>user2^.ip then
 begin
  PermissionDenied('');
  exit;
 end;
 if user2^.password<>encode(hlist.Strings[1]) then
 begin
  PermissionDenied('');
  exit;
 end;
 Wallop(MSG_SERVER_NOSUCH,wallopKill,GetLangT(LNG_KICK,user^.username,user2^.username,'ゴースト'),false);
 WriteAllServers(MSG_SRV_USEROFFLINE,user2^.username,'');
 KickUser(user2,'<'+user^.username+'> サーバーから切断しました: ゴースト');
end;

procedure Handler_AddChannel;
var
 ch: TChannel;
 str: String;
begin
 tmp_pos:=910;
 if not CheckLevel('',napUserModerator) then exit;
 str:=ChannelName(gcmd.cmd);
 if str='' then
 begin
   Error(GetLangT(LNG_INVALIDARGS),true);
   exit;
 end;
 ch:=Findchannel(str);
 tmp_pos:=911;
 if ch<>nil then exit;
 if db_channels.count>=max_channels_total then
 if user^.server=nil then
 begin
   Error(GetLangT(LNG_CHANNELLIMIT),true);
   exit;
 end;
 tmp_pos:=912;
 ch:=TChannel.Create(str);
 ch.state:=[chRegistered];
 db_channels.Add(ch);
 tmp_pos:=913;
 Wallop(MSG_SERVER_NOSUCH,wallopChannel,GetLangT(LNG_CHCREATED,Level2Str(user^.level),user^.username,str),true);
 if user^.server=nil then
  WriteAllServers(gcmd.id,user^.username,gcmd.cmd);
 tmp_pos:=914;
 if local=cons then
  cmd_list.AddDoubleCmd(MSG_CMD_LISTCHANNELS,0,'',''); 
end;

procedure Handler_ServerConnect(srv: TServer; timer: Boolean);
var
 host,port: String;
 i: Integer;
begin
 tmp_pos:=915;
 if not timer then
 begin
   if not isLogged then exit;
   if not CheckLevel('',napUserAdmin) then exit;
   if not CheckParams(1) then exit;
   if local=nil then exit;
   hlist.Strings[0]:=lowercase(hlist.Strings[0]);
   i:=pos(':',hlist.Strings[0]);
   if i=0 then
   begin
     host:=hlist.Strings[0];
     port:='8888';
   end
   else
   begin
     host:=copy(hlist.Strings[0],1,i-1);
     port:=copy(hlist.Strings[0],i+1,5);
   end;
   host:=lowercase(host);
   srv:=FindServer(host,true);
   if local<>cons then
   if AnsiLowerCase(local.nick)<>AnsiLowerCase(cons_reg_user) then
   if restrict_outgoing then
   if (srv=nil) or (srv.relink=0) then
   begin
     SplitString(outgoing_list,hlst);
     if GetIndex(hlst,host,false)=-1 then
     begin
       Error(GetLangT(LNG_OUTGOINGRESTRICTED,host));
       exit;
     end;
   end;
   tmp_pos:=916;
   if srv=nil then
   begin
     srv:=TServer.Create;
     srv.host:=host;
     srv.port:=StrToIntDef(port,8888);
     db_servers.Add(srv);
   end;
 end
 else
  if srv=nil then exit;
 tmp_pos:=917;
 if srv.connected<>conNotConnected then
 begin
   if not timer then
    Error(GetLangT(LNG_LINKED),true);
   exit;
 end;
 tmp_pos:=918;
 if timer then
  Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKREQ2,srv.host),true)
 else
  Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKREQ,user^.username,host),true);
 srv.login_start:=GetTickCount;
 linking:=true;
 srv.Connect;
end;

procedure Handler_ServerDisconnect;
var
 srv: TServer;
begin
 tmp_pos:=920;
 if not isLogged then exit;
 if not CheckLevel('',napUserAdmin) then exit;
 if not CheckParams(1) then exit;
 srv:=FindServer(gcmd.cmd,true);
 tmp_pos:=921;
 if srv=nil then
 begin
   Error(GetLangT(LNG_NOSUCHSERVER),true);
   exit;
 end;
 if (srv.connected<>conConnected) or (srv.hub<>nil) then
 begin
   Error(GetLangT(LNG_CANTDELINK,srv.host),true);
   exit;
 end;
 tmp_pos:=922;
 if not CheckLag(srv) then
 begin // sending disconnect message
   WriteAllServersEx(srv,MSG_SRV_SHUTDOWN,'',user^.username);
   srv.Flush;
 end;
 DisconnectServer(srv,true,false,'Handler_ServerDisconnect');
 Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_DELINKREQ,srv.host,user^.username),true);
end;

procedure Handler_KillServer;
var
 srv: TServer;
begin
 tmp_pos:=1259;
 if not isLogged then exit;
 if not CheckLevel('',napUserElite) then exit;
 if not CheckParams(1) then exit;
 srv:=FindServer(gcmd.cmd,true);
 if srv=nil then
 begin
   Error(GetLangT(LNG_NOSUCHSERVER),true);
   exit;
 end;
 tmp_pos:=1260;
 if (srv.connected<>conConnected) or (srv.hub<>nil) then
 begin
   Error(GetLangT(LNG_CANTKILL,srv.host),true);
   exit;
 end;
 tmp_pos:=1261;
 DisconnectServer(srv,true,false,'Handler_KillServer');
 Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_KILLSERVER,user^.username,srv.host),true);
end;

procedure Handler_RemoveServer;
var
 srv: TServer;
 i: Integer;
begin
 tmp_pos:=930;
 if not isLogged then exit;
 if not CheckLevel('',napUserElite) then exit;
 if not CheckParams(1) then exit;
 srv:=FindServer(gcmd.cmd,true);
 if srv=nil then
 begin
   Error(GetLangT(LNG_NOSUCHSERVER),true);
   exit;
 end;
 tmp_pos:=931;
 if srv.connected<>conNotConnected then
 begin
   Error(GetLangT(LNG_CANTREMOVE,srv.host),true);
   exit;
 end;
 for i:=db_servers.count-1 downto 0 do
  if db_servers.Items[i]=srv then
  begin
    Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_REMOVESERVER,user^.username,srv.host),true);
    db_servers.Delete(i);
    srv.Free;
    exit;
  end;
 tmp_pos:=932;
 Error(GetLangT(LNG_NOSUCHSERVER),true);
end;

procedure DisconnectServer(srv: TServer; inform_servers, do_wallop: Boolean; sender: String);
var
 i,j: Integer;
 srv2: TServer;
 user2: POnlineUser;
begin
 tmp_pos:=933;
 if srv=nil then exit;
 if srv.connected=conNotConnected then
 begin
   srv.ResetData;
   exit;
 end;
 tmp_pos:=934;
 if srv.logged then
  if srv.hub=nil then
   WriteAllServers(MSG_SRV_DISCONNECTED,'',srv.host,GetServerLink(srv));
 if db_servers<>nil then
 for i:=0 to db_servers.count-1 do
 begin
   srv2:=db_servers.Items[i];
   if srv2.hub=srv then
   begin
    DisconnectServer(srv2,false,false,'req-'+sender);
    Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_DELINKHUB,srv2.host,srv.host),true);
   end;
 end;
 tmp_pos:=935;
 srv.ResetData;
 srv.login_start:=GetTickCount;
 if srv.thread<>nil then
 try
  srv.thread.Terminate;
  srv.thread:=nil;
  except
 end;
 tmp_pos:=936;
 if srv.socket<>INVALID_SOCKET then
 begin
  DoCloseSocket(srv.socket);
  srv.socket:=INVALID_SOCKET;
 end;
 if srv.out_list<>nil then
 try
  FreeCmdList(srv.out_list);
  srv.out_list:=nil;
  except
 end;
 tmp_pos:=937;
 if srv.out_buf<>nil then
 try
  FreeCmdList(srv.out_buf);
  srv.out_buf:=nil;
  except
 end;
 tmp_pos:=938;
 sleep(5);
 for j:=0 to USERS_NAME_ARRAY-1 do
 for i:=db_online.names[j].Count-1 downto 0 do
 try
   user2:=db_online.names[j].Items[i];
   if user2^.server=srv then
     KickUser(user2,'');
   if ((i mod 50)=0) then
   begin
    {$I checksync.pas}
   end;
  except
 end;
 tmp_pos:=939;
 CountStats;
 if do_wallop then Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_DELINK,srv.host,sender),true);
end;

procedure Handler_SetLastInv;
var
 s: PNapCmdEx;
 i: Integer;
begin
 if not CheckParams(1) then exit;
 local:=FindLocalUser(hlist.Strings[0]);
 if local=nil then exit;
 for i:=db_invitations.count-1 downto 0 do
 begin
   s:=db_invitations.Items[i];
   if s^.data=local.nick then
    db_invitations.Delete(i)
   else
    if (GetTickCount-s^.id)>EXPIRE_INVITATION then
     db_invitations.Delete(i);
 end;
 db_invitations.AddCmd(GetTickCount,NextParamEx(gcmd.cmd),local.nick);
end;

procedure Handler_ForwardAllServers;
var
 srv,srv2,srv3: TServer;
 i,j: Integer;
 str: String;
 user: POnlineUser;
begin
 tmp_pos:=940;
 if not CheckParams(2) then exit;
 srv:=FindServer(StrToIntDef(hlist.Strings[1],0));
 if srv=nil then exit; // error
 if not srv.logged then exit; // error
 tmp_pos:=941;
 srv2:=GetServerLink(srv);
 if srv2<>server then exit; // error
 if (srv2<>nil) and (srv.logged=false) then exit; // error
 tmp_pos:=942;
 gcmd.id:=StrToIntDef(hlist.Strings[0],0);
 if gcmd.id=0 then exit;
 if search_noforward_requests then
 if gcmd.id=MSG_CLIENT_SEARCH then
 try
  tmp_pos:=943;
  if CheckLag(server) then // won't be able to return search results
   if hlist.count>2 then
   begin // close search
     tmp_pos:=944;
     str:=hlist.Strings[2]; // user
     user:=db_online.FindUser(str);
     tmp_pos:=945;
     if user<>nil then
     if user^.server<>nil then
     begin
       j:=CountLinkedServers(server);
       for i:=1 to j do
        user^.server.Exec(MSG_SRV_SEARCH_END,user^.username);
     end;
     tmp_pos:=946;
     exit;
   end;
   tmp_pos:=947;
  except
   on E:Exception do
    DebugLog('Exception in Handler_ForwardAllServers (pos='+IntToStr(tmp_pos)+') : '+E.Message);
 end;
 tmp_pos:=948;
 srv3:=GetServerLink(server);
 tmp_pos:=949;
 if srv3<>nil then
 for i:=0 to db_servers.count-1 do
 begin
   srv2:=db_servers.Items[i];
   if srv2.logged and (srv2.hub=nil) then
    if srv2<>srv3 then
     srv2.Exec(MSG_SRV_FORWARDALL,gcmd.cmd);
 end;
 gcmd.cmd:=NextParamEx(gcmd.cmd,2);
 ProcessServerCommand(srv);
end;

procedure CountStats;
var
 i: Integer;
 srv: TServer;
 num_users,num_files, srv_num, srv_direct, num_max: Integer;
 num_bytes: Int64;
begin
 num_users:=local_users;
 num_files:=local_files;
 num_bytes:=local_bytes;
 num_max:=max_users;
 srv_num:=0;
 srv_direct:=0;
 tmp_pos:=950;
 if db_servers<>nil then
 for i:=0 to db_servers.Count-1 do
 begin
   tmp_pos:=951;
   srv:=db_servers.Items[i];
   tmp_pos:=952;
   if srv.logged then
   begin
     inc(num_users,srv.num_users);
     inc(num_files,srv.num_files);
     inc(num_bytes,srv.num_bytes);
     inc(num_max,srv.max_users);
     inc(srv_num);
     if srv.hub=nil then
      inc(srv_direct);
   end;
 end;
 tmp_pos:=953;
 total_users:=num_users;
 total_files:=num_files;
 total_bytes:=num_bytes;
 total_users_limit:=num_max;
 num_servers:=srv_num;
 direct_links:=srv_direct;
 if total_users_max<total_users then total_users_max:=total_users;
 if total_files_max<total_files then total_files_max:=total_files;
 if total_bytes_max<total_bytes then total_bytes_max:=total_bytes;
end;

procedure Handler_ServerStats;
var
 a,b,c,d: Int64;
begin
 tmp_pos:=960;
 if not CheckParams(4) then exit;
 if server=nil then exit;
 a:=StrToInt64Def(hlist.Strings[0],server.num_users);
 b:=StrToInt64Def(hlist.Strings[1],server.num_files);
 c:=StrToInt64Def(hlist.Strings[2],server.num_bytes);
 d:=StrToInt64Def(hlist.Strings[3],server.max_users);
 tmp_pos:=961;
 if (a<0) or (b<0) or (c<0) or (d<0) then exit; // something weird
 server.num_users:=a;
 server.num_files:=b;
 server.num_bytes:=c;
 server.max_users:=d;
 tmp_pos:=962;
 CountStats;
end;

procedure Handler_SrvRelay;
var
 i,id,src,dst: Integer;
 srv, srv2: TServer;
begin
 tmp_pos:=970;
 SplitString(gcmd.cmd,hlist);
 if hlist.Count<3 then exit; // internal error
 dst:=StrToIntDef(hlist.Strings[0],0);
 src:=StrToIntDef(hlist.Strings[1],0);
 tmp_pos:=971;
 if (dst=0) or (src=0) then exit; // internal error
 id:=StrToIntDef(hlist.Strings[2],0);
 gcmd.cmd:=NextParamEx(gcmd.cmd,3);
 tmp_pos:=972;
 if dst=myserverhandle then
 begin
   // message for this server
   try
     srv:=nil;
     tmp_pos:=973;
     for i:=0 to db_servers.Count-1 do
     begin
       srv2:=db_servers.Items[i];
       if srv2.server_handle=src then
         srv:=srv2;
     end;
     tmp_pos:=974;
     if srv=nil then exit;
     if srv.connected<>conConnected then exit;
     tmp_pos:=975;
     gcmd.id:=id;
     ProcessServerCommand(srv);
    except
     on E:Exception do
      DebugLog('Exception in Handler_SrvRelay (pos='+IntToStr(tmp_pos)+') : '+E.Message);
   end;
   exit;
 end;
 tmp_pos:=976;
 for i:=0 to db_servers.Count-1 do
 begin
   srv2:=db_servers.Items[i];
   tmp_pos:=977;
   if srv2.logged then
   if srv2.server_handle=dst then
   begin
     tmp_pos:=978;
     if search_noforward_results then
      if id=MSG_SRV_SEARCH_RESULT then // to avoid too much traffic
       if CheckLag(GetServerLink(srv2)) then
        exit;
     tmp_pos:=979;
     if browse_noforward_results then
      if id=MSG_CLIENT_RELAY then // do not return browse result
       if FirstParam(gcmd.cmd)='212' then
        if CheckLag(GetServerLink(srv2)) then
         exit;
     tmp_pos:=980;
     srv2.Relay(id,gcmd.cmd,dst,src);
     exit;
   end;
 end;
end;

procedure Handler_SrvRemoteDisconnect;
var
 srv: TServer;
begin
 tmp_pos:=981;
 srv:=FindServer(gcmd.cmd,true);
 if srv=nil then exit;
 tmp_pos:=982;
 if srv.connected=conConnected then
  DisconnectServer(srv,false,true,'Handler_SrvRemoteDisconnect from '+server.host);
end;

procedure Handler_ServerSyncChannel;
var
 ch: TChannel;
 overwrite: Boolean;
 st: TChannelState;
 i: Integer;
begin
 tmp_pos:=990;
 SplitString(gcmd.cmd,hlist);
 if hlist.Count<6 then exit;
 overwrite:=hlist.Strings[1]='1';
 st:=[];
 i:=StrToIntDef(hlist.Strings[5],0);
 if (i and 1)>0 then st:=st+[chRegistered];
 if (i and 2)>0 then st:=st+[chPrivate];
 if (i and 4)>0 then st:=st+[chModerated];
 if (i and 8)>0 then st:=st+[chTopic];
 ch:=FindChannel(hlist.Strings[0]);
 tmp_pos:=991;
 if ch=nil then
 begin // new channel
   tmp_pos:=992;
   ch:=TChannel.Create(hlist.Strings[0]);
   ch.topic:=hlist.Strings[2];
   ch.limit:=StrToIntDef(hlist.Strings[3],ch.limit);
   ch.level:=Str2Level(hlist.Strings[4]);
   ch.state:=st;
   tmp_pos:=993;
   db_channels.Add(ch);
   WriteAllServers(gcmd.id,'',gcmd.cmd,server);
   exit;
 end;
 tmp_pos:=994;
 if not overwrite then exit; // remote server should change its channel props, not this one
 ch.SetTopic(hlist.Strings[2]);
 ch.limit:=StrToIntDef(hlist.Strings[3],ch.limit);
 ch.level:=Str2Level(hlist.Strings[4]);
 ch.state:=st;
end;

procedure Handler_ServerChannelVoice;
var
 user: POnlineUser;
 ch: TChannel;
 forward_all: Boolean;
 voice,voice2: Boolean;
 srv: TServer;
begin
 tmp_pos:=995;
 SplitString(gcmd.cmd,hlist);
 if hlist.Count<5 then exit;
 voice:=hlist.Strings[3]='1';
 forward_all:=hlist.Strings[4]='1';
 ch:=FindChannel(hlist.Strings[0]);
 tmp_pos:=996;
 if ch=nil then exit;
 if not (chModerated in ch.state) then exit;
 user:=db_online.FindUser(hlist.Strings[1]);
 tmp_pos:=997;
 if user=nil then exit;
 if ch.FindUser(user)=-1 then exit;
 voice2:=StrHash_FindString(ch.voices,user^.username,true);
 tmp_pos:=998;
 if voice=voice2 then exit;
 if not voice then
 begin // can speak here, but can't on linked server
   if forward_all then
    WriteAllServersEx(server,MSG_SRV_VOICE,'',ch.channel+' '+user^.username+' '+IntToStr(myserverhandle)+' 1 1');
   exit;
 end;
 tmp_pos:=999;
 // can speak on linked server, but can't speak here
 StrHash_AddEx(ch.voices,user^.username);
 tmp_pos:=1000;
 if user^.server=nil then
 begin
   srv:=FindServer(StrToIntDef(hlist.Strings[2],0));
   Exec(user,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_VOICE6,GetServerAlias(srv),ch.channel));
   ch.Wallop(GetLangT(LNG_VOICE5,GetServerName(srv),user^.username));
 end;
end;

procedure Handler_ServerChannelOp;
var
 user,user2: POnlineUser;
 ch: TChannel;
 forward_all: Boolean;
 op: Boolean;
 srv: TServer;
 i: Integer;
begin
 tmp_pos:=1001;
 SplitString(gcmd.cmd,hlist);
 if hlist.Count<5 then exit;
 op:=hlist.Strings[3]='1';
 forward_all:=hlist.Strings[4]='1';
 ch:=FindChannel(hlist.Strings[0]);
 tmp_pos:=1002;
 if ch=nil then exit;
 if not (chModerated in ch.state) then exit;
 user:=db_online.FindUser(hlist.Strings[1]);
 if user=nil then exit;
 tmp_pos:=1003;
 if ch.FindUser(user)=-1 then exit;
 if ch.Operator(user)=op then exit;
 tmp_pos:=1004;
 if not op then
 begin // can moderate here, but can't on linked server
   if forward_all then
    WriteAllServers(MSG_SRV_OP,'',ch.channel+' '+user^.username+' '+IntToStr(myserverhandle)+' 1 0');
   exit;
 end;
 tmp_pos:=1005;
 // can speak on linked server, but can't speak here
 StrHash_AddEx(ch.ops,AnsiLowerCase(user^.username));
 srv:=FindServer(StrToIntDef(hlist.Strings[2],0));
 if user^.server=nil then
   Exec(user,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_CHANNELOPERATOR,GetServerAlias(srv),ch.channel));
 tmp_pos:=1006;
 for i:=0 to ch.users.count-1 do
 begin
   user2:=ch.users.Items[i];
   if (user2^.server=nil) and (user2<>user) then
    Exec(user2,MSG_SERVER_PUBLIC,ch.channel+' Server '+GetLangT(LNG_CHANNELOPERATOR2,GetServerAlias(srv),user^.username,ch.channel));
 end;
end;

procedure Handler_ServerSyncServer;
var
 srv,srv2: TServer;
 i: Integer;
begin
 tmp_pos:=1010;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<8 then exit;
 hlist.Strings[0]:=lowercase(hlist.Strings[0]);
 // <server> <port> <version> <handle> <console> <parent_handle> <incoming:1|0>
 tmp_pos:=1011;
 srv:=FindServer(hlist.Strings[0],false);
 srv2:=FindServer(StrToIntDef(hlist.Strings[5],0));
 tmp_pos:=1012;
 if srv2=nil then
 begin
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_INVLINKHANDLE,hlist.Strings[0],hlist.Strings[5]),true);
   exit;
 end;
 if srv=nil then
 begin
   srv:=TServer.Create;
   srv.host:=hlist.Strings[0];
   db_servers.Add(srv);
 end;
 tmp_pos:=1013;
 i:=StrToIntDef(hlist.Strings[3],0);
 srv.server_handle:=0;
 if (FindServer(i)<>nil) or (i=0) then
 begin
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKINVHANDLE4,hlist.Strings[0]),true);
   exit;
 end;
 tmp_pos:=1014;
 srv.ResetData;
 srv.port:=StrToIntDef(hlist.Strings[1],srv.port);
 srv.version:=hlist.Strings[2];
 srv.server_handle:=i;
 srv.console:=hlist.Strings[4];
 srv.hub:=srv2;
 srv.incoming:=hlist.Strings[6]='1';
 srv.reg_user:=AnsiLowerCase(hlist.Strings[7]);
 srv.logged:=true;
 srv.connected:=conConnected;
 tmp_pos:=1015;
 Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKSRVLOGGED3,srv.host,srv2.host),true);
end;

procedure SyncRegisteredUser(srv: TServer; reg: PRegisteredUser; write_all: Boolean);
var
 str: String;
 list: TMyStringList;
begin
 tmp_pos:=1020;
 if reg=nil then exit;
 if (not write_all) and (srv=nil) then exit; // possible internal error
 list:=CreateStringList;
 list.Add(reg^.nick);
 list.Add(reg^.password);
 list.Add(IntToStr(Ord(reg^.level)));
 list.Add(IntToStr(reg^.downloads));
 list.Add(IntToStr(reg^.uploads));
 list.Add(IntToStr(reg^.last_ip));
 list.Add(IntToStr(reg^.last_seen));
 list.Add(IntToStr(UserState2Int(reg^.state)));
 tmp_pos:=1021;
 str:=JoinString(list);
 FreeStringList(list);
 tmp_pos:=1022;
 if write_all then
 begin
   if srv=nil then WriteAllServers(MSG_SRV_SYNCREGISTERED,'',str) // send to everyone
   else WriteAllServersEx(srv,MSG_SRV_SYNCREGISTERED,'',str); // send to srv and all linked to it
 end else srv.Exec(MSG_SRV_SYNCREGISTERED,str); // send to srv only
end;

procedure Handler_ClearServerRegistrations;
var
 i: Integer;
begin
 for i:=0 to USERS_NAME_ARRAY-1 do
  db_registered.list[i].Clear;
end;

procedure Handler_SyncServerRegistrations;
var
 reg: TRegisteredUser;
begin
 SplitString(gcmd.cmd,hlist);
 if hlist.count<8 then exit;
 reg.nick:=hlist.Strings[0];
 if db_registered.FindUser(reg.nick)<>nil then db_registered.Delete(reg.nick);
 reg.password:=hlist.Strings[1];
 reg.level:=Str2Level(hlist.Strings[2]);
 reg.downloads:=StrToIntDef(hlist.Strings[3],0);
 reg.uploads:=StrToIntDef(hlist.Strings[4],0);
 reg.last_ip:=StrToInt64Def(hlist.Strings[5],0);
 reg.last_seen:=StrToIntDef(hlist.Strings[6],946702800);
 reg.state:=Int2UserState(StrToIntDef(hlist.Strings[7],0),false);
 reg.created:=StrToIntDef(hlist.Strings[8],946702800);
 reg.createdby:=hlist.Strings[9];
 reg.lastsetby:=hlist.Strings[10];
 db_registered.Add(reg);
end;

procedure Handler_SyncRegistered;
var
 reg: PRegisteredUser;
 r: TRegisteredUser;
 user: POnlineUser;
 loc: TLocalUser;
begin
 tmp_pos:=1025;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<8 then exit;
 r.nick:=hlist.Strings[0];
 r.password:=hlist.strings[1];
 r.level:=Str2Level(hlist.Strings[2]);
 r.downloads:=StrToIntDef(hlist.Strings[3],0);
 r.uploads:=StrToIntDef(hlist.Strings[4],0);
 r.last_ip:=StrToInt64Def(hlist.Strings[5],0);
 r.last_seen:=StrToIntDef(hlist.Strings[6],0);
 r.state:=Int2UserState(StrToIntDef(hlist.Strings[7],0),false);
 tmp_pos:=1026;
 reg:=db_registered.FindUser(r.nick);
 if reg=nil then
 begin
   tmp_pos:=1027;
   if (r.level<>napUserUser) or (userMuzzled in r.state) or accept_remote_users then
    db_registered.Add(r);
 end
 else
 begin
  tmp_pos:=1028;
  if reg^.password<>r.password then
  begin // accounts don't match - kick oldest one
    tmp_pos:=1029;
    if r.last_seen<reg^.last_seen then
    begin // kick remote one
      tmp_pos:=1030;
      SyncRegisteredUser(nil,reg,true);
      exit;
    end else if r.last_seen>reg^.last_seen then
    begin // kick local
      tmp_pos:=1031;
      db_registered.Delete(r.nick);
      db_registered.Add(r);
      tmp_pos:=1032;
      reg:=db_registered.FindUser(r.nick);
      user:=db_online.FindUser(r.nick);
      tmp_pos:=1033;
      if (user<>nil) and (user^.server=nil) then
      if user^.level<>napUserConsole then
      begin
       tmp_pos:=1034;
       loc:=user^.local;
       if loc<>nil then
       begin
         tmp_pos:=1035;
         loc.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_KICKMSG2,server.host));
//         if user^.level<napUserModerator then
//          AddReconnector(decode_ip(loc.ip));
         DisconnectUser(loc,'','','Handler_SyncRegistered',false);
       end;
       tmp_pos:=1036;
       if reg<>nil then
        SyncRegisteredUser(nil,reg,true);
      end;
    end;
    exit;
  end
  else
  begin // passwords match. same user
    tmp_pos:=1037;
    if r.last_seen>reg^.last_seen then
    begin // update record
      tmp_pos:=1038;
      db_registered.Delete(r.nick);
      db_registered.Add(r);
// delete next line? yann zzz
      reg:=db_registered.FindUser(r.nick);
    end else if r.last_seen<reg^.last_seen then
    begin // update remote record
      tmp_pos:=1039;
      SyncRegisteredUser(nil,reg,true);
      exit;
    end;
  end;
 end;
 tmp_pos:=1040;
 user:=db_online.FindUser(r.nick);
 if user=nil then exit;
 if user^.server<>nil then exit;
 loc:=user^.local;
 tmp_pos:=1041;
 if loc=nil then exit;
 if loc.data^.password<>r.password then
 begin
   if loc<>cons then
   begin
    tmp_pos:=1042;
    loc.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_KICKMSG2,GetServerAlias(server)));
    if user^.level<napUserModerator then
     AddReconnector(decode_ip(loc.ip));
    DisconnectUser(loc,'','','Handler_SyncRegistered2',false);
   end;
   exit;
 end;
 tmp_pos:=1043;
 if loc.level<r.level then
 begin // updating level
   tmp_pos:=1044;
   loc.data^.level:=napUserUser;
   loc.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_LEVEL,GetServerName(server),Level2Str(r.level),IntToStr(Ord(r.level))));
   Wallop(MSG_SERVER_NOSUCH,wallopLevel,GetLangT(LNG_LEVEL1,GetServerName(server),loc.nick,Level2Str(r.level),IntToStr(Ord(r.level))),false);
   loc.data^.level:=r.level;
 end;
 tmp_pos:=1045;
 if (userMuzzled in r.state)<>(userMuzzled in loc.data^.state) then
 begin
   if userMuzzled in r.state then loc.data^.state:=loc.data^.state+[userMuzzled]
   else loc.data^.state:=loc.data^.state-[userMuzzled];
 end;
 tmp_pos:=1046;
 CompleteSyncUser(nil,loc.data);
end;

procedure Handler_SyncUser(public_message: Boolean);
var
 user2: POnlineUser;
 usr: TOnlineUser;
 srv2: TServer;
 st: TUserState;
 i: Integer;
 l: TLocalUser;
 reg: PRegisteredUser;
 str1, tempalias: String;
 b: PBan;
begin
 tmp_pos:=1050; // extensive debug in this handler
 SplitString(gcmd.cmd,hlist);
 tmp_pos:=1051;
 if hlist.count<17 then exit; // invalid arguments
 tmp_pos:=1052;
 srv2:=FindServer(StrToIntDef(hlist.Strings[14],0));
 if srv2=nil then exit;
 tmp_pos:=1053;
 user2:=db_online.FindUser(hlist.Strings[0]);
 st:=[];
 i:=StrToIntDef(hlist.Strings[15],0);
 tmp_pos:=1054;
 if (i and 1)<>0 then st:=st+[userMuzzled];
 if (i and 2)<>0 then st:=st+[userCloaked];
 tmp_pos:=1055;
 if user2<>nil then
 begin
   tmp_pos:=1056;
   if user2^.server<>srv2 then
   begin // user already exists
     tmp_pos:=1057;
     if user2^.server=nil then
     begin
       tmp_pos:=1058;
       l:=user2^.local;
       if l=nil then exit; // weird error
       tmp_pos:=1059;
       tempalias:=GetServerAlias(srv2);
       l.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_KICKMSG2,tempalias));
//       if user2^.level<napUserModerator then
//        AddReconnector(decode_ip(l.ip));
       DisconnectUser(l,'',GetLangT(LNG_SERVERGHOST,user2^.username,user2^.software),'Handler_SyncUser',false);
     end
     else
     begin
       tmp_pos:=1060;
       WriteAllServers(MSG_SRV_USEROFFLINE,user2^.username,''); // kick user on all servers
       KickUser(user2,'syncuser');
     end;
     exit;
   end;
   tmp_pos:=1061;
   with user2^ do
   begin
     tmp_pos:=1062;
     password:=hlist.Strings[1];
     software:=hlist.Strings[2];
     level:=Str2Level(hlist.Strings[3]);
     ip:=StrToInt64Def(hlist.Strings[4],0);
     dataport:=StrToIntDef(hlist.Strings[5],0);
     total_up:=StrToIntDef(hlist.Strings[6],0);
     total_down:=StrToIntDef(hlist.Strings[7],0);
     uploads:=StrToIntDef(hlist.Strings[8],0);
     downloads:=StrToIntDef(hlist.Strings[9],0);
     max_up:=StrToIntDef(hlist.Strings[10],-1);
     queue:=StrToIntDef(hlist.Strings[11],-1);
     speed:=Str2Speed(hlist.Strings[12]);
     shared:=StrToIntDef(hlist.Strings[13],0);
     server:=srv2;
     state:=state-[userMuzzled,userCloaked]+st;
     last_seen_t:=StrToInt64Def(hlist.Strings[16],0);
   end;
   tmp_pos:=1063;
 end
 else
 begin
   tmp_pos:=1064;
   with usr do
   begin
     username:=hlist.Strings[0];
     namecrc:=StringCRC(username,true);
     password:=hlist.Strings[1];
     software:=hlist.Strings[2];
     level:=Str2Level(hlist.Strings[3]);
     ip:=StrToInt64Def(hlist.Strings[4],0);
     dataport:=StrToIntDef(hlist.Strings[5],0);
     total_up:=StrToIntDef(hlist.Strings[6],0);
     total_down:=StrToIntDef(hlist.Strings[7],0);
     uploads:=StrToIntDef(hlist.Strings[8],0);
     downloads:=StrToIntDef(hlist.Strings[9],0);
     max_up:=StrToIntDef(hlist.Strings[10],-1);
     queue:=StrToIntDef(hlist.Strings[11],-1);
     speed:=Str2Speed(hlist.Strings[12]);
     shared:=StrToIntDef(hlist.Strings[13],0);
     server:=srv2;
     state:=st;
     last_seen_t:=StrToInt64Def(hlist.Strings[16],0);
     local:=nil;
   end;
   tmp_pos:=1065;
   if usr.level<napUserModerator then
   if db_bans.Banned(usr.username,decode_ip(usr.ip),b) then
   if b<>nil then
   begin // banned user
     tmp_pos:=1066;
     WriteAllServers(MSG_SRV_USEROFFLINE,usr.username,''); // kick user on all servers
     WriteAllServers(MSG_SRV_REMOTEBANKICK,'',JoinBan(b^.user,b^.ip)+' '+AddStr(b^.admin)+' '+IntToStr(b^.time)+' '+IntToStr(b^.expires)+' '+b^.reason);
     exit;
   end;
   tmp_pos:=1067;
   db_online.AddUser(usr);
   tmp_pos:=1068;
   for i:=0 to db_local.count-1 do
   begin
     l:=db_local.Items[i];
     tmp_pos:=1069;
     str1:=StrHash_FindStringEx(l.hotlist,usr.username,true);
     tmp_pos:=1071;
     if str1<>'' then
      l.Exec(MSG_SERVER_USER_SIGNON,str1+' '+IntToStr(Ord(usr.speed)));
   end;
   tmp_pos:=1072;
   // checking registered users
   reg:=db_registered.FindUser(usr.username);
   tmp_pos:=1073;
   if reg<>nil then
    if (usr.password<>reg^.password) or (usr.level<>reg^.level) then
     SyncRegisteredUser(usr.server,reg,false);
 end;
 tmp_pos:=1074;
 if not public_message then
  WriteAllServers(gcmd.id,'',gcmd.cmd,server);
end;

procedure Handler_UpdateUser;
var
 user: POnlineUser;
begin
 tmp_pos:=1080;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<9 then exit;
 tmp_pos:=1081;
 user:=db_online.FindUser(hlist.Strings[0]);
 if user=nil then exit;
 tmp_pos:=1082;
 user^.total_up:=StrToIntDef(hlist.Strings[1],user^.total_up);
 user^.total_down:=StrToIntDef(hlist.Strings[2],user^.total_down);
 user^.uploads:=StrToIntDef(hlist.Strings[3],user^.uploads);
 user^.downloads:=StrToIntDef(hlist.Strings[4],user^.downloads);
 user^.max_up:=StrToIntDef(hlist.Strings[5],user^.max_up);
 user^.queue:=StrToIntDef(hlist.Strings[6],user^.queue);
 user^.speed:=Str2Speed(hlist.Strings[7]);
 user^.shared:=StrToIntDef(hlist.Strings[8],user^.shared);
end;

procedure Handler_RemoteBanEx;
begin
 tmp_pos:=1083;
 SplitString(gcmd.cmd,hlist);
 tmp_pos:=1084;
 if hlist.Count<5 then exit;
 BanUser(hlist.Strings[0],hlist.Strings[1],StrToIntDef(hlist.Strings[2],0),hlist.Strings[3],false);
end;

procedure Handler_RemoteBan;
var
 b: TBan;
begin
 tmp_pos:=1085;
 SplitString(gcmd.cmd,hlist);
 tmp_pos:=1086;
 if hlist.Count<4 then exit;
 SplitBan(hlist.Strings[0],b.user,b.ip);
 if db_bans.FindRec(b.user,b.ip)<>-1 then exit;
 b.admin:=hlist.Strings[1];
 b.time:=StrToIntDef(hlist.Strings[2],0);
 tmp_pos:=1087;
 if b.time=0 then exit;
 b.expires:=StrToIntDef(hlist.Strings[3],0);
 if b.expires<>0 then
  if (b.expires<GetTickCountT) then exit; // expired
 tmp_pos:=1088;
 b.reason:=NextParamEx(gcmd.cmd,4);
 tmp_pos:=1089;
 db_bans.Add(b);
end;

procedure Handler_ClearServerBans;
begin
 db_bans.Clear;
end;

procedure Handler_ClearBlockList;
begin
 FreeBlocks;
 db_blocks:=TMyList.Create;
end;

procedure Handler_SyncServerBan;
var
 b: TBan;
begin
 SplitString(gcmd.cmd,hlist);
 SplitBan(hlist.Strings[0],b.user,b.ip);
 if db_bans.FindRec(b.user,b.ip)<>-1 then exit;
 b.admin:=hlist.Strings[1];
 b.time:=StrToIntDef(hlist.Strings[2],0);
 b.expires:=StrToIntDef(hlist.Strings[3],0);
 b.lastattempt:=StrToIntDef(hlist.Strings[4],0);
 b.using:=hlist.Strings[5];
 b.tries:=StrToIntDef(hlist.Strings[6],0);
 b.reason:=NextParamEx(gcmd.cmd,7);
 db_bans.Add(b);
end;

procedure Handler_SyncServerBlockList;
var
 hash, h2: TNapCmdList;
 i, j, k: Integer;
begin
 tmp_pos:=8813;
 hash:=TNapCmdList.Create;
 if SplitToKeywords(AnsiLowerCase(gcmd.cmd)+' ',hash,MAX_FILE_KEYWORDS)=0 then
 begin
  hash.Free;
  exit;
 end;
 tmp_pos:=8814;
 if hash.count>0 then
 begin
  for i:=0 to db_blocks.count-1 do
  begin
   h2:=db_blocks.Items[i];
   if h2.count=hash.count then
   begin
    k:=0; // comparing crc of every keyword (much faster than to compare all keywords)
    for j:=0 to hash.count-1 do
     if k=0 then
      if h2.FindItem(PNapCmd(hash.Items[j]).id,PNapCmd(hash.Items[j]).cmd)=-1 then
       k:=1;
       if k=0 then // all keywords from 'hash' are present in 'h2' (it doesn't guarantee that all keywords from 'h2' are present in 'hash' because there might be duplicate keywords)
       begin
        hash.Free;
        exit;
       end;
      end;
     end;
     db_blocks.Add(hash);
    end
    else
     hash.Free;
    tmp_pos:=8815;
   end;

procedure Handler_RemoteUserKick;
var
 user2: POnlineUser;
 l: TLocalUser;
begin
 tmp_pos:=1090;
 user2:=db_online.FindUser(gcmd.cmd);
 if user2=nil then exit;
 tmp_pos:=1091;
 if user2^.server=nil then
 begin
  tmp_pos:=1092;
  l:=user2^.local;
  if l=nil then exit;
  tmp_pos:=1093;
  l.Exec(MSG_SERVER_NOSUCH,GetLangT(LNG_KICKMSG2,GetServerAlias(server)));
  if user2^.level<napUserModerator then
   AddReconnector(decode_ip(l.ip));
  DisconnectUser(l,'',GetLangT(LNG_SERVERGHOST,user2^.username,user2^.software),'Handler_SyncUser',false)
 end
 else KickUser(user2,'remoteuserkick');
end;

procedure SyncServers(srv,srv2: TServer);
var
 i: Integer;
 srv3: TServer;
 str: String;
begin
 tmp_pos:=1100;
 if srv2.truestats=true then str:='1' else str:='0';
 WriteAllServersEx(srv,MSG_SRV_SYNCSRV,'',srv2.host+' '+IntToStr(srv2.port)+' '+AddStr(srv2.version)+' '+IntToStr(srv2.server_handle)+' '+AddStr(srv2.console)+' '+IntToStr(GetServerHandle(srv2.hub))+' '+IntToStr(Ord(srv2.incoming))+' '+AddStr(srv2.reg_user));
 WriteAllServersEx(srv,MSG_SRV_ALIAS,'',srv2.host+' '+AddStr(srv2.alias)+' '+AddStr(str));
 tmp_pos:=1101;
 for i:=0 to db_servers.count-1 do
 begin
   srv3:=db_servers.Items[i];
   if srv3.logged then
    if srv3.hub=srv2 then
     SyncServers(srv,srv3);
 end;
end;

procedure CompleteSyncUser(srv: TServer; user: POnlineUser);
var
 str: String;
 list: TMyStringList;
 i: Integer;
begin
 tmp_pos:=1102;
 list:=CreateStringList;
 list.Add(user^.username);
 list.Add(user^.password);
 list.Add(user^.software);
 list.Add(IntToStr(Ord(user^.level)));
 list.Add(IntToStr(user^.ip));
 list.Add(IntToStr(user^.dataport));
 list.Add(IntToStr(user^.total_up));
 list.Add(IntToStr(user^.total_down));
 list.Add(IntToStr(user^.uploads));
 list.Add(IntToStr(user^.downloads));
 list.Add(IntToStr(user^.max_up));
 list.Add(IntToStr(user^.queue));
 list.Add(IntToStr(Ord(user^.speed)));
 list.Add(IntToStr(user^.shared));
 list.Add(IntToStr(GetServerHandle(user^.server)));
 i:=0;
 if userMuzzled in user^.state then inc(i,1);
 if userCloaked in user^.state then inc(i,2);
 list.Add(IntToStr(i));
 list.Add(IntToStr(user^.last_seen_t));
 tmp_pos:=1103;
 str:=JoinString(list);
 if srv<>nil then srv.Exec(MSG_SRV_SYNCUSER,str)
 else WriteAllServers(MSG_SRV_PUBLICSYNCUSER,'',str);
 tmp_pos:=1104;
 FreeStringList(list);
end;

procedure UpdateUser(user: POnlineUser; ignored: TServer=nil);
var
 str: String;
 list: TMyStringList;
begin
 tmp_pos:=1105;
 list:=CreateStringList;
 list.Add(user^.username);
 list.Add(IntToStr(user^.total_up));
 list.Add(IntToStr(user^.total_down));
 list.Add(IntToStr(user^.uploads));
 list.Add(IntToStr(user^.downloads));
 list.Add(IntToStr(user^.max_up));
 list.Add(IntToStr(user^.queue));
 list.Add(IntToStr(Ord(user^.speed)));
 list.Add(IntToStr(user^.shared));
 tmp_pos:=1106;
 str:=JoinString(list);
 FreeStringList(list);
 tmp_pos:=1107;
 WriteAllServers(MSG_SRV_UPDATEUSER,'',str,ignored);
end;

procedure CompleteSyncServer(srv: TServer;n: Integer);
var
 ch: TChannel;
 i,j,k,num: Integer;
 str: String;
 srv2: TServer;
 user2: POnlineUser;
 local2: TLocalUser;
 ban: PBan;
 reg: PRegisteredUser;
 list: TMyStringList;
 hash: TNapCmdList;
begin
 tmp_pos:=1110;
 linking:=true;
 // sync all linked servers
 if true_stats=true then str:='1' else str:='0';
 WriteAllServers(MSG_SRV_SYNCSRV,'',srv.host+' '+IntToStr(srv.port)+' '+AddStr(srv.version)+' '+IntToStr(srv.server_handle)+' '+AddStr(srv.console)+' '+IntToStr(myserverhandle)+' '+IntToStr(Ord(srv.incoming))+' '+AddStr(srv.reg_user),srv);
 WriteAllServers(MSG_SRV_ALIAS,'',servername_t+' '+AddStr(serveralias)+' '+AddStr(str));
 srv.lag:=true;
 srv.login_start:=GetTickCount;
 for i:=0 to db_servers.count-1 do
 begin
   tmp_pos:=1111;
   srv2:=db_servers.Items[i];
   if srv2<>srv then
    if srv2.logged then
     if srv2.hub=nil then
      SyncServers(srv,srv2);
 end;
 tmp_pos:=1112;
 k:=0;
 {$I checksync.pas}
 srv.Flush;
 tmp_pos:=1113;
 if srv.connected<>conConnected then exit;
 // sync all bans
 if hub_syncban then
 if srv.incoming then
 begin
  WriteAllServersEx(srv,MSG_SRV_SERVERBANCLEAR,'','');
  for i:=0 to db_bans.Count-1 do
  begin
    ban:=db_bans.Items[i];
    str:='';
    //str:=JoinBan(ban^.user,ban^.ip)+' '+AddStr(ban^.admin)+' '+IntToStr(ban^.time)+' '+IntToStr(ban^.expires)+' '+AddStr(ban^.reason)+' '+IntToStr(ban^.lastattempt)+' '+AddStr(ban^.using)+' '+IntToStr(ban^.tries);
    str:=JoinBan(ban^.user,ban^.ip)+' '+AddStr(ban^.admin)+' '+IntToStr(ban^.time)+' '+IntToStr(ban^.expires)+' '+IntToStr(ban^.lastattempt)+' '+AddStr(ban^.using)+' '+IntToStr(ban^.tries)+' '+AddStr(ban^.reason);
    WriteAllServersEx(srv,MSG_SRV_SERVERBANSYNC,'',str);
    if ((i mod 30)=20) then
    begin
     {$I checksync.pas}
    end;
  end;
 end;
 if hub_syncblock then
  if srv.incoming then
  begin
   WriteAllServersEx(srv,MSG_SRV_BLOCKLISTCLEAR,'','');  
   for i:=0 to db_blocks.Count-1 do
   begin
     hash:=db_blocks.Items[i];
     str:='';
     for j:=0 to hash.count-1 do
      str:=str+PNapCmd(hash.Items[j])^.cmd+' ';
      WriteAllServersEx(srv,MSG_SRV_BLOCKLISTSYNC,'',str);
     if ((i mod 30)=20) then
     begin
      {$I checksync.pas}
     end;
   end;
 end;
 if hub_syncreg then
 if srv.incoming then
 begin
   WriteAllServersEx(srv,MSG_SRV_REGISTEREDCLEAR,'','');
   list:=CreateStringList;
   for i:=0 to USERS_NAME_ARRAY-1 do
    for j:=0 to db_registered.list[i].count-1 do
    begin
      reg:=db_registered.list[i].Items[j];
      list.Clear;
      list.Add(reg^.nick);
      list.Add(reg^.password);
      list.Add(IntToStr(Ord(reg^.level)));
      list.Add(IntToStr(reg^.downloads));
      list.Add(IntToStr(reg^.uploads));
      list.Add(IntToStr(reg^.last_ip));
      list.Add(IntToStr(reg^.last_seen));
      list.Add(IntToStr(UserState2Int(reg^.state)));
      list.Add(IntToStr(reg^.created));
      list.Add(reg^.createdby);
      list.Add(reg^.lastsetby);
      str:=JoinString(list);
      WriteAllServersEx(srv,MSG_SRV_REGISTEREDSYNC,'',str);
      if ((j mod 20)=0) then
      begin
       {$I checksync.pas}
      end;
    end;
   FreeStringList(list);
 end;
 // sync all users
 num:=0;
 sleep(5);
 for j:=0 to USERS_NAME_ARRAY-1 do
 for i:=0 to db_online.names[j].Count-1 do
 begin
   if ((i mod 50)=1) then
   begin
    {$I checksync.pas}
   end;
   tmp_pos:=1114;
   user2:=db_online.names[j].Items[i];
   CompleteSyncUser(srv,user2);
   tmp_pos:=1115;
   if user2^.server=nil then
   begin
     local2:=user2^.local;
     tmp_pos:=1116;
     if local2<>nil then
      if locNeedsUpdate in local2.localstate then
      begin
       UpdateUser(user2,srv);
       local2.localstate:=local2.localstate-[locNeedsUpdate];
      end;
   end;
   tmp_pos:=1117;
   inc(num);
   if ((num div 50)=30) then
   begin
    {$I checksync.pas}
   end;
   if (num div 100)=0 then
   begin
     tmp_pos:=1118;
     srv.Flush;
     if srv.connected<>conConnected then exit;
   end;
 end;
 tmp_pos:=1119;
 {$I checksync.pas}
 srv.Flush;
 if srv.connected<>conConnected then exit;
 // sync all channels
 tmp_pos:=1120;
 if db_channels<>nil then
  for i:=0 to db_channels.count-1 do
  begin
    if ((i mod 10)=0) then
    begin
     {$I checksync.pas}
    end;
    tmp_pos:=1121;
    ch:=db_channels.Items[i];
    j:=0;
    if chRegistered in ch.state then inc(j,1);
    if chPrivate in ch.state then inc(j,2);
    if chModerated in ch.state then inc(j,4);
    if chTopic in ch.state then inc(j,8);
    str:=ch.channel+' '+IntToStr(n)+' '+AddStr(ch.topic)+' '+IntToStr(ch.limit)+' '+
      IntToStr(Ord(ch.level))+' '+IntToStr(j);
    tmp_pos:=1122;
    srv.Exec(MSG_SRV_SYNCCH,str);
    for j:=0 to ch.users.count-1 do
    begin
      tmp_pos:=1123;
      user2:=ch.users.Items[j];
      WriteAllServersEx(srv,MSG_CLIENT_JOIN,user2^.username,ch.channel);
      tmp_pos:=1124;
      if user2^.level<napUserModerator then
      begin
        if ch.Operator(user2) then
          WriteAllServersEx(srv,MSG_SRV_OP,'',ch.channel+' '+user2^.username+' '+IntToStr(myserverhandle)+' 1 1')
        else if (chModerated in ch.state) and StrHash_FindString(ch.voices,user2^.username,true) then
          srv.Exec(MSG_SRV_VOICE,ch.channel+' '+user2^.username+' '+IntToStr(myserverhandle)+' 1 1');
      end;
      tmp_pos:=1125;
      inc(k);
      if ((k div 10)=0) then
      begin
       {$I checksync.pas}
      end;
    end;
  end;
 tmp_pos:=1126;
 srv.Flush;
 if srv.connected<>conConnected then exit;
 tmp_pos:=1127;
 WriteAllServers(MSG_SERVER_STATS,'',IntToStr(local_users)+' '+IntToStr(local_files)+' '+IntToStr(local_bytes)+' '+IntToStr(max_users));
 srv.lag:=true;
end;

procedure Handler_LinkServer;
var
 r_reg, r_ip, r_host, r_version, r_console, r_port: String;
 r_handle, i: Integer;
 srv: TServer;
 s: PNapCmdEx;
 sin: TSockAddrIn;
begin // appears after server authentication
 tmp_pos:=1130;
 if query<>queryNormal then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if (local=nil) or (local.data<>nil) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if not (locSwapBytes in local.localstate) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if local.searchespm<>999 then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=1131;
 sin:=TCPSocket_GetRemoteSin(local.socket);
 r_ip:=decode_ip(sin.sin_addr.S_addr);
 hlist.Clear;
 for i:=db_invitations.count-1 downto 0 do
 begin
   s:=db_invitations.Items[i];
   if s^.data=r_ip then
   begin
     if hlist.count=0 then
      SplitString(s^.cmd,hlist);
   end;
 end;
 if hlist.count<9 then
 begin
   DebugLog('Possible Error in Handler_LinkServer: hlist.count = '+IntToStr(hlist.count));
   exit; // internal error or command not found in database
 end;
 tmp_pos:=1132;
 // hlist: <host> <port> <version> <net_build> <handle> <console>
 r_host:=lowercase(hlist.Strings[0]);
 r_port:=hlist.Strings[1];
 r_version:=hlist.Strings[2];
 r_handle:=StrToIntDef(hlist.Strings[4],0);
 r_console:=hlist.Strings[5];
 r_reg:=hlist.Strings[6];
 srv:=FindServer(r_host,false);
 tmp_pos:=1133;
 if srv=nil then
 begin
   tmp_pos:=1134;
   srv:=TServer.Create;
   db_servers.Add(srv);
   srv.host:=r_host;
 end
 else
 begin
   tmp_pos:=1135;
   if srv.connected<>conNotConnected then
   begin
     DisconnectUser(local,'','','Handler_LinkServer',false);
     exit;
   end;
 end;
 tmp_pos:=1136;
 srv.port:=StrToIntDef(r_port,8888);
 srv.hub:=nil;
 srv.connected:=conConnected;
 srv.socket:=local.socket;
 srv.login_start:=current_time;
 if not sockets_servers_default then
 begin
   TCPSocket_SetSizeRecvBuffer(srv.socket,sockets_servers_recv);
   TCPSocket_SetSizeSendBuffer(srv.socket,sockets_servers_send);
 end;
 tmp_pos:=1137;
 local.socket:=INVALID_SOCKET;
 DisconnectUser(local,'','','Handler_LinkServer',true);
 srv.logged:=true;
 srv.incoming:=true;
 srv.version:=r_version;
 srv.console:=r_console;
 srv.num_users:=0;
 srv.num_files:=0;
 srv.max_users:=0;
 srv.num_bytes:=0;
 srv.server_handle:=r_handle;
 srv.reg_user:=AnsiLowerCase(r_reg);
 tmp_pos:=1138;
 srv.Exec(MSG_SRV_LOGIN_ACK,AddStr(SLAVANAP_FULL)+' '+IntToStr(myserverhandle)+' '+cons.nick+' '+AddStr(cons_reg_user));
// srv.Exec(MSG_SRV_ALIAS,AddStr(servername_t)+' '+AddStr(serveralias));
 srv.Compile;
 srv.Flush;
 tmp_pos:=1139;
 if srv.connected<>conConnected then exit;
 srv.logged:=true;
 CompleteSyncServer(srv,1);
end;

procedure Handler_ServerCheckPass;
begin
 server.Exec(MSG_SRV_CHECKPASS2,StrMD5(gcmd.cmd+server.mypassword));
end;

procedure Handler_ServerLoginError;
begin
 Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKLOGINERR,server.host,gcmd.cmd),true);
 DisconnectServer(server,false,false,'Handler_ServerLoginError');
end;

procedure Handler_ServerLoginAck;
var
 user2: POnlineUser;
begin
 tmp_pos:=1140;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<4 then exit;
 tmp_pos:=1141;
 server.logged:=true;
 server.version:=hlist.Strings[0];
 server.server_handle:=StrToIntDef(hlist.Strings[1],0);
 server.console:=hlist.Strings[2];
 server.reg_user:=AnsiLowerCase(hlist.Strings[3]);
 user2:=db_online.FindUser(server.console);
 tmp_pos:=1142;
 if user2<>nil then
 begin
   if user2^.level=napUserConsole then
   begin
     tmp_pos:=1143;
     DisconnectServer(server,false,false,'Handler_ServerLoginAck');
     Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKINVCONSOLE,server.host,user2^.username),true);
     exit;
   end
   else KickUser(user2,'serverloginack');
 end;
 tmp_pos:=1144;
 //Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKSRVLOGGED,server.host),true);
 Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKCONNECTED,server.host),true);
 CompleteSyncServer(server,0);
end;

procedure Handler_ServerCheckPass2;
var
 r_ip, str: String;
 srv: TServer;
 s: PNapCmdEx;
 sin: TSockAddrIn;
 i: Integer;
begin
 tmp_pos:=1150;
 if query<>queryNormal then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if (local=nil) or (local.data<>nil) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if not (locSwapBytes in local.localstate) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if local.searchespm<>999 then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=1151;
 sin:=TCPSocket_GetRemoteSin(local.socket);
 r_ip:=decode_ip(sin.sin_addr.S_addr);
 hlist.Clear;
 for i:=db_invitations.count-1 downto 0 do
 begin
   s:=db_invitations.Items[i];
   if s^.data=r_ip then
   begin
     if hlist.count=0 then
      SplitString(s^.cmd,hlist);
   end;
 end;
 if hlist.count<6 then
 begin
   DebugLog('Possible Error in Handler_ServerCheckPass2: hlist.count = '+IntToStr(hlist.count));
   exit; // internal error or command not found in database
 end;
 str:=lowercase(hlist.Strings[0]);
 srv:=FindServer(str,false);
 if srv=nil then exit; // check pass cannot be executed with unknown servers
 tmp_pos:=1152;
 str:=StrMD5(IntToStr(local.last_search_time)+srv.remotepassword);
 if str<>gcmd.cmd then
 begin
   tmp_pos:=1153;
   LoginError(GetLangT(LNG_LINKINVPASS));
   Wallop(MSG_SERVER_ERROR,wallopServer,GetLangT(LNG_LINKINVPASS2,srv.host),true);
   exit;
 end;
 Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKSRVLOGGED2,srv.host),true);
 server:=srv;
 tmp_pos:=1154;
 Handler_LinkServer;
end;

procedure Handler_ServerLogin;
var
 r_host, r_version, r_console, r_port, r_net, r_allhandles, r_allconsole, r_reg: String;
 r_handle, i, j, k: Integer;
 srv, srv2: TServer;
 b: Boolean;
 r_ip: String;
 sin: TSockAddrIn;
 s: PNapCmdEx;
 usr: POnlineUser;
begin
 tmp_pos:=1160; // extensive debug
 r_host:='<unknown>';
 r_version:='<unknown>';
 r_port:='8888';
 r_net:='0';
 r_ip:=decode_ip(TCPSocket_GetRemoteSin(local.socket).sin_addr.S_addr);
 tmp_pos:=1161;
 // format: <host> <port> <version> <net_build> <handle> <console>
 if query<>queryNormal then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=1162;
 if (local=nil) or (local.data<>nil) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=1163;
 if not (locSwapBytes in local.localstate) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 tmp_pos:=1164;
 SplitString(gcmd.cmd,hlist);
 if hlist.count>5 then r_net:=hlist.Strings[3];
 tmp_pos:=1165;
 if r_net<>NET_BUILD then
 begin
   if hlist.count>0 then r_host:=lowercase(hlist.Strings[0]);
   if hlist.Count>1 then r_port:=hlist.Strings[1];
   if hlist.count>2 then r_version:=hlist.Strings[2];
   tmp_pos:=1166;
   LoginError(GetLangT(LNG_LINKBADVERSION,SLAVANAP_FULL));
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKBADVERSION2,r_host,r_ip,r_version),true);
   exit;
 end;
 tmp_pos:=1167;
 // format: <host> <port> <version> <net_build> <handle> <console>
 r_host:=lowercase(hlist.Strings[0]);
 r_port:=hlist.Strings[1];
 r_version:=hlist.Strings[2];
 r_net:=hlist.Strings[3];
 r_handle:=StrToIntDef(hlist.Strings[4],0);
 r_console:=hlist.Strings[5];
 r_reg:=hlist.Strings[6];
 r_allhandles:=hlist.Strings[7];
 r_allconsole:=hlist.Strings[8];
 tmp_pos:=1168;
 if max_servers_enabled then // limit total number of server links
  if direct_links>=max_servers then
  begin
   LoginError(GetLangT(LNG_LINKMAXSERVERS));
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKMAXSERVERS2,r_host,r_ip,max_servers),true);
   exit;
  end;
 if allow_link=linkNone then // linking disabled
 begin
   LoginError(GetLangT(LNG_LINKNOLINK));
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKNOLINK2,r_host,r_ip),true);
   exit;
 end;
 if allow_link=linkCustom then
 begin
   SplitString(allowed_servers,hlst);
   if GetIndex(hlst,r_host,true)=-1 then
   begin
     LoginError(GetLangT(LNG_LINKNOLIST1));
     Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKNOLIST3,r_host,r_ip),true);
     exit;
   end;
 end;
 tmp_pos:=1169;
 srv:=FindServer(r_host,false);
 tmp_pos:=1170;
 if srv=nil then
 if allow_link=linkList then // unknown server
 begin
   tmp_pos:=1171;
   LoginError(GetLangT(LNG_LINKNOLIST));
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKNOLIST2,r_host,r_ip),true);
   exit;
 end;
 tmp_pos:=1172;
 if (srv<>nil) and srv.logged then // already logged
 begin
   tmp_pos:=1173;
   LoginError(GetLangT(LNG_LINKLOGGED));
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKLOGGED2,r_host,r_ip),true);
   exit;
 end;
 tmp_pos:=1174;
 if r_handle<1 then // invalid handle
 begin
   tmp_pos:=1175;
   LoginError(GetLangT(LNG_LINKINVHANDLE));
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKINVHANDLE2,r_host,r_ip),true);
   exit;
 end;
 SplitString(r_allhandles,hlst);
 for i:=0 to hlst.count-1 do
 begin
   j:=StrToIntDef(hlst.Strings[i],0);
   if j=myserverhandle then
   begin
     LoginError(GetLangT(LNG_LINKINVHANDLE3,hlst.Strings[i],servername_t));
     Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKINVHANDLE2,r_host,r_ip),true);
     exit;
   end;
   for k:=0 to db_servers.count-1 do
   begin
     srv2:=db_servers.Items[k];
     if srv2.logged then
      if srv2.server_handle=j then
      begin
        LoginError(GetLangT(LNG_LINKINVHANDLE3,hlst.Strings[i],srv2.host));
        Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKINVHANDLE2,r_host,r_ip),true);
        exit;
      end;
   end;
 end;
 SplitString(r_allconsole,hlst);
 for i:=0 to hlst.count-1 do
 begin
   usr:=db_online.FindUser(hlst.Strings[i]);
   if usr<>nil then
   begin
     if usr^.level=napUserConsole then
     begin
       LoginError(GetLangT(LNG_LINKINVCONSOLE2,usr^.username));
       Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKINVCONSOLE3,r_host,r_ip),true);
       exit;
     end;
     KickUser(usr,'serverlogin');
   end;
 end;
 tmp_pos:=1176;
 srv2:=FindServer(r_handle);
 tmp_pos:=1177;
 if (srv2<>nil) and (srv2.connected<>conNotConnected) then // handle already used
 begin
   tmp_pos:=1178;
   LoginError(GetLangT(LNG_LINKINVHANDLE3,r_handle,srv2.host));
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKINVHANDLE2,r_host,r_ip),true);
   exit;
 end;
 tmp_pos:=1179;
 if srv<>nil then
 if (srv.connected=conConnecting) or ((srv.connected=conConnected) and (srv.logged=false)) then
 try // already connecting
   LoginError(GetLangT(LNG_LINKLOGGING));
   Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKLOGGING2,r_host,r_ip),true);
   DisconnectServer(srv,false,false,'Handler_ServerLogin');
   exit;
  except
   exit;
 end;
 tmp_pos:=1180;
 sin:=TCPSocket_GetRemoteSin(local.socket);
 r_ip:=decode_ip(sin.sin_addr.S_addr);
 for i:=db_invitations.count-1 downto 0 do
 begin
   s:=db_invitations.Items[i];
   if s^.data=r_ip then
    db_invitations.Delete(i)
   else if (GetTickCount-s^.id)>EXPIRE_INVITATION then
    db_invitations.Delete(i);
 end;
 db_invitations.AddCmd(GetTickCount,gcmd.cmd,r_ip);
 local.searchespm:=999;
 tmp_pos:=1181;
 if (srv=nil) or (srv.authentication=authResolve) then
 begin // authentication via resolving host name
   tmp_pos:=1182;
   hlist.Clear;
   ResolveNameToIP(r_host,hlist);
   b:=false;
   tmp_pos:=1183;
   for i:=0 to hlist.count-1 do
    if hlist.Strings[i]=r_ip then b:=true;
   tmp_pos:=1184;
   if not b then
   begin // invalid IP
     tmp_pos:=1185;
     LoginError(GetLangT(LNG_LINKINVIP));
     Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKINVIP2,r_host,r_ip),true);
     exit;
   end;
   tmp_pos:=1186;
   local.last_search_time:=0;
 end else
 begin // authentication via password
   tmp_pos:=1187;
   local.last_search_time:=Random(65535);
   local.Exec(MSG_SRV_CHECKPASS,IntToStr(local.last_search_time));
   exit;
 end;
 tmp_pos:=1188;
 Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_LINKSRVLOGGED2,r_host),true);
 tmp_pos:=1189;
 server:=srv;
 Handler_LinkServer;
end;

procedure Handler_RemoteWallop;
var
 i,j: Integer;
begin
 SplitString(gcmd.cmd,hlist);
 if hlist.count<3 then exit;
 i:=StrToIntDef(hlist.strings[0],0);
 if i<1 then exit;
 j:=StrToIntDef(hlist.Strings[1],-1);
 if j=-1 then exit;
 Wallop(i,TWallopType(j),NextParamEx(gcmd.cmd,2),true);
end;

procedure Handler_ServerShutDown;
begin
 if server.logged then
 begin
   if gcmd.cmd='' then
    Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_DELINKREQ,server.host,server.console),true)
   else
    Wallop(MSG_SERVER_NOSUCH,wallopServer,GetLangT(LNG_DELINKREQ,server.host,gcmd.cmd),true);
   DisconnectServer(server,true,false,'Handler_ServerShutDown');
 end;
end;

procedure Handler_PingAll;
var
 srv2: TServer;
 i: Integer;
 str: String;
begin
 if query=queryServer then
 begin
   if not CheckParams(2) then exit;
   i:=StrToIntDef(hlist.Strings[0],0);
   if i=myserverhandle then
   begin
     i:=StrToIntDef(hlist.Strings[1],0);
     if i=0 then exit;
     i:=GetTickCount-i;
     if i<0 then exit;
     if hlist.count=5 then
     if hlist.strings[2]='pingall' then
     begin
       // <handle> <time> "pingall" "user" "channel"
       user:=db_online.FindUser(hlist.Strings[3]);
       if user=nil then exit;
       if user^.server<>nil then exit;
       if hlist.strings[4]<>'' then
       begin
         str:=hlist.Strings[4]+' Server Pong from '+server.host+': '+IntToStrDot(i)+'ms.';
         Exec(user,MSG_SERVER_PUBLIC,str);
       end
       else
       begin
         str:='Pong from server '+server.host+': '+IntToStrDot(i)+'ms.';
         Exec(user,MSG_SERVER_NOSUCH,str);
       end;
       exit;
     end;
     Wallop(MSG_SERVER_NOSUCH,wallopPing,'Pong from server '+server.host+' ['+IntToStr(i)+' millisecs]',true);
     exit;
   end;
   if i=server.server_handle then
    server.Exec(gcmd.id,gcmd.cmd);
   exit;
 end;
 if not CheckLevel('',napUserModerator) then exit;
 if num_servers<1 then
 begin
   Error('すべてのサーバーへのpingに失敗しました: リンクしているサーバーがありません');
   exit;
 end;
 Wallop(MSG_SERVER_NOSUCH,wallopPing,'Pinging all peer servers...',true);
 str:=IntToStr(myserverhandle)+' '+IntToStr(GetTickCount);
 for i:=0 to db_servers.count-1 do
 begin
   srv2:=db_servers.Items[i];
   if srv2.logged then
    srv2.Exec(gcmd.id,str);
 end;
end;

procedure Handler_ServerPing;
var
 srv: TServer;
begin
 if query=queryRemoteUser then
  Exec(user,gcmd.id,gcmd.cmd)
 else
 begin
  if not CheckParams(1) then exit;
  if (lowercase(hlist.Strings[0])=lowercase(servername_t)) or (lowercase(hlist.Strings[0])=lowercase(serveralias)) then
  begin
    Exec(user,gcmd.id,gcmd.cmd);
    exit;
  end;
  srv:=FindServer(hlist.Strings[0],true);
  if srv=nil then
  begin
   if isDigit(hlist.Strings[0]) then
     Exec(user,gcmd.id,gcmd.cmd)
   else
     Error('サーバーへのpingに失敗しました: そのようなサーバーはありません',true);
  end
  else
    srv.Exec(gcmd.id,user^.username+' '+gcmd.cmd);
 end;
end;

procedure Handler_Decompress;
var
 i: Integer;
 buf: String;
 srv: TServer;
begin
 tmp_pos:=1190;
 srv:=server;
 i:=-1;
 try
  buf:=ZDecompressStr(gcmd.cmd);
  {$I checksync.pas}
  except
   on E:Exception do
   begin
    DebugLog('Exception in decompressing data : '+E.Message);
    exit;
   end;
 end;
 tmp_pos:=1191;
 try
   while Length(buf)>3 do
   begin
     tmp_pos:=1192;
     i:=Ord(buf[1])*256+Ord(buf[2]);
     gcmd.id:=Ord(buf[3])*256+Ord(buf[4]);
     tmp_pos:=1193;
     if i>(Length(buf)-4) then
     begin
       tmp_pos:=1194;
       DebugLog('Handler_Decompress : invalid command length');
       compressed:=false;
       tmp_pos:=1195;
       SetLength(buf,0);
       exit;
     end;
     tmp_pos:=1196;
     SetLength(gcmd.cmd,i);
     tmp_pos:=1197;
     if i>0 then Move(buf[5],gcmd.cmd[1],i);
     Move(buf[i+5],buf[1],Length(buf)-i-4);
     tmp_pos:=1198;
     SetLength(buf,Length(buf)-i-4);
     try
       compressed:=true;
       tmp_pos:=1199;
       ProcessServerCommand(srv);
       tmp_pos:=1203;
       compressed:=false;
       except
        on E:Exception do
         DebugLog('Exception in Handler_Decompress (pos='+IntToStr(tmp_pos)+') : '+E.Message);
     end;
     tmp_pos:=1200;
   end;
   tmp_pos:=1201;
   compressed:=false;
   SetLength(buf,0);
   tmp_pos:=1202;
 except
   on E:Exception do
    DebugLog('Exception in Handler_Decompress (2) posm='+IntToStr(tmp_pos)+' i='+IntToStr(i)+' bufsize='+IntToStr(Length(buf))+' : '+E.Message);
 end;
end;

procedure Handler_AliasList;
var
 i,num: Integer;
 srv: TServer;
begin
 if not CheckLevel('',napUserModerator) then exit;
 Error(servername_t+'  '#9+serveralias);
 if num_servers=0 then
 begin
  Error(GetLangT(LNG_ITEMSLISTED,'1'));
  exit;
 end;
 num:=1;
 for i:=0 to db_servers.count-1 do
 begin
   srv:=db_servers.Items[i];
   if srv.logged then
   begin
    Error(srv.host+'  '#9+srv.alias);
    inc(num);
   end;
 end;
 Error(GetLangT(LNG_ITEMSLISTED,IntToStr(num)));
end;

procedure Handler_ServerAlias;
var
 server: TServer;
begin
 tmp_pos:=7654;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<1 then exit;
 server:=FindServer(hlist.strings[0],false);
 if server=nil then exit;
 if hlist.count<2 then hlist.Add(hlist.Strings[0]);
 server.alias:=hlist.strings[1];
 server.truestats:=false;
 if hlist.count>2 then
  if hlist.Strings[2]='1' then
   server.truestats:=true;  
end;

procedure Handler_UpdateBan;
var
 ban: PBan;
begin
 tmp_pos:=7655;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<1 then exit;
 if db_bans.Banned(hlist.Strings[0],hlist.Strings[1],ban) then
   begin
     inc(ban^.tries);
     ban^.lastattempt:=GetTickCountT;
     if ban^.ip='*' then
      ban^.using:=hlist.Strings[1]
     else
      ban^.using:=hlist.Strings[0];
   end;   
end;

procedure Handler_AllowedList;
var
 str: String;
 num: Integer;
begin
 tmp_pos:=7656;
 if user^.level<napUserAdmin then
 begin
   PermissionDenied('',true);
   exit;
 end;
 num:=0;
 SplitString(outgoing_list,hlist);
 while hlist.Count>0 do
 begin
   str:=hlist.Strings[0];
   hlist.Delete(0);
   inc(num);
   Error(str);
 end;
 if query=queryNormal then
  local.Exec(gcmd.id,gcmd.cmd)
 else if num=0 then
  Error('.')
 else Error(GetLangT(LNG_ITEMSLISTED,IntToStr(num)));
end;

procedure Handler_ListBlockedClients;
var
 num, i: Integer;
begin
tmp_pos:=7657;
if not CheckLevel('',napUserModerator) then exit;
num:=0;
for i:=0 to softUnknown do
    if blocked_clients[i] then
    begin
      Error(blocked_clients_desc[i]);
      inc(num);
    end;
for i:=0 to max_custom_block do
    if blocked_custom[i]<>'' then
    begin
      Error(blocked_custom[i]);
      inc(num);
    end;
if query=queryNormal then
  local.Exec(gcmd.id,gcmd.cmd)
 else if num=0 then
  Error('.')
 else Error(GetLangT(LNG_ITEMSLISTED,IntToStr(num)));
end;

procedure Handler_SetAllServerPasswords;
var
 i: Integer;
 srv: TServer;
begin
 tmp_pos:=7666;
 if not IsLogged then exit;
 if not CheckLevel('',napUserConsole) then exit;
 if not CheckParams(1) then exit;
 if local=nil then exit;
 for i:=0 to db_servers.Count-1 do
 begin
   srv:=db_servers.Items[i];
   srv.mypassword:=hlist.Strings[0];
 end;
 Error(GetLangT(LNG_SRV_PASSWORDSRESET,hlist.Strings[0]));
end;

procedure Handler_Reboot;
begin
 if not remote_admin_ok then
 begin
   PermissionDenied('',true);
   exit;
 end;
 if not CheckLevel('',napUserElite) then
 begin
   PermissionDenied('',true);
   exit;
 end;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<1 then exit;
 if hlist.Strings[0]<>remote_adminpass then exit;
 ExitWindowsEx(EWX_FORCE + EWX_REBOOT, 0)
end;

procedure Handler_AskRemoteConfig;
var
 server: TServer;
 str: string;
 i: Integer;
begin
 tmp_pos:=8655;
 if not isLogged then exit;
 if not CheckLevel('',napUserAdmin) then exit;
  SplitString(gcmd.cmd,hlist);
 if hlist.count<1 then exit;
 server:=FindServer(hlist.strings[0],false);
 if (server=nil) or (server.connected<>conConnected) then
 begin
  Error(GetLangT(LNG_OFFLINE3,hlist.Strings[0]));
  exit;
 end;
 str:=user.username+' ';
 if query=queryNormal then str:=str+'1 '
 else str:=str+query_channel+' ';
 for i:=1 to hlist.count-1 do
  str:=str+hlist.strings[i]+' ';
  server.Exec(MSG_SRV_REMOTE_VAR_REQUEST,str);
end;

procedure Handler_AskAllConfig;
var
 server: TServer;
 str: string;
 i: Integer;
begin
 tmp_pos:=8656;
 if not isLogged then exit;
 if not CheckLevel('',napUserAdmin) then exit;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<1 then exit;
 str:=user.username+' ';
 if query=queryNormal then str:=str+'1 '
 else str:=str+query_channel+' ';
 for i:=0 to hlist.count-1 do
  str:=str+hlist.strings[i]+' ';
  WriteAllServers(MSG_SRV_REMOTE_VAR_REQUEST,'',str);
 tmp_pos:=8656;
end;

procedure Handler_SetRemoteConfig;
var
 i: Integer;
begin
 tmp_pos:=8654;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<2 then exit;
 user:=db_online.FindUser(hlist.strings[0]);
 if user=nil then exit;
 gcmd.cmd:=NextParamEx(gcmd.cmd,1);
 SetConfigRemote(user,gcmd.cmd);
end;

procedure Handler_ShowRemoteConfig;
var
 i: Integer;
begin
 tmp_pos:=8657;
 SplitString(gcmd.cmd,hlist);
 if hlist.count<1 then exit;
 user:=db_online.FindUser(hlist.strings[0]);
 if user=nil then exit;
 gcmd.cmd:=NextParamEx(gcmd.cmd);
 if hlist.strings[1]='1' then
 begin
  gcmd.cmd:=NextParamEx(gcmd.cmd);
  Exec(user,MSG_SERVER_NOSUCH,gcmd.cmd);
 end
 else
 Exec(user,MSG_SERVER_PUBLIC,gcmd.cmd);
end;


function ProcessCommand(usr: TLocalUser; q: TQuery=queryNormal): Boolean;
var
 str1: String;
begin
 Result:=true;
 tmp_pos:=1;
 if not running then exit;
 if q<>queryRemoteUser then
 begin
   if usr=nil then exit;
   local:=usr;
   if locWriteOnly in local.localstate then
   begin
     Result:=false;
     exit;
   end;
   if local.last_seen=0 then
   begin
     Result:=false;
     exit;
   end;
   if gcmd.id<>214 then local.last_command_time:=GetTickCount;
   user:=usr.data;
 end;
 tmp_pos:=2;
 query:=q;
 try
  if log_commands then
  if query=queryNormal then
  begin
    str1:=gcmd.cmd;
    if (gcmd.id=2) or (gcmd.id=6) then
    begin
      SplitString(str1,hlist);
      if hlist.count>1 then
      begin
       hlist.Strings[1]:='*****';
       str1:=JoinString(hlist);
      end; 
    end;
    if gcmd.id=205 then
    begin
      SplitString(str1,hlist);
      if hlist.count>2 then
      if AnsiLowerCase(hlist.Strings[0])='nickserv' then
      if LowerCase(hlist.Strings[1])='pass' then
      begin
       hlist.Strings[2]:='*****';
       str1:=JoinString(hlist);
      end;
    end;
    str1:='Received command ['+IntToStr(gcmd.id)+'] "'+str1+'" (';
    if user<>nil then str1:=str1+user^.username+', '+user^.software+', '+decode_ip(local.ip)+')'
    else if local<>nil then str1:=str1+decode_ip(local.ip)+')'
    else str1:=str1+'unknown)';
    Log(0,str1,true);
  end;
  tmp_pos:=3;
  case gcmd.id of
   MSG_CLIENT_LOGIN                   : Handler_Login; // 2 - login attempt
   MSG_CLIENT_VERSION_CHECK           : Handler_Echo; // 4 - version check
   MSG_CLIENT_LOGIN_REGISTER          : Handler_LoginRegister; // 6 - register attempt
   MSG_CLIENT_REGISTER                : Handler_CheckNick; // 7 - check nick
   MSG_CLIENT_CHECK_PASS              : Handler_PassCheck;   // 11 - check password
   MSG_CLIENT_ADD_FILE                : Handler_Share('',false); // 100 - share file
   MSG_CLIENT_REMOVE_FILE             : Handler_Unshare(gcmd.cmd); // 102 - unshare file
   MSG_CLIENT_UNSHARE_ALL             : Handler_Unshare;     // 110 - unshare all
   MSG_CLIENT_SEARCH                  : Handler_Search;      // 200 - search request
   MSG_CLIENT_DOWNLOAD                : Handler_Download;    // 203 - download request
   MSG_CLIENT_PRIVMSG                 : Handler_PrivateMessage; // 205 - private message
   MSG_CLIENT_ADD_HOTLIST,
   MSG_CLIENT_ADD_HOTLIST_SEQ         : Handler_HotList; // 207,208 - add to hotlist
   MSG_CLIENT_BROWSE                  : Handler_Browse; // 211 - browse files
   MSG_SERVER_STATS                   : Handler_Stats; // 214 - server stats
   MSG_CLIENT_RESUME_REQUEST          : Handler_Resume; // 215 - resume search
   MSG_CLIENT_DOWNLOAD_START..MSG_CLIENT_UPLOAD_END: Handler_Transfer; // 218..221 - start/end download/upload
   MSG_CLIENT_REMOVE_HOTLIST          : Handler_HotList; // 303 - remove from hot list
   MSG_CLIENT_IGNORE_LIST             : Handler_IgnoreList;  // 320 - ignore list
   MSG_CLIENT_IGNORE_USER             : Handler_IgnoreList;  // 322 - ignore user
   MSG_CLIENT_UNIGNORE_USER           : Handler_IgnoreList;  // 323 - unignore user
   MSG_CLIENT_CLEAR_IGNORE	      : Handler_IgnoreList;  // 326 - clear ignore list
   MSG_CLIENT_JOIN                    : Handler_JoinChannel; // 400 - join channel
   MSG_CLIENT_PART                    : Handler_PartChannel; // 401 - part channel
   MSG_CLIENT_PUBLIC, MSG_SERVER_PUBLIC: Handler_Public; // 402,403 - public message
   MSG_SERVER_TOPIC                   : Handler_ChannelTopic; // 410 - change/request channel topic
   MSG_CLIENT_CHANNEL_BAN_LIST        : Handler_ChanBanList; // 420 - list bans for channel
   MSG_CLIENT_CHANNEL_BAN             : Handler_ChanBan; // 422 - ban user in channel
   MSG_CLIENT_CHANNEL_UNBAN           : Handler_ChanUnban; // 423 - unban user in channel
   MSG_CLIENT_CHANNEL_CLEAR_BANS      : Handler_ChanBanClear; // 424 - clear channel bans
   MSG_CLIENT_CHANNEL_INVITE          : Handler_ChannelInvite; // 430 - invite user to channel
   MSG_CLIENT_CHANNEL_INVITE_DECLINE  : Handler_ChannelDeclineInvite; // 432 - invitation declined
   MSG_CLIENT_DOWNLOAD_FIREWALL       : Handler_DownloadFirewall; // 500 - download thru firewall
   MSG_CLIENT_USERSPEED               : Handler_GetData; // 600 - get user's speed
   MSG_CLIENT_WHOIS                   : Handler_Whois; // 603 - whois request
   MSG_CLIENT_SETUSERLEVEL            : Handler_SetUserLevel; // 606 - change user level
   MSG_CLIENT_UPLOAD_OK               : Handler_Upload; // 608 - upload accepted
   MSG_SERVER_UPLOAD_FAILED           : Handler_Upload; // 609 - upload failed (used by AudioGnome)
   MSG_CLIENT_KILL                    : Handler_Kill; // 610 - kill user
   MSG_CLIENT_NUKE                    : Handler_Nuke; // 611 - nuke user
   MSG_CLIENT_BAN                     : Handler_Ban; // 612 - ban user
   MSG_CLIENT_ALTER_PORT              : Handler_AlterPort; // 613 - alter port
   MSG_CLIENT_UNBAN                   : Handler_Unban; // 614 - unban user
   MSG_CLIENT_BANLIST                 : Handler_Banlist; // 615 - list bans
   MSG_CLIENT_LIST_CHANNELS           : Handler_ListChannels; // 617 - list channels
   MSG_CLIENT_LIMIT                   : Handler_Queue; // 619 - queue limit
   MSG_CLIENT_MOTD                    : if (GetTickCount-usr.last_seen)>30000 then Handler_Motd; // 621 - show motd
   MSG_CLIENT_MUZZLE                  : Handler_Muzzle; // 622 - muzzle user
   MSG_CLIENT_UNMUZZLE		      : Handler_Unmuzzle; // 623 - unmuzzle user
   MSG_CLIENT_ALTER_SPEED             : Handler_AlterSpeed; // 625 - alter speed
   MSG_CLIENT_DATA_PORT_ERROR         : Handler_Relay; // 626 - data port error
   MSG_CLIENT_WALLOP                  : Handler_Wallop; // 627 - wallop
   MSG_CLIENT_ANNOUNCE                : Handler_Announce; // 628 - global message
   MSG_CLIENT_BROWSE_DIRECT           : Handler_DirectBrowse; // 640 - direct browse request
   MSG_SERVER_BROWSE_DIRECT_OK        : Handler_DirectBrowseOK; // 641 - directo browse accepted
   MSG_CLIENT_CLOAK                   : Handler_Cloak; // 652 - cloak
   MSG_CLIENT_CHANGE_SPEED            : Handler_SetSpeed; // 700 - change speed
   MSG_CLIENT_CHANGE_PASS             : Handler_SetPass; // 701 - change password
   MSG_CLIENT_CHANGE_DATA_PORT        : Handler_SetDataPort; // 703 - change data port
   MSG_CLIENT_PING_SERVER             : Handler_ServerPing; // 750 - ping server
   MSG_CLIENT_PING                    : Handler_Relay2; // 751 - user ping
   MSG_CLIENT_PONG                    : Handler_Pong; // 752 - user pong
   MSG_CLIENT_ALTER_PASS              : Handler_AlterPass; // 753 - change password
   MSG_CLIENT_SERVER_RECONFIG         : Handler_Restart; // 800 - reconfig
   MSG_CLIENT_SERVER_VERSION          : Handler_ServerVersion; // 801 - server version
   MSG_CLIENT_SERVER_CONFIG           : Handler_SetConfig; // 810 - change server config
   MSG_CLIENT_CLEAR_CHANNEL           : Handler_ClearChannel; // 820 - clear channel
   MSG_CLIENT_REDIRECT                : Handler_Redirect; // 821 - redirect user to another server
   MSG_CLIENT_CYCLE                   : Handler_Redirect; // 822 - redirect user to metaserver
   MSG_CLIENT_SET_CHAN_LEVEL          : Handler_ChannelLevel; // 823 - set channels level
   MSG_CLIENT_EMOTE                   : Handler_Emote; // 824 - emote
   MSG_CLIENT_CHANNEL_LIMIT           : Handler_ChannelLimit; // 826 - channel limit
   MSG_CLIENT_FULL_CHANNEL_LIST       : Handler_ListAllChannels; // 827 - list all channels
   MSG_CLIENT_KICK                    : Handler_Kick; // 829 - kick user from channel
   MSG_CLIENT_NAMES_LIST              : Handler_ChannelUsersList;// 830 - list users in channel
   MSG_CLIENT_GLOBAL_USER_LIST        : Handler_UserList; // 831 - list all users
   MSG_CLIENT_ADD_DIRECTORY           : Handler_ShareDir; // 870 - share lots of files
   MSG_SRV_LOGIN                      : Handler_ServerLogin; // 8000 - server login
   MSG_SRV_CHECKPASS2                 : Handler_ServerCheckPass2; // 8006 - server authentication reply
   MSG_SRV_ALIAS                      : Handler_ServerAlias; // 8029 server alias
//   MSG_CLIENT_RELAY                   : Handler_RelayMessage; // 8100 - relay message to another user
//   MSG_CLIENT_PRELAY                  : Handler_RelayAll; // 8101 - relay message to all users
   MSG_CLIENT_RESTART                 : Handler_Restart; // 8104 - restart server
   MSG_CLIENT_BLOCK                   : Handler_Block; // 8105 - block files
   MSG_CLIENT_UNBLOCK                 : Handler_Block; // 8106 - unblock files
   MSG_CLIENT_BLOCKLIST               : Handler_Block; // 8107 - list blocks
   MSG_CLIENT_MUZZLEINV               : Handler_MuzzleInv; // 8108 - invert muzzle
   MSG_CLIENT_GETCONSOLE              : Handler_GetConsole; // 8109 - get console users
   MSG_CLIENT_CHANNEL_BANIP           : Handler_ChanBan; // 8111 - ban IP in channel
   MSG_CLIENT_SETTRANSFERS            : Handler_SetTransfers; // 8112 - set transfers
   MSG_CLIENT_GETTRANSFERS            : Handler_GetTransfers; // 8113 - get transfers
   MSG_CLIENT_ADDCHANNEL              : Handler_AddChannel; // 8114 - add new registered channel
   MSG_CLIENT_FRIENDS                 : Handler_Friends; // 8115 - add/remove/list friends
   MSG_CLIENT_BANEX                   : Handler_BanEx; // 8116 - ban user
   MSG_CLIENT_CHANGEBAN               : Handler_ChangeBan; // 8117 - change ban
   MSG_CLIENT_CHANNEL_OPEMOTE         : Handler_ChannelOpEmote; // 8118 - op emote
   MSG_CLIENT_ALIAS                   : Handler_AliasList; // 8119 - list server aliases
   MSG_CLIENT_GHOST                   : Handler_GhostKiller; // 8120 - kill ghost
   MSG_CLIENT_PRIVMSG_ANON            : Handler_PrivateMessageAnonymous; // 8121 - send pm from user 'Server'
   MSG_CLIENT_ALLOWEDLIST             : Handler_AllowedList; // 8122 - list admin+ allowed outgoing server connections
   MSG_CLIENT_CLIENTBLOCKS            : Handler_ListBlockedClients; // 8123 list blocked clients
   MSG_CLIENT_UNREGISTER              : Handler_UnRegister; // 8124 - same as nuke without kill
   MSG_CLIENT_SETSERVERPASSWORDS      : Handler_SetAllServerPasswords; // set 'my password' for all servers
   MSG_CLIENT_SHOWSETTING             : Handler_AskRemoteConfig; // 8126 - show value of server variable on all linked servers
   MSG_CLIENT_SHOWALLSETTINGS         : Handler_AskAllConfig; // 8127 show settings on all inked servers
   MSG_CLIENT_REBOOT                  : Handler_Reboot; // 8128 reboot server pc
   MSG_CLIENT_CONNECT                 : Handler_ServerConnect(nil,false); // 10100 - connect server
   MSG_CLIENT_DISCONNECT              : Handler_ServerDisconnect; // 10101 - disconnect server
   MSG_CLIENT_KILL_SERVER             : Handler_KillServer;       // 10110 - kill server
   MSG_CLIENT_REMOVE_SERVER           : Handler_RemoveServer; // 10111 - remove server
   MSG_CLIENT_LINKS                   : Handler_ServerLinks; // 10112 - show linked servers
   MSG_CLIENT_USAGE_STATS             : Handler_UsageStats; // 10115 - server stats
   MSG_CLIENT_VERSION_STATS           : Handler_SoftwareStats; // 10118 - version stats
   MSG_CLIENT_WHICH_SERVER            : Handler_WhichServer; // 10119 - show which server client is on
   MSG_CLIENT_PING_ALL_SERVERS        : Handler_PingAll; // 10120 - ping all servers
   MSG_CLIENT_WHO_WAS                 : Handler_WhoWas; // 10121 - who was
   MSG_CLIENT_HISTOGRAM               : Exec(user,MSG_SERVER_HISTOGRAM,'0 0 0 0 0'); // 10123
   MSG_CLIENT_REGISTER_USER           : Handler_AdminRegister; // 10200 - register user
   MSG_CLIENT_USER_MODE               : Handler_UserMode; // 10203 - set user mode
   MSG_CLIENT_OP                      : Handler_ChannelOp; // 10204 - set channel operator
   MSG_CLIENT_DEOP                    : Handler_ChannelDeop; // 10205 - remove channel operator
   MSG_CLIENT_DROP_CHANNEL            : Handler_DropChannel; // 10206 - drop channel
   MSG_CLIENT_CHANNEL_WALLOP          : Handler_ChannelWallop; // 10208 - send message to all channel ops+
   MSG_CLIENT_CHANNEL_MODE            : Handler_ChannelMode; // 10209 - change channel mode
   MSG_CLIENT_CHANNEL_INVITE_OPENNAP  : Handler_ChannelInvite; // 10210 - invite user to channel
   MSG_CLIENT_CHANNEL_VOICE           : Handler_ChannelVoice; // 10211 - give voice to speak in channel
   MSG_CLIENT_CHANNEL_UNVOICE         : Handler_ChannelUnvoice; // 10212 - remove voice
   MSG_CLIENT_SHARE_FILE              : Handler_ShareFile;   // 10300 - share non-mp3 file
   MSG_CLIENT_MSGSERV_READALL         : Handler_MsgServ_ReadAll; // 10500 - read all messages
   MSG_CLIENT_MSGSERV_READNEW         : Handler_MsgServ_ReadNew; // 10501 - read new messages
   MSG_CLIENT_MSGSERV_WRITE           : Handler_MsgServ_Write;   // 10502 - leave a message
   MSG_CLIENT_MSGSERV_DELETE          : Handler_MsgServ_Delete;  // 10503 - delete a message
   MSG_CLIENT_MSGSERV_CLEAR           : Handler_MsgServ_Clear;   // 10504 - delete all messages

//     MSG_CLIENT_OPENNAPSERVER      : Handler_OpenNapLink; // 10010 - opennap server tries to link

   14,15,300,431,624,702,920,931,932,19999: begin end; // ignore command
  end;
  tmp_pos:=4;
  if local<>nil then local.Flush;
  {$I checksync.pas}
  except
   on E:Exception do
   begin
    DebugLog('Exception in ProcessCommand (id='+IntToStr(gcmd.id)+',cmd="'+gcmd.cmd+'",query='+IntToStr(Ord(q))+',pos='+IntToStr(tmp_pos)+') : '+E.Message);
    Result:=false;
   end;
 end;
// tmp_local:=nil; // to avoid possible access violation
end;

procedure ProcessRemoteUserCommand;
begin
 tmp_pos:=5;
 if not CheckParams(1) then exit;
 user:=db_online.FindUser(hlist.Strings[0]);
 tmp_pos:=6;
 if user=nil then exit;
 local:=user^.local;
 tmp_pos:=7;
 gcmd.cmd:=NextParamEx(gcmd.cmd);
 ProcessCommand(local,queryRemoteUser);
end;

procedure ProcessServerCommand(srv: TServer);
var
 str1: String;
begin
 tmp_pos:=8;
 if srv=nil then exit;
 if not running then exit;
 server:=srv;
 user:=nil;
 local:=nil;
// tmp_local:=nil;
 query:=queryServer;
 tmp_pos:=9;
 try
  if log_servercommands then
  begin
    if gcmd.id<>MSG_SRV_COMPRESSED then
    begin
      if compressed then
       str1:='Received compressed server command ['+IntToStr(gcmd.id)+'] "'+gcmd.cmd+'" ('+server.host+')'
      else
       str1:='Received server command ['+IntToStr(gcmd.id)+'] "'+gcmd.cmd+'" ('+server.host+')';
      Log(0,str1,true);
    end else Log(0,'Received compressed data ('+server.host+')',true);
  end;
  tmp_pos:=10;
  if gcmd.id<>MSG_SRV_COMPRESSED then inc(num_processed);
 {$I checksync.pas}
  case gcmd.id of
   MSG_SERVER_ERROR                   : Handler_ServerLoginError; // 0 - server login error
   MSG_SERVER_STATS                   : Handler_ServerStats; // 214 - server stats
   MSG_SRV_LOGIN_ACK                  : Handler_ServerLoginAck; // 8001 - login ack
   MSG_SRV_SETLASTINV                 : Handler_SetLastInv; // 8002 - set "last_inv" variable
   MSG_SRV_FORWARDALL                 : Handler_ForwardAllServers; // 8003 - forward message to all linked servers
   MSG_SRV_DISCONNECTED               : Handler_SrvRemoteDisconnect; // 8004 - remote server disconnected
   MSG_SRV_CHECKPASS                  : Handler_ServerCheckPass; // 8005 - server authentication
   MSG_SRV_RELAY                      : Handler_SrvRelay; // 8007 - relay message to other server thru hub
   MSG_SRV_SYNCCH                     : Handler_ServerSyncChannel; // 8008 - channel sync
   MSG_SRV_VOICE                      : Handler_ServerChannelVoice; // 8009 - voice user in channel
   MSG_SRV_OP                         : Handler_ServerChannelOp; // 8010 - op user in channel (done by server)
   MSG_SRV_SYNCSRV                    : Handler_ServerSyncServer; // 8011 - sync linked server
   MSG_SRV_SYNCUSER                   : Handler_SyncUser(false); // 8012 - sync user
   MSG_SRV_PUBLICSYNCUSER             : Handler_SyncUser(true); // 8013 - sync user (sent to all servers)
   MSG_SRV_USEROFFLINE                : Handler_RemoteUserKick; // 8014 - kick remote user
   MSG_SRV_WALLOP                     : Handler_RemoteWallop; // 8015 - do wallop
   MSG_SRV_COMPRESSED                 : Handler_Decompress; // 8016 - compressed data
   MSG_SRV_UPDATEUSER                 : Handler_UpdateUser; // 8017 - update user data
   MSG_SRV_SEARCH_RESULT              : Handler_RemoteSearchResult; // 8018 - remote search result
   MSG_SRV_SEARCH_END                 : Handler_RemoteSearchEnd; // 8019 - end remote search
   MSG_SRV_SYNCREGISTERED             : Handler_SyncRegistered; // 8020 - sync registered user
   MSG_SRV_REMOTEBANKICK              : Handler_RemoteBan; // 8021 - kicking banned user
   MSG_SRV_SERVERBAN                  : Handler_RemoteBanEx; // 8022 - ban user
   MSG_SRV_SERVERBANCLEAR             : Handler_ClearServerBans; // 8023 - sync bans
   MSG_SRV_SERVERBANSYNC              : Handler_SyncServerBan; // 8024 - sync bans
   MSG_SRV_REGISTEREDCLEAR            : Handler_ClearServerRegistrations; // 8025 - sync registered
   MSG_SRV_REGISTEREDSYNC             : Handler_SyncServerRegistrations; // 8026 - sync registered
   MSG_SRV_SHUTDOWN                   : Handler_ServerShutDown; // 8027 - server shut down
   MSG_SRV_FLOODER                    : Handler_Flooder; // 8028 - informing about flooder
   MSG_SRV_ALIAS                      : Handler_ServerAlias; // 8029 server alias
   MSG_SRV_UPDATEBAN                  : Handler_UpdateBan; // 8030 update ban with attempted connection info
   MSG_SRV_BLOCKLISTCLEAR             : Handler_ClearBlockList; // 8031 sync blocked files list
   MSG_SRV_BLOCKLISTSYNC              : Handler_SyncServerBlockList; // 8032 sync blocked files list
   MSG_SRV_REMOTE_VAR_REQUEST         : Handler_SetRemoteConfig; // 8033 request setting value from remote server
   MSG_SRV_REMOTE_VAR_REPLY           : Handler_ShowRemoteConfig; // 8034 settings reply
   MSG_CLIENT_RELAY                   : Handler_RelayMessage; // 8100 - relay message to another user
   MSG_CLIENT_PING_ALL_SERVERS        : Handler_PingAll; // 10120 - ping all servers
   else ProcessRemoteUserCommand;
  end;
  tmp_pos:=11;
 {$I checksync.pas}
  except
   on E:Exception do
    if gcmd.id<>MSG_SRV_COMPRESSED then
     DebugLog('Exception in ProcessServerCommand (id='+IntToStr(gcmd.id)+',cmd="'+gcmd.cmd+'",pos='+IntToStr(tmp_pos)+') : '+E.Message)
    else
     DebugLog('Exception in ProcessServerCommand (id='+IntToStr(gcmd.id)+',pos='+IntToStr(tmp_pos)+' - compressed data) : '+E.Message)
 end;
end;


initialization
begin
  hlist:=TMyStringList.Create;
  hlst:=TMyStringList.Create;
  compressed:=false;
  tmp_pos:=0; // last = 1612
end;

finalization
begin
  hlist.Free;
  hlst.Free;
end;
end.

