diff options
author | Matthias Schiffer <mschiffer@universe-factory.net> | 2017-11-14 15:22:06 +0100 |
---|---|---|
committer | Matthias Schiffer <mschiffer@universe-factory.net> | 2017-11-14 15:22:06 +0100 |
commit | bd6bbe25c29f019b259b8e66ba72d8a913f41118 (patch) | |
tree | a5a08a572aece77975f4d26e94865b0156a28a4e | |
download | torrenthash-bd6bbe25c29f019b259b8e66ba72d8a913f41118.tar torrenthash-bd6bbe25c29f019b259b8e66ba72d8a913f41118.zip |
Initial commit
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | torrenthash.c | 226 |
3 files changed, 238 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff15a7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +torrenthash +*.torrent diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..76c2ef8 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +CRYPTO_CFLAGS := $(shell pkg-config --cflags libcrypto) +CRYPTO_LDFLAGS := $(shell pkg-config --libs libcrypto) + +CFLAGS += -Wall -Wextra $(CRYPTO_CFLAGS) +LDFLAGS += $(CRYPTO_LDFLAGS) + +all : torrenthash + +clean : + rm -f torrenthash diff --git a/torrenthash.c b/torrenthash.c new file mode 100644 index 0000000..0d237f7 --- /dev/null +++ b/torrenthash.c @@ -0,0 +1,226 @@ +/* + Copyright (c) 2017, Matthias Schiffer <mschiffer@universe-factory.net> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <openssl/sha.h> + + +static const char * skip_bcode(const char *data, const char *end); + + +/** Prints a (printf-style) error message and exits */ +#define exit_error(...) do { fprintf(stderr, __VA_ARGS__); exit(1); } while (0) + +/** Prints an error messages about unexpected EOF and exits */ +__attribute__((noreturn)) +static void eof(void) { + exit_error("unexptected EOF\n"); +} + + +/** Returns the length of the file assiciated with file descriptor fd */ +static size_t flen(int fd) { + struct stat statbuf; + if (fstat(fd, &statbuf) < 0) + exit_error("fstat: %m\n"); + + return statbuf.st_size; +} + + +/** + Returns a pointer after the current integer + + An integer has the form "i42e". 'i' is already skipped by skip_bcode. +*/ +static const char * skip_int(const char *data, const char *end) { + if (data >= end) + eof(); + + const char *e = memchr(data, 'e', end-data); + if (!e) + eof(); + + return e+1; +} + +/** + Returns a pointer after the current byte string + + A byte string has the form length:data (length is a decimal number + and data can be arbitrary binary data). +*/ +static const char * skip_string(const char *data, const char *end) { + if (data >= end) + eof(); + + const char *colon = memchr(data, ':', end-data); + if (!colon) + eof(); + + char *endp; + size_t len = (size_t)strtoull(data, &endp, 10); + if (endp != colon) + exit_error("syntax error\n"); + + const char *ret = colon + 1 + len; + if (ret >= end || ret <= colon /* Check for overflow */) + eof(); + + return ret; +} + +/** + Returns a pointer after the current dictionary + + A dictionary has the form "dkeyvaluekeyvalue...e", where keys are + byte strings and values are arbitrary bcode objects. 'd' is already + skipped by skip_bcode. +*/ +static const char * skip_dict(const char *key, const char *end) { + while (key < end) { + if (key[0] == 'e') + return key+1; + + const char *value = skip_string(key, end); + key = skip_bcode(value, end); + } + + eof(); +} + +/** + Returns a pointer after the current list + + A dictionary has the form "lvaluevalue...e", where values are arbitrary + bcode objects. 'l' is already skipped by skip_bcode. +*/ +static const char * skip_list(const char *data, const char *end) { + while (data < end) { + if (data[0] == 'e') + return data+1; + + data = skip_bcode(data, end); + } + + eof(); +} + +/** + Returns a pointer after the current bencoded object + + Possible object types are integers, byte strings, dictionaries and + lists. +*/ +static const char * skip_bcode(const char *data, const char *end) { + if (data >= end) + eof(); + + switch(data[0]) { + case 'i': + return skip_int(data+1, end); + case '0' ... '9': + return skip_string(data, end); + case 'd': + return skip_dict(data+1, end); + case 'l': + return skip_list(data+1, end); + default: + exit_error("unexpected character '%c'\n", data[0]); + } +} + + +/** + Prints the SHA1 hash of the data block bounded by the data and end + arguments. +*/ +static void print_hash(const char *data, const char *end) { + SHA_CTX ctx; + unsigned char hash[20]; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, (unsigned char *)data, end-data); + SHA1_Final(hash, &ctx); + + for (size_t i = 0; i < 20; i++) + printf("%02x", hash[i]); + + printf("\n"); +} + + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: torrenthash <file>\n"); + return 1; + } + + int fd = open(argv[1], O_RDONLY); + if (fd < 0) + exit_error("open: %m\n"); + size_t len = flen(fd); + if (len == 0) + exit_error("empty file\n"); + + /* Map the file into memory */ + const char *data = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) + exit_error("mmap: %m\n"); + + const char *end = data + len; + + /* The file must have a dictionary on the top level */ + if (data[0] != 'd') + exit_error("unexpected character '%c'\n", data[0]); + + /* Iterate through the dictionary entries */ + for (const char *key = data+1; key < end;) { + if (key[0] == 'e') + exit_error("key 'info' not found\n"); + + const char *value = skip_string(key, end); + const char *next_key = skip_bcode(value, end); + + /* Check for "info" key */ + if ((value-key) == 6 && memcmp(key, "4:info", 6) == 0) { + print_hash(value, next_key); + return 0; + } + + key = next_key; + } + + eof(); +} |