]> jfr.im git - irc/quakenet/newserv.git/blobdiff - control/control.c
merge
[irc/quakenet/newserv.git] / control / control.c
index 8c61628c90a528ab2fe6136ad8e964a87201b377..f117483937e19c555c64696ffa1fb9b77a0877c6 100644 (file)
 #include "../core/schedule.h"
 #include "../lib/base64.h"
 #include "../core/modules.h"
+#include "../lib/version.h"
 #include "control.h"
 
 #include <stdio.h>
 #include <string.h>
 #include <stdarg.h>
 
-nick *mynick;
+MODULE_VERSION("");
+
+nick *hooknick;
 
-nick *statsnick;
+nick *mynick;
 
-CommandTree *controlcmds; 
+CommandTree *controlcmds;
+ControlMsg controlreply;
+ControlWall controlwall;
 
 void handlemessages(nick *target, int messagetype, void **args);
 int controlstatus(void *sender, int cargc, char **cargv);
@@ -39,30 +44,57 @@ int controlchannel(void *sender, int cargc, char **cargv);
 int relink(void *sender, int cargc, char **cargv);
 int die(void *sender, int cargc, char **cargv);
 int controlinsmod(void *sender, int cargc, char **cargv);
-int controlrmmod(void *sender, int cargc, char **cargv);
+int controllsmod(void *sender, int cargc, char **cargv);
 int controlrehash(void *sender, int cargc, char **cargv);
-int controlshowcommands(void *sender, int cargc, char **cargv);
 int controlreload(void *sender, int cargc, char **cargv);
+int controlhelpcmd(void *sender, int cargc, char **cargv);
+void controlnoticeopers(flag_t permissionlevel, flag_t noticelevel, char *format, ...);
 
 void _init() {
   controlcmds=newcommandtree();
-    
-  addcommandtotree(controlcmds,"status",10,1,&controlstatus);
-  addcommandtotree(controlcmds,"whois",10,1,&controlwhois);
-  addcommandtotree(controlcmds,"channel",10,1,&controlchannel);
-  addcommandtotree(controlcmds,"relink",10,1,&relink);
-  addcommandtotree(controlcmds,"die",10,1,&die);
-  addcommandtotree(controlcmds,"insmod",10,1,&controlinsmod);
-  addcommandtotree(controlcmds,"rmmod",10,1,&controlrmmod);
-  addcommandtotree(controlcmds,"rehash",10,1,&controlrehash);
-  addcommandtotree(controlcmds,"showcommands",10,0,&controlshowcommands);
-  addcommandtotree(controlcmds,"reload",10,1,&controlreload);
+  controlreply=&controlmessage;
+  controlwall=&controlnoticeopers;
+
+  registercontrolhelpcmd("status",NO_DEVELOPER,1,&controlstatus,"Usage: status ?level?\nDisplays status information, increasing level gives more verbose information.");
+  registercontrolhelpcmd("whois",NO_OPERED,1,&controlwhois,"Usage: whois <nickname|#numeric>\nDisplays lots of information about the specified nickname or numeric.");
+  registercontrolhelpcmd("channel",NO_OPER,1,&controlchannel,"Usage: channel <#channel>\nDisplays channel information.");
+  registercontrolhelpcmd("relink",NO_DEVELOPER,1,&relink,"Usage: relink\nRelinks service to the network.");
+  registercontrolhelpcmd("die",NO_DEVELOPER,1,&die,"Usage: die <reason>\nTerminates the service.");
+  registercontrolhelpcmd("insmod",NO_DEVELOPER,1,&controlinsmod,"Usage: insmod <module>\nAdds a module to the running instance.");
+  registercontrolhelpcmd("rmmod",NO_DEVELOPER,1,&controlrmmod,"Usage: rmmod <module>\nRemoves a module from the running instance.");
+  registercontrolhelpcmd("lsmod",NO_DEVELOPER,0,&controllsmod,"Usage: lsmod\nLists currently running modules.");
+  registercontrolhelpcmd("rehash",NO_DEVELOPER,1,&controlrehash,"Usage: rehash\nReloads configuration file.");
+  registercontrolhelpcmd("showcommands",NO_ACCOUNT,0,&controlshowcommands,"Usage: showcommands\nShows all registered commands.");
+  registercontrolhelpcmd("reload",NO_DEVELOPER,1,&controlreload,"Usage: reload <module>\nReloads specified module.");
+  registercontrolhelpcmd("help",NO_ANYONE,1,&controlhelpcmd,"Usage: help <command>\nShows help for specified command.");
   
   scheduleoneshot(time(NULL)+1,&controlconnect,NULL);
 }
 
-void registercontrolcmd(const char *name, int level, int maxparams, CommandHandler handler) {
-  addcommandtotree(controlcmds,name,level,maxparams,handler);
+void _fini() {
+  deleteallschedules(&controlconnect);
+  if (mynick) {
+    deregisterlocaluser(mynick,"Leaving");
+  }
+  
+  deregistercontrolcmd("status",&controlstatus);
+  deregistercontrolcmd("whois",&controlwhois);
+  deregistercontrolcmd("channel",&controlchannel);
+  deregistercontrolcmd("relink",&relink);
+  deregistercontrolcmd("die",&die);
+  deregistercontrolcmd("insmod",&controlinsmod);
+  deregistercontrolcmd("rmmod",&controlrmmod);
+  deregistercontrolcmd("lsmod",&controllsmod);
+  deregistercontrolcmd("rehash",&controlrehash);
+  deregistercontrolcmd("showcommands",&controlshowcommands);
+  deregistercontrolcmd("reload",&controlreload);
+  deregistercontrolcmd("help",&controlhelpcmd);
+  
+  destroycommandtree(controlcmds);
+}
+
+void registercontrolhelpcmd(const char *name, int level, int maxparams, CommandHandler handler, char *help) {
+  addcommandhelptotree(controlcmds,name,level,maxparams,handler,help);
 }
 
 int deregistercontrolcmd(const char *name, CommandHandler handler) {
@@ -79,7 +111,8 @@ void controlconnect(void *arg) {
   myrealname=getcopyconfigitem("control","realname","NewServ Control Service",REALLEN);
   myauthname=getcopyconfigitem("control","authname","C",ACCOUNTLEN);
 
-  mynick=registerlocaluser(cnick->content,myident->content,myhost->content,myrealname->content,myauthname->content,UMODE_SERVICE|UMODE_DEAF|UMODE_OPER|UMODE_ACCOUNT,&handlemessages);
+  mynick=registerlocaluser(cnick->content,myident->content,myhost->content,myrealname->content,myauthname->content,UMODE_SERVICE|UMODE_DEAF|UMODE_OPER|UMODE_ACCOUNT|UMODE_INV,&handlemessages);
+  triggerhook(HOOK_CONTROL_REGISTERED, mynick);
   cp=findchannel("#twilightzone");
   if (!cp) {
     localcreatechannel(mynick,"#twilightzone");
@@ -96,19 +129,19 @@ void controlconnect(void *arg) {
 }
 
 void handlestats(int hooknum, void *arg) {
-  sendmessagetouser(mynick,statsnick,"%s",(char *)arg);
+  controlreply(hooknick,"%s",(char *)arg);
 }
 
 int controlstatus(void *sender, int cargc, char **cargv) {
-  unsigned int level=5;
-
-  statsnick=(nick *)sender;
+  unsigned long level=5;
+  hooknick=(nick *)sender;
   
   if (cargc>0) {
     level=strtoul(cargv[0],NULL,10);
   }
-  
+
   registerhook(HOOK_CORE_STATSREPLY,&handlestats);
+
   triggerhook(HOOK_CORE_STATSREQUEST,(void *)level);
   deregisterhook(HOOK_CORE_STATSREPLY,&handlestats);
   return CMD_OK;
@@ -123,55 +156,67 @@ int controlrehash(void *sender, int cargc, char **cargv) {
   triggerhook(HOOK_CORE_REHASH,(void *)0);
   return CMD_OK;
 }
-  
+
+void handlewhois(int hooknum, void *arg) {
+  controlreply(hooknick,"%s",(char *)arg);
+}
+
 int controlwhois(void *sender, int cargc, char **cargv) {
   nick *target;
   channel **channels;
   char buf[BUFSIZE];
   int i;
   
-  if (cargc<1) {
-    sendmessagetouser(mynick,(nick *)sender,"Usage: whois <user>");
-    return CMD_ERROR;
-  }
+  if (cargc<1)
+    return CMD_USAGE;
   
   if (cargv[0][0]=='#') {
     if (!(target=getnickbynumericstr(cargv[0]+1))) {
-      sendmessagetouser(mynick,sender,"Sorry, couldn't find numeric %s",cargv[0]+1);
+      controlreply(sender,"Sorry, couldn't find numeric %s",cargv[0]+1);
       return CMD_ERROR;
     }
   } else {
     if ((target=getnickbynick(cargv[0]))==NULL) {
-      sendmessagetouser(mynick,(nick *)sender,"Sorry, couldn't find that user");
+      controlreply((nick *)sender,"Sorry, couldn't find that user");
       return CMD_ERROR;
     }
   }
   
-  sendmessagetouser(mynick,(nick *)sender,"Nick      : %s",target->nick);
-  sendmessagetouser(mynick,(nick *)sender,"Numeric   : %s",longtonumeric(target->numeric,5));
-  sendmessagetouser(mynick,(nick *)sender,"User@Host : %s@%s (%d user(s) on this host)",target->ident,target->host->name->content,target->host->clonecount);
+  controlreply((nick *)sender,"Nick      : %s",target->nick);
+  controlreply((nick *)sender,"Numeric   : %s",longtonumeric(target->numeric,5));
+  controlreply((nick *)sender,"User@Host : %s@%s (%d user(s) on this host)",target->ident,target->host->name->content,target->host->clonecount);
   if (IsSetHost(target)) {
     if (target->shident) {
-      sendmessagetouser(mynick,(nick *)sender,"Fakehost  : %s@%s",target->shident->content, target->sethost->content);
+      controlreply((nick *)sender,"Fakehost  : %s@%s",target->shident->content, target->sethost->content);
     } else {
-      sendmessagetouser(mynick,(nick *)sender,"Fakehost  : %s",target->sethost->content);
+      controlreply((nick *)sender,"Fakehost  : %s",target->sethost->content);
     }
   }
-  sendmessagetouser(mynick,(nick *)sender,"Timestamp : %lu",target->timestamp);
-  sendmessagetouser(mynick,(nick *)sender,"IP address: %d.%d.%d.%d",(target->ipaddress)>>24,(target->ipaddress>>16)&255, (target->ipaddress>>8)&255,target->ipaddress&255);
-  sendmessagetouser(mynick,(nick *)sender,"Realname  : %s (%d user(s) have this realname)",target->realname->name->content,target->realname->usercount);
+  controlreply((nick *)sender,"Timestamp : %lu",target->timestamp);
+  controlreply((nick *)sender,"IP address: %s",IPtostr(target->p_ipaddr));
+  controlreply((nick *)sender,"Realname  : %s (%d user(s) have this realname)",target->realname->name->content,target->realname->usercount);
   if (target->umodes) {
-    sendmessagetouser(mynick,(nick *)sender,"Umode(s)  : %s",printflags(target->umodes,umodeflags));
+    controlreply((nick *)sender,"Umode(s)  : %s",printflags(target->umodes,umodeflags));
   }
   if (IsAccount(target)) {
-    sendmessagetouser(mynick,(nick *)sender,"Account   : %s",target->authname);
+    controlreply((nick *)sender,"Account   : %s",target->authname);
     if (target->accountts) 
-      sendmessagetouser(mynick,(nick *)sender,"AccountTS : %ld",target->accountts);
+      controlreply((nick *)sender,"AccountTS : %ld",target->accountts);
+    if (target->auth) 
+      controlreply((nick *)sender,"UserID    : %ld",target->auth->userid);
+    if (target->accountflags) 
+      controlreply((nick *)sender,"AccFlags  : %s",printflags(target->accountflags,accountflags));
   }
+
+  hooknick=(nick *)sender;
+  registerhook(HOOK_CONTROL_WHOISREPLY,&handlewhois);
+  triggerhook(HOOK_CONTROL_WHOISREQUEST,target);
+  deregisterhook(HOOK_CONTROL_WHOISREPLY,&handlewhois);
+
   if (target->channels->cursi==0) {
-    sendmessagetouser(mynick,(nick *)sender,"Channels  : none");
+    controlreply((nick *)sender,"Channels  : none");
   } else if (target->channels->cursi>50) {
-    sendmessagetouser(mynick,(nick *)sender,"Channels  : - (total: %d)",target->channels->cursi);
+    controlreply((nick *)sender,"Channels  : - (total: %d)",target->channels->cursi);
   } else {
     buf[0]='\0';
     channels=(channel **)target->channels->content;
@@ -183,7 +228,7 @@ int controlwhois(void *sender, int cargc, char **cargv) {
         if (strlen(buf)==0) {
           break;
         } else {
-          sendmessagetouser(mynick,(nick *)sender,"Channels  : %s",buf);
+          controlreply((nick *)sender,"Channels  : %s",buf);
           buf[0]='\0';
           i--;
         }
@@ -195,65 +240,81 @@ int controlwhois(void *sender, int cargc, char **cargv) {
 }
 
 int controlinsmod(void *sender, int cargc, char **cargv) {
-  if (cargc<1) {
-    sendmessagetouser(mynick,(nick *)sender,"Usage: insmod <modulename>");
-    return CMD_ERROR;
-  }
+  if (cargc<1)
+    return CMD_USAGE;
   
   switch(insmod(cargv[0])) {
     case -1:
-      sendmessagetouser(mynick,(nick *)sender,"Unable to load module %s",cargv[0]);
+      controlreply((nick *)sender,"Unable to load module %s",cargv[0]);
       return CMD_ERROR;
       
     case 1:
-      sendmessagetouser(mynick,(nick *)sender,"Module %s already loaded, or name not valid",cargv[0]);
+      controlreply((nick *)sender,"Module %s already loaded, or name not valid",cargv[0]);
       return CMD_ERROR;
       
     case 0:
-      sendmessagetouser(mynick,(nick *)sender,"Module %s loaded.",cargv[0]);
+      controlreply((nick *)sender,"Module %s loaded.",cargv[0]);
       return CMD_OK;
     
     default:
-      sendmessagetouser(mynick,(nick *)sender,"An unknown error occured.");
+      controlreply((nick *)sender,"An unknown error occured.");
       return CMD_ERROR;
   }
 }
 
 int controlrmmod(void *sender, int cargc, char **cargv) {
-  if (cargc<1) {
-    sendmessagetouser(mynick,(nick *)sender,"Usage: insmod <modulename>");
-    return CMD_ERROR;
-  }
+  if (cargc<1)
+    return CMD_USAGE;
   
   switch(rmmod(cargv[0])) {
     case 1:
-      sendmessagetouser(mynick,(nick *)sender,"Module %s is not loaded.",cargv[0]);
+      controlreply((nick *)sender,"Module %s is not loaded.",cargv[0]);
       return CMD_ERROR;
       
     case 0:
-      sendmessagetouser(mynick,(nick *)sender,"Module %s unloaded.",cargv[0]);
+      controlreply((nick *)sender,"Module %s unloaded.",cargv[0]);
       return CMD_OK;
     
     default:
-      sendmessagetouser(mynick,(nick *)sender,"An unknown error occured.");
+      controlreply((nick *)sender,"An unknown error occured.");
       return CMD_ERROR;
   }
 }
 
-int controlreload(void *sender, int cargc, char **cargv) {
-  if (cargc<1) {
-    sendmessagetouser(mynick,(nick *)sender,"Usage: reload <modulename>");
-    return CMD_ERROR;
+int controllsmod(void *sender, int cargc, char **cargv) {
+  int i=0;
+  char *ptr;
+
+  if (cargc < 1) { /* list all loaded modules */
+    ptr = lsmod(i);
+    controlreply((nick *)sender,"Module");
+    while (ptr != NULL) {
+      const char *ver = lsmodver(i);
+      controlreply((nick *)sender,"%s%s%s%s", ptr, ver?" (":"", ver?ver:"", ver?")":"");
+      ptr = lsmod(++i);
+    }
+  } else {
+    ptr = lsmod(getindex(cargv[0]));
+    controlreply((nick *)sender,"Module \"%s\" %s", cargv[0], (ptr ? "is loaded." : "is NOT loaded."));
   }
-    
-  controlrmmod(sender, cargc, cargv);
-  return controlinsmod(sender, cargc, cargv);
+  return CMD_OK;
 }
 
+int controlreload(void *sender, int cargc, char **cargv) {
+  if (cargc<1)
+    return CMD_USAGE;
+
+  controlreply((nick *)sender,"Imma gonna try and reload %s",cargv[0]);
+  
+  safereload(cargv[0]);
+  
+  return CMD_OK;
+}  
+
 int relink(void *sender, int cargc, char **cargv) {
   if (cargc<1) {
-    sendmessagetouser(mynick,(nick *)sender,"You must give a reason.");
-    return CMD_ERROR;
+    controlreply((nick *)sender,"You must give a reason.");
+    return CMD_USAGE;
   }
   
   irc_send("%s SQ %s 0 :%s",mynumeric->content,myserver->content,cargv[0]);
@@ -264,14 +325,15 @@ int relink(void *sender, int cargc, char **cargv) {
 
 int die(void *sender, int cargc, char **cargv) {
   if (cargc<1) {
-    sendmessagetouser(mynick,(nick *)sender,"You must give a reason.");
-    return CMD_ERROR;
+    controlreply((nick *)sender,"You must give a reason.");
+    return CMD_USAGE;
   }
+
+  controlwall(NO_OPER,NL_OPERATIONS,"DIE from %s: %s",((nick *)sender)->nick, cargv[0]);
   
-  irc_send("%s SQ %s 0 :%s",mynumeric->content,myserver->content,cargv[0]);
-  irc_disconnected();
+  newserv_shutdown_pending=1;
   
-  exit(0);
+  return CMD_OK;
 }
 
 int controlchannel(void *sender, int cargc, char **cargv) {
@@ -280,15 +342,14 @@ int controlchannel(void *sender, int cargc, char **cargv) {
   chanban *cbp;
   char buf[BUFSIZE];
   char buf2[12];
-  int i,j;
-  
-  if (cargc<1) {
-    sendmessagetouser(mynick,(nick *)sender,"Usage: channel #chan");
-    return CMD_ERROR;
-  }
+  int i,j, ops=0, voice=0;
+  char timebuf[30];
+
+  if (cargc<1)
+    return CMD_USAGE;
   
   if ((cp=findchannel(cargv[0]))==NULL) {
-    sendmessagetouser(mynick,(nick *)sender,"Couldn't find channel: %s",cargv[0]);
+    controlreply((nick *)sender,"Couldn't find channel: %s",cargv[0]);
     return CMD_ERROR;
   }
   
@@ -296,14 +357,19 @@ int controlchannel(void *sender, int cargc, char **cargv) {
     sprintf(buf2,"%d",cp->limit);
   }
   
-  sendmessagetouser(mynick,(nick *)sender,"Channel : %s",cp->index->name->content);
+  controlreply((nick *)sender,"Channel : %s",cp->index->name->content);
+  strftime(timebuf, 30, "%d/%m/%y %H:%M", localtime(&(cp->timestamp)));
+  controlreply((nick *)sender,"C-time  : %ld [%s]",cp->timestamp,timebuf);
   if (cp->topic) {
-    sendmessagetouser(mynick,(nick *)sender,"Topic   : %s",cp->topic->content);
-    sendmessagetouser(mynick,(nick *)sender,"T-time  : %ld [%s\]",cp->topictime,ctime(&(cp->topictime)));
+    controlreply((nick *)sender,"Topic   : %s",cp->topic->content);
+    strftime(timebuf, 30, "%d/%m/%y %H:%M", localtime(&(cp->topictime)));
+    controlreply((nick *)sender,"T-time  : %ld [%s]",cp->topictime,timebuf);
+  } else { 
+    controlreply((nick *)sender,"Topic   : (none)");
   }
-  sendmessagetouser(mynick,(nick *)sender,"Mode(s) : %s %s%s%s",printflags(cp->flags,cmodeflags),IsLimit(cp)?buf2:"",
+  controlreply((nick *)sender,"Mode(s) : %s %s%s%s",printflags(cp->flags,cmodeflags),IsLimit(cp)?buf2:"",
     IsLimit(cp)?" ":"",IsKey(cp)?cp->key->content:"");
-  sendmessagetouser(mynick,(nick *)sender,"Users   : %d (hash size %d, utilisation %.1f%%); %d unique hosts",
+  controlreply((nick *)sender,"Users   : %d (hash size %d, utilisation %.1f%%); %d unique hosts",
     cp->users->totalusers,cp->users->hashsize,((float)(100*cp->users->totalusers)/cp->users->hashsize),
     countuniquehosts(cp));
   i=0;
@@ -312,7 +378,7 @@ int controlchannel(void *sender, int cargc, char **cargv) {
   for (j=0;j<=cp->users->hashsize;j++) {
     if (i==4 || j==cp->users->hashsize) {
       if(i>0) {
-        sendmessagetouser(mynick,(nick *)sender,"Users   : %s",buf);
+        controlreply((nick *)sender,"Users   : %s",buf);
       }
       i=0;
       memset(buf,' ',72);
@@ -320,6 +386,10 @@ int controlchannel(void *sender, int cargc, char **cargv) {
         break;
     }
     if (cp->users->content[j]!=nouser) {      
+      if (cp->users->content[j]&CUMODE_VOICE)
+        voice++;
+      else if (cp->users->content[j]&CUMODE_OP) 
+        ops++;
       np=getnickbynumeric(cp->users->content[j]);
       sprintf(&buf[i*18],"%c%c%-15s ",cp->users->content[j]&CUMODE_VOICE?'+':' ',
         cp->users->content[j]&CUMODE_OP?'@':' ', np?np->nick:"!BUG-NONICK!");
@@ -328,9 +398,9 @@ int controlchannel(void *sender, int cargc, char **cargv) {
         buf[i*18]=' ';
     }
   }
-    
+  controlreply((nick *)sender, "Users   : Opped: %d, Voiced: %d", ops,voice);
   for (cbp=cp->bans;cbp;cbp=cbp->next) {
-    sendmessagetouser(mynick,(nick *)sender,"Ban     : %s",bantostringdebug(cbp));
+    controlreply((nick *)sender,"Ban     : %s",bantostringdebug(cbp));
   }
   return CMD_OK;
 }
@@ -345,7 +415,7 @@ int controlshowcommands(void *sender, int cargc, char **cargv) {
   controlreply(np,"The following commands are registered at present:");
   
   for(i=0;i<n;i++) {
-    controlreply(np,"%s (level %d)",cmdlist[i]->command->content,cmdlist[i]->level);
+    controlreply(np,"%s",cmdlist[i]->command->content);
   }
 
   controlreply(np,"End of list.");
@@ -376,12 +446,12 @@ void handlemessages(nick *target, int messagetype, void **args) {
                
       cmd=findcommandintree(controlcmds,cargv[0],1);
       if (cmd==NULL) {
-        sendmessagetouser(mynick,sender,"Unknown command.");
+        controlreply(sender,"Unknown command.");
         return;
       }
       
-      if (cmd->level>=10 && !IsOper(sender)) {
-        sendmessagetouser(mynick,sender,"You need to be opered to use this command.");
+      if (cmd->level>0 && !IsOper(sender)) {
+        controlreply(sender,"You need to be opered to use this command.");
         return;
       }
       
@@ -394,7 +464,8 @@ void handlemessages(nick *target, int messagetype, void **args) {
         cargc=(cmd->maxparams)+1;
       }
       
-      (cmd->handler)((void *)sender,cargc-1,&(cargv[1]));
+      if((cmd->handler)((void *)sender,cargc-1,&(cargv[1])) == CMD_USAGE)
+        controlhelp(sender, cmd);
       break;
       
     case LU_KILLED:
@@ -408,7 +479,7 @@ void handlemessages(nick *target, int messagetype, void **args) {
   }
 }
 
-void controlreply(nick *target, char *message, ... ) {
+void controlmessage(nick *target, char *message, ... ) {
   char buf[512];
   va_list va;
     
@@ -453,3 +524,95 @@ void controlnotice(nick *target, char *message, ... ) {
   sendnoticetouser(mynick,target,"%s",buf);
 }
 
+void controlspecialrmmod(void *arg) {
+  struct specialsched *a = (struct specialsched *)arg;
+  
+  a->schedule = NULL;
+  sstring *froo = a->modulename;
+
+  rmmod(froo->content);
+  freesstring(froo);
+}
+
+void controlspecialreloadmod(void *arg) {
+  struct specialsched *a = (struct specialsched *)arg;
+
+  a->schedule = NULL;
+  sstring *froo = a->modulename;
+
+  safereload(froo->content);
+  freesstring(froo);
+}
+
+void controlhelp(nick *np, Command *cmd) {
+  char *cp = cmd->help, *sp = cp;
+  if(!cp || !*cp) {
+    controlreply(np, "Sorry, no help for this command.");
+  } else {
+    int finished = 0;
+    for(;;cp++) {
+      if(*cp == '\0' || *cp == '\n') {
+        if(*cp == '\0') {
+          finished = 1;
+        } else {
+          *cp = '\0';
+        }
+
+        if(sp != cp)
+          controlreply(np, "%s", sp);
+
+        if(finished)
+          break;
+
+        *cp = '\n';
+
+        sp = cp + 1;
+      }
+    }
+  }
+}
+
+int controlhelpcmd(void *sender, int cargc, char **cargv) {
+  Command *cmd;
+  nick *np = (nick *)sender;
+
+  if (cargc<1)
+    return CMD_USAGE;
+
+  cmd=findcommandintree(controlcmds,cargv[0],1);
+  if (cmd==NULL) {
+    controlreply(np,"Unknown command.");
+    return CMD_ERROR;
+  }
+
+  controlhelp(np, cmd);
+  return CMD_OK;
+}
+
+void controlnoticeopers(flag_t permissionlevel, flag_t noticelevel, char *format, ...) {
+  int i;
+  nick *np;
+  char broadcast[512];
+  va_list va;
+
+  va_start(va, format);
+  vsnprintf(broadcast, sizeof(broadcast), format, va);
+  va_end(va);
+
+  for(i=0;i<NICKHASHSIZE;i++)
+    for(np=nicktable[i];np;np=np->next)
+      if (IsOper(np))
+        controlnotice(np, "%s", broadcast);
+}
+
+void controlnswall(int noticelevel, char *format, ...) {
+  char broadcast[512];
+  va_list va;
+
+  va_start(va, format);
+  vsnprintf(broadcast, sizeof(broadcast), format, va);
+  va_end(va);
+
+  controlwall(NO_OPER, noticelevel, "%s", broadcast);
+}
+