#!/usr/bin/env python

import re
RSYNC_DOMAIN_REGEX = re.compile("rsync\.\S+\.gentoo\.org")
RSYNC_STATUS_PAGE="http://mirrorstats.gentoo.org/rsync/"
RSYNC_TCP_PORT=873
MIN_RSYNC_PROTO=29
RECV_BUFSIZE=4096
SOCKET_TIMEOUT=5

import sys, os

def fetch_rotation_servers():
	rotation_servers = set()
	rotation_servers.add("rsync.gentoo.org")
	from urllib2 import urlopen
	f = urlopen(RSYNC_STATUS_PAGE)
	try:
		for line in f:
			rotation_servers.update(re.findall(RSYNC_DOMAIN_REGEX, line))
	finally:
		f.close()
	rotation_servers = list(rotation_servers)
	rotation_servers.sort()
	return rotation_servers

def parse_protocol(mybytes):
	mylines = mybytes.split("\n", 1)
	if mylines:
		mysplit = mylines[0].split()
		if len(mysplit) == 2 and mysplit[0] == "@RSYNCD:":
			return mysplit[1]
	return ""

def scan_servers(rotation_servers, verbose, min_proto):
	import socket
	addr_families = []
	addr_families.append(socket.AF_INET)
	if socket.has_ipv6:
		addr_families.append(socket.AF_INET6)

	from socket import error, herror, gaierror, timeout
	socket_error = (error, herror, gaierror, timeout)

	for host_name in rotation_servers:
		addrinfo = []
		for family in addr_families:
			try:
				addrinfo.extend(socket.getaddrinfo(
					host_name, None, family, socket.SOCK_STREAM))
			except socket_error, e:
				sys.stderr.write(host_name + " " + str(e) + "\n")
				sys.stderr.flush()
				del e
				continue
		for family, socktype, proto, canonname, sockaddr in addrinfo:
			sockaddr = list(sockaddr)
			sockaddr[1] = RSYNC_TCP_PORT
			sockaddr = tuple(sockaddr)
			try:
				s = socket.socket(family)
			except socket_error, e:
				sys.stderr.write(host_name + " " + sockaddr[0] + " " + str(e) + "\n")
				sys.stderr.flush()
				del e
				continue
			s.settimeout(SOCKET_TIMEOUT)
			fqdn = ""
			rsync_proto = ""
			error = ""
			rsync_proto_good = False
			try:
				s.connect(sockaddr)
				try:
					s.shutdown(socket.SHUT_WR)
					mybytes = []
					while True:
						mybytes.append(s.recv(RECV_BUFSIZE))
						if not mybytes[-1]:
							break
					rsync_proto = parse_protocol("".join(mybytes))
					try:
						rsync_proto_good = int(rsync_proto) >= min_proto
					except ValueError:
						pass
				finally:
					s.shutdown(socket.SHUT_RD)
			except socket_error, e:
				if not rsync_proto:
					error = " ".join([str(x) for x in e.args])
				del e
			if rsync_proto_good and not verbose:
				continue
			try:
				fqdn = socket.getfqdn(socket.gethostbyaddr(sockaddr[0])[0])
			except socket_error:
				pass
			yield host_name, fqdn, sockaddr[0], rsync_proto, error

if __name__ == "__main__":
	from optparse import OptionParser
	parser = OptionParser()
	parser.add_option("-v", "--verbose", action="store_true",
		dest="verbose", default=False,
		help="include good servers in output (only bad by default)")
	parser.add_option("--min-proto", action="store",
		dest="min_proto", default=MIN_RSYNC_PROTO,
		help="minimum protocol for good servers (default %s)" % MIN_RSYNC_PROTO)
	options, args = parser.parse_args()
	for mydata in scan_servers(fetch_rotation_servers(),
		options.verbose, options.min_proto):
		print "\t".join(mydata)
		sys.stdout.flush()
