This commit is contained in:
x3 2021-05-02 21:19:33 +02:00
commit bfef7d9084
Signed by: x3
GPG Key ID: 7E9961E8AD0E240E
17 changed files with 1739 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.o
/torrent-verify
tags

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "subm/heapless-bencode"]
path = subm/heapless-bencode
url = https://github.com/willemt/heapless-bencode

24
Makefile Normal file
View File

@ -0,0 +1,24 @@
MultiThread = Yes
PROGNAME = torrent-verify
CC = cc
CFLAGS = -Wall -std=gnu99 -I./subm/heapless-bencode -Werror
CPPFLAGS = -DPROGRAM_NAME='"$(PROGNAME)"' -DBUILD_INFO \
-DBUILD_HASH="\"`git rev-parse --abbrev-ref HEAD` -> `git rev-parse --short HEAD`\"" -DBUILD_DATE="\"`date -I`\""
ifeq ($(MultiThread), Yes)
CFLAGS += -lpthread
CPPFLAGS += -DMT
endif
SOURCE = $(wildcard subm/heapless-bencode/*.c) $(wildcard src/*.c)
#OBJ = $(addsuffix .o,$(basename $(SOURCE)))
OBJS = $(SOURCE:.c=.o)
all: $(PROGNAME)
$(PROGNAME): $(OBJS)
$(CC) -o $@ $+ $(CFLAGS) $(CPPFLAGS)
clean:
-rm -- $(OBJS) $(PROGNAME)

95
src/main.c Normal file
View File

@ -0,0 +1,95 @@
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "metainfo.h"
#include "verify.h"
#include "showinfo.h"
#include "opts.h"
#ifndef PROGRAM_NAME
#define PROGRAM_NAME "torrent-verify"
#endif
void usage() {
fprintf(stderr, "Usage: " PROGRAM_NAME " [-h | -i | -s] [-n] [-v data_path] [--] .torrent_file...\n");
exit(EXIT_FAILURE);
}
void help() {
printf(
"Usage:\n"
" " PROGRAM_NAME " [options] <.torrent file>\n"
"\n"
"OPTIONS:\n"
" -h print this help text\n"
" -i show info about the torrent file\n"
" -v PATH verify the torrent file, pass in the path of the files\n"
" -s don't write any output\n"
" -n Don't use torrent name as a folder when verifying\n"
"\n"
"EXIT CODE\n"
" If no error, exit code is 0. In verify mode exit code is 0 if it's\n"
" verified correctly, otherwise non-zero\n"
#ifdef BUILD_INFO
"\n"
BUILD_HASH " (" BUILD_DATE ")\n"
#ifdef MT
"MultiThread support\n"
#endif
#endif
);
exit(EXIT_SUCCESS);
}
int main(int argc, char** argv) {
if (sizeof(long int) < 8) {
fprintf(stderr, "long int is less than 8 bytes. Create an issue please.\n");
exit(1);
}
if (opts_parse(argc, argv) == -1)
usage();
if (opt_help)
help();
if (optind >= argc) {
fprintf(stderr, "Provide at least one torrent file\n");
usage();
}
/*
if (data_path && optind != argc - 1) {
fprintf(stderr, "If used with -v, only input 1 bittorrent file\n");
usage();
}
*/
int exit_code = EXIT_SUCCESS;
for (int i = optind; i < argc; i++) {
metainfo_t m;
if (metainfo_create(&m, argv[i]) == -1) {
return EXIT_FAILURE;
}
if (opt_showinfo && !opt_silent) {
showinfo(&m);
}
if (opt_data_path) { /* Verify */
int verify_result = verify(&m, opt_data_path, !opt_no_use_dir);
if (verify_result != 0) {
if (!opt_silent)
printf("Torrent verify failed: %s\n", strerror(verify_result));
exit_code = EXIT_FAILURE;
} else {
if (!opt_silent)
printf("Torrent verified successfully\n");
}
}
metainfo_destroy(&m);
}
return exit_code;
}

393
src/metainfo.c Normal file
View File

@ -0,0 +1,393 @@
#include "metainfo.h"
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "sha1.h"
/* 128 MiB */
#define MAX_TORRENT_SIZE 128*1024*1024
/*
* Read the file in memory, and return the pointer to it (which needs to be
* freed) in out_contents and the size in out_size. If the file is too big,
* fail. Returns 0 on success and an errno on fail.
*/
static int metainfo_read(const char* path, char** out_contents, int* out_size) {
int ret = 0;
FILE* f = NULL;
long size;
char* contents = NULL, *curr_contents = NULL;
size_t read;
f = fopen(path, "rb");
if (!f) {
ret = errno;
goto end;
}
/* Get the file size, and bail if it's too large */
if (fseek(f, 0, SEEK_END) == -1) {
ret = errno;
goto end;
}
size = ftell(f);
if (size > MAX_TORRENT_SIZE) {
ret = EFBIG;
goto end;
}
rewind(f);
contents = curr_contents = malloc(size);
if (!contents) {
ret = ENOMEM;
goto end;
}
/* Read it in */
while ((read = fread(curr_contents, 1, \
size - (curr_contents - contents), f)) > 0) {
curr_contents += read;
}
if (ferror(f)) {
ret = errno;
free(curr_contents);
curr_contents = NULL;
goto end;
}
end:
if (f)
fclose(f);
*out_size = size;
*out_contents = contents;
return ret;
}
static int len_strcmp(const char* s1, int s1_len, const char* s2, int s2_len) {
return (s1_len == s2_len) && (strncmp(s1, s2, s1_len) == 0);
}
static int read_benc_int_into(bencode_t* benc, long int* val, int def) {
if (!bencode_int_value(benc, val)) {
*val = def;
return -1;
}
return 0;
}
#define tkey(s) len_strcmp(key, klen, s, strlen(s))
#define ttype(t) bencode_is_##t(&item)
static int metainfo_hash_info(metainfo_t* metai, bencode_t* info) {
const char* info_start;
int info_len;
/* Almost as if this function was made for this, lol */
bencode_dict_get_start_and_len(info, &info_start, &info_len);
SHA1_CTX ctx;
SHA1Init(&ctx);
SHA1Update(&ctx, (const unsigned char*)info_start, info_len);
SHA1Final((unsigned char*)&metai->info_hash, &ctx);
return 0;
}
static int metainfo_parse_info(metainfo_t* metai, bencode_t* benc) {
metainfo_hash_info(metai, benc);
while (bencode_dict_has_next(benc)) {
const char* key;
int klen;
bencode_t item;
bencode_dict_get_next(benc, &item, &key, &klen);
if (tkey("name") && ttype(string)) {
metai->name = item;
} else if (tkey("piece length") && ttype(int)) {
read_benc_int_into(&item, &metai->piece_length, -1);
} else if (tkey("pieces") && ttype(string)) {
int plen = -1;
bencode_string_value(&item, (const char**)&metai->pieces, &plen);
/* A piece hash is always 20 bytes */
if (plen % sizeof(sha1sum_t) != 0) {
fprintf(stderr, "A piece hash is not 20 bytes somewhere\n" \
"\tHash string length is: %d\n", plen);
/* This should never happen tho */
plen -= plen % sizeof(sha1sum_t);
}
metai->piece_count = plen / sizeof(sha1sum_t);
} else if (tkey("length") && ttype(int)) { /* If single file */
metai->is_multi_file = 0;
read_benc_int_into(&item, &metai->file_size, -1);
} else if (tkey("files") && ttype(list)) { /* If multiple files */
metai->is_multi_file = 1;
metai->files = item;
} else if (tkey("private") && ttype(int)) {
read_benc_int_into(&item, &metai->is_private, 0);
} else if (tkey("source") && ttype(string)) {
metai->source = item;
} else {
fprintf(stderr, "Unknown key in info dict: %.*s\n", klen, key);
}
}
return 0;
}
static int metainfo_parse(metainfo_t* metai, bencode_t* benc) {
int ret = 0;
/* All .torrent files are dictionaries, so this is an error */
if (!bencode_is_dict(benc)) {
metainfo_destroy(metai);
fprintf(stderr, "File is not a valid .torrent file\n");
return -1;
}
while (bencode_dict_has_next(benc)) {
const char* key;
int klen;
bencode_t item;
bencode_dict_get_next(benc, &item, &key, &klen);
if (tkey("announce")) {
metai->announce = item;
} else if (tkey("created by") && ttype(string)) {
metai->created_by = item;
} else if (tkey("creation date") && ttype(int)) {
read_benc_int_into(&item, &metai->creation_date, -1);
} else if (tkey("info") && ttype(dict)) {
metainfo_parse_info(metai, &item);
} else if (tkey("comment") && ttype(string)) {
metai->comment = item;
} else {
fprintf(stderr, "Unknown dict key: %.*s\n", klen, key);
}
}
return ret;
}
int metainfo_create(metainfo_t* metai, const char* path) {
char* bytes;
int size;
int ret = metainfo_read(path, &bytes, &size);
if (ret) {
fprintf(stderr, "Metafile reading failed: %s\n", \
strerror(ret));
return -1;
}
memset(metai, 0, sizeof(metainfo_t));
metai->bytes = bytes;
metai->bytes_size = size;
bencode_t benc;
bencode_init(&benc, bytes, size);
if (metainfo_parse(metai, &benc) == -1) {
metainfo_destroy(metai);
fprintf(stderr, "Can't parse metainfo file\n");
return -1;
}
return 0;
}
void metainfo_destroy(metainfo_t* metai) {
if (metai->bytes) {
free(metai->bytes);
metai->bytes = NULL;
}
}
const sha1sum_t* metainfo_infohash(metainfo_t* metai) {
return &metai->info_hash;
}
static int metainfo_get_string(bencode_t* benc, const char** str, int* len) {
if (!benc->start)
return -1;
return bencode_string_value(benc, str, len) - 1;
}
int metainfo_announce(metainfo_t* metai, const char** str, int* len) {
return metainfo_get_string(&metai->announce, str, len);
}
int metainfo_created_by(metainfo_t* metai, const char** str, int* len) {
return metainfo_get_string(&metai->created_by, str, len);
}
int metainfo_creation_date(metainfo_t* metai) {
return metai->creation_date;
}
int metainfo_source(metainfo_t* metai, const char** str, int* len) {
return metainfo_get_string(&metai->source, str, len);
}
int metainfo_is_private(metainfo_t* metai) {
return metai->is_private;
}
int metainfo_name(metainfo_t* metai, const char** str, int* len) {
return metainfo_get_string(&metai->name, str, len);
}
int metainfo_comment(metainfo_t* metai, const char** str, int* len) {
return metainfo_get_string(&metai->comment, str, len);
}
int metainfo_pieces(metainfo_t* metai, const sha1sum_t** piece_hash) {
if (!metai->pieces)
return -1;
*piece_hash = metai->pieces;
return 0;
}
int metainfo_piece_index(metainfo_t* metai, int index, \
const sha1sum_t** piece_hash) {
if (!metai->pieces || index >= metai->piece_count)
return -1;
*piece_hash = metai->pieces + index;
return 0;
}
int metainfo_piece_size(metainfo_t* metai) {
return metai->piece_length;
}
long int metainfo_piece_count(metainfo_t* metai) {
return metai->piece_count;
}
int metainfo_is_multi_file(metainfo_t* metai) {
return metai->is_multi_file;
}
long int metainfo_file_count(metainfo_t* metai) {
if (!(metai->is_multi_file && bencode_is_list(&metai->files)))
return 0;
long int count = 0;
bencode_t iterb = metai->files;
while (bencode_list_get_next(&iterb, NULL) != 0)
count++;
return count;
}
static int metainfo_file_dict2fileinfo(bencode_t* f_dict, fileinfo_t* finfo) {
int has_path = 0, has_size = 0;
while (bencode_dict_has_next(f_dict) && (!has_path || !has_size)) {
const char* key;
int klen;
bencode_t item;
bencode_dict_get_next(f_dict, &item, &key, &klen);
if (tkey("length") && ttype(int)) {
has_size = 1;
bencode_int_value(&item, &finfo->size);
} else if (tkey("path") && ttype(list)) {
has_path = 1;
finfo->path = item;
} else {
fprintf(stderr, "Unknown key in files dict: %*.s\n", klen, key);
}
}
return (has_path && has_size) ? 0 : -1;
}
int metainfo_file_index(metainfo_t* metai, int index, fileinfo_t* finfo) {
if (!(metai->is_multi_file && bencode_is_list(&metai->files)))
return -1;
bencode_t iterb = metai->files;
while (index-- && bencode_list_get_next(&iterb, NULL) != 0);
if (!bencode_is_dict(&iterb))
return -1;
return metainfo_file_dict2fileinfo(&iterb, finfo);
}
int metainfo_fileiter_create(const metainfo_t* metai, fileiter_t* fileiter) {
if (!metai->is_multi_file || !bencode_is_list(&metai->files))
return -1;
fileiter->filelist = metai->files;
return 0;
}
int metainfo_file_next(fileiter_t* iter, fileinfo_t* finfo) {
if (!bencode_list_has_next(&iter->filelist))
return -1;
bencode_t f_dict;
bencode_list_get_next(&iter->filelist, &f_dict);
return metainfo_file_dict2fileinfo(&f_dict, finfo);
}
int metainfo_fileinfo(metainfo_t* metai, fileinfo_t* finfo) {
if (metai->is_multi_file)
return -1;
finfo->size = metai->file_size;
/* In the case of single files, the name is the filename */
finfo->path = metai->name;
return 0;
}
#ifdef _WIN32
#define PATH_SEP '\\'
#else
#define PATH_SEP '/'
#endif
int metainfo_fileinfo_path(fileinfo_t* finfo, char* out_str) {
int count = 0;
if (bencode_is_list(&finfo->path)) {
bencode_t local_copy = finfo->path;
bencode_t item;
while (bencode_list_has_next(&local_copy)) {
/* If not in the first iter, append separator */
if (count > 0) {
if (out_str)
*out_str++ = PATH_SEP;
count++;
}
bencode_list_get_next(&local_copy, &item);
int slen;
const char* s;
bencode_string_value(&item, &s, &slen);
count += slen;
if (out_str) {
memcpy(out_str, s, slen);
out_str += slen;
}
}
} else {
/* Single file, we shouldn't even copy here, but it's easier... */
int slen;
const char* s;
bencode_string_value(&finfo->path, &s, &slen);
if (out_str) {
memcpy(out_str, s, slen);
}
count = slen;
}
return count;
}
long int metainfo_fileinfo_size(fileinfo_t* finfo) {
return finfo->size;
}
#undef tkey
#undef ttype

162
src/metainfo.h Normal file
View File

@ -0,0 +1,162 @@
#ifndef METAFILE_H
#define METAFILE_H
#include <bencode.h>
/* This file will parse the .torrent file and make accessor functions */
typedef struct {
const char* str;
int len;
} lenstr_t;
typedef struct {
bencode_t path;
long int size;
} fileinfo_t;
typedef struct {
bencode_t filelist;
} fileiter_t;
typedef unsigned char sha1sum_t[20];
/*
typedef struct __attribute__((packed)) sha1sum_t {
unsigned char hash[20];
} sha1sum_t;
*/
typedef struct {
char* bytes;
int bytes_size;
sha1sum_t info_hash;
const sha1sum_t* pieces;
int piece_count;
long int piece_length, creation_date;
bencode_t announce, created_by, name, source, comment;
long int is_private;
int is_multi_file;
union {
long int file_size;
bencode_t files;
};
} metainfo_t;
/*
* Returns 0 on success, -1 on error
*/
int metainfo_create(metainfo_t* metai, const char* path);
void metainfo_destroy(metainfo_t* metai);
/*
* Get the info_hash of the torrent as a pointer
*/
const sha1sum_t* metainfo_infohash(metainfo_t* metai);
/*
* Get the announce url
*/
int metainfo_announce(metainfo_t* metai, const char** str, int* len);
/*
* Get the created by string
*/
int metainfo_created_by(metainfo_t* metai, const char** str, int* len);
/*
* Get the creation date, as a unix timestamp
*/
int metainfo_creation_date(metainfo_t* metai);
/*
* Get the source
*/
int metainfo_source(metainfo_t* metai, const char** str, int* len);
/*
* Get the name of the torrent
*/
int metainfo_name(metainfo_t* metai, const char** str, int* len);
/*
* Get the comment of the torrent
*/
int metainfo_comment(metainfo_t* metai, const char** str, int* len);
/*
* Is this torrent private?
*/
int metainfo_is_private(metainfo_t* metai);
/*
* Get the array of pieces
*/
int metainfo_pieces(metainfo_t* metai, const sha1sum_t** piece_hash);
/*
* Get the number of pieces, this will return the number
*/
long int metainfo_piece_count(metainfo_t* metai);
/*
* Get the index'th piece hash, returns -1 if invalid
*/
int metainfo_piece_index(metainfo_t* metai, int index, \
const sha1sum_t** piece_hash);
/*
* Get the size of 1 piece
*/
int metainfo_piece_size(metainfo_t* metai);
/*
* Return 1 if the torrent has multiple files, or 0 if has only 1
*/
int metainfo_is_multi_file(metainfo_t* metai);
/*
* Return the number of files, if it's a multi file. This is slow
*/
long int metainfo_file_count(metainfo_t* metai);
/*
* Get the index'th file information, if it's a multi file
* This is slow and shouldn't be used
*/
int metainfo_file_index(metainfo_t* metai, int index, fileinfo_t* finfo);
/*
* Create a file iterator.
* This interface should be used to get the file infos.
* This doesn't need to be freed.
*/
int metainfo_fileiter_create(const metainfo_t* metai, fileiter_t* fileiter);
/*
* Get the next fileinfo in a multi file torrent
* Return -1 if there is no more files.
*/
int metainfo_file_next(fileiter_t* iter, fileinfo_t* finfo);
/*
* Get the file information, if torrent has only 1 file
*/
int metainfo_fileinfo(metainfo_t* metai, fileinfo_t* finfo);
/*
* Copy the file path from a fileinto_t struct into the buffer at 'out_str',
* adding separators according to the current platform.
* if 'out_str' is NULL, return the required bytes to store the name,
* otherwise the number of bytes copied, or -1 if error.
* The count does NOT include the null terminator
*/
int metainfo_fileinfo_path(fileinfo_t* len, char* out_str);
/*
* Return the size of the file in bytes, ofc
*/
long int metainfo_fileinfo_size(fileinfo_t* finfo);
#endif

39
src/opts.c Normal file
View File

@ -0,0 +1,39 @@
#include "opts.h"
#include <unistd.h>
int opt_silent = 0;
int opt_showinfo = 0;
int opt_help = 0;
int opt_no_use_dir = 0;
int opt_pretty_progress = 0;
char* opt_data_path = NULL;
int opts_parse(int argc, char** argv) {
int opt;
while ((opt = getopt(argc, argv, "pnihsv:")) != -1) {
switch (opt) {
case 'i':
opt_showinfo = 1;
break;
case 'h':
opt_help = 1;
break;
case 's':
opt_silent = 1;
break;
case 'n':
opt_no_use_dir = 1;
break;
case 'p':
opt_pretty_progress = 1;
break;
case 'v':
opt_data_path = optarg;
break;
default:
return -1;
}
}
return 0;
}

14
src/opts.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef OPTS_H
#define OPTS_H
extern int opt_silent;
extern int opt_showinfo;
extern int opt_help;
extern int opt_no_use_dir;
extern int opt_pretty_progress;
extern char* opt_data_path;
/* Parse the given arguments. Return -1 if error */
int opts_parse(int argc, char** argv);
#endif

296
src/sha1.c Normal file
View File

@ -0,0 +1,296 @@
/*
SHA-1 in C
By Steve Reid <steve@edmweb.com>
100% Public Domain
Test Vectors (from FIPS PUB 180-1)
"abc"
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
A million repetitions of "a"
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
*/
/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
/* #define SHA1HANDSOFF * Copies data before messing with it. */
#define SHA1HANDSOFF
#include <stdio.h>
#include <string.h>
/* for uint32_t */
#include <stdint.h>
#include "sha1.h"
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
#if BYTE_ORDER == LITTLE_ENDIAN
#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
|(rol(block->l[i],8)&0x00FF00FF))
#elif BYTE_ORDER == BIG_ENDIAN
#define blk0(i) block->l[i]
#else
#error "Endianness not defined!"
#endif
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
^block->l[(i+2)&15]^block->l[i&15],1))
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
/* Hash a single 512-bit block. This is the core of the algorithm. */
void SHA1Transform(
uint32_t state[5],
const unsigned char buffer[64]
)
{
uint32_t a, b, c, d, e;
typedef union
{
unsigned char c[64];
uint32_t l[16];
} CHAR64LONG16;
#ifdef SHA1HANDSOFF
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
memcpy(block, buffer, 64);
#else
/* The following had better never be used because it causes the
* pointer-to-const buffer to be cast into a pointer to non-const.
* And the result is written through. I threw a "const" in, hoping
* this will cause a diagnostic.
*/
CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer;
#endif
/* Copy context->state[] to working vars */
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
/* 4 rounds of 20 operations each. Loop unrolled. */
R0(a, b, c, d, e, 0);
R0(e, a, b, c, d, 1);
R0(d, e, a, b, c, 2);
R0(c, d, e, a, b, 3);
R0(b, c, d, e, a, 4);
R0(a, b, c, d, e, 5);
R0(e, a, b, c, d, 6);
R0(d, e, a, b, c, 7);
R0(c, d, e, a, b, 8);
R0(b, c, d, e, a, 9);
R0(a, b, c, d, e, 10);
R0(e, a, b, c, d, 11);
R0(d, e, a, b, c, 12);
R0(c, d, e, a, b, 13);
R0(b, c, d, e, a, 14);
R0(a, b, c, d, e, 15);
R1(e, a, b, c, d, 16);
R1(d, e, a, b, c, 17);
R1(c, d, e, a, b, 18);
R1(b, c, d, e, a, 19);
R2(a, b, c, d, e, 20);
R2(e, a, b, c, d, 21);
R2(d, e, a, b, c, 22);
R2(c, d, e, a, b, 23);
R2(b, c, d, e, a, 24);
R2(a, b, c, d, e, 25);
R2(e, a, b, c, d, 26);
R2(d, e, a, b, c, 27);
R2(c, d, e, a, b, 28);
R2(b, c, d, e, a, 29);
R2(a, b, c, d, e, 30);
R2(e, a, b, c, d, 31);
R2(d, e, a, b, c, 32);
R2(c, d, e, a, b, 33);
R2(b, c, d, e, a, 34);
R2(a, b, c, d, e, 35);
R2(e, a, b, c, d, 36);
R2(d, e, a, b, c, 37);
R2(c, d, e, a, b, 38);
R2(b, c, d, e, a, 39);
R3(a, b, c, d, e, 40);
R3(e, a, b, c, d, 41);
R3(d, e, a, b, c, 42);
R3(c, d, e, a, b, 43);
R3(b, c, d, e, a, 44);
R3(a, b, c, d, e, 45);
R3(e, a, b, c, d, 46);
R3(d, e, a, b, c, 47);
R3(c, d, e, a, b, 48);
R3(b, c, d, e, a, 49);
R3(a, b, c, d, e, 50);
R3(e, a, b, c, d, 51);
R3(d, e, a, b, c, 52);
R3(c, d, e, a, b, 53);
R3(b, c, d, e, a, 54);
R3(a, b, c, d, e, 55);
R3(e, a, b, c, d, 56);
R3(d, e, a, b, c, 57);
R3(c, d, e, a, b, 58);
R3(b, c, d, e, a, 59);
R4(a, b, c, d, e, 60);
R4(e, a, b, c, d, 61);
R4(d, e, a, b, c, 62);
R4(c, d, e, a, b, 63);
R4(b, c, d, e, a, 64);
R4(a, b, c, d, e, 65);
R4(e, a, b, c, d, 66);
R4(d, e, a, b, c, 67);
R4(c, d, e, a, b, 68);
R4(b, c, d, e, a, 69);
R4(a, b, c, d, e, 70);
R4(e, a, b, c, d, 71);
R4(d, e, a, b, c, 72);
R4(c, d, e, a, b, 73);
R4(b, c, d, e, a, 74);
R4(a, b, c, d, e, 75);
R4(e, a, b, c, d, 76);
R4(d, e, a, b, c, 77);
R4(c, d, e, a, b, 78);
R4(b, c, d, e, a, 79);
/* Add the working vars back into context.state[] */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
/* Wipe variables */
a = b = c = d = e = 0;
#ifdef SHA1HANDSOFF
memset(block, '\0', sizeof(block));
#endif
}
/* SHA1Init - Initialize new context */
void SHA1Init(
SHA1_CTX * context
)
{
/* SHA1 initialization constants */
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
/* Run your data through this. */
void SHA1Update(
SHA1_CTX * context,
const unsigned char *data,
uint32_t len
)
{
uint32_t i;
uint32_t j;
j = context->count[0];
if ((context->count[0] += len << 3) < j)
context->count[1]++;
context->count[1] += (len >> 29);
j = (j >> 3) & 63;
if ((j + len) > 63)
{
memcpy(&context->buffer[j], data, (i = 64 - j));
SHA1Transform(context->state, context->buffer);
for (; i + 63 < len; i += 64)
{
SHA1Transform(context->state, &data[i]);
}
j = 0;
}
else
i = 0;
memcpy(&context->buffer[j], &data[i], len - i);
}
/* Add padding and return the message digest. */
void SHA1Final(
unsigned char digest[20],
SHA1_CTX * context
)
{
unsigned i;
unsigned char finalcount[8];
unsigned char c;
#if 0 /* untested "improvement" by DHR */
/* Convert context->count to a sequence of bytes
* in finalcount. Second element first, but
* big-endian order within element.
* But we do it all backwards.
*/
unsigned char *fcp = &finalcount[8];
for (i = 0; i < 2; i++)
{
uint32_t t = context->count[i];
int j;
for (j = 0; j < 4; t >>= 8, j++)
*--fcp = (unsigned char) t}
#else
for (i = 0; i < 8; i++)
{
finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
}
#endif
c = 0200;
SHA1Update(context, &c, 1);
while ((context->count[0] & 504) != 448)
{
c = 0000;
SHA1Update(context, &c, 1);
}
SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
for (i = 0; i < 20; i++)
{
digest[i] = (unsigned char)
((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
}
/* Wipe variables */
memset(context, '\0', sizeof(*context));
memset(&finalcount, '\0', sizeof(finalcount));
}
void SHA1(
char *hash_out,
const char *str,
int len)
{
SHA1_CTX ctx;
unsigned int ii;
SHA1Init(&ctx);
for (ii=0; ii<len; ii+=1)
SHA1Update(&ctx, (const unsigned char*)str + ii, 1);
SHA1Final((unsigned char *)hash_out, &ctx);
hash_out[20] = '\0';
}

44
src/sha1.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef SHA1_H
#define SHA1_H
/*
SHA-1 in C
By Steve Reid <steve@edmweb.com>
100% Public Domain
*/
#include "stdint.h"
typedef struct
{
uint32_t state[5];
uint32_t count[2];
unsigned char buffer[64];
} SHA1_CTX;
void SHA1Transform(
uint32_t state[5],
const unsigned char buffer[64]
);
void SHA1Init(
SHA1_CTX * context
);
void SHA1Update(
SHA1_CTX * context,
const unsigned char *data,
uint32_t len
);
void SHA1Final(
unsigned char digest[20],
SHA1_CTX * context
);
void SHA1(
char *hash_out,
const char *str,
int len);
#endif /* SHA1_H */

97
src/showinfo.c Normal file
View File

@ -0,0 +1,97 @@
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "showinfo.h"
#include "util.h"
void showinfo(metainfo_t* m) {
const char* s;
int slen;
long int lint;
char str_buff[100] = {0};
if (metainfo_name(m, &s, &slen) != 0) {
s = "[UNKNOWN (>_<)]";
slen = strlen("[UNKNOWN (>_<)]");
}
printf("Name: %.*s\n", slen, s);
util_byte2hex((const unsigned char*)metainfo_infohash(m), sizeof(sha1sum_t), 0, str_buff);
printf("Info hash: %s\n", str_buff);
lint = metainfo_piece_count(m);
printf("Piece count: %ld\n", lint);
lint = metainfo_piece_size(m);
if (util_byte2human(lint, 1, 0, str_buff, sizeof(str_buff)) == -1)
strncpy(str_buff, "err", sizeof(str_buff));
printf("Piece size: %ld (%s)\n", lint, str_buff);
printf("Is multi file: %s\n", metainfo_is_multi_file(m) ? "Yes" : "No");
if (metainfo_is_multi_file(m)) {
printf("File count is: %ld\n", metainfo_file_count(m));
}
printf("Is private: %s\n", metainfo_is_private(m) ? "Yes" : "No");
if (metainfo_announce(m, &s, &slen) != -1) {
printf("Tracker: %.*s\n", slen, s);
}
lint = metainfo_creation_date(m);
if (lint != -1) {
struct tm* time = localtime(&lint);
if (strftime(str_buff, sizeof(str_buff), "%F %T", time) == 0)
strncpy(str_buff, "overflow", sizeof(str_buff));
printf("Creation date: %s\n", str_buff);
}
if (metainfo_created_by(m, &s, &slen) != -1) {
printf("Created by: %.*s\n", slen, s);
}
if (metainfo_source(m, &s, &slen) != -1) {
printf("Source: %.*s\n", slen, s);
}
printf("Files:\n");
unsigned long total_size = 0;
fileinfo_t f;
if (metainfo_is_multi_file(m)) {
fileiter_t fi;
if (metainfo_fileiter_create(m, &fi) == 0) {
while (metainfo_file_next(&fi, &f) == 0) {
int pathlen = metainfo_fileinfo_path(&f, NULL);
char pathbuff[pathlen];
metainfo_fileinfo_path(&f, pathbuff);
if (util_byte2human(f.size, 1, -1, str_buff, sizeof(str_buff)) == -1) {
strncpy(str_buff, "err", sizeof(str_buff));
}
printf("\t%8s %.*s\n", str_buff, pathlen, pathbuff);
total_size += metainfo_fileinfo_size(&f);
}
}
} else {
metainfo_fileinfo(m, &f);
int pathlen = metainfo_fileinfo_path(&f, NULL);
char pathbuff[pathlen];
metainfo_fileinfo_path(&f, pathbuff);
if (util_byte2human(f.size, 1, -1, str_buff, sizeof(str_buff)) == -1) {
strncpy(str_buff, "err", sizeof(str_buff));
}
printf("\t%8s %.*s\n", str_buff, pathlen, pathbuff);
total_size = metainfo_fileinfo_size(&f);
}
if (util_byte2human(total_size, 1, -1, str_buff, sizeof(str_buff)) == -1)
strncpy(str_buff, "err", sizeof(str_buff));
printf("Total size: %s\n", str_buff);
}

10
src/showinfo.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef SHOWINFO_H
#define SHOWINFO_H
#include "metainfo.h"
/*
* Print the contents of a metainfo file
*/
void showinfo(metainfo_t* m);
#endif

47
src/util.c Normal file
View File

@ -0,0 +1,47 @@
#include <stdio.h>
#include "util.h"
#define B_IN_KiB 1024ull
#define B_IN_MiB B_IN_KiB * 1024
#define B_IN_GiB B_IN_MiB * 1024
#define B_IN_TiB B_IN_GiB * 1024
#define B_IN_KB 1000ull
#define B_IN_MB B_IN_KB * 1000
#define B_IN_GB B_IN_MB * 1000
#define B_IN_TB B_IN_GB * 1000
int util_byte2human(long int bytes, int binary, int precision, char* out, size_t out_len) {
#define S_CONV(t) ((binary) ? (B_IN_##t##iB) : (B_IN_##t##B))
#define S_LESS(t) (bytes < S_CONV(t))
#define S_SUFFIX(t) ((binary) ? (t "iB") : (t "B"))
if (!out)
return -1;
int written;
if (S_LESS(K))
written = snprintf(out, out_len, "%ld B", bytes);
else if (S_LESS(M))
written = snprintf(out, out_len, "%.*f %s", (precision == -1) ? 0 : precision, bytes / (double)(S_CONV(K)), S_SUFFIX("K"));
else if (S_LESS(G))
written = snprintf(out, out_len, "%.*f %s", (precision == -1) ? 1 : precision, bytes / (double)(S_CONV(M)), S_SUFFIX("M"));
else if (S_LESS(T))
written = snprintf(out, out_len, "%.*f %s", (precision == -1) ? 2 : precision, bytes / (double)(S_CONV(G)), S_SUFFIX("G"));
else
written = snprintf(out, out_len, "%.*f %s", (precision == -1) ? 3 : precision, bytes / (double)(S_CONV(T)), S_SUFFIX("T"));
return (written >= out_len) ? -1 : written;
#undef S_SUFFIX
#undef S_LESS
#undef S_CONV
}
void util_byte2hex(const unsigned char* bytes, int bytes_len, int uppercase, char* out) {
const char* hex = (uppercase) ? "0123456789ABCDEF" : "0123456789abcdef";
for (int i = 0; i < bytes_len; i++) {
*out++ = hex[bytes[i] >> 4];
*out++ = hex[bytes[i] & 0xF];
}
*out = '\0';
}

19
src/util.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef UTIL_H
#define UTIL_H
/*
* Convert byte to human readable.
* If binary is non-zero, use 1024 as conversation number, or else
* use 1000.
* precision is the precision, or -1 for the default
* Returns the characters written on success, or -1 on error (like out is too small)
*/
int util_byte2human(long int bytes, int binary, int precision, char* out, size_t out_len);
/*
* Convert raw bytes in 'bytes' to hex format into out
* out has to be at least bytes_len * 2 + 1 large
*/
void util_byte2hex(const unsigned char* bytes, int bytes_len, int uppercase, char* out);
#endif

478
src/verify.c Normal file
View File

@ -0,0 +1,478 @@
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "verify.h"
#include "sha1.h"
#include "opts.h"
#ifdef MT
#include <sys/sysinfo.h>
#include <pthread.h>
#include <semaphore.h>
typedef struct {
uint8_t* piece_data;
int piece_data_size;
int piece_index;
const sha1sum_t* expected_result;
int results_match;
int done;
sem_t sem_filled_buffer;
} verify_thread_data_t;
typedef struct {
pthread_t thread;
verify_thread_data_t thread_data;
} verify_thread_t;
static int mt_max_thread = 0;
static verify_thread_t* mt_threads = NULL;
/* Which thread data to fill? */
static verify_thread_data_t* mt_td_tofill = NULL;
static pthread_cond_t mt_cond_tofill;
/* Mutex for _tofill */
static pthread_mutex_t mt_mut_tofill;
/* Worker thread signals to main thread to fill it's buffer */
static sem_t mt_sem_needs_fill;
#endif
/*
* Check if file, or directory exists
* Return 0 if yes, or errno
*/
static int verify_file_exists(const char* path) {
if (access(path, F_OK|R_OK) == 0)
return 0;
return errno;
}
/*
* Uselessly complex function to get the file path into a stack
* buffer if the size is enough, or allocate one and copy it there
* heap_str needs to be freed, if it's not null
* Returns a pointer to the path string
*/
static char* verify_get_path(fileinfo_t* finfo, const char* data_dir, \
size_t data_dir_len, const char* torrent_name, int torrent_name_len, \
char* stack_str, size_t stack_str_size, \
char** heap_str, size_t* heap_str_size) {
int path_len = metainfo_fileinfo_path(finfo, NULL);
int req_len = path_len + data_dir_len + torrent_name_len + 1 + 1;
char* path_ptr = stack_str;
if (req_len > stack_str_size) {
/* Stack is not large enough, use the heap */
if (!(*heap_str)) {
/* Heap is not yet allocated */
*heap_str_size = req_len;
path_ptr = *heap_str = malloc(*heap_str_size);
} else if (path_len > *heap_str_size) {
/* Heap size is not large enough, reallocate */
*heap_str_size = req_len;
path_ptr = *heap_str = realloc(*heap_str, *heap_str_size);
} else {
/* Heap is allocated, and is large enough */
path_ptr = *heap_str;
}
}
char* path_ptr_curr = path_ptr;
memcpy(path_ptr_curr, data_dir, data_dir_len);
path_ptr_curr += data_dir_len;
/* WARNING: Not portable here */
*path_ptr_curr++ = '/';
memcpy(path_ptr_curr, torrent_name, torrent_name_len);
path_ptr_curr += torrent_name_len;
/* This may include multiple /'s but idc lol */
*path_ptr_curr++ = '/';
path_ptr_curr += metainfo_fileinfo_path(finfo, path_ptr_curr);
*path_ptr_curr = '\0';
return path_ptr;
}
typedef int (*fullpath_iter_cb)(const char* path, void* data);
/*
* Call the callback function with every full path
* in the torrent. If callbacks returns non-zero, terminate the iter
* If append_torrent_folder is 1 and the torrent is a multi file one,
* the torrent name will be appended after data_dir
*/
static int verify_fullpath_iter(metainfo_t* m, const char* data_dir, \
int append_torrent_folder, fullpath_iter_cb cb, void* cb_data) {
/* A sensible default on the stack */
char path_buffer[512];
/* If the above buffer is too small, malloc one */
char* path_heap_ptr = NULL;
size_t path_heap_size;
const char* torrent_folder = "";
int torrent_folder_len = 0;
int result = 0;
size_t data_dir_len = strlen(data_dir);
fileinfo_t finfo;
if (metainfo_is_multi_file(m)) {
fileiter_t fiter;
if (append_torrent_folder)
metainfo_name(m, &torrent_folder, &torrent_folder_len);
metainfo_fileiter_create(m, &fiter);
while (result == 0 && metainfo_file_next(&fiter, &finfo) == 0) {
char* path = verify_get_path(&finfo, data_dir, data_dir_len, \
torrent_folder, torrent_folder_len, path_buffer, \
sizeof(path_buffer), &path_heap_ptr, &path_heap_size);
result = cb(path, cb_data);
}
} else {
metainfo_fileinfo(m, &finfo);
char* path = verify_get_path(&finfo, data_dir, data_dir_len, \
torrent_folder, torrent_folder_len, path_buffer, \
sizeof(path_buffer), &path_heap_ptr, &path_heap_size);
result = cb(path, cb_data);
}
if (path_heap_ptr)
free(path_heap_ptr);
return result;
}
static int verify_is_files_exists_cb(const char* path, void* data) {
return verify_file_exists(path);
}
/*
* Check if the files in the torrent exists, or not
* If append_torrent_folder is 1 and the torrent is a multi file one,
* the torrent name will be appended after data_dir
* Return 0 if yes, and is readable, or an errno
*/
static int verify_is_files_exists(metainfo_t* m, const char* data_dir, \
int append_torrent_folder) {
return verify_fullpath_iter(m, data_dir, append_torrent_folder, \
verify_is_files_exists_cb, NULL);
}
/*
* Read in 1 piece size amount of data
* Returns 0 if buffer got filled, -1 if error and 1 if end of the file
*/
static int verify_read_piece(const char* path, FILE** f, int piece_size, \
uint8_t* out_bytes, int* out_bytes_size) {
if (!*f) {
/* If first file, open it */
*f = fopen(path, "rb");
if (!*f)
return -1;
}
int read;
out_bytes += *out_bytes_size;
while (*out_bytes_size != piece_size && (read = fread(out_bytes, \
1, piece_size - *out_bytes_size, *f)) > 0) {
*out_bytes_size += read;
out_bytes += read;
}
if (ferror