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 url = sys.argv # 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()