Exemple #1
0
// Parse a UDP byte buffer, return response from udpTracker
func parseUDP(buf []byte, addr *net.UDPAddr) ([]byte, error) {
	// Attempt to grab generic UDP connection fields
	packet := new(udp.Packet)
	err := packet.UnmarshalBinary(buf)
	if err != nil {
		// Because no transaction ID is present on failure, we must return nil
		return nil, errUDPHandshake
	}

	// Create a udpTracker to handle this client
	udpTracker := tracker.UDPTracker{TransID: packet.TransID}

	// Check for maintenance mode
	if common.Static.Maintenance {
		// Return tracker error with maintenance message
		return udpTracker.Error("Maintenance: " + common.Static.StatusMessage), nil
	}

	// Action switch
	// Action 0: Connect
	if packet.Action == 0 {
		// Validate UDP udpTracker handshake
		if packet.ConnID != udpInitID {
			return udpTracker.Error("Invalid UDP udpTracker handshake"), errUDPHandshake
		}

		// Generate a connection ID, which will be expected for this client next call
		expID := uint64(common.RandRange(1, 1000000000))

		// Store this client's address and ID in map
		udpAddrToID[addr.String()] = expID

		// Generate connect response
		connect := udp.ConnectResponse{
			Action:  0,
			TransID: packet.TransID,
			ConnID:  expID,
		}

		// Grab bytes from connect response
		connectBuf, err := connect.MarshalBinary()
		if err != nil {
			log.Println(err.Error())
			return udpTracker.Error("Could not generate UDP connect response"), errUDPWrite
		}

		return connectBuf, nil
	}

	// For all udpTracker actions other than connect, we must validate the connection ID for this
	// address, ensuring it matches the previously set value

	// Ensure connection ID map contains this IP address
	expID, ok := udpAddrToID[addr.String()]
	if !ok {
		return udpTracker.Error("Client must properly handshake before announce"), errUDPHandshake
	}

	// Validate expected connection ID using map
	if packet.ConnID != expID {
		return udpTracker.Error("Invalid UDP connection ID"), errUDPHandshake
	}

	// Clear this IP from the connection map after 2 minutes
	// note: this is done to conserve memory and prevent session fixation
	go func(addr *net.UDPAddr) {
		<-time.After(2 * time.Minute)
		delete(udpAddrToID, addr.String())
	}(addr)

	// Action 1: Announce
	if packet.Action == 1 {
		// Retrieve UDP announce request from byte buffer
		announce := new(udp.AnnounceRequest)
		err := announce.UnmarshalBinary(buf)
		log.Println(announce)
		if err != nil {
			return udpTracker.Error("Malformed UDP announce"), errUDPInteger
		}

		// Convert UDP announce to query map
		query := announce.ToValues()

		// Check if a proper IP was set, and if not, use the UDP connection address
		if query.Get("ip") == "0" {
			query.Set("ip", strings.Split(addr.String(), ":")[0])
		}

		// Trigger an anonymous announce
		return tracker.Announce(udpTracker, data.UserRecord{}, query), nil
	}

	// Action 2: Scrape
	if packet.Action == 2 {
		// Generate UDP scrape packet from byte buffer
		scrape := new(udp.ScrapeRequest)
		err := scrape.UnmarshalBinary(buf)
		if err != nil {
			return udpTracker.Error("Malformed UDP scrape"), errUDPHandshake
		}

		// Convert UDP scrape to query map
		query := scrape.ToValues()

		// Store IP in query map
		query.Set("ip", strings.Split(addr.String(), ":")[0])

		// Trigger a scrape
		return tracker.Scrape(udpTracker, query), nil
	}

	// No action matched
	return udpTracker.Error("Invalid action"), errUDPAction
}
Exemple #2
0
// Parse incoming HTTP connections before making tracker calls
func parseHTTP(w http.ResponseWriter, r *http.Request) {
	// Create a tracker to handle this client
	httpTracker := tracker.HTTPTracker{}

	// Count incoming connections
	atomic.AddInt64(&common.Static.HTTP.Current, 1)
	atomic.AddInt64(&common.Static.HTTP.Total, 1)

	// Add header to identify goat
	w.Header().Add("Server", fmt.Sprintf("%s/%s", App, Version))

	// Store current URL path
	url := r.URL.Path

	// Split URL into segments
	urlArr := strings.Split(url, "/")

	// If configured, Detect if client is making an API call
	url = urlArr[1]
	if url == "api" {
		// Output JSON
		w.Header().Add("Content-Type", "application/json")

		// API enabled
		if common.Static.Config.API {
			// API authentication
			auth := new(api.BasicAuthenticator).Auth(r)
			if !auth {
				http.Error(w, api.ErrorResponse("Authentication failed"), 401)
				return
			}

			// Handle API calls, output JSON
			api.Router(w, r)
			return
		}

		http.Error(w, api.ErrorResponse("API is currently disabled"), 503)
		return
	}

	// Check for maintenance mode
	if common.Static.Maintenance {
		// Return tracker error with maintenance message
		if _, err := w.Write(httpTracker.Error("Maintenance: " + common.Static.StatusMessage)); err != nil {
			log.Println(err.Error())
		}

		return
	}

	// Detect if passkey present in URL
	var passkey string
	if len(urlArr) == 3 {
		passkey = urlArr[1]
		url = urlArr[2]
	}

	// Make sure URL is valid torrent function
	if url != "announce" && url != "scrape" {
		if _, err := w.Write(httpTracker.Error("Malformed announce")); err != nil {
			log.Println(err.Error())
		}

		return
	}

	// Verify that torrent client is advertising its User-Agent, so we can use a whitelist
	if r.Header.Get("User-Agent") == "" {
		if _, err := w.Write(httpTracker.Error("Your client is not identifying itself")); err != nil {
			log.Println(err.Error())
		}

		return
	}

	client := r.Header.Get("User-Agent")

	// If configured, verify that torrent client is on whitelist
	if common.Static.Config.Whitelist {
		whitelist := new(data.WhitelistRecord).Load(client, "client")
		if whitelist == (data.WhitelistRecord{}) || !whitelist.Approved {
			if _, err := w.Write(httpTracker.Error("Your client is not whitelisted")); err != nil {
				log.Println(err.Error())
			}

			// Block things like browsers and web crawlers, because they will just clutter up the table
			if strings.Contains(client, "Mozilla") || strings.Contains(client, "Opera") {
				return
			}

			// Insert unknown clients into list for later approval
			if whitelist == (data.WhitelistRecord{}) {
				whitelist.Client = client
				whitelist.Approved = false

				log.Printf("whitelist: detected new client '%s', awaiting manual approval", client)

				go whitelist.Save()
			}

			return
		}
	}

	// Parse querystring into a Values map
	query := r.URL.Query()

	// Check if IP was previously set
	if query.Get("ip") == "" {
		// If no IP set, detect and store it in query map
		query.Set("ip", strings.Split(r.RemoteAddr, ":")[0])
	}

	// Put client in query map
	query.Set("client", client)

	// Check if server is configured for passkey announce
	if common.Static.Config.Passkey && passkey == "" {
		if _, err := w.Write(httpTracker.Error("No passkey found in announce URL")); err != nil {
			log.Println(err.Error())
		}

		return
	}

	// Validate passkey if needed
	user := new(data.UserRecord).Load(passkey, "passkey")
	if common.Static.Config.Passkey && user == (data.UserRecord{}) {
		if _, err := w.Write(httpTracker.Error("Invalid passkey")); err != nil {
			log.Println(err.Error())
		}

		return
	}

	// Put passkey in query map
	query.Set("passkey", user.Passkey)

	// Mark client as HTTP
	query.Set("udp", "0")

	// Get user's total number of active torrents
	seeding := user.Seeding()
	leeching := user.Leeching()
	if seeding == -1 || leeching == -1 {
		if _, err := w.Write(httpTracker.Error("Failed to calculate active torrents")); err != nil {
			log.Println(err.Error())
		}

		return
	}

	// Verify that client has not exceeded this user's torrent limit
	activeSum := seeding + leeching
	if user.TorrentLimit < activeSum {
		msg := fmt.Sprintf("Exceeded active torrent limit: %d > %d", activeSum, user.TorrentLimit)
		if _, err := w.Write(httpTracker.Error(msg)); err != nil {
			log.Println(err.Error())
		}

		return
	}

	// Tracker announce
	if url == "announce" {
		// Validate required parameter input
		required := []string{"info_hash", "ip", "port", "uploaded", "downloaded", "left"}
		// Validate required integer input
		reqInt := []string{"port", "uploaded", "downloaded", "left"}

		// Check for required parameters
		for _, r := range required {
			if query.Get(r) == "" {
				if _, err := w.Write(httpTracker.Error("Missing required parameter: " + r)); err != nil {
					log.Println(err.Error())
				}

				return
			}
		}

		// Check for all valid integers
		for _, r := range reqInt {
			if query.Get(r) != "" {
				_, err := strconv.Atoi(query.Get(r))
				if err != nil {
					if _, err := w.Write(httpTracker.Error("Invalid integer parameter: " + r)); err != nil {
						log.Println(err.Error())
					}

					return
				}
			}
		}

		// Only allow compact announce
		if query.Get("compact") == "" || query.Get("compact") != "1" {
			if _, err := w.Write(httpTracker.Error("Your client does not support compact announce")); err != nil {
				log.Println(err.Error())
			}

			return
		}

		// NOTE: currently, we do not bother using gzip to compress the tracker announce response
		// This is done for two reasons:
		// 1) Clients may or may not support gzip in the first place
		// 2) gzip may actually make announce response larger, as per testing in What.CD's ocelot

		// Perform tracker announce
		if _, err := w.Write(tracker.Announce(httpTracker, user, query)); err != nil {
			log.Println(err.Error())
		}

		return
	}

	// Tracker scrape
	if url == "scrape" {
		// Check for required parameter info_hash
		if query.Get("info_hash") == "" {
			if _, err := w.Write(httpTracker.Error("Missing required parameter: info_hash")); err != nil {
				log.Println(err.Error())
			}

			return
		}

		if _, err := w.Write(tracker.Scrape(httpTracker, query)); err != nil {
			log.Println(err.Error())
		}

		return
	}

	return
}