Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
#ifdef NEEDS_FORKPTY
static int login_tty(int fd)
{
(void) setsid();
if (!isatty(fd))
return (-1);
(void) dup2(fd, 0);
(void) dup2(fd, 1);
(void) dup2(fd, 2);
if (fd > 2)
(void) close(fd);
return (0);
}
static int openpty(int *amaster, int *aslave, char *name, struct termios *termp, winsize *winp)
{
char line[] = "/dev/ptyXX";
const char *cp1, *cp2;
int master, slave, ttygid;
struct group *gr;
if ((gr = getgrnam("tty")) != NULL)
ttygid = gr->gr_gid;
else
ttygid = -1;
for (cp1 = "pqrsPQRS"; *cp1; cp1++) {
line[8] = *cp1;
for (cp2 = "0123456789abcdefghijklmnopqrstuv"; *cp2; cp2++) {
line[5] = 'p';
line[9] = *cp2;
if ((master = open(line, O_RDWR, 0)) == -1) {
if (errno == ENOENT)
break; /* try the next pty group */
} else {
line[5] = 't';
(void) chown(line, getuid(), ttygid);
(void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
/* Hrm... SunOS doesn't seem to have revoke
(void) revoke(line); */
if ((slave = open(line, O_RDWR, 0)) != -1) {
*amaster = master;
*aslave = slave;
if (name)
strcpy(name, line);
if (termp)
(void) tcsetattr(slave,
TCSAFLUSH, termp);
if (winp)
(void) ioctl(slave, TIOCSWINSZ,
(char *)winp);
return (0);
}
(void) close(master);
}
}
}
errno = ENOENT; /* out of ptys */
return (-1);
}
static int forkpty(int *amaster, char *name, termios *termp, winsize *winp)
{
int master, slave, pid;
if (openpty(&master, &slave, name, termp, winp) == -1)
return (-1);
switch (pid = FORK()) {
case -1:
return (-1);
case 0:
/*
* child
*/
(void) close(master);
login_tty(slave);
return (0);
}
/*
* parent
*/
*amaster = master;
(void) close(slave);
return (pid);
}
#endif /* NEED_FORKPTY */
int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
char str[MAX_PATH+1];
char fname[MAX_PATH+1];
char fullpath[MAX_PATH+1];
char fullcmdline[MAX_PATH+1];
char* argv[MAX_ARGS];

rswindell
committed
BYTE* bp;
BYTE buf[XTRN_IO_BUF_LEN];
BYTE output_buf[XTRN_IO_BUF_LEN*2];
ulong avail;

rswindell
committed
ulong output_len;
bool native=false; // DOS program by default
bool rio_abortable_save=rio_abortable;

rswindell
committed
int rd;
int wr;

rswindell
committed
int argc;

rswindell
committed
int in_pipe[2];
int out_pipe[2];
fd_set ibits;
struct timeval timeout;
if(online==ON_LOCAL)
eprintf("Executing external: %s",cmdline);
XTRN_LOADABLE_MODULE;
XTRN_LOADABLE_JS_MODULE;
attr(cfg.color[clr_external]); /* setup default attributes */
SAFECOPY(str,cmdline); /* Set fname to program name only */
truncstr(str," ");
SAFECOPY(fname,getfname(str));
for(i=0;i<cfg.total_natvpgms;i++)
if(!stricmp(fname,cfg.natvpgm[i]->name))
break;
if(i<cfg.total_natvpgms || mode&EX_NATIVE)
native=true;
sprintf(fullpath,"%s%s",startup_dir,fname);
if(startup_dir!=NULL && cmdline[0]!='/' && cmdline[0]!='.' && fexist(fullpath))
sprintf(fullcmdline,"%s%s",startup_dir,cmdline);
else
SAFECOPY(fullcmdline,cmdline);
if(native) { // Native (32-bit) external
// Current environment passed to child process
sprintf(dszlog,"%sPROTOCOL.LOG",cfg.node_dir);
setenv("DSZLOG",dszlog,1); /* Makes the DSZ LOG active */
setenv("SBBSNODE",cfg.node_dir,1);
setenv("SBBSCTRL",cfg.ctrl_dir,1);
setenv("SBBSDATA",cfg.data_dir,1);
setenv("SBBSEXEC",cfg.exec_dir,1);
sprintf(sbbsnnum,"%u",cfg.node_num);
if(setenv("SBBSNNUM",sbbsnnum,1))
errormsg(WHERE,ERR_WRITE,"environment",0);
#if defined(__FreeBSD__)
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
/* ToDo: This seems to work for every door except Iron Ox
ToDo: Iron Ox is unique in that it runs perfectly from
ToDo: tcsh but not at all from anywhere else, complaining
ToDo: about corrupt files. I've ruled out the possibilty
ToDo: of it being a terminal mode issue... no other ideas
ToDo: come to mind. */
FILE * doscmdrc;
sprintf(str,"%s.doscmdrc",cfg.node_dir);
if((doscmdrc=fopen(str,"w+"))==NULL) {
errormsg(WHERE,ERR_CREATE,str,0);
return(-1);
}
if(startup_dir!=NULL && startup_dir[0])
fprintf(doscmdrc,"assign C: %s\n",startup_dir);
else
fprintf(doscmdrc,"assign C: .\n");
fprintf(doscmdrc,"assign D: %s\n",cfg.node_dir);
SAFECOPY(str,cfg.exec_dir);
if((p=strrchr(str,'/'))!=NULL)
*p=0;
if((p=strrchr(str,'/'))!=NULL)
*p=0;
fprintf(doscmdrc,"assign E: %s\n",str);
/* setup doscmd env here */
/* ToDo Note, this assumes that the BBS uses standard dir names */
fprintf(doscmdrc,"DSZLOG=E:\\node%d\\PROTOCOL.LOG\n",cfg.node_num);
fprintf(doscmdrc,"SBBSNODE=D:\\\n");
fprintf(doscmdrc,"SBBSCTRL=E:\\ctrl\\\n");
fprintf(doscmdrc,"SBBSDATA=E:\\data\\\n");
fprintf(doscmdrc,"SBBSEXEC=E:\\exec\\\n");
fprintf(doscmdrc,"SBBSNNUM=%d\n",cfg.node_num);
fclose(doscmdrc);
SAFECOPY(str,fullcmdline);
sprintf(fullcmdline,"%s -F %s",startup->dosemu_path,str);
#else
bprintf("\r\nExternal DOS programs are not yet supported in \r\n%s\r\n"
,VERSION_NOTICE);
return(-1);
if(!(mode&EX_INR) && input_thread_running)

rswindell
committed
pthread_mutex_lock(&input_thread_mutex);
if((mode&EX_INR) && (mode&EX_OUTR)) {
struct winsize winsize;
struct termios term;
memset(&term,0,sizeof(term));
cfsetispeed(&term,B19200);
cfsetospeed(&term,B19200);
if(mode&EX_BIN)
cfmakeraw(&term);
else {
term.c_iflag = TTYDEF_IFLAG;
term.c_oflag = TTYDEF_OFLAG;
term.c_lflag = TTYDEF_LFLAG;
term.c_cflag = TTYDEF_CFLAG;
memcpy(term.c_cc,ttydefchars,sizeof(term.c_cc));
winsize.ws_row=rows;
// #warning Currently cols are forced to 80 apparently TODO
winsize.ws_col=80;
if((pid=forkpty(&in_pipe[1],NULL,&term,&winsize))==-1) {
pthread_mutex_unlock(&input_thread_mutex);
errormsg(WHERE,ERR_EXEC,fullcmdline,0);

rswindell
committed
return(-1);
}
out_pipe[0]=in_pipe[1];
}
else {
if(mode&EX_INR)
if(pipe(in_pipe)!=0) {
errormsg(WHERE,ERR_CREATE,"in_pipe",0);
return(-1);
}
if(mode&EX_OUTR)
if(pipe(out_pipe)!=0) {
errormsg(WHERE,ERR_CREATE,"out_pipe",0);
return(-1);
}
if((pid=FORK())==-1) {
pthread_mutex_unlock(&input_thread_mutex);
errormsg(WHERE,ERR_EXEC,fullcmdline,0);

rswindell
committed
return(-1);
}
}
if(pid==0) { /* child process */
sigset_t sigs;
sigfillset(&sigs);
sigprocmask(SIG_UNBLOCK,&sigs,NULL);
if(!(mode&EX_BIN)) {
static char term_env[256];
if(useron.misc&ANSI)
sprintf(term_env,"TERM=%s",startup->xtrn_term_ansi);
else
sprintf(term_env,"TERM=%s",startup->xtrn_term_dumb);
putenv(term_env);
}
#ifdef __FreeBSD__
if(!native)
chdir(cfg.node_dir);
else
#endif
if(startup_dir!=NULL && startup_dir[0])
chdir(startup_dir);
if(mode&EX_SH || strcspn(fullcmdline,"<>|;\"")!=strlen(fullcmdline)) {
argv[0]=comspec;
argv[1]="-c";
argv[2]=fullcmdline;
argv[3]=NULL;
} else {
argv[0]=fullcmdline; /* point to the beginning of the string */
for(i=0;fullcmdline[i] && argc<MAX_ARGS;i++) /* Break up command line */
if(fullcmdline[i]==SP) {
fullcmdline[i]=0; /* insert nulls */
argv[argc++]=fullcmdline+i+1; /* point to the beginning of the next arg */
}
argv[argc]=NULL;
}
if(mode&EX_INR && !(mode&EX_OUTR)) {

rswindell
committed
close(in_pipe[1]); /* close write-end of pipe */
dup2(in_pipe[0],0); /* redirect stdin */
close(in_pipe[0]); /* close excess file descriptor */
}

rswindell
committed
if(mode&EX_OUTR && !(mode&EX_INR)) {

rswindell
committed
close(out_pipe[0]); /* close read-end of pipe */
dup2(out_pipe[1],1); /* stdout */
dup2(out_pipe[1],2); /* stderr */
close(out_pipe[1]); /* close excess file descriptor */

rswindell
committed
}
if(mode&EX_BG) /* background execution, detach child */
{
lprintf("Detaching external process");
daemon(TRUE,FALSE);
}
sprintf(str,"!ERROR %d executing %s",errno,argv[0]);
errorlog(str);
_exit(-1); /* should never get here */
lprintf("Node %d executing external: %s",cfg.node_num,fullcmdline);

rswindell
committed
if(mode&EX_OUTR) {
if(!(mode&EX_INR))
close(out_pipe[1]); /* close write-end of pipe */
while(!terminated) {

rswindell
committed
if(waitpid(pid, &i, WNOHANG)!=0) /* child exited */
break;
if(mode&EX_CHKTIME)
gettimeleft();

rswindell
committed
if(!online && !(mode&EX_OFFLINE)) {
sprintf(str,"%s hung-up in external program",useron.alias);
logline("X!",str);
break;
}

rswindell
committed
/* Input */
if(mode&EX_INR && RingBufFull(&inbuf)) {
if((wr=RingBufRead(&inbuf,buf,sizeof(buf)))!=0)
write(in_pipe[1],buf,wr);
}
/* Output */
FD_ZERO(&ibits);
FD_SET(out_pipe[0],&ibits);
timeout.tv_sec=0;
timeout.tv_usec=1000;
if(!select(out_pipe[0]+1,&ibits,NULL,NULL,&timeout)) {
continue;
}
avail=RingBufFree(&outbuf)/2; // Leave room for wwiv/telnet expansion
if(avail==0) {
lprintf("Node %d !output buffer full (%u bytes)"
,cfg.node_num,RingBufFull(&outbuf));
YIELD();
continue;
}
rd=avail;
if(rd>(int)sizeof(buf))
rd=sizeof(buf);

rswindell
committed
continue;

rswindell
committed
if(mode&EX_BIN) /* telnet IAC expansion */
bp=telnet_expand(buf, rd, output_buf, output_len);

rswindell
committed
else /* LF to CRLF expansion */
bp=lf_expand(buf, rd, output_buf, output_len);
/* Did expansion overrun the output buffer? */
if(output_len>sizeof(output_buf)) {
errorlog("OUTPUT_BUF OVERRUN");
output_len=sizeof(output_buf);
}

rswindell
committed
/* Does expanded size fit in the ring buffer? */
if(output_len>RingBufFree(&outbuf)) {
errorlog("output buffer overflow");
output_len=RingBufFree(&outbuf);

rswindell
committed
RingBufWrite(&outbuf, bp, output_len);
}
if(waitpid(pid, &i, WNOHANG)==0) { // Child still running?
kill(pid, SIGHUP); // Tell child user has hung up
time_t start=time(NULL); // Wait up to 10 seconds
while(time(NULL)-start<10) { // for child to terminate
if(waitpid(pid, &i, WNOHANG)!=0)
break;
mswait(500);
}
if(waitpid(pid, &i, WNOHANG)==0) // Child still running?
kill(pid, SIGKILL); // terminate child process
}
/* close unneeded descriptors */
if(mode&EX_INR)
close(in_pipe[1]);
close(out_pipe[0]);

rswindell
committed
}

rswindell
committed
waitpid(pid, &i, 0); /* Wait for child to terminate */
if(!(mode&EX_OFFLINE)) { /* !off-line execution */
curatr=~0; // Can't guarantee current attributes
attr(LIGHTGRAY); // Force to "normal"
rio_abortable=rio_abortable_save; // Restore abortable state
/* Got back to Text/NVT mode */
if(telnet_mode&TELNET_MODE_BIN_RX) {
send_telnet_cmd(TELNET_DONT,TELNET_BINARY);
telnet_mode&=~TELNET_MODE_BIN_RX;
}
pthread_mutex_unlock(&input_thread_mutex);
return(WEXITSTATUS(i));
}
#endif /* !WIN32 */
uint fakeriobp=0xffff;
/*****************************************************************************/
/* Returns command line generated from instr with %c replacments */
/*****************************************************************************/
char* sbbs_t::cmdstr(char *instr, char *fpath, char *fspec, char *outstr)
{
char str[256],*cmd;
int i,j,len;
if(outstr==NULL)
cmd=cmdstr_output;
else
cmd=outstr;
len=strlen(instr);
for(i=j=0;i<len && j<(int)sizeof(cmdstr_output);i++) {
if(instr[i]=='%') {
i++;
cmd[j]=0;
char ch=instr[i];
if(isalpha(ch))
ch=toupper(ch);
switch(ch) {
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
case 'A': /* User alias */
strcat(cmd,useron.alias);
break;
case 'B': /* Baud (DTE) Rate */
strcat(cmd,ultoa(dte_rate,str,10));
break;
case 'C': /* Connect Description */
strcat(cmd,connection);
break;
case 'D': /* Connect (DCE) Rate */
strcat(cmd,ultoa((ulong)cur_rate,str,10));
break;
case 'E': /* Estimated Rate */
strcat(cmd,ultoa((ulong)cur_cps*10,str,10));
break;
case 'F': /* File path */
strcat(cmd,fpath);
break;
case 'G': /* Temp directory */
strcat(cmd,cfg.temp_dir);
break;
case 'H': /* Port Handle or Hardware Flow Control */
#if defined(__unix__)
strcat(cmd,ultoa(client_socket,str,10));
#else
strcat(cmd,ultoa(client_socket_dup,str,10));
#endif
break;
case 'I': /* UART IRQ Line */
strcat(cmd,ultoa(cfg.com_irq,str,10));
break;
case 'J':
strcat(cmd,cfg.data_dir);
break;
case 'K':
strcat(cmd,cfg.ctrl_dir);
break;
case 'L': /* Lines per message */
strcat(cmd,ultoa(cfg.level_linespermsg[useron.level],str,10));
break;
case 'M': /* Minutes (credits) for user */
strcat(cmd,ultoa(useron.min,str,10));
break;
case 'N': /* Node Directory (same as SBBSNODE environment var) */
strcat(cmd,cfg.node_dir);
break;
case 'O': /* SysOp */
strcat(cmd,cfg.sys_op);
break;
case 'P': /* COM Port */
strcat(cmd,ultoa(online==ON_LOCAL ? 0:cfg.com_port,str,10));
break;
case 'Q': /* QWK ID */
strcat(cmd,cfg.sys_id);
break;
case 'R': /* Rows */
break;
case 'S': /* File Spec */
strcat(cmd,fspec);
break;
case 'T': /* Time left in seconds */
gettimeleft();
strcat(cmd,ultoa(timeleft,str,10));
break;
case 'U': /* UART I/O Address (in hex) */
strcat(cmd,ultoa(cfg.com_base,str,16));
break;
case 'V': /* Synchronet Version */
sprintf(str,"%s%c",VERSION,REVISION);
break;
case 'W': /* Time-slice API type (mswtype) */
#if 0 //ndef __FLAT__
#endif
break;
case 'X':
strcat(cmd,cfg.shell[useron.shell]->code);
break;
case '&': /* Address of msr */
sprintf(str,"%lu",(DWORD)&fakeriobp);
strcat(cmd,str);
break;
case 'Y':
strcat(cmd,comspec);
break;
case 'Z':
strcat(cmd,cfg.text_dir);
break;
case '~': /* DOS-compatible (8.3) filename */
#ifdef _WIN32
char sfpath[MAX_PATH+1];
SAFECOPY(sfpath,fpath);
GetShortPathName(fpath,sfpath,sizeof(sfpath));
strcat(cmd,sfpath);
#else
strcat(cmd,fpath);
#endif
break;
case '!': /* EXEC Directory */
strcat(cmd,cfg.exec_dir);
break;
case '#': /* Node number (same as SBBSNNUM environment var) */
sprintf(str,"%d",cfg.node_num);
strcat(cmd,str);
break;
case '*':
sprintf(str,"%03d",cfg.node_num);
strcat(cmd,str);
break;
case '$': /* Credits */
strcat(cmd,ultoa(useron.cdt+useron.freecdt,str,10));
break;
case '%': /* %% for percent sign */
strcat(cmd,"%");
break;
case '.': /* .exe for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
strcat(cmd,".exe");
#endif
break;
case '?': /* Platform */
#ifdef __OS2__
strcpy(str,"OS2");
#else
strcpy(str,PLATFORM_DESC);
#endif
strlwr(str);
strcat(cmd,str);
break;
default: /* unknown specification */
if(isdigit(instr[i])) {
sprintf(str,"%0*d",instr[i]&0xf,useron.number);
strcat(cmd,str); }
break; }
j=strlen(cmd); }
else
cmd[j++]=instr[i]; }
cmd[j]=0;
return(cmd);
}
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
/****************************************************************************/
/* Returns command line generated from instr with %c replacments */
/* This is the C-exported version */
/****************************************************************************/
extern "C" char* cmdstr(scfg_t* cfg, user_t* user, const char* instr, const char* fpath
,const char* fspec, char* cmd)
{
char str[256];
int i,j,len;
len=strlen(instr);
for(i=j=0;i<len && j<MAX_PATH;i++) {
if(instr[i]=='%') {
i++;
cmd[j]=0;
char ch=instr[i];
if(isalpha(ch))
ch=toupper(ch);
switch(ch) {
case 'A': /* User alias */
if(user!=NULL)
strcat(cmd,user->alias);
break;
case 'B': /* Baud (DTE) Rate */
break;
case 'C': /* Connect Description */
break;
case 'D': /* Connect (DCE) Rate */
break;
case 'E': /* Estimated Rate */
break;
case 'F': /* File path */
strcat(cmd,fpath);
break;
case 'G': /* Temp directory */
strcat(cmd,cfg->temp_dir);
break;
case 'H': /* Port Handle or Hardware Flow Control */
break;
case 'I': /* UART IRQ Line */
strcat(cmd,ultoa(cfg->com_irq,str,10));
break;
case 'J':
strcat(cmd,cfg->data_dir);
break;
case 'K':
strcat(cmd,cfg->ctrl_dir);
break;
case 'L': /* Lines per message */
if(user!=NULL)
strcat(cmd,ultoa(cfg->level_linespermsg[user->level],str,10));
break;
case 'M': /* Minutes (credits) for user */
if(user!=NULL)
strcat(cmd,ultoa(user->min,str,10));
break;
case 'N': /* Node Directory (same as SBBSNODE environment var) */
strcat(cmd,cfg->node_dir);
break;
case 'O': /* SysOp */
strcat(cmd,cfg->sys_op);
break;
case 'P': /* COM Port */
break;
case 'Q': /* QWK ID */
strcat(cmd,cfg->sys_id);
break;
case 'R': /* Rows */
if(user!=NULL)
strcat(cmd,ultoa(user->rows,str,10));
break;
case 'S': /* File Spec */
strcat(cmd,fspec);
break;
case 'T': /* Time left in seconds */
break;
case 'U': /* UART I/O Address (in hex) */
strcat(cmd,ultoa(cfg->com_base,str,16));
break;
case 'V': /* Synchronet Version */
sprintf(str,"%s%c",VERSION,REVISION);
break;
case 'W': /* Time-slice API type (mswtype) */
#if 0 //ndef __FLAT__
strcat(cmd,ultoa(mswtyp,str,10));
#endif
break;
case 'X':
if(user!=NULL)
strcat(cmd,cfg->shell[user->shell]->code);
break;
case '&': /* Address of msr */
sprintf(str,"%lu",(DWORD)&fakeriobp);
strcat(cmd,str);
break;
case 'Y':
break;
case 'Z':
strcat(cmd,cfg->text_dir);
break;
case '~': /* DOS-compatible (8.3) filename */
#ifdef _WIN32
char sfpath[MAX_PATH+1];
SAFECOPY(sfpath,fpath);
GetShortPathName(fpath,sfpath,sizeof(sfpath));
strcat(cmd,sfpath);
#else
strcat(cmd,fpath);
#endif
break;
case '!': /* EXEC Directory */
strcat(cmd,cfg->exec_dir);
break;
case '#': /* Node number (same as SBBSNNUM environment var) */
sprintf(str,"%d",cfg->node_num);
strcat(cmd,str);
break;
case '*':
sprintf(str,"%03d",cfg->node_num);
strcat(cmd,str);
break;
case '$': /* Credits */
if(user!=NULL)
strcat(cmd,ultoa(user->cdt+user->freecdt,str,10));
break;
case '%': /* %% for percent sign */
strcat(cmd,"%");
break;
case '.': /* .exe for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
strcat(cmd,".exe");
#endif
break;
case '?': /* Platform */
#ifdef __OS2__
strcpy(str,"OS2");
#else
strcpy(str,PLATFORM_DESC);
#endif
strlwr(str);
strcat(cmd,str);
break;
default: /* unknown specification */
if(isdigit(instr[i]) && user!=NULL) {
sprintf(str,"%0*d",instr[i]&0xf,user->number);
strcat(cmd,str);
}
break;
}
j=strlen(cmd);
}
else
cmd[j++]=instr[i];
}
cmd[j]=0;
return(cmd);
}