commit 8c36138e5b23b9b8bf9bbdb37afdddb64cdd0f0e
parent e8afeb7ad7cb399b2bf71806c009d5e0733a1137
Author: sin <sin@2f30.org>
Date: Sun, 27 Dec 2015 17:56:14 +0000
rename to torrentd
Diffstat:
M | Makefile | | | 4 | ++-- |
D | storrentd.c | | | 1221 | ------------------------------------------------------------------------------ |
A | torrentd.c | | | 1221 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 1223 insertions(+), 1223 deletions(-)
diff --git a/Makefile b/Makefile
@@ -11,8 +11,8 @@ LIBS = -L$(CURLLIB) -lcurl
CFLAGS = -g -std=c99 -Wall -Wextra $(INCS)
LDFLAGS = $(LIBS)
-OBJ = storrentd.o sha1.o
-BIN = storrentd
+OBJ = torrentd.o sha1.o
+BIN = torrentd
all: $(BIN)
diff --git a/storrentd.c b/storrentd.c
@@ -1,1221 +0,0 @@
-/* See LICENSE file for copyright and license details. */
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-
-#include <netdb.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-
-#include <ctype.h>
-#include <err.h>
-#include <fcntl.h>
-#include <setjmp.h>
-#include <stdarg.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <curl/curl.h>
-
-#include "sha1.h"
-
-#define PORT 49155
-
-enum {
- EVSTARTED,
- EVCOMPLETED,
- EVSTOPPED
-};
-
-struct buf {
- char *p;
- size_t n;
-};
-
-struct ben {
- int type;
- struct ben *k;
- struct ben *v;
- char *start, *end;
- char *s;
- long long i;
- long long len;
- struct ben *next;
-};
-
-struct file {
- size_t len;
- char *path;
-};
-
-struct announce {
- char **urls;
- size_t len;
-};
-
-struct peer {
- char hostname[16]; /* ipv4 only */
- char port[6];
- int amchoking;
- int aminterested;
- int peerchoking;
- int peerinterested;
-};
-
-struct torrent {
- char *buf;
- size_t buflen;
- struct ben *ben;
- struct ben *info;
- struct ben *pieces;
- struct announce *announcers;
- size_t nannouncers;
- char *filename;
- struct file *files;
- size_t nfiles;
- size_t totallen;
- uint8_t infohash[20];
- long long piecelen;
- long long npieces;
- uint32_t *piecebm;
- struct peer *peers;
- size_t npeers;
- size_t uploaded;
- size_t downloaded;
-};
-
-/* util functions */
-void *emalloc(size_t);
-void *ecalloc(size_t, size_t);
-uint32_t *newbit(int);
-void setbit(uint32_t *, int);
-void clrbit(uint32_t *, int);
-int tstbit(uint32_t *, int);
-uint8_t hex2int(int);
-uint8_t int2hex(int);
-uint8_t *hex2bin(const char *, uint8_t *, size_t);
-char *bin2hex(const uint8_t *, char *, size_t);
-void sha1sum(uint8_t *, unsigned long, uint8_t *);
-int readfile(char *, char **, size_t *);
-ssize_t writen(int, const void *, size_t);
-ssize_t readn(int, void *, size_t);
-#undef strlcpy
-size_t strlcpy(char *, const char *, size_t);
-#undef strlcat
-size_t strlcat(char *, const char *, size_t);
-void initbuf(struct buf *);
-void freebuf(struct buf *);
-size_t fillbuf(struct buf *, void *, size_t);
-int unpack(uint8_t *, size_t, char *, ...);
-int pack(uint8_t *, size_t, char *, ...);
-int dial(char *, char *);
-
-/* ben parsing functions */
-char *parse(char *, char *, struct ben **);
-char *bdecode(char *, char *, struct ben **);
-char *bstr2str(struct ben *);
-int bstrcmp(struct ben *, struct ben *);
-struct ben *dlook(struct ben *, struct ben *);
-struct ben *dlookstr(struct ben *, char *);
-void bfree(struct ben *);
-void bprint(struct ben *, int);
-
-/* torrent file handling */
-void dumptorrent(struct torrent *);
-struct torrent *loadtorrent(char *);
-void unloadtorrent(struct torrent *);
-int piecehash(struct torrent *, long long, uint8_t *);
-char *peerid(void);
-
-/* tracker related functionality */
-int trackerget(struct torrent *, int);
-
-/* peer handling */
-int parsepeers(struct torrent *, struct buf *);
-
-jmp_buf savesp;
-
-void *
-emalloc(size_t size)
-{
- void *p;
-
- p = malloc(size);
- if (!p)
- err(1, "malloc");
- return p;
-}
-
-void *
-ecalloc(size_t n, size_t size)
-{
- void *p;
-
- p = calloc(n, size);
- if (!p)
- err(1, "calloc");
- return p;
-}
-
-uint32_t *
-newbit(int n)
-{
- return ecalloc((n + 31) / 32, 4);
-}
-
-void
-setbit(uint32_t *b, int n)
-{
- b[n / 32] |= (1 << n);
-}
-
-void
-clrbit(uint32_t *b, int n)
-{
- b[n / 32] &= ~(1 << n);
-}
-
-int
-tstbit(uint32_t *b, int n)
-{
- return (b[n / 32] & (1 << n % 32)) != 0;
-}
-
-uint8_t
-hex2int(int c)
-{
- c = toupper(c);
- if (c >= '0' && c <= '9')
- return c - '0';
- if (c >= 'A' && c <= 'F')
- return c - 'A' + 10;
- errx(1, "%c is not hex", c);
-}
-
-uint8_t
-int2hex(int c)
-{
- const char *hex = "0123456789ABCDEF";
- return hex[c & 0xf];
-}
-
-uint8_t *
-hex2bin(const char *h, uint8_t *b, size_t n)
-{
- size_t i;
-
- for (i = 0; i < n; i++)
- b[i] = hex2int(h[i * 2]) << 4 | hex2int(h[i * 2 + 1]);
- return b;
-}
-
-char *
-bin2hex(const uint8_t *b, char *h, size_t n)
-{
- size_t i;
-
- for (i = 0; i < n; i++) {
- h[i * 2] = int2hex(b[i] >> 4);
- h[i * 2 + 1] = int2hex(b[i]);
- }
- h[i * 2] = '\0';
- return h;
-}
-
-void
-sha1sum(uint8_t *b, unsigned long n, uint8_t *md)
-{
- struct sha1 ctx;
-
- sha1_init(&ctx);
- sha1_update(&ctx, b, n);
- sha1_sum(&ctx, md);
-}
-
-int
-readfile(char *file, char **b, size_t *n)
-{
- struct stat sb;
- int fd;
-
- if ((fd = open(file, O_RDONLY)) < 0)
- return -1;
- if (fstat(fd, &sb) < 0) {
- close(fd);
- return -1;
- }
- *b = emalloc(*n = sb.st_size);
- if (read(fd, *b, *n) != (ssize_t)*n) {
- free(*b);
- close(fd);
- return -1;
- }
- close(fd);
- return 0;
-}
-
-ssize_t
-writen(int fd, const void *buf, size_t count)
-{
- const char *p = buf;
- ssize_t n;
- size_t total = 0;
-
- while (count > 0) {
- n = write(fd, p + total, count);
- if (n < 0)
- err(1, "write");
- else if (n == 0)
- break;
- total += n;
- count -= n;
- }
- return total;
-}
-
-ssize_t
-readn(int fd, void *buf, size_t count)
-{
- char *p = buf;
- ssize_t n;
- size_t total = 0;
-
- while (count > 0) {
- n = read(fd, p + total, count);
- if (n < 0)
- err(1, "read");
- else if (n == 0)
- break;
- total += n;
- count -= n;
- }
- return total;
-}
-
-size_t
-strlcpy(char *dst, const char *src, size_t dsize)
-{
- const char *osrc = src;
- size_t nleft = dsize;
-
- /* Copy as many bytes as will fit. */
- if (nleft != 0) {
- while (--nleft != 0) {
- if ((*dst++ = *src++) == '\0')
- break;
- }
- }
-
- /* Not enough room in dst, add NUL and traverse rest of src. */
- if (nleft == 0) {
- if (dsize != 0)
- *dst = '\0'; /* NUL-terminate dst */
- while (*src++)
- ;
- }
-
- return(src - osrc - 1); /* count does not include NUL */
-}
-
-size_t
-strlcat(char *dst, const char *src, size_t dsize)
-{
- const char *odst = dst;
- const char *osrc = src;
- size_t n = dsize;
- size_t dlen;
-
- /* Find the end of dst and adjust bytes left but don't go past end. */
- while (n-- != 0 && *dst != '\0')
- dst++;
- dlen = dst - odst;
- n = dsize - dlen;
-
- if (n-- == 0)
- return(dlen + strlen(src));
- while (*src != '\0') {
- if (n != 0) {
- *dst++ = *src;
- n--;
- }
- src++;
- }
- *dst = '\0';
-
- return(dlen + (src - osrc)); /* count does not include NUL */
-}
-
-void
-initbuf(struct buf *b)
-{
- memset(b, 0, sizeof(*b));
-}
-
-void
-freebuf(struct buf *b)
-{
- free(b->p);
- b->p = NULL;
-}
-
-size_t
-fillbuf(struct buf *b, void *ptr, size_t n)
-{
- if (!(b->p = realloc(b->p, b->n + n)))
- err(1, "realloc");
- memcpy(b->p + b->n, ptr, n);
- b->n += n;
- return b->n;
-}
-
-int
-unpack(uint8_t *s, size_t n, char *fmt, ...)
-{
- va_list arg;
- uint8_t *b, *e;
-
- b = s;
- e = b + n;
- va_start(arg, fmt);
- for (; *fmt; fmt++) {
- switch (*fmt) {
- case '_':
- s++;
- break;
- case 'b':
- if (s + 1 > e)
- goto err;
- *va_arg(arg, int *) = *s++;
- break;
- case 'w':
- if (s + 2 > e)
- goto err;
- *va_arg(arg, int *) = s[0] << 8 | s[1];
- s += 2;
- break;
- case 'l':
- if (s + 4 > e)
- goto err;
- *va_arg(arg, int *) = s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
- s += 4;
- break;
- case 'v':
- if (s + 4 > e)
- goto err;
- *va_arg(arg, long long *) =
- (long long)s[0] << 56 |
- (long long)s[1] << 48 |
- (long long)s[2] << 40 |
- (long long)s[3] << 32 |
- (long long)s[4] << 24 |
- (long long)s[5] << 16 |
- (long long)s[6] << 8 |
- (long long)s[7];
- s += 8;
- break;
- }
- }
- va_end(arg);
- return s - b;
-err:
- va_end(arg);
- return -1;
-}
-
-int
-pack(uint8_t *s, size_t n, char *fmt, ...)
-{
- va_list arg;
- uint8_t *b, *e;
- long long v;
- int i;
-
- b = s;
- e = b + n;
- va_start(arg, fmt);
- for (; *fmt; fmt++) {
- switch (*fmt) {
- case '_':
- i = 0;
- if (0) {
- case 'b':
- i = va_arg(arg, int);
- }
- if (s + 1 > e)
- goto err;
- *s++ = i & 0xff;
- break;
- case 'w':
- i = va_arg(arg, int);
- if (s + 2 > e)
- goto err;
- *s++ = (i >> 8) & 0xff;
- *s++ = i & 0xff;
- break;
- case 'l':
- i = va_arg(arg, int);
- if (s + 4 > e)
- goto err;
- *s++ = (i >> 24) & 0xff;
- *s++ = (i >> 16) & 0xff;
- *s++ = (i >> 8) & 0xff;
- *s++ = i & 0xff;
- break;
- case 'v':
- v = va_arg(arg, long long);
- if (s + 8 > e)
- goto err;
- *s++ = (v >> 56) & 0xff;
- *s++ = (v >> 48) & 0xff;
- *s++ = (v >> 40) & 0xff;
- *s++ = (v >> 32) & 0xff;
- *s++ = (v >> 24) & 0xff;
- *s++ = (v >> 16) & 0xff;
- *s++ = (v >> 8) & 0xff;
- *s++ = v & 0xff;
- break;
- case '*':
- i = va_arg(arg, int);
- if (s + i > e)
- goto err;
- memmove(s, va_arg(arg, void *), i);
- s += i;
- break;
- }
- }
- va_end(arg);
- return s - b;
-err:
- va_end(arg);
- return -1;
-}
-
-int
-dial(char *host, char *port)
-{
- struct addrinfo hints, *res, *r;
- int s;
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
- hints.ai_socktype = SOCK_STREAM;
- if (getaddrinfo(host, port, &hints, &res)) {
- warnx("can't resolve %s", host);
- return -1;
- }
- for (r = res; r; r = r->ai_next) {
- s = socket(r->ai_family, r->ai_socktype, 0);
- if (s < 0)
- continue;
- if (!connect(s, r->ai_addr, r->ai_addrlen))
- break;
- close(s);
- }
- freeaddrinfo(res);
- if (!r) {
- warnx("cannot connect to %s on port %s", host, port);
- return -1;
- }
- return s;
-}
-
-void
-error(char *msg)
-{
- fprintf(stderr, "%s\n", msg);
- longjmp(savesp, 1);
-}
-
-char *
-parsestr(char *s, char *e, struct ben **b)
-{
- char *p;
- long long len = 0;
-
- for (p = s; p != e && isdigit((int)*p); p++)
- len = len * 10 + *p - '0';
- if (p == e)
- error("bad string");
- if (*p++ != ':')
- error("expected ':' in string");
- if (p + len > e)
- error("bad string");
- p += len;
- *b = emalloc(sizeof(**b));
- (*b)->type = 's';
- (*b)->s = &p[-len];
- (*b)->start = s;
- (*b)->end = p;
- (*b)->len = len;
- return p;
-}
-
-char *
-parseint(char *s, char *e, struct ben **b)
-{
- char *p = s;
- long long v;
- int isneg = 0, iszero = 0;
-
- if (*p != 'i')
- error("expected integer");
- if (++p == e)
- error("bad integer");
-
- if (*p == '-')
- isneg = 1;
- else if (*p == '0')
- iszero = 1;
- else
- p--;
-
- v = 0;
- while (1) {
- if (++p == e)
- error("bad integer");
- if (*p == 'e' || iszero) {
- if (*p != 'e')
- error("0 not followed by e");
- break;
- }
- if (isneg && *p == '0')
- error("i-0 is invalid");
- if (!isdigit((int)*p))
- error("unexpected char in integer");
- v = v * 10 + *p - '0';
- }
- if (*p++ != 'e')
- error("expected terminator");
-
- *b = emalloc(sizeof(**b));
- (*b)->type = 'i';
- (*b)->start = s;
- (*b)->end = p;
- (*b)->i = isneg ? -v : v;
- return p;
-}
-
-char *
-parsedl(char *s, char *e, struct ben **b)
-{
- char *p = s;
- struct ben head;
- struct ben *bp = &head;
- int type = *p;
- long long len = 0;
-
- memset(&head, 0, sizeof(head));
- if (*p != 'd' && *p != 'l')
- error("expected dictionary or list");
- p++;
- while (1) {
- if (p == e)
- error("bad dictionary or list");
- if (*p == 'e')
- break;
- len++;
- bp->next = emalloc(sizeof(*bp));
- bp = bp->next;
- bp->type = type;
- bp->k = NULL;
- if (type == 'd')
- p = parsestr(p, e, &bp->k);
- p = parse(p, e, &bp->v);
- bp->next = NULL;
- }
- if (*p++ != 'e')
- error("expected terminator");
-
- *b = head.next;
- if (!head.next) {
- *b = malloc(sizeof(**b));
- (*b)->type = type;
- (*b)->k = NULL;
- (*b)->v = NULL;
- (*b)->next = NULL;
- }
- (*b)->len = len;
- (*b)->start = s;
- (*b)->end = p;
- return p;
-}
-
-char *
-parse(char *s, char *e, struct ben **b)
-{
- if (s == e)
- return s;
- switch (*s) {
- case 'i':
- return parseint(s, e, b);
- break;
- case 'l':
- case 'd':
- return parsedl(s, e, b);
- break;
- default:
- if (!isdigit((int)*s))
- error("unknown type");
- return parsestr(s, e, b);
- }
-}
-
-char *
-bdecode(char *s, char *e, struct ben **b)
-{
- *b = NULL;
- if (!setjmp(savesp))
- return parse(s, e, b);
- bfree(*b);
- *b = NULL;
- return NULL;
-}
-
-char *
-bstr2str(struct ben *b)
-{
- char *s;
-
- s = emalloc(b->len + 1);
- memcpy(s, b->s, b->len);
- s[b->len] = '\0';
- return s;
-}
-
-int
-bstrcmp(struct ben *a, struct ben *b)
-{
- long long i;
-
- if (a->type != 's' || b->type != 's')
- return 1;
- if (a->len != b->len)
- return 1;
- for (i = 0; i < a->len; i++)
- if (a->s[i] != b->s[i])
- return 1;
- return 0;
-}
-
-struct ben *
-dlook(struct ben *d, struct ben *k)
-{
- struct ben *b;
-
- if (d->type != 'd' || k->type != 's')
- return NULL;
- for (b = d; b; b = b->next)
- if (!bstrcmp(b->k, k))
- return b->v;
- return NULL;
-}
-
-struct ben *
-dlookstr(struct ben *d, char *s)
-{
- struct ben k;
-
- k.type = 's';
- k.s = s;
- k.len = strlen(s);
- return dlook(d, &k);
-}
-
-void
-bfree(struct ben *b)
-{
- struct ben *bp, *btmp;
-
- if (!b)
- return;
- switch (b->type) {
- case 's':
- free(b);
- break;
- case 'i':
- free(b);
- break;
- case 'l':
- case 'd':
- bp = b;
- while (bp) {
- btmp = bp->next;
- bfree(bp->k);
- bfree(bp->v);
- free(bp);
- bp = btmp;
- }
- break;
- }
-}
-
-void
-printstr(struct ben *b)
-{
- int64_t i;
-
- for (i = 0; i < b->len; i++)
- putchar(b->s[i]);
-}
-
-void
-printindent(int indent)
-{
- while (indent--)
- putchar(' ');
-}
-
-void
-bprint(struct ben *b, int indent)
-{
- struct ben *bl;
- struct ben *bd;
-
- if (!b)
- return;
- switch (b->type) {
- case 's':
- printindent(indent);
- printstr(b);
- putchar('\n');
- break;
- case 'i':
- printindent(indent);
- printf("%lld\n", (long long)b->i);
- break;
- case 'l':
- printindent(indent);
- printf("[\n");
- for (bl = b; bl; bl = bl->next)
- bprint(bl->v, indent + 1);
- printindent(indent);
- printf("]\n");
- break;
- case 'd':
- printindent(indent);
- printf("{\n");
- for (bd = b; bd; bd = bd->next) {
- if (bd->k) {
- printindent(indent + 1);
- printstr(bd->k);
- printf(" :\n");
- bprint(bd->v, indent + 2);
- }
- }
- printindent(indent);
- printf("}\n");
- break;
- }
-}
-
-void
-calcinfohash(struct torrent *t)
-{
- sha1sum((uint8_t *)t->info->start,
- t->info->end - t->info->start, t->infohash);
-}
-
-void
-dumptorrent(struct torrent *t)
-{
- uint8_t md[20];
- char hex[41];
- long long i;
- size_t n, m;
-
- printf("announcers:\n");
- for (n = 0; n < t->nannouncers; n++) {
- printf("%zu: announcer\n", n);
- for (m = 0; m < t->announcers[n].len; m++)
- printf("\t%zu: %s\n", m,
- t->announcers[n].urls[m]);
- }
-
- if (t->filename[0])
- printf("directory: %s\n", t->filename);
- if (t->nfiles)
- printf("files (%zu):\n", t->nfiles);
- for (n = 0; n < t->nfiles; n++) {
- printf("\tpath: %s, length: %zu\n",
- t->files[n].path, t->files[n].len);
- }
-
- bin2hex(t->infohash, hex, 20);
- printf("infohash: %s\n", hex);
- printf("piecelen: %lld\n", t->piecelen);
- printf("npieces: %lld\n", t->npieces);
-
- for (i = 0; i < t->npieces; i++) {
- piecehash(t, i, md);
- bin2hex(md, hex, 20);
- printf("piece[%lld] hash: %s\n", i, hex);
- }
-}
-
-struct torrent *
-loadtorrent(char *f)
-{
- struct torrent *t;
- struct ben *files, *file;
- struct ben *length, *path, *tmp, *a;
- size_t i, j, pathlen;
- char *p;
-
- t = ecalloc(1, sizeof(*t));
- if (readfile(f, &t->buf, &t->buflen) < 0) {
- warnx("failed to read %s", f);
- goto fail;
- }
- if (!bdecode(t->buf, t->buf + t->buflen, &t->ben)) {
- warnx("failed to decode %s", f);
- goto fail;
- }
-
- t->info = dlookstr(t->ben, "info");
- if (!t->info) {
- warnx("no info dictionary in %s", f);
- goto fail;
- }
-
- t->pieces = dlookstr(t->info, "pieces");
- if (!t->pieces) {
- warnx("no pieces field in %s", f);
- goto fail;
- }
- if (t->pieces->len % 20) {
- warnx("incorrect pieces length in %s", f);
- goto fail;
- }
-
- if ((tmp = dlookstr(t->ben, "announce-list")) && tmp->len) {
- if (tmp->type != 'l') {
- warnx("announce-list must be of type list");
- goto fail;
- }
- t->announcers = ecalloc(tmp->len, sizeof(struct announce));
-
- for (i = 0; tmp; tmp = tmp->next, i++, t->nannouncers++) {
- if (tmp->v->type != 'l') {
- warnx("announce-list item must be of type list");
- goto fail;
- }
- if (!tmp->v->len)
- continue;
- t->announcers[i].urls = ecalloc(tmp->v->len, sizeof(char *));
- for (a = tmp->v, j = 0; a; a = a->next, j++, t->announcers[i].len++) {
- if (a->v->type != 's') {
- warnx("announce item must be of type string");
- goto fail;
- }
- t->announcers[i].urls[j] = bstr2str(a->v);
- }
- }
- } else if ((tmp = dlookstr(t->ben, "announce"))) {
- t->announcers = ecalloc(tmp->len, sizeof(struct announce));
- t->nannouncers = 1;
- t->announcers[0].len = 1;
- t->announcers[0].urls = ecalloc(1, sizeof(char *));
- t->announcers[0].urls[0] = bstr2str(tmp);
- } else {
- warnx("no announce field in %s", f);
- goto fail;
- }
-
- if (!dlookstr(t->info, "piece length")) {
- warnx("no piece length field in %s", f);
- goto fail;
- }
- t->piecelen = dlookstr(t->info, "piece length")->i;
- if (t->piecelen <= 0) {
- warnx("piecelen is <= 0 in %s", f);
- goto fail;
- }
-
- if (!dlookstr(t->info, "name")) {
- warnx("no filename field in %s", f);
- goto fail;
- }
- t->filename = bstr2str(dlookstr(t->info, "name"));
-
- /* multi-file mode or single file? */
- if ((files = dlookstr(t->info, "files"))) {
- if (files->type != 'l') {
- warnx("files must be of type list");
- goto fail;
- }
- t->files = ecalloc(files->len, sizeof(struct file));
-
- for (file = files; file; file = file->next, t->nfiles++) {
- if (!(length = dlookstr(file->v, "length"))) {
- warnx("no length field in %s", f);
- goto fail;
- }
- if (length->type != 'i') {
- warnx("length must be of type integer");
- goto fail;
- }
- if (length->i < 0) {
- warnx("length is < 0 in %s", f);
- goto fail;
- }
- t->totallen += length->i;
- t->files[t->nfiles].len = length->i;
-
- if (!(path = dlookstr(file->v, "path")))
- break;
- if (path->type != 'l')
- warnx("path must be of type list");
-
- /* check path list and count path buffer size */
- for (tmp = path, pathlen = 0; tmp; tmp = tmp->next) {
- if (tmp->v->type != 's') {
- warnx("path item must be of type string");
- goto fail;
- }
- pathlen += tmp->v->len + 1;
- }
- t->files[t->nfiles].path = ecalloc(1, pathlen);
- for (tmp = path; tmp; tmp = tmp->next) {
- p = bstr2str(tmp->v);
- strlcat(t->files[t->nfiles].path, p, pathlen);
- free(p);
- if (tmp != path && tmp->next)
- strlcat(t->files[t->nfiles].path, "/", pathlen);
- }
- }
- } else {
- if (!(length = dlookstr(t->info, "length"))) {
- warnx("no length field in %s", f);
- goto fail;
- }
- if (length->type != 'i') {
- warnx("length must be of type integer");
- goto fail;
- }
- if (length->i < 0) {
- warnx("length is < 0 in %s", f);
- goto fail;
- }
- t->nfiles = 1;
- t->files = ecalloc(1, sizeof(struct file));
- t->files[0].len = length->i;
- t->files[0].path = t->filename;
- t->filename = strdup("");
- t->totallen = length->i;
- }
-
- t->npieces = (t->totallen + t->piecelen - 1) / t->piecelen;
- t->piecebm = newbit(t->npieces);
-
- calcinfohash(t);
-
- return t;
-fail:
- unloadtorrent(t);
- return NULL;
-}
-
-void
-unloadtorrent(struct torrent *t)
-{
- size_t i, j;
-
- if (!t)
- return;
-
- i = t->nannouncers;
- while (i--) {
- j = t->announcers[i].len;
- while (j--)
- free(t->announcers[i].urls[j]);
- free(t->announcers[i].urls);
- }
- free(t->announcers);
-
- for (i = 0; i < t->nfiles; i++)
- free(t->files[i].path);
- free(t->files);
-
- bfree(t->ben);
- free(t->buf);
- free(t->piecebm);
- free(t);
-}
-
-int
-piecehash(struct torrent *t, long long n, uint8_t *md)
-{
- if (n < 0 || n >= t->npieces)
- return -1;
- memcpy(md, t->pieces->start + n * 20, 20);
- return 0;
-}
-
-char *
-peerid(void)
-{
- static char id[] = "-DP4242-000000000000";
- return id;
-}
-
-size_t
-writefn(void *ptr, size_t size, size_t nmemb, struct buf *b)
-{
- return fillbuf(b, ptr, size * nmemb);
-}
-
-int
-trackerget(struct torrent *t, int event)
-{
- CURL *curl;
- CURLcode res;
- struct buf reply;
- char buf[8192], *infohash, *id, *ev;
- int r;
-
- switch (event) {
- case EVSTARTED:
- ev = "started";
- break;
- case EVCOMPLETED:
- ev = "completed";
- break;
- case EVSTOPPED:
- ev = "stopped";
- break;
- default:
- return -1;
- }
-
- if (!(curl = curl_easy_init()))
- return -1;
-
- if (!(infohash = curl_easy_escape(curl, (char *)t->infohash, 20))) {
- r = -1;
- goto err0;
- }
-
- if (!(id = curl_easy_escape(curl, peerid(), 20))) {
- r = -1;
- goto err1;
- }
-
- r = snprintf(buf, sizeof(buf),
- "%s?info_hash=%s&peer_id=%s&port=%d&uploaded=%zu&"
- "downloaded=%zu&left=%zu&compact=1&event=%s",
- t->announcers[0].urls[0], infohash, id, PORT,
- t->uploaded, t->downloaded, t->totallen - t->downloaded,
- ev);
- if (r < 0 || (size_t)r >= sizeof(buf)) {
- r = -1;
- goto err2;
- }
-
- initbuf(&reply);
- curl_easy_setopt(curl, CURLOPT_URL, buf);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefn);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &reply);
- if ((res = curl_easy_perform(curl)) != CURLE_OK) {
- r = - 1;
- goto err3;
- }
-
- if (parsepeers(t, &reply) < 0) {
- r = - 1;
- goto err3;
- }
-
- r = 0;
-err3:
- freebuf(&reply);
-err2:
- curl_free(id);
-err1:
- curl_free(infohash);
-err0:
- curl_easy_cleanup(curl);
- return r;
-}
-
-int
-parsepeers(struct torrent *t, struct buf *b)
-{
- struct sockaddr_in sa;
- struct ben *reply, *peers;
- struct peer *peer;
- char *p, *errstr;
-
- if (!bdecode(b->p, b->p + b->n, &reply))
- return -1;
-
- if (dlookstr(reply, "failure reason")) {
- errstr = bstr2str(dlookstr(reply, "failure reason"));
- warnx("tracker failure: %s", errstr);
- bfree(reply);
- free(errstr);
- return -1;
- }
-
- if (!(peers = dlookstr(reply, "peers"))) {
- warnx("no peers field in tracker reply");
- bfree(reply);
- return -1;
- }
-
- if (peers->type != 's') {
- warnx("expected compact reply");
- bfree(reply);
- return -1;
- }
-
- if (peers->len % 6) {
- warnx("peers length needs to be a multiple of 6 bytes");
- bfree(reply);
- return -1;
- }
-
- for (p = peers->s; p < &peers->s[peers->len]; p += 6) {
- t->peers = realloc(t->peers, (t->npeers + 1) * sizeof(*t->peers));
- if (!t->peers)
- err(1, "realloc");
- peer = &t->peers[t->npeers];
- memset(peer, 0, sizeof(*peer));
- memcpy(&sa.sin_addr, p, 4);
- inet_ntop(AF_INET, &sa.sin_addr, peer->hostname,
- sizeof(peer->hostname));
- snprintf(peer->port, sizeof(peer->port), "%d",
- (int)ntohs(*(short *)&p[4]));
- printf("%s:%s\n", peer->hostname, peer->port);
- peer->amchoking = 1;
- peer->peerchoking = 1;
- t->npeers++;
- }
-
- free(reply);
- return 0;
-}
-
-void
-usage(char *argv0)
-{
- fprintf(stderr, "usage: %s torrent\n", argv0);
- exit(1);
-}
-
-int
-main(int argc, char *argv[])
-{
- struct torrent *t;
-
- if (argc != 2)
- usage(argv[0]);
- if (!(t = loadtorrent(argv[1])))
- exit(1);
- dumptorrent(t);
- trackerget(t, EVSTARTED);
- unloadtorrent(t);
- exit(0);
-}
diff --git a/torrentd.c b/torrentd.c
@@ -0,0 +1,1221 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <curl/curl.h>
+
+#include "sha1.h"
+
+#define PORT 49155
+
+enum {
+ EVSTARTED,
+ EVCOMPLETED,
+ EVSTOPPED
+};
+
+struct buf {
+ char *p;
+ size_t n;
+};
+
+struct ben {
+ int type;
+ struct ben *k;
+ struct ben *v;
+ char *start, *end;
+ char *s;
+ long long i;
+ long long len;
+ struct ben *next;
+};
+
+struct file {
+ size_t len;
+ char *path;
+};
+
+struct announce {
+ char **urls;
+ size_t len;
+};
+
+struct peer {
+ char hostname[16]; /* ipv4 only */
+ char port[6];
+ int amchoking;
+ int aminterested;
+ int peerchoking;
+ int peerinterested;
+};
+
+struct torrent {
+ char *buf;
+ size_t buflen;
+ struct ben *ben;
+ struct ben *info;
+ struct ben *pieces;
+ struct announce *announcers;
+ size_t nannouncers;
+ char *filename;
+ struct file *files;
+ size_t nfiles;
+ size_t totallen;
+ uint8_t infohash[20];
+ long long piecelen;
+ long long npieces;
+ uint32_t *piecebm;
+ struct peer *peers;
+ size_t npeers;
+ size_t uploaded;
+ size_t downloaded;
+};
+
+/* util functions */
+void *emalloc(size_t);
+void *ecalloc(size_t, size_t);
+uint32_t *newbit(int);
+void setbit(uint32_t *, int);
+void clrbit(uint32_t *, int);
+int tstbit(uint32_t *, int);
+uint8_t hex2int(int);
+uint8_t int2hex(int);
+uint8_t *hex2bin(const char *, uint8_t *, size_t);
+char *bin2hex(const uint8_t *, char *, size_t);
+void sha1sum(uint8_t *, unsigned long, uint8_t *);
+int readfile(char *, char **, size_t *);
+ssize_t writen(int, const void *, size_t);
+ssize_t readn(int, void *, size_t);
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);
+#undef strlcat
+size_t strlcat(char *, const char *, size_t);
+void initbuf(struct buf *);
+void freebuf(struct buf *);
+size_t fillbuf(struct buf *, void *, size_t);
+int unpack(uint8_t *, size_t, char *, ...);
+int pack(uint8_t *, size_t, char *, ...);
+int dial(char *, char *);
+
+/* ben parsing functions */
+char *parse(char *, char *, struct ben **);
+char *bdecode(char *, char *, struct ben **);
+char *bstr2str(struct ben *);
+int bstrcmp(struct ben *, struct ben *);
+struct ben *dlook(struct ben *, struct ben *);
+struct ben *dlookstr(struct ben *, char *);
+void bfree(struct ben *);
+void bprint(struct ben *, int);
+
+/* torrent file handling */
+void dumptorrent(struct torrent *);
+struct torrent *loadtorrent(char *);
+void unloadtorrent(struct torrent *);
+int piecehash(struct torrent *, long long, uint8_t *);
+char *peerid(void);
+
+/* tracker related functionality */
+int trackerget(struct torrent *, int);
+
+/* peer handling */
+int parsepeers(struct torrent *, struct buf *);
+
+jmp_buf savesp;
+
+void *
+emalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p)
+ err(1, "malloc");
+ return p;
+}
+
+void *
+ecalloc(size_t n, size_t size)
+{
+ void *p;
+
+ p = calloc(n, size);
+ if (!p)
+ err(1, "calloc");
+ return p;
+}
+
+uint32_t *
+newbit(int n)
+{
+ return ecalloc((n + 31) / 32, 4);
+}
+
+void
+setbit(uint32_t *b, int n)
+{
+ b[n / 32] |= (1 << n);
+}
+
+void
+clrbit(uint32_t *b, int n)
+{
+ b[n / 32] &= ~(1 << n);
+}
+
+int
+tstbit(uint32_t *b, int n)
+{
+ return (b[n / 32] & (1 << n % 32)) != 0;
+}
+
+uint8_t
+hex2int(int c)
+{
+ c = toupper(c);
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ errx(1, "%c is not hex", c);
+}
+
+uint8_t
+int2hex(int c)
+{
+ const char *hex = "0123456789ABCDEF";
+ return hex[c & 0xf];
+}
+
+uint8_t *
+hex2bin(const char *h, uint8_t *b, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ b[i] = hex2int(h[i * 2]) << 4 | hex2int(h[i * 2 + 1]);
+ return b;
+}
+
+char *
+bin2hex(const uint8_t *b, char *h, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++) {
+ h[i * 2] = int2hex(b[i] >> 4);
+ h[i * 2 + 1] = int2hex(b[i]);
+ }
+ h[i * 2] = '\0';
+ return h;
+}
+
+void
+sha1sum(uint8_t *b, unsigned long n, uint8_t *md)
+{
+ struct sha1 ctx;
+
+ sha1_init(&ctx);
+ sha1_update(&ctx, b, n);
+ sha1_sum(&ctx, md);
+}
+
+int
+readfile(char *file, char **b, size_t *n)
+{
+ struct stat sb;
+ int fd;
+
+ if ((fd = open(file, O_RDONLY)) < 0)
+ return -1;
+ if (fstat(fd, &sb) < 0) {
+ close(fd);
+ return -1;
+ }
+ *b = emalloc(*n = sb.st_size);
+ if (read(fd, *b, *n) != (ssize_t)*n) {
+ free(*b);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+ssize_t
+writen(int fd, const void *buf, size_t count)
+{
+ const char *p = buf;
+ ssize_t n;
+ size_t total = 0;
+
+ while (count > 0) {
+ n = write(fd, p + total, count);
+ if (n < 0)
+ err(1, "write");
+ else if (n == 0)
+ break;
+ total += n;
+ count -= n;
+ }
+ return total;
+}
+
+ssize_t
+readn(int fd, void *buf, size_t count)
+{
+ char *p = buf;
+ ssize_t n;
+ size_t total = 0;
+
+ while (count > 0) {
+ n = read(fd, p + total, count);
+ if (n < 0)
+ err(1, "read");
+ else if (n == 0)
+ break;
+ total += n;
+ count -= n;
+ }
+ return total;
+}
+
+size_t
+strlcpy(char *dst, const char *src, size_t dsize)
+{
+ const char *osrc = src;
+ size_t nleft = dsize;
+
+ /* Copy as many bytes as will fit. */
+ if (nleft != 0) {
+ while (--nleft != 0) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src. */
+ if (nleft == 0) {
+ if (dsize != 0)
+ *dst = '\0'; /* NUL-terminate dst */
+ while (*src++)
+ ;
+ }
+
+ return(src - osrc - 1); /* count does not include NUL */
+}
+
+size_t
+strlcat(char *dst, const char *src, size_t dsize)
+{
+ const char *odst = dst;
+ const char *osrc = src;
+ size_t n = dsize;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end. */
+ while (n-- != 0 && *dst != '\0')
+ dst++;
+ dlen = dst - odst;
+ n = dsize - dlen;
+
+ if (n-- == 0)
+ return(dlen + strlen(src));
+ while (*src != '\0') {
+ if (n != 0) {
+ *dst++ = *src;
+ n--;
+ }
+ src++;
+ }
+ *dst = '\0';
+
+ return(dlen + (src - osrc)); /* count does not include NUL */
+}
+
+void
+initbuf(struct buf *b)
+{
+ memset(b, 0, sizeof(*b));
+}
+
+void
+freebuf(struct buf *b)
+{
+ free(b->p);
+ b->p = NULL;
+}
+
+size_t
+fillbuf(struct buf *b, void *ptr, size_t n)
+{
+ if (!(b->p = realloc(b->p, b->n + n)))
+ err(1, "realloc");
+ memcpy(b->p + b->n, ptr, n);
+ b->n += n;
+ return b->n;
+}
+
+int
+unpack(uint8_t *s, size_t n, char *fmt, ...)
+{
+ va_list arg;
+ uint8_t *b, *e;
+
+ b = s;
+ e = b + n;
+ va_start(arg, fmt);
+ for (; *fmt; fmt++) {
+ switch (*fmt) {
+ case '_':
+ s++;
+ break;
+ case 'b':
+ if (s + 1 > e)
+ goto err;
+ *va_arg(arg, int *) = *s++;
+ break;
+ case 'w':
+ if (s + 2 > e)
+ goto err;
+ *va_arg(arg, int *) = s[0] << 8 | s[1];
+ s += 2;
+ break;
+ case 'l':
+ if (s + 4 > e)
+ goto err;
+ *va_arg(arg, int *) = s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
+ s += 4;
+ break;
+ case 'v':
+ if (s + 4 > e)
+ goto err;
+ *va_arg(arg, long long *) =
+ (long long)s[0] << 56 |
+ (long long)s[1] << 48 |
+ (long long)s[2] << 40 |
+ (long long)s[3] << 32 |
+ (long long)s[4] << 24 |
+ (long long)s[5] << 16 |
+ (long long)s[6] << 8 |
+ (long long)s[7];
+ s += 8;
+ break;
+ }
+ }
+ va_end(arg);
+ return s - b;
+err:
+ va_end(arg);
+ return -1;
+}
+
+int
+pack(uint8_t *s, size_t n, char *fmt, ...)
+{
+ va_list arg;
+ uint8_t *b, *e;
+ long long v;
+ int i;
+
+ b = s;
+ e = b + n;
+ va_start(arg, fmt);
+ for (; *fmt; fmt++) {
+ switch (*fmt) {
+ case '_':
+ i = 0;
+ if (0) {
+ case 'b':
+ i = va_arg(arg, int);
+ }
+ if (s + 1 > e)
+ goto err;
+ *s++ = i & 0xff;
+ break;
+ case 'w':
+ i = va_arg(arg, int);
+ if (s + 2 > e)
+ goto err;
+ *s++ = (i >> 8) & 0xff;
+ *s++ = i & 0xff;
+ break;
+ case 'l':
+ i = va_arg(arg, int);
+ if (s + 4 > e)
+ goto err;
+ *s++ = (i >> 24) & 0xff;
+ *s++ = (i >> 16) & 0xff;
+ *s++ = (i >> 8) & 0xff;
+ *s++ = i & 0xff;
+ break;
+ case 'v':
+ v = va_arg(arg, long long);
+ if (s + 8 > e)
+ goto err;
+ *s++ = (v >> 56) & 0xff;
+ *s++ = (v >> 48) & 0xff;
+ *s++ = (v >> 40) & 0xff;
+ *s++ = (v >> 32) & 0xff;
+ *s++ = (v >> 24) & 0xff;
+ *s++ = (v >> 16) & 0xff;
+ *s++ = (v >> 8) & 0xff;
+ *s++ = v & 0xff;
+ break;
+ case '*':
+ i = va_arg(arg, int);
+ if (s + i > e)
+ goto err;
+ memmove(s, va_arg(arg, void *), i);
+ s += i;
+ break;
+ }
+ }
+ va_end(arg);
+ return s - b;
+err:
+ va_end(arg);
+ return -1;
+}
+
+int
+dial(char *host, char *port)
+{
+ struct addrinfo hints, *res, *r;
+ int s;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ if (getaddrinfo(host, port, &hints, &res)) {
+ warnx("can't resolve %s", host);
+ return -1;
+ }
+ for (r = res; r; r = r->ai_next) {
+ s = socket(r->ai_family, r->ai_socktype, 0);
+ if (s < 0)
+ continue;
+ if (!connect(s, r->ai_addr, r->ai_addrlen))
+ break;
+ close(s);
+ }
+ freeaddrinfo(res);
+ if (!r) {
+ warnx("cannot connect to %s on port %s", host, port);
+ return -1;
+ }
+ return s;
+}
+
+void
+error(char *msg)
+{
+ fprintf(stderr, "%s\n", msg);
+ longjmp(savesp, 1);
+}
+
+char *
+parsestr(char *s, char *e, struct ben **b)
+{
+ char *p;
+ long long len = 0;
+
+ for (p = s; p != e && isdigit((int)*p); p++)
+ len = len * 10 + *p - '0';
+ if (p == e)
+ error("bad string");
+ if (*p++ != ':')
+ error("expected ':' in string");
+ if (p + len > e)
+ error("bad string");
+ p += len;
+ *b = emalloc(sizeof(**b));
+ (*b)->type = 's';
+ (*b)->s = &p[-len];
+ (*b)->start = s;
+ (*b)->end = p;
+ (*b)->len = len;
+ return p;
+}
+
+char *
+parseint(char *s, char *e, struct ben **b)
+{
+ char *p = s;
+ long long v;
+ int isneg = 0, iszero = 0;
+
+ if (*p != 'i')
+ error("expected integer");
+ if (++p == e)
+ error("bad integer");
+
+ if (*p == '-')
+ isneg = 1;
+ else if (*p == '0')
+ iszero = 1;
+ else
+ p--;
+
+ v = 0;
+ while (1) {
+ if (++p == e)
+ error("bad integer");
+ if (*p == 'e' || iszero) {
+ if (*p != 'e')
+ error("0 not followed by e");
+ break;
+ }
+ if (isneg && *p == '0')
+ error("i-0 is invalid");
+ if (!isdigit((int)*p))
+ error("unexpected char in integer");
+ v = v * 10 + *p - '0';
+ }
+ if (*p++ != 'e')
+ error("expected terminator");
+
+ *b = emalloc(sizeof(**b));
+ (*b)->type = 'i';
+ (*b)->start = s;
+ (*b)->end = p;
+ (*b)->i = isneg ? -v : v;
+ return p;
+}
+
+char *
+parsedl(char *s, char *e, struct ben **b)
+{
+ char *p = s;
+ struct ben head;
+ struct ben *bp = &head;
+ int type = *p;
+ long long len = 0;
+
+ memset(&head, 0, sizeof(head));
+ if (*p != 'd' && *p != 'l')
+ error("expected dictionary or list");
+ p++;
+ while (1) {
+ if (p == e)
+ error("bad dictionary or list");
+ if (*p == 'e')
+ break;
+ len++;
+ bp->next = emalloc(sizeof(*bp));
+ bp = bp->next;
+ bp->type = type;
+ bp->k = NULL;
+ if (type == 'd')
+ p = parsestr(p, e, &bp->k);
+ p = parse(p, e, &bp->v);
+ bp->next = NULL;
+ }
+ if (*p++ != 'e')
+ error("expected terminator");
+
+ *b = head.next;
+ if (!head.next) {
+ *b = malloc(sizeof(**b));
+ (*b)->type = type;
+ (*b)->k = NULL;
+ (*b)->v = NULL;
+ (*b)->next = NULL;
+ }
+ (*b)->len = len;
+ (*b)->start = s;
+ (*b)->end = p;
+ return p;
+}
+
+char *
+parse(char *s, char *e, struct ben **b)
+{
+ if (s == e)
+ return s;
+ switch (*s) {
+ case 'i':
+ return parseint(s, e, b);
+ break;
+ case 'l':
+ case 'd':
+ return parsedl(s, e, b);
+ break;
+ default:
+ if (!isdigit((int)*s))
+ error("unknown type");
+ return parsestr(s, e, b);
+ }
+}
+
+char *
+bdecode(char *s, char *e, struct ben **b)
+{
+ *b = NULL;
+ if (!setjmp(savesp))
+ return parse(s, e, b);
+ bfree(*b);
+ *b = NULL;
+ return NULL;
+}
+
+char *
+bstr2str(struct ben *b)
+{
+ char *s;
+
+ s = emalloc(b->len + 1);
+ memcpy(s, b->s, b->len);
+ s[b->len] = '\0';
+ return s;
+}
+
+int
+bstrcmp(struct ben *a, struct ben *b)
+{
+ long long i;
+
+ if (a->type != 's' || b->type != 's')
+ return 1;
+ if (a->len != b->len)
+ return 1;
+ for (i = 0; i < a->len; i++)
+ if (a->s[i] != b->s[i])
+ return 1;
+ return 0;
+}
+
+struct ben *
+dlook(struct ben *d, struct ben *k)
+{
+ struct ben *b;
+
+ if (d->type != 'd' || k->type != 's')
+ return NULL;
+ for (b = d; b; b = b->next)
+ if (!bstrcmp(b->k, k))
+ return b->v;
+ return NULL;
+}
+
+struct ben *
+dlookstr(struct ben *d, char *s)
+{
+ struct ben k;
+
+ k.type = 's';
+ k.s = s;
+ k.len = strlen(s);
+ return dlook(d, &k);
+}
+
+void
+bfree(struct ben *b)
+{
+ struct ben *bp, *btmp;
+
+ if (!b)
+ return;
+ switch (b->type) {
+ case 's':
+ free(b);
+ break;
+ case 'i':
+ free(b);
+ break;
+ case 'l':
+ case 'd':
+ bp = b;
+ while (bp) {
+ btmp = bp->next;
+ bfree(bp->k);
+ bfree(bp->v);
+ free(bp);
+ bp = btmp;
+ }
+ break;
+ }
+}
+
+void
+printstr(struct ben *b)
+{
+ int64_t i;
+
+ for (i = 0; i < b->len; i++)
+ putchar(b->s[i]);
+}
+
+void
+printindent(int indent)
+{
+ while (indent--)
+ putchar(' ');
+}
+
+void
+bprint(struct ben *b, int indent)
+{
+ struct ben *bl;
+ struct ben *bd;
+
+ if (!b)
+ return;
+ switch (b->type) {
+ case 's':
+ printindent(indent);
+ printstr(b);
+ putchar('\n');
+ break;
+ case 'i':
+ printindent(indent);
+ printf("%lld\n", (long long)b->i);
+ break;
+ case 'l':
+ printindent(indent);
+ printf("[\n");
+ for (bl = b; bl; bl = bl->next)
+ bprint(bl->v, indent + 1);
+ printindent(indent);
+ printf("]\n");
+ break;
+ case 'd':
+ printindent(indent);
+ printf("{\n");
+ for (bd = b; bd; bd = bd->next) {
+ if (bd->k) {
+ printindent(indent + 1);
+ printstr(bd->k);
+ printf(" :\n");
+ bprint(bd->v, indent + 2);
+ }
+ }
+ printindent(indent);
+ printf("}\n");
+ break;
+ }
+}
+
+void
+calcinfohash(struct torrent *t)
+{
+ sha1sum((uint8_t *)t->info->start,
+ t->info->end - t->info->start, t->infohash);
+}
+
+void
+dumptorrent(struct torrent *t)
+{
+ uint8_t md[20];
+ char hex[41];
+ long long i;
+ size_t n, m;
+
+ printf("announcers:\n");
+ for (n = 0; n < t->nannouncers; n++) {
+ printf("%zu: announcer\n", n);
+ for (m = 0; m < t->announcers[n].len; m++)
+ printf("\t%zu: %s\n", m,
+ t->announcers[n].urls[m]);
+ }
+
+ if (t->filename[0])
+ printf("directory: %s\n", t->filename);
+ if (t->nfiles)
+ printf("files (%zu):\n", t->nfiles);
+ for (n = 0; n < t->nfiles; n++) {
+ printf("\tpath: %s, length: %zu\n",
+ t->files[n].path, t->files[n].len);
+ }
+
+ bin2hex(t->infohash, hex, 20);
+ printf("infohash: %s\n", hex);
+ printf("piecelen: %lld\n", t->piecelen);
+ printf("npieces: %lld\n", t->npieces);
+
+ for (i = 0; i < t->npieces; i++) {
+ piecehash(t, i, md);
+ bin2hex(md, hex, 20);
+ printf("piece[%lld] hash: %s\n", i, hex);
+ }
+}
+
+struct torrent *
+loadtorrent(char *f)
+{
+ struct torrent *t;
+ struct ben *files, *file;
+ struct ben *length, *path, *tmp, *a;
+ size_t i, j, pathlen;
+ char *p;
+
+ t = ecalloc(1, sizeof(*t));
+ if (readfile(f, &t->buf, &t->buflen) < 0) {
+ warnx("failed to read %s", f);
+ goto fail;
+ }
+ if (!bdecode(t->buf, t->buf + t->buflen, &t->ben)) {
+ warnx("failed to decode %s", f);
+ goto fail;
+ }
+
+ t->info = dlookstr(t->ben, "info");
+ if (!t->info) {
+ warnx("no info dictionary in %s", f);
+ goto fail;
+ }
+
+ t->pieces = dlookstr(t->info, "pieces");
+ if (!t->pieces) {
+ warnx("no pieces field in %s", f);
+ goto fail;
+ }
+ if (t->pieces->len % 20) {
+ warnx("incorrect pieces length in %s", f);
+ goto fail;
+ }
+
+ if ((tmp = dlookstr(t->ben, "announce-list")) && tmp->len) {
+ if (tmp->type != 'l') {
+ warnx("announce-list must be of type list");
+ goto fail;
+ }
+ t->announcers = ecalloc(tmp->len, sizeof(struct announce));
+
+ for (i = 0; tmp; tmp = tmp->next, i++, t->nannouncers++) {
+ if (tmp->v->type != 'l') {
+ warnx("announce-list item must be of type list");
+ goto fail;
+ }
+ if (!tmp->v->len)
+ continue;
+ t->announcers[i].urls = ecalloc(tmp->v->len, sizeof(char *));
+ for (a = tmp->v, j = 0; a; a = a->next, j++, t->announcers[i].len++) {
+ if (a->v->type != 's') {
+ warnx("announce item must be of type string");
+ goto fail;
+ }
+ t->announcers[i].urls[j] = bstr2str(a->v);
+ }
+ }
+ } else if ((tmp = dlookstr(t->ben, "announce"))) {
+ t->announcers = ecalloc(tmp->len, sizeof(struct announce));
+ t->nannouncers = 1;
+ t->announcers[0].len = 1;
+ t->announcers[0].urls = ecalloc(1, sizeof(char *));
+ t->announcers[0].urls[0] = bstr2str(tmp);
+ } else {
+ warnx("no announce field in %s", f);
+ goto fail;
+ }
+
+ if (!dlookstr(t->info, "piece length")) {
+ warnx("no piece length field in %s", f);
+ goto fail;
+ }
+ t->piecelen = dlookstr(t->info, "piece length")->i;
+ if (t->piecelen <= 0) {
+ warnx("piecelen is <= 0 in %s", f);
+ goto fail;
+ }
+
+ if (!dlookstr(t->info, "name")) {
+ warnx("no filename field in %s", f);
+ goto fail;
+ }
+ t->filename = bstr2str(dlookstr(t->info, "name"));
+
+ /* multi-file mode or single file? */
+ if ((files = dlookstr(t->info, "files"))) {
+ if (files->type != 'l') {
+ warnx("files must be of type list");
+ goto fail;
+ }
+ t->files = ecalloc(files->len, sizeof(struct file));
+
+ for (file = files; file; file = file->next, t->nfiles++) {
+ if (!(length = dlookstr(file->v, "length"))) {
+ warnx("no length field in %s", f);
+ goto fail;
+ }
+ if (length->type != 'i') {
+ warnx("length must be of type integer");
+ goto fail;
+ }
+ if (length->i < 0) {
+ warnx("length is < 0 in %s", f);
+ goto fail;
+ }
+ t->totallen += length->i;
+ t->files[t->nfiles].len = length->i;
+
+ if (!(path = dlookstr(file->v, "path")))
+ break;
+ if (path->type != 'l')
+ warnx("path must be of type list");
+
+ /* check path list and count path buffer size */
+ for (tmp = path, pathlen = 0; tmp; tmp = tmp->next) {
+ if (tmp->v->type != 's') {
+ warnx("path item must be of type string");
+ goto fail;
+ }
+ pathlen += tmp->v->len + 1;
+ }
+ t->files[t->nfiles].path = ecalloc(1, pathlen);
+ for (tmp = path; tmp; tmp = tmp->next) {
+ p = bstr2str(tmp->v);
+ strlcat(t->files[t->nfiles].path, p, pathlen);
+ free(p);
+ if (tmp != path && tmp->next)
+ strlcat(t->files[t->nfiles].path, "/", pathlen);
+ }
+ }
+ } else {
+ if (!(length = dlookstr(t->info, "length"))) {
+ warnx("no length field in %s", f);
+ goto fail;
+ }
+ if (length->type != 'i') {
+ warnx("length must be of type integer");
+ goto fail;
+ }
+ if (length->i < 0) {
+ warnx("length is < 0 in %s", f);
+ goto fail;
+ }
+ t->nfiles = 1;
+ t->files = ecalloc(1, sizeof(struct file));
+ t->files[0].len = length->i;
+ t->files[0].path = t->filename;
+ t->filename = strdup("");
+ t->totallen = length->i;
+ }
+
+ t->npieces = (t->totallen + t->piecelen - 1) / t->piecelen;
+ t->piecebm = newbit(t->npieces);
+
+ calcinfohash(t);
+
+ return t;
+fail:
+ unloadtorrent(t);
+ return NULL;
+}
+
+void
+unloadtorrent(struct torrent *t)
+{
+ size_t i, j;
+
+ if (!t)
+ return;
+
+ i = t->nannouncers;
+ while (i--) {
+ j = t->announcers[i].len;
+ while (j--)
+ free(t->announcers[i].urls[j]);
+ free(t->announcers[i].urls);
+ }
+ free(t->announcers);
+
+ for (i = 0; i < t->nfiles; i++)
+ free(t->files[i].path);
+ free(t->files);
+
+ bfree(t->ben);
+ free(t->buf);
+ free(t->piecebm);
+ free(t);
+}
+
+int
+piecehash(struct torrent *t, long long n, uint8_t *md)
+{
+ if (n < 0 || n >= t->npieces)
+ return -1;
+ memcpy(md, t->pieces->start + n * 20, 20);
+ return 0;
+}
+
+char *
+peerid(void)
+{
+ static char id[] = "-TD4242-000000000000";
+ return id;
+}
+
+size_t
+writefn(void *ptr, size_t size, size_t nmemb, struct buf *b)
+{
+ return fillbuf(b, ptr, size * nmemb);
+}
+
+int
+trackerget(struct torrent *t, int event)
+{
+ CURL *curl;
+ CURLcode res;
+ struct buf reply;
+ char buf[8192], *infohash, *id, *ev;
+ int r;
+
+ switch (event) {
+ case EVSTARTED:
+ ev = "started";
+ break;
+ case EVCOMPLETED:
+ ev = "completed";
+ break;
+ case EVSTOPPED:
+ ev = "stopped";
+ break;
+ default:
+ return -1;
+ }
+
+ if (!(curl = curl_easy_init()))
+ return -1;
+
+ if (!(infohash = curl_easy_escape(curl, (char *)t->infohash, 20))) {
+ r = -1;
+ goto err0;
+ }
+
+ if (!(id = curl_easy_escape(curl, peerid(), 20))) {
+ r = -1;
+ goto err1;
+ }
+
+ r = snprintf(buf, sizeof(buf),
+ "%s?info_hash=%s&peer_id=%s&port=%d&uploaded=%zu&"
+ "downloaded=%zu&left=%zu&compact=1&event=%s",
+ t->announcers[0].urls[0], infohash, id, PORT,
+ t->uploaded, t->downloaded, t->totallen - t->downloaded,
+ ev);
+ if (r < 0 || (size_t)r >= sizeof(buf)) {
+ r = -1;
+ goto err2;
+ }
+
+ initbuf(&reply);
+ curl_easy_setopt(curl, CURLOPT_URL, buf);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefn);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &reply);
+ if ((res = curl_easy_perform(curl)) != CURLE_OK) {
+ r = - 1;
+ goto err3;
+ }
+
+ if (parsepeers(t, &reply) < 0) {
+ r = - 1;
+ goto err3;
+ }
+
+ r = 0;
+err3:
+ freebuf(&reply);
+err2:
+ curl_free(id);
+err1:
+ curl_free(infohash);
+err0:
+ curl_easy_cleanup(curl);
+ return r;
+}
+
+int
+parsepeers(struct torrent *t, struct buf *b)
+{
+ struct sockaddr_in sa;
+ struct ben *reply, *peers;
+ struct peer *peer;
+ char *p, *errstr;
+
+ if (!bdecode(b->p, b->p + b->n, &reply))
+ return -1;
+
+ if (dlookstr(reply, "failure reason")) {
+ errstr = bstr2str(dlookstr(reply, "failure reason"));
+ warnx("tracker failure: %s", errstr);
+ bfree(reply);
+ free(errstr);
+ return -1;
+ }
+
+ if (!(peers = dlookstr(reply, "peers"))) {
+ warnx("no peers field in tracker reply");
+ bfree(reply);
+ return -1;
+ }
+
+ if (peers->type != 's') {
+ warnx("expected compact reply");
+ bfree(reply);
+ return -1;
+ }
+
+ if (peers->len % 6) {
+ warnx("peers length needs to be a multiple of 6 bytes");
+ bfree(reply);
+ return -1;
+ }
+
+ for (p = peers->s; p < &peers->s[peers->len]; p += 6) {
+ t->peers = realloc(t->peers, (t->npeers + 1) * sizeof(*t->peers));
+ if (!t->peers)
+ err(1, "realloc");
+ peer = &t->peers[t->npeers];
+ memset(peer, 0, sizeof(*peer));
+ memcpy(&sa.sin_addr, p, 4);
+ inet_ntop(AF_INET, &sa.sin_addr, peer->hostname,
+ sizeof(peer->hostname));
+ snprintf(peer->port, sizeof(peer->port), "%d",
+ (int)ntohs(*(short *)&p[4]));
+ printf("%s:%s\n", peer->hostname, peer->port);
+ peer->amchoking = 1;
+ peer->peerchoking = 1;
+ t->npeers++;
+ }
+
+ free(reply);
+ return 0;
+}
+
+void
+usage(char *argv0)
+{
+ fprintf(stderr, "usage: %s torrent\n", argv0);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct torrent *t;
+
+ if (argc != 2)
+ usage(argv[0]);
+ if (!(t = loadtorrent(argv[1])))
+ exit(1);
+ dumptorrent(t);
+ trackerget(t, EVSTARTED);
+ unloadtorrent(t);
+ exit(0);
+}