/* Copyright (C) 1998-2003 XIAO, Gang of Universite de Nice - Sophia Antipolis
*
* This program 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.
*
* This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* line input / output / translation routines
* and error routines
*/
#include <utime.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "wims.h"
int is_class_module=0;
int trusted_module(void);
void phtml_put_base(char *fname,int cache);
void module_error(char msg[]);
void user_error(char msg[]);
void accessfile(char *content, char *type, char *s,...);
int remove_tree(char *p);
void output(char *s,...);
char *_getvar(char *vname);
int checkhost(char *hlist);
char *lastdata;
char lastdatafile[MAX_FNAME+1];
char *datacache[DATAFIELD_LIMIT];
int nextdatacache;
struct stat ftst;
char lastftest[MAX_FNAME+1];
int lastftype;
char outbuf[8192];
char *outptr;
/* These modules can execute private programs.
* adm/ modules are always trusted, so need no definition here.
*/
char *trusted_modules="";
/* bit 0: module is not trusted.
* bit 1: file in wimshome.
* bit 2: readdef or file in writable directory.
*/
int untrust=0; /* non-zero if user detrusts the module. */
int error_status=0;
char pidbuf[32];
void delete_pid(void);
/* Internal use only */
void _debug(char *s,...)
{
va_list vp;
char buf[MAX_LINELEN+1];
vsnprintf(buf,sizeof(buf),s,vp);
setvar("debug",buf); module_error("debug");
}
/* HTTP response header for non-processed CGI interface */
void nph_header(int code)
{
char *cstr;
switch(code) {
case 200: cstr="OK"; break;
case 301: cstr="Moved Permanently"; break;
case 302: cstr="Moved Temporarily"; break;
case 420: cstr="WIMS Nested Error"; break;
case 450: cstr="WIMS User Error"; break;
case 500: cstr="WIMS Internal Error"; break;
case 550: cstr="WIMS Module Error"; break;
default: cstr="ERROR"; break;
}
if(httpd_type
!=httpd_wims
) printf("Status: %d %s\r\n",code
,cstr
);
else {
char *p, tbuf[256];
mystrncpy
(tbuf
,ctime(&nowtime
),sizeof(tbuf
));
for(p
=tbuf
+strlen(tbuf
);p
>=tbuf
&& (*(p
-1)=='\n' || *(p
-1)=='\r'); p
--);
*p=0;
Date: %s\r\n\
Connection: close\r\n\
",code,cstr,tbuf);
}
}
void flushoutput(void)
{
int l2;
if(outptr<=outbuf) return;
l2=outptr-outbuf;
if(lastout_file!=-1) (void)write(lastout_file,outbuf,l2);
else fwrite(outbuf
,l2
,1,stdout
);
outptr=outbuf;
}
void internal_warn(char msg[])
{
char buf[1024];
snprintf(buf
,sizeof(buf
),"%s: %s\n",nowstr
,msg
);
accessfile(buf,"a","%s/internal_error.log",log_dir);
}
/* Internal error: panic and forget about requester. */
void internal_error(char msg[])
{
if(error_status<2) {
nph_header(500);
printf("Cache-Control: no-cache\nPragma: no-cache\r\n\
Content-type: text/plain\r\n\r\n\r\n\
WIMS panick! %s
\n%s
\n"
,msg
,strerror(errno
));
error_status=2; internal_warn(msg);
}
}
void tex_nospace(char *p)
{
char *p1, *p2;
char buf[MAX_LINELEN+1];
for(p1=buf,p2=p;*p2 && p1-buf<MAX_LINELEN-4;p2++) {
if(!isspace(*p2
)) {*p1
++=*p2
;continue;}
if(*(p1-1)=='\\' ||
(p1>buf && myisalnum(*(p1-1)) && myisalnum(*p2)))
*p1++=' ';
*p1++=*p2;
}
}
void nametoolong(char *p)
{
char buf[MAX_FNAME+17];
force_setvar("wims_error_data",buf);
module_error("file_name_too_long");
}
off_t ftest_size;
/* A simple front-end of stat(). */
int ftest(char *fname)
{
if(strcmp(fname
,lastftest
)==0) return lastftype
;
/* if(fname[0]=='/' || fname[0]=='.') fprintf(stderr,"ftest: %s\n",fname); */
mystrncpy(lastftest,fname,sizeof(lastftest));
if(stat(fname,&ftst)) return lastftype=-1;
ftest_size=ftst.st_size;
if(S_ISREG(ftst.st_mode)) {
if((ftst.st_mode&(S_IXUSR|S_IXGRP|S_IXOTH))!=0) return lastftype=is_exec;
else return lastftype=is_file;
}
if(S_ISDIR(ftst.st_mode)) return lastftype=is_dir;
if(S_ISFIFO(ftst.st_mode)) return lastftype=is_fifo;
if(S_ISSOCK(ftst.st_mode)) return lastftype=is_socket;
return lastftype=is_unknown;
}
char fnbuf[MAX_FNAME+1];
/* make a filename and check length */
char *mkfname(char buf[], char *s,...)
{
va_list vp;
char *p;
if(buf==NULL) p=fnbuf; else p=buf;
vsnprintf(p,MAX_FNAME,s,vp);
if(strlen(p
)>=MAX_FNAME
-1) nametoolong
(p
);
return p;
}
void sysmask_trigger(char *s)
{
char buf[MAX_FNAME+1];
struct stat st;
mkfname(buf,"%s/%s",sysmask_trigger_dir,s);
stat(buf,&st);
}
/* read-in a file into buffer. Use open() and read().
* Return buffer address which will be malloc'ed if buf=NULL. */
char *readfile(char *fname, char buf[], long int buflen)
{
int fd, t, st;
long int l, lc;
char *bf;
t=0; if(buf) buf[0]=0;
st=ftest(fname); if(st!=is_file) {
if(st==is_exec) { /* refuse to open executable file */
setvar("executable",fname); module_error("executable");
}
return NULL;
}
l=ftst.st_size; if(l<0) return NULL;
if(l>=buflen) {
if(buflen<MAX_LINELEN) l=buflen-1;
else {
setvar(error_data_string,fname); module_error("file_too_long");
}
else user_error("cmd_output_too_long");
}
}
fd=open(fname,O_RDONLY); if(fd==-1) return NULL;
if(buf==NULL) bf=xmalloc(l+8); else {bf=buf;if(l==0) {t=1; l=buflen-1;}}
lc=read(fd,bf,l); close(fd);
if(lc<0 || lc>l || (lc!=l && t==0))
{if(buf
==NULL
) free(bf
); else buf
[0]=0; return NULL
;}
bf[lc]=0; _tolinux(bf); return bf;
}
/* Get a line in a stored working file.
* Buffer length is always MAX_LINELEN. */
int wgetline(char buf[], size_t buflen, WORKING_FILE *f)
{
int i,j; unsigned int n;
i=f->linepointer; buf[0]=0;
if(i>=f->linecnt || f->textbuf==NULL) return EOF;
n=f->lines[i].llen;
if(n>=buflen) n=buflen-1;
if(n
>0) memmove(buf
,f
->lines
[i
].
address,n
); buf
[n
]=0;
for(j=i+1;j<f->linecnt && f->lines[j].isstart==0;j++);
f->l=i; f->linepointer=j;
if(j>=f->linecnt && n==0) return EOF; else return n;
}
int get_cached_file(char *name)
{
int i,l,flag;
l
=strlen(name
); if(l
>=127) return -1;
if(strncmp(module_prefix
,module_dir
,strlen(module_dir
))!=0) flag
=8; else flag
=0;
for(i=0;i<mcachecnt;i++) {
if(flag
==mcache
[i
].
nocache && strcmp(name
,mcache
[i
].
name)==0) {
memmove(&m_file
,mcache
+i
,sizeof(WORKING_FILE
));
m_file.nocache|=4;
return i;
}
}
return -1;
}
/* Open a work file. Returns 0 if OK. */
int open_working_file(WORKING_FILE *f, char *fname)
{
char *p, *q;
void *vp;
int i,j,k,laststart,lc[LINE_LIMIT];
f->linecnt=f->linepointer=0;
f->for_idx=f->nocache=0;
f->l=-1; f->lines=NULL;
f->textbuf=readfile(fname,NULL,WORKFILE_LIMIT);
if(f->textbuf==NULL) return -1;
p=f->textbuf; if(*p) {
for(i
=0,p
--; i
<LINE_LIMIT
-1 && p
!=NULL
;p
=strchr(p
,'\n')) {
if(i>0) *p=0;
lc[i++]=(++p-f->textbuf);
}
}
else i=0;
if(i>=LINE_LIMIT-1) module_error("file_too_long");
lc
[i
]=lc
[i
-1]+strlen(f
->textbuf
+lc
[i
-1])+1;
f->textbuf[lc[i]]=0;
f->linecnt=i; laststart=0;
f->for_stack=xmalloc(MAX_FOR_LEVEL*sizeof(FOR_STACK)+(i+1)*sizeof(LINE_STRUCT));
vp=f->for_stack+MAX_FOR_LEVEL; f->lines=vp;
for(j=0;j<i;j++) {
f->lines[j].address=p=(f->textbuf)+lc[j];
f->lines[j].llen=lc[j+1]-lc[j]-1;
(f->lines[j]).execcode=(f->lines[j]).varcode=-1;
if(j==0) goto normal;
q=f->lines[laststart].address+f->lines[laststart].llen-1;
if(q>=f->textbuf && *q=='\\') {
f->lines[laststart].llen+=f->lines[j].llen; *q='\n';
f->lines[j].isstart=f->lines[j].llen=0;
}
else {
normal: for(q=p;myislspace(*q);q++);
f->lines[j].isstart=1; laststart=j; k=0;
switch(*q) {
/* isstart: bit 1 = start.
* bit 2 = exec
* bit 3 (4) = label
* bit 4 (8) = hard comment (!!)
* bit 5 (16) = soft comment (#) */
case exec_prefix_char: {
if(myisalpha(q[1])) f->lines[j].isstart=3;
else f->lines[j].isstart=9;
k=1; break;
}
case label_prefix_char: {f->lines[j].isstart=5; k=1; break;}
case comment_prefix_char: {f->lines[j].isstart=17; break;}
default: {f->lines[j].isstart=1; break;}
}
if(k && q>p) {f->lines[j].address=q;f->lines[j].llen-=q-p;}
}
}
f->lines[i].isstart=1; f->lines[i].llen=0;
f->lines[i].address=(f->textbuf)+lc[i];
mfilecnt++; return 0;
}
/* close an earlier opened working file */
void close_working_file(WORKING_FILE *f, int cache)
{
f->linepointer=f->l=0;
if(cache && untrust==0 && mcachecnt<MAX_MCACHE && (f->nocache&7)==0) {
memmove(mcache
+mcachecnt
,f
,sizeof(WORKING_FILE
));
mcachecnt++;
}
else if((f->nocache&4)==0) {
if(f
->for_stack
!=NULL
) free(f
->for_stack
);
if(f
->textbuf
!=NULL
) free(f
->textbuf
);
}
f->for_stack=NULL; f->textbuf=NULL; f->linecnt=0;
}
void free_mcache(void)
{
int i;
untrust=0;
for(i=mcachecnt-1;i>=0;i--) close_working_file(mcache+i,0);
mcachecnt=0;
}
void cleantmpdir(void);
void user_error_log(char msg[]);
void nested_error(char msg[])
{
fprintf(stderr
,"\nNested error! %s\n",msg
);
nph_header(420);
printf("\r\n\r\nWIMS error processing aborted on nested error.\r\n\r\n%s\r\n",msg
);
}
/* Send an error message to requester and exit.
* This is for user errors, language-sensitive. */
void user_error(char msg[])
{
char erfname[MAX_FNAME+1];
if(error_status) nested_error(msg);
error_status=1;
mkfname(erfname,"%s.%s",user_error_msg_file,lang);
if(ftest(erfname)!=is_file) internal_error("user_error(): error message file not found.\n\
Bad installation.");
force_setvar("wims_user_error",msg);
if(strcmp(msg
,"threshold")!=0) user_error_log
(msg
);
if(lastout_file!=-1 && outputing) {
flushoutput(); close(lastout_file); lastout_file=-1;
}
nph_header(450);
phtml_put_base(erfname,0);
if(strcmp(msg
,"double_click")!=0) delete_pid
();
else {
cleantmpdir(); flushlog();
}
}
void module_error_log(char msg[]);
/* Messages for module errors. English only. */
/* This is really rudimentary for the time being. */
void module_error(char msg[])
{
int send=0;
char *p;
WORKING_FILE mf;
if(error_status) nested_error(msg);
error_status=1; untrust=0;
module_error_log(msg);
nph_header(550);
printf("Server: %s %s (%s)\r\n", SHORTSWNAME
,wims_version
,LONGSWNAME
);
p=getvar("wims_main_font");
if(p
!=NULL
&& *p
!=0) printf("Content-type: text/plain; charset=%s\r\n\r\n",p
);
else printf("Content-type: text/plain\r\n\r\n");
p=getvar(ro_name[ro_module]); if(p==NULL) p="???";
printf("ERROR.\n\nwims has detected an error in the module '%s'.",p
);
if(m_file.
l>=0) printf("\n\nIn file '%s', line %d:",
m_file.name,m_file.l+1);
if(open_working_file(&mf,mkfname(NULL,"%s.%s",module_error_msg_file,lang))!=0)
internal_error("module_error(): error message file not found.");
while(wgetline(tmplbuf,MAX_LINELEN,&mf)!=EOF) {
if(tmplbuf[0]!=tag_prefix_char) {
if(send
) {substit
(tmplbuf
); puts(tmplbuf
);}
continue;
}
strip_trailing_spaces(tmplbuf);
if(tmplbuf
[1]==0 || strcmp(msg
,tmplbuf
+1)==0) send
=1;
else send=0;
}
close_working_file(&mf,0);
outptr
=outbuf
; delete_pid
(); exit(1);
}
/* Output kernel routine */
void _output_(char *s)
{
int l,l2;
l
=strlen(s
); output_length
+=l
;
if(output_length>=OUTPUT_LENGTH_LIMIT) {
module_error("output_too_long"); return;
}
l2=sizeof(outbuf)-(outptr-outbuf);
put: if(l<=l2) {
memmove(outptr
,s
,l
); outptr
+=l
; return;
}
memmove(outptr
,s
,l2
); s
+=l2
; l
-=l2
;
if(lastout_file!=-1) (void)write(lastout_file,outbuf,sizeof(outbuf));
else fwrite(outbuf
,sizeof(outbuf
),1,stdout
);
outptr=outbuf; l2=sizeof(outbuf); goto put;
}
/* Output with no var. */
void output0(char buf[])
{
char *p1, *p2, *ps;
int dynsave;
if(backslash_insmath) {
ps=buf; dynsave=instex_usedynamic;
p2=find_matching(p1+2,')');
if(p2==NULL) break;
if(p1>buf && *(p1-1)=='\\') continue;
*p1=0; if(*(p2-1)=='\\') *(p2-1)=0; *p2++=0; _output_(ps); ps=p2;
instex_usedynamic=1; insmath(p1+2);
}
if(*ps) _output_(ps); instex_usedynamic=dynsave;
}
else _output_(buf);
}
/* Output routine */
void output(char *s,...)
{
va_list vp;
char buf[4*MAX_LINELEN+1];
vsnprintf(buf,sizeof(buf),s,vp);
output0(buf);
}
/* read in tmpf in tmp directory, and places in p.
* Maximal length: MAX_LINELEN. */
void read_tmp_file(char *p, const char *fname)
{
char *name, *pp;
name=mkfname(NULL,"%s/%s",tmp_dir,fname);
if(!exec_is_module || !outputing || !direct_exec
|| strcmp(fname
,"exec.out")!=0) {
readfile(name,p,MAX_LINELEN);
pp=getvar("wims_exec_dollar_double");
if(pp
&& strcmp(pp
,"yes")==0) {
string_modify(p,pp,pp+1,"$$");
}
}
else {
char *s;
s=readfile(name,NULL,OUTPUT_LENGTH_LIMIT);
if(s==NULL) {*p=0; return;}
if(memcmp(s
,"Error: ", strlen("Error: "))==0) mystrncpy
(p
,s
,MAX_LINELEN
);
if(output_length>=OUTPUT_LENGTH_LIMIT) module_error("output_too_long");
else _output_(s);
chmod(name,S_IRUSR|S_IWUSR);
}
}
/* verify whether the module is trusted.
* Returns 1 if yes, 0 if no. -1 for error. */
int trusted_module(void)
{
char *modname, *w, buf[MAX_LINELEN+1];
int i,n;
static int _trusted=-1; /* avoid repeated computations */
if(untrust&255) return 0;
if(_trusted>=0) return _trusted;
modname=getvar(ro_name[ro_module]);
if(modname==NULL || *modname==0) return 0;
strcmp(modname
,home_module
)==0 ||
tr:
is_class_module=1;
setenv("trusted_module","yes",1);
return _trusted=1;
}
n=wordnum(trusted_modules); for(i=0;i<n;i++) {
w=fnd_word(trusted_modules,i+1,buf);
if(strcmp(w
,modname
)==0) goto tr
;
}
return _trusted=0;
}
/* file should be in the module directory, but
* it may also be somewhere else.
* buf[] requires MAX_FNAME+1 length.
* Returns 0 if found. */
int find_module_file(char *fname, char buf[], int mode)
{
char *p, dtest[32];
fname=find_word_start(fname);
if(*fname==0) return -1;
/* Name checking: no directory backtracing. */
if(strstr(fname
,parent_dir_string
)!=NULL
) {
setvar(error_data_string,fname); module_error("illegal_fname");
return -1;
}
p
=strchr(fname
,'/'); if(p
==NULL
|| p
>fname
+10) goto openit
;
memmove(dtest
,fname
,p
-fname
); dtest
[p
-fname
]=0;
if(strcmp(dtest
,"datamodule")==0) {
mkfname(buf,"modules/data%s",p); goto lastopen;
}
if(strcmp(dtest
,"wimshome")==0 && trusted_module
()) {
mkfname(buf,"%s%s",getvar("wims_home"),p); goto lastopen;
}
if(strcmp(dtest
,"writable")==0) {
mkfname(buf,"%s/%s",tmp_dir,p+1);
}
else {
mkfname(buf,"w/%s/%s",module_prefix,p+1);
}
untrust|=4;
}
else {
openit: mkfname(buf,"%s/%s",module_prefix,fname);
}
if(mode) return 0;
if(ftest(buf)!=is_file) {
if(lastftype==is_exec) {
isexec:
setvar("executable",fname); module_error("executable");
return -1;
}
(!trusted_module() || is_class_module)) return -1;
mkfname(buf,"scripts/%s",fname);
lastopen:
if(mode) return 0;
if(ftest(buf)!=is_file) {
if(lastftype==is_exec) goto isexec;
else return -1;
}
}
return 0;
}
/* check whether a file is user-submitted */
/* This is deprecated because of the wimshome/ method. */
/* int user_file(char *name) {
if(name[0]=='/' || name[0]=='.' ||
strstr(name,"classes/")!=NULL ||
strstr(name,"forums/")!=NULL ||
strstr(name,"sessions/")!=NULL ||
strstr(name,"doc/")!=NULL) return 1; else return 0;
} */
/* returns 1 if violation */
int datafile_check(char *name) {
if((untrust&255)==0) return 0;
if(strncmp(name
,"datamodule/",strlen("datamodule/"))==0) return 0;
return 1;
}
/* returns 0 if success */
void readdatafile(char *name)
{
char *pp;
if(strcmp(name
,lastdatafile
)==0) return;
lastdata[0]=0; readfile(name,lastdata,WORKFILE_LIMIT);
mystrncpy(lastdatafile,name,sizeof(lastdatafile));
datacache[0]=lastdata; nextdatacache=1;
if(lastdata[0]==tag_string[1]) {
datacache[1]=lastdata; nextdatacache++;
}
pp
=strstr(lastdata
,tag_string
);
if(pp) datacache[nextdatacache]=pp;
else datacache
[nextdatacache
]=lastdata
+strlen(lastdata
);
}
char *_nextdata(char *p)
{
char *pp;
if(!*p) return p;
if(pp) return pp;
}
/* datafile structure: number of records.
* tag=1 if direct access */
unsigned int datafile_recordnum(char *p)
{
char nbuf[MAX_LINELEN+1], *pp;
int i, t, ret;
t=untrust; ret=0;
if(direct_datafile) mystrncpy(nbuf,p,sizeof(nbuf));
else if(datafile_check(p)!=0 || find_module_file(p,nbuf,0)) goto ret;
readdatafile(nbuf);
for(i=nextdatacache, pp=datacache[i]; *pp;) {
pp=_nextdata(pp+1); i++;
if(i<DATAFIELD_LIMIT) {
datacache[i]=pp; nextdatacache=i;
}
}
ret=i-1;
ret:
untrust=t;
return ret;
}
/* datafile structure: find record n, starting from 1 */
char *datafile_fnd_record(char *p, int n, char bf[])
{
char nbuf[MAX_LINELEN+1], *pp, *p2;
int i, t;
bf[0]=0; t=untrust;
if(n<0) goto ret;
if(direct_datafile) mystrncpy(nbuf,p,sizeof(nbuf));
else if(datafile_check(p)!=0 || find_module_file(p,nbuf,0)) goto ret;
readdatafile(nbuf); if(*lastdata==0) goto ret;
if(n>nextdatacache) {
for(i=nextdatacache, pp=datacache[i]; i<n && *pp;) {
pp=_nextdata(pp+1); i++;
if(i<DATAFIELD_LIMIT) {
datacache[i]=pp; nextdatacache=i;
}
}
}
else pp=datacache[n];
if(!*pp) goto ret;
if(n
>1 || (n
==1 && *pp
!=tag_string
[1])) pp
+=strlen(tag_string
);
else if(n
==1) pp
+=strlen(tag_string
)-1;
if(n<nextdatacache) p2=datacache[n+1];
else {
if(n<DATAFIELD_LIMIT-1 && n==nextdatacache) {
nextdatacache++; datacache[nextdatacache]=p2;
}
}
if(p2-pp>=MAX_LINELEN) p2=pp+MAX_LINELEN-1;
if(p2<pp) p2=pp;
ret:
untrust=t; return bf;
}
char hex2char(char c1, char c2)
{
char tbuf[16];
if(c1<'0' || c1>'f' || c2<'0' || c2>'f') {
invl:
snprintf(tbuf
,sizeof(tbuf
),"%%%c%c",c1
,c2
);
setvar(error_data_string,tbuf);
user_error("invalid_char_in_query_string");
}
if(c1>'9' && c1<'A') goto invl;
if(c2>'9' && c2<'A') goto invl;
if(c1>'F' || c2>'F') goto invl;
if(c1>='A') c1=c1-'A'+'9'+1;
if(c2>='A') c2=c2-'A'+'9'+1;
return (c1-'0')*16+c2-'0';
}
/* Converts back http escaped chars, slight. Does not check buffer length.
* Returns converted string length. */
int _http2env(char outs[], char ins[])
{
int j,k,l;
for(j
=k
=0;j
<l
&& !isspace(ins
[j
]);j
++,k
++) {
if(isspace(ins
[j
])) { /* skip space characters in query string */
k--;continue;
}
if(ins[j]=='%') {
/* skip Carriage-Return. */
if(ins[j+1]=='0' && (ins[j+2]=='d' || ins[j+2]=='D')) {
j+=2; k--; continue;
}
outs[k]=hex2char(ins[j+1],ins[j+2]);
j+=2; continue;
}
outs[k]=ins[j];
}
outs[k]=0;
return k;
}
/* Converts back http escaped chars. Does not check buffer length.
* Returns converted string length. */
int http2env(char outs[], char ins[])
{
int j,k,l;
for(j
=k
=0;j
<l
&& !isspace(ins
[j
]);j
++,k
++) {
if(isspace(ins
[j
])) { /* skip space characters in query string */
k--;continue;
}
if(ins[j]=='%') {
/* skip Carriage-Return. */
if(ins[j+1]=='0' && (ins[j+2]=='d' || ins[j+2]=='D')) {
j+=2; k--; continue;
}
outs[k]=hex2char(ins[j+1],ins[j+2]);
j+=2; continue;
}
if(ins[j]=='+') {
outs[k]=' '; continue;
}
if(ins[j]=='?' || ins[j]=='&') {
outs[k]=0; continue;
}
outs[k]=ins[j];
}
outs[k]=0;
return k;
}
/* translate a string to http querystring style.
* '&' is not translated.
* Buffer p must be at least MAX_LINELEN. */
void tohttpquery(char *p)
{
char trlist[]=" ()[]{}+-*^|/\"\'!:;,<>\n";
char *pp;
for(pp=p;*pp;pp++) {
if(*pp==' ') {
*pp='+'; continue;
}
if(strchr(trlist
,*pp
)==NULL
) continue;
if(*pp=='+' && pp>p && *(pp-1)=='&') continue;
if(pp>p && *(pp-1)=='\\') {
ovlstrcpy(pp-1,pp);pp--;continue;
}
if(*pp=='\n') {
string_modify(p,pp,pp+1,"%%0D%%0A");pp+=5;
}
else {
string_modify(p,pp,pp+1,"%%%02X",*pp);pp+=2;
}
}
}
/* substitute backslash parameters. Internal use only. */
void slashsubst(char *p)
{
char *p1, *p2, *pt, *pp, namebuf[128];
int n;
p1++; for(p2=p1; myisalnum(*p2) || *p2=='_'; p2++);
if(p2<=p1 || p2>p1+100) continue;
memmove(namebuf
+n
,p1
,p2
-p1
); namebuf
[p2
-p1
+n
]=0;
pt=_getvar(namebuf); if(pt==NULL) continue;
if(*p2=='[' && (pp=find_matching(p2+1,']'))!=NULL) {
string_modify(p,pp+1,pp+1,")");
string_modify(p,p1-1,p1,"$(%s",mathfont_prefix);
}
else string_modify(p,p1-1,p1,"$%s",mathfont_prefix);
}
}
/* two alarm handlers. */
void alarm1(int s)
{
if(killpid>0 && kill(killpid,SIGKILL)) module_error("timeup");
killpid=0;
}
void alarm2(int s)
{
cleantmpdir();
alarm1(s); module_error("timeup");
}
void finalalarm(void)
{
time_t curr;
if(curr>=limtime) alarm2(SIGALRM);
errno=0;
if(signal(SIGALRM,alarm2)==SIG_ERR)
alarm(limtime-curr+1);
}
void initalarm(void)
{
limtimex=nowtime+4*rlimit_cpu/3;
limtime=limtimex+2; finalalarm();
}
void forkalarm(void)
{
time_t curr;
if(curr>=limtimex) {alarm1(SIGALRM); return;}
if(signal(SIGALRM,alarm1)==SIG_ERR)
alarm(limtimex-curr+1);
}
/* create pid tag */
void create_pid(void)
{
char buf[MAX_FNAME+1], pbuf[256], obuf[MAX_FNAME+1];
struct stat dst;
struct utimbuf ub;
if(robot_access || *session_prefix==0) return;
if(cmd_type==cmd_getframe) return;
mkfname(buf,"%s/.pid",s2_prefix);
/* another process running? */
if(readfile(buf,pbuf,sizeof(pbuf))!=NULL) {
mkfname(obuf,"/proc/%s",pbuf);
if(stat(obuf,&dst)==0) user_error("double_click");
}
snprintf(pidbuf
,sizeof(pidbuf
),"%u",getpid
());
accessfile(pidbuf,"w","%s",buf);
/* Touch session time */
if(strstr(session_prefix
,"sessions/")==NULL
) return;
ub.actime=ub.modtime=nowtime;
utime(session_prefix,&ub);
if(strchr(session_prefix
,'_')!=NULL
) { /* touch parent too */
char sbuf[MAX_FNAME+1], *p;
mystrncpy(sbuf,session_prefix,sizeof(sbuf));
p
=strchr(sbuf
,'_'); if(p
!=NULL
) *p
=0;
utime(sbuf,&ub);
}
}
struct mxtab mxtab[MAX_MULTIEXEC];
int mxno=0;
int execredirected(char *cmdf, char *inf, char *outf, char *errf, char *arg[])
{
pid_t pid;
int status, t;
if(robot_access) return 0;
if(errf!=NULL)
accessfile("No time left to execute subprograms.\n","w","%s",errf);
return -100;
}
lastdatafile[0]=lastftest[0]=0;
fflush(NULL
); /* flush all output streams before forking
* otherwise they will be doubled */
pid=fork(); if(pid==-1) return -1;
if(!pid) { /* child */
char buf[MAX_LINELEN+1]; int k;
(void)nice(10); /* lower priority for children */
if(is_multiexec) {
dup2(mxtab[multiexec_index].pipe_stdin[0],0);
dup2(mxtab[multiexec_index].pipe_stdout[1],1);
dup2(mxtab[multiexec_index].pipe_stderr[1],2);
}
else {
if(inf
!=NULL
) (void)freopen(inf
,"r",stdin
);
if(outf
!=NULL
) (void)freopen(outf
,"w",stdout
);
if(errf
!=NULL
) (void)freopen(errf
,"w",stderr
);
}
/* This is to patch LinuxPPC uid wrapping
* for scripts */
int tf;
char tbuf[16];
tf=open(cmdf,O_RDONLY); (void)read(tf,tbuf,8); close(tf);
if(memcmp(tbuf
+1,"ELF",3)!=0) t
=1;
}
if(wrapexec==-1) {
setreuid(getuid(),getuid());setregid(getgid(),getgid());
}
if(wrapexec==1 || (t==1 && wrapexec==0)) {
setreuid(geteuid(),geteuid());setregid(getegid(),getegid());
}
errno=0;
if(strchr(cmdf
,'/')) execve
(cmdf
,arg
,environ
);
else execvp(cmdf,arg);
snprintf(buf
,sizeof(buf
),"Failed to execute");
for(k=0;arg[k];k++) {
snprintf(buf
+t
,sizeof(buf
)-t
," %s",arg
[k
]);
}
accessfile(buf,"a","%s/exec.fail",tmp_dir);
}
else { /* parent */
wrapexec=0; status=0;
if(exec_wait && !is_multiexec) {
killpid=pid; forkalarm();
waitpid(pid,&status,0); killpid=0; finalalarm();
}
return WEXITSTATUS(status);
}
}
/* preparation for resident execution.
* Returns 1 if already up, otherwise 0. */
int multiexec(char *cmd, char **abuf)
{
char *p;
int i;
if(robot_access) return 0;
if(strstr(tmp_dir
,"sessions/")==NULL
) return 0;
if(strstr(tmp_debug
,"yes")!=NULL
&& checkhost
(manager_site
)>=1)
setenv("multiexec_debug","yes",1);
p=getvar("wims_multiexec");
if(p==NULL || wordchr(p,cmd)==NULL) return 0; /* not allowed */
if(!multiexec_random[0]) {
snprintf(multiexec_random
, sizeof(multiexec_random
),
"%lX%lX%lX%lX%lX%lX%lX%lX",
random(),random(),random(),random(),
random(),random(),random(),random());
setenv("multiexec_random",multiexec_random,1);
}
for(i
=0;i
<mxno
&& strcmp(cmd
,mxtab
[i
].
cmd)!=0; i
++);
multiexec_index=i;
if(i==mxno) {
if(mxno>=MAX_MULTIEXEC) return 0;
if(pipe(mxtab[i].pipe_stdin)<0) return 0;
if(pipe(mxtab[i].pipe_stdout)<0) return 0;
if(pipe(mxtab[i].pipe_stderr)<0) return 0;
mystrncpy(mxtab[i].cmd,cmd,sizeof(mxtab[i].cmd));
mxno++; is_multiexec=1;
exportall(); setenv("wims_exec_parm",multiexec_random,1);
execredirected(abuf[0],NULL,NULL,NULL,abuf);
}
is_multiexec=0;
return 1;
}
/* my system(), but with variable parms
* More secure than system(), and direct fork. */
int call_ssh(char *s,...)
{
va_list vp;
char buf[MAX_LINELEN+1];
char *arg[1024];
char *inf=NULL, *outf=NULL, *errf=NULL;
char *cmdf, *p, *p2;
int i, d;
if(robot_access) return 0;
vsnprintf(buf,sizeof(buf),s,vp);
p=find_word_start(buf); if(*p==0) return 0;
cmdf=p;
for(i=0;*p!=0 && i<1000; p=find_word_start(p2)) {
switch(*p) {
case '\'': {
d=0; break;
}
case '"': {
d=0; break;
}
default: d=1; p2=find_word_end(p); break;
}
if(*p2) *p2++=0;
if(!d) {arg[i++]=p; continue;}
switch(*p) {
case '<': inf=++p; break;
case '>': {
p++; if(*p=='&') {
merge: p++; errf=outf=p; break;
}
else outf=p;
break;
}
case '&': {
p++; if(*p=='>') goto merge;
else break;
}
case '2': {
if(*(p+1)=='>') {errf=p+2; break;}
}
default: arg[i++]=p; break;
}
}
arg[i]=NULL;
return execredirected(cmdf,inf,outf,errf,arg);
}
/* Read/write to a file with variable parms to print filename */
void accessfile(char *content, char *type, char *s,...)
{
va_list vp;
char buf[MAX_FNAME+1];
int fd;
if(robot_access) return;
vsnprintf(buf,sizeof(buf),s,vp);
if(strlen(buf
)>=MAX_FNAME
-1) nametoolong
(buf
);
switch(*type) {
case 'r': readfile(buf,content,MAX_LINELEN); return;
case 'e': readfile(buf,content,MAX_LINELEN/4); return; /* limited read */
case 'w': fd=creat(buf,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); break;
case 'a': fd=open(buf,O_WRONLY|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); break;
default : return;
}
lastdatafile[0]=lastftest[0]=0;
if(fd==-1) return;
(void)write
(fd
,content
,strlen(content
)); close
(fd
);
}
/* system(), but with variable parms
* Uses sh to execute command. */
int call_sh(char *s,...)
{
va_list vp;
char buf[MAX_LINELEN+1];
char *abuf[8];
if(robot_access) return 0;
vsnprintf(buf,sizeof(buf),s,vp);
abuf[0]="sh"; abuf[1]="-c"; abuf[2]=buf; abuf[3]=NULL;
return execredirected(abuf[0],NULL,NULL,NULL,abuf);
}
void _getdef(char buf[], char *name, char value[])
{
char *p1, *p2, *p3, *p4;
if(*name==0) goto nothing; /* this would create segfault. */
p2
=find_word_start
(p1
+strlen(name
));
if((p1
>buf
&& !isspace(*(p1
-1))) || *p2
!='=') continue;
p3=p1; while(p3>buf && *(p3-1)!='\n') p3--;
p3=find_word_start(p3);
if(p3<p1 && *p3!='!') continue;
if(p3<p1) {
p3++; p4=find_word_end(p3);
if(find_word_start(p4)!=p1) continue;
if(p4
-p3
!=3 || (strncmp(p3
,"set",3)!=0 &&
if(p4
-p3
!=6 || strncmp(p3
,"define",6)!=0) continue;
}
}
p2=find_word_start(p2);
if(p2>p3) goto nothing;
if(p3-p2>=MAX_LINELEN) user_error("cmd_output_too_long");
memmove(value
,p2
,p3
-p2
); value
[p3
-p2
]=0;
strip_trailing_spaces(value); return;
}
nothing:
value[0]=0; return;
}
/* Get variable definition from a file.
* Result stored in buffer value of length MAX_LINELEN.
*/
void getdef(char *fname, char *name, char value[])
{
char buf[MAX_LINELEN+1], tbuf[MAX_LINELEN+1], nbuf[MAX_NAMELEN+1];
char *p1, *p2;
value[0]=0; if(readfile(fname,buf,sizeof(buf)-16)==NULL) return;
mystrncpy(value,name,MAX_LINELEN);
for(p1=value; *p1; p1=p2) {
while(*p1 && !myisalnum(*p1) && *p1!='_') p1++;
if(*p1==0) break;
for(p2=p1; myisalnum(*p2) || *p2=='_'; p2++);
if(p2-p1>MAX_NAMELEN) continue;
memmove(nbuf
,p1
,p2
-p1
); nbuf
[p2
-p1
]=0;
_getdef(buf,nbuf,tbuf);
string_modify(value,p1,p2,"%s",tbuf);
}
}
int _setdef_changed;
void _setdef(char buf[], char *name, char *value)
{
char *p1, *p2, *p3;
int n;
p2
=find_word_start
(p1
+strlen(name
));
if((p1
>buf
&& !isspace(*(p1
-1))) || *p2
!='=') continue;
p3=p1; while(p3>buf && *(p3-1)==' ') p3--;
if(p3>buf && *(p3-1)!='\n') continue;
string_modify(buf,p2,p3,"%s",value);
_setdef_changed++;
}
return;
}
if(n>0 && buf[n-1]!='\n')
snprintf(buf
+n
,MAX_LINELEN
-n
,"\n%s=%s\n",name
,value
);
else
snprintf(buf
+n
,MAX_LINELEN
-n
,"%s=%s\n",name
,value
);
_setdef_changed++;
}
/* Set variable definition to a file. */
void setdef(char *fname, char *name)
{
char buf[MAX_LINELEN+1];
char *p1, *p2, *p3;
_setdef_changed=0;
if(strchr(name
,'=')==NULL
) return;
for(p1=name;*p1;p1++) {
if(isspace(*p1
) && *p1
!=' ' && *p1
!='\n') *p1
=' ';
if(*p1==' ') {
for(p2
=p1
+1; isspace(*p2
) && *p2
!='\n'; p2
++);
if(p2>p1+1) ovlstrcpy(p1+1,p2);
p2=p1+1; if(*p2=='=' || *p2=='\n') ovlstrcpy(p1,p2);
}
}
if(readfile(fname,buf,sizeof(buf))==NULL) buf[0]=0;
for(p1=find_word_start(name); p1!=NULL; p1=p2) {
p2
=strchr(p1
,'\n'); if(p2
!=NULL
) *p2
++=0;
p1=find_word_start(p1);
p3
=strchr(p1
,'='); if(p3
==NULL
) continue;
*p3++=0; p3=find_word_start(p3);
_setdef(buf,p1,p3);
}
if(_setdef_changed) accessfile(buf,"w","%s",fname);
}
/* check whether connecting host is part of given list.
* Returns 0 if no, 1 if yes.
*/
int checkhost(char *hlist)
{
char buf[MAX_LINELEN+1];
char lbuf[1024], hbuf1[256], hbuf2[256];
char *p1, *p2, *pb, *pe, *pp;
if(*remote_addr==0) return 0;
snprintf(hbuf1
,sizeof(hbuf1
),"+%s+",remote_addr
);
if(*remote_host!=0) {
snprintf(hbuf2
,sizeof(hbuf2
),"+%s+",remote_host
);
for(p1
=hbuf2
; *p1
; p1
++) *p1
=tolower(*p1
);
}
else hbuf2[0]=0;
mystrncpy(buf,find_word_start(hlist),sizeof(buf)); strip_trailing_spaces(buf);
for(p1=buf; *p1; p1++) {
if(!myisalnum
(*p1
) && strchr(".-_",*p1
)==NULL
) *p1
=' ';
}
if(strcmp(buf
,"all")==0) return 1; /* all is all */
for(p1=find_word_start(buf); *p1; p1=find_word_start(p2)) {
p2=find_word_end(p1); if(*p2) *p2++=0;
if(p2-p1<3) continue;
if(myisalnum(*p1)) pb="+"; else pb="";
if(myisalnum(*(p2-1))) pe="+"; else pe="";
snprintf(lbuf
,sizeof(lbuf
),"%s%s%s",pb
,p1
,pe
);
for(pp=p1; *pp && (myisdigit(*pp) || *pp=='.'); pp++);
if(*pp) pp=hbuf2; /* host name */
else pp=hbuf1; /* ip number */
if(strstr(pp
,lbuf
)!=NULL
) return 1; /* found */
}
return 0;
}
/* return 1 if a word of bf2 is a substring of host.
* Like checkhost, but with time check.
* The content of bf2[] is destroyed.
*/
int checkhostt(char bf2[])
{
char *p1, *p2, *p3;
/* compare with starting time */
if(p1
>bf2
&& !isspace(*(p1
-1))) continue;
p3=find_word_start(++p1); p2=find_word_end(p3);
if(p2-p3!=14) continue;
p3[8]='.'; p3[11]=':'; if(*p2) *p2++=0;
if(strncmp(nowstr
,p3
,14)<0) return 0;
ovlstrcpy(p1-1,p2); p1-=2;
}
/* compare with ending time */
if(p1
>bf2
&& !isspace(*(p1
-1))) continue;
p3=find_word_start(++p1); p2=find_word_end(p3);
if(p2-p3!=14) continue;
p3[8]='.'; p3[11]=':'; if(*p2) *p2++=0;
if(strncmp(nowstr
,p3
,14)>0) return 0;
ovlstrcpy(p1-1,p2); p1-=2;
}
p1=find_word_start(bf2); if(*p1==0) return 1;
return checkhost(p1);
}
/* bad identification */
void bad_ident(void)
{
if(cookiegot[0]!=0) {
}
user_error("bad_ident");
}
void instex_flush(void)
{
char *p;
setenv("texgif_style","",1);
setenv("texgif_tmpdir",tmp_dir,1);
setenv("texgif_src",instex_src,1);
setenv("texgif_outfile",instex_fname,1);
unsetenv("w_instex_color");
getwimstexsize=0; fix_tex_size(); getwimstexsize=1;
for(p=instex_fname;*p;p++) if(*p=='\n') *p=' ';
wrapexec=0; call_ssh("%s/%s >%s/ins.Out 2>%s/ins.Err",
bin_dir,instex_processor,
tmp_dir,tmp_dir);
call_ssh("mv %s %s >/dev/null 2>/dev/null", instex_fname,s2_prefix);
instex_src[0]=instex_fname[0]=0; instex_cnt=0;
}
/* put last.phtml */
void putlastout(void)
{
int t;
if(instex_cnt>0) instex_flush();
t=catfile(stdout,"%s/%s",s2_prefix,lastout);
if(t
==0) printf("Content-type: text/plain\r\n\r\n");
}
struct sockaddr_un sun;
/* returns >=0 if OK. */
int kerneld(char *p, int bufsize)
{
int sock, s, t, t1, l, *ip;
struct timeval tv;
fd_set rset;
sock=socket(PF_UNIX,SOCK_STREAM,0);
s=connect(sock,(const struct sockaddr *)&sun,sizeof(sun));
if(s) {bad: close(sock); return -1;}
ip=(int *) p;
l
=strlen(p
+sizeof(int)); *ip
=l
;
s=write(sock,p,l+sizeof(int)); if(s!=l+sizeof(int)) goto bad;
for(t=0, l=bufsize-1; t<l+sizeof(int);) {
tv.tv_sec=2; tv.tv_usec=0;
FD_ZERO(&rset); FD_SET(sock,&rset);
if(select(sock+1,&rset,NULL,NULL,&tv)<=0) goto bad;
t1=read(sock,p+t,l+sizeof(int)-t);
if(t1+t<sizeof(int)) goto bad;
if (t1 < 0) goto bad;
l=*ip; if(l<=0) goto bad;
if(l>=bufsize-sizeof(int)-4) user_error("cmd_output_too_long");
t+=t1;
}
p[l+sizeof(int)]=0;
close(sock);
return l+sizeof(int);
}
void _daemoncmd(char *p)
{
char buf[MAX_LINELEN+1+sizeof(int)];
char *p1, *p2, *p3;
mystrncpy(buf+sizeof(int),p,sizeof(buf)-sizeof(int));
if(kerneld(buf,sizeof(buf))<0)
internal_error("Daemon communication error.");
p1=find_word_start(buf+sizeof(int));
p2=find_word_end(p1); if(*p2) *p2++=0;
mystrncpy(p,p2,MAX_LINELEN); return;
}
p1=find_word_start(p2); p2=find_word_end(p1); if(*p2) *p2++=0;
p2=find_word_start(p2); p3=find_word_end(p2);
if(*p3) {
*p3++=0; p3=find_word_start(p3); strip_trailing_spaces(p3);
setvar("wims_error_data",p3);
}
switch(*p1) {
case '1': user_error(p2);
case '2': module_error(p2);
case '3':
default: internal_error(p2);
}
*p=0;
}