blob: 2f48a0877e4c9f2acae608214aef2b05dca3e01d [file] [log] [blame]
import base64
import urllib2
import os
import hashlib
import time
import socket
import httplib
import urllib
# TODO: Use util.command
from subprocess import check_call
from poster.encode import multipart_encode
from util.file import sha1sum, copyfile
import logging
log = logging.getLogger(__name__)
def getfile(baseurl, filehash, format_):
url = "%s/sign/%s/%s" % (baseurl, format_, filehash)
log.debug("%s: GET %s", filehash, url)
r = urllib2.Request(url)
return urllib2.urlopen(r)
def get_token(baseurl, username, password, slave_ip, duration):
auth = base64.encodestring('%s:%s' % (username, password)).rstrip('\n')
url = '%s/token' % baseurl
data = urllib.urlencode({
'slave_ip': slave_ip,
'duration': duration,
})
headers = {
'Authorization': 'Basic %s' % auth,
'Content-Length': str(len(data)),
}
r = urllib2.Request(url, data, headers)
return urllib2.urlopen(r).read()
def remote_signfile(options, urls, filename, fmt, token, dest=None):
filehash = sha1sum(filename)
if dest is None:
dest = filename
if fmt == 'gpg':
dest += '.asc'
parent_dir = os.path.dirname(os.path.abspath(dest))
if not os.path.exists(parent_dir):
os.makedirs(parent_dir)
# Check the cache
cached_fn = None
if options.cachedir:
log.debug("%s: checking cache", filehash)
cached_fn = os.path.join(options.cachedir, fmt, filehash)
if os.path.exists(cached_fn):
log.info("%s: exists in the cache; copying to %s", filehash, dest)
cached_fp = open(cached_fn, 'rb')
tmpfile = dest + '.tmp'
fp = open(tmpfile, 'wb')
hsh = hashlib.new('sha1')
while True:
data = cached_fp.read(1024 ** 2)
if not data:
break
hsh.update(data)
fp.write(data)
fp.close()
newhash = hsh.hexdigest()
if os.path.exists(dest):
os.unlink(dest)
os.rename(tmpfile, dest)
log.info("%s: OK", filehash)
# See if we should re-sign NSS
if options.nsscmd and filehash != newhash and os.path.exists(os.path.splitext(filename)[0] + ".chk"):
cmd = '%s "%s"' % (options.nsscmd, dest)
log.info("Regenerating .chk file")
log.debug("Running %s", cmd)
check_call(cmd, shell=True)
return True
errors = 0
pendings = 0
max_errors = 20
max_pending_tries = 300
while True:
if pendings >= max_pending_tries:
log.error("%s: giving up after %i tries", filehash, pendings)
return False
if errors >= max_errors:
log.error("%s: giving up after %i tries", filehash, errors)
return False
# Try to get a previously signed copy of this file
try:
url = urls[0]
log.info("%s: processing %s on %s", filehash, filename, url)
req = getfile(url, filehash, fmt)
headers = req.info()
responsehash = headers['X-SHA1-Digest']
tmpfile = dest + '.tmp'
fp = open(tmpfile, 'wb')
while True:
data = req.read(1024 ** 2)
if not data:
break
fp.write(data)
fp.close()
newhash = sha1sum(tmpfile)
if newhash != responsehash:
log.warn(
"%s: hash mismatch; trying to download again", filehash)
os.unlink(tmpfile)
errors += 1
continue
if os.path.exists(dest):
os.unlink(dest)
os.rename(tmpfile, dest)
log.info("%s: OK", filehash)
# See if we should re-sign NSS
if options.nsscmd and filehash != responsehash and os.path.exists(os.path.splitext(filename)[0] + ".chk"):
cmd = '%s "%s"' % (options.nsscmd, dest)
log.info("Regenerating .chk file")
log.debug("Running %s", cmd)
check_call(cmd, shell=True)
# Possibly write to our cache
if cached_fn:
cached_dir = os.path.dirname(cached_fn)
if not os.path.exists(cached_dir):
log.debug("Creating %s", cached_dir)
os.makedirs(cached_dir)
log.info("Copying %s to cache %s", dest, cached_fn)
copyfile(dest, cached_fn)
break
except urllib2.HTTPError, e:
try:
if 'X-Pending' in e.headers:
log.debug("%s: pending; try again in a bit", filehash)
time.sleep(1)
pendings += 1
continue
except:
raise
errors += 1
# That didn't work...so let's upload it
log.info("%s: uploading for signing", filehash)
req = None
try:
try:
nonce = open(options.noncefile, 'rb').read()
except IOError:
nonce = ""
req = uploadfile(url, filename, fmt, token, nonce=nonce)
nonce = req.info()['X-Nonce']
open(options.noncefile, 'wb').write(nonce)
except urllib2.HTTPError, e:
# python2.5 doesn't think 202 is ok...but really it is!
if 'X-Nonce' in e.headers:
log.debug("updating nonce")
nonce = e.headers['X-Nonce']
open(options.noncefile, 'wb').write(nonce)
if e.code != 202:
log.exception("%s: error uploading file for signing: %s %s",
filehash, e.code, e.msg)
urls.pop(0)
urls.append(url)
except (urllib2.URLError, socket.error, httplib.BadStatusLine):
# Try again in a little while
log.exception("%s: connection error; trying again soon", filehash)
# Move the current url to the back
urls.pop(0)
urls.append(url)
time.sleep(1)
continue
except (urllib2.URLError, socket.error):
# Try again in a little while
log.exception("%s: connection error; trying again soon", filehash)
# Move the current url to the back
urls.pop(0)
urls.append(url)
time.sleep(1)
errors += 1
continue
return True
def buildValidatingOpener(ca_certs):
"""Build and register an HTTPS connection handler that validates that we're
talking to a host matching ca_certs (a file containing a list of
certificates we accept.
Subsequent calls to HTTPS urls will validate that we're talking to an acceptable server.
"""
try:
from poster.streaminghttp import StreamingHTTPSHandler as HTTPSHandler, \
StreamingHTTPSConnection as HTTPSConnection
assert HTTPSHandler # pyflakes
assert HTTPSConnection # pyflakes
except ImportError:
from httplib import HTTPSConnection
from urllib2 import HTTPSHandler
import ssl
class VerifiedHTTPSConnection(HTTPSConnection):
def connect(self):
# overrides the version in httplib so that we do
# certificate verification
# sock = socket.create_connection((self.host, self.port),
# self.timeout)
# if self._tunnel_host:
# self.sock = sock
# self._tunnel()
sock = socket.socket()
sock.connect((self.host, self.port))
# wrap the socket using verification with the root
# certs in trusted_root_certs
self.sock = ssl.wrap_socket(sock,
self.key_file,
self.cert_file,
cert_reqs=ssl.CERT_REQUIRED,
ca_certs=ca_certs,
ssl_version=ssl.PROTOCOL_TLSv1,
)
# wraps https connections with ssl certificate verification
class VerifiedHTTPSHandler(HTTPSHandler):
def __init__(self, connection_class=VerifiedHTTPSConnection):
self.specialized_conn_class = connection_class
HTTPSHandler.__init__(self)
def https_open(self, req):
return self.do_open(self.specialized_conn_class, req)
https_handler = VerifiedHTTPSHandler()
opener = urllib2.build_opener(https_handler)
urllib2.install_opener(opener)
def uploadfile(baseurl, filename, format_, token, nonce):
"""Uploads file (given by `filename`) to server at `baseurl`.
`sesson_key` and `nonce` are string values that get passed as POST
parameters.
"""
from poster.encode import multipart_encode
filehash = sha1sum(filename)
try:
fp = open(filename, 'rb')
params = {
'filedata': fp,
'sha1': filehash,
'filename': os.path.basename(filename),
'token': token,
'nonce': nonce,
}
datagen, headers = multipart_encode(params)
r = urllib2.Request(
"%s/sign/%s" % (baseurl, format_), datagen, headers)
return urllib2.urlopen(r)
finally:
fp.close()