/* $Id$ */ /* * Copyright (c) 2005 Dimitri Sokolyuk * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include /* htons() */ #include #include /* AF_INET */ #include #include #include /* inet_ntoa() */ #include #include #include #include #include #include /* getpagesize() */ #include "bencode.h" #include "meta.h" #include "peer.h" #include "tracker.h" #include "tools.h" #include "version.h" #ifndef lint static const char rcsid[] = "@(#)$Id$"; #endif /* not lint */ #if FAILURE /* ref: http://wiki.theory.org/BitTorrentTrackerProtocol */ static const struct { int code; char *msg; } failure[] = { { 100, "Invalid Request Type" }, { 101, "Missing `info_hash'" }, { 102, "Missing `peer_id'" }, { 103, "Missing `port'" }, { 150, "Invalid `info_hash'" }, { 151, "Invalid `peer_id'" }, { 152, "Invalid `numwait'" }, { 200, "`info_hash' not found in database" }, { 500, "Client sent an event request before the specified time" }, { 900, "Generic Error" }, }; #endif static int parseanswer(struct bttracker *, char *); static int parseplist(struct bttracker *, struct btnode *); static char *buildrequest(struct bttracker *); static int fillsin(char *, int, struct sockaddr_in *); static int startlistener(int *); static int parseanswer(struct bttracker *tp, char *src) { struct btnode *nhead, *np; src = strstr(src, "\r\n\r\n") + 4; if ((nhead = btbencode(src)) == NULL) errx(1, "fatal error: %s\n", src); if ((np = btfinddict(nhead, "failure reason")) != NULL) errx(1, "failure reason: %s\n", np->str); #if FAILURE if ((np = btfinddict(nhead, "failure code")) != NULL) { int i; for (i = 0; failure[i].code != 900; i++) if (np->num == failure[i].code) break; errx(1, failure[i].msg); } #endif if ((np = btfinddict(nhead, "warning message")) != NULL) warnx("warning message: %s\n", np->str); if ((np = btfinddict(nhead, "interval")) != NULL) tp->interval = np->num; if ((np = btfinddict(nhead, "min interval")) != NULL) tp->minterval = np->num; if ((np = btfinddict(nhead, "tracker id")) != NULL) memcpy(tp->trackerid, np->str, SHA1LEN); if ((np = btfinddict(nhead, "complete")) != NULL) tp->complete = np->num; if ((np = btfinddict(nhead, "incomplete")) != NULL) tp->incomplete = np->num; if ((np = btfinddict(nhead, "peers")) != NULL) parseplist(tp, np); #if 0 /* removing dead peers is senseless; collect all peers until we finish downloading */ tp->npeers = btclearoldpeers(&tp->plist); #endif btfreenode(nhead); return 0; } static int parseplist(struct bttracker *tp, struct btnode *node) { struct btnode *np; struct btpeer *p; char *host; int port, n; switch (node->type) { case NODE_LIST: /* if the tracker answers with the peer list * remove the compact flag for further requests */ tp->compact = COMP_NO; for (; node != NULL; node = node->next) { if ((p = calloc(1, sizeof(struct btpeer))) == NULL) return -1; host = NULL; port = 0; if ((np = btfinddict(node->list, "peer id")) != NULL) memcpy(&p->peerid, np->str, SHA1LEN); if ((np = btfinddict(node->list, "ip")) != NULL) host = np->str; if ((np = btfinddict(node->list, "port")) != NULL) port = np->num; if (host != NULL) { fillsin(host, port, &p->sin); if (btaddpeer(&tp->plist, p)) free(p); } } break; case NODE_STR: /* compact mode */ tp->compact = COMP_YES; /* just to be sure */ n = node->len / 6; while (n-- != 0) { if ((p = calloc(1, sizeof(struct btpeer))) == NULL) return -1; p->sin.sin_family = AF_INET; memcpy(&p->sin.sin_addr, node->str + 6 * n, 4); memcpy(&p->sin.sin_port, node->str + 6 * n + 4, 2); if (btaddpeer(&tp->plist, p) == 1) free(p); } break; default: return -1; } return 0; } static const char *event[] = { NULL, "started", "completed", "stopped" }; static char * buildrequest(struct bttracker *tp) { char hash[HASHSZL]; char *buf; int len; size_t buflen = 1024; if ((buf = calloc(buflen, sizeof(char))) == NULL) return NULL; bthexdump(hash, tp->infohash, sizeof(hash)); len = snprintf(buf, buflen, "GET /%s?info_hash=%s", tp->announce->path, hash); bthexdump(hash, tp->localid, sizeof(hash)); len += snprintf(buf + len, buflen - len, "&peer_id=%s", hash); len += snprintf(buf + len, buflen - len, "&port=%d", tp->localport); if (tp->key != NULL) len += snprintf(buf + len, buflen - len, "key=%s", tp->key); if (*tp->trackerid != '\0') { bthexdump(hash, tp->trackerid, sizeof(hash)); len += snprintf(buf + len, buflen - len, "trackerid=%s", hash); } len += snprintf(buf + len, buflen - len, "&uploaded=%lld&downloaded=%lld&left=%lld", tp->uploaded, tp->downloaded, tp->left); if (tp->compact != COMP_NO) len += snprintf(buf + len, buflen - len, "&compact=%d", tp->compact); if (tp->numwait != 0) len += snprintf(buf + len, buflen - len, "&numwait=%d", tp->numwait); if (tp->event != EV_NONE) len += snprintf(buf + len, buflen - len, "&event=%s", event[tp->event]); len += snprintf(buf + len, buflen - len, " HTTP/1.0\nHost: %s\nUser-Agent: %s/%d.%d\n\n", tp->announce->host, USERAGENT, VERSION_MAJOR, VERSION_MINOR); return buf; } /* TODO: review and add proper error handling * as well as move it all into main select loop */ int btcalltracker(struct bttracker *tp) { char *buf; size_t buflen, offset; int sockd, ret; struct timeval tv; if (gettimeofday(&tv, NULL) == -1) err(1, "gettimeofday"); tp->lastrqst = tv.tv_sec; /* set time mark */ if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) == -1) err(1, "socket"); if (connect(sockd, (struct sockaddr *)&tp->sin, sizeof(struct sockaddr)) == -1) err(1, "connect"); if ((buf = buildrequest(tp)) == NULL) return -1; #if DEBUGTRACKER printf("BUF (%d): %s\n", strlen(buf), buf); #endif if (send(sockd, buf, strlen(buf), 0) == -1) err(1, "send"); free(buf); buflen = sysconf(_SC_PAGESIZE); /* XXX */ if ((buf = calloc(buflen, sizeof(char))) == NULL) err(1, "alloc"); offset = 0; for(;;) { if (offset >= buflen) { char *nbuf; buflen *= 2; if ((nbuf = realloc(buf, buflen)) == NULL) err(1, "realloc"); buf = nbuf; } if ((ret = recv(sockd, buf + offset, buflen - offset, 0)) == -1) err(1, "recv"); if (ret == 0) break; offset += ret; } close(sockd); #if DEBUGTRACKER /* remove all '\0' chars */ if (strlen(buf) != 0) { while ((strlen(buf)) < buflen) buf[strlen(buf)] = ' '; buf[buflen] = '\0'; } printf("BUF (%d): %s\n", buflen, buf); #endif ret = parseanswer(tp, buf); free(buf); return ret; } struct bttracker * btinittracker(struct btmeta *mp, int port) { struct bttracker *tp; if ((tp = calloc(1, sizeof(struct bttracker))) == NULL) return NULL; if (fillsin(mp->announce->host, mp->announce->port, &tp->sin) == -1) return NULL; tp->announce = mp->announce; tp->infohash = mp->infohash; btpeerid(tp->localid); if ((tp->listener = startlistener(&port)) == -1) err(1, "listen"); tp->localport = port; tp->compact = COMP_YES; /* we start always in compact mode */ *tp->trackerid = '\0'; /* isn't known at begin */ tp->downloaded = 0; tp->uploaded = 0; tp->left = mp->left; tp->event = EV_STARTED; /* init value */ LIST_INIT(&tp->plist); return tp; } /* TODO: if at all it should be called from `statistics calulator' */ void btupdatetracker(struct bttracker *tp, int dl, int ul, enum event e) { tp->downloaded += dl; tp->uploaded += ul; tp->event = e; /* XXX tp->left = mp->left; */ } void btfreetracker(struct bttracker *tp) { btdelplist(&tp->plist); free(tp); } static int fillsin(char *host, int port, struct sockaddr_in *sin) { struct hostent *h; memset(sin, 0, sizeof(struct sockaddr_in)); sin->sin_family = AF_INET; if (inet_aton(host, &sin->sin_addr) == 0) { h = gethostbyname(host); if (h == NULL) { err(1, "gethostbyname"); return -1; } memcpy(&sin->sin_addr, h->h_addr, h->h_length); } sin->sin_port = htons(port); return 0; } int startlistener(int *port) { struct sockaddr_in sin; int maxport, yes = 1; int sockd; maxport = *port + 8; if (maxport > 65535) maxport = 65535; if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; if (setsockopt(sockd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) return -1; sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); memset(&sin.sin_zero, '\0', 8); for (; *port <= maxport; *port++) { sin.sin_port = htons(*port); if (bind(sockd, (struct sockaddr *)&sin, sizeof(sin)) == 0) break; } if (*port > maxport || listen(sockd, SOMAXCONN) == -1) { close(sockd); return -1; } return sockd; }