/* * $Id$ * * Copyright (C) 2006 iptelorg GmbH * * This file is part of ser, a free SIP server. * * ser is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * For a license to use the ser software under conditions * other than those described here, or to purchase support for this * software, please contact iptel.org by e-mail at the following addresses: * info@iptel.org * * ser is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * send commands using binrpc * * History: * -------- * 2006-02-14 created by andrei * 2009-06-29 command line completion for cfg groups and vars (andrei) * 2009-06-30 command line completion for mi cmds (andrei) * 2010-08-08 command line completion for counters/statistic (andrei) */ #include /* exit, abort */ #include #include #include #include #include /* isprint */ #include #include /* unix sock*/ #include /* udp sock */ #include /* writev */ #include /* gethostbyname */ #include /* time */ #ifdef USE_READLINE #include #include #define USE_CFG_VARS /* cfg group and vars completion */ #define USE_MI /* mi completion */ #define USE_COUNTERS /* counters/statistics completion */ #endif #include "parse_listen_id.h" #include "license.h" #include "../../modules/ctl/ctl_defaults.h" /* default socket & port */ #include "../../modules/ctl/binrpc.h" #include "../../modules/ctl/binrpc.c" /* ugly hack */ #ifndef NAME #define NAME "sercmd" #endif #ifndef VERSION #define VERSION "0.2" #endif #define IOVEC_CNT 20 #define MAX_LINE_SIZE 16384 /* for non readline mode */ #define MAX_REPLY_SIZE 65536 #define MAX_BODY_SIZE 65536 #define MAX_BINRPC_ARGS 256 #ifndef UNIX_PATH_MAX #define UNIX_PATH_MAX 108 #endif static char id[]="$Id$"; static char version[]= NAME " " VERSION; static char compiled[]= __TIME__ " " __DATE__; static char help_msg[]="\ Usage: " NAME " [options][-s address] [ cmd ]\n\ Options:\n\ -s address unix socket name or host name to send the commands on\n\ -R name force reply socket name, for the unix datagram socket mode\n\ -D dir create the reply socket in the directory if no reply \n\ socket is forced (-R) and a unix datagram socket is selected\n\ as the transport\n\ -f format print the result using format. Format is a string containing\n\ %v at the places where values read from the reply should be\n\ substituted. To print '%v', escape it using '%': %%v.\n\ -v Verbose \n\ -V Version number\n\ -h This help message\n\ address:\n\ [proto:]name[:port] where proto is one of tcp, udp, unixs or unixd\n\ e.g.: tcp:localhost:2048 , unixs:/tmp/ser_ctl\n\ cmd:\n\ method [arg1 [arg2...]]\n\ arg:\n\ string or number; to force a number to be interpreted as string \n\ prefix it by \"s:\", e.g. s:1\n\ Examples:\n\ " NAME " -s unixs:/tmp/ser_unix system.listMethods\n\ " NAME " -f \"pid: %v desc: %v\\n\" -s udp:localhost:2047 core.ps \n\ " NAME " ps # uses default ctl socket \n\ " NAME " # enters interactive mode on the default socket \n\ " NAME " -s tcp:localhost # interactive mode, default port \n\ "; int verbose=0; char* reply_socket=0; /* unix datagram reply socket name */ char* sock_dir=0; /* same as above, but only the directory */ char* unix_socket=0; struct sockaddr_un mysun; int quit; /* used only in interactive mode */ struct binrpc_val* rpc_array; int rpc_no=0; #ifdef USE_CFG_VARS struct binrpc_val* cfg_vars_array; int cfg_vars_no; struct cfg_var_grp{ struct cfg_var_grp* next; str grp_name; /**< group name */ str* var_names; /**< str array, null terminated */ int var_no; }; struct cfg_var_grp* cfg_grp_lst; /** cfg groups list, allong with var names*/ struct cfg_var_grp* crt_cfg_grp; #endif /* USE_CFG_VARS */ #ifdef USE_MI struct binrpc_val* mi_which_array; int mi_which_no; str* mi_cmds; int mi_cmds_no; #endif /* USE_MI */ #ifdef USE_COUNTERS struct binrpc_val* cnt_grps_array; /* response array */ int cnt_grps_no; /* number of response records */ struct cnt_var_grp { struct cnt_var_grp * next; str grp_name; str* var_names; /**< str array (null terminated strings)*/ int var_no; struct binrpc_val* cnt_vars_array; /* var_name will point here */ int cnt_vars_no; /* cnt_vars_array size (no. of response records) */ }; struct cnt_var_grp* cnt_grp_lst; /* counters groups list, allong with vars */ struct cnt_var_grp* crt_cnt_grp; #endif /* USE_COUNTERS */ #define IOV_SET(vect, str) \ do{\ (vect).iov_base=(str); \ (vect).iov_len=strlen((str)); \ }while(0) #define INT2STR_MAX_LEN (19+1+1) /* 2^64~= 16*10^18 => 19+1 digits + \0 */ /* returns a pointer to a static buffer containing l in asciiz & sets len */ static inline char* int2str(unsigned int l, int* len) { static char r[INT2STR_MAX_LEN]; int i; i=INT2STR_MAX_LEN-2; r[INT2STR_MAX_LEN-1]=0; /* null terminate */ do{ r[i]=l%10+'0'; i--; l/=10; }while(l && (i>=0)); if (l && (i<0)){ fprintf(stderr, "BUG: int2str: overflow\n"); } if (len) *len=(INT2STR_MAX_LEN-2)-i; return &r[i+1]; } static char* trim_ws(char* l) { char* ret; for(;*l && ((*l==' ')||(*l=='\t')||(*l=='\n')||(*l=='\r')); l++); ret=l; if (*ret==0) return ret; for(l=l+strlen(l)-1; (l>ret) && ((*l==' ')||(*l=='\t')||(*l=='\n')||(*l=='\r')); l--); *(l+1)=0; return ret; } int gen_cookie() { return rand(); } struct binrpc_cmd{ char* method; int argc; struct binrpc_val argv[MAX_BINRPC_ARGS]; }; struct cmd_alias{ char* name; char* method; char* format; /* reply print format */ }; struct sercmd_builtin{ char* name; int (*f)(int, struct binrpc_cmd*); char* doc; }; static int sercmd_help(int s, struct binrpc_cmd* cmd); static int sercmd_ver(int s, struct binrpc_cmd* cmd); static int sercmd_quit(int s, struct binrpc_cmd* cmd); static int sercmd_warranty(int s, struct binrpc_cmd* cmd); static struct cmd_alias cmd_aliases[]={ { "ps", "core.ps", "%v\t%v\n" }, { "list", "system.listMethods", 0 }, { "ls", "system.listMethods", 0 }, { "server", "core.version", 0 }, { "serversion", "core.version", 0 }, { "who", "ctl.who", "[%v] %v: %v %v -> %v %v\n"}, { "listen", "ctl.listen", "[%v] %v: %v %v\n"}, { "dns_mem_info", "dns.mem_info", "%v / %v\n"}, { "dns_debug", "dns.debug", "%v (%v): size=%v ref=%v expire=%vs last=%vs ago f=%v\n"}, { "dns_debug_all", "dns.debug_all", "%v (%v) [%v]: size=%v ref=%v expire=%vs last=%vs ago f=%v\n" "\t\t%v:%v expire=%vs f=%v\n"}, { "dst_blacklist_mem_info", "dst_blacklist.mem_info", "%v / %v\n"}, { "dst_blacklist_debug", "dst_blacklist.debug", "%v:%v:%v expire:%v flags: %v\n"}, {0,0,0} }; static struct sercmd_builtin builtins[]={ { "?", sercmd_help, "help"}, { "help", sercmd_help, "displays help for a command"}, { "version", sercmd_ver, "displays " NAME "version"}, { "quit", sercmd_quit, "exits " NAME }, { "exit", sercmd_quit, "exits " NAME }, { "warranty", sercmd_warranty, "displays " NAME "'s warranty info"}, { "license", sercmd_warranty, "displays " NAME "'s license"}, {0,0} }; #ifdef USE_READLINE enum complete_states { COMPLETE_INIT, COMPLETE_CMD_NAME, #ifdef USE_CFG_VARS COMPLETE_CFG_GRP, COMPLETE_CFG_VAR, #endif /* USE_CFG_VARS */ #ifdef USE_MI COMPLETE_MI, #endif /* USE_Mi */ #ifdef USE_COUNTERS COMPLETE_CNT_GRP, COMPLETE_CNT_VAR, #endif /* USE_COUNTERS */ COMPLETE_NOTHING }; /* instead of rl_attempted_completion_over which is not present in some readline emulations, use attempted_completion_state */ static enum complete_states attempted_completion_state; static int crt_param_no; /* commands for which we complete the params to other method names */ char* complete_params_methods[]={ "?", "h", "help", "system.methodSignature", "system.methodHelp", 0 }; #ifdef USE_CFG_VARS /* commands for which we complete the first param with a cfg var grp*/ char* complete_params_cfg_var[]={ "cfg.get", "cfg.help", "cfg.set_delayed_int", "cfg.set_delayed_string", "cfg.set_now_int", "cfg.set_now_string", 0 }; #endif /* USE_CFG_VARS */ #ifdef USE_MI /* commands for which we complete the first param with an mi command*/ char* complete_params_mi[]={ "mi", "mi_fifo", "mi_dg", "mi_xmlrpc", 0 }; #endif /* USE_MI */ #ifdef USE_COUNTERS /* commands for which we complete the first param with a counter group */ char* complete_param1_counter_grp[] = { "cnt.get", "cnt.get_raw", "cnt.grp_get_all", "cnt.reset", "cnt.var_list", "cnt.help", 0 }; /* commands for which we completed the 2nd param with a counter name */ char* complete_param2_counter_name[] = { "cnt.get", "cnt.get_raw", "cnt.reset", "cnt.help", 0 }; #endif /* USE_COUNTERS */ #endif /* USE_READLINE */ static int parse_arg(struct binrpc_val* v, char* arg) { int i; double f; char* tmp; int len; i=strtol(arg, &tmp, 10); if ((tmp==0) || (*tmp)){ f=strtod(arg, &tmp); if ((tmp==0) || (*tmp)){ /* not an int or a float => string */ len=strlen(arg); if ((len>=2) && (arg[0]=='s') && (arg[1]==':')){ tmp=&arg[2]; len-=2; }else{ tmp=arg; } v->type=BINRPC_T_STR; v->u.strval.s=tmp; v->u.strval.len=len; }else{ /* float */ v->type=BINRPC_T_DOUBLE; v->u.fval=f; } }else{ /* int */ v->type=BINRPC_T_INT; v->u.intval=i; } return 0; } static int parse_cmd(struct binrpc_cmd* cmd, char** argv, int count) { int r; cmd->method=argv[0]; if ((count-1)>MAX_BINRPC_ARGS){ fprintf(stderr, "ERROR: too many args %d, only %d allowed\n", count-1, MAX_BINRPC_ARGS); return -1; } for (r=1; rargv[r-1], argv[r])<0) return -1; } cmd->argc=r-1; return 0; } void print_binrpc_val(struct binrpc_val* v, int ident) { int r; if ((v->type==BINRPC_T_STRUCT) && !v->u.end) ident--; /* fix to have strut beg. idented differently */ for (r=0; rname.s){ printf("%.*s: ", v->name.len, v->name.s); } switch(v->type){ case BINRPC_T_INT: printf("%d", v->u.intval); break; case BINRPC_T_STR: case BINRPC_T_BYTES: printf("%.*s", v->u.strval.len, v->u.strval.s); break; case BINRPC_T_ARRAY: printf("%c", (v->u.end)?']':'['); break; case BINRPC_T_STRUCT: printf("%c", (v->u.end)?'}':'{'); break; case BINRPC_T_DOUBLE: printf("%f", v->u.fval); break; default: printf("ERROR: unknown type %d\n", v->type); }; } /* opens, and connects on a STREAM unix socket * returns socket fd or -1 on error */ int connect_unix_sock(char* name, int type) { struct sockaddr_un ifsun; int s; int len; int ret; int retries; retries=0; s=-1; memset(&ifsun, 0, sizeof (struct sockaddr_un)); len=strlen(name); if (len>UNIX_PATH_MAX){ fprintf(stderr, "ERROR: connect_unix_sock: name too long " "(%d > %d): %s\n", len, UNIX_PATH_MAX, name); goto error; } ifsun.sun_family=AF_UNIX; memcpy(ifsun.sun_path, name, len); #ifdef HAVE_SOCKADDR_SA_LEN ifsun.sun_len=len; #endif s=socket(PF_UNIX, type, 0); if (s==-1){ fprintf(stderr, "ERROR: connect_unix_sock: cannot create unix socket" " %s: %s [%d]\n", name, strerror(errno), errno); goto error; } if (type==SOCK_DGRAM){ /* we must bind so that we can receive replies */ if (reply_socket==0){ if (sock_dir==0) sock_dir="/tmp"; retry: ret=snprintf(mysun.sun_path, UNIX_PATH_MAX, "%s/" NAME "_%d", sock_dir, rand()); if ((ret<0) ||(ret>=UNIX_PATH_MAX)){ fprintf(stderr, "ERROR: buffer overflow while trying to" "generate unix datagram socket name"); goto error; } }else{ if (strlen(reply_socket)>UNIX_PATH_MAX){ fprintf(stderr, "ERROR: buffer overflow while trying to" "use the provided unix datagram socket name (%s)", reply_socket); goto error; } strcpy(mysun.sun_path, reply_socket); } mysun.sun_family=AF_UNIX; if (bind(s, (struct sockaddr*)&mysun, sizeof(mysun))==-1){ if (errno==EADDRINUSE && (reply_socket==0) && (retries < 10)){ retries++; /* try another one */ goto retry; } fprintf(stderr, "ERROR: could not bind the unix socket to" " %s: %s (%d)\n", mysun.sun_path, strerror(errno), errno); goto error; } unix_socket=mysun.sun_path; } if (connect(s, (struct sockaddr *)&ifsun, sizeof(ifsun))==-1){ fprintf(stderr, "ERROR: connect_unix_sock: connect(%s): %s [%d]\n", name, strerror(errno), errno); goto error; } return s; error: if (s!=-1) close(s); return -1; } int connect_tcpudp_socket(char* address, int port, int type) { struct sockaddr_in addr; struct hostent* he; int sock; sock=-1; /* resolve destination */ he=gethostbyname(address); if (he==0){ fprintf(stderr, "ERROR: could not resolve %s\n", address); goto error; } /* open socket*/ addr.sin_family=he->h_addrtype; addr.sin_port=htons(port); memcpy(&addr.sin_addr.s_addr, he->h_addr_list[0], he->h_length); sock = socket(he->h_addrtype, type, 0); if (sock==-1){ fprintf(stderr, "ERROR: socket: %s\n", strerror(errno)); goto error; } if (connect(sock, (struct sockaddr*) &addr, sizeof(struct sockaddr))!=0){ fprintf(stderr, "ERROR: connect: %s\n", strerror(errno)); goto error; } return sock; error: if (sock!=-1) close(sock); return -1; } static void hexdump(unsigned char* buf, int len, int ascii) { int r, i; /* dump it in hex */ for (r=0; rmethod, strlen(cmd->method)); if (ret<0) goto binrpc_err; for (r=0; rargc; r++){ switch(cmd->argv[r].type){ case BINRPC_T_STR: ret=binrpc_addstr(&body, cmd->argv[r].u.strval.s, cmd->argv[r].u.strval.len); break; case BINRPC_T_INT: ret=binrpc_addint(&body, cmd->argv[r].u.intval); break; case BINRPC_T_DOUBLE: ret=binrpc_adddouble(&body, cmd->argv[r].u.fval); break; default: fprintf(stderr, "ERROR: unsupported type %d\n", cmd->argv[r].type); } if (ret<0) goto binrpc_err; } ret=binrpc_build_hdr(BINRPC_REQ, binrpc_pkt_len(&body), cookie, msg_hdr, BINRPC_MAX_HDR_SIZE); if (ret<0) goto binrpc_err; v[0].iov_base=msg_hdr; v[0].iov_len=ret; v[1].iov_base=msg_body; v[1].iov_len=binrpc_pkt_len(&body); write_again: if ((n=writev(s, v, 2))<0){ if (errno==EINTR) goto write_again; goto error_send; } return n; error_send: return -1; binrpc_err: return -2; } static int binrpc_errno=0; /* reads the whole reply * returns < 0 on error, reply size on success + initializes in_pkt * if ret==-2 (parse error), sets binrpc_errno to the binrpc error * error returns: -1 - read error (check errno) * -2 - binrpc parse error (chekc binrpc_errno) * -3 - cookie error (the cookied doesn't match) * -4 - message too big */ static int get_reply(int s, unsigned char* reply_buf, int max_reply_size, int cookie, struct binrpc_parse_ctx* in_pkt, unsigned char** body) { unsigned char* crt; unsigned char* hdr_end; unsigned char* msg_end; int n; int ret; hdr_end=crt=reply_buf; msg_end=reply_buf+max_reply_size; binrpc_errno=0; do{ n=read(s, crt, (int)(msg_end-crt)); if (n<=0){ if (errno==EINTR) continue; goto error_read; } if (verbose >= 3){ /* dump it in hex */ printf("received %d bytes in reply (@offset %d):\n", n, (int)(crt-reply_buf)); hexdump(crt, n, 1); } crt+=n; /* parse header if not parsed yet */ if (hdr_end==reply_buf){ hdr_end=binrpc_parse_init(in_pkt, reply_buf, n, &ret); if (ret<0){ if (ret==E_BINRPC_MORE_DATA) continue; goto error_parse; } if (verbose>1){ printf("new packet: type %02x, len %d, cookie %02x\n", in_pkt->type, in_pkt->tlen, in_pkt->cookie); } if (in_pkt->cookie!=cookie){ fprintf(stderr, "bad reply, cookie doesn't match: sent %02x " "and received %02x\n", cookie, in_pkt->cookie); goto error; } msg_end=hdr_end+in_pkt->tlen; if ((int)(msg_end-reply_buf)>max_reply_size) goto error_toolong; } }while(crtoffset, *p, binrpc_error(ret)); goto error; } rec++; if (fmt){ print_binrpc_val(&val, 0); }else{ print_binrpc_val(&val, in_pkt->in_struct+in_pkt->in_array); putchar('\n'); } } if (fmt && *f){ /* print the rest, with empty values */ while(*f){ s=f; f=parse_fmt(f, &val.type, &f_size); printf("%.*s", f_size, s); } } return 0; error: return -1; /*error_read_again: fprintf(stderr, "ERROR: more data needed\n"); return -2; */ } static int print_fault(struct binrpc_parse_ctx* in_pkt, unsigned char* body, int size) { printf("error: "); return print_body(in_pkt, body, size, "%v - %v\n"); } static int run_binrpc_cmd(int s, struct binrpc_cmd * cmd, char* fmt) { int cookie; unsigned char reply_buf[MAX_REPLY_SIZE]; unsigned char* msg_body; struct binrpc_parse_ctx in_pkt; int ret; cookie=gen_cookie(); if ((ret=send_binrpc_cmd(s, cmd, cookie))<0){ if (ret==-1) goto error_send; else goto binrpc_err; } /* read reply */ memset(&in_pkt, 0, sizeof(in_pkt)); if ((ret=get_reply(s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))<0){ switch(ret){ case -1: goto error_read; case -2: goto error_parse; case -3: goto error_cookie; case -4: goto error_toobig; } goto error; } switch(in_pkt.type){ case BINRPC_FAULT: if (print_fault(&in_pkt, msg_body, in_pkt.tlen)<0){ goto error; } break; case BINRPC_REPL: if (print_body(&in_pkt, msg_body, in_pkt.tlen, fmt)<0){ goto error; } break; default: fprintf(stderr, "ERROR: not a reply\n"); goto error; } if (verbose) printf(".\n"); /* normal exit */ return 0; binrpc_err: fprintf(stderr, "ERROR while building the packet: %s\n", binrpc_error(ret)); goto error; error_parse: fprintf(stderr, "ERROR while parsing the reply: %s\n", binrpc_error(binrpc_errno)); goto error; error_cookie: fprintf(stderr, "ERROR: cookie does not match\n"); goto error; error_toobig: fprintf(stderr, "ERROR: reply too big\n"); goto error; error_send: fprintf(stderr, "ERROR: send packet failed: %s (%d)\n", strerror(errno), errno); goto error; error_read: fprintf(stderr, "ERROR: read reply failed: %s (%d)\n", strerror(errno), errno); goto error; error: return -1; } static int parse_line(struct binrpc_cmd* cmd, char* line) { char* p; int count; cmd->method=strtok(line, " \t"); if (cmd->method==0) goto error_no_method; count=0; for(p=strtok(0, " \t"); p; p=strtok(0, " \t")){ if (count>=MAX_BINRPC_ARGS) goto error_too_many; if (parse_arg(&cmd->argv[count], p)<0){ goto error_arg; } count++; } cmd->argc=count; return 0; error_no_method: printf( "ERROR: no method name\n"); return -1; error_too_many: printf("ERROR: too many arguments (%d), no more than %d allowed\n", count, MAX_BINRPC_ARGS); return -1; error_arg: printf("ERROR: bad argument %d: %s\n", count+1, p); return -1; } /* resolves builtin aliases */ static void fix_cmd(struct binrpc_cmd* cmd, char** format) { int r; for (r=0; cmd_aliases[r].name; r++){ if (strcmp(cmd_aliases[r].name, cmd->method)==0){ cmd->method=cmd_aliases[r].method; if (*format==0) *format=cmd_aliases[r].format; break; } } } /* intercept builtin commands, returns 1 if intercepted, 0 if not, <0 on error */ static int run_builtins(int s, struct binrpc_cmd* cmd) { int r; int ret; for (r=0; builtins[r].name; r++){ if (strcmp(builtins[r].name, cmd->method)==0){ ret=builtins[r].f(s, cmd); return (ret<0)?ret:1; } } return 0; } /* runs command from cmd */ inline static int run_cmd(int s, struct binrpc_cmd* cmd, char* format) { int ret; char* fmt; fmt=format; fix_cmd(cmd, &fmt); if (!(ret=run_builtins(s, cmd))){ ret=run_binrpc_cmd(s, cmd, fmt); } return (ret>0)?0:ret; } /* runs a command represented in line */ inline static int run_line(int s, char* l, char* format) { struct binrpc_cmd cmd; int ret; if ((ret=parse_line(&cmd, l))==0){ return run_cmd(s, &cmd, format); } return ret; } static void free_rpc_array(struct binrpc_val* a, int size) { int r; for (r=0; roffset, *p, binrpc_error(ret)); goto error; } if (rec>=*records){ t=realloc(a, *records*sizeof(struct binrpc_val)*2); if (t==0) goto error_mem; a=t; *records*=2; } a[rec]=val; if (val.name.s){ if ((a[rec].name.s=malloc(val.name.len+1))==0) goto error_mem; memcpy(a[rec].name.s, val.name.s, val.name.len); a[rec].name.s[val.name.len+1]=0; /* 0-term */ } if (val.u.strval.s){ if (val.type==BINRPC_T_STR){ if ((a[rec].u.strval.s=malloc(val.u.strval.len+1))==0) goto error_mem; memcpy(a[rec].u.strval.s, val.u.strval.s, val.u.strval.len); a[rec].u.strval.s[val.u.strval.len]=0; /* 0-term */ }else if (val.type==BINRPC_T_BYTES){ if ((a[rec].u.strval.s=malloc(val.u.strval.len))==0) goto error_mem; memcpy(a[rec].u.strval.s, val.u.strval.s, val.u.strval.len); } } rec++; } if (rec && (rec<*records)){ a=realloc(a, rec*sizeof(struct binrpc_val)); } *records=rec; return a; error_mem: fprintf(stderr, "ERROR: parse_reply_body: out of memory\n"); error: if (a){ free_rpc_array(a, rec); } *records=0; return 0; } static int get_sercmd_list(int s) { struct binrpc_cmd cmd; int cookie; unsigned char reply_buf[MAX_REPLY_SIZE]; unsigned char* msg_body; struct binrpc_parse_ctx in_pkt; int ret; cmd.method="system.listMethods"; cmd.argc=0; cookie=gen_cookie(); if ((ret=send_binrpc_cmd(s, &cmd, cookie))<0){ if (ret==-1) goto error_send; else goto binrpc_err; } /* read reply */ memset(&in_pkt, 0, sizeof(in_pkt)); if ((ret=get_reply(s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))<0){ goto error; } switch(in_pkt.type){ case BINRPC_FAULT: if (print_fault(&in_pkt, msg_body, in_pkt.tlen)<0){ goto error; } break; case BINRPC_REPL: rpc_no=100; /* default cmd list */ if ((rpc_array=parse_reply_body(&rpc_no, &in_pkt, msg_body, in_pkt.tlen))==0) goto error; break; default: fprintf(stderr, "ERROR: not a reply\n"); goto error; } return 0; binrpc_err: error_send: error: return -1; } #if defined(USE_CFG_VARS) || defined (USE_MI) || defined (USE_COUNTERS) /** check if cmd is a rpc command. * Quick check (using the internal rpc_array) if cmd is a valid rpc command. * @param cmd - null terminated ascii string * @return 1 on success, 0 on failure. */ static int is_rpc_cmd(char* cmd) { int r; int cmd_len; cmd_len=strlen(cmd); for (r=0; r: */ for (p=grp_name.s; pnext){ if (grp->grp_name.len==grp_name.len && memcmp(grp->grp_name.s, grp_name.s, grp_name.len)==0){ break; /* found */ } } if (grp==0){ /* not found => create a new one */ grp=malloc(sizeof(*grp)); if (grp==0) goto error_mem; memset(grp, 0, sizeof(*grp)); grp->grp_name=grp_name; if (last_grp){ last_grp->next=grp; last_grp=grp; }else{ cfg_grp_lst=grp; last_grp=cfg_grp_lst; } } grp->var_no++; } /* alloc the var arrays per group */ for (grp=cfg_grp_lst; grp; grp=grp->next){ grp->var_names=malloc(sizeof(str)*grp->var_no); if (grp->var_names==0) goto error_mem; memset(grp->var_names, 0, sizeof(str)*grp->var_no); grp->var_no=0; } /* reparse to get the var names per group */ for (r=0; r: */ for (p=grp_name.s; pnext){ if (grp->grp_name.len==grp_name.len && memcmp(grp->grp_name.s, grp_name.s, grp_name.len)==0){ /* add var */ grp->var_names[grp->var_no]=var_name; grp->var_no++; } } break; } } } return 0; binrpc_err: error_send: error: error_mem: return -1; } void free_cfg_grp_lst() { struct cfg_var_grp* grp; struct cfg_var_grp* last; grp=cfg_grp_lst; while(grp){ last=grp; grp=grp->next; free(last); } cfg_grp_lst=0; } #endif /* USE_CFG_VARS */ #ifdef USE_MI /* retrieve the mi list */ static int get_mi_list(int s) { struct binrpc_cmd cmd; int cookie; unsigned char reply_buf[MAX_REPLY_SIZE]; unsigned char* msg_body; struct binrpc_parse_ctx in_pkt; char* p; char* end; str mi_name; int mi_which_results; int r; int ret; cmd.method="mi"; cmd.argv[0].type=BINRPC_T_STR; cmd.argv[0].u.strval.s="which"; cmd.argv[0].u.strval.len=strlen(cmd.argv[0].u.strval.s); cmd.argc=1; if (!is_rpc_cmd(cmd.method)) goto error; cookie=gen_cookie(); if ((ret=send_binrpc_cmd(s, &cmd, cookie))<0){ if (ret==-1) goto error_send; else goto binrpc_err; } /* read reply */ memset(&in_pkt, 0, sizeof(in_pkt)); if ((ret=get_reply(s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))<0){ goto error; } switch(in_pkt.type){ case BINRPC_FAULT: if (print_fault(&in_pkt, msg_body, in_pkt.tlen)<0){ goto error; } break; case BINRPC_REPL: mi_which_no=25; /* default rpc list */ if ((mi_which_array=parse_reply_body(&mi_which_no, &in_pkt, msg_body, in_pkt.tlen))==0) goto error; break; default: fprintf(stderr, "ERROR: not a reply\n"); goto error; } /* get the mi commands number */ mi_which_results=0; for (r=0; r=end) || (*p!=':')) continue; p++; /* skip over to the next ':' */ for(;p=end) continue; p++; /* skip over spaces */ for(;p=end || *p=='\n') continue; if (mi_cmds_no >= mi_which_results){ fprintf(stderr, "BUG: wrong mi cmds no (%d >= %d)\n", mi_cmds_no, mi_which_results); goto error; } mi_name.s=p; for(; pnext){ if (grp->grp_name.len==grp_name.len && memcmp(grp->grp_name.s, grp_name.s, grp_name.len)==0){ break; /* found */ } } if (grp==0){ /* not found => create a new one */ grp=malloc(sizeof(*grp)); if (grp==0) goto error_mem; memset(grp, 0, sizeof(*grp)); grp->grp_name=grp_name; if (last_grp){ last_grp->next=grp; last_grp=grp; }else{ cnt_grp_lst=grp; last_grp=cnt_grp_lst; } } } /* gets vars per group */ for (grp=cnt_grp_lst; grp; grp=grp->next){ cmd.method="cnt.var_list"; cmd.argv[0].type=BINRPC_T_STR; cmd.argv[0].u.strval=grp->grp_name; cmd.argc=1; if (!is_rpc_cmd(cmd.method)) goto error; cookie=gen_cookie(); if ((ret=send_binrpc_cmd(s, &cmd, cookie))<0){ if (ret==-1) goto error_send; else goto binrpc_err; } /* read reply */ memset(&in_pkt, 0, sizeof(in_pkt)); if ((ret=get_reply(s, reply_buf, MAX_REPLY_SIZE, cookie, &in_pkt, &msg_body))<0){ goto error; } switch(in_pkt.type){ case BINRPC_FAULT: if (print_fault(&in_pkt, msg_body, in_pkt.tlen)<0){ goto error; } break; case BINRPC_REPL: grp->cnt_vars_no=100; /* default counter list */ if ((grp->cnt_vars_array=parse_reply_body(&grp->cnt_vars_no, &in_pkt, msg_body, in_pkt.tlen))==0) goto error; break; default: fprintf(stderr, "ERROR: not a reply\n"); goto error; } grp->var_no = 0; grp->var_names=malloc(sizeof(str)*grp->cnt_vars_no); if (grp->var_names==0) goto error_mem; memset(grp->var_names, 0, sizeof(str)*grp->var_no); for (r=0; rcnt_vars_no; r++) { if (grp->cnt_vars_array[r].type!=BINRPC_T_STR) continue; var_name=grp->cnt_vars_array[r].u.strval; grp->var_names[grp->var_no] = var_name; grp->var_no++; } } return 0; binrpc_err: error_send: error: error_mem: return -1; } void free_cnt_grp_lst() { struct cnt_var_grp* grp; struct cnt_var_grp* last; grp=cnt_grp_lst; while(grp){ last=grp; grp=grp->next; if (last->cnt_vars_array) free_rpc_array(last->cnt_vars_array, last->cnt_vars_no); free(last); } cnt_grp_lst=0; } #endif /* USE_COUNTERS */ static void print_formatting(char* prefix, char* format, char* suffix) { if (format){ printf("%s", prefix); for (;*format;format++){ switch(*format){ case '\t': printf("\\t"); break; case '\n': printf("\\n"); break; case '\r': printf("\\r"); break; default: putchar(*format); } } printf("%s", suffix); } } static int sercmd_help(int s, struct binrpc_cmd* cmd) { int r; if (cmd->argc && (cmd->argv[0].type==BINRPC_T_STR)){ /* if it has args, try command help */ for (r=0; cmd_aliases[r].name; r++){ if (strcmp(cmd->argv[0].u.strval.s, cmd_aliases[r].name)==0){ printf("%s is an alias for %s", cmd->argv[0].u.strval.s, cmd_aliases[r].method); print_formatting(" with reply formatting: \"", cmd_aliases[r].format, "\""); putchar('\n'); return 0; } } for(r=0; builtins[r].name; r++){ if (strcmp(cmd->argv[0].u.strval.s, builtins[r].name)==0){ printf("builtin command: %s\n", builtins[r].doc?builtins[r].doc:"undocumented"); return 0; } } cmd->method="system.methodHelp"; if (run_binrpc_cmd(s, cmd, 0)<0){ printf("error: no such command %s\n", cmd->argv[0].u.strval.s); } return 0; } if (rpc_no==0){ if (get_sercmd_list(s)<0) goto error; } for (r=0; rnext; } for(;grp; grp=grp->next){ if (len<=grp->grp_name.len && memcmp(text, grp->grp_name.s, len)==0) { /* zero-term copy of the grp name */ name=malloc(grp->grp_name.len+1); if (name){ memcpy(name, grp->grp_name.s, grp->grp_name.len); name[grp->grp_name.len]=0; } return name; } } break; case COMPLETE_CFG_VAR: if (state==0){ /* init */ len=strlen(text); idx=0; } while(idx < crt_cfg_grp->var_no){ if (len<=crt_cfg_grp->var_names[idx].len && memcmp(text, crt_cfg_grp->var_names[idx].s, len)==0) { /* zero-term copy of the var name */ name=malloc(crt_cfg_grp->var_names[idx].len+1); if (name){ memcpy(name, crt_cfg_grp->var_names[idx].s, crt_cfg_grp->var_names[idx].len); name[crt_cfg_grp->var_names[idx].len]=0; } idx++; return name; } idx++; } break; #endif /* USE_CFG_VARS */ #ifdef USE_MI case COMPLETE_MI: if (state==0){ /* init */ len=strlen(text); idx=0; } while(idx < mi_cmds_no){ if (len<=mi_cmds[idx].len && memcmp(text, mi_cmds[idx].s, len)==0) { /* zero-term copy of the var name */ name=malloc(mi_cmds[idx].len+1); if (name){ memcpy(name, mi_cmds[idx].s, mi_cmds[idx].len); name[mi_cmds[idx].len]=0; } idx++; return name; } idx++; } break; #endif /* USE_MI */ #ifdef USE_COUNTERS case COMPLETE_CNT_GRP: if (state==0){ /* init */ len=strlen(text); cnt_grp=cnt_grp_lst; }else{ cnt_grp=cnt_grp->next; } for(;cnt_grp; cnt_grp=cnt_grp->next){ if (len<=cnt_grp->grp_name.len && memcmp(text, cnt_grp->grp_name.s, len)==0) { /* zero-term copy of the cnt_grp name */ name=malloc(cnt_grp->grp_name.len+1); if (name){ memcpy(name, cnt_grp->grp_name.s, cnt_grp->grp_name.len); name[cnt_grp->grp_name.len]=0; } return name; } } break; case COMPLETE_CNT_VAR: if (state==0){ /* init */ len=strlen(text); idx=0; } while(idx < crt_cnt_grp->var_no){ if (len<=crt_cnt_grp->var_names[idx].len && memcmp(text, crt_cnt_grp->var_names[idx].s, len)==0) { /* zero-term copy of the var name */ name=malloc(crt_cnt_grp->var_names[idx].len+1); if (name){ memcpy(name, crt_cnt_grp->var_names[idx].s, crt_cnt_grp->var_names[idx].len); name[crt_cnt_grp->var_names[idx].len]=0; } idx++; return name; } idx++; } break; #endif /* USE_COUNTERS */ } /* no matches */ return 0; } char** sercmd_completion(const char* text, int start, int end) { int i, j; int cmd_start, cmd_end, cmd_len; int whitespace; #ifdef USE_CFG_VARS struct cfg_var_grp* grp; static int grp_start; int grp_len; #endif /* USE_CFG_VARS */ #ifdef USE_COUNTERS struct cnt_var_grp* cnt_grp; static int cnt_grp_start; int cnt_grp_len; #endif /* USE_COUNTERS */ crt_param_no=0; /* skip over whitespace at the beginning */ for (j=0; (jnext){ if (grp_len==grp->grp_name.len && memcmp(&rl_line_buffer[grp_start], grp->grp_name.s, grp_len)==0) { attempted_completion_state=COMPLETE_CFG_VAR; crt_cfg_grp=grp; goto end; } } } } #endif /* USE_CFG_VARS */ #ifdef USE_COUNTERS /* see if we complete counter names for this command */ for(i=0; complete_param2_counter_name[i]; i++){ if ((cmd_len==strlen(complete_param2_counter_name[i])) && (strncmp(&rl_line_buffer[cmd_start], complete_param2_counter_name[i], cmd_len)==0)){ /* get the group name: */ /* find grp_start */ for(j=cmd_end; (jnext){ if (cnt_grp_len==cnt_grp->grp_name.len && memcmp(&rl_line_buffer[cnt_grp_start], cnt_grp->grp_name.s, cnt_grp_len)==0) { attempted_completion_state=COMPLETE_CNT_VAR; crt_cnt_grp=cnt_grp; goto end; } } } } #endif /* COUNTERS */ } attempted_completion_state=COMPLETE_NOTHING; } end: return 0; /* let readline call sercmd_generator */ } #endif /* USE_READLINE */ /* on exit cleanup */ static void cleanup() { if (unix_socket){ if (unlink(unix_socket)<0){ fprintf(stderr, "ERROR: failed to delete %s: %s\n", unix_socket, strerror(errno)); } } } int main(int argc, char** argv) { int c; char* sock_name; int port_no; int sock_type; int s; struct binrpc_cmd cmd; int rec; struct id_list* sock_id; char* format; int interactive; char* line; char* l; quit=0; format=0; line=0; interactive=0; rec=1; s=-1; sock_name=0; port_no=0; sock_type=UNIXS_SOCK; opterr=0; while((c=getopt(argc, argv, "UVhs:D:R:vf:"))!=-1){ switch(c){ case 'V': printf("version: %s\n", version); printf("%s\n", id); printf("%s compiled on %s \n", __FILE__, compiled); exit(0); break; case 'h': printf("version: %s\n", version); printf("%s", help_msg); exit(0); break; case 's': sock_name=optarg; break; case 'R': reply_socket=optarg; break; case 'D': sock_dir=optarg; break; case 'U': sock_type=UDP_SOCK; break; case 'v': verbose++; break; case 'f': format=str_escape(optarg); if (format==0){ fprintf(stderr, "ERROR: memory allocation failure\n"); goto error; } break; case '?': if (isprint(optopt)) fprintf(stderr, "Unknown option `-%c'.\n", optopt); else fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt); goto error; case ':': fprintf(stderr, "Option `-%c' requires an argument.\n", optopt); goto error; default: abort(); } } if (sock_name==0){ sock_name=DEFAULT_CTL_SOCKET; } /* init the random number generator */ srand(getpid()+time(0)); /* we don't need very strong random numbers */ if (sock_name==0){ fprintf(stderr, "ERROR: no ser address specified\n"); goto error; } sock_id=parse_listen_id(sock_name, strlen(sock_name), sock_type); if (sock_id==0){ fprintf(stderr, "ERROR: error parsing ser adress %s\n", sock_name); goto error; } switch(sock_id->proto){ case UDP_SOCK: case TCP_SOCK: if (sock_id->port==0){ sock_id->port=DEFAULT_CTL_PORT; /* fprintf(stderr, "ERROR: no port specified: %s:\n", sock_name); goto error; */ } if ((s=connect_tcpudp_socket(sock_id->name, sock_id->port, (sock_id->proto==UDP_SOCK)?SOCK_DGRAM: SOCK_STREAM))<0){ goto error; } break; case UNIXS_SOCK: case UNIXD_SOCK: if ((s=connect_unix_sock(sock_id->name, (sock_id->proto==UNIXD_SOCK)?SOCK_DGRAM: SOCK_STREAM))<0) goto error; break; case UNKNOWN_SOCK: fprintf(stderr, "ERROR: Bad socket type for %s\n", sock_name); goto error; } free(sock_id); /* not needed anymore */ sock_id=0; if (optind>=argc){ interactive=1; /*fprintf(stderr, "ERROR: no command specified\n"); goto error; */ }else{ if (parse_cmd(&cmd, &argv[optind], argc-optind)<0) goto error; if (run_cmd(s, &cmd, format)<0) goto error; goto end; } /* interactive mode */ if (get_sercmd_list(s)==0){ #ifdef USE_CFG_VARS get_cfgvars_list(s); #endif /* USE_CFG_VARS */ #ifdef USE_MI get_mi_list(s); #endif /* USE_MI */ #ifdef USE_COUNTERS get_counters_list(s); #endif /* USE_COUNTERS */ } /* banners */ printf("%s %s\n", NAME, VERSION); printf("%s\n", COPYRIGHT); printf("%s\n", DISCLAIMER); #ifdef USE_READLINE /* initialize readline */ /* allow conditional parsing of the ~/.inputrc file*/ rl_readline_name=NAME; rl_completion_entry_function=sercmd_generator; rl_attempted_completion_function=sercmd_completion; while(!quit){ line=readline(NAME "> "); if (line==0) /* EOF */ break; l=trim_ws(line); /* trim whitespace */ if (*l){ add_history(l); run_line(s, l, format); } free(line); line=0; } #else line=malloc(MAX_LINE_SIZE); if (line==0){ fprintf(stderr, "memory allocation error\n"); goto error; } printf(NAME "> "); fflush(stdout); /* prompt */ while(!quit && fgets(line, MAX_LINE_SIZE, stdin)){ l=trim_ws(line); if (*l){ run_line(s, l, format); } printf(NAME "> "); fflush(stdout); /* prompt */ }; free(line); line=0; #endif /* USE_READLINE */ end: /* normal exit */ if (line) free(line); if (format) free(format); if (rpc_array) free_rpc_array(rpc_array, rpc_no); #ifdef USE_CFG_VARS if (cfg_grp_lst) free_cfg_grp_lst(); if (cfg_vars_array){ free_rpc_array(cfg_vars_array, cfg_vars_no); cfg_vars_array=0; cfg_vars_no=0; } #endif /* USE_CFG_VARS */ #ifdef USE_MI if (mi_cmds) free_mi_cmds(); if (mi_which_array){ free_rpc_array(mi_which_array, mi_which_no); mi_which_array=0; mi_which_no=0; } #endif /* USE_MI */ #ifdef USE_COUNTERS if (cnt_grp_lst) free_cnt_grp_lst(); if (cnt_grps_array){ free_rpc_array(cnt_grps_array, cnt_grps_no); cnt_grps_array=0; cnt_grps_no=0; } #endif /* USE_COUNTERS */ cleanup(); exit(0); error: if (line) free(line); if (format) free(format); if (rpc_array) free_rpc_array(rpc_array, rpc_no); #ifdef USE_CFG_VARS if (cfg_grp_lst) free_cfg_grp_lst(); if (cfg_vars_array){ free_rpc_array(cfg_vars_array, cfg_vars_no); cfg_vars_array=0; cfg_vars_no=0; } #endif /* USE_CFG_VARS */ #ifdef USE_MI if (mi_cmds) free_mi_cmds(); if (mi_which_array){ free_rpc_array(mi_which_array, mi_which_no); mi_which_array=0; mi_which_no=0; } #endif /* USE_MI */ #ifdef USE_COUNTERS if (cnt_grp_lst) free_cnt_grp_lst(); if (cnt_grps_array){ free_rpc_array(cnt_grps_array, cnt_grps_no); cnt_grps_array=0; cnt_grps_no=0; } #endif /* USE_COUNTERS */ cleanup(); exit(-1); }