I wanted to download data from a webserver that is part of an Active Directory and offers negotiate authentication via Kerberos only, i.e. the webserver returns the HTTP error code 401 and a HTTP header WWW-Authenticate: Negotiate if your client does not provide any authorization. The client is not part of that Active Directory and should not use a system wide keytab nor a delegation user. As a base installation of FreeBSD comes with the Heimdal Kerberos tools, we can use kinit to create a temporary keytab and impersonate the specific Active Directory user who is allowed to download from that webserver.
# make -C /usr/ports/ftp/curl configIf GSSAPI_NONE is selected, choose GSSAPI_BASE, exit the dialog, save the options, and recompile curl:
# make -C /usr/ports/ftp/curl all deinstall reinstall clean
# make -C /usr/ports/ftp/py-pycurl install cleanIf curl is not installed on your system, make sure to select GSSAPI_BASE during its options dialog as described above
[libdefaults] dns_lookup_kdc = yes dns_lookup_realm = yes
#!/usr/local/bin/python2.7
import pycurl, StringIO, getpass, sys, subprocess, os, tempfile
# username and url expected as command line arguments
if len(sys.argv) != 2:
sys.exit("usage: %s <username> <url>")
username = sys.argv[1]
url = sys.argv[2]
# don't echo password on the console
password = getpass.getpass(username + "'s password: ")
# temporary file to save the user's keytab to, which gets deleted
# when the file is closed
# both kinit and pycurl honor this environment variable
os.environ["KRB5CCNAME"] = tempfile.NamedTemporaryFile().name
# create temporary keytab, read password from stdin
# had no success with lifetime or renew time of 120 seconds or less
kinit = subprocess.Popen(("kinit", "--password-file=STDIN", "-l", "300",
"-r", "300", "--no-forwardable", username),
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, close_fds=True)
(stdoutdata, stderrdata) = kinit.communicate(password)
# user pycurl to read url into this buffer
buffer = StringIO.StringIO()
curl = pycurl.Curl()
curl.setopt(pycurl.URL, url)
curl.setopt(pycurl.WRITEDATA, buffer)
curl.setopt(pycurl.USERNAME, username)
curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_NEGOTIATE)
# curl.setopt(pycurl.VERBOSE, True)
curl.perform()
print "HTTP code: %i" % (curl.getinfo(pycurl.RESPONSE_CODE),)
print buffer.getvalue()
# after a pycurl object is closed, we can no longer call getinfo() on it
curl.close()