123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- #include <dirent.h>
- #include <fcntl.h>
- #include <paths.h>
- #include <stdlib.h>
- #include <sys/wait.h>
- #include "soh.h"
- #include "unix.h"
- /**********************************************************************
- This file contains functions for running Unix programs. One of the
- paradigms of Unix philosophy sounds like this: "a program should do
- one thing and do it well". This program tries to follow this philosophy,
- so the main task is to process HTTP requests from the client, and the
- rest of the work is delegated to other programs. For example, to send a
- file to a client, all you need to do is send it the correct HTTP headers
- and call the "cat" program, replacing stdout with the client's socket
- descriptor.
- ***********************************************************************/
- /**************************
- * UTILS *
- **************************/
- int createPipe( int* pipeInput, int* pipeOutput ){
-
- int i;
- int pipes[2];
-
- //create pipe
- if( pipe(pipes) ){
- perror( "create pipes" );
- return -1;
- }
-
- //set close_on_exec flag on pipe
- for( i = 0; i < 2; i++ ) if( fcntl(pipes[i], F_SETFD, FD_CLOEXEC) ){
- perror( "set close_on_exec flag on pipe" );
- for( i = 0; i < 2; i++ ) close( pipes[i] );
- return -1;
- }
-
- *pipeInput = pipes[1];
- *pipeOutput = pipes[0];
-
- return 0;
- }
- static struct progdesc* getUnixProgramByAlias( struct instance* this, const char* alias ){
-
- int i;
-
- for( i = 0; this->progs[i].name; i++ ){
- if( !this->progs[i].path ) continue;
- if( strcmp(alias, this->progs[i].alias) ) continue;
- return &this->progs[i];
- }
-
- return NULL;
- }
- /********************************
- * RUN PROGRAM *
- ********************************/
- int unixProgramResult( pid_t programPid, const char* programName ){
-
- int wstatus;
- pid_t waitres;
-
- //wait while programs works
- waitres = waitpid( programPid, &wstatus, 0 );
- if( waitres < 0 ) {
- perror( "waitpid" );
- return -2;
- }
-
- //print result
- if( WIFSIGNALED(wstatus) ) {
- printf( "%s was killed by signal %d\n", programName, WTERMSIG(wstatus) );
- return -1;
- }
-
- if( WIFEXITED(wstatus) ){
- if( WEXITSTATUS(wstatus) ){
- printf( "%s return code %d\n", programName, WEXITSTATUS(wstatus) );
- return -1;
- }
- return 0;
- }
-
- printf( "FATAL: waitpid return unknown status.\n" );
- return -2;
- }
- pid_t startUnixProgram( char* argv[], char* envp[], int stdio[3] ){
-
- int i;
- int res;
- pid_t forkRes;
- FILE* stdFiles[] = { stdin, stdout, stderr };
- char* errstr[] = { "dup2 stdin", "dup2 stdout", "dup2 stderr" };
-
- //after fork main thread return
- //and child be works in this function
- forkRes = fork();
- if( forkRes ){
- if( forkRes < 0 ) perror( "startUnixProgram fork" );
- return forkRes;
- }
-
- for( i = 0; i < 3; i++ ) if( stdio[i] >= 0 ){
- res = dup2( stdio[i], fileno(stdFiles[i]) );
- if( res < 0 ){
- perror( errstr[i] );
- exit( 1 );
- }
- }
-
- execve( argv[0], argv, envp );
- perror( "execve" );
- exit( 66 );
- }
- static int startUnixPrograms( struct instance* this, char** argv[], int count ){
-
- int i;
- int res;
- int pipeInput;
- int pipeOutput;
- pid_t pids[count];
- int stdio[3] = { -1, -1, -1 };
- int programResult;
-
- //init some vars
- res = 0;
- pipeInput = -1;
- pipeOutput = -1;
-
- /********* start programs *********/
- for( i = 0; i < count; i++ ){
-
- //if last, then connect prorgamm out to client socket
- if( i + 1 == count ) stdio[1] = this->clientSocket;
- else{
- //create new pipe
- res = createPipe( &pipeInput, &pipeOutput );
- if( res ){
- if( pipeOutput >= 0 ) close( pipeOutput );
- goto l_monitor;
- }
-
- //connect programm stdout to pipe input
- stdio[1] = pipeInput;
- }
-
- //start program
- pids[i] = startUnixProgram( argv[i], this->envp, stdio );
- if( pids[i] < 0 ){
- if( pipeInput >= 0 ) close( pipeInput );
- if( pipeOutput >= 0 ) close( pipeOutput );
- goto l_monitor;
- }
-
- //close our copy pipeInput of current pipe
- if( pipeInput >= 0 ){
- close( pipeInput );
- pipeInput = -1;
- }
-
- //close our copy pipeOutput of previous pipe
- if( stdio[0] >= 0 ) close( stdio[0] );
-
- //connect current pipeOutput to stdin of next programm
- stdio[0] = pipeOutput;
- }
-
- /********* wait results *********/
- l_monitor:
- //in case if error occurred while starting programs
- if( i != count ){
- count = i;
- printf( "Start unix programs failed.\n" );
- }
-
- //wait results
- for( i = 0; i < count; i++ ){
- programResult = unixProgramResult( pids[i], argv[i][0] );
- if( programResult == -1 ) res = -1;
- else if( programResult == -2 ) return -2;
- }
-
- return res;
- }
- /********************************************
- * SEARCH PROGRAMS ON HOST *
- ********************************************/
- static int searchUnixProgramInDir( struct progdesc* prog, DIR* dir, char* path ){
-
- int res;
- char tmpbuf[8192];
- struct dirent* dirent;
- struct stat statbuf;
-
- for( dirent = readdir(dir); dirent; dirent = readdir(dir) ){
-
- //compare name
- if( strcmp(prog->name, dirent->d_name) ) continue;
-
- //full path to file
- snprintf( tmpbuf, sizeof(tmpbuf), "%s/%s", path, dirent->d_name );
-
- //get file info via stat and check type
- res = stat( tmpbuf, &statbuf );
- if( res || (statbuf.st_mode & S_IFMT) != S_IFREG ) continue;
-
- //check file is executable
- if( !(statbuf.st_mode & S_IXUSR) & !(statbuf.st_mode & S_IXGRP) & !(statbuf.st_mode & S_IXOTH) ) continue;
-
- //set programm path
- prog->path = customMalloc( strlen(tmpbuf) + 1 );
- if( !prog->path ) return -2;
- sprintf( prog->path, "%s", tmpbuf );
-
- //search next program
- return 1;
-
- }
-
- return 0;
- }
- #define PARSE_MAX_PATHS 20
- int searchUnixPrograms( struct instance* this ){
-
- int i;
- int i2;
- int res;
- char* ePath;
- size_t ePathOffset;
- int pathsCount;
- char paths[PARSE_MAX_PATHS][256];
-
- DIR* dir;
-
- printf( "Searching for avaliable utils... " );
- fflush( stdout );
-
- //init some vars
- pathsCount = 0;
- ePathOffset = 0;
-
- //get env PATH
- ePath = getenv( "PATH" );
- if( !ePath ) ePath = _PATH_STDPATH;
-
- /********* parse ePath into paths *********/
- for( pathsCount = 0; pathsCount < PARSE_MAX_PATHS; pathsCount++ ){
-
- res = parserCore( ePath, ':', paths[pathsCount], sizeof(paths[pathsCount]), &ePathOffset );
- if( res < 0 ){
- printf( "%s: Failed to parse PATH environment variable.\n", __FUNCTION__ );
- return -2; //error
- }
-
- if( !res ) break;
- }
-
- /********* search progs *********/
-
- this->progs = unixProgramDB;
-
- //i - progs DB
- for( i = 0; unixProgramDB[i].name; i++ ){
-
- //i2 - paths
- for( i2 = 0; i2 < pathsCount; i2++ ){
-
- dir = opendir( paths[i2] );
- if( !dir ) continue;
-
- res = searchUnixProgramInDir( &unixProgramDB[i], dir, paths[i2] );
-
- closedir( dir );
-
- if( res < 0 ) return -2;
- if( res > 0 ) break;
- }
-
- }
-
- printf( "done.\n" );
-
- return 0;
- }
- /*************************************
- * DOWNLOAD HANDLER *
- *************************************/
- static int checkIndexValidity( struct file_db* indexItem ){
-
- int res;
- char type;
- struct stat statbuf;
-
- //get info about file
- res = lstat( indexItem->desc + 1, &statbuf );
- if( res ){
- perror( "archiver stat" );
- return -2;
- }
-
- //convert stat type to our type
- res = convertStatType( &statbuf, &type );
- if( res ) return -2;
-
- //make sure the type in the index matches the type of the file on disk
- if( (indexItem->desc[0] & FILEDESC_TYPE_MASK) != type ){
- printf( "FATAL: File type in the index does not match with file type on the disk.\n" );
- return -2;
- }
-
- //for file also check size
- if( (indexItem->desc[0] & FILEDESC_TYPE_MASK) != FILEDESC_TYPE_DIR ){
- if( indexItem->size != statbuf.st_size ){
- printf( "FATAL: Filesize in the index does not match with filesize on the disk.\n" );
- return -2;
- }
- }
-
- return 0;
- }
- static int getFileMime( struct instance* this, char* filename, char* mimeBuf, int mimeBufLen ){
-
- int res;
- int offset;
- int pipeInput;
- int pipeOutput;
- int stdio[3];
- struct progdesc* file;
- char* args[6];
- pid_t filePid;
-
- //init some vars
- offset = 0;
-
- //get file util
- file = getUnixProgramByAlias( this, "file" );
- if( !file ) return -1;
-
- //prepare args
- args[0] = file->path;
- args[1] = "-i";
- args[2] = "-b";
- args[3] = "--";
- args[4] = filename;
- args[5] = NULL;
-
- //create pipe
- res = createPipe( &pipeInput, &pipeOutput );
- if( res ) return -1;
-
- //route output to pipe
- stdio[0] = -1; //default stdin
- stdio[1] = pipeInput; //stdout to pipe input
- stdio[2] = -1; //default stderr
-
- //start file
- filePid = startUnixProgram( args, this->envp, stdio );
- close( pipeInput );
- if( filePid < 0 ){
- close( pipeOutput );
- return -1;
- }
-
- //read mime from pipe
- l_readNext:
- res = read( pipeOutput, mimeBuf + offset, mimeBufLen - offset );
- if( res > 0 ){
- offset += res;
- goto l_readNext;
- }
-
- //set null-terminator and close pipe
- mimeBuf[offset-1] = 0x00;
- close( pipeOutput );
-
- return unixProgramResult( filePid, args[0] );
- }
- int unixDownloadHandler( struct instance* this, struct file_db* requested, char* format ){
-
- int res;
- char buf[4096];
- char encodedFilename[2048];
- int headersLength;
- char parsedFormat[8];
- size_t formatOffset;
- struct progdesc* reader;
- struct progdesc* compressor;
- int programsCount;
- char* readerArgv[6];
- char* compressorArgv[2];
- char** argvArr[3];
- char fileExtension[16];
- char flagIsRaw;
- char flagIfView;
- char mime[1024];
-
- //init some vars
- formatOffset = 0;
- reader = NULL;
- compressor = NULL;
- fileExtension[0] = 0x00;
- flagIsRaw = 0x00;
- flagIfView = 0x00;
-
- //check the validity of the index
- res = checkIndexValidity( requested );
- if( res ){
- httpSendStatus( this, 500, "Internal server error." );
- return res;
- }
-
- /********* select reader *********/
-
- //if view in browser requested
- if( !strcmp("view", format) ){
- flagIfView = 0xff;
- goto l_raw;
- }
-
- //if raw format requested
- if( !format[0] ){
- l_raw:
- //raw format is available only for regular files, remember?
- if( (requested->desc[0] & FILEDESC_TYPE_MASK) != FILEDESC_TYPE_FILE ) goto l_501;
- reader = getUnixProgramByAlias( this, "raw" );
- if( !reader ) goto l_501;
- flagIsRaw = 0xff;
- goto l_prepare_argv;
- }
-
- //parse format
- res = parserCore( format, '.', parsedFormat, sizeof(parsedFormat), &formatOffset );
- if( res < 0 ) goto l_400;
-
- //search suitable reader
- reader = getUnixProgramByAlias( this, parsedFormat );
- if( !reader ) goto l_501;
-
- if( reader->type != PROGDESC_TYPE_ARCHIVER ){
-
- //not a regular file can be sent only with the help of archivers, remember?
- if( (requested->desc[0] & FILEDESC_TYPE_MASK) != FILEDESC_TYPE_FILE ) goto l_501;
-
- if( reader->type != PROGDESC_TYPE_COMPRESSOR ) goto l_501;
- compressor = reader;
-
- reader = getUnixProgramByAlias( this, "raw" );
- if( !reader ) goto l_501;
- }
-
- /********* select compressor *********/
-
- //parse next part of format and search compressor if need
- if( res ){
-
- //e.g. xz.xz
- if( compressor ) goto l_400;
-
- res = parserCore( format, '.', parsedFormat, sizeof(parsedFormat), &formatOffset );
- if( res < 0 ) goto l_400;
- if( res ) goto l_501;// e.g. tar.xz.*
-
- compressor = getUnixProgramByAlias( this, parsedFormat );
- if( !compressor ) goto l_501;
- if( compressor->type != PROGDESC_TYPE_COMPRESSOR ) goto l_501;
- }
-
- /********* file extension *********/
-
- if( reader->type == PROGDESC_TYPE_ARCHIVER ){
- if( compressor ){
- res = sprintf( fileExtension, ".%s.%s", reader->alias, compressor->alias );
- } else {
- res = sprintf( fileExtension, ".%s", reader->alias );
- }
- } else {
- res = sprintf( fileExtension, ".%s", compressor->alias );
- }
-
- if( res < 0 ) goto l_500;
-
- /********* prepare argv for programs *********/
-
- l_prepare_argv:
-
- printf( "using reader %s\n", reader->name );
- if( compressor ) printf( "using compressor %s\n", compressor->name );
- programsCount = 1;
-
- //reader argv
- readerArgv[0] = reader->path;
- if( !strcmp(reader->name, "tar") ){
-
- readerArgv[1] = "-cf";
- readerArgv[2] = "-";
- readerArgv[3] = "--";
- readerArgv[4] = requested->desc + 1;
- readerArgv[5] = NULL;
-
- }else{
-
- readerArgv[1] = "--";
- readerArgv[2] = requested->desc + 1;
- readerArgv[3] = NULL;
-
- }
-
- argvArr[0] = readerArgv;
-
- //compressor argv
- if( !compressor ) argvArr[1] = NULL;
- else{
-
- compressorArgv[0] = compressor->path;
- compressorArgv[1] = NULL;
-
- argvArr[1] = compressorArgv;
- argvArr[2] = NULL;
-
- programsCount = 2;
-
- }
-
- /********* send HTTP headers *********/
-
- //encode filename
- res = uriEncode( requested->desc + 1, encodedFilename, sizeof(encodedFilename) );
- if( res < 0 ) goto l_500;
-
- //if a raw file is requested, then we can send its size
- if( flagIsRaw ){
- if( flagIfView ){
-
- res = getFileMime( this, requested->desc + 1, mime, 1024 );
- if( res == -1 ) goto l_500;
- if( res == -2 ){
- httpSendStatus( this, 500, "Internal server error." );
- return -2;
- }
-
- res = sprintf( buf,
- "%s 200 OK\r\n" //this->protocol
- "Content-Type: %s\r\n" //mime
- "Content-Length: %li\r\n" //requested->size
- "\r\n",
- this->protocol, mime, requested->size
- );
- } else {
- res = sprintf( buf,
- "%s 200 OK\r\n" //this->protocol
- "Content-Disposition: attachment; filename=\"%s\"\r\n" //encodedFilename
- "Content-Length: %li\r\n" //requested->size
- "\r\n",
- this->protocol, encodedFilename, requested->size
- );
- }
-
-
- //if an archiver and/or compressor is used, you can't know the size of the output file
- } else {
- res = sprintf( buf,
- "%s 200 OK\r\n" //this->protocol
- "Content-Disposition: attachment; filename=\"%s%s\"\r\n" //name.extension
- "\r\n",
- this->protocol, encodedFilename, fileExtension
- );
- }
-
- if( res < 0 ) goto l_500;
- headersLength = res;
-
- //send headers
- res = sendData( this->clientSocket, buf, headersLength );
- if( res ) return -1;
-
- /********* start unix programs *********/
-
- printf( "<<< Uploading %s...\n", requested->desc + 1 );
- res = startUnixPrograms( this, argvArr, programsCount );
- if( !res ) printf( "Done.\n" );
- else printf( "Failed.\n" );
- return res;
-
- l_400: return httpSendStatus( this, 400, "Bad request." );
- l_500: return httpSendStatus( this, 500, "Internal server error." );
- l_501: return httpSendStatus( this, 501, "Not implemented." );
- }
|