summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile10
-rw-r--r--torrenthash.c226
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();
+}