torrentd

simple torrent daemon
git clone git@git.2f30.org/torrentd.git
Log | Files | Refs | LICENSE

commit 8c36138e5b23b9b8bf9bbdb37afdddb64cdd0f0e
parent e8afeb7ad7cb399b2bf71806c009d5e0733a1137
Author: sin <sin@2f30.org>
Date:   Sun, 27 Dec 2015 17:56:14 +0000

rename to torrentd

Diffstat:
MMakefile | 4++--
Dstorrentd.c | 1221------------------------------------------------------------------------------
Atorrentd.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); +}