/*
* command.c
*
* Written by Toshiharu OHNO <tony-o@iij.ad.jp>
* Copyright (c) 1993, Internet Initiative Japan, Inc. All rights reserved.
* See ``COPYRIGHT.iij''
*
* Rewritten by Archie Cobbs <archie@freebsd.org>
* Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
* See ``COPYRIGHT.whistle''
*/
#include "ppp.h"
#include "console.h"
#include "web.h"
#include "command.h"
#include "ccp.h"
#include "iface.h"
#include "radius.h"
#include "bund.h"
#include "link.h"
#include "lcp.h"
#include "nat.h"
#include "ipcp.h"
#include "ip.h"
#include "devices.h"
#include "netgraph.h"
#include "custom.h"
#include "ngfunc.h"
#include "util.h"
/*
* DEFINITIONS
*/
struct layer {
const char *name;
void (*opener)(Context ctx);
void (*closer)(Context ctx);
const char *desc;
};
typedef struct layer *Layer;
#define DEFAULT_OPEN_LAYER "link"
/* Set menu options */
enum {
SET_ENABLE,
SET_DISABLE,
SET_RULE,
SET_QUEUE,
SET_PIPE,
SET_TABLE,
};
/*
* INTERNAL FUNCTIONS
*/
/* Commands */
static int ShowVersion(Context ctx, int ac, char *av[], void *arg);
static int ShowLayers(Context ctx, int ac, char *av[], void *arg);
static int ShowTypes(Context ctx, int ac, char *av[], void *arg);
static int ShowSummary(Context ctx, int ac, char *av[], void *arg);
static int ShowEvents(Context ctx, int ac, char *av[], void *arg);
static int ShowGlobal(Context ctx, int ac, char *av[], void *arg);
static int OpenCommand(Context ctx, int ac, char *av[], void *arg);
static int CloseCommand(Context ctx, int ac, char *av[], void *arg);
static int LoadCommand(Context ctx, int ac, char *av[], void *arg);
static int ExitCommand(Context ctx, int ac, char *av[], void *arg);
static int QuitCommand(Context ctx, int ac, char *av[], void *arg);
static int NullCommand(Context ctx, int ac, char *av[], void *arg);
static int GlobalSetCommand(Context ctx, int ac, char *av[], void *arg);
static int SetDebugCommand(Context ctx, int ac, char *av[], void *arg);
/* Other stuff */
static int DoCommandTab(Context ctx, CmdTab cmdlist, int ac, char *av[]);
static Layer GetLayer(const char *name);
/*
* INTERNAL VARIABLES
*/
static int exitflag;
const struct cmdtab GlobalSetCmds[] = {
{ "enable [opt ...]", "Enable option" ,
GlobalSetCommand, NULL, (void *) SET_ENABLE },
{ "disable [opt ...]", "Disable option" ,
GlobalSetCommand, NULL, (void *) SET_DISABLE },
{ "startrule num", "Initial ipfw rule number" ,
GlobalSetCommand, NULL, (void *) SET_RULE },
{ "startqueue num", "Initial ipfw queue number" ,
GlobalSetCommand, NULL, (void *) SET_QUEUE },
{ "startpipe num", "Initial ipfw pipe number" ,
GlobalSetCommand, NULL, (void *) SET_PIPE },
{ "starttable num", "Initial ipfw table number" ,
GlobalSetCommand, NULL, (void *) SET_TABLE },
{ NULL },
};
static const struct confinfo gGlobalConfList[] = {
{ 0, GLOBAL_CONF_TCPWRAPPER, "tcp-wrapper" },
{ 0, 0, NULL },
};
static const struct cmdtab ShowCommands[] = {
{ "bundle [name]", "Bundle status",
BundStat, AdmitBund, NULL },
{ "repeater [name]", "Repeater status",
RepStat, AdmitRep, NULL },
{ "ccp", "CCP status",
CcpStat, AdmitBund, NULL },
{ "ecp", "ECP status",
EcpStat, AdmitBund, NULL },
{ "eap", "EAP status",
EapStat, AdmitBund, NULL },
{ "events", "Current events",
ShowEvents, NULL, NULL },
{ "ipcp", "IPCP status",
IpcpStat, AdmitBund, NULL },
{ "ipv6cp", "IPV6CP status",
Ipv6cpStat, AdmitBund, NULL },
{ "iface", "Interface status",
IfaceStat, AdmitBund, NULL },
{ "routes", "IP routing table",
IpShowRoutes, NULL, NULL },
{ "layers", "Layers to open/close",
ShowLayers, NULL, NULL },
{ "phys", "Physical device status",
PhysStat, AdmitPhys, NULL },
{ "link", "Link status",
LinkStat, AdmitLink, NULL },
{ "auth", "Auth status",
AuthStat, AdmitLink, NULL },
{ "radius", "RADIUS status",
RadStat, AdmitLink, NULL },
{ "lcp", "LCP status",
LcpStat, AdmitLink, NULL },
{ "nat", "NAT status",
NatStat, AdmitLink, NULL },
{ "mem", "Memory map",
MemStat, NULL, NULL },
{ "mp", "Multi-link status",
MpStat, AdmitBund, NULL },
{ "console", "Console status",
ConsoleStat, NULL, NULL },
{ "web", "Web status",
WebStat, NULL, NULL },
{ "global", "Global settings",
ShowGlobal, NULL, NULL },
{ "types", "Supported device types",
ShowTypes, NULL, NULL },
{ "version", "Version string",
ShowVersion, NULL, NULL },
{ "summary", "Daemon status summary",
ShowSummary, NULL, NULL },
{ NULL },
};
static const struct cmdtab SetCommands[] = {
{ "bundle ...", "Bundle specific stuff",
CMD_SUBMENU, AdmitBund, (void *) BundSetCmds },
{ "repeater ...", "Repeater specific stuff",
CMD_SUBMENU, AdmitRep, (void *) RepSetCmds },
{ "link ...", "Link specific stuff",
CMD_SUBMENU, AdmitLink, (void *) LinkSetCmds },
{ "phys ...", "Phys specific stuff",
CMD_SUBMENU, AdmitPhys, (void *) PhysSetCmds },
{ "iface ...", "Interface specific stuff",
CMD_SUBMENU, AdmitBund, (void *) IfaceSetCmds },
{ "ipcp ...", "IPCP specific stuff",
CMD_SUBMENU, AdmitBund, (void *) IpcpSetCmds },
{ "ipv6cp ...", "IPV6CP specific stuff",
CMD_SUBMENU, AdmitBund, (void *) Ipv6cpSetCmds },
{ "ccp ...", "CCP specific stuff",
CMD_SUBMENU, AdmitBund, (void *) CcpSetCmds },
{ "ecp ...", "ECP specific stuff",
CMD_SUBMENU, AdmitBund, (void *) EcpSetCmds },
{ "eap ...", "EAP specific stuff",
CMD_SUBMENU, AdmitBund, (void *) EapSetCmds },
{ "auth ...", "Auth specific stuff",
CMD_SUBMENU, AdmitLink, (void *) AuthSetCmds },
{ "radius ...", "RADIUS specific stuff",
CMD_SUBMENU, AdmitLink, (void *) RadiusSetCmds },
{ "console ...", "Console specific stuff",
CMD_SUBMENU, NULL, (void *) ConsoleSetCmds },
{ "web ...", "Web specific stuff",
CMD_SUBMENU, NULL, (void *) WebSetCmds },
{ "global ...", "Global settings",
CMD_SUBMENU, NULL, (void *) GlobalSetCmds },
#ifdef USE_NG_NETFLOW
{ "netflow ...", "NetFlow settings",
CMD_SUBMENU, NULL, (void *) NetflowSetCmds },
#endif
{ "nat ...", "Nat settings",
CMD_SUBMENU, NULL, (void *) NatSetCmds },
{ "debug level", "Set netgraph debug level",
SetDebugCommand, NULL, NULL },
#define _WANT_DEVICE_CMDS
#include "devices.h"
{ NULL },
};
const struct cmdtab gCommands[] = {
{ "new bundle link ...", "Create new bundle",
BundCreateCmd, NULL, NULL },
{ "rnew repeater link1 link2", "Create new repeater",
RepCreateCmd, NULL, NULL },
{ "bundle [name]", "Choose/list bundles",
BundCommand, NULL, NULL },
{ "msession [msesid]", "Choose link by multy-session-id",
MSessionCommand, NULL, NULL },
{ "repeater [name]", "Choose/list repeaters",
RepCommand, NULL, NULL },
{ "custom ...", "Custom stuff",
CMD_SUBMENU, NULL, (void *) CustomCmds },
{ "link [name]", "Choose link",
LinkCommand, NULL, NULL },
{ "session [sesid]", "Choose link by session-id",
SessionCommand, NULL, NULL },
{ "phys [name]", "Choose phys",
PhysCommand, NULL, NULL },
{ "open [layer]", "Open a layer",
OpenCommand, AdmitLink, NULL },
{ "close [layer]", "Close a layer",
CloseCommand, AdmitLink, NULL },
{ "load label", "Read from config file",
LoadCommand, NULL, NULL },
{ "set ...", "Set parameters",
CMD_SUBMENU, NULL, (void *) SetCommands },
{ "show ...", "Show status",
CMD_SUBMENU, NULL, (void *) ShowCommands },
{ "exit", "Exit console",
ExitCommand, NULL, NULL },
{ "null", "Do nothing",
NullCommand, NULL, NULL },
{ "log [+/-opt ...]", "Set/view log options",
LogCommand, NULL, NULL },
{ "quit", "Quit program",
QuitCommand, NULL, NULL },
{ "help ...", "Help on any command",
HelpCommand, NULL, NULL },
{ NULL },
};
/*
* Layers
*/
struct layer gLayers[] = {
{ "iface",
IfaceOpenCmd,
IfaceCloseCmd,
"System interface"
},
{ "ipcp",
IpcpOpenCmd,
IpcpCloseCmd,
"IPCP: IP control protocol"
},
{ "ipv6cp",
Ipv6cpOpenCmd,
Ipv6cpCloseCmd,
"IPV6CP: IPv6 control protocol"
},
{ "ccp",
CcpOpenCmd,
CcpCloseCmd,
"CCP: compression ctrl prot."
},
{ "ecp",
EcpOpenCmd,
EcpCloseCmd,
"ECP: encryption ctrl prot."
},
{ "bund",
BundOpenCmd,
BundCloseCmd,
"Multilink bundle"
},
{ "link",
LinkOpenCmd,
LinkCloseCmd,
"Link layer"
},
{ "phys",
PhysOpenCmd,
PhysCloseCmd,
"Physical link layer"
},
};
#define NUM_LAYERS (sizeof(gLayers) / sizeof(*gLayers))
/*
* DoCommand()
*
* Executes command. Returns TRUE if user wants to quit.
*/
int
DoCommand(Context ctx, int ac, char *av[], const char *file, int line)
{
int rtn;
char filebuf[100];
exitflag = FALSE;
rtn = DoCommandTab(ctx, gCommands, ac, av);
/* Bad usage? */
if (rtn < 0) {
if (file) {
snprintf(filebuf,sizeof(filebuf),"%s:%d: ", file, line);
HelpCommand(ctx, ac, av, filebuf);
} else {
HelpCommand(ctx, ac, av, NULL);
}
}
return(exitflag);
}
/*
* DoCommandTab()
*
* Execute command from given command menu
*/
static int
DoCommandTab(Context ctx, CmdTab cmdlist, int ac, char *av[])
{
CmdTab cmd;
int rtn = 0;
/* Huh? */
if (ac <= 0)
return(-1);
/* Find command */
if (FindCommand(cmdlist, av[0], &cmd))
return(-1);
/* Check command admissibility */
if (cmd->admit && !(cmd->admit)(ctx, cmd))
return(0);
/* Find command and either execute or recurse into a submenu */
if (cmd->func == CMD_SUBMENU)
rtn = DoCommandTab(ctx, (CmdTab) cmd->arg, ac - 1, av + 1);
else if (cmd->func == CMD_UNIMPL)
Log(LG_ERR, ("command '%s' is not implemented", av[0]));
else
rtn = (cmd->func)(ctx, ac - 1, av + 1, cmd->arg);
return(rtn);
}
/*
* FindCommand()
*/
int
FindCommand(CmdTab cmds, char *str, CmdTab *cmdp)
{
int nmatch;
int len = strlen(str);
for (nmatch = 0; cmds->name; cmds++) {
if (cmds->name && !strncmp(str, cmds->name, len)) {
*cmdp = cmds;
nmatch++;
}
}
switch (nmatch) {
case 0:
return(-1);
case 1:
return(0);
default:
return(-2);
}
}
/********** COMMANDS **********/
/*
* GlobalSetCommand()
*/
static int
GlobalSetCommand(Context ctx, int ac, char *av[], void *arg)
{
int val;
if (ac == 0)
return(-1);
switch ((intptr_t)arg) {
case SET_ENABLE:
EnableCommand(ac, av, &gGlobalConf.options, gGlobalConfList);
break;
case SET_DISABLE:
DisableCommand(ac, av, &gGlobalConf.options, gGlobalConfList);
break;
case SET_RULE:
if (rule_pool)
Log(LG_ERR, ("Rule pool is not empty. Impossible to set initial number"));
else {
val = atoi(*av);
if (val <= 0 || val>=65535)
Log(LG_ERR, ("Incorrect rule number"));
else
rule_pool_start = val;
}
break;
case SET_QUEUE:
if (queue_pool)
Log(LG_ERR, ("Queue pool is not empty. Impossible to set initial number"));
else {
val = atoi(*av);
if (val <= 0 || val>=65535)
Log(LG_ERR, ("Incorrect queue number"));
else
queue_pool_start = val;
}
break;
case SET_PIPE:
if (rule_pool)
Log(LG_ERR, ("Pipe pool is not empty. Impossible to set initial number"));
else {
val = atoi(*av);
if (val <= 0 || val>=65535)
Log(LG_ERR, ("Incorrect rule number"));
else
pipe_pool_start = val;
}
break;
case SET_TABLE:
if (rule_pool)
Log(LG_ERR, ("Table pool is not empty. Impossible to set initial number"));
else {
val = atoi(*av);
if (val <= 0 || val>127) /* table 0 is usually possible but we deny it */
Log(LG_ERR, ("Incorrect rule number"));
else
table_pool_start = val;
}
break;
default:
return(-1);
}
return 0;
}
/*
* HelpCommand()
*/
int
HelpCommand(Context ctx, int ac, char *av[], void *arg)
{
int depth;
CmdTab menu, cmd;
char *mark, *mark_save;
const char *errfmt;
char buf[100];
int err;
for (mark = buf, depth = *buf = 0, menu = gCommands;
depth < ac;
depth++, menu = (CmdTab) cmd->arg) {
if ((err = FindCommand(menu, av[depth], &cmd))) {
int k;
for (*buf = k = 0; k <= depth; k++)
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%s%c",
av[k], k == depth ? '\0' : ' ');
switch (err) {
case -1:
errfmt = "%sUnknown command: '%s'. Try \"help\".";
break;
case -2:
errfmt = "%sAmbiguous command: '%s'";
break;
default:
errfmt = "%sUnknown error: '%s'";
}
if (arg) {
Log(LG_ERR, (errfmt, (char*)arg, buf));
} else {
Log(LG_ERR, (errfmt, "", buf));
}
return(0);
}
sprintf(mark, depth ? " %s" : "%s", cmd->name);
mark_save = mark;
if ((mark = strchr(mark + 1, ' ')) == NULL)
mark = mark_save + strlen(mark_save);
if (cmd->func != CMD_SUBMENU)
{
Printf("Usage: %s\r\n", buf);
return(0);
}
}
/* Show list of available commands in this submenu */
*mark = 0;
if (!*buf)
Printf("Available commands:\r\n");
else
Printf("Commands available under \"%s\":\r\n", buf);
for (cmd = menu; cmd->name; cmd++) {
snprintf(buf, sizeof(buf), "%s", cmd->name);
if ((mark = strchr(buf, ' ')))
*mark = 0;
Printf(" %-9s: %-20s%s", buf, cmd->desc,
((cmd - menu) & 1)? "\r\n" : "\t");
}
if ((cmd - menu) & 1)
Printf("\r\n");
return(0);
}
/*
* SetDebugCommand()
*/
static int
SetDebugCommand(Context ctx, int ac, char *av[], void *arg)
{
switch (ac) {
case 1:
NgSetDebug(atoi(av[0]));
break;
default:
return(-1);
}
return(0);
}
/*
* ShowVersion()
*/
static int
ShowVersion(Context ctx, int ac, char *av[], void *arg)
{
Printf("MPD version: %s\r\n", gVersion);
Printf(" Compiled-in system features:\r\n");
#ifdef USE_NG_CAR
Printf(" ng_car : yes\r\n");
#else
Printf(" ng_car : no\r\n");
#endif
#ifdef USE_NG_DEFLATE
Printf(" ng_deflate : yes\r\n");
#else
Printf(" ng_deflate : no\r\n");
#endif
#ifdef USE_NG_IPACCT
Printf(" ng_ipacct : yes\r\n");
#else
Printf(" ng_ipacct : no\r\n");
#endif
#ifdef USE_NG_NAT
Printf(" ng_nat : yes\r\n");
#else
Printf(" ng_nat : no\r\n");
#endif
#ifdef USE_NG_NETFLOW
Printf(" ng_netflow : yes\r\n");
#else
Printf(" ng_netflow : no\r\n");
#endif
#ifdef USE_NG_PRED1
Printf(" ng_pred1 : yes\r\n");
#else
Printf(" ng_pred1 : emulated\r\n");
#endif
#ifdef USE_NG_TCPMSS
Printf(" ng_tcpmss : yes\r\n");
#else
Printf(" ng_tcpmss : emulated\r\n");
#endif
return(0);
}
/*
* ShowEvents()
*/
static int
ShowEvents(Context ctx, int ac, char *av[], void *arg)
{
EventDump(ctx, "mpd events");
return(0);
}
/*
* ShowGlobal()
*/
static int
ShowGlobal(Context ctx, int ac, char *av[], void *arg)
{
Printf("Global settings:\r\n");
Printf(" startrule : %d\r\n", rule_pool_start);
Printf(" startpipe : %d\r\n", pipe_pool_start);
Printf(" startqueue : %d\r\n", queue_pool_start);
Printf(" starttable : %d\r\n", table_pool_start);
Printf("Global options:\r\n");
OptStat(ctx, &gGlobalConf.options, gGlobalConfList);
return 0;
}
/*
* ExitCommand()
*/
static int
ExitCommand(Context ctx, int ac, char *av[], void *arg)
{
exitflag = TRUE;
return(0);
}
/*
* QuitCommand()
*/
static int
QuitCommand(Context ctx, int ac, char *av[], void *arg)
{
SendSignal(SIGTERM);
exitflag = TRUE;
return(0);
}
/*
* NullCommand()
*/
static int
NullCommand(Context ctx, int ac, char *av[], void *arg)
{
return(0);
}
/*
* LoadCommand()
*/
static int
LoadCommand(Context ctx, int ac, char *av[], void *arg)
{
static int depth=0;
if (ac != 1)
return(-1);
else {
if (depth>20) {
Log(LG_ERR, ("Depth limit was reached while loading '%s'!", *av));
Log(LG_ERR, ("There is a configuration loop!"));
return(-2);
}
depth++;
ReadFile(gConfigFile, *av, DoCommand, ctx);
depth--;
}
return(0);
}
/*
* OpenCommand()
*/
static int
OpenCommand(Context ctx, int ac, char *av[], void *arg)
{
Layer layer;
const char *name;
switch (ac) {
case 0:
name = DEFAULT_OPEN_LAYER;
break;
case 1:
name = av[0];
break;
default:
return(-1);
}
if ((layer = GetLayer(name)) != NULL)
(*layer->opener)(ctx);
return(0);
}
/*
* CloseCommand()
*/
static int
CloseCommand(Context ctx, int ac, char *av[], void *arg)
{
Layer layer;
const char *name;
switch (ac) {
case 0:
name = DEFAULT_OPEN_LAYER;
break;
case 1:
name = av[0];
break;
default:
return(-1);
}
if ((layer = GetLayer(name)) != NULL)
(*layer->closer)(ctx);
return(0);
}
/*
* GetLayer()
*/
static Layer
GetLayer(const char *name)
{
int k, found;
if (name == NULL)
name = "iface";
for (found = -1, k = 0; k < NUM_LAYERS; k++) {
if (!strncasecmp(name, gLayers[k].name, strlen(name))) {
if (found > 0) {
Log(LG_ERR, ("%s: ambiguous", name));
return(NULL);
} else
found = k;
}
}
if (found < 0) {
Log(LG_ERR, ("unknown layer \"%s\": try \"show layers\"", name));
return(NULL);
}
return(&gLayers[found]);
}
/*
* ShowLayers()
*/
static int
ShowLayers(Context ctx, int ac, char *av[], void *arg)
{
int k;
Printf("\tName\t\tDescription\r\n"
"\t----\t\t-----------\r\n");
for (k = 0; k < NUM_LAYERS; k++)
Printf("\t%s\t\t%s\r\n", gLayers[k].name, gLayers[k].desc);
return(0);
}
/*
* ShowTypes()
*/
static int
ShowTypes(Context ctx, int ac, char *av[], void *arg)
{
PhysType pt;
int k;
Printf("Supported device types:\r\n\t");
for (k = 0; (pt = gPhysTypes[k]); k++)
Printf(" %s", pt->name);
Printf("\r\n");
return(0);
}
/*
* ShowSummary()
*/
static int
ShowSummary(Context ctx, int ac, char *av[], void *arg)
{
int b,l;
Bund B;
Link L;
Rep R;
PhysInfo P;
char buf[64];
Printf("Current daemon status summary\r\n");
Printf("Iface\t\tBund\tLink\tLCP\tDevice\t\tUser\t\tFrom\r\n");
for (b = 0; b<gNumBundles; b++) {
B=gBundles[b];
if (B) {
Printf("%s\t%s\t%s\t", B->iface.ifname, (B->iface.up?"Up":"Down"), B->name);
for (l = 0; l < B->n_links; l++) {
if (l != 0) {
Printf("\t\t\t");
}
L=B->links[l];
if (L) {
PhysGetPeerAddr(L->phys, buf, sizeof(buf));
Printf("%s\t%s\t%s\t%s\t%8s\t%s",
L->name,
FsmStateName(L->lcp.fsm.state),
(L->phys->type?L->phys->type->name:""),
gPhysStateNames[L->phys->state],
L->lcp.auth.params.authname,
buf
);
Printf("\r\n");
}
}
}
}
for (b = 0; b<gNumReps; b++) {
R=gReps[b];
if (R) {
Printf("Repeater\t%s\t", R->name);
for (l = 0; l < 2; l++) {
if (l != 0) {
Printf("\t\t\t");
}
P=R->physes[l];
if (P) {
PhysGetPeerAddr(P, buf, sizeof(buf));
Printf("%s\t%s\t%s\t%s\t%8s\t%s",
P->name,
"",
(P->type?P->type->name:""),
gPhysStateNames[P->state],
"",
buf
);
Printf("\r\n");
}
}
}
}
return(0);
}
/*
* AdmitBund()
*/
int
AdmitBund(Context ctx, CmdTab cmd)
{
if (!ctx->bund) {
Log(LG_ERR, ("No bundle selected for '%s' command", cmd->name));
return(FALSE);
}
return(TRUE);
}
/*
* AdmitLink()
*/
int
AdmitLink(Context ctx, CmdTab cmd)
{
if (!ctx->lnk) {
Log(LG_ERR, ("No link selected for '%s' command", cmd->name));
return(FALSE);
}
return(TRUE);
}
/*
* AdmitRep()
*/
int
AdmitRep(Context ctx, CmdTab cmd)
{
if (!ctx->rep) {
Log(LG_ERR, ("No repeater selected for '%s' command", cmd->name));
return(FALSE);
}
return(TRUE);
}
/*
* AdmitPhys()
*/
int
AdmitPhys(Context ctx, CmdTab cmd)
{
if (!ctx->phys) {
Log(LG_ERR, ("No phys selected for '%s' command", cmd->name));
return(FALSE);
}
return(TRUE);
}
/*
* AdmitDev()
*/
int
AdmitDev(Context ctx, CmdTab cmd)
{
if (!ctx->phys) {
Log(LG_ERR, ("No phys selected for '%s' command", cmd->name));
return(FALSE);
}
if (ctx->phys->type == NULL) {
Log(LG_ERR, ("Type of phys \"%s\" is unspecified for '%s' command", ctx->phys->name, cmd->name));
return(FALSE);
}
if (strncmp(cmd->name, ctx->phys->type->name, strlen(ctx->phys->type->name))) {
Log(LG_ERR, ("[%s] Phys type is %s, '%s' command isn't allowed here!",
ctx->phys->name, ctx->phys->type->name, cmd->name));
return(FALSE);
}
return(TRUE);
}
syntax highlighted by Code2HTML, v. 0.9.1