/* netlib-unshar http://netlib.bell-labs.com/netlib/access/unshar.c * * This program unpacks the shell scripts sent by netlib. More * precisely, it attempts to do nothing but create new files in the * current directory, taking care not to overwrite existing files, nor * to write files elsewhere, nor to execute any commands. * * This program assumes Standard C. It does not pretend to interpret * all valid shell scripts, only certain ones used in netlib. * Send other bug reports to: ehg@research.bell-labs.com. * * The author of this software is Eric Grosse. Copyright (c) 1998. * Permission to use, copy, modify, and distribute this software for any * purpose without fee is hereby granted, provided that this entire notice * is included in all copies of any software which is or includes a copy * or modification of this software and in all copies of the supporting * documentation for such software. * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. */ /* SAMPLE INPUT 1: (reply) From netlibd Mon Apr 10 22:19 EDT 1995 To: ehg Subject: Re: send master/index 1 #!/bin/sh # to unpack, sh this message in an empty directory PATH=/bin:/usr/bin echo "Anything free comes with no guarantee!" cat > index <<'CUT HERE............' lib a for approximation algorithms from various sources CUT HERE............ test ! -d linpack && mkdir linpack cat > linpack/dgefa.f <<'CUT HERE............' subroutine dgefa(a,lda,n,ipvt,info) CUT HERE............ #define END */ /* SAMPLE INPUT 2: (stree) echo unshar.c 1>&2 sed >unshar.c <<'-------cut here----- unshar.c' 's/^X//' X/* netlib-unshar X * X * This program unpacks the shell scripts sent by netlib. More -------cut here----- unshar.c */ /* SAMPLE INPUT 3: (shar) if test -f 'acoshl.c' -a X"$1" != X"-c"; then echo 'x - skipping acoshl.c (File already exists)' else echo 'x - extracting acoshl.c (Text)' sed 's/^X//' << 'SHAR_EOF' > 'acoshl.c' && y = acoshl( x ); SHAR_EOF chmod 0666 acoshl.c || echo 'restore of acoshl.c failed' Wc_c="`wc -c < 'acoshl.c'`" test 2873 -eq "$Wc_c" || echo 'acoshl.c: original size 2873, current size' "$Wc_c" fi exit 0 */ /* SAMPLE INPUT 4: (bigmail) cat > netlib <<'CUT HERE............' # A good place to put all this is the dumpster. CUT HERE............ cat > netlib <<'CUT HERE............' # A good place to put all this is the dumpster. CUT HERE............ test -w netlib && test -r 10832P00 && ( cat 10832P00 >> netlib; rm 10832P00 ) */ /* SAMPLE INPUT 5: (shar 3.49) added 6jun98 if test ! -d 'lapackpp'; then echo 'x - creating directory lapackpp' mkdir 'lapackpp' fi if test -f 'lapackpp/arch.h' -a X"$1" != X"-c"; then echo 'x - skipping lapackpp/arch.h (File already exists)' else echo 'x - extracting lapackpp/arch.h (Text)' sed 's/^X//' << 'SHAR_EOF' > 'lapackpp/arch.h' && ... SHAR_EOF chmod 0660 lapackpp/arch.h || echo 'restore of lapackpp/arch.h failed' Wc_c="`wc -c < 'lapackpp/arch.h'`" test 440 -eq "$Wc_c" || echo 'lapackpp/arch.h: original size 4, current size' "$Wc_c" fi */ /* Implementation Note. Obviously, this code is less elegant and less general than just invoking the shell. Its reason for being is purely to protect against malicious input. (That's also the reason I wasn't satisfied with the unshar programs I found on the net.) If you find a security hole here, please alert me immediately! ehg@research.bell-labs.com If you make small edits to let this program cope with other, widely used shar formats, I'm also happy to absorb those. I made a conscious decision not to interpret chmod. Frequently these set directories to be read-only, then try to create files there. More seriously, if chmod +x were interpreted, then we'd need to watch out for creating "mkdir" in the local directory. */ #include #include #include #include #ifdef DOS #define SLASH '\\' #else #define SLASH '/' #endif int verbose = 1; void quit(void) { fprintf(stderr,"Sorry.\n"); if(getenv("Errordump")) abort(); exit(1); } /* fgets() but strip \r, for Unix processes reading Windows files */ char* fgetsr(char *s, int n, FILE *stream) { char *r, *line = fgets(s,n,stream); if(line==NULL) return NULL; r = strrchr(line,'\r'); if(r!=NULL && r[1]=='\n' && r[2]=='\0'){ r[0] = '\n'; r[1] = '\0'; } return line; } /* parses line==" > pathname <<'CUT HERE' 's/^X//'\n" */ /* Note: a newline is added to cut, to simplify later matching. */ void setcut(char *line, char *path, int npath, char *cut, int ncut, unsigned int *drop1p) { char *s = line; int i, n; *path = 0; *cut = 0; *drop1p = 0; while(*s){ s += strspn(s," \t"); switch(*s){ case '\'': case '\"': if(s[1]=='s' && s[3]=='^' && s[2]==s[5] && s[5]==s[6] && s[0]==s[7]){ *drop1p = (unsigned char)(s[4]); s += 8; }else{ fprintf(stderr,"couldn't parse %s\n",s); quit(); } break; case '>': s += 1+strspn(s+1," \t"); n = strcspn(s," \t\n<"); if(n>=npath){ fprintf(stderr,"pathname too long in:\n%s",line); quit(); } strncpy(path,s,n); path[n] = 0; s += n; if(path[0]=='\'' && path[n-1]=='\''){ /* strip quotes */ /* memmove is missing from some otherwise usable compilers like gcc, so do it inline... memmove(path,path+1,n-2); */ for(i = 0; i=ncut){ fprintf(stderr,"cutstring too long in:\n%s",line); quit(); } strncpy(cut,s,n); cut[n] = '\n'; cut[n+1] = 0; s += n+1; break; case '&': if(strncmp(s,"&&",2)==0){ s += 2; }else{ fprintf(stderr,"unrecognized: %s",s); fprintf(stderr," in: %s",line); quit(); } break; case '\n': case '\0': return; break; default: fprintf(stderr,"unrecognized: %s",s); fprintf(stderr," in: %s",line); quit(); break; } } } int unsafe(char *s) { char c, *r = s; /* check for characters interpreted by the shell (by which nefarious users might otherwise break into the system) */ for (c = *s; c != '\0'; c = *++s) { if ( strchr(" \"'`$\n;&|^<>()\\", c) || (c & 0200) || !isprint(c) ){ if(verbose) fprintf(stderr,"char %c = %u\n",c,c); return(1); } } for(; r=strchr(r,'.'); r++){ if(r[1]!='.') continue; if(r>s && r[-1]!=SLASH) continue; if(r[2]!=0 && r[2]!=SLASH) continue; fprintf(stderr,"no .. path components allowed!\n"); fprintf(stderr," %s\n",s); quit(); } return(0); } /* like fopen(,"w") but restricts to local directory tree and avoids overwriting existing file */ FILE* safeopen(char*path) { FILE *f; if(unsafe(path)){ fprintf(stderr,"filename contains dangerous chars\n"); quit(); } f = fopen(path,"r"); if(f){ fprintf(stderr,"filename %s collision.\n",path); fprintf(stderr,"unshar in empty directory.\n"); quit(); } f = fopen(path,"w"); if(!f){ fprintf(stderr,"can't open %s\n",path); quit(); } return(f); } /* copy disclaimers, etc. onto standard error */ void echo(char *mess) { int n = strlen(mess); if(strcmp(mess+n-6," 1>&2\n")==0){ mess[n-6] = '\n'; mess[n-5] = 0; } while(*mess){ if(isprint(*mess) || *mess=='\n') fputc(*mess,stderr); mess++; } } /* return nonzero if line ends in && or ||, and if so chop it */ int shellbool(char *line) { int n; char *s; n = strlen(line); s = line+n-3; if(strcmp(s,"&&\n")==0 || strcmp(s,"||\n")==0){ *s = '\n'; return(1); } return(0); } /* Terminator2 test -w netlib && test -r 10832P00 && test -r 10832P01 && ( cat 10832P00 >> netlib; rm 10832P00 cat 10832P01 >> netlib; rm 10832P01 ) */ void bigmail(char *r, FILE *in) { char line[5000], assembly[1000], piece[30], *s; int n; FILE *f, *af; s = strchr(r,' '); if(!s || strcmp(s," &&\n")!=0 || s<=r || s-r>=sizeof(assembly)) return; *s = 0; strcpy(assembly,r); if(unsafe(assembly)) return; while(1){ if(!fgetsr(line,sizeof(line),in)) return; if(strcmp(line,"(\n")==0) break; if(strncmp(line,"test -r ",8)!=0) return; r = line+8; s = strchr(r,' '); if(!s || strcmp(s," &&\n")!=0 || s<=r || s-r>=sizeof(piece)) return; *s = 0; strcpy(piece,r); if(unsafe(piece)) return; f = fopen(piece,"r"); if(!f) return; fclose(f); } if(verbose) fprintf(stderr,"reassembling %s\n",assembly); af = fopen(assembly,"a"); while(1){ if(!fgetsr(line,sizeof(line),in)){ fprintf(stderr,"unexpected EOF\n"); return; } if(strcmp(line,")\n")==0) break; if(strncmp(line," cat ",5)!=0){ fprintf(stderr,"contact ehg@research.bell-labs.com"); quit(); } r = line+5; s = strchr(r,' '); if(!s || s<=r || s-r>=sizeof(piece)) return; *s++ = 0; strcpy(piece,r); if(unsafe(piece)) return; n = strlen(assembly); if( strncmp(s,">> ",3)!=0 || strncmp(s+3,assembly,n)!=0 || strncmp(s+3+n,"; rm ",5)!=0) return; f = fopen(piece,"r"); if(!f){ fprintf(stderr,"couldn't reopen %s",piece); quit(); } if(verbose) fprintf(stderr," %s\n",piece); while((n = fread(line,1,sizeof(line),f))>0){ if(fwrite(line,1,n,af)!=n){ fprintf(stderr,"write error\n"); quit(); } } fclose(f); remove(piece); } fclose(af); } void mkdir(char *cmd) { int n = strlen(cmd); if(n>1000) /* don't stress system() */ return; if(cmd[n-1]=='\n') cmd[n-1] = 0; if(strncmp(cmd,"mkdir ",6)!=0 || unsafe(cmd+6)) return; if(verbose) fprintf(stderr,"%s\n",cmd); system(cmd); } /* unpack one input file into (possibly many) output files */ void unshar(FILE *in) { char line[5000], *line0, cut[100], path[1000]; /* plenty big enough for netlib shar files */ FILE *out = 0; unsigned int drop1; /* drop first char on line, if it equals drop1 */ int skipnext = 0; while(fgetsr(line,sizeof(line),in)){ /* newline retained in line */ if(out){ if(strcmp(line,cut)==0){ fclose(out); out = 0; continue; } if(drop1 && drop1==(unsigned char)line[0]) line0 = line+1; else line0 = line; if(fputs(line0,out)==EOF){ fprintf(stderr,"write error\n"); quit(); } continue; } if(skipnext){ skipnext = shellbool(line); continue; } fprintf(stderr, ">>>>> %s\n", line); line0 = line; while(isspace(*line0)) line0++; if(strncmp(line0,"echo ",5)==0){ echo(line0+5); }else if(strncmp(line0,"exit ",5)==0){ return; }else if(strncmp(line0,"cat ",4)==0 || strncmp(line0,"sed ",4)==0){ setcut(line0+4,path,sizeof(path),cut,sizeof(cut),&drop1); out = safeopen(path); if(verbose) fprintf(stderr,"extracting %s\n",path); }else if(strncmp(line,"if test -f '",12)==0){ while(fgetsr(line,sizeof(line),in)) if(strcmp(line,"else\n")==0) break; }else if(strncmp(line,"if test ! -d '",14)==0){ line0 = strchr(line+14,'\''); if(!line0){fprintf(stderr,"missing '\n");quit();} strcpy(line+8,"mkdir"); line[13] = ' '; *line0 = 0; mkdir(line+8); while(fgetsr(line,sizeof(line),in)) if(strcmp(line,"fi\n")==0) break; }else if(strncmp(line,"test ! -d ",10)==0){ line0 = strchr(line+10,' '); if(!line0){fprintf(stderr,"unrec test -d\n");quit();} if(strncmp(line0," && mkdir ",10)!=0) continue; mkdir(line0+4); }else if(strncmp(line,"mkdir ",6)==0){ mkdir(line); }else if(strncmp(line,"test -w ",8)==0){ bigmail(line+8,in); }else{ skipnext = shellbool(line); /* and otherwise ignore */ } } } void main(int argc, char**argv) { FILE *in; if(argc<=1){ unshar(stdin); }else{ while(--argc>0){ if(strcmp(*++argv,"-v")==0){ verbose = 1 - verbose; continue; } in = fopen(*argv,"r"); if(!in){ fprintf(stderr,"can't open %s\n",*argv); quit(); } if(verbose) fprintf(stderr,"======= %s =======\n",*argv); unshar(in); } } exit(0); }