Initial commit
This commit is contained in:
commit
bd6bbe25c2
3 changed files with 238 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
torrenthash
|
||||
*.torrent
|
10
Makefile
Normal file
10
Makefile
Normal file
|
@ -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
|
226
torrenthash.c
Normal file
226
torrenthash.c
Normal file
|
@ -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();
|
||||
}
|
Reference in a new issue