227 lines
5.6 KiB
C
227 lines
5.6 KiB
C
|
/*
|
||
|
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();
|
||
|
}
|