/* ========================================================================== ** * pq_cgi.c * * Copyright: * Copyright (C) 2011 by Christopher R. Hertel * * Email: crh@ubiqx.mn.org * * $Id: pq_cgi.c 31 2011-07-25 19:50:24Z crh $ * * -------------------------------------------------------------------------- ** * * Description: * Prequel CGI program to create and send BranchCache tags for a file. * * -------------------------------------------------------------------------- ** * * License: * * 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. * * -------------------------------------------------------------------------- ** * * Notes: * + This is a test program. It works, but it's a bit sloppy. * * + To compile: * * cc -o pq_cgi util.c ubi_sLinkList.c pq_cgi.c -lmagic -lssl -lcgi * * + The cgilib library (man 5 cgi) is available from: * http://www.infodrom.org/projects/cgilib/ * Version 0.6 is shipped with Ubuntu, including Ubuntu 11.04. Look for * package "cgilib". * * Unfortunately, cgilib version 0.6 appears to have some bugs in the * portion that writes HTML headers, so this program just writes the * headers directly (which seems to work). * * I have tested with version 0.7 of cgilib, and it has the same problems. * * + Libmagic is used to determine the MIME type of the Content. This is * required in a CGI script because HTTP clients expect it. In Ubuntu, * the required package is called "libmagic1". Not sure what it would * be called in other Linux distros or OSes. * * + OpenSSL is also required, at this point. * * + The ubi_sLinkList module is a simple singly-linked-list implementation. * * + The util module provides some shortcut macros and functions that make * it easier for me to write code and more difficult for others to figure * out what I'm doing. Sorry. * * + BranchCache[tm], as implemented on Windows Servers, will not generate * the hashes (Content Information) for a file (Content) until it * receives a request for the file that indicates that the client * supports BranchCache[tm]. In response to that initial request, the * Windows server returns the file itself (again, the Content). * * This testing program, in contrast, generates the hashes on the fly * and does not store them anywhere. This could be a problem for very * large files, and would certainly stress a server that is under heavy * load. * * The approach taken by Windows ensures that the hashes are generated * in the background at a lower priority, so that creating hashes does * not conflict with the primary purpose of the server. * * + Unfortunately, the segment information blocks include the segment hash, * which cannot be calculated until all of the block hashes for the * segment have been calculated. All segment records must be sent before * the block records can be sent, so we have to process the entire * file before we can send the hashes to the client. Dang. * * ToDo: * * + Update the whole thing to handle the different hashing algorithms. * (W2K8r2 supports SHA-384 and SHA-512, though Windows7 clients do not.) * * + Support use of an externally managed Server Secret rather than using * a hard-coded string in the source file. * * + Design a better PeerDist (BranchCache[tm]) implementation that can be * used to as a stand-alone code library to add PeerDist support to * whatever. (Work is progressing on this.) * * ========================================================================== ** */ #include /* Standard C stuff. */ #include /* Standard boolean type for C. */ #include /* Standard Integer types. */ #include /* For strstr(3). */ #include /* System types. */ #include /* System status. */ #include /* Unix stuff. */ #include /* Libmagic magic number recognition. */ #include /* OpenSSL sha tools. */ #include /* OpenSSL hmac tools. */ #include /* OpenSSL, but what is evp? */ #include /* Lightweight CGI toolkit (man 5 cgi). */ #include "ubi_sLinkList.h" /* Singly linked list. */ #include "util.h" /* Prequel utility functions. */ /* -------------------------------------------------------------------------- ** * Macros: * * IsPeerDist - For proper operation, we need to check that the client * has indicated support for BranchCache[tm] by listing * "peerdist" as an acceptable encoding format. If the * client has not indicated support for "peerdist", then * we should return the original Content rather than the * Content Information. * * For testing, however, it may be useful to disable this * test to make sure that the Content Information (hashes) * are returned regardless of the client's request. * * Define NO_CHECK_PEERDIST to disable this test. * In other words, add "-DNO_CHECK_PEERDIST" to the compiler * command line to force the pq_cgi program to generate * hashes even when the client has not requested them. * This is useful when testing using, for example, Firefox * or Lynx or some other browser that doesn't actually * support peerdist encoding. */ #ifdef NO_CHECK_PEERDIST #define IsPeerDist true #else #define IsPeerDist hdrContains( "HTTP_ACCEPT_ENCODING", "peerdist" ) #endif /* -------------------------------------------------------------------------- ** * Defined Constants: * * MAX_64K - 64K. This is the size of a BranchCache block. * MAX_32M - 32M. This is the size of a BranchCache segment. */ #define MAX_64K 0x00010000 #define MAX_32M 0x02000000 /* -------------------------------------------------------------------------- ** * Typedefs: * * hash256 - Just the right number of bytes to hold an SHA-256 hash. * * segEntry - Contains per-segment data: * node - The linked list node header. * segOffset - The offset into the original Content at which * the Segment begins. This basically means "this * chunk of hashes maps to the segment starting at * segOffset. * segSize - The number of bytes in the Segment. This will * be 32M (0x02000000) except for the last * segment, in which case it will be the * remainder of file-size / 32M. * blkStart - The index of the first block to be transmitted, * relative to the first segment to be transmitted. * This will always be zero except in the first * segment in the linked list, in which case it * will be the index of the block that contains * the start of the requested range. * blkCount - The number of blocks in this segment. This * will be 512 except for the last segment. * segHash - The hash of all of the block hashes for this * segment. * blkHash[] - The individual block hashes, one per block. * * segEntries are stored in a linked list until the last * Segment has been processed. We then walk the list in order * to send the Content Info to the client. */ typedef unsigned char hash256[SHA256_DIGEST_LENGTH]; typedef struct { ubi_slNode node; /* Linked List header. */ uint64_t segOffset; /* ullOffsetInContent */ uint32_t segSize; /* cbSegment */ int blkStart; /* First block/seg in range. */ int blkCount; /* Blocks in this segment. */ hash256 segHash; /* SegmentHashOfData */ hash256 blkHash[512]; /* Array of block hashes. */ } segEntry; /* -------------------------------------------------------------------------- ** * Global variables: * * cgi - Pointer (possibly NULL) to an s_cgi structure containing the * parsed out cgi name/value pairs. In this program, we do not * expect to receive any such pairs. * * contentf - FILE pointer to the content file. This is used when we are * generating tags to be returned. * * contentLen - Size of the content file, in bytes. * * rangeOffset - Start of the requested data range. Defaults to zero. * rangeLength - Number of bytes to be included in the range. If this * is zero, then all bytes from the rangeOffset to the * end of file should be included. The default is zero. * * segList - Linked list of segments, including block hashes for each * block in the segment. * * secretStr - Our server-side "secret" string, aka. an "arbitrary length * binary string stored on the server". See the definition * of Server Secret in section 1.1 of [MS-PCCRC]. * * srvrSecret - The SHA-256 hash of the secretStr, aka. the "Server * Secret". See the definition of Server Secret in * [MS-PCCRC], section 1.1. This field must be initialized * from the mainline. * * bufr[] - A read buffer, big enough to hold an entire BC block. */ s_cgi *cgi; FILE *contentf; off_t contentLen; off_t rangeOffset = 0; off_t rangeLength = 0; uint32_t dwRBILS = 0; ubi_slNewList( segList ); static const char *secretStr = "The angels took my racehorse away."; unsigned char srvrSecret[SHA256_DIGEST_LENGTH]; static unsigned char bufr[MAX_64K]; /* -------------------------------------------------------------------------- ** * Static Functions: */ static void say16( uint16_t val ) /* ------------------------------------------------------------------------ ** * Write a 16-bit integer to in Intel byte order. * * Input: val - A 16-bit unsigned integer value. * * Output: * * Notes: The putchar(3) function returns EOF on error. We discard * this result, which is okay for a testing program such as * this one but which would be wrong for production code. * * ------------------------------------------------------------------------ ** */ { (void)putchar( (unsigned char)(val & 0xFF) ); (void)putchar( (unsigned char)((val>>8) & 0xFF) ); } /* say16 */ static void say32( uint32_t val ) /* ------------------------------------------------------------------------ ** * Write a 32-bit integer to in Intel byte order. * * Input: val - A 32-bit unsigned integer value. * * Output: * * Notes: The putchar(3) function returns EOF on error. We discard * this result, which is okay for a testing program such as * this one but which would be wrong for production code. * * ------------------------------------------------------------------------ ** */ { putchar( (unsigned char)( val & 0xFF) ); putchar( (unsigned char)((val>>8 ) & 0xFF) ); putchar( (unsigned char)((val>>16) & 0xFF) ); putchar( (unsigned char)((val>>24) & 0xFF) ); } /* say32 */ static void say64( uint64_t val ) /* ------------------------------------------------------------------------ ** * Write a 64-bit integer to in Intel byte order. * * Input: val - A 64-bit unsigned integer value. * * Output: * * Notes: As above for and . * * ------------------------------------------------------------------------ ** */ { say32( (uint32_t)(val && 0xFFFFFFFF) ); say32( (uint32_t)((val >> 32) && 0xFFFFFFFF) ); } /* say64 */ static void sayBytes( const unsigned char *bytes, const int len ) /* ------------------------------------------------------------------------ ** * Write a length-delimited string of bytes to . * * Input: bytes - A pointer to an array of bytes to be printed. * len - The number of bytes to be printed. * * Output: * * ------------------------------------------------------------------------ ** */ { int i; for( i = 0; i < len; i++ ) { putchar( bytes[i] ); } } /* sayBytes */ static void sayHMAC( const unsigned char *hash ) /* ------------------------------------------------------------------------ ** * Calculate and output the segment signature (Kp), aka Segment Secret. * * Input: hash - A pointer to the segment hash, which is the hash of * the block hashes of the segment. * * Output: * * Notes: must be initialized by a call to * before this function is called. * * See: [MS-PCCRC:2.2]. * * ------------------------------------------------------------------------ ** */ { int i; unsigned char *result; unsigned int result_len; /* Calculate the HMAC-SHA-256 of the input using the as * the key. */ result = HMAC( EVP_sha256(), /* Hash type. */ srvrSecret, /* Key. */ SHA256_DIGEST_LENGTH, /* Key length. */ hash, /* Data. */ SHA256_DIGEST_LENGTH, /* Data length. */ NULL, /* Unused. */ &result_len ); /* Result length. */ /* Output the . */ for( i = 0; i < result_len; i++ ) putchar( result[i] ); } /* sayHMAC */ #ifndef NO_CHECK_PEERDIST static bool hdrContains( const char *hdrname, const char *str ) /* ------------------------------------------------------------------------ ** * Determine whether an HTTP header contains the given substring. * * Input: hdrname - A string containing an HTML header name. HTML * headers are key:value pairs. This is the key. * str - A substring for which to search in the value * portion of the header. * * Output: True if the named header line exists and contains the * substring, otherwise false. * * Notes: The string matching is case-sensitive. * This is a somewhat crude approach. * * The HTML headers are passed to CGI programs as environmental * variables, which is why getenv(3) is used. * * ------------------------------------------------------------------------ ** */ { char *hdrval; hdrval = getenv( hdrname ); if( NULL != hdrval ) { if( NULL != strstr( hdrval, str ) ) return( true ); } return( false ); } /* hdrContains */ #endif /* NO_CHECK_PEERDIST */ static void segSHA256( segEntry *se ) /* ------------------------------------------------------------------------ ** * Calculate the segment hash for a segment. * * Input: se - A pointer to a segment node in the linked list. * The segment node should have all of the block hashes * for the segment already computed. This function will * compute the segment hash over the block hashes. * * Output: * * Notes: This is pretty simple. It just generates the SHA-256 hash * of the block hashes within a segment. * * ------------------------------------------------------------------------ ** */ { int i; SHA256_CTX ctx[1]; SHA256_Init( ctx ); for( i = 0; i < se->blkCount; i++ ) SHA256_Update( ctx, &(se->blkHash[i]), SHA256_DIGEST_LENGTH ); SHA256_Final( se->segHash, ctx ); } /* segSHA256 */ static void blockSHA256( unsigned char *hash, const unsigned char *bufr, const int len ) /* ------------------------------------------------------------------------ ** * Calculate the SHA-256 over an entire block. * * Input: hash - An array of bytes into which the calculated hash will * be written. * bufr - The buffer containing the source bytes to be hashed. * len - The number of bytes in to be included in the * hash. * * Output: * * ------------------------------------------------------------------------ ** */ { SHA256_CTX ctx[1]; SHA256_Init( ctx ); SHA256_Update( ctx, bufr, len ); SHA256_Final( hash, ctx ); } /* blockSHA256 */ static segEntry *newSegEntry( void ) /* ------------------------------------------------------------------------ ** * Allocate and initialize a new segment entry. * * Input: * * Output: A pointer to the new segment entry, or NULL if the entry * could not be allocated. * * Notes: This function adds the new entry to the end of the linked * list of entries. * * A segment entry is a node in a linked list. Each such node * represents a segment of the original content, and contains * all of the block hashes for that segment as well as the * segment hash. * * In this test program, that's how we keep track of the content * information. The right way to do it, however, would be to * store the content information in a file or database. * * ------------------------------------------------------------------------ ** */ { segEntry *s = (segEntry *)calloc( 1, sizeof(segEntry) ); if( s ) (void)ubi_slAddTail( segList, s ); return( s ); } /* newSegEntry */ static void calcHashes( void ) /* ------------------------------------------------------------------------ ** * Read blocks to calculate block hashes, plus segment hashes. * * Input: * * Output: Returns true if the Content Information was generated, false * otherwise. * * Notes: This is the workhorse. It reads the files a block at a time * and calculates the block hashes, which are stored in a * segEntry linked list node. When a segment's worth of blocks * has been read, the segment hash is calculated from the block * hashes. * * ------------------------------------------------------------------------ ** */ { int i, j; size_t result; segEntry *se; off_t tmpOffset; /* Scratch space. */ off_t segStart; /* Index of first segment to send. */ off_t segCount; /* Number of segments to send. */ int blkStart; /* Index of the first block within the first segment. */ int blkCount; /* Number of blocks to send from the last segment. */ /* Start at the right place, based upon the desired range. * segStart is the index of the starting Segment. * tmpOffset is the offset of the starting Segment. * blkStart is the index, relative to the first segment (segStart), * of the first block to be transmitted. */ segStart = rangeOffset / MAX_32M; tmpOffset = segStart * MAX_32M; blkStart = (rangeOffset - tmpOffset) / MAX_64K; (void)fseeko( contentf, tmpOffset, SEEK_SET ); /* Calculate the value of dwReadBytesInLastSegment. * dwRBILS is the number of bytes within the last segment that are * included in the range. */ if( rangeLength ) { dwRBILS = (rangeOffset + rangeLength) % MAX_32M; } /* How many segments and blocks do we need? * tmpOffset is the offset of the last byte of the range. * segCount is the total number of segments to transmit. * blkCount is the maximum number blocks in the last segment that * are to be sent. * If there is only one segment, then dwRBILS (if set) must be reduced * to account for the number of bytes we are skipping. */ tmpOffset = (rangeLength ? (rangeOffset + rangeLength) : contentLen) - 1; segCount = 1 + (tmpOffset / MAX_32M) - segStart; blkCount = (1 + (tmpOffset / MAX_64K)) % (MAX_32M / MAX_64K); if( rangeLength && (1 == segCount) ) dwRBILS -= (rangeOffset % MAX_32M); /* Now read the segments and calculate the hashes. */ for( i = segStart; (i < segCount) && !feof( contentf ); i++ ) { se = newSegEntry(); se->segOffset = (uint64_t)ftello( contentf ); /* Blocks within the segment. */ for( j = 0; (j < 512) && !feof( contentf ); j++ ) { result = fread( bufr, 1, MAX_64K, contentf ); if( result > 0 ) { se->segSize += result; blockSHA256( se->blkHash[se->blkCount], bufr, result ); se->blkCount += 1; } } segSHA256( se ); } /* Insert the blkStart and blkCount values into the first and last entries * in the list. */ se = (segEntry *)ubi_slFirst( segList ); if( se ) se->blkStart = blkStart; if( blkCount > 0 ) { se = (segEntry *)ubi_slLast( segList ); if( se ) se->blkCount = blkCount; } } /* calcHashes */ void sendHashes( void ) /* ------------------------------------------------------------------------ ** * Send BranchCache Content Information instead of content. * * Input: * * Output: * * Notes: The libcgi (see cgi(5)) function cgiSetHeader(3) appears to be * badly borken. When I attempt to format headers using that * function, I get malformed strings in the message. Therefore, * this function bypasses the use of cgiSetHeader(3) and * cgiHeader(3) and manually writes the required additional * header strings. * * ------------------------------------------------------------------------ ** */ { magic_t mcookie; bool typeset = false; int i; segEntry *e; off_t tmpLen; /* If we will send a response representing a subrange rather than the full * content, let the client know. */ if( rangeOffset || rangeLength ) Say( "Status: 206 Partial Content\r\n" ); /* Do our best to send the correct Content-Type header. * Note that this is only required by HTTP[S]. An * SMB2 server doesn't care about the MIME type of * the content. */ mcookie = magic_open( MAGIC_SYMLINK | MAGIC_ERROR | MAGIC_MIME_TYPE ); if( NULL != mcookie ) { if( 0 == magic_load( mcookie, NULL ) ) { /* Fix: The return values should be tested. */ Say( "Content-Type: %s\r\n", magic_file( mcookie, getenv( "PATH_TRANSLATED" ) ) ); typeset = true; } magic_close( mcookie ); } if( !typeset ) Say( "Content-Type: application/octet-stream\r\n" ); /* Default type. */ /* Additional headers (peerdist and otherwise). */ Say( "Content-Encoding: peerdist\r\n" ); if( rangeLength || rangeOffset ) { tmpLen = (rangeLength ? (rangeOffset + rangeLength) : contentLen) - 1; Say( "Content-Range: bytes %ld-%ld/%ld\r\n", rangeOffset, tmpLen, contentLen ); } Say( "X-P2P-PeerDist: Version=1.0, ContentLength=%ld\r\n", rangeLength ? rangeLength : contentLen ); /* Now calculate all of the hashes. * We do this on the fly and must calculate all hashes before * the results can be sent. Windows systems do not start * calculating hashes until the first time the hashes are * requested, so the first request does not receive hashes. * The hashes are stored on-disk and may fall victim to an * LRU algorithm if the cache becomes full. */ calcHashes(); /* Add the content length (of the PeerDist message) and the header is done. * Notes: The v1.0 header is 18 bytes. * Each SegmentDescription is 80 bytes *if* SHA-256 is in use. * Each SegmentContentBlock is 4 bytes plus an array of hashes. * One SegmentContentBlock per Segment means 84 bytes per segment. * If SHA-256 is in use, that's 32 bytes per block. * FIX: We should not need to do this. Chunked Encoding should work, * but stibtest is borken and cannot read large chunked input. * */ tmpLen = 512 * (off_t)(ubi_slCount( segList ) - 1); tmpLen += (off_t)(((segEntry *)ubi_slLast( segList ))->blkCount); tmpLen -= (off_t)(((segEntry *)ubi_slFirst( segList ))->blkStart); tmpLen = 18 + (84 * (off_t)ubi_slCount( segList )) + (32 * tmpLen ); Say( "Content-Length: %ld\r\n", tmpLen ); Say( "\r\n" ); /* Write the Content Information message, in raw binary form, to . * This first chunk includes version and algorithm IDs, plus summary * information, such as the number of segments in the file and the * number of bytes in the last segment. */ say16( 0x0100 ); /* Version Number: 1.0 -> 0x01,0x00 -> 0x0100. */ say32( 0x0000800C ); /* Hash Algorithm to use: 0x800C == SHA256. */ say32( rangeOffset % MAX_32M ); /* dwOffsetInFirstSegment */ say32( dwRBILS ); /* dwReadBytesInLastSegment */ say32( ubi_slCount( segList ) ); /* cSegments */ /* Write a record for each segment. */ for( e = (segEntry *)ubi_slFirst( segList ); (NULL != e); e = (segEntry *)ubi_slNext( e ) ) { say64( e->segOffset ); /* ullOffsetInContent */ say32( e->segSize ); /* cbSegment */ say32( 0x10000 ); /* cbBlockSize */ sayBytes( e->segHash, SHA256_DIGEST_LENGTH ); /* SegmentHashOfData */ sayHMAC( e->segHash ); /* SegmentSecret */ } /* Write a record for each block in the requested range. */ for( e = (segEntry *)ubi_slFirst( segList ); (NULL != e); e = (segEntry *)ubi_slNext( e ) ) { say32( e->blkCount - e->blkStart ); /* cBlocks */ for( i = e->blkStart; i < e->blkCount; i++ ) /* BlockHashes */ sayBytes( e->blkHash[i], SHA256_DIGEST_LENGTH ); } } /* sendHashes */ static bool getRange( const char *range ) /* ------------------------------------------------------------------------ ** * Collect and validate the content range information. * * Input: range - Either NULL, or the right-hand (value) side of the * "Range" header, which should be in the form: * bytes-unit "=" byte-range-set * See RFC2616, section 14.35 (URL below). * * Output: True if the range is valid, else False. * * Notes: The file pathname of the file to be opened should be stored in * the environment variable. * * No "Range" header is considered equivalent to "bytes=0-", * which is valid. * * There is, at present, no behavior defined in [MS-PCCRC] or * [MS-PCCRTP] for handling a list or more than one range. * Windows 2008R2 IIS punts, returns the ranges without peerdist * encoding. Therefore, we consider multple byteranges to be * "invalid" peerdist requests. * * See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 * * ------------------------------------------------------------------------ ** */ { char *pathinfo; struct stat statbufr; const char *s; const char *dash; off_t endOffset; /* We need to retrive the file metadata to find the file size. */ pathinfo = getenv( "PATH_TRANSLATED" ); if( pathinfo && (0 == stat( pathinfo, &statbufr )) ) { contentLen = statbufr.st_size; /* If the file is too small for peerdist, return false. */ if( contentLen < MAX_64K ) return( false ); } else return( false ); /* If there is no Range header, it's still a valid range ("bytes=0-"). */ if( NULL == range ) return( true ); /* If we don't recognize the bytes-unit, bail out. */ if( 0 != strncmp( "bytes=", range, 6 ) ) return( false ); /* Consume the "bytes=" string and any whitespace. */ s = range + 6; while( ' ' == *s ) /* Eat whitespace. */ s++; /* If it's a multirange request, bail out. */ if( NULL != strchr( s, ',' ) ) return( false ); /* If there is no '-' in the string, bail out. */ dash = strchr( s, '-' ); if( NULL == dash ) return( false ); /* See if we have a Suffix byte range. */ if( '-' == *s ) { rangeOffset = contentLen - (off_t)strtoull( (s+1), NULL, 10 ); if( rangeOffset < 0 ) return( false ); return( true ); } /* It must be in offset-last-byte-pos format. * This format is -, where is the starting offset and is * the offset of the last byte in the range. That means that must * be greater than . * Also, can be blank which means "all remaining bytes". */ rangeOffset = (off_t)strtoull( s, NULL, 10 ); if( rangeOffset > contentLen ) return( false ); s = dash + 1; while( ' ' == *s ) s++; if( ('\0' == *s) || ('\n' == *s) ) /* "-" format. */ endOffset = contentLen; else endOffset = (off_t)strtoull( s, NULL, 10 ); if( endOffset < rangeOffset ) /* Can't handle a negative range. */ return( false ); if( endOffset >= contentLen ) /* Range ends beyond eof. */ rangeLength = contentLen - rangeOffset; else /* Normal "-" format. */ rangeLength = (endOffset + 1) - rangeOffset; return( true ); } /* getRange */ static bool openFile( void ) /* ------------------------------------------------------------------------ ** * Open the file specified in the URL, if possible. * * Input: * * Output: True if the file was opened and is large enough to be cached, * else false. * * Notes: If the file could not be opened, or is less than 64K in size, * then this function will return indicating that the * file could not be tagged. * * ------------------------------------------------------------------------ ** */ { char *pathinfo; /* If we fail to open the file, then return false to indicate failure. */ pathinfo = getenv( "PATH_TRANSLATED" ); if( NULL == (contentf = fopen( pathinfo, "r" )) ) return( false ); return( true ); } /* openFile */ int main( int argc, char *argv[] ) /* ------------------------------------------------------------------------ ** * Program mainline. * * Input: argc - You know what this is. * argv - You know what to do. * * Output: Either EXIT_SUCCESS or EXIT_FAILURE. * There is currently no code in this program that produces * EXIT_FAILURE as a result. * * ------------------------------------------------------------------------ ** */ { char *range; cgiDebug( 0, 0 ); /* Set debugging levels. See cgiDebug(3). */ cgi = cgiInit(); /* Initialize the library. May return NULL. */ /* If peerdist is in the acceptable encoding list, * and if we can handle the requested range, * and we can open the requested file, * then we can attempt to send the hashes instead of the content. * Otherwise, just redirect to the content itself. */ range = getenv( "HTTP_RANGE" ); if( IsPeerDist && getRange( range ) && openFile() ) { blockSHA256( srvrSecret, (unsigned char *)secretStr, strlen( secretStr ) ); sendHashes(); } else cgiRedirect( getenv( "PATH_INFO" ) ); cgiFree( cgi ); /* Clean up, even if is NULL. */ return( EXIT_SUCCESS ); /* Finished. */ } /* main */ /* ========================================================================== */