/******************************************************************************
* File: prgrun.c an addition to CircleMUD *
* Usage: Asynchronyously run unix programs from MUD *
* Author: Petr Vilim (Petr.Vilim@st.mff.cuni.cz) *
* *
*****************************************************************************/
/*
* WARNING 1: It works on Linux. It should work on all UNIXes but
* I didn't try it.
*
* WARNING 2: Use on your own risk :-)
*
* WARNING 3: Playing with this you can easily make some security hole,
* if you make some command modifying spefied file for example.
* Mud password travels through net uncrypted and can be easily
* caught (try tcpdump). So some hacker can log on mud with your
* char and can destroy all your files... (and can modify
* file ~/.klogin and I don't know what more)
* This is the reason why I didn't make command exec which
* calls function system with given parametrs.
*/
/*
* You haven't to give me a credit. If you want you can mail me.
*/
#include <sys/types.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include "conf.h"
#include "sysdep.h"
#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "interpreter.h"
#include "auction.h"
#include "db.h"
#define PQUEUE_LENGTH 20 /* Max. number of requests in queue */
#define MAX_ARGS 5 /* Max. number of arguments of program */
extern struct char_data *get_ch_by_id(long idnum);
extern struct descriptor_data *descriptor_list;
struct program_info {
char *args[MAX_ARGS+1];/* args[0] is name of program,
rest are arguments, after last argument
must be NULL */
char *input; /* Input sended to process */
long char_id; /* ID of character */
char *name; /* Name used when printing output */
int timeout; /* Timeout in seconds */
};
struct program_info program_queue[PQUEUE_LENGTH];
int pqueue_counter=0; /* Number of requests in queue */
int pid=-1; /* PID of runnig command, -1 if none */
int to[2], from[2]; /* file descriptors of pipes between processes */
time_t start_time; /* Time when process started, for timeout */
int had_output; /* Flag if running command already had an output */
FILE *process_in; /* Standart input for runned program */
/*
* get_ch_by_id : given an ID number, searches every descriptor for a
* character with that number and returns a pointer to it.
*/
void program_fork() { /* Start new command */
int i;
long flags;
pipe(to);
pipe(from);
start_time=time(0);
had_output=0;
pid=fork();
if (pid < 0) {
pid=-1; /* Some problem with fork */
return;
}
if (pid==0) { /* Child process */
dup2 (from[1], 1); /* Set from[1] as standart output */
close (from[0]);
close (from[1]);
dup2 (to[0], 0);
close (to[0]);
close (to[1]);
for (i=2; i<1000; i++)
close(i); /* Close all opened file descriptors
1000 should be enough I hope :-) */
execvp(program_queue[0].args[0], program_queue[0].args);
exit(0);
}
close(from[1]);
close(to[0]);
process_in=fdopen(to[1], "w");
setbuf(process_in, NULL);
if (program_queue[0].input) /* Send input to program if any */
fprintf(process_in, program_queue[0].input);
flags=fcntl(from[0], F_GETFL);
fcntl(from[0], F_SETFL, flags | O_NONBLOCK);
/* Set from[0] into no-blocking mode */
}
void program_done() {
int i;
if (pid!=-1) {
close(from[0]); /* Close process standart output */
fclose(process_in); /* Close process standart input */
waitpid(pid, NULL, 0); /* Wait for process termination */
for (i=0; i<MAX_ARGS+1; i++)
if (program_queue[0].args[i]) free(program_queue[0].args[i]);
else break;
free(program_queue[0].name);
if (program_queue[0].input) free(program_queue[0].input);
for (i=0; i<pqueue_counter; i++)
program_queue[i]=program_queue[i+1]; /* shift queue */
pqueue_counter--;
if (pqueue_counter)
program_fork(); /* Start next process */
else
pid=-1;
}
}
void process_program_output() {
int len;
struct char_data *ch;
char *c, *d;
if (pid==-1)
return;
len=read(from[0], buf, MAX_STRING_LENGTH);
if ((len==-1) && (errno==EAGAIN)) { /* No datas are available now */
if (time(0)<start_time+program_queue[0].timeout)
return;
else
sprintf(buf, "&15Killing &c%s&15 becouse of timeout.\r\n",
program_queue[0].name);
} else if (len < 0) { /* Error with read or timeout */
sprintf(buf, "&15Error with reading from &c%s&15, killed.",
program_queue[0].name);
} else if (len == 0) { /* EOF readed */
if (had_output)
buf[0]=0;
else
sprintf(buf, "&15No output from &13%s&15.\r\n", program_queue[0].name);
};
ch=get_ch_by_id(program_queue[0].char_id);
if (len<=0) {
kill(pid, 9); /* kill process with signal 9 if is still running */
program_done();
if ((ch) && (buf[0]))
send_to_char(buf, ch);
return;
}
had_output=1;
buf[len]='\0';
for (c=buf, d=buf1; *c; *d++=*c++)
if (*c=='\n') *d++='\r';
*d=0;
if (!ch) return; /* Player quited the game? */
send_to_char("&15Output from &13", ch);
send_to_char(program_queue[0].name, ch);
send_to_char("&15:&12\r\n", ch);
send_to_char(buf1, ch);
}
/* Following function add program into queue */
void add_program(struct program_info prg, struct char_data *ch)
{
prg.char_id=GET_IDNUM(ch);
if (pqueue_counter==PQUEUE_LENGTH) {
send_to_char("Sorry, there are too many requests now, try later.\r\n", ch);
return;
}
program_queue[pqueue_counter]=prg;
pqueue_counter++;
if (pqueue_counter==1) /* No process is running now so start new process */
program_fork();
}
/*
* get_ch_by_id : given an ID number, searches every descriptor for a
* character with that number and returns a pointer to it.
*/
/*
* swho calls program w.
* It shows who is logged on (not in mud, in unix)
* and what they are doing
*/
ACMD(do_swho) {
struct program_info swho;
swho.args[0]=strdup("w");
swho.args[1]=NULL;
swho.input=NULL;
swho.timeout=10;
swho.name=strdup("swho");
add_program(swho, ch);
}
/*
* slast calls program last.
* It shows last 10 login of specified user or last 10 logins
* if no user is specified.
*/
ACMD(do_slast) {
struct program_info slast;
slast.args[0]=strdup("last");
slast.args[1]=strdup("-10");
one_argument(argument, arg);
if (!*arg) /* No user specified */
slast.args[2]=NULL;
else
slast.args[2]=strdup(arg);
slast.args[3]=NULL;
slast.input=NULL;
slast.timeout=5;
slast.name=strdup("slast");
add_program(slast, ch);
}
/*
* grep is normal grep <pattern> <file>
* Useful is for example:
* grep ../syslog <player>
* or grep ../log/syslog.2 <player>
*/
ACMD(do_grep) {
struct program_info grep;
argument=one_argument(argument, arg);
skip_spaces(&argument);
grep.args[0]=strdup("grep");
/* Ignore cas, becouse one_argument function makes all characters lower. */
grep.args[1]=strdup("-i");
grep.args[2]=strdup(arg);
grep.args[3]=strdup(argument);
grep.args[4]=NULL;
grep.input=NULL;
grep.timeout=10;
grep.name=strdup("grep");
add_program(grep, ch);
}
/*
* sps calls program ps.
* I use for example sps aux, sps ux on Linux, but on other
* platforms it can have another parameters
*/
ACMD(do_sps) {
struct program_info sps;
skip_spaces(&argument);
sps.args[0]=strdup("ps");
sps.args[1]=strdup(argument);
sps.args[2]=NULL;
sps.input=NULL;
sps.timeout=1;
sps.name=strdup("sps");
add_program(sps, ch);
}
ACMD(do_showadd){
struct program_info showadd;
skip_spaces(&argument);
showadd.args[0]=strdup("showadd");
showadd.args[1]=strdup(argument);
showadd.args[2]=NULL;
showadd.input=NULL;
showadd.timeout=100;
showadd.name=strdup("showadd");
add_program(showadd, ch);
}
/*
void show(char *filename)
{
FILE *fl;
struct char_file_u player;
struct descriptor_data d;
int num = 0;
if (!(fl = fopen(filename, "r+"))) {
perror("error opening playerfile");
exit(1);
}
for (;;) {
fread(&player, sizeof(struct char_file_u), 1, fl);
if (feof(fl)) {
fclose(fl);
exit(1);
}
printf("%5d. ID: %5ld %-20s %-40s\n", ++num,
player.char_specials_saved.idnum, player.name,
player.host);
}
}
*/