├── .gitignore ├── Makefile ├── README ├── sha1.c └── torrentcheck.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.exe 3 | torrentcheck 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS := -O 2 | 3 | all: torrentcheck 4 | 5 | clean: 6 | rm *.o 7 | 8 | torrentcheck: torrentcheck.o sha1.o 9 | $(CC) -o $@ $^ $(CFLAGS) 10 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | torrentcheck - catalog a .torrent file and optionally verify content hashes 2 | Usage: torrentcheck -t torrent-file [-p content-path] [-n] [-h] [-c] [-d] 3 | Options: -n suppresses progress count, -h shows all hash values, 4 | -c or -d uses comma or dot formatted byte counts. 5 | Returns 0 if successful, nonzero return code if errors found. 6 | 7 | Option: -sha1 [optional hash] acts as a simple SHA1 filter. 8 | If -sha1 is followed by a hex hash, the return code will be zero 9 | on match and nonzero otherwise. 10 | 11 | Summary 12 | ------- 13 | This program is a command-line utility to catalog and verify torrent files. 14 | Run with only the -t option, it displays the metadata, name, and size of 15 | each file in the torrent. Run with the -t and -p options, it computes the 16 | hashes of all files in the torrent, compares them against the hashes stored 17 | in the metadata, and warns of any errors. 18 | 19 | If torrentcheck returns "torrent is good" at the end of its output, every 20 | byte of every file in the torrent is present and correct, to a high degree of 21 | certainty as explained below. 22 | 23 | For example, if you run torrents on a fast external server and then download 24 | the files, this utility will verify that the files you received are complete 25 | and uncorrupted. It can also be used to verify backups or to automatically 26 | check a series of torrents using scripting. 27 | 28 | The -t parameter should be the path to the .torrent metadata file. The -p path 29 | should point to the file or files. It can include or leave out the torrent name. 30 | The -n option suppresses the running count, which is useful if you are writing 31 | the output to a file. The -h option shows all piece hash values. The -c or -d 32 | options produce comma or dot formatted byte counts for readability. 33 | 34 | The -sha1 option disables torrent checking, and instead acts as a SHA1 filter. 35 | Most Windows machines do not have a SHA1 utility, so I included this mode as a 36 | convenience feature. It reads in binary data from standard input until end of 37 | file, and prints the SHA1 hash. If a SHA1 hash is provided on the command line, 38 | it will return 0 if the hashes match or nonzero if they do not. This mode 39 | should agree with the output of "openssl dgst -sha1" or "digest -a sha1" 40 | 41 | Examples 42 | -------- 43 | torrentcheck -t \torrents\ubuntu-10.10-desktop-i386.iso.torrent 44 | torrentcheck -t \torrents\ubuntu-10.10-desktop-i386.iso.torrent -p \download 45 | torrentcheck -t \torrents\ubuntu-10.10-desktop-i386.iso.torrent -p \download && echo good 46 | torrentcheck -t \torrents\ubuntu-10.10-desktop-i386.iso.torrent -p \download || echo bad 47 | torrentcheck -t \torrents\ubuntu-10.10-desktop-i386.iso.torrent -p \download\ubuntu-10.10-desktop-i386.iso 48 | torrentcheck -sha1 < \download\ubuntu-10.10-desktop-i386.iso 49 | torrentcheck -sha1 b28bbd742aff85d21b9ad96bb45b67c2d133be99 < \download\ubuntu-10.10-desktop-i386.iso && echo good 50 | (These are for Windows; use forward slashes in Unix/Linux) 51 | 52 | Automation and scripting 53 | ------------------------ 54 | Torrentcheck returns 0 in the Unix $? return code or Windows errorlevel 55 | if it successfully verifies a torrent, or nonzero return codes if it fails. 56 | 57 | If you have your torrents in \torrents and the downloaded files in \share, 58 | make a "bad" directory under \torrents, cd to \torrents, and run: 59 | 60 | (Windows) 61 | for %i in (*.torrent) do torrentcheck -t "%i" -p \share || move "%i" bad 62 | (Linux) 63 | for i in *.torrent; do torrentcheck -t "$i" -p /share || mv "$i" bad ; done 64 | 65 | This will check all the torrents, and move any that are not fully 66 | downloaded and correct into \torrents\bad. 67 | 68 | Run this command to generate a master list file with the contents of all your 69 | torrents. This file can be searched to find a particular file and which torrent 70 | it comes from. 71 | 72 | (Windows) 73 | for %i in (*.torrent) do torrentcheck -t "%i" >> masterlist.txt & echo. >> masterlist.txt 74 | (Linux) 75 | for i in *.torrent; do torrentcheck -t "$i" >> masterlist.txt ; echo >> masterlist.txt ; done 76 | 77 | Detailed description 78 | -------------------- 79 | BitTorrent is a file sharing system which uses a metadata file, usually with 80 | the .torrent extension, to identify a data file or group of files. Given the 81 | metadata file, a BitTorrent client can download and share the data files. 82 | It can also verify the integrity of the files. 83 | 84 | The metadata file uses an encoding scheme called "bencode" which can store 85 | integers, strings, lists, and key-value pairs. It can represent binary values 86 | without any escaping, so a bencoded string can be loaded into memory and parsed 87 | in place, without any decoding. Torrent metadata contains the names and sizes 88 | of all the files in the torrent, and also contains a series of SHA1 hashes on 89 | each piece of the data file or files. The piece size is specified in the 90 | metadata, ranging from 32KiB (32768) to 4MiB (4194304) in a sample of torrents. 91 | 92 | SHA1 is a complex error-checking code designed by the National Security Agency 93 | for the military Defense Messaging System. It inputs an arbitrarily long byte 94 | string and outputs a 20-byte check code. If any bit in the input changes, the 95 | check code will change. SHA1 is complex enough so that even by deliberate 96 | effort it is very difficult to find two strings with the same check code. The 97 | chance of this happening by accident is small enough to ignore. 98 | 99 | To check a single-file torrent, allocate a buffer equal to the "piece size" 100 | string in the metadata, open the input file identified by the "name" string 101 | or specified on the command line, and read in pieces one at a time. The last 102 | piece will likely be short; keep track of the number of bytes actually read. 103 | Hash each piece, and compare the hash code against the corresponding hash code 104 | in the metadata. Any mismatch is an error. 105 | 106 | To check a multiple-file torrent, allocate a buffer as above. Read files in 107 | order from the "files" list in the metadata and reconstruct the paths, where 108 | the "name" string may be the base directory. Read from each file in sequence 109 | into the buffer until the buffer is full or the last file has been read, then 110 | hash it and check against the list in the metadata. Any mismatch is an error. 111 | 112 | Hash pieces span multiple files, so a missing or corrupt file can cause the 113 | previous or next file to fail as well. In particular, a missing file usually 114 | causes the previous and next files to fail verification. This is an artifact of 115 | the torrent format, and there is no way to avoid it. Torrents often contain a 116 | large media file and a small descriptive text file. If the text file is 117 | missing, the media file usually cannot be verified. 118 | 119 | Torrentcheck also verifies the length of each file, and flags an error if the 120 | length is wrong even if the hash codes match. It is designed to handle files 121 | over 4GB on a 32-bit machine. 122 | 123 | The SHA1 implementation used by torrentcheck was written by David Ireland, 124 | AM Kuchling, and Peter Gutmann. The source code does not contain a copyright 125 | notice, and this file is widely used on the Internet. 126 | 127 | Compiling 128 | --------- 129 | There is no makefile. The required gcc lines are at the top of the 130 | torrentcheck.c source file. The major catch in compiling is making 64-bit file 131 | I/O work. It is tested on Windows, Linux, and Solaris, but you may have to 132 | experiment with compiler options to get 64-bit ftell and fseek working. 133 | -------------------------------------------------------------------------------- /sha1.c: -------------------------------------------------------------------------------- 1 | /* For torrentcheck.c, main() commented out */ 2 | /* sha1.c : Implementation of the Secure Hash Algorithm */ 3 | 4 | /* SHA: NIST's Secure Hash Algorithm */ 5 | 6 | /* This version written November 2000 by David Ireland of 7 | DI Management Services Pty Limited 8 | 9 | Adapted from code in the Python Cryptography Toolkit, 10 | version 1.0.0 by A.M. Kuchling 1995. 11 | */ 12 | 13 | /* AM Kuchling's posting:- 14 | Based on SHA code originally posted to sci.crypt by Peter Gutmann 15 | in message <30ajo5$oe8@ccu2.auckland.ac.nz>. 16 | Modified to test for endianness on creation of SHA objects by AMK. 17 | Also, the original specification of SHA was found to have a weakness 18 | by NSA/NIST. This code implements the fixed version of SHA. 19 | */ 20 | 21 | /* Here's the first paragraph of Peter Gutmann's posting: 22 | 23 | The following is my SHA (FIPS 180) code updated to allow use of the "fixed" 24 | SHA, thanks to Jim Gillogly and an anonymous contributor for the information on 25 | what's changed in the new version. The fix is a simple change which involves 26 | adding a single rotate in the initial expansion function. It is unknown 27 | whether this is an optimal solution to the problem which was discovered in the 28 | SHA or whether it's simply a bandaid which fixes the problem with a minimum of 29 | effort (for example the reengineering of a great many Capstone chips). 30 | */ 31 | 32 | /* h files included here to make this just one file ... */ 33 | 34 | /* global.h */ 35 | 36 | #ifndef _GLOBAL_H_ 37 | #define _GLOBAL_H_ 1 38 | 39 | /* POINTER defines a generic pointer type */ 40 | typedef unsigned char *POINTER; 41 | 42 | /* UINT4 defines a four byte word */ 43 | //typedef unsigned long int UINT4; 44 | typedef unsigned int UINT4; 45 | 46 | /* BYTE defines a unsigned character */ 47 | typedef unsigned char BYTE; 48 | 49 | #ifndef TRUE 50 | #define FALSE 0 51 | #define TRUE ( !FALSE ) 52 | #endif /* TRUE */ 53 | 54 | #endif /* end _GLOBAL_H_ */ 55 | 56 | /* sha.h */ 57 | 58 | #ifndef _SHA_H_ 59 | #define _SHA_H_ 1 60 | 61 | /* #include "global.h" */ 62 | 63 | /* The structure for storing SHS info */ 64 | 65 | typedef struct 66 | { 67 | UINT4 digest[ 5 ]; /* Message digest */ 68 | UINT4 countLo, countHi; /* 64-bit bit count */ 69 | UINT4 data[ 16 ]; /* SHS data buffer */ 70 | int Endianness; 71 | } SHA_CTX; 72 | 73 | /* Message digest functions */ 74 | 75 | void SHAInit(SHA_CTX *); 76 | void SHAUpdate(SHA_CTX *, BYTE *buffer, int count); 77 | void SHAFinal(BYTE *output, SHA_CTX *); 78 | 79 | #endif /* end _SHA_H_ */ 80 | 81 | /* endian.h */ 82 | 83 | #ifndef _ENDIAN_H_ 84 | #define _ENDIAN_H_ 1 85 | 86 | void endianTest(int *endianness); 87 | 88 | #endif /* end _ENDIAN_H_ */ 89 | 90 | /* sha.c */ 91 | 92 | #include 93 | #include 94 | 95 | static void SHAtoByte(BYTE *output, UINT4 *input, unsigned int len); 96 | 97 | /* The SHS block size and message digest sizes, in bytes */ 98 | 99 | #define SHS_DATASIZE 64 100 | #define SHS_DIGESTSIZE 20 101 | 102 | 103 | /* The SHS f()-functions. The f1 and f3 functions can be optimized to 104 | save one boolean operation each - thanks to Rich Schroeppel, 105 | rcs@cs.arizona.edu for discovering this */ 106 | 107 | /*#define f1(x,y,z) ( ( x & y ) | ( ~x & z ) ) // Rounds 0-19 */ 108 | #define f1(x,y,z) ( z ^ ( x & ( y ^ z ) ) ) /* Rounds 0-19 */ 109 | #define f2(x,y,z) ( x ^ y ^ z ) /* Rounds 20-39 */ 110 | /*#define f3(x,y,z) ( ( x & y ) | ( x & z ) | ( y & z ) ) // Rounds 40-59 */ 111 | #define f3(x,y,z) ( ( x & y ) | ( z & ( x | y ) ) ) /* Rounds 40-59 */ 112 | #define f4(x,y,z) ( x ^ y ^ z ) /* Rounds 60-79 */ 113 | 114 | /* The SHS Mysterious Constants */ 115 | 116 | #define K1 0x5A827999L /* Rounds 0-19 */ 117 | #define K2 0x6ED9EBA1L /* Rounds 20-39 */ 118 | #define K3 0x8F1BBCDCL /* Rounds 40-59 */ 119 | #define K4 0xCA62C1D6L /* Rounds 60-79 */ 120 | 121 | /* SHS initial values */ 122 | 123 | #define h0init 0x67452301L 124 | #define h1init 0xEFCDAB89L 125 | #define h2init 0x98BADCFEL 126 | #define h3init 0x10325476L 127 | #define h4init 0xC3D2E1F0L 128 | 129 | /* Note that it may be necessary to add parentheses to these macros if they 130 | are to be called with expressions as arguments */ 131 | /* 32-bit rotate left - kludged with shifts */ 132 | 133 | #define ROTL(n,X) ( ( ( X ) << n ) | ( ( X ) >> ( 32 - n ) ) ) 134 | 135 | /* The initial expanding function. The hash function is defined over an 136 | 80-UINT2 expanded input array W, where the first 16 are copies of the input 137 | data, and the remaining 64 are defined by 138 | 139 | W[ i ] = W[ i - 16 ] ^ W[ i - 14 ] ^ W[ i - 8 ] ^ W[ i - 3 ] 140 | 141 | This implementation generates these values on the fly in a circular 142 | buffer - thanks to Colin Plumb, colin@nyx10.cs.du.edu for this 143 | optimization. 144 | 145 | The updated SHS changes the expanding function by adding a rotate of 1 146 | bit. Thanks to Jim Gillogly, jim@rand.org, and an anonymous contributor 147 | for this information */ 148 | 149 | #define expand(W,i) ( W[ i & 15 ] = ROTL( 1, ( W[ i & 15 ] ^ W[ (i - 14) & 15 ] ^ \ 150 | W[ (i - 8) & 15 ] ^ W[ (i - 3) & 15 ] ) ) ) 151 | 152 | 153 | /* The prototype SHS sub-round. The fundamental sub-round is: 154 | 155 | a' = e + ROTL( 5, a ) + f( b, c, d ) + k + data; 156 | b' = a; 157 | c' = ROTL( 30, b ); 158 | d' = c; 159 | e' = d; 160 | 161 | but this is implemented by unrolling the loop 5 times and renaming the 162 | variables ( e, a, b, c, d ) = ( a', b', c', d', e' ) each iteration. 163 | This code is then replicated 20 times for each of the 4 functions, using 164 | the next 20 values from the W[] array each time */ 165 | 166 | #define subRound(a, b, c, d, e, f, k, data) \ 167 | ( e += ROTL( 5, a ) + f( b, c, d ) + k + data, b = ROTL( 30, b ) ) 168 | 169 | /* Initialize the SHS values */ 170 | 171 | void SHAInit(SHA_CTX *shsInfo) 172 | { 173 | endianTest(&shsInfo->Endianness); 174 | /* Set the h-vars to their initial values */ 175 | shsInfo->digest[ 0 ] = h0init; 176 | shsInfo->digest[ 1 ] = h1init; 177 | shsInfo->digest[ 2 ] = h2init; 178 | shsInfo->digest[ 3 ] = h3init; 179 | shsInfo->digest[ 4 ] = h4init; 180 | 181 | /* Initialise bit count */ 182 | shsInfo->countLo = shsInfo->countHi = 0; 183 | } 184 | 185 | 186 | /* Perform the SHS transformation. Note that this code, like MD5, seems to 187 | break some optimizing compilers due to the complexity of the expressions 188 | and the size of the basic block. It may be necessary to split it into 189 | sections, e.g. based on the four subrounds 190 | 191 | Note that this corrupts the shsInfo->data area */ 192 | 193 | static void SHSTransform( digest, data ) 194 | UINT4 *digest, *data ; 195 | { 196 | UINT4 A, B, C, D, E; /* Local vars */ 197 | UINT4 eData[ 16 ]; /* Expanded data */ 198 | 199 | /* Set up first buffer and local data buffer */ 200 | A = digest[ 0 ]; 201 | B = digest[ 1 ]; 202 | C = digest[ 2 ]; 203 | D = digest[ 3 ]; 204 | E = digest[ 4 ]; 205 | memcpy( (POINTER)eData, (POINTER)data, SHS_DATASIZE ); 206 | 207 | /* Heavy mangling, in 4 sub-rounds of 20 interations each. */ 208 | subRound( A, B, C, D, E, f1, K1, eData[ 0 ] ); 209 | subRound( E, A, B, C, D, f1, K1, eData[ 1 ] ); 210 | subRound( D, E, A, B, C, f1, K1, eData[ 2 ] ); 211 | subRound( C, D, E, A, B, f1, K1, eData[ 3 ] ); 212 | subRound( B, C, D, E, A, f1, K1, eData[ 4 ] ); 213 | subRound( A, B, C, D, E, f1, K1, eData[ 5 ] ); 214 | subRound( E, A, B, C, D, f1, K1, eData[ 6 ] ); 215 | subRound( D, E, A, B, C, f1, K1, eData[ 7 ] ); 216 | subRound( C, D, E, A, B, f1, K1, eData[ 8 ] ); 217 | subRound( B, C, D, E, A, f1, K1, eData[ 9 ] ); 218 | subRound( A, B, C, D, E, f1, K1, eData[ 10 ] ); 219 | subRound( E, A, B, C, D, f1, K1, eData[ 11 ] ); 220 | subRound( D, E, A, B, C, f1, K1, eData[ 12 ] ); 221 | subRound( C, D, E, A, B, f1, K1, eData[ 13 ] ); 222 | subRound( B, C, D, E, A, f1, K1, eData[ 14 ] ); 223 | subRound( A, B, C, D, E, f1, K1, eData[ 15 ] ); 224 | subRound( E, A, B, C, D, f1, K1, expand( eData, 16 ) ); 225 | subRound( D, E, A, B, C, f1, K1, expand( eData, 17 ) ); 226 | subRound( C, D, E, A, B, f1, K1, expand( eData, 18 ) ); 227 | subRound( B, C, D, E, A, f1, K1, expand( eData, 19 ) ); 228 | 229 | subRound( A, B, C, D, E, f2, K2, expand( eData, 20 ) ); 230 | subRound( E, A, B, C, D, f2, K2, expand( eData, 21 ) ); 231 | subRound( D, E, A, B, C, f2, K2, expand( eData, 22 ) ); 232 | subRound( C, D, E, A, B, f2, K2, expand( eData, 23 ) ); 233 | subRound( B, C, D, E, A, f2, K2, expand( eData, 24 ) ); 234 | subRound( A, B, C, D, E, f2, K2, expand( eData, 25 ) ); 235 | subRound( E, A, B, C, D, f2, K2, expand( eData, 26 ) ); 236 | subRound( D, E, A, B, C, f2, K2, expand( eData, 27 ) ); 237 | subRound( C, D, E, A, B, f2, K2, expand( eData, 28 ) ); 238 | subRound( B, C, D, E, A, f2, K2, expand( eData, 29 ) ); 239 | subRound( A, B, C, D, E, f2, K2, expand( eData, 30 ) ); 240 | subRound( E, A, B, C, D, f2, K2, expand( eData, 31 ) ); 241 | subRound( D, E, A, B, C, f2, K2, expand( eData, 32 ) ); 242 | subRound( C, D, E, A, B, f2, K2, expand( eData, 33 ) ); 243 | subRound( B, C, D, E, A, f2, K2, expand( eData, 34 ) ); 244 | subRound( A, B, C, D, E, f2, K2, expand( eData, 35 ) ); 245 | subRound( E, A, B, C, D, f2, K2, expand( eData, 36 ) ); 246 | subRound( D, E, A, B, C, f2, K2, expand( eData, 37 ) ); 247 | subRound( C, D, E, A, B, f2, K2, expand( eData, 38 ) ); 248 | subRound( B, C, D, E, A, f2, K2, expand( eData, 39 ) ); 249 | 250 | subRound( A, B, C, D, E, f3, K3, expand( eData, 40 ) ); 251 | subRound( E, A, B, C, D, f3, K3, expand( eData, 41 ) ); 252 | subRound( D, E, A, B, C, f3, K3, expand( eData, 42 ) ); 253 | subRound( C, D, E, A, B, f3, K3, expand( eData, 43 ) ); 254 | subRound( B, C, D, E, A, f3, K3, expand( eData, 44 ) ); 255 | subRound( A, B, C, D, E, f3, K3, expand( eData, 45 ) ); 256 | subRound( E, A, B, C, D, f3, K3, expand( eData, 46 ) ); 257 | subRound( D, E, A, B, C, f3, K3, expand( eData, 47 ) ); 258 | subRound( C, D, E, A, B, f3, K3, expand( eData, 48 ) ); 259 | subRound( B, C, D, E, A, f3, K3, expand( eData, 49 ) ); 260 | subRound( A, B, C, D, E, f3, K3, expand( eData, 50 ) ); 261 | subRound( E, A, B, C, D, f3, K3, expand( eData, 51 ) ); 262 | subRound( D, E, A, B, C, f3, K3, expand( eData, 52 ) ); 263 | subRound( C, D, E, A, B, f3, K3, expand( eData, 53 ) ); 264 | subRound( B, C, D, E, A, f3, K3, expand( eData, 54 ) ); 265 | subRound( A, B, C, D, E, f3, K3, expand( eData, 55 ) ); 266 | subRound( E, A, B, C, D, f3, K3, expand( eData, 56 ) ); 267 | subRound( D, E, A, B, C, f3, K3, expand( eData, 57 ) ); 268 | subRound( C, D, E, A, B, f3, K3, expand( eData, 58 ) ); 269 | subRound( B, C, D, E, A, f3, K3, expand( eData, 59 ) ); 270 | 271 | subRound( A, B, C, D, E, f4, K4, expand( eData, 60 ) ); 272 | subRound( E, A, B, C, D, f4, K4, expand( eData, 61 ) ); 273 | subRound( D, E, A, B, C, f4, K4, expand( eData, 62 ) ); 274 | subRound( C, D, E, A, B, f4, K4, expand( eData, 63 ) ); 275 | subRound( B, C, D, E, A, f4, K4, expand( eData, 64 ) ); 276 | subRound( A, B, C, D, E, f4, K4, expand( eData, 65 ) ); 277 | subRound( E, A, B, C, D, f4, K4, expand( eData, 66 ) ); 278 | subRound( D, E, A, B, C, f4, K4, expand( eData, 67 ) ); 279 | subRound( C, D, E, A, B, f4, K4, expand( eData, 68 ) ); 280 | subRound( B, C, D, E, A, f4, K4, expand( eData, 69 ) ); 281 | subRound( A, B, C, D, E, f4, K4, expand( eData, 70 ) ); 282 | subRound( E, A, B, C, D, f4, K4, expand( eData, 71 ) ); 283 | subRound( D, E, A, B, C, f4, K4, expand( eData, 72 ) ); 284 | subRound( C, D, E, A, B, f4, K4, expand( eData, 73 ) ); 285 | subRound( B, C, D, E, A, f4, K4, expand( eData, 74 ) ); 286 | subRound( A, B, C, D, E, f4, K4, expand( eData, 75 ) ); 287 | subRound( E, A, B, C, D, f4, K4, expand( eData, 76 ) ); 288 | subRound( D, E, A, B, C, f4, K4, expand( eData, 77 ) ); 289 | subRound( C, D, E, A, B, f4, K4, expand( eData, 78 ) ); 290 | subRound( B, C, D, E, A, f4, K4, expand( eData, 79 ) ); 291 | 292 | /* Build message digest */ 293 | digest[ 0 ] += A; 294 | digest[ 1 ] += B; 295 | digest[ 2 ] += C; 296 | digest[ 3 ] += D; 297 | digest[ 4 ] += E; 298 | } 299 | 300 | /* When run on a little-endian CPU we need to perform byte reversal on an 301 | array of long words. */ 302 | 303 | static void longReverse(UINT4 *buffer, int byteCount, int Endianness ) 304 | { 305 | UINT4 value; 306 | 307 | if (Endianness==TRUE) return; 308 | byteCount /= sizeof( UINT4 ); 309 | while( byteCount-- ) 310 | { 311 | value = *buffer; 312 | value = ( ( value & 0xFF00FF00L ) >> 8 ) | \ 313 | ( ( value & 0x00FF00FFL ) << 8 ); 314 | *buffer++ = ( value << 16 ) | ( value >> 16 ); 315 | } 316 | } 317 | 318 | /* Update SHS for a block of data */ 319 | 320 | void SHAUpdate(SHA_CTX *shsInfo, BYTE *buffer, int count) 321 | { 322 | UINT4 tmp; 323 | int dataCount; 324 | 325 | /* Update bitcount */ 326 | tmp = shsInfo->countLo; 327 | if ( ( shsInfo->countLo = tmp + ( ( UINT4 ) count << 3 ) ) < tmp ) 328 | shsInfo->countHi++; /* Carry from low to high */ 329 | shsInfo->countHi += count >> 29; 330 | 331 | /* Get count of bytes already in data */ 332 | dataCount = ( int ) ( tmp >> 3 ) & 0x3F; 333 | 334 | /* Handle any leading odd-sized chunks */ 335 | if( dataCount ) 336 | { 337 | BYTE *p = ( BYTE * ) shsInfo->data + dataCount; 338 | 339 | dataCount = SHS_DATASIZE - dataCount; 340 | if( count < dataCount ) 341 | { 342 | memcpy( p, buffer, count ); 343 | return; 344 | } 345 | memcpy( p, buffer, dataCount ); 346 | longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness); 347 | SHSTransform( shsInfo->digest, shsInfo->data ); 348 | buffer += dataCount; 349 | count -= dataCount; 350 | } 351 | 352 | /* Process data in SHS_DATASIZE chunks */ 353 | while( count >= SHS_DATASIZE ) 354 | { 355 | memcpy( (POINTER)shsInfo->data, (POINTER)buffer, SHS_DATASIZE ); 356 | longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness ); 357 | SHSTransform( shsInfo->digest, shsInfo->data ); 358 | buffer += SHS_DATASIZE; 359 | count -= SHS_DATASIZE; 360 | } 361 | 362 | /* Handle any remaining bytes of data. */ 363 | memcpy( (POINTER)shsInfo->data, (POINTER)buffer, count ); 364 | } 365 | 366 | /* Final wrapup - pad to SHS_DATASIZE-byte boundary with the bit pattern 367 | 1 0* (64-bit count of bits processed, MSB-first) */ 368 | 369 | void SHAFinal(BYTE *output, SHA_CTX *shsInfo) 370 | { 371 | int count; 372 | BYTE *dataPtr; 373 | 374 | /* Compute number of bytes mod 64 */ 375 | count = ( int ) shsInfo->countLo; 376 | count = ( count >> 3 ) & 0x3F; 377 | 378 | /* Set the first char of padding to 0x80. This is safe since there is 379 | always at least one byte free */ 380 | dataPtr = ( BYTE * ) shsInfo->data + count; 381 | *dataPtr++ = 0x80; 382 | 383 | /* Bytes of padding needed to make 64 bytes */ 384 | count = SHS_DATASIZE - 1 - count; 385 | 386 | /* Pad out to 56 mod 64 */ 387 | if( count < 8 ) 388 | { 389 | /* Two lots of padding: Pad the first block to 64 bytes */ 390 | memset( dataPtr, 0, count ); 391 | longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness ); 392 | SHSTransform( shsInfo->digest, shsInfo->data ); 393 | 394 | /* Now fill the next block with 56 bytes */ 395 | memset( (POINTER)shsInfo->data, 0, SHS_DATASIZE - 8 ); 396 | } 397 | else 398 | /* Pad block to 56 bytes */ 399 | memset( dataPtr, 0, count - 8 ); 400 | 401 | /* Append length in bits and transform */ 402 | shsInfo->data[ 14 ] = shsInfo->countHi; 403 | shsInfo->data[ 15 ] = shsInfo->countLo; 404 | 405 | longReverse( shsInfo->data, SHS_DATASIZE - 8, shsInfo->Endianness ); 406 | SHSTransform( shsInfo->digest, shsInfo->data ); 407 | 408 | /* Output to an array of bytes */ 409 | SHAtoByte(output, shsInfo->digest, SHS_DIGESTSIZE); 410 | 411 | /* Zeroise sensitive stuff */ 412 | memset((POINTER)shsInfo, 0, sizeof(shsInfo)); 413 | } 414 | 415 | static void SHAtoByte(BYTE *output, UINT4 *input, unsigned int len) 416 | { /* Output SHA digest in byte array */ 417 | unsigned int i, j; 418 | 419 | for(i = 0, j = 0; j < len; i++, j += 4) 420 | { 421 | output[j+3] = (BYTE)( input[i] & 0xff); 422 | output[j+2] = (BYTE)((input[i] >> 8 ) & 0xff); 423 | output[j+1] = (BYTE)((input[i] >> 16) & 0xff); 424 | output[j ] = (BYTE)((input[i] >> 24) & 0xff); 425 | } 426 | } 427 | 428 | 429 | //unsigned char digest[20]; 430 | //unsigned char message[3] = {'a', 'b', 'c' }; 431 | //unsigned char *mess56 = 432 | // "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; 433 | 434 | /* Correct solutions from FIPS PUB 180-1 */ 435 | //char *dig1 = "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D"; 436 | //char *dig2 = "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1"; 437 | //char *dig3 = "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F"; 438 | 439 | /* Output should look like:- 440 | a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d 441 | A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D <= correct 442 | 84983e44 1c3bd26e baae4aa1 f95129e5 e54670f1 443 | 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 <= correct 444 | 34aa973c d4c4daa4 f61eeb2b dbad2731 6534016f 445 | 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F <= correct 446 | */ 447 | 448 | //main() 449 | //{ 450 | // SHA_CTX sha; 451 | // int i; 452 | // BYTE big[1000]; 453 | // 454 | // SHAInit(&sha); 455 | // SHAUpdate(&sha, message, 3); 456 | // SHAFinal(digest, &sha); 457 | // 458 | // for (i = 0; i < 20; i++) 459 | // { 460 | // if ((i % 4) == 0) printf(" "); 461 | // printf("%02x", digest[i]); 462 | // } 463 | // printf("\n"); 464 | // printf(" %s <= correct\n", dig1); 465 | // 466 | // SHAInit(&sha); 467 | // SHAUpdate(&sha, mess56, 56); 468 | // SHAFinal(digest, &sha); 469 | // 470 | // for (i = 0; i < 20; i++) 471 | // { 472 | // if ((i % 4) == 0) printf(" "); 473 | // printf("%02x", digest[i]); 474 | // } 475 | // printf("\n"); 476 | // printf(" %s <= correct\n", dig2); 477 | // 478 | // /* Fill up big array */ 479 | // for (i = 0; i < 1000; i++) 480 | // big[i] = 'a'; 481 | // 482 | // SHAInit(&sha); 483 | // /* Digest 1 million x 'a' */ 484 | // for (i = 0; i < 1000; i++) 485 | // SHAUpdate(&sha, big, 1000); 486 | // SHAFinal(digest, &sha); 487 | // 488 | // for (i = 0; i < 20; i++) 489 | // { 490 | // if ((i % 4) == 0) printf(" "); 491 | // printf("%02x", digest[i]); 492 | // } 493 | // printf("\n"); 494 | // printf(" %s <= correct\n", dig3); 495 | // 496 | // return 0; 497 | //} 498 | 499 | /* endian.c */ 500 | 501 | void endianTest(int *endian_ness) 502 | { 503 | if((*(unsigned short *) ("#S") >> 8) == '#') 504 | { 505 | /* printf("Big endian = no change\n"); */ 506 | *endian_ness = !(0); 507 | } 508 | else 509 | { 510 | /* printf("Little endian = swap\n"); */ 511 | *endian_ness = 0; 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /torrentcheck.c: -------------------------------------------------------------------------------- 1 | // torrentcheck.c 2 | // Version 1.00 Mike Ingle 2010-12-01 3 | // 4 | // To build: 5 | // Linux/unix gcc 6 | // You may have to experiment to get 64-bit fseek/ftell working. 7 | // gcc -O torrentcheck.c sha1.c -o torrentcheck 8 | // gcc -O -Dfopen=fopen64 -D_FILE_OFFSET_BITS=64 -DUSE_FTELLO torrentcheck.c sha1.c -o torrentcheck 9 | // 10 | // For win32 mingw "gcc version 4.5.0": 11 | // gcc -O -D_FILE_OFFSET_BITS=64 -DUSE_FTELLO64 -DWIN32 torrentcheck.c sha1.c -o torrentcheck 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | // Begin required for SHA1 22 | typedef unsigned char *POINTER; 23 | typedef unsigned int UINT4; 24 | typedef unsigned char BYTE; 25 | typedef struct 26 | { 27 | UINT4 digest[ 5 ]; /* Message digest */ 28 | UINT4 countLo, countHi; /* 64-bit bit count */ 29 | UINT4 data[ 16 ]; /* SHS data buffer */ 30 | int Endianness; 31 | } SHA_CTX; 32 | 33 | extern void SHAInit(SHA_CTX *); 34 | extern void SHAUpdate(SHA_CTX *, BYTE *buffer, int count); 35 | extern void SHAFinal(BYTE *output, SHA_CTX *); 36 | #define SHA1_LEN 20 37 | // End required for SHA1 38 | 39 | // For filter mode only, torrent buffer length depends on the torrent 40 | #define inputBufLen 262144 41 | 42 | // Portability 43 | #ifdef NATIVE64BIT 44 | typedef long int INT64; 45 | #else 46 | typedef long long INT64; 47 | #endif 48 | 49 | #ifdef USE_FTELLO64 50 | #define tc_fseek fseeko64 51 | #define tc_ftell ftello64 52 | #else 53 | #ifdef USE_FTELLO 54 | #define tc_fseek fseeko 55 | #define tc_ftell ftello 56 | #else 57 | #define tc_fseek fseek 58 | #define tc_ftell ftell 59 | #endif 60 | #endif 61 | 62 | #ifdef WIN32 63 | #define DIR_SEPARATOR '\\' 64 | #else 65 | #define DIR_SEPARATOR '/' 66 | #endif 67 | 68 | typedef struct { 69 | char* filePath; 70 | INT64 numBytes; 71 | int errorsFound; 72 | } fileRecord; 73 | 74 | // Extracts the integer 75 | // Returns the new offset into the input, 76 | // or -1 if unable to parse the input 77 | int beParseInteger(BYTE* benstr,int benstrLen,int benstrOffset,INT64* longOut) { 78 | INT64 foundValue = 0; 79 | INT64 negPos = 1; 80 | int i; 81 | BYTE b; 82 | for(i=benstrOffset;i benstrLen) { 124 | return (-1); 125 | } else { 126 | *stringBegin = benstr + i + 1; 127 | *stringLen = foundLen; 128 | return (i + 1 + foundLen); 129 | } 130 | default: 131 | return (-1); 132 | } 133 | } 134 | return (-1); 135 | } 136 | 137 | 138 | //// Return offset of an element in a list, or -1 if not found 139 | //int beFindInList(BYTE* benstr,int benstrLen,int benstrOffset,int listIndex) { 140 | // int i; 141 | // if ((benstrOffset < 0)||(benstrOffset >= benstrLen)) return (-1); 142 | // if (benstr[benstrOffset] != 'l') return (-1); 143 | // benstrOffset++; 144 | // if (benstr[benstrOffset] == 'e') return (-1); 145 | // for(i=0;i= benstrLen)) return (-1); 148 | // } 149 | // if (benstr[benstrOffset] == 'e') return (-1); 150 | // return (benstrOffset); 151 | //} 152 | 153 | 154 | // Return offset of an element in a dict, or -1 if not found 155 | // dictKey is a null-terminated string 156 | int beFindInDict(BYTE* benstr,int benstrLen,int benstrOffset,BYTE* dictKey) { 157 | BYTE* stringPtr; 158 | int stringLen; 159 | int dictKeyLen; 160 | 161 | if ((benstrOffset < 0)||(benstrOffset >= benstrLen)) return (-1); 162 | if (benstr[benstrOffset] != 'd') return (-1); 163 | dictKeyLen = strlen(dictKey); 164 | benstrOffset++; 165 | while ((benstrOffset >= 0)&&(benstrOffset < benstrLen)) { 166 | if (benstr[benstrOffset] == 'e') return (-1); 167 | benstrOffset = beParseString(benstr,benstrLen,benstrOffset,&stringPtr,&stringLen); 168 | if (benstrOffset < 0) return (-1); 169 | if ((stringLen == dictKeyLen) && (memcmp(stringPtr,dictKey,stringLen) == 0)) { 170 | return (benstrOffset); 171 | } else { 172 | benstrOffset = beStepOver(benstr,benstrLen,benstrOffset); 173 | } 174 | } 175 | return (-1); 176 | } 177 | 178 | 179 | // Step over an object (including all its embedded objects) 180 | // Returns new offset, or -1 if unable to parse the input 181 | int beStepOver(BYTE* benstr,int benstrLen,int benstrOffset) { 182 | BYTE* bp; 183 | int ip; 184 | if ((benstrOffset < 0)||(benstrOffset >= benstrLen)) return (-1); 185 | switch (benstr[benstrOffset]) { 186 | case 'i': 187 | benstrOffset ++; 188 | while(benstrOffset < benstrLen) { 189 | switch (benstr[benstrOffset]) { 190 | case '0': case '1': case '2': case '3': case '4': 191 | case '5': case '6': case '7': case '8': case '9': 192 | case '-': 193 | benstrOffset++; 194 | break; 195 | case 'e': 196 | benstrOffset++; 197 | if (benstrOffset < benstrLen) return benstrOffset; 198 | else return (-1); 199 | default: 200 | return (-1); 201 | } 202 | } 203 | return (-1); 204 | case '0': case '1': case '2': case '3': case '4': 205 | case '5': case '6': case '7': case '8': case '9': 206 | benstrOffset = beParseString(benstr,benstrLen,benstrOffset,&bp,&ip); 207 | if ((benstrOffset < 0) || (benstrOffset >= benstrLen)) return(-1); 208 | else return benstrOffset; 209 | case 'l': 210 | case 'd': 211 | benstrOffset++; 212 | while (benstrOffset < benstrLen) { 213 | if (benstr[benstrOffset] == 'e') { 214 | benstrOffset++; 215 | if (benstrOffset <= benstrLen) return benstrOffset; 216 | else return (-1); 217 | } else { 218 | benstrOffset = beStepOver(benstr,benstrLen,benstrOffset); 219 | if ((benstrOffset < 0) || (benstrOffset >= benstrLen)) return (-1); 220 | } 221 | } 222 | return (-1); 223 | default: 224 | return (-1); 225 | } 226 | } 227 | 228 | 229 | char* print64(INT64 val,char* buf,char useCommaDot) { 230 | INT64 divisor = 1000000000000000000l; 231 | char* bp = buf; 232 | int nonzero = 0; 233 | int digit; 234 | if (val < 0) { 235 | *bp = '-'; bp++; 236 | val = 0 - val; 237 | } 238 | while(divisor > 0) { 239 | digit = val / divisor; 240 | if (digit != 0) nonzero = 1; 241 | if (nonzero) { 242 | *bp = '0' + digit; bp++; 243 | if (useCommaDot&&((divisor == 1000l)||(divisor == 1000000l)|| 244 | (divisor == 1000000000l)||(divisor == 1000000000000l))) { 245 | *bp = useCommaDot; bp++; 246 | } 247 | } 248 | val -= digit * divisor; 249 | divisor /= 10; 250 | } 251 | if (nonzero == 0) { 252 | *bp = '0' + digit; bp++; 253 | } 254 | *bp = 0; 255 | return buf; 256 | } 257 | 258 | 259 | void backspaceProgressLine(int *showProgressChars) { // remove the progress line by backspacing 260 | if (*showProgressChars > 0) { 261 | fwrite("\ 262 | \010\010\010\010\010\010\010\010\010\010\010\010\010\010\010\010\ 263 | \010\010\010\010\010\010\010\010\010\010\010\010\010\010\010\010\ 264 | \010\010\010\010\010\010\010\010\010\010\010\010\010\010\010\010",*showProgressChars,1,stdout); 265 | *showProgressChars = 0; 266 | } 267 | } 268 | 269 | 270 | int sha1Filter(char* compareHash) { 271 | BYTE inputBuffer[inputBufLen]; 272 | SHA_CTX sha1ctx; 273 | unsigned char sha1hash[SHA1_LEN]; 274 | char hexHash[48]; 275 | int bytesRead = 0; 276 | #ifdef WIN32 277 | // Not portable, but then neither is Windows. 278 | _setmode(_fileno(stdin), _O_BINARY); // http://oldwiki.mingw.org/index.php/binary 279 | #endif 280 | SHAInit(&sha1ctx); 281 | while(!feof(stdin)) { 282 | bytesRead = fread(inputBuffer,1,inputBufLen,stdin); 283 | SHAUpdate(&sha1ctx,inputBuffer,bytesRead); 284 | } 285 | SHAFinal(sha1hash,&sha1ctx); 286 | sprintf(hexHash, 287 | "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 288 | (int)sha1hash[0], (int)sha1hash[1], (int)sha1hash[2], (int)sha1hash[3], 289 | (int)sha1hash[4], (int)sha1hash[5], (int)sha1hash[6], (int)sha1hash[7], 290 | (int)sha1hash[8], (int)sha1hash[9], (int)sha1hash[10], (int)sha1hash[11], 291 | (int)sha1hash[12], (int)sha1hash[13], (int)sha1hash[14], (int)sha1hash[15], 292 | (int)sha1hash[16], (int)sha1hash[17], (int)sha1hash[18], (int)sha1hash[19]); 293 | printf("%s\n",hexHash); 294 | if (compareHash == NULL) return 0; 295 | if (strcasecmp(hexHash,compareHash) == 0) return 0; 296 | return 1; 297 | } 298 | 299 | 300 | int main(int argc,char* argv[]) { 301 | char* torrentFile = NULL; 302 | char* contentPath = NULL; 303 | int contentPathLen = 0; 304 | FILE* fp; 305 | BYTE* torrent; 306 | int torrentLen; 307 | int i; 308 | INT64 bytesRead = -1; 309 | unsigned char sha1hash[SHA1_LEN]; 310 | int torrentInfo = -1; 311 | int ofs = -1; 312 | int ofs2 = -1; 313 | int torrentPieceLen = -1; 314 | int lastFile = 0; 315 | int torrentFiles = -1; 316 | int thisFileOffset = -1; 317 | int multiFileTorrent = 0; 318 | int filterMode = 0; 319 | char* filterHash = NULL; 320 | int readLen; 321 | INT64 torrentPrivate = 0; 322 | INT64 fileBytesExpected = -1; 323 | INT64 fileBytesActual = -1; 324 | INT64 fileBytesRead = -1; 325 | BYTE* pieceList = NULL; 326 | int pieceListLen = -1; 327 | BYTE* pieceBuf = NULL; 328 | int bytesInBuf = 0; 329 | int bytesToRead = 0; 330 | BYTE* rootName = NULL; 331 | int rootNameLen = -1; 332 | BYTE* announce = NULL; 333 | int announceLen = -1; 334 | int numPieces = -1; 335 | int numFiles = 0; 336 | INT64 pieceLen = -1; 337 | BYTE* fileName = NULL; 338 | int fileNameLen = -1; 339 | char* filePath; 340 | char* filePath2; 341 | char* filePathActual; 342 | int filePathMax = 8192; 343 | int filePathOfs; 344 | INT64 totalBytes = 0; 345 | INT64 totalBytesDone = 0; 346 | int piecesDone = 0; 347 | int errorsFound = 0; 348 | int errorsFoundThisFile = 0; 349 | int piecesChecked = 0; 350 | int firstFileThisPiece = 0; 351 | int currentFile = 0; 352 | fileRecord* fileRecordList = NULL; 353 | int maxFileRecords = 16; 354 | int showProgressCount = 1; 355 | int showPieceDetail = 0; 356 | char useCommaDot = 0; 357 | int thisPieceBad = 0; 358 | int showProgressChars = 0; 359 | char progressBuf[48]; 360 | char p64Buf1[32]; 361 | char p64Buf2[32]; 362 | SHA_CTX sha1ctx; 363 | // 364 | char* encoding = NULL; 365 | iconv_t convDescriptor; 366 | char *inPtr, *outPtr; 367 | size_t inBytesLeft, outBytesLeft; 368 | char filePathUTF8[1024]; 369 | int filePathUTF8Len; 370 | 371 | // Check the build for a working hasher and correct word lengths 372 | SHAInit(&sha1ctx); 373 | SHAUpdate(&sha1ctx,"testing SHA1 hash",17); 374 | SHAFinal(sha1hash,&sha1ctx); 375 | if (memcmp(sha1hash,"\xac\x1f\xd0\xda\xea\x37\x65\x87\x9a\xde\xfa\x33\x38\x62\x71\xf3\x85\x08\xa8\xbd",SHA1_LEN) != 0) errorsFound++; 376 | SHAInit(&sha1ctx); 377 | SHAUpdate(&sha1ctx,"~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#\"! !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",189); 378 | SHAFinal(sha1hash,&sha1ctx); 379 | if (memcmp(sha1hash,"\x11\xe5\x6b\x84\xd8\xda\xb8\x93\xcd\x8e\x2d\x85\xe4\x3c\xc0\x0d\x5a\xd1\xbb\x78",SHA1_LEN) != 0) errorsFound++; 380 | if (errorsFound > 0) { 381 | printf("SHA1 function test failed - this build is faulty!\n"); 382 | return 3; 383 | } 384 | if ((sizeof(INT64) != 8) || (sizeof(UINT4) != 4)) { 385 | printf("Wrong word length UINT4=%zu INT64=%zu - this build is faulty!\n",sizeof(UINT4),sizeof(INT64)); 386 | return 3; 387 | } 388 | 389 | for(i=1;i\n"); 435 | return 3; 436 | } 437 | 438 | filePath = malloc(filePathMax); 439 | if (filePath == NULL) { 440 | printf("Unable to malloc %i bytes for file path\n",filePathMax); 441 | return 2; 442 | } 443 | 444 | fp = fopen(torrentFile,"rb"); 445 | if (fp == NULL) { 446 | printf("Failed to open torrent metadata file %s\n",torrentFile); 447 | return 2; 448 | } 449 | 450 | tc_fseek(fp,0l,SEEK_END); 451 | torrentLen = (int) tc_ftell(fp); 452 | rewind(fp); 453 | if (torrentLen > 16777216) { 454 | printf("Torrent metadata file %s is %i bytes long.\n",torrentFile,torrentLen); 455 | printf("That is unusually large for a torrent file. You may have specified an\n"); 456 | printf("incorrect file. The metadata must be loaded into memory, so this may\n"); 457 | printf("take some time or fail due to lack of memory.\n"); 458 | printf("\n"); 459 | } 460 | 461 | torrent = malloc(torrentLen); 462 | if (torrent == NULL) { 463 | printf("Unable to malloc %i bytes for torrent metadata\n",torrentLen); 464 | return 2; 465 | } 466 | bytesRead = fread(torrent,1,torrentLen,fp); 467 | if (fp != NULL) fclose(fp); fp = NULL; 468 | i = beStepOver(torrent,torrentLen,0); 469 | if (i != torrentLen) { 470 | printf("Unable to parse torrent metadata file %s\n",torrentFile); 471 | return 2; 472 | } 473 | 474 | torrentInfo = beFindInDict(torrent,torrentLen,0,"info"); 475 | if (torrentInfo < 0) { 476 | printf("Unable to read \"info\" from torrent\n"); 477 | return 2; 478 | } 479 | 480 | // name.utf-8 > name 481 | ofs = beFindInDict(torrent,torrentLen,torrentInfo,"name.utf-8"); 482 | if (ofs >= 0) { 483 | ofs = beParseString(torrent,torrentLen,ofs,&rootName,&rootNameLen); 484 | } 485 | if (ofs < 0) { 486 | ofs = beFindInDict(torrent,torrentLen,torrentInfo,"name"); 487 | if (ofs >= 0) { 488 | ofs = beParseString(torrent,torrentLen,ofs,&rootName,&rootNameLen); 489 | } 490 | else { 491 | printf("Unable to read \"name\" from torrent\n"); 492 | return 2; 493 | } 494 | } 495 | 496 | ofs = beFindInDict(torrent,torrentLen,torrentInfo,"private"); 497 | if (ofs >= 0) { 498 | ofs = beParseInteger(torrent,torrentLen,ofs,&torrentPrivate); 499 | } 500 | 501 | ofs = beFindInDict(torrent,torrentLen,torrentInfo,"length"); 502 | if (ofs >= 0) { // single file 503 | ofs = beParseInteger(torrent,torrentLen,ofs,&fileBytesExpected); 504 | totalBytes = fileBytesExpected; 505 | } else { // multi file 506 | multiFileTorrent = 1; 507 | torrentFiles = beFindInDict(torrent,torrentLen,torrentInfo,"files"); 508 | 509 | // Count files 510 | thisFileOffset = torrentFiles; 511 | if ((thisFileOffset >= 0)&&(thisFileOffset < torrentLen-1) && 512 | (torrent[thisFileOffset] == 'l')) thisFileOffset++; 513 | while ((thisFileOffset >= 0)&&(thisFileOffset < torrentLen)) { 514 | if (torrent[thisFileOffset] != 'd') break; 515 | thisFileOffset = beStepOver(torrent,torrentLen,thisFileOffset); 516 | numFiles ++; 517 | } 518 | 519 | fileRecordList = malloc(numFiles * sizeof(fileRecord)); 520 | if (torrent == NULL) { 521 | printf("Unable to malloc %zu bytes for file record list\n",numFiles * sizeof(fileRecord)); 522 | return 2; 523 | } 524 | 525 | // Catalog individual files and sum length 526 | thisFileOffset = torrentFiles; 527 | currentFile = 0; 528 | if ((thisFileOffset >= 0)&&(thisFileOffset < torrentLen-1) && 529 | (torrent[thisFileOffset] == 'l')) thisFileOffset++; 530 | while ((thisFileOffset >= 0)&&(thisFileOffset < torrentLen)) { 531 | if (torrent[thisFileOffset] != 'd') break; 532 | ofs = beFindInDict(torrent,torrentLen,thisFileOffset,"length"); 533 | if (ofs >= 0) { 534 | ofs = beParseInteger(torrent,torrentLen,ofs,&fileBytesExpected); 535 | } 536 | if (ofs < 0) { 537 | printf("Unable to read \"length\" from torrent\n"); 538 | return 2; 539 | } 540 | // path.utf-8 > path 541 | ofs = beFindInDict(torrent,torrentLen,thisFileOffset,"path.utf-8"); 542 | if (ofs < 0) { 543 | ofs = beFindInDict(torrent,torrentLen,thisFileOffset,"path"); 544 | if (ofs < 0) { 545 | printf("Unable to read \"path\" from torrent\n"); 546 | return 2; 547 | } 548 | } 549 | 550 | filePathOfs = 0; 551 | if (torrent[ofs] == 'l') ofs++; 552 | while((ofs>=0) && (ofs= filePathMax) { 563 | filePathMax *= 2; 564 | filePath = realloc(filePath,filePathMax); 565 | if (filePath == NULL) { 566 | printf("Unable to realloc %i bytes for file path\n",filePathMax); 567 | return 2; 568 | } 569 | } 570 | memcpy(filePath+filePathOfs,fileName,fileNameLen); 571 | filePathOfs += fileNameLen; 572 | filePath[filePathOfs] = 0; 573 | } 574 | 575 | // encoding 576 | if(encoding != NULL) { 577 | inPtr = filePath, outPtr = filePathUTF8; 578 | inBytesLeft = strlen(filePath), outBytesLeft = 1024; 579 | if((convDescriptor = iconv_open("utf-8", encoding)) == (iconv_t)-1) { 580 | return -1; 581 | } 582 | 583 | if(iconv(convDescriptor, &inPtr, &inBytesLeft, &outPtr, &outBytesLeft) == -1) { 584 | // fail 585 | iconv_close(convDescriptor); 586 | return -1; 587 | } 588 | 589 | // success 590 | filePathUTF8Len = 1024 - outBytesLeft; 591 | // *(outPtr + filePathUTF8Len) = '\0'; 592 | iconv_close(convDescriptor); 593 | 594 | if(filePathUTF8Len >= filePathMax) { 595 | filePathMax *= 2; 596 | filePath = realloc(filePath,filePathMax); 597 | if (filePath == NULL) { 598 | printf("Unable to realloc %i bytes for file path\n",filePathMax); 599 | return 2; 600 | } 601 | } 602 | memcpy(filePath, filePathUTF8, filePathUTF8Len); 603 | filePath[filePathUTF8Len] = '\0'; 604 | } 605 | 606 | fileRecordList[currentFile].filePath = malloc(strlen(filePath)+1); 607 | if (fileRecordList[currentFile].filePath == NULL) { 608 | printf("Unable to malloc %zu bytes for file path\n",strlen(filePath)+1); 609 | return 2; 610 | } 611 | strcpy(fileRecordList[currentFile].filePath,filePath); 612 | fileRecordList[currentFile].numBytes = fileBytesExpected; 613 | fileRecordList[currentFile].errorsFound = 0; 614 | 615 | thisFileOffset = beStepOver(torrent,torrentLen,thisFileOffset); 616 | totalBytes += fileBytesExpected; 617 | currentFile ++; 618 | } 619 | } 620 | 621 | filePath2 = malloc(filePathMax); 622 | if (filePath2 == NULL) { 623 | printf("Unable to malloc %i bytes for file path (2)\n",filePathMax); 624 | return 2; 625 | } 626 | 627 | if ((ofs < 0)&&(torrentFiles < 0)) { 628 | printf("Unable to read \"info->length\" or \"info->files\" from torrent\n"); 629 | return 2; 630 | } 631 | 632 | ofs = beFindInDict(torrent,torrentLen,torrentInfo,"pieces"); 633 | if (ofs >= 0) { 634 | ofs = beParseString(torrent,torrentLen,ofs,&pieceList,&pieceListLen); 635 | } 636 | if (ofs < 0) { 637 | printf("Unable to read \"pieces\" from torrent\n"); 638 | return 2; 639 | } 640 | 641 | numPieces = pieceListLen / SHA1_LEN; 642 | if (numPieces * SHA1_LEN != pieceListLen) { 643 | printf("Pieces list length is not a multiple of 20\n"); 644 | return 2; 645 | } 646 | 647 | ofs = beFindInDict(torrent,torrentLen,torrentInfo,"piece length"); 648 | if (ofs >= 0) { 649 | ofs = beParseInteger(torrent,torrentLen,ofs,&pieceLen); 650 | } 651 | if (ofs < 0) { 652 | printf("Unable to read \"piece length\" from torrent\n"); 653 | return 2; 654 | } 655 | 656 | printf("Torrent file : %s\n",torrentFile); 657 | printf("Metadata info : %i bytes, %i piece%s, %s bytes per piece%s\n",torrentLen,numPieces,((numPieces==1)?"":"s"),print64(pieceLen,p64Buf1,useCommaDot),((torrentPrivate==1)?", private":"")); 658 | printf("Torrent name : "); 659 | fwrite(rootName,rootNameLen,1,stdout); 660 | printf("\n"); 661 | 662 | if (multiFileTorrent) { 663 | printf("Content info : %i file%s, %s bytes\n",numFiles,((numFiles==1)?"":"s"),print64(totalBytes,p64Buf1,useCommaDot)); 664 | } else { 665 | printf("Content info : single file, %s bytes\n",print64(totalBytes,p64Buf1,useCommaDot)); 666 | } 667 | 668 | ofs = beFindInDict(torrent,torrentLen,0,"announce"); 669 | if (ofs >= 0) { 670 | ofs = beParseString(torrent,torrentLen,ofs,&announce,&announceLen); 671 | } 672 | if (ofs >= 0) { 673 | printf("Announce URL : "); 674 | fwrite(announce,announceLen,1,stdout); 675 | printf("\n"); 676 | } 677 | 678 | if ((multiFileTorrent)&&(contentPath == NULL)) { 679 | printf("\n"); 680 | if (useCommaDot) { 681 | printf("F# Bytes File name\n"); 682 | printf("--- ------------- -------------------------------------------------------------\n"); 683 | } else { 684 | printf("F# Bytes File name\n"); 685 | printf("--- ----------- ---------------------------------------------------------------\n"); 686 | } 687 | for(i=0;i 0)&&(filePath[filePathOfs-1] != DIR_SEPARATOR)) { 720 | filePath[filePathOfs] = DIR_SEPARATOR; 721 | filePathOfs++; 722 | } 723 | memcpy(filePath + filePathOfs,rootName,rootNameLen); 724 | filePathOfs += rootNameLen; 725 | if ((filePathOfs > 0)&&(filePath[filePathOfs-1] != DIR_SEPARATOR)) { 726 | filePath[filePathOfs] = DIR_SEPARATOR; 727 | filePathOfs++; 728 | } 729 | strcpy(filePath+filePathOfs,fileRecordList[currentFile].filePath); 730 | 731 | fileBytesExpected = fileRecordList[currentFile].numBytes; 732 | 733 | filePathActual = filePath; 734 | fp = fopen(filePath,"rb"); 735 | if (fp == NULL) { // Try without root path 736 | memcpy(filePath2,contentPath,contentPathLen); 737 | filePathOfs = contentPathLen; 738 | if ((filePathOfs > 0)&&(filePath2[filePathOfs-1] != DIR_SEPARATOR)) { 739 | filePath2[filePathOfs] = DIR_SEPARATOR; 740 | filePathOfs++; 741 | } 742 | strcpy(filePath2+filePathOfs,fileRecordList[currentFile].filePath); 743 | filePathActual = filePath2; 744 | fp = fopen(filePath2,"rb"); 745 | } 746 | 747 | if (fp == NULL) { 748 | backspaceProgressLine(&showProgressChars); 749 | printf("Unable to open file %s or %s\n",filePath,filePath2); 750 | errorsFound++; 751 | errorsFoundThisFile++; 752 | } else { 753 | tc_fseek(fp,0l,SEEK_END); 754 | fileBytesActual = tc_ftell(fp); 755 | rewind(fp); 756 | if (fileBytesActual != fileBytesExpected) { 757 | backspaceProgressLine(&showProgressChars); 758 | printf("File %s length mismatch, expected %s bytes, found %s bytes\n",filePathActual,print64(fileBytesExpected,p64Buf1,useCommaDot),print64(fileBytesActual,p64Buf2,useCommaDot)); 759 | errorsFound++; 760 | errorsFoundThisFile++; 761 | } 762 | } 763 | fileBytesRead = 0; 764 | while(fileBytesRead < fileBytesExpected) { 765 | if (fileBytesExpected - fileBytesRead < pieceLen - bytesRead) { 766 | bytesToRead = fileBytesExpected - fileBytesRead; 767 | } else { 768 | bytesToRead = pieceLen - bytesRead; 769 | } 770 | if (fp != NULL) readLen = fread(pieceBuf+bytesRead,1,bytesToRead,fp); 771 | else readLen = 0; 772 | bytesRead += bytesToRead; fileBytesRead += bytesToRead; 773 | if ((fp != NULL)&&(readLen != bytesToRead)) { 774 | backspaceProgressLine(&showProgressChars); 775 | printf("Short read, got %i bytes, expected %i bytes at offset %s of %s\n",readLen,bytesToRead,print64(fileBytesRead,p64Buf1,useCommaDot),filePathActual); 776 | errorsFound ++; 777 | errorsFoundThisFile ++; 778 | } 779 | if (currentFile + 1 >= numFiles) lastFile = 1; 780 | if ((bytesRead == pieceLen)||((lastFile==1)&&(fileBytesRead == fileBytesExpected))) { 781 | 782 | if ((fp != NULL)&&(readLen == bytesToRead)) { 783 | SHAInit(&sha1ctx); 784 | SHAUpdate(&sha1ctx,pieceBuf,bytesRead); 785 | SHAFinal(sha1hash,&sha1ctx); 786 | } 787 | totalBytesDone += bytesRead; 788 | i = piecesDone * SHA1_LEN; 789 | 790 | if ((fp == NULL)||(readLen != bytesToRead)) { 791 | errorsFound ++; 792 | errorsFoundThisFile ++; 793 | thisPieceBad = 2; 794 | for(i=firstFileThisPiece;i<=currentFile;i++) { 795 | fileRecordList[i].errorsFound = errorsFoundThisFile; 796 | } 797 | } else if (memcmp(pieceList+i,sha1hash,SHA1_LEN) != 0) { 798 | errorsFound ++; 799 | errorsFoundThisFile ++; 800 | thisPieceBad = 1; 801 | for(i=firstFileThisPiece;i<=currentFile;i++) { 802 | fileRecordList[i].errorsFound = errorsFoundThisFile; 803 | } 804 | } 805 | 806 | if (thisPieceBad || showPieceDetail) { 807 | backspaceProgressLine(&showProgressChars); 808 | if ((showPieceDetail == 1)||(thisPieceBad == 1)) { 809 | printf("piece %i computed SHA1 %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",piecesDone, 810 | (int)sha1hash[0], (int)sha1hash[1], (int)sha1hash[2], (int)sha1hash[3], 811 | (int)sha1hash[4], (int)sha1hash[5], (int)sha1hash[6], (int)sha1hash[7], 812 | (int)sha1hash[8], (int)sha1hash[9], (int)sha1hash[10], (int)sha1hash[11], 813 | (int)sha1hash[12], (int)sha1hash[13], (int)sha1hash[14], (int)sha1hash[15], 814 | (int)sha1hash[16], (int)sha1hash[17], (int)sha1hash[18], (int)sha1hash[19]); 815 | 816 | printf("piece %i expected SHA1 %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",piecesDone, 817 | (int)pieceList[i+0], (int)pieceList[i+1], (int)pieceList[i+2], (int)pieceList[i+3], 818 | (int)pieceList[i+4], (int)pieceList[i+5], (int)pieceList[i+6], (int)pieceList[i+7], 819 | (int)pieceList[i+8], (int)pieceList[i+9], (int)pieceList[i+10], (int)pieceList[i+11], 820 | (int)pieceList[i+12], (int)pieceList[i+13], (int)pieceList[i+14], (int)pieceList[i+15], 821 | (int)pieceList[i+16], (int)pieceList[i+17], (int)pieceList[i+18], (int)pieceList[i+19]); 822 | } 823 | printf("piece %i is files %i-%i, %s bytes, %s total bytes, %i error%s\n",piecesDone,firstFileThisPiece+1,currentFile+1,print64(bytesRead,p64Buf1,useCommaDot),print64(totalBytesDone,p64Buf2,useCommaDot),errorsFound,((errorsFound == 1)?"":"s")); 824 | thisPieceBad = 0; 825 | } 826 | 827 | for(i=firstFileThisPiece;i 0) { 860 | return 1; 861 | } else { 862 | return 0; 863 | } 864 | 865 | ///////////////////////////////////////////////////////////////////////////////////// 866 | } else if (contentPath != NULL) { // single file torrent 867 | strcpy(filePath,contentPath); 868 | filePathOfs = strlen(filePath); 869 | if ((filePathOfs > 0)&&(filePath[filePathOfs-1] != DIR_SEPARATOR)) { 870 | filePath[filePathOfs] = DIR_SEPARATOR; 871 | filePathOfs++; 872 | } 873 | memcpy(filePath + filePathOfs,rootName,rootNameLen); 874 | filePath[filePathOfs+rootNameLen] = 0; 875 | 876 | fp = fopen(filePath,"rb"); 877 | if (fp == NULL ) { 878 | fp = fopen(contentPath,"rb"); 879 | if (fp != NULL) strcpy(filePath,contentPath); 880 | } 881 | if (fp == NULL) { 882 | printf("Unable to open file %s or %s\n",contentPath,filePath); 883 | return 2; 884 | } 885 | 886 | tc_fseek(fp,0l,SEEK_END); 887 | fileBytesActual = tc_ftell(fp); 888 | rewind(fp); 889 | if (fileBytesActual != fileBytesExpected) { 890 | backspaceProgressLine(&showProgressChars); 891 | printf("File length mismatch, expected %s bytes, found %s bytes\n",print64(fileBytesExpected,p64Buf1,useCommaDot),print64(fileBytesActual,p64Buf2,useCommaDot)); 892 | errorsFound++; 893 | } 894 | 895 | for(piecesDone = 0;piecesDone 0) { 943 | return 1; 944 | } else { 945 | return 0; 946 | } 947 | } 948 | 949 | 950 | return 2; // How did we get here? 951 | } 952 | 953 | 954 | // EOF 955 | --------------------------------------------------------------------------------