diff -ruBEN polygraph-4.3.1/configure.in polygraph-4.3.1-mm/configure.in
--- polygraph-4.3.1/configure.in	2011-03-03 22:02:32.000000000 +0000
+++ polygraph-4.3.1-mm/configure.in	2011-03-04 23:15:41.000000000 +0000
@@ -386,6 +386,149 @@
 	sleep 1;
 fi
 
+AC_ARG_WITH(krb5-config,
+  AC_HELP_STRING([--with-krb5-config=PATH],
+                 [specify path to krb5-config (default=detect)]), [
+	case "$withval" in
+	yes) unset krb5confpath ;;
+	no)  krb5confpath=no ;;
+	*)   krb5confpath=$withval ;;
+	esac
+])
+if test x"$krb5confpath" != "xno"; then
+    if test "x$krb5confpath" != "x"; then
+        if ! test -x "$krb5confpath"; then
+            AC_MSG_WARN([krb5-config '$krb5confpath' not executable, ignoring])
+            AC_CHECK_PROG(ac_krb5_config, krb5-config, yes, no)
+            krb5confpath=krb5-config
+        fi
+        krb5_config_path=`dirname $krb5confpath`
+        AC_CHECK_PROG(ac_krb5_config, krb5-config, yes, no, $krb5_config_path)
+    else
+    AC_CHECK_PROG(ac_krb5_config,krb5-config,yes,no)
+        krb5confpath=krb5-config
+    fi
+fi
+if test "x$ac_krb5_config" = "xyes" ; then
+  ac_heimdal="`$krb5confpath --version 2>/dev/null | grep -i heimdal`"
+  if test "x$ac_heimdal" != "x" ; then
+    AC_DEFINE(HAVE_HEIMDAL_KERBEROS,1,[Define to 1 if you have Heimdal Kerberos])
+  else
+    AC_DEFINE(HAVE_MIT_KERBEROS,1,[Define to 1 if you have MIT Kerberos])
+  fi
+  KRB5INCS="`$krb5confpath --cflags krb5 2>/dev/null`"
+  KRB5LIBS="`$krb5confpath --libs krb5 2>/dev/null`"
+  KRB5INCS="`$krb5confpath --cflags gssapi 2>/dev/null` $KRB5INCS"
+  KRB5LIBS="`$krb5confpath --libs gssapi 2>/dev/null` $KRB5LIBS"
+  CPPFLAGS="$CPPFLAGS $KRB5INCS"
+  LIBS="$LIBS $KRB5LIBS"
+  AC_CHECK_HEADERS(gssapi.h gssapi/gssapi.h gssapi/gssapi_krb5.h)
+  if test "x$ac_heimdal" = "x" ; then
+    AC_CHECK_HEADERS(gssapi/gssapi_generic.h)
+  fi
+
+  AC_CACHE_CHECK([for broken Heimdal krb5.h],squid_cv_broken_heimdal_krb5_h, [
+    AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#include <krb5.h>
+int
+main(void)
+{
+        krb5_context context;
+
+        krb5_init_context(&context);
+
+        return 0;
+}
+]])], [ squid_cv_broken_heimdal_krb5_h=no ], [
+    AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#if defined(__cplusplus)
+extern "C" {
+#endif
+#include <krb5.h>
+#if defined(__cplusplus)
+}
+#endif
+int
+main(void)
+{
+        krb5_context context;
+
+        krb5_init_context(&context);
+
+        return 0;
+}
+]])], [ squid_cv_broken_heimdal_krb5_h=yes ], [ squid_cv_broken_heimdal_krb5_h=no ])
+    ])
+  ])
+
+  if test "x$squid_cv_broken_heimdal_krb5_h" = "xyes"; then
+    AC_DEFINE(HAVE_BROKEN_HEIMDAL_KRB5_H, 1, [Define to 1 if Heimdal krb5.h is broken for C++])
+  fi
+  AC_CHECK_HEADERS(krb5.h com_err.h et/com_err.h)
+
+  dnl do compile check
+  AC_MSG_CHECKING([for working krb5])
+  AC_TRY_RUN([
+#if HAVE_KRB5_H
+#if HAVE_BROKEN_HEIMDAL_KRB5_H
+extern "C" {
+#include <krb5.h>
+}
+#else
+#include <krb5.h>
+#endif
+#endif
+
+int
+main(void)
+{
+        krb5_context context;
+
+        krb5_init_context(&context);
+
+       return 0;
+}
+], [unset no_krb5
+   AC_DEFINE(HAVE_KRB5, 1, [KRB5 support])
+   AC_MSG_RESULT(yes)],
+  AC_MSG_RESULT(no))
+
+   dnl do compile check
+  AC_MSG_CHECKING([for working gssapi])
+  AC_TRY_RUN([
+#if HAVE_GSSAPI_H
+#include <gssapi.h>
+#elif HAVE_GSSAPI_GSSAPI_H
+#include <gssapi/gssapi.h>
+#endif /* HAVE_GSSAPI_H */
+
+#if !HAVE_HEIMDAL_KERBEROS
+#if HAVE_GSSAPI_GSSAPI_EXT_H
+#include <gssapi/gssapi_ext.h>
+#endif /* HAVE_GSSAPI_GSSAPI_EXT_H */
+#if HAVE_GSSAPI_GSSAPI_KRB5_H
+#include <gssapi/gssapi_krb5.h>
+#endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */
+#if HAVE_GSSAPI_GSSAPI_GENERIC_H
+#include <gssapi/gssapi_generic.h>
+#endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */
+#endif /* HAVE_HEIMDAL_KERBEROS */
+
+int
+main(void)
+{
+    OM_uint32 val;
+    gss_OID_set set;
+
+    gss_create_empty_oid_set(&val, &set);
+
+    return 0;
+}
+], [AC_MSG_RESULT(yes)
+   AC_DEFINE(HAVE_GSSAPI, 1, [GSSAPI support])],
+  [AC_MSG_RESULT(no)])
+fi
+
 dnl check whether loganalizers/comparator should be built
 AC_MSG_CHECKING(whether to build comparator)
 AC_ARG_ENABLE(comparator,
diff -ruBEN polygraph-4.3.1/src/client/CltCfg.cc polygraph-4.3.1-mm/src/client/CltCfg.cc
--- polygraph-4.3.1/src/client/CltCfg.cc	2011-02-09 02:44:45.000000000 +0000
+++ polygraph-4.3.1-mm/src/client/CltCfg.cc	2011-03-04 23:15:41.000000000 +0000
@@ -51,7 +51,10 @@
 	theBusyPeriod(0), theIdlePeriodDur(0),
 	theFtpProxyCycleCnt(0), theHttpProxyCycleCnt(0), theSocksProxyCycleCnt(0),
 	thePipelineDepth(0),
-	theRecurRatio(-1), theSpnegoAuthRatio(-1), theEmbedRecurRatio(-1),
+	theRecurRatio(-1), theSpnegoAuthRatio(-1), preferKerberosAuth(false), 
+	clearKerberosCache(false), theKerberosConfigPath(0), 
+	theKerberosProxySPN(0), theKerberosServerSPN(0),
+	theEmbedRecurRatio(-1),
 	theAbortProb(0), theAuthError(0),
 	theWaitXactLmt(-1), theIcpPort(-1),
 	theCredentialCycleCnt(0), theForeignWorld(0),
@@ -84,7 +87,6 @@
 	delete thePopModel;
 	delete theBusyPeriod;
 	delete theContainerTags;
-	delete theAcceptedContentCodings;
 	delete theForeignWorld;
 	delete theWarmupPlan;
 	delete thePostContentSel;
@@ -105,6 +107,11 @@
 
 	theRobot->recurRatio(theRecurRatio);
 	theRobot->spnegoRatio(theSpnegoAuthRatio);
+	theRobot->kerberos(preferKerberosAuth);
+	theRobot->kerberosClearCache(clearKerberosCache);
+	theKerberosConfigPath = theRobot->kerberosConfigPath();
+	theKerberosProxySPN = theRobot->kerberosProxySPN();
+	theKerberosServerSPN = theRobot->kerberosServerSPN();
 	theRobot->embedRecurRatio(theEmbedRecurRatio);
 	theRobot->abortProb(theAbortProb);
 	theRobot->authError(theAuthError);
diff -ruBEN polygraph-4.3.1/src/client/CltCfg.cc.orig polygraph-4.3.1-mm/src/client/CltCfg.cc.orig
--- polygraph-4.3.1/src/client/CltCfg.cc.orig	1970-01-01 01:00:00.000000000 +0100
+++ polygraph-4.3.1-mm/src/client/CltCfg.cc.orig	2011-02-09 02:44:45.000000000 +0000
@@ -0,0 +1,869 @@
+
+/* Web Polygraph       http://www.web-polygraph.org/
+ * (C) 2003-2006 The Measurement Factory
+ * Licensed under the Apache License, Version 2.0 */
+
+#include "base/polygraph.h"
+
+#include <fstream>
+#include "xstd/h/iomanip.h"
+
+#include "xstd/rndDistrs.h"
+#include "xstd/StringIdentifier.h"
+#include "xstd/Ssl.h"
+#include "base/RndPermut.h"
+#include "base/OidGenStat.h"
+#include "pgl/RobotSym.h"
+#include "pgl/SessionSym.h"
+#include "pgl/AclSym.h"
+#include "pgl/GoalSym.h"
+#include "pgl/PglStringSym.h"
+#include "pgl/PglStaticSemx.h"
+#include "pgl/SingleRangeSym.h"
+#include "pgl/MultiRangeSym.h"
+#include "runtime/AddrMap.h"
+#include "runtime/HostMap.h"
+#include "runtime/PubWorld.h"
+#include "runtime/PopModel.h"
+#include "runtime/Goal.h"
+#include "runtime/XactAbortCoord.h"
+#include "runtime/LogComment.h"
+#include "runtime/httpHdrs.h"
+#include "csm/XmlTagIdentifier.h"
+#include "csm/ContentMgr.h"
+#include "csm/ContentSel.h"
+#include "client/WarmupPlan.h"
+#include "client/ServerRep.h"
+#include "client/RegExGroup.h"
+#include "client/ForeignWorld.h"
+#include "client/Client.h"
+#include "client/CltCfg.h"
+#include "client/CltOpts.h"
+#include "client/SingleRangeCfg.h"
+#include "client/MultiRangeCfg.h"
+
+Memberships CltCfg::TheGlbMemberships;
+
+
+CltCfg::CltCfg(): theRobot(0), 
+	theOriginSel(0), theReqTypeSel(0), theReqMethodSel(0),
+	thePopModel(0),
+	theBusyPeriod(0), theIdlePeriodDur(0),
+	theFtpProxyCycleCnt(0), theHttpProxyCycleCnt(0), theSocksProxyCycleCnt(0),
+	thePipelineDepth(0),
+	theRecurRatio(-1), theSpnegoAuthRatio(-1), theEmbedRecurRatio(-1),
+	theAbortProb(0), theAuthError(0),
+	theWaitXactLmt(-1), theIcpPort(-1),
+	theCredentialCycleCnt(0), theForeignWorld(0),
+	theContainerTags(0),
+	theAcceptedContentCodings(0), acceptingGzipContent(false),
+	theCookiesKeepLimitSel(0),
+	theWarmupPlan(0),
+	theReqBodyPauseProb(-1),
+	theReqBodyRecurrence(-1),
+	thePostContentSel(0), theUploadContentSel(0),
+	theForeignInterestProb(-1),
+	thePublicInterestProb(-1),
+	thePassiveFtp(-1), theSocksProb(-1), theSocksChainingProb(-1),
+	genUniqUrls(false), didWarmup(false), theRangeSel(0),
+	ftpProxiesSet(false) {
+}
+
+CltCfg::~CltCfg() {
+	while (theCredentials.count())
+		delete theCredentials.dequeue();
+	while (theFtpProxies.count())
+		delete theFtpProxies.dequeue();
+	while (theHttpProxies.count())
+		delete theHttpProxies.dequeue();
+	while (theSocksProxies.count())
+		delete theSocksProxies.dequeue();
+	delete theOriginSel;
+	delete theReqTypeSel;
+	delete theReqMethodSel;
+	delete thePopModel;
+	delete theBusyPeriod;
+	delete theContainerTags;
+	delete theAcceptedContentCodings;
+	delete theForeignWorld;
+	delete theWarmupPlan;
+	delete thePostContentSel;
+	delete theUploadContentSel;
+	delete theRangeSel;
+}
+
+void CltCfg::configure(const RobotSym *aRobot) {
+	AgentCfg::configure(aRobot);
+
+	Assert(!theRobot && aRobot);
+	theRobot = aRobot;
+
+	theRobot->waitXactLimit(theWaitXactLmt);
+	thePeerHttp = theRobot->peerHttp();
+	thePeerIcp = theRobot->peerIcp();
+	theRobot->icpPort(theIcpPort);
+
+	theRobot->recurRatio(theRecurRatio);
+	theRobot->spnegoRatio(theSpnegoAuthRatio);
+	theRobot->embedRecurRatio(theEmbedRecurRatio);
+	theRobot->abortProb(theAbortProb);
+	theRobot->authError(theAuthError);
+	theRobot->uniqueUrls(genUniqUrls);
+	theUriThrower = theRobot->rawUriThrower();
+	theRobot->reqBodyPauseProb(theReqBodyPauseProb);
+	theRobot->reqBodyPauseStart(theReqBodyPauseStart);
+	theRobot->reqBodyRecurrence(theReqBodyRecurrence);
+
+	if (PopModelSym *pms = theRobot->popModel()) {
+		thePopModel = new PopModel;
+		thePopModel->configure(pms);
+	}
+
+	configureInterests();
+	configureReqTypes();
+	configureReqMethods();
+	configureOrigins();
+	configureProxies();
+	configureCredentials();
+	configureMemberships();
+	configureContainerTags();
+	configureAcceptedContentCodings();
+	configurePipeDepth();
+	configureRanges();
+	configurePostContents();
+	configureUploadContents();
+	configureTrace();
+
+	if (AclSym *acl = theRobot->acl())
+		theAcl.configure(*acl);
+
+	if (SessionSym *ss = theRobot->session()) {
+		if (GoalSym *bps = ss->busyPeriod()) {
+			theBusyPeriod = new Goal;
+			theBusyPeriod->configure(*bps);
+		}
+		theIdlePeriodDur = ss->idlePeriodDuration();
+		ss->heartbeatGap(theSessionHeartbitGap);
+
+		if (!theBusyPeriod != !theIdlePeriodDur) {
+			cerr << theRobot->loc() << "both busy and idle periods "
+				<< "should be specified (or not specified) for a robot session"
+				<< endl << xexit;
+		}
+	}
+
+	if (!thePopModel && theRecurRatio > 0)
+		cerr << theRobot->loc() << "popularity model must be specified for"
+			<< " positive recurrence ratio (robot " << theRobot->kind() << ')'
+			<< endl << xexit;
+
+	if (!theRobot->cookieSender(theCookieSenderProb))
+		theCookieSenderProb = 1;
+	theCookiesKeepLimitSel = theRobot->cookiesKeepLimit();
+
+	if (theReqBodyPauseProb >= 0 &&
+		theReqBodyPauseStart >= 0)
+		cerr << theRobot->loc() << "Both req_body_pause_prob and req_body_pause_start"
+			<< " should not be specified (robot " << theRobot->kind() << ')'
+			<< endl << xexit;
+
+	theRobot->passiveFtp(thePassiveFtp);
+
+	checkConfiguration();
+}
+
+int CltCfg::viservLimit() const {
+	return TheHostMap->iterationCount();
+}
+
+void CltCfg::configureInterests() {
+	Array<StringSym*> istrs;
+	Array<double> iprobs;
+	if (theRobot->interests(istrs, iprobs)) {
+		double pubProb = 0;
+		double privProb = 0;
+		for (int i = 0; i < istrs.count(); ++i)
+			if (istrs[i]->val() == "foreign")
+				theForeignInterestProb = iprobs[i];
+                        else
+			if (istrs[i]->val() == "public")
+				pubProb = iprobs[i];
+                        else
+			if (istrs[i]->val() == "private")
+				privProb = iprobs[i];
+			else {
+				cerr << theRobot->loc() << "interests array can "
+					"have only \"foreign\", \"private\" and "
+					"\"public\" values" << endl << xexit;
+			}
+		if (privProb > 0) {
+			thePublicInterestProb = pubProb / (privProb + pubProb);
+			Comment(1) << theRobot->loc() << "private Robot URLs are "
+				"currently not supported, private interest is "
+				"treated as additional public interest" << endc;
+		} else
+		if (pubProb > 0)
+			thePublicInterestProb = 1;
+		// XXX: private worlds are not supported for now
+		thePublicInterestProb = 1;
+	}
+}
+
+void CltCfg::configureReqTypes() {
+	static StringIdentifier sidf;
+	if (!sidf.count()) {
+		sidf.add("Basic", Client::rqtBasic);
+		sidf.add("Ims200", Client::rqtIms200);
+		sidf.add("Ims304", Client::rqtIms304);
+		sidf.add("Reload", Client::rqtReload);
+		sidf.add("Range", Client::rqtRange);
+		sidf.add("Upload", Client::rqtUpload);
+	}
+
+	theReqTypeSel = theRobot->msgTypes(sidf);
+	if (!theReqTypeSel)
+		theReqTypeSel = new ConstDistr(0, Client::rqtBasic); // default
+	theReqTypeSel->rndGen(LclRndGen("client_req_types"));
+}
+
+void CltCfg::configurePipeDepth() {
+	thePipelineDepth = theRobot->pipelineDepth();
+	if (thePipelineDepth)
+		thePipelineDepth->rndGen(LclRndGen("client_pipe_depth"));
+}
+
+void CltCfg::configureReqMethods() {
+	static StringIdentifier sidf;
+	if (!sidf.count()) {
+		sidf.add("GET", Client::rqmGet);
+		sidf.add("HEAD", Client::rqmHead);
+		sidf.add("POST", Client::rqmPost);
+		sidf.add("PUT", Client::rqmPut);
+	}
+
+	theReqMethodSel = theRobot->reqMethods(sidf);
+	if (!theReqMethodSel)
+		theReqMethodSel = new ConstDistr(0, Client::rqmGet); // default
+	theReqMethodSel->rndGen(LclRndGen("client_req_methods"));
+}
+
+void CltCfg::configureOrigins() {
+	PtrArray<NetAddr*> origNames;
+
+	RndDistr *iad = 0;
+	if (!theRobot->origins(origNames, theOriginSel) || !origNames.count()) {
+		const bool isActive = !theRobot->reqInterArrival(iad) || iad;
+		if (isActive && theForeignInterestProb < 1)
+			Comment(0) << theRobot->loc() << "no origin addresses "
+				"specified for active Robot '" <<
+				theRobot->kind() << "' with some non-foreign "
+				"interest" << endc << xexit;
+		return;
+	}
+	if (theForeignInterestProb >= 1) {
+		Comment(0) << theRobot->loc() << "origin addresses specified "
+			"for Robot '" << theRobot->kind() << "' with "
+			"foreign-only interest" << endc;
+		delete theOriginSel;
+		theOriginSel = 0;
+		return;
+	}
+
+	Assert(theOriginSel);
+	theOriginSel->rndGen(LclRndGen("client_origins"));
+
+	theViservs.stretch(origNames.count());
+	for (int i = 0; i < origNames.count(); ++i)
+		addOrigName(*origNames[i]);
+}
+
+// converts origin name into viserv idx
+void CltCfg::addOrigName(const NetAddr &oname) {
+	if (!TheAddrMap->has(oname)) {
+		if (oname.isDomainName())
+			cerr << here << "visible server name " << oname
+				<< " is not found in address maps" << endl << xexit;
+		TheAddrMap->add(oname);
+	}
+
+	int viserv = -1;
+	if (!TheHostMap->find(oname, viserv))
+		TheHostMap->addAt(viserv, oname);
+	HostCfg *host = TheHostMap->at(viserv);
+	if (!host->thePubWorld)
+		PubWorld::Add(host, new PubWorld(UniqId::Create()));
+
+	if (!host->theServerRep)
+		host->theServerRep = new ServerRep(oname, viserv);
+
+	// quit if an origin entry is repeated because it complicates
+	// private world accounting (two origins would have one world)
+	if (hasViserv(viserv)) {
+		Comment << theRobot->loc() << "error: origin " << oname <<
+			" is listed more than once in robot's origins" << 
+			endc << xexit;
+	}
+
+	theViservs.append(viserv);
+
+	checkTargets(viserv);
+}
+
+// checks content cfg and creates new server representative for viserv
+void CltCfg::checkTargets(int viserv) {
+	const NetAddr &visName = TheHostMap->at(viserv)->theAddr;
+	int niamIdx; // name in AddrMap index
+	Assert(TheAddrMap->find(visName, niamIdx));
+
+	for (AddrMapAddrIter i = TheAddrMap->addrIter(niamIdx); i; ++i) {
+		const NetAddr &addr = i.addr();
+		int targetIdx = -1;
+		if (!TheHostMap->find(addr, targetIdx) ||
+			!TheHostMap->at(targetIdx)->theContent) {
+			Comment << theRobot->loc() << "error: Robot cannot find"
+				<< " configuration for server@" << addr;
+			if (addr != i.name())
+				Comment << " (visible as " << i.name() << ")";
+			Comment << endc << xexit;
+		}
+	}
+}
+
+void CltCfg::checkConfiguration() const {
+	// collect reachable contents
+	ContentSet contents;
+	for (int i = 0; i < theViservs.count(); ++i) {
+		const NetAddr &visName = TheHostMap->at(theViservs[i])->theAddr;
+		int niamIdx; // name in AddrMap index
+		Assert(TheAddrMap->find(visName, niamIdx));
+		for (AddrMapAddrIter j = TheAddrMap->addrIter(niamIdx); j; ++j) {
+			int targetIdx = -1;
+			Assert(TheHostMap->find(j.addr(), targetIdx));
+			const HostCfg *const hcfg = TheHostMap->at(targetIdx);
+			Assert(hcfg && hcfg->theContent);
+			const Array<ContentCfg*> &srvContents =
+				hcfg->theContent->contents();
+			for (int k = 0; k < srvContents.count(); ++k)
+				contents.insert(srvContents[k]);
+		}
+	}
+
+	checkPostContentsConfiguration(contents);
+	checkUploadContentsConfiguration(contents);
+	checkRangesConfiguration(contents);
+}
+
+void CltCfg::checkPostContentsConfiguration(const ContentSet &contents) const {
+	const bool havePostContents = thePostContentSel;
+	if (havePostRequest()) {
+		if (!havePostContents) {
+			Comment(0) << theRobot->loc() << "'POST' request method"
+				" configured but no 'post_contents' set"
+				<< endc << xexit;
+		}
+		return;
+	}
+	bool noPostRequest = true;
+	for (ContentSet::const_iterator i = contents.begin();
+		i != contents.end(); ++i) {
+		const ContentCfg *const content = *i;
+		Assert(content);
+		const CltCfg *const behavior =
+			TheCltBehaviorCfgs.get(content->id());
+		if (!behavior || !behavior->havePostRequest())
+			continue;
+		if (!havePostContents) {
+			Comment(0) << behavior->theRobot->loc() << "'POST' "
+				"request method configured for Content '" <<
+				content->kind() << "' but Robot '" <<
+				theRobot->kind() << "' has no 'post_contents' "
+				"set" << endc << xexit;
+		}
+		noPostRequest = false;
+	}
+	if (havePostContents && noPostRequest) {
+		Comment(0) << theRobot->loc() << "'post_contents' set but no "
+			"'POST' request method configured" << endl << xexit;
+	}
+}
+
+void CltCfg::checkUploadContentsConfiguration(const ContentSet &contents) const {
+	const bool haveUploadContents = theUploadContentSel;
+	if (haveUploadRequest()) {
+		if (!haveUploadContents) {
+			Comment(0) << theRobot->loc() << "'Upload' request "
+				"type or 'PUT' request method configured but no"
+				" 'upload_contents' set" << endl << xexit;
+		}
+		return;
+	}
+	bool noUploadRequest = true;
+	for (ContentSet::const_iterator i = contents.begin();
+		i != contents.end(); ++i) {
+		const ContentCfg *const content = *i;
+		Assert(content);
+		const CltCfg *const behavior =
+			TheCltBehaviorCfgs.get(content->id());
+		if (!behavior || !behavior->haveUploadRequest())
+			continue;
+		if (!haveUploadContents) {
+			Comment(0) << behavior->theRobot->loc() << "'Upload' "
+				"request type or 'PUT' request method "
+				"configured for Content '" << content->kind()
+				<< "' but Robot '" << theRobot->kind() << "' "
+				"has no 'upload_contents' set" << endc << xexit;
+		}
+		noUploadRequest = false;
+	}
+	if (haveUploadContents && noUploadRequest) {
+		Comment(0) << theRobot->loc() << "'upload_contents' set but no "
+			"'Upload' request type or 'PUT' request method "
+			"configured" << endl << xexit;
+	}
+}
+
+void CltCfg::checkRangesConfiguration(const ContentSet &contents) const {
+	const bool haveRanges = theRangeSel;
+	if (haveRangeRequest() && haveRanges)
+		return;
+	bool noRangeRequest = true;
+	bool allRanges = true;
+	for (ContentSet::const_iterator i = contents.begin();
+		i != contents.end(); ++i) {
+		const ContentCfg *const content = *i;
+		Assert(content);
+		const CltCfg *const behavior =
+			TheCltBehaviorCfgs.get(content->id());
+		if (!behavior) {
+			allRanges = false;
+			continue;
+		}
+		const bool needRanges = behavior->theReqTypeSel ?
+			behavior->haveRangeRequest() : haveRangeRequest();
+		if (needRanges && !behavior->theRangeSel)
+			allRanges = false;
+		if (behavior->theRangeSel && !needRanges) {
+			Comment(0) << behavior->theRobot->loc() <<
+				"'ranges' set but no 'Range' request type "
+				"configured for Content '" << content->kind() <<
+				"' and Robot '" << theRobot->kind() << '\''
+				<< endl << xexit;
+		}
+		if (!haveRanges && !behavior->theRangeSel && needRanges) {
+			Comment(0) << behavior->theRobot->loc() << "'Range' "
+				"request type configured but no 'ranges' set "
+				"for Content '" << content->kind() << "' and "
+				"Robot '" << theRobot->kind() << '\''
+				<< endc << xexit;
+		}
+		if (behavior->haveRangeRequest())
+			noRangeRequest = false;
+	}
+	if (haveRanges && noRangeRequest) {
+		Comment(0) << theRobot->loc() << "'ranges' set but no 'Range' "
+			"request type configured" << endl << xexit;
+	}
+	if (haveRangeRequest() && !allRanges) {
+		Comment(0) << theRobot->loc() << "'Range' request "
+			"type configured but no 'ranges' set"
+			<< endl << xexit;
+	}
+}
+
+bool CltCfg::havePostRequest() const {
+	return theRobot->arrayHasElem("req_methods", "POST");
+}
+
+bool CltCfg::haveUploadRequest() const {
+	return theRobot->arrayHasElem("req_types", "Upload") ||
+		theRobot->arrayHasElem("req_methods", "PUT");
+}
+
+bool CltCfg::haveRangeRequest() const {
+	return theRobot->arrayHasElem("req_types", "Range");
+}
+
+void CltCfg::configureProxies() {
+	Array<NetAddr*> addrs;
+
+	ftpProxiesSet = theRobot->ftpProxies(addrs);
+	if (ftpProxiesSet) {
+		theFtpProxies.resize(addrs.count());
+		while (addrs.count())
+			theFtpProxies.enqueue(addrs.pop());
+	}
+
+	if (theRobot->httpProxies(addrs)) {
+		theHttpProxies.resize(addrs.count());
+		while (addrs.count())
+			theHttpProxies.enqueue(addrs.pop());
+		if (TheCltOpts.theProxyAddr)
+			Comment << theRobot->loc() << "--proxy option and "
+				<< "Robot.http_proxies field are mutually exclusive" << endc << xexit;
+	}
+
+	if (theRobot->proxies(addrs)) {
+		Comment(1) << theRobot->loc() << "Robot::proxies field is "
+			<< "deprecated in favor of the Robot::http_proxies "
+			<< "field. See also Robot::ftp_proxies" << endc;
+		if (!theHttpProxies.empty())
+			Comment << theRobot->loc() << "Robot.http_proxies and "
+				<< "Robot.proxies field are mutually exclusive" << endc << xexit;
+		if (TheCltOpts.theProxyAddr)
+			Comment << theRobot->loc() << "--proxy option and "
+				<< "Robot.proxies field are mutually exclusive" << endc << xexit;
+		theHttpProxies.resize(addrs.count());
+		while (addrs.count())
+			theHttpProxies.enqueue(addrs.pop());
+	}
+
+	configureSocksProxies();
+}
+
+void CltCfg::configureSocksProxies() {
+	Array<NetAddr*> addrs;
+	const bool socksProbDefault = !theRobot->socksProb(theSocksProb);
+	if (theRobot->socksProxies(addrs)) {
+		theSocksProxies.resize(addrs.count());
+		while (addrs.count())
+			theSocksProxies.enqueue(addrs.pop());
+		if (socksProbDefault)
+			theSocksProb = 1;
+	} else if (theSocksProb > 0) {
+		cerr << theRobot->loc() << "Robot::socks_prob field "
+			<< "is positive but no Robot::socks_proxies configured"
+			<< endl << xexit;
+	}
+	const bool socksProxyPresent = !theSocksProxies.empty();
+	const bool otherProxyPresent =
+		!theHttpProxies.empty() || !theFtpProxies.empty();
+	theRobot->socksChainingProb(theSocksChainingProb);
+	if (theSocksChainingProb > 0) {
+		if (!socksProxyPresent) {
+			cerr << theRobot->loc()
+				<< "Robot::socks_chaining_prob field is positive "
+				<< "but no Robot::socks_proxies configured"
+				<< endl << xexit;
+		}
+		if (!otherProxyPresent) {
+			cerr << theRobot->loc()
+				<< "Robot::socks_chaining_prob field is positive "
+				<< "but no Robot::http_proxies/Robot::ftp_proxies "
+				<< "configured"	<< endl << xexit;
+		}
+	}
+}
+
+void CltCfg::configureCredentials() {
+	Array<String*> creds;
+	theRobot->credentials(creds);
+	theCredentials.resize(creds.count());
+	while (creds.count())
+		theCredentials.enqueue(creds.pop());
+}
+
+void CltCfg::configureContainerTags() {
+	Array<String*> tags;
+	if (!theRobot->containerTags(tags))
+		tags.append(new String("<embed src>")); // default
+	theContainerTags = new XmlTagIdentifier();
+	theContainerTags->configure(tags);
+	while (tags.count()) delete tags.pop();
+}
+
+void CltCfg::configureAcceptedContentCodings() {
+	Array<String*> codings;
+	if (theRobot->acceptedContentCodings(codings)) {
+		theAcceptedContentCodings = new String;
+		for (int i = 0; i < codings.count(); ++i) {
+			const String &coding = *codings[i];
+			if (coding.startsWith("*") || coding.startsWith("gzip"))
+				acceptingGzipContent = true;
+			if (*theAcceptedContentCodings)
+				*theAcceptedContentCodings += ", ";
+			*theAcceptedContentCodings += coding;
+			delete codings[i];
+		}
+	}
+}
+
+void CltCfg::configureMemberships() {
+	// initialize global array once
+	const Array<MembershipMapSym*> &syms = PglStaticSemx::TheMembershipsToUse;
+	if (TheGlbMemberships.count() != syms.count()) {
+		TheGlbMemberships.stretch(syms.count());
+		for (int i = 0; i < syms.count(); ++i) {
+			MembershipMap *m = new MembershipMap;
+			m->configure(*syms[i], i+1);
+			TheGlbMemberships.append(m);
+		}
+	}
+
+	for (int i = 0; i < TheGlbMemberships.count(); ++i) {
+		MembershipMap *g = TheGlbMemberships[i];
+		bool belongs = false;
+		for (int u = 0; !belongs && u < theCredentials.count(); ++u) {
+			belongs = g->hasMember(*theCredentials.top(u));
+		}
+		if (belongs)
+			theLclMemberships.append(g);
+	}
+}
+
+void CltCfg::configureTrace() {
+	if (const String fname = theRobot->foreignTrace()) {
+		if (theForeignInterestProb > 0) {
+			theForeignWorld = new ForeignWorld;
+			theForeignWorld->configure(fname);
+		} else {
+			Comment(1) << theRobot->loc() << "warning: foreign "
+				<< "trace '" << fname << "' is configured "
+				<< "but no foreign interest specified"
+				<< endc;
+		}
+	} else
+	if (theForeignInterestProb > 0) {
+		Comment << theRobot->loc() << "error: foreign interest is "
+			<< "positive but no foreign trace configured"
+			<< endc << xexit;
+	}
+}
+
+void CltCfg::configureRanges() {
+	Array<const RangeSym*> syms;
+	if (!theRobot->ranges(syms, theRangeSel))
+		return; // no ranges configured
+
+	theRangeSel->rndGen(LclRndGen("client_ranges"));
+	theRanges.stretch(syms.count());
+	for (int i = 0; i < syms.count(); ++i) {
+		const RangeSym &sym = *syms[i];
+		if (sym.isA(SingleRangeSym::TheType)) {
+			SingleRangeCfg *const r = new SingleRangeCfg;
+			r->configure((const SingleRangeSym&)sym);
+			theRanges.append(r);
+		}
+		else
+		if (sym.isA(MultiRangeSym::TheType)) {
+			MultiRangeCfg *const r = new MultiRangeCfg;
+			r->configure((const MultiRangeSym&)sym);
+			theRanges.append(r);
+		}
+		else
+			cerr
+				<< theRobot->loc()
+				<< "Unknown range type \"" << sym.type() << '"'
+				<< endl << xexit;
+	}
+}
+
+void CltCfg::configurePostContents() {
+	Array<ContentSym*> syms;
+	if (!theRobot->reqContents("post_contents", syms, thePostContentSel))
+		return; // no contents configured
+
+	thePostContentSel->rndGen(LclRndGen("client_post_contents"));
+	thePostContents.stretch(syms.count());
+	TheContentMgr.get(syms, thePostContents);
+}
+
+void CltCfg::configureUploadContents() {
+	Array<ContentSym*> syms;
+	if (!theRobot->reqContents("upload_contents", syms, theUploadContentSel)) {
+		if (!theRobot->reqContents("put_contents", syms, theUploadContentSel))
+			return; // no contents configured
+		else {
+			Comment(1) << theRobot->loc() << "Robot::put_contents field "
+				"is deprecated in favor of the "
+				"Robot::upload_contents field" << endc;
+		}
+	}
+
+	theUploadContentSel->rndGen(LclRndGen("client_upload_contents"));
+	theUploadContents.stretch(syms.count());
+	TheContentMgr.get(syms, theUploadContents);
+}
+
+bool CltCfg::hasViserv(int viserv) const {
+	for (int origin = 0; origin < theViservs.count(); ++origin) {
+		if (theViservs[origin] == viserv)
+			return true;
+	}
+	return false;
+}
+
+bool CltCfg::selectCredentials(String &newCred) {
+	if (theCredentials.empty())
+		return false;
+
+	// randomize in the beginning of each cycle
+	if (theCredentialCycleCnt >= theCredentials.count())
+		theCredentialCycleCnt = 0;
+	if (!theCredentialCycleCnt) {
+		static RndGen rng;
+		theCredentials.randomize(rng);
+	}
+	theCredentialCycleCnt++;
+		
+	String *credential = theCredentials.dequeue();
+	theCredentials.enqueue(credential);
+	newCred = *credential;
+	return true;
+}
+
+int CltCfg::selectViserv() {
+	if (theWarmupPlan) {
+		const int viservIdx = theWarmupPlan->selectViserv();
+		if (viservIdx >= 0)
+			return viservIdx;
+		stopWarmup();
+	}
+
+	const int viservIdx = (int)theOriginSel->trial();
+	Assert(0 <= viservIdx && viservIdx < theViservs.count());
+	return theViservs[viservIdx];
+}
+
+bool CltCfg::selectFtpProxy(NetAddr &newAddr) {
+	if (!ftpProxiesSet)
+		return false;
+
+	selectProxy(theFtpProxies, theFtpProxyCycleCnt, newAddr);
+	return true;
+}
+
+bool CltCfg::selectHttpProxy(NetAddr &newAddr) {
+	if (TheCltOpts.theProxyAddr)
+		newAddr = TheCltOpts.theProxyAddr;
+	else
+		selectProxy(theHttpProxies, theHttpProxyCycleCnt, newAddr);
+	return true;
+}
+
+bool CltCfg::selectSocksProxy(NetAddr &newAddr, bool &doSocksChaining) {
+	if (theSocksProb <= 0)
+		return false;
+
+	static RndGen *const rng1 = LclRndGen("socks_prob");
+	if (!rng1->event(theSocksProb))
+		return false;
+
+	selectProxy(theSocksProxies, theSocksProxyCycleCnt, newAddr);
+	if (theSocksChainingProb > 0) {
+		static RndGen *const rng2 = LclRndGen("socks_chaining_prob");
+		doSocksChaining = rng2->event(theSocksChainingProb);
+	} else
+		doSocksChaining = false;
+	return true;
+}
+
+// XXX: merge this with SrvCfg::selectAbortCoord()
+void CltCfg::selectAbortCoord(XactAbortCoord &coord) {
+	static RndGen rng1, rng2; // uncorrelated unless theAbortProb is 1
+	if (rng1.event(theAbortProb)) {
+		const int whether = rng2.state();
+		(void)rng2.trial();
+		coord.configure(rng2.state(), whether);
+	} else {
+		const int whether = rng1.state();
+		(void)rng1.trial();
+		coord.configure(whether, rng1.state());
+	}
+}
+
+bool CltCfg::selectFtpMode(bool &usePassive) {
+	if (thePassiveFtp < 0)
+		return false;
+
+	static RndGen rng;
+	usePassive = rng.event(thePassiveFtp);
+	return true;
+}
+
+int CltCfg::findMemberships(const String &user, Memberships &groups) const {
+	if (user) {
+		for (int i = 0; i < theLclMemberships.count(); ++i) {
+			MembershipMap *g = theLclMemberships[i];
+			if (g->hasMember(user))
+				groups.append(g);
+		}
+	}
+	return groups.count();
+}
+
+bool CltCfg::followAllUris(const RepHdr &rep) const {
+	if (theUriThrower.len() > 0) {
+		return theUriThrower == "*" ||
+			rep.theServer.startsWith(theUriThrower);
+	} else {
+		return false;
+	}
+}
+
+RangeCfg::RangesInfo CltCfg::makeRangeSet(ostream &os, const ObjId &oid, ContentCfg &contentCfg) const {
+	Assert(theRangeSel);
+	return theRanges[theRangeSel->ltrial()]->makeRangeSet(os, oid, contentCfg);
+}
+
+// may be called multiple times
+void CltCfg::startWarmup() {
+	if (!theWarmupPlan && !didWarmup && theForeignInterestProb < 1) {
+		theWarmupPlan = new WarmupPlan(theViservs);
+
+		Comment(7) << "started a warmup plan for Robot '" <<
+			theRobot->kind() << "' with " <<
+			theWarmupPlan->planCount() << " cold servers, out "
+			"of " << PubWorld::Count() << " already visible" << endc;
+	}
+}
+
+void CltCfg::stopWarmup() {
+	Assert(theWarmupPlan && !didWarmup);
+
+	Comment(7) << "stopped warmup plan for Robot '" << theRobot->kind() <<
+		"' with " << theWarmupPlan->planCount() << " cold servers, "
+		"out of " << PubWorld::Count() << " globally visible" << endc;
+
+	delete theWarmupPlan;
+	theWarmupPlan = 0;
+	didWarmup = true;
+}
+
+void CltCfg::selectProxy(Ring<NetAddr*> &proxies, int &proxyCycleCnt, NetAddr &newAddr) {
+	if (proxies.empty())
+		return;
+
+	// randomize in the beginning of each cycle
+	if (proxyCycleCnt >= proxies.count())
+		proxyCycleCnt = 0;
+	if (!proxyCycleCnt) {
+		static RndGen rng;
+		proxies.randomize(rng);
+	}
+	proxyCycleCnt++;
+
+	NetAddr *addr = proxies.dequeue();
+	proxies.enqueue(addr);
+	newAddr = *addr;
+}
+
+/* CltSharedCfgs */
+
+CltSharedCfgs::~CltSharedCfgs() {
+	while (count()) delete pop();
+}
+
+CltCfg *CltSharedCfgs::getConfig(const RobotSym *rs) {
+	for (int i = 0; i < count(); ++i) {
+		if (item(i)->theRobot == rs)
+			return item(i);
+	}
+	return addConfig(rs);
+}
+
+CltCfg *CltSharedCfgs::addConfig(const RobotSym *rs) {
+	CltCfg *cfg = new CltCfg;
+	cfg->configure(rs);
+	append(cfg);
+	return cfg;
+}
diff -ruBEN polygraph-4.3.1/src/client/CltCfg.h polygraph-4.3.1-mm/src/client/CltCfg.h
--- polygraph-4.3.1/src/client/CltCfg.h	2010-12-10 18:25:55.000000000 +0000
+++ polygraph-4.3.1-mm/src/client/CltCfg.h	2011-03-04 23:15:41.000000000 +0000
@@ -119,6 +119,11 @@
 		double thePublicRatio;     // how often to request a "public" object
 		double theRecurRatio;      // how often to re-visit an old object
 		double theSpnegoAuthRatio; // how often negotiate/spnego auth is preferred over NTLMSSP
+		bool preferKerberosAuth;   // is negotiate/kerberos auth preferred over negotiate/spnego
+		bool clearKerberosCache;   // clear cache before new session
+		String theKerberosConfigPath;   // is the Kerberos configuration file location (krb5.conf) 
+		String theKerberosProxySPN;   // is the Kerberos service principal name for the proxy
+		String theKerberosServerSPN;   // is the Kerberos service principal name for the server
 		double theEmbedRecurRatio; // how often to re-visit an old embedded obj
 		double theAbortProb;	   // prob of aborting a transaction
 		double theAuthError;       // prob of an authentication error
diff -ruBEN polygraph-4.3.1/src/client/CltCfg.h.orig polygraph-4.3.1-mm/src/client/CltCfg.h.orig
--- polygraph-4.3.1/src/client/CltCfg.h.orig	1970-01-01 01:00:00.000000000 +0100
+++ polygraph-4.3.1-mm/src/client/CltCfg.h.orig	2010-12-10 18:25:55.000000000 +0000
@@ -0,0 +1,178 @@
+
+/* Web Polygraph       http://www.web-polygraph.org/
+ * (C) 2003-2006 The Measurement Factory
+ * Licensed under the Apache License, Version 2.0 */
+
+#ifndef POLYGRAPH__CLIENT_CLTCFG_H
+#define POLYGRAPH__CLIENT_CLTCFG_H
+
+#include <set>
+#include "xstd/Ring.h"
+#include "xstd/BigSize.h"
+#include "xstd/NetAddr.h"
+#include "runtime/AgentCfg.h"
+#include "client/MembershipMap.h"
+#include "client/AclGroup.h"
+#include "client/RangeCfg.h"
+
+class Goal;
+class ObjId;
+class RndDistr;
+class PopModel;
+class RepHdr;
+class RobotSym;
+class WarmupPlan;
+class XactAbortCoord;
+class RegExGroup;
+class ForeignWorld;
+class XmlTagIdentifier;
+class ContentCfg;
+
+// Client configuration items that can be shared among multiple clients
+class CltCfg: public AgentCfg {
+	public:
+		CltCfg();
+		~CltCfg();
+
+		void configure(const RobotSym *aRobot);
+
+		int viservLimit() const;
+		int viservCount() const { return theViservs.count(); }
+		bool hasViserv(int viserv) const;
+
+		void startWarmup();
+		void stopWarmup();
+
+		int selectViserv();
+		bool selectFtpProxy(NetAddr &addr);
+		bool selectHttpProxy(NetAddr &addr);
+		bool selectSocksProxy(NetAddr &addr, bool &doSocksChaining);
+		bool selectCredentials(String &newName);
+		void selectAbortCoord(XactAbortCoord &coord);
+		bool selectFtpMode(bool &usePassive);
+
+		bool followAllUris(const RepHdr &rep) const;
+		int findMemberships(const String &user, Memberships &memberships) const;
+		const AclGroup &acl() const { return theAcl; }
+		ForeignWorld *foreignWorld() { return theForeignWorld; }
+
+		RangeCfg::RangesInfo makeRangeSet(ostream &os, const ObjId &oid, ContentCfg &contentCfg) const;
+
+	protected:
+		void configureInterests();
+		void configureReqTypes();
+		void configureReqMethods();
+		void configureOrigins();
+		void configureProxies();
+		void configureSocksProxies();
+		void configureCredentials();
+		void configureMemberships();
+		void configureContainerTags();
+		void configureAcceptedContentCodings();
+		void configurePipeDepth();
+		void configureTrace();
+		void configureRanges();
+		void configurePostContents();
+		void configureUploadContents();
+
+		void addOrigName(const NetAddr &oname);
+		void checkTargets(int viserv);
+
+		typedef std::set<const ContentCfg*> ContentSet;
+		void checkConfiguration() const;
+		void checkPostContentsConfiguration(const ContentSet &contents) const;
+		void checkUploadContentsConfiguration(const ContentSet &contents) const;
+		void checkRangesConfiguration(const ContentSet &contents) const;
+		bool havePostRequest() const;
+		bool haveUploadRequest() const;
+		bool haveRangeRequest() const;
+
+		static void selectProxy(Ring<NetAddr*> &proxies, int &proxyCycleCnt, NetAddr &newAddr);
+
+	protected:
+		static Memberships TheGlbMemberships;
+
+	public:
+		const RobotSym *theRobot;  // used to identify/share configs
+
+		Array<int> theViservs;     // origin idx to HostMap idx (viserv)
+		RndDistr *theOriginSel;    // selects origin idx
+		RndDistr *theReqTypeSel;   // selects request types
+		RndDistr *theReqMethodSel; // selects request methods
+		PopModel *thePopModel;     // popularity model to select old oids
+
+		Goal *theBusyPeriod;       // sessions's busy period config
+		RndDistr *theIdlePeriodDur;// sessions's idle period config
+		Time theSessionHeartbitGap;// hearbit interval
+
+		Ring<NetAddr*> theFtpProxies;
+		Ring<NetAddr*> theHttpProxies;
+		Ring<NetAddr*> theSocksProxies;
+		int theFtpProxyCycleCnt;      // to randomize proxy selection
+		int theHttpProxyCycleCnt;      // to randomize proxy selection
+		int theSocksProxyCycleCnt;    // to randomize proxy selection
+
+		RndDistr *thePipelineDepth;// maximum concurrent pipelined requests
+
+		NetAddr thePeerHttp;       // caching peer talking HTTP
+		NetAddr thePeerIcp;        // caching peer talking ICP
+		double thePublicRatio;     // how often to request a "public" object
+		double theRecurRatio;      // how often to re-visit an old object
+		double theSpnegoAuthRatio; // how often negotiate/spnego auth is preferred over NTLMSSP
+		double theEmbedRecurRatio; // how often to re-visit an old embedded obj
+		double theAbortProb;	   // prob of aborting a transaction
+		double theAuthError;       // prob of an authentication error
+
+		int theWaitXactLmt;        // limit on the number of postponed requests
+		int theIcpPort;            // IPC client should use this port
+
+		Memberships theLclMemberships; // all user groups users belong to
+		Ring<String*> theCredentials;  // all users under this cfg
+		int theCredentialCycleCnt;
+		AclGroup theAcl;
+
+		String theUriThrower;          // follow all URIs from this server
+		ForeignWorld *theForeignWorld; // foreign URLs trace, if any
+
+		XmlTagIdentifier *theContainerTags;
+		String *theAcceptedContentCodings; // for Accept-Encoding header
+		bool acceptingGzipContent;         // pre-parsed status
+
+		RndDistr *theCookiesKeepLimitSel; // how many cookies to keep
+
+		WarmupPlan *theWarmupPlan;
+
+		double theReqBodyPauseProb; // how often to send "Expect: 100-continue"
+		BigSize theReqBodyPauseStart;  // body size limt to send "Expect: 100-continue"
+
+		double theReqBodyRecurrence;
+		Array<ContentCfg*> thePostContents;
+		RndDistr *thePostContentSel;
+		Array<ContentCfg*> theUploadContents;
+		RndDistr *theUploadContentSel;
+
+		double theForeignInterestProb;
+		double thePublicInterestProb;
+
+		double thePassiveFtp; // probability of a robot using passive FTP
+		double theSocksProb; // probability of a robot using SOCKS proxy
+		double theSocksChainingProb; // probability of a SOCKS-using robot also using HTTP/FTP proxies
+
+		bool genUniqUrls;          // each generated URL must be unique
+		bool didWarmup;            // warmup plan was completed
+		Array<const RangeCfg*> theRanges; // range configurations
+		RndDistr *theRangeSel; // selects single range
+
+		bool ftpProxiesSet; // true if Robot.ftp_proxies field is set
+};
+
+class CltSharedCfgs: protected Array<CltCfg*> {
+	public:
+		~CltSharedCfgs();
+		CltCfg *getConfig(const RobotSym *cfg);
+
+	protected:
+		CltCfg *addConfig(const RobotSym *cfg);
+};
+
+#endif
diff -ruBEN polygraph-4.3.1/src/client/GssKrbCtx.h polygraph-4.3.1-mm/src/client/GssKrbCtx.h
--- polygraph-4.3.1/src/client/GssKrbCtx.h	1970-01-01 01:00:00.000000000 +0100
+++ polygraph-4.3.1-mm/src/client/GssKrbCtx.h	2011-03-04 23:15:41.000000000 +0000
@@ -0,0 +1,78 @@
+#ifndef __HTTP_GSSKRBCTX_H
+#define __HTTP_GSSKRBCTX_H
+
+#ifdef HAVE_KRB5_H
+#ifdef HAVE_BROKEN_HEIMDAL_KRB5_H
+extern "C" {
+#include <krb5.h>
+}
+#else
+#include <krb5.h>
+#endif
+#endif /* HAVE_KRB5_H */
+
+#ifdef HAVE_GSSAPI_H
+#include <gssapi.h>
+#elif defined(HAVE_GSSAPI_GSSAPI_H)
+#include <gssapi/gssapi.h>
+#endif /* HAVE_GSSAPI_H */
+
+#ifndef gss_nt_service_name
+#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
+#endif
+
+#if !defined(HAVE_HEIMDAL_KERBEROS)
+#ifdef HAVE_GSSAPI_GSSAPI_EXT_H
+#include <gssapi/gssapi_ext.h>
+#endif /* HAVE_GSSAPI_GSSAPI_EXT_H */
+#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H
+#include <gssapi/gssapi_krb5.h>
+#endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */
+#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
+#include <gssapi/gssapi_generic.h>
+#endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */
+#endif /* !HAVE_HEIMDAL_KERBEROS */
+
+class GssKrbXact {
+        public:
+		GssKrbXact();
+		OM_uint32 gssMajorStatus;
+		OM_uint32 gssMinorStatus;
+		gss_name_t gssServerName;
+                void gssCleanup();
+                int checkGssError(String gssFunctionName);
+                NtlmAuthState NegoKerberosAuthPrint(ostream &os, String gssBase64Token);
+		static int pton(const char *p);
+
+		class KrbEnv {
+        		public:
+				KrbEnv();
+				~KrbEnv();
+				String krbUserp;
+				String krbPasswdp;
+				String krbServerName;
+				String krbCfgPath;
+				String krbSPN;
+				bool clearKrbCc;
+				NtlmAuthState krbSetup();
+
+			private:
+				krb5_context theKrbCtx;
+				krb5_principal theKrbClt;
+				krb5_ccache theKrbCc;
+				krb5_creds theKrbCreds;
+				krb5_error_code theKrbErrorCode;
+				struct krbCtxStruct {
+					String theKrbCfgPath;
+					krb5_context theKrbCtx;
+					struct krbCtxStruct *theNextKrbCtx;
+				} *theKrbCtxStruct;
+				NtlmAuthState krbCleanup();
+                		int krbCheckError(String theKrbFunctionName);
+		};
+		friend class KrbEnv;
+
+        private:
+                gss_ctx_id_t theGssContext;
+};
+#endif
diff -ruBEN polygraph-4.3.1/src/client/HttpCltXact.cc polygraph-4.3.1-mm/src/client/HttpCltXact.cc
--- polygraph-4.3.1/src/client/HttpCltXact.cc	2010-12-22 23:42:25.000000000 +0000
+++ polygraph-4.3.1-mm/src/client/HttpCltXact.cc	2011-03-04 23:15:41.000000000 +0000
@@ -16,6 +16,7 @@
 #include "client/MarkupBodyParser.h"
 #include "client/MultiPartParser.h"
 #include "client/NtlmAuth.h"
+#include "client/KerberosAuth.h"
 #include "client/SingleCxm.h"
 #include "client/PipelinedCxm.h"
 #include "client/PrivCache.h"
@@ -586,16 +587,17 @@
 void HttpCltXact::makeProxyAuthHdr(ostream &os) {
 	const HttpAuthScheme scheme(theOwner->proxyAuthScheme(theOid));
 	Connection::NtlmAuth &ntlmState(theConn->theProxyNtlmState);
-	makeAuthHdr(hfpProxyAuthorization, scheme, ntlmState, os);
+	makeAuthHdr(true, hfpProxyAuthorization, scheme, ntlmState, os);
 }
 
 void HttpCltXact::makeOriginAuthHdr(ostream &os) {
 	const HttpAuthScheme scheme(theOwner->originAuthScheme(theOid));
 	Connection::NtlmAuth &ntlmState(theConn->theOriginNtlmState);
-	makeAuthHdr(hfpAuthorization, scheme, ntlmState, os);
+	makeAuthHdr(false, hfpAuthorization, scheme, ntlmState, os);
 }
 
-void HttpCltXact::makeAuthHdr(const String &header, const HttpAuthScheme scheme, Connection::NtlmAuth &ntlmState, ostream &os) {
+void HttpCltXact::makeAuthHdr(const bool proxyAuth, const String &header, const HttpAuthScheme scheme, 
+				Connection::NtlmAuth &ntlmState, ostream &os) {
 	theOid.authCred(false);
 	switch (ntlmState.state) {
 		case ntlmNone: { // Initiating auth
@@ -607,9 +609,37 @@
 			} else
 			if (scheme == authNegotiate) {
 				makeAuthorization(header, scheme, os);
-				NegoNtlmAuthPrintT1(os, ntlmState.useSpnegoNtlm);
-				os << crlf;
-				ntlmState.state = ntlmSentT1;
+				if (!theOwner->cfg()->preferKerberosAuth) { // Spnego
+					NegoNtlmAuthPrintT1(os, ntlmState.useSpnegoNtlm);
+					os << crlf;
+					ntlmState.state = ntlmSentT1;
+				} else { // Kerberos otherwise
+					if (theOwner->credentialsFor(theOid, theCred)) {
+						Area aUser = theCred.name();
+						String sUser(aUser.data(), aUser.size());
+						Area aPass = theCred.password();
+						String sPass(aPass.data(), aPass.size());
+						if (proxyAuth) {
+							ntlmState.state = 
+								CltGssXact::NegoKerberosAuthPrintT1(os, sUser,
+								sPass, theOwner->proxy(theOid).addrA(),
+								theOwner->cfg()->theKerberosConfigPath,
+								theOwner->cfg()->clearKerberosCache,
+								theOwner->cfg()->theKerberosProxySPN);
+						} else {// Oid2UrlHost(theOid).addrA() should have the name but 
+							// has only the IP. need to look for right lookup call 
+							ntlmState.state = 
+								CltGssXact::NegoKerberosAuthPrintT1(os, sUser,
+								sPass, Oid2UrlHost(theOid).addrA(),
+								theOwner->cfg()->theKerberosConfigPath,
+								theOwner->cfg()->clearKerberosCache,
+								theOwner->cfg()->theKerberosServerSPN);
+						}
+						os << crlf;
+					} else {
+						ntlmState.state = kerberosError;
+					}
+				}
 			} else
 			if (scheme == authBasic) {
 				if (theOwner->credentialsFor(theOid, theCred)) {
@@ -623,6 +653,15 @@
 			}
 			break;
 		}
+		case kerberosSentT1: {
+			if (!header.cmp(hfpProxyAuthorization)) {
+				ntlmState.state = CltGssXact::NegoKerberosAuthPrintT3(os, ntlmState.hdrRcvdT2);
+			} else {
+				ntlmState.state = CltGssXact::NegoKerberosAuthPrintT3(os, ntlmState.hdrRcvdT2);
+			}
+			os << crlf;
+			break;
+		}
 		case ntlmSentT1: {
 			if (theOwner->credentialsFor(theOid, theCred)) {
 				Area aUser = theCred.name();
@@ -646,7 +685,12 @@
 			}
 			break;
 		}
+		case kerberosDone:
 		case ntlmDone:
+		case kerberosError: {
+			// do nothing
+			break;
+		}
 		case ntlmError: {
 			// do nothing
 			break;
@@ -943,21 +987,26 @@
 					authError = startAuth(authNtlm);
 				} else
 				if (challenge.scheme == authNegotiate) {
-					ntlmState.useSpnegoNtlm = isSpnegoNtlm(challenge.params.cstr());
-					if (ntlmState.useSpnegoNtlm && theOwner->cfg()->theSpnegoAuthRatio >= 0) {
-					     static RndGen rng;
-					     ntlmState.useSpnegoNtlm = rng.event(theOwner->cfg()->theSpnegoAuthRatio);
-					}
+                                	if (!theOwner->cfg()->preferKerberosAuth) { // Spnego
+						ntlmState.useSpnegoNtlm = isSpnegoNtlm(challenge.params.cstr());
+						if (ntlmState.useSpnegoNtlm && theOwner->cfg()->theSpnegoAuthRatio >= 0) {
+					     		static RndGen rng;
+					     		ntlmState.useSpnegoNtlm = rng.event(theOwner->cfg()->theSpnegoAuthRatio);
+						}
 					
-					// According to rfc4559 spnego-based negotiate auth is not supposed
-					// to be used by proxies (but can be passed transparently to servers),
-					// so we can't really say whether we need to retry here or what. Need
-					// to test on available clients and proxies.
-					doRetry = ntlmState.useSpnegoNtlm;
+						// According to rfc4559 spnego-based negotiate auth is not supposed
+						// to be used by proxies (but can be passed transparently to servers),
+						// so we can't really say whether we need to retry here or what. Need
+						// to test on available clients and proxies.
+						doRetry = ntlmState.useSpnegoNtlm;
 					
-					// sent nothing, in case of NTLMSSP proxy should signal disconnect, 
-					// Negotiate authentication will resume once we reconnect
-					authError = startAuth(authNegotiate);
+						// sent nothing, in case of NTLMSSP proxy should signal disconnect, 
+						// Negotiate authentication will resume once we reconnect
+						authError = startAuth(authNegotiate);
+					} else { // Kerberos otherwise
+						doRetry = true;
+						authError = startAuth(authNegotiate);
+					}
 				} else
 				if (!theCred.image()) {
 					// sent nothing, Basic auth
@@ -970,6 +1019,11 @@
 				// else auth correctly denied due to invalid credentials
 				break;
 			}
+			case kerberosSentT1: {
+				ntlmState.hdrRcvdT2 = challenge.params;
+				doRetry = true; // continue processing in HopByHops
+				break;
+			}
 			case ntlmSentT1: {
 				ntlmState.hdrRcvdT2 = challenge.params;
 				doRetry = true; // continue processing in HopByHops
@@ -1000,6 +1054,14 @@
 					authError = proxyAuth ? errProxyAuthAllowed : errOriginAuthAllowed;
 				break;
 			}
+			case kerberosSentT1: { // as expected
+				ntlmState.state = kerberosDone;
+				break;
+                        }
+			case kerberosDone: {
+				// do nothing, everything is ok
+				break;
+			}
 			case ntlmDone: {
 				// do nothing, everything is ok
 				break;
diff -ruBEN polygraph-4.3.1/src/client/HttpCltXact.cc.orig polygraph-4.3.1-mm/src/client/HttpCltXact.cc.orig
--- polygraph-4.3.1/src/client/HttpCltXact.cc.orig	1970-01-01 01:00:00.000000000 +0100
+++ polygraph-4.3.1-mm/src/client/HttpCltXact.cc.orig	2010-12-22 23:42:25.000000000 +0000
@@ -0,0 +1,1437 @@
+
+/* Web Polygraph       http://www.web-polygraph.org/
+ * (C) 2003-2006 The Measurement Factory
+ * Licensed under the Apache License, Version 2.0 */
+
+#include "base/polygraph.h"
+
+#include "base/StatIntvlRec.h"
+#include "client/AnyBodyParser.h"
+#include "client/Client.h"
+#include "client/CltCfg.h"
+#include "client/CltDataFilterRegistry.h"
+#include "client/CltOpts.h"
+#include "client/ChunkedCodingParser.h"
+#include "client/HttpCltXact.h"
+#include "client/MarkupBodyParser.h"
+#include "client/MultiPartParser.h"
+#include "client/NtlmAuth.h"
+#include "client/SingleCxm.h"
+#include "client/PipelinedCxm.h"
+#include "client/PrivCache.h"
+#include "client/RegExGroups.h"
+#include "client/ServerRep.h"
+#include "client/UriScriptBodyParser.h"
+#include "csm/BodyIter.h"
+#include "csm/ContentCfg.h"
+#include "csm/oid2Url.h"
+#include "runtime/globals.h"
+#include "runtime/httpText.h"
+#include "runtime/polyErrors.h"
+#include "runtime/CompoundXactInfo.h"
+#include "runtime/ErrorMgr.h"
+#include "runtime/Farm.h"
+#include "runtime/HostMap.h"
+#include "runtime/HttpCookies.h"
+#include "runtime/HttpDate.h"
+#include "runtime/LogComment.h"
+#include "runtime/PageInfo.h"
+#include "runtime/PubWorld.h"
+#include "runtime/SharedOpts.h"
+#include "runtime/StatPhaseMgr.h"
+#include "runtime/StatPhaseSync.h"
+
+
+HttpCltXact::HttpCltXact() {
+	HttpCltXact::reset();
+}
+
+void HttpCltXact::reset() {
+	CltXact::reset();
+
+	theRepHdr.reset();
+	theOlcTimes.reset();
+	theReqBodySize = -1;
+	thePeerState = peerUnknown;
+	theSavedRepHeader.reset();
+	the100ContinueState = csNone;
+
+	theRangesSize = Size();
+	theRangeCount = Size();
+	theBodyPartsSize = 0;
+	theBodyPartCount = 0;
+
+	theActualRepType = -1;
+
+	theReqFlags = 0;
+}
+
+HttpAuthScheme HttpCltXact::proxyAuth() const {
+	Assert(theOwner);
+	return theOwner->proxyAuthScheme(theOid);
+}
+
+PipelinedCxm *HttpCltXact::getPipeline() {
+	Assert(theMgr);
+
+	if (!theConn->pipelineable())
+		return 0;
+
+	// if we are not already pipelining, create a pipeline manager
+	if (!theMgr->pipelining()) {
+		Assert(theState == stBodyWaiting);
+		PipelinedCxm *mgr = PipelinedCxm::Get();
+		mgr->assumeReadControl(this, theMgr);
+		theMgr->release(this);
+		theMgr = mgr;
+		return mgr;
+	}
+
+	return dynamic_cast<PipelinedCxm*>(theMgr);
+}
+
+void HttpCltXact::pipeline(PipelinedCxm *aMgr) {
+	Assert(theMgr != aMgr); // or we will overincrement useLevel
+	theMgr->release(this);
+	aMgr->join(this); // we also call control() in exec()
+	theMgr = aMgr;
+	theConn = theMgr->conn();
+	Assert(theConn);
+	theConn->startUse();
+}
+
+void HttpCltXact::exec(Connection *const aConn) {
+	CltXact::exec(aConn);
+
+	theOwner->cfg()->selectAbortCoord(theAbortCoord);
+
+	Should (!theConn->tunneling() || theConn->sslConfigured());
+	if (theConn->sslConfigured() && !theConn->sslActive()) {
+		if (theConn->tunneling())
+			theOid.connect(true);
+		else
+			theConn->sslActivate();
+	}
+
+	if (!theMgr)
+		theMgr = SingleCxm::Get(); // may be changed to PipelinedCxm later
+	theMgr->control(this);
+
+	newState(stConnWaiting);
+}
+
+void HttpCltXact::finish(Error err) {
+	if (!err && theRepHdr.redirect())
+		redirect();
+
+	// special actions for objects with bodies
+	if (!theOid.head() && theBodyParser && theBodyParser->used()) {
+		// check MD5
+		if (!err && !theOid.aborted() && theRepHdr.theChecksum.set()) {
+			theCheckAlg.final();
+			if (!theCheckAlg.sum().equal(theRepHdr.theChecksum)) {
+				if (ReportError(errChecksum)) {
+					theRepHdr.theChecksum.print(Comment << "MD5 expected: ") << endc;
+					theCheckAlg.sum().print(Comment << "MD5 received: ") << endc;
+					if (TheOpts.theDumpFlags(dumpErr, dumpAny))
+						printMsg(theConn->theRdBuf);
+				}
+				err = errOther;
+			}
+		}
+
+		// cache
+		if (!err && theOid.cachable()) {
+			if (PrivCache *cache = theOwner->privCache())
+				cache->storeOid(theOid);
+		}
+	}
+
+	if (err && theOwner->privCache())
+		theOwner->privCache()->purgeOid(theOid);
+
+	if (!theMgr) {
+		// no manager if lifetime timeout happens while in waiting queue
+		Must(err);
+		Must(err == errXactLifeTimeExpired);
+	} else if (err) {
+		if (err != errPipelineAbort)
+			theMgr->noteAbort(this);
+	} else {
+		theMgr->noteDone(this);
+	}
+
+	if (theAuthXact) {
+		++theAuthXact->exchanges;
+		theAuthXact->reqSize += theReqSize.actual();
+		theAuthXact->repSize += theRepSize.actual();
+		if (theHttpStatus != RepHdr::sc407_ProxyAuthRequired || theError) {
+			theAuthXact->lifeTime = TheClock - theAuthXact->startTime;
+			theAuthXact->final = true;
+		}
+	}
+
+	if (theOid.aborted())
+		theActualRepType = TheUnknownContentId;
+
+	CltXact::finish(err);
+}
+
+// XXX: the needMore value and the return value are unused
+bool HttpCltXact::controlledPostRead(bool &needMore) {
+	if (theState == stHdrWaiting) {
+		const Error err = getHeader();
+		if (err || (theOid.connect() && theState == stBodyWaiting)) {
+			finish(err);
+			return false;
+		}
+	}
+
+	if (theState == stBodyWaiting) {
+		getBody();
+		return false;
+	}
+
+	return false;
+}
+
+Error HttpCltXact::getHeader() {
+	Assert(theState == stHdrWaiting);
+
+	if (theRepHdr.parse(theConn->theRdBuf.content(), theConn->theRdBuf.contSize())) {
+		Error err = interpretHeader();
+
+		if (theRepHdr.theStatus != RepHdr::sc100_Continue) {
+			// dump reply header after interpretHeader() to dump more oid flags
+			static int respCount = 0;
+			if (!respCount++ || TheOpts.theDumpFlags(dumpRep, dumpHdr))
+				printMsg(theConn->theRdBuf, theRepHdr.theHdrSize);
+
+			if (!err)
+				err = handleAuth();
+			if (!err) {
+				consume(theRepHdr.theHdrSize);
+				if (theHttpStatus == RepHdr::sc407_ProxyAuthRequired)
+					saveRepHeader();
+				newState(stBodyWaiting);
+			}
+		} else {
+			if (!err) {
+				consume(theRepHdr.theHdrSize);
+				Assert(the100ContinueState == csWaiting);
+				the100ContinueState = csDone;
+				theMgr->resumeWriting(this);
+			}
+			theRepHdr.reset();
+			theRepSize.reset();
+			Assert(theState == stHdrWaiting);
+		}
+
+		return err;
+	} else
+	if (expectMore()) {
+		if (theConn->theRdBuf.full())
+			return errHugeHdr;
+		else
+			return 0;
+	} else {
+		const bool readNothing = !theConn->theRdBuf.contSize();
+		// HTTP pconn race condition?
+		const bool race = readNothing && theConn->useCnt() > 1;
+		doRetry = doRetry || race;
+		if (race)
+			return errOther;
+		else
+			return readNothing ? errNoHdrClose : errPrematureEoh;
+	}
+	Should(false); // not reached
+	return errOther;
+}
+
+void HttpCltXact::getBody() {
+	Assert(theState == stBodyWaiting);
+	Assert(theBodyParser);
+
+	parse();
+
+	if (expectMore()) {
+		checkOverflow();
+		return;
+	}
+
+	const Size leftoverSize = unconsumed();
+
+	// no more data, set expected size if it was unknown
+	if (!theRepSize.expected().known())
+		theRepSize.expect(theRepSize.actual() + leftoverSize);
+
+	const bool pgAborted = cfgAbortedReply();
+
+	// make sure we do not leave anything behind and complain if needed
+	if (leftoverSize > 0) {
+		if (pgAborted) {
+			// no need to complain, a Polygraph-initiated abort
+		} else
+		if (theRepSize.actual() + leftoverSize < theRepSize.expected()) {
+			// no need for parser to complain since it is not a content error
+		} else {
+			const ParseBuffer leftovers(theConn->theRdBuf.content(),
+				leftoverSize);
+			theBodyParser->noteLeftovers(leftovers);
+		}
+		consume(leftoverSize);
+	}
+
+	if (theRepSize.expected() == theRepSize.actual()) {
+		finish(0);
+		return;
+	}
+
+	if (theRepSize.expected() < theRepSize.actual()) {
+		finish(errExtraRepData);
+		return;
+	}
+
+	Assert(theRepSize.expected() > theRepSize.actual());
+
+	if (pgAborted) {
+		theConn->lastUse(true);
+		theOid.aborted(true);
+		finish(0); // not an error, configuration told server to abort
+		return;
+	}
+
+	finish(errPrematureEof);
+}
+
+bool HttpCltXact::controlledFill(bool &needMore) {
+	if (!CltXact::controlledFill(needMore))
+		return false;
+
+	if (the100ContinueState != csWaiting && theReqSize.expectToGetMore()) {
+		WrBuf &buf = theConn->theWrBuf;
+		// append request body if needed
+		const char *bodyStart = buf.space();
+		if (theBodyIter && !theBodyIter->pour())
+			return false;
+		theReqSize.got(buf.space() - bodyStart);
+		if (TheOpts.theDumpFlags(dumpReq, dumpBody))
+			printMsg(bodyStart, buf.space() - bodyStart);
+	}
+
+	return true;
+}
+
+// possibly many transactions move on, after a single raw write
+bool HttpCltXact::controlledPostWrite(Size &size, bool &needMore) {
+	CltXact::controlledPostWrite(size, needMore);
+
+	if (!needMore) {
+		Assert(the100ContinueState != csWaiting);
+		if (theOid.foreignUrl())
+			TheEmbedStats.foreignUrlRequested++;
+	} else
+	if (the100ContinueState == csWaiting) {
+		Assert(theState == stSpaceWaiting);
+		// We do not need to write more if we wrote the headers.
+		// HttpCltXact::controlledFill does not fill body in csWaiting.
+		if (theConn->theWrBuf.empty()) { // wrote all headers, now need to wait
+			needMore = false; // but will resumeWriting()
+			newState(stHdrWaiting);
+		}
+	}
+
+	return true;
+}
+
+void HttpCltXact::makeReq(WrBuf &buf) {
+	ofixedstream os(buf.space(), buf.spaceSize());
+
+	if (theState == stConnWaiting) {
+		if (theOid.connect())
+			makeConnectReq(os);
+		else
+			makeExplicitReq(os);
+		newState(stSpaceWaiting);
+	}
+}
+
+// make a CONNECT request
+void HttpCltXact::makeConnectReq(ostream &os) {
+	os << rlpConnect;
+	Oid2UrlHost(theOid, true, os);
+	makeReqVersion(os);
+	makeHopByHopHdrs(os);
+
+	static int reqCount = 0;
+	finishReqHdrs(os, !reqCount++);
+
+	// no body for CONNECT requests
+	theReqOid.type(TheBodilessContentId);
+}
+
+// make a non-CONNECT request
+void HttpCltXact::makeExplicitReq(ostream &os) {
+	Assert(the100ContinueState == csNone);
+	Assert(!theReqContentCfg);
+	Assert(!theBodyIter);
+	// decide whether the request should have a body
+	if (theOid.post() || theOid.put()) {
+		theReqSize.expectedBody(true);
+		theReqContentCfg = theOwner->selectReqContent(theOid, theReqOid);
+		theBodyIter = theReqContentCfg->getBodyIter(theReqOid);
+		theBodyIter->start(&theConn->theWrBuf);
+		theReqBodySize = theBodyIter->contentSize();
+
+		static RndGen rng;
+		if (theReqBodySize > 0 &&
+			((theOwner->cfg()->theReqBodyPauseProb >= 0 &&
+			rng.event(theOwner->cfg()->theReqBodyPauseProb)) ||
+			(theOwner->cfg()->theReqBodyPauseStart >= 0 &&
+			theReqBodySize >= theOwner->cfg()->theReqBodyPauseStart.byte())))
+				the100ContinueState = csWaiting;
+	} else
+		theReqOid.type(TheBodilessContentId);
+
+	// make headers
+	{
+		makeReqMethod(os);
+		makeEndToEndHdrs(os);
+		makeHopByHopHdrs(os);
+		makeCookies(os); // last, to know buffer space left
+		static int reqCount = 0;
+		finishReqHdrs(os, !reqCount++);
+	}
+
+	// where we should abort
+	theAbortSize = theAbortCoord.pos(theReqSize.header(), theReqBodySize);
+}
+
+void HttpCltXact::finishReqHdrs(ostream &os, bool forceDump) {
+	IOBuf &buf = theConn->theWrBuf;
+	const char *reqStart = buf.space();
+	buf.appended((streamoff)os.tellp());
+
+	// give filters a chance, they may add their own headers
+	TheCltDataFilterRegistry().apply(this, buf);
+
+	buf.append("\r\n", Min(buf.spaceSize(), Size(2))); // end-of-header
+
+	Size fullSize = buf.space() - reqStart;
+	theReqSize.header(fullSize);
+	if (theReqBodySize.known())
+		fullSize += theReqBodySize;
+	theReqSize.expect(fullSize);
+	theReqSize.got(buf.space() - reqStart);
+
+	// dump request header
+	if (forceDump || TheOpts.theDumpFlags(dumpReq, dumpHdr))
+		printMsg(reqStart, buf.space() - reqStart);
+}
+
+void HttpCltXact::makeReqMethod(ostream &os) {
+	if (theOid.get())
+		os << rlpGet;
+	else
+	if (theOid.post())
+		os << rlpPost;
+	else
+	if (theOid.head())
+		os << rlpHead;
+	else
+	if (theOid.put())
+		os << rlpPut;
+	else
+		Assert(false);
+}
+
+void HttpCltXact::makeReqVersion(ostream &os) {
+	if (theOwner->httpVersion() <= HttpVersion(1,0))
+		os << rlsHttp1p0;
+	else
+		os << rlsHttp1p1;
+}
+
+void HttpCltXact::makeEndToEndHdrs(ostream &os) {
+
+	// send full URL only if we are talking directly to a proxy
+	if (theOwner->proxy(theOid) && !theConn->tunneling())
+		Oid2Url(theOid, os);
+	else
+		Oid2UrlPath(theOid, os);
+
+	makeReqVersion(os);
+	
+	/* request-header fields */
+
+	os << hfAccept;
+	if (const String *codings = theOwner->cfg()->theAcceptedContentCodings)
+		os << hfpAcceptEncoding << *codings << crlf;
+
+	os << hfpHost;
+	Oid2UrlHost(theOid, false, os);
+	os << crlf;
+
+	if (theOid.imsAny() && olcTimes().lmt() >= 0) {
+		const Time t = theOid.ims200() ?
+			(olcTimes().lmt() - Time::Sec(1)) : 
+			(olcTimes().lmt() + Time::Sec(1));
+		HttpDatePrint(os << hfpIMS, t) << crlf;
+		theReqFlags |= xfValidation;
+	}
+
+	if (theOid.reload())
+		os << hfReload;
+
+	if (theOid.range()) {
+		RangeCfg::RangesInfo res = theOwner->makeRangeSet(os, theOid, *theRepContentCfg);
+		theRangeCount = res.theCount;
+		theRangesSize = res.theTotalSize;
+	}
+
+	/* entity-header fields */
+
+	os << hfpXXact << TheGroupId << ' ' << theId << ' ' << hex << theReqFlags << dec << crlf;
+
+	// send public world info
+	if (!theOid.foreignUrl()) {
+		if (const HostCfg *host = TheHostMap->at(theOid.viserv())) {
+			const PubWorld &pubWorld = *host->thePubWorld;
+			os << hfpXLocWorld << pubWorld.localSlice() << crlf;
+
+			if (const PubWorldSlice *slice = pubWorld.sliceToSync())
+				os << hfpXRemWorld << *slice << crlf;
+		}
+
+		if (theSrvRep)
+			os << hfpXTarget << theSrvRep->addr() << crlf;
+	}
+
+	os << hfpXAbort << theAbortCoord.whether() 
+		<< ' ' << theAbortCoord.where() << crlf;
+
+	// report our readiness to change phase
+	os << hfpXPhaseSyncPos << TheStatPhaseMgr.phaseSyncPos() << crlf;
+
+	if (theReqContentCfg &&
+		theReqContentCfg->calcChecksumNeed(theReqOid))
+		putChecksum(*theReqContentCfg, theReqOid, os);
+
+	if (theBodyIter) {
+		theBodyIter->putHeaders(os);
+		const String &pfx(theReqContentCfg->url_pfx(theReqOid.hash()));
+		const String &ext(theReqContentCfg->url_ext(theReqOid.hash()));
+		if (pfx || ext)
+			os
+				<< hfpContDisposition << '"'
+				<< pfx << theReqOid.name() << ext
+				<< '"' << crlf;
+	}
+
+	Assert(the100ContinueState != csDone);
+	if (the100ContinueState == csWaiting)
+		os << hfExpect100Continue;
+
+	makeOriginAuthHdr(os);
+}
+
+void HttpCltXact::makeHopByHopHdrs(ostream &os) {
+	/* general-header fields */
+	// persistency indication depends on HTTP version
+	if (theOwner->httpVersion() <= HttpVersion(1,0)) {
+		if (theConn->reusable())
+			os << (theOwner->proxy(theOid) ? hfConnAlivePxy : hfConnAliveOrg);
+	} else {
+		if (!theConn->reusable())
+			os << (theOwner->proxy(theOid) ? hfConnClosePxy : hfConnCloseOrg);
+	}
+
+	makeProxyAuthHdr(os);
+}
+
+void HttpCltXact::makeCookies(ostream &os) {
+	HttpCookies *const cookies(theOwner->cookies(theOid));
+
+	if (theOwner->doCookies()) {
+		// configure future response parser to collect new cookies
+		theRepHdr.collectCookies(cookies);
+	}
+
+	// send back all cookies we kept (if any)
+	if (!cookies)
+		return; // but we may collect some from the response
+
+	Assert(theOwner->doCookies());
+
+	for (const HttpCookie *cookie = cookies->first();
+		cookie;
+		cookie = cookies->next()) {
+
+		WrBuf &buf = theConn->theWrBuf;
+		const Size usedSize = (streamoff)os.tellp();
+		const Size headerSize =
+			hfpCookie.len() + cookie->data().len() + 4;
+		const Size spaceRemaining = buf.spaceSize() - usedSize;
+
+		if (headerSize < spaceRemaining)
+			os << hfpCookie << cookie->data() << crlf;
+		else
+		if (ReportError(errCookiesDontFit)) {
+			Comment << "header size: " << headerSize
+				<< "; space left: " << spaceRemaining << endc;
+		}
+	}
+}
+
+void HttpCltXact::makeProxyAuthHdr(ostream &os) {
+	const HttpAuthScheme scheme(theOwner->proxyAuthScheme(theOid));
+	Connection::NtlmAuth &ntlmState(theConn->theProxyNtlmState);
+	makeAuthHdr(hfpProxyAuthorization, scheme, ntlmState, os);
+}
+
+void HttpCltXact::makeOriginAuthHdr(ostream &os) {
+	const HttpAuthScheme scheme(theOwner->originAuthScheme(theOid));
+	Connection::NtlmAuth &ntlmState(theConn->theOriginNtlmState);
+	makeAuthHdr(hfpAuthorization, scheme, ntlmState, os);
+}
+
+void HttpCltXact::makeAuthHdr(const String &header, const HttpAuthScheme scheme, Connection::NtlmAuth &ntlmState, ostream &os) {
+	theOid.authCred(false);
+	switch (ntlmState.state) {
+		case ntlmNone: { // Initiating auth
+			if (scheme == authNtlm) {
+				makeAuthorization(header, scheme, os);
+				NtlmAuthPrintT1(os);
+				os << crlf;
+				ntlmState.state = ntlmSentT1;
+			} else
+			if (scheme == authNegotiate) {
+				makeAuthorization(header, scheme, os);
+				NegoNtlmAuthPrintT1(os, ntlmState.useSpnegoNtlm);
+				os << crlf;
+				ntlmState.state = ntlmSentT1;
+			} else
+			if (scheme == authBasic) {
+				if (theOwner->credentialsFor(theOid, theCred)) {
+					makeAuthorization(header, scheme, os);
+					PrintBase64(os, theCred.image().data(), theCred.image().len());
+					os << crlf;
+				}
+			} else {
+				Should(scheme == authNone);
+				// no header, we have not been asked to authenticate
+			}
+			break;
+		}
+		case ntlmSentT1: {
+			if (theOwner->credentialsFor(theOid, theCred)) {
+				Area aUser = theCred.name();
+				String sUser(aUser.data(), aUser.size());
+				Area aPass = theCred.password();
+				String sPass(aPass.data(), aPass.size());
+				makeAuthorization(header, scheme, os);
+				if (NegoNtlmAuthPrintT3(os,
+					ntlmState.hdrRcvdT2.cstr(),
+					sUser.cstr(),
+					sPass.cstr(),
+					ntlmState.useSpnegoNtlm)) {
+					os << crlf;
+					ntlmState.state = ntlmSentT3;
+				} else {
+					os << crlf;
+					ntlmState.state = ntlmError;
+				}
+			} else {
+				ntlmState.state = ntlmError;
+			}
+			break;
+		}
+		case ntlmDone:
+		case ntlmError: {
+			// do nothing
+			break;
+		}
+		default: {
+			Should(false);
+			ntlmState.state = ntlmError;
+		}
+	}
+}
+
+void HttpCltXact::makeAuthorization(const String &header, const HttpAuthScheme scheme, ostream &os) {
+	os << header;
+	switch (scheme) {
+		case authNtlm:
+			os << "NTLM ";
+			break;
+		case authNegotiate:
+			os << "Negotiate ";
+			break;
+		default:
+			os << "Basic ";
+	}
+}
+
+Error HttpCltXact::interpretHeader() {
+	theOwner->absorbCookies(theOid, theRepHdr.theCookies);
+	theRepSize.header(theRepHdr.theHdrSize);
+
+	// does reply line make sense?
+	if (!theRepHdr.theHttpVersion.known() || theRepHdr.theStatus < 0)
+		return errHttpRLine;
+
+	if (100 <= theRepHdr.theStatus && theRepHdr.theStatus < 200) {
+		if (theRepHdr.theStatus == RepHdr::sc100_Continue) {
+			theContinueMsgTime = TheClock - theStartTime;
+			return the100ContinueState == csWaiting ? 0 : errUnexpected100Continue;
+		} else
+			return errUnsupportedControlMsg;
+	}
+
+	if (theOid.connect() && theRepHdr.theStatus == RepHdr::sc200_OK) {
+		theRepSize.expect(theRepHdr.theHdrSize);
+		theActualRepType = TheBodilessContentId;
+		return 0;
+	}
+
+	if (const Error err = setStatusCode(theRepHdr.theStatus))
+		return err;
+
+	if (theSrvRep) // TODO: why is this done here and not earlier or later?
+		theSrvRep->noteRequest();
+
+	// check content-length, set RepSize if possible
+	if (theRepHdr.expectBody()) {
+		if (theOid.head()) {
+			theActualRepType = TheBodilessContentId;
+			theRepSize.expect(theRepHdr.theHdrSize);
+		} else {
+			if (theRepHdr.theStatus == RepHdr::sc200_OK)
+				theActualRepType = theOid.type();
+			else
+				theActualRepType = TheUnknownContentId;
+			theRepSize.expectedBody(true);
+			if (theRepHdr.theTransferEncoding == MsgHdr::tcChunked) {
+				if (theRepHdr.theContSize >= 0) // MUST ignore
+					ReportError(errChunkedButCLen);
+			} else
+			if (theRepHdr.theContSize >= 0)
+				theRepSize.expect(theRepHdr.theHdrSize + theRepHdr.theContSize);
+			else
+			if (theRepHdr.persistentConnection() &&
+				!theRepHdr.multiRange())
+				return errPersistButNoCLen;
+		}
+	} else {
+		theActualRepType = TheBodilessContentId;
+		if (theRepHdr.theContSize >= 0)
+			return errUnexpectedCLen;
+		theRepSize.expect(theRepHdr.theHdrSize);
+	}
+
+	if (theRepHdr.theStatus == RepHdr::sc206_PartialContent) {
+		if (theRepHdr.theContRangeFirstByte >= 0 &&
+			theRepHdr.theContRangeLastByte >= 0) {
+			if (theRepHdr.theContSize + theRepHdr.theContRangeFirstByte - theRepHdr.theContRangeLastByte != 1)
+				return errPartContBadByteRange;
+			if (theRepHdr.theContRangeInstanceLength >= 0 &&
+				theRepHdr.theContRangeInstanceLength < theRepHdr.theContSize)
+				return errPartContBadInstanceLen;
+		}
+	} else
+	if (theRepHdr.theStatus != RepHdr::sc416_RequestedRangeNotSatisfiable) {
+		if (theRepHdr.theContRangeFirstByte >= 0 ||
+			theRepHdr.theContRangeLastByte >= 0 ||
+			theRepHdr.theContRangeInstanceLength >= 0)
+			return errUnexpectedCRange;
+	}
+
+	// check that we can handle transfer encoding
+	if (theRepHdr.theTransferEncoding == MsgHdr::tcOther)
+		return errUnknownTransferEncoding;
+
+	theOid.repToRedir(theRepHdr.redirect());
+
+	if (!theOid.connect() &&
+		!theRepHdr.polyHeaders() &&
+		theRepHdr.expectPolyHeaders()) {
+		if (const HostCfg *host = TheHostMap->at(theOid.viserv())) {
+			// do not expect Polygraph-specific headers from non-HTTP origins
+			if (host->theProtocol == Agent::pHTTP) {
+				theOid.foreignSrc(true);
+				noteError(errForeignSrc);
+			}
+		}
+	}
+
+	checkAcl();
+
+	// firstHand here means our request reached the server and
+	// server's reply reached us
+	const bool firstHand = id().myMutant(theRepHdr.theXactId);
+
+	theOid.cachable(theRepHdr.isCachable);
+	theOid.hit(!firstHand && theRepHdr.theXactId && theOid.basic());
+
+	if (theOid.hit()) {
+		if (!theOid.offeredHit() && !TheCltOpts.ignoreFalseHits)
+			noteError(errFalseHit);
+
+		if (!theOid.cachable())
+			noteError(errUnchbHit);
+	}
+
+	if (firstHand) { // native miss
+		checkDateSync();
+		firstHandSync();
+	} else
+	if (theRepHdr.theXactId) { // native second-hand response
+		if (theOid.reload())
+			noteError(errReloadHit);
+	} else
+	if (theRepHdr.theDate >= 0 && theRepHdr.theDate < HttpDateAtMost(theStartTime)) {
+		// our response is aged: it was generated before we asked for it
+		if (theOid.reload())
+			noteError(errReloadHit);
+	}
+
+	if (theOid.offeredHit() && !theOid.hit())
+		noteError(errFalseMiss);
+
+	checkFreshness();
+
+	// note if the server will close the connection
+	if (!theRepHdr.persistentConnection()) {
+		theConn->lastUse(true);
+		theMgr->noteLastXaction(this);
+	}
+
+	theBodyParser = selectBodyParser();
+	theRepFlags = theRepHdr.theXactFlags;
+
+	return 0;
+}
+
+void HttpCltXact::noteError(const Error &err) {
+	if (err == errFalseMiss) {
+		if (TheCltOpts.printFalseMisses) {
+			Oid2Url(theOid, cout << "False-Miss: ");
+			cout << endl;
+			printMsg(theConn->theRdBuf, theRepHdr.theHdrSize);
+		}
+		return; // do not report false misses; they may not be errors
+	}
+
+	if (err == errForeignSrc) {
+		if (theOid.foreignUrl())
+			return; // response to a foreign URL may be foreign
+
+		static bool informed = false;
+		if (!informed) {
+			Comment(5) << "fyi: received first foreign response to " <<
+				"Polygraph-specific URL:" << endc;
+			printMsg(theConn->theRdBuf);
+			informed = true;
+		}
+
+		if (theHttpStatus == RepHdr::sc200_OK && theOwner->cfg()->acl())
+			return; // let checkAcl() handle this case
+
+		if (TheOpts.acceptForeignMsgs)
+			return; // the user told us to accept all foreign responses
+	}
+
+	if (!ReportError(err))
+		return;
+
+	/* supply additional information */
+
+	if (err == errStaleHit || err == errReloadHit) {
+		HttpDatePrint(cout << "\trep: ", theRepHdr.theDate) << endl;
+		HttpDatePrint(cout << "\tlmt: ", olcTimes().lmt()) << endl;
+		HttpDatePrint(cout << "\treq: ", theStartTime) << endl;
+		HttpDatePrint(cout << "\tnow: " ) << endl;
+		HttpDatePrint(cout << "\texp: ", olcTimes().exp()) << endl;
+	}
+
+	if (TheOpts.theDumpFlags(dumpErr, dumpAny))
+		printMsg(theConn->theRdBuf);
+}
+
+void HttpCltXact::saveRepHeader() {
+	theConn->theRdBuf.copyContent(theSavedRepHeader, theRepHdr.theHdrSize);
+}
+
+void HttpCltXact::firstHandSync() {
+	// update public world info
+	if (theRepHdr.theRemWorld)
+		updatePubWorld(theRepHdr.theRemWorld);
+
+	if (theSrvRep)
+		theSrvRep->noteFirstHandResponse();
+
+	// sync phases
+	if (theRepHdr.thePhaseSyncPos >= 0 && theRepHdr.theGroupId)
+		TheStatPhaseSync.notePhaseSync(theRepHdr.theGroupId, theRepHdr.thePhaseSyncPos);
+}
+
+Error HttpCltXact::doForbidden() {
+	Error err;
+	if (theOwner->hasCredentials()) {
+		if (theCred.image()) { // authed
+			if (theCred.valid())
+				err = errForbiddenAfterAuth;
+		} else
+		if (theAuthXact) // authing
+			err = errForbiddenDuringAuth;
+		else
+			err = errForbiddenBeforeAuth;
+	} else
+		err = errForbiddenWoutCreds;
+	return err;
+}
+
+Error HttpCltXact::doProxyAuth() {
+	const bool needed(theHttpStatus == RepHdr::sc407_ProxyAuthRequired);
+	Connection::NtlmAuth &ntlmState(theConn->theProxyNtlmState);
+	const Error err(doAuth(true, needed, ntlmState));
+	if (err && ReportError(err)) {
+		Comment << "robot: " << theOwner->host()
+			<< " credentials: " << theCred.image()
+			<< " proxy: " << theOwner->proxy(theOid) << endc;
+	}
+	return err;
+}
+
+Error HttpCltXact::doOriginAuth() {
+	Error err;
+	// do no origin authentication during proxy authentication
+	if (theHttpStatus != RepHdr::sc407_ProxyAuthRequired) {
+		const bool needed(theHttpStatus == RepHdr::sc401_Unauthorized);
+		Connection::NtlmAuth &ntlmState(theConn->theOriginNtlmState);
+		err = doAuth(false, needed, ntlmState);
+		if (err && ReportError(err)) {
+			Comment << "robot: " << theOwner->host()
+				<< " credentials: " << theCred.image()
+				<< " origin: " << Oid2UrlHost(theOid) << endc;
+		}
+	}
+	return err;
+}
+
+// For fresh connection auth scheme is determined by proxy response headers.
+// If NTLM or Negotiate auth is started for a connection it never changes later.
+// If a proxy changes auth scheme, all compound auth xactions that have not yet
+// finished authenticating will fail. Other xactions will not be affected.
+Error HttpCltXact::doAuth(const bool proxyAuth, const bool needed, Connection::NtlmAuth &ntlmState) {
+	Error authError;
+
+	if (needed) { // denied
+		AuthChallenge &challenge(proxyAuth ?
+			theRepHdr.theProxyAuthenticate :
+			theRepHdr.theOriginAuthenticate);
+		if (challenge.scheme == authNone)
+			return proxyAuth ? errProxyAuthHeaders : errOriginAuthHeaders;
+		if (!theOwner->hasCredentials())
+			return proxyAuth ? errProxyAuthWoutCreds : errOriginAuthWoutCreds;
+		switch (ntlmState.state) {
+			case ntlmNone: {
+				if (challenge.scheme == authNtlm) {
+					// sent nothing, proxy should signal disconnect, 
+					// NTLM authentication will resume once we reconnect
+					doRetry = true;
+					authError = startAuth(authNtlm);
+				} else
+				if (challenge.scheme == authNegotiate) {
+					ntlmState.useSpnegoNtlm = isSpnegoNtlm(challenge.params.cstr());
+					if (ntlmState.useSpnegoNtlm && theOwner->cfg()->theSpnegoAuthRatio >= 0) {
+					     static RndGen rng;
+					     ntlmState.useSpnegoNtlm = rng.event(theOwner->cfg()->theSpnegoAuthRatio);
+					}
+					
+					// According to rfc4559 spnego-based negotiate auth is not supposed
+					// to be used by proxies (but can be passed transparently to servers),
+					// so we can't really say whether we need to retry here or what. Need
+					// to test on available clients and proxies.
+					doRetry = ntlmState.useSpnegoNtlm;
+					
+					// sent nothing, in case of NTLMSSP proxy should signal disconnect, 
+					// Negotiate authentication will resume once we reconnect
+					authError = startAuth(authNegotiate);
+				} else
+				if (!theCred.image()) {
+					// sent nothing, Basic auth
+					doRetry = true;
+					authError = startAuth(authBasic);
+				} else
+				if (theCred.valid()) { // sent valid Basic auth
+					authError = proxyAuth ? errProxyAuthAfterAuth : errOriginAuthAfterAuth;
+				}
+				// else auth correctly denied due to invalid credentials
+				break;
+			}
+			case ntlmSentT1: {
+				ntlmState.hdrRcvdT2 = challenge.params;
+				doRetry = true; // continue processing in HopByHops
+				break;
+			}
+			case ntlmSentT3: { // auth failed
+				ntlmState.state = ntlmError;
+				if (!theCred.valid()) // sent invalid NTLM auth
+					break;
+				// fall through
+			}
+			default: { // errors and weird cases
+				authError = proxyAuth ? errProxyAuthAfterAuth : errOriginAuthAfterAuth;
+			}
+		}
+	} else { // allowed
+		switch (ntlmState.state) {
+			case ntlmNone: {
+				// succeeded at Basic Auth
+				if (theCred.image() && !theCred.valid()) { // sent invalid
+					authError = proxyAuth ? errProxyAuthAllowed : errOriginAuthAllowed;
+				}
+				break;
+			}
+			case ntlmSentT3: { // as expected
+				ntlmState.state = ntlmDone;
+				if (!theCred.valid())
+					authError = proxyAuth ? errProxyAuthAllowed : errOriginAuthAllowed;
+				break;
+			}
+			case ntlmDone: {
+				// do nothing, everything is ok
+				break;
+			}
+			default: { // fall though for all weird cases
+				authError = proxyAuth ? errProxyAuthAllowed : errOriginAuthAllowed;
+			}
+		}
+	}
+	return authError;
+}
+
+Error HttpCltXact::startAuth(const HttpAuthScheme scheme) {
+	if (theAuthXact)
+		return errAuthBug;
+
+	theAuthXact = CompoundXactInfo::Create();
+	theAuthXact->startTime = theStartTime;
+	if (theHttpStatus == RepHdr::sc401_Unauthorized)
+		theOwner->noteOriginAuthReq(this, scheme);
+	else
+		theOwner->noteProxyAuthReq(this, scheme);
+	return 0;
+}
+
+Error HttpCltXact::handleAuth() {
+	if (theHttpStatus == RepHdr::sc403_Forbidden)
+		return doForbidden();
+
+	Error err = doProxyAuth();
+	if (!err)
+		err = doOriginAuth();
+	return err;
+}
+
+void HttpCltXact::redirect() {
+	ObjId dest = theRepHdr.theLocn.oid;
+	dest.rediredReq(true);
+	dest.get(true);
+
+	if (!dest.foreignUrl()) {
+		if (!validRelOid(dest)) {
+			ReportError(errRedirLocation);
+			return;
+		}
+
+		if (theRepHdr.theLocn.host) {
+			if (const Error err = setViserv(theRepHdr.theLocn.host, dest)) {
+				noteError(err);
+				return;
+			}
+		} else {
+			dest.viserv(theOid.viserv());
+		}
+	
+		theOwner->selectTarget(dest);
+		dest.repeat(true); // polysrv only redirects to seen URLs
+	} else {
+		dest.type(TheForeignContentId);
+		dest.repeat(false); // XXX: may be a repeat, we do not know
+	}
+	// XXX: hot() is unset
+
+	theOwner->noteRedirect(this, dest);
+}
+
+BodyParser *HttpCltXact::selectBodyParser() {
+	BodyParser *cparser = selectContentParser();
+
+	if (theRepHdr.chunkedEncoding())
+		return ChunkedCodingParser::GetOne(this, cparser);
+	if (theRepHdr.multiRange())
+		return MultiPartParser::GetOne(this, cparser, theRepHdr.theBoundary);
+
+	return cparser;
+}
+
+BodyParser *HttpCltXact::selectContentParser() {
+	// do not parse if we are not going to request embedded objects
+	if (theOwner->cfg()->theEmbedRecurRatio <= 0)
+		return AnyBodyParser::GetOne(this);
+
+	// parse if content is markup
+	if (theRepHdr.markupContent())
+		return selectMarkupBodyParser();
+	
+	// if content is unknown, use URL extension (if any) to guess type
+	if (!theRepHdr.knownContentType() && OidImpliesMarkup(theOid, theRepContentCfg))
+		return selectMarkupBodyParser();
+
+	// parse if domestic content may have embedded tags
+	if (!theOid.foreignUrl() && theRepContentCfg->hasEmbedCont())
+		return selectMarkupBodyParser();
+
+	// do not parse otherwise
+	return AnyBodyParser::GetOne(this);
+}
+
+BodyParser *HttpCltXact::selectMarkupBodyParser() {
+	if (!thePage) {
+		thePage = PageInfo::Create();
+		thePage->start = theStartTime;
+	}
+	if (theOwner->cfg()->followAllUris(theRepHdr))
+			return UriScriptBodyParser::GetOne(this, theOwner->cfg());
+	return MarkupBodyParser::GetOne(this, theOwner->cfg());
+}
+
+const ObjTimes &HttpCltXact::olcTimes() const {
+	// not calculated or not configured, but can be
+	if (theOlcTimes.lmt() < 0 && theRepContentCfg) 
+		theRepContentCfg->calcTimes(theOid, theOlcTimes);
+	return theOlcTimes;
+}
+
+void HttpCltXact::checkAcl() {
+	const AclGroup &acl = theOwner->cfg()->acl();
+	if (!acl) // no access controls configured
+		return;
+
+	if (!acl.needsCheck(theOid.foreignUrl()))
+		return;
+
+	if (RepHdr::PositiveStatusCode(theHttpStatus)) {
+		if (!theOid.foreignUrl() && theOid.foreignSrc())
+			checkAclMatch(acl.rewrite(), "rewritten");
+		else
+			checkAclMatch(acl.allow(), "allowed");
+		return;
+	} 
+
+	if (theHttpStatus == RepHdr::sc407_ProxyAuthRequired) {
+		// do not check acls if we did not authenticate yet but can
+		if (!theCred.image() && theOwner->hasCredentials())
+			return;
+		// do not check acls if we sent invalid credentials
+		if (theCred.image() && !theCred.valid())
+			return;
+	}
+
+	checkAclMatch(acl.deny(), "denied");
+}
+
+void HttpCltXact::checkAclMatch(const RegExGroup *matchedGrp, const char *action) {
+	RegExMatchee m;
+	buildAclMatchee(m);
+	static Array<RegExGroup*> matches;
+	matches.reset();
+	theOwner->cfg()->acl().match(m, matches);
+
+	bool explainMatch = false;
+
+	if (matches.count() == 0)
+		explainMatch = ReportError(errAclNoMatches);
+	else
+	if (matches.count() == 1 && matches.last() != matchedGrp)
+		explainMatch = ReportError(errAclWrongMatch);
+	else
+	if (matches.count() > 1)
+		explainMatch = ReportError(errAclManyMatches);
+
+	if (explainMatch)
+		explainAclMatch(m, action, matches);
+}
+
+void HttpCltXact::buildAclMatchee(RegExMatchee &m) const {
+	// form complete URL
+	static WrBuf buf;
+	buf.reset();
+
+	ofixedstream os(buf.space(), buf.spaceSize()-Size(1));
+	m.urlHost = buf.space() + 0; // URL host alone
+	Oid2UrlHost(theOid, false, os);
+	Should(os << ends);
+	
+	m.urlPath = buf.space() + os.tellp(); // URL path alone
+	Oid2UrlPath(theOid, os);
+	Should(os << ends);
+	
+	m.url = buf.space() + os.tellp(); // complete URL
+	Oid2Url(theOid, os);
+	Should(os << ends);
+
+	buf.appended(Size(os.tellp()));
+	buf.append("", 1); // terminate even if stream is full
+
+	m.userName = theCred.image().cstr();
+	m.memberships = &theOwner->memberships();
+}
+
+void HttpCltXact::explainAclMatch(const RegExMatchee &m, const char *action, const Array<RegExGroup*> &ourMatches) const {
+	ostream &os = Comment(6) << "URL: " << m.url << endl;
+	if (theCred.valid())
+		os << "\tuser: " << theCred.image() << endl;
+	os << "\tmembership maps: " << m.memberships->count() << endl;
+	os << "\tgroups: ";
+	for (int g = 0; g < m.memberships->count(); ++g) {
+		if (g)
+			os << "; ";
+		dumpMatchingGroupNames(os, m.memberships->item(g));
+		if (g >= 10) {
+			os << "; ...";
+			break;
+		}
+	}
+	os << endl;
+	os << "\twas probably " << action
+		<< " but matches " << ourMatches.count() << " rule(s)";
+	for (int i = 0; i < ourMatches.count(); ++i) {
+		os << (i == 0 ? ": " : ", ");
+		os << theOwner->cfg()->acl().ruleName(ourMatches[i]);
+	}
+	os << endc;
+}
+
+void HttpCltXact::dumpMatchingGroupNames(ostream &os, const MembershipMap *map) const {
+	MembershipMap::GroupIterator i = map->groupIterator(theCred);
+	for (int count = 0; i && count <= 5; ++count, ++i) {
+		if (count)
+			os << ", ";
+		os << *i;
+	}
+}
+
+void HttpCltXact::checkFreshness() {
+	// cannot check without the date header
+	if (theRepHdr.theDate < 0)
+		return;
+
+	// skip in-transit modicications to avoid false errors due to racing
+	if (HttpDateAtMost(theStartTime) <= olcTimes().lmt())
+		return;
+
+	// response is stale if it was generated before modification time
+	if (theRepHdr.theDate < olcTimes().lmt())
+		noteError(errStaleHit);
+}
+
+bool HttpCltXact::cfgAbortedReply() const {
+	Assert(theRepSize.expected().known());
+	Size newRepSize = theRepSize.expected();
+
+	if (!theRepHdr.theAbortCoord)
+		return false;
+
+	const Size abSz =
+		theRepHdr.theAbortCoord.pos(theRepHdr.theHdrSize, theRepSize.expected()-theRepHdr.theHdrSize);
+
+	if (abSz < 0)
+		return false;
+
+	/* expecting abort, sooner or later */
+
+	newRepSize = abSz;
+
+	// we may have leftovers if we were parsing aborted content
+	if (abSz <= theRepSize.actual() + unconsumed())
+		return true; // reached abort level
+
+	return false;
+}
+
+Error HttpCltXact::setStatusCode(int aStatus) {
+	theHttpStatus = aStatus;
+	if (theHttpStatus != RepHdr::sc200_OK &&
+		theHttpStatus != RepHdr::sc202_Accepted &&
+		theHttpStatus != RepHdr::sc206_PartialContent &&
+		theHttpStatus != RepHdr::sc304_NotModified &&
+		theHttpStatus != RepHdr::sc401_Unauthorized &&
+		theHttpStatus != RepHdr::sc403_Forbidden &&
+		theHttpStatus != RepHdr::sc407_ProxyAuthRequired &&
+		theHttpStatus != RepHdr::sc416_RequestedRangeNotSatisfiable &&
+		theHttpStatus != RepHdr::sc417_ExpectationFailed &&
+		!theRepHdr.redirect())
+		return errHttpStatusCode;
+	return Error();
+}
+
+void HttpCltXact::checkDateSync() {
+	if (theRepHdr.theDate < 0) {
+		ReportError(errHttpNoDate);
+		return;
+	}
+
+	const Time maxGap = Time::Sec(60);
+
+	// cannot measure drift using replies that took too long
+	if (TheClock - theStartTime >= maxGap/2)
+		return;
+
+	if (theRepHdr.theDate > TheClock.time() + maxGap ||
+		theRepHdr.theDate < TheClock.time() - maxGap) {
+		if (ReportError(errSyncDate)) {
+			HttpDatePrint(Comment << "request generated:  ", theStartTime) << endc;
+			HttpDatePrint(Comment << "response generated: ", theRepHdr.theDate) << endc;
+			HttpDatePrint(Comment << "response received:  ", TheClock.time()) << endc;
+			const Time diff = theRepHdr.theDate - TheClock.time();
+			Comment << "time difference:  " << diff << endc;
+		}
+		return;
+	}
+}
+
+// called by BodyParsers, must not finish()
+void HttpCltXact::noteContent(const ParseBuffer &content) {
+	if (theRepHdr.theChecksum.set())
+		theCheckAlg.update(content.data(), content.size());
+}
+
+// called by BodyParsers, must not finish()
+Error HttpCltXact::noteEmbedded(ReqHdr &hdr) {
+	TheEmbedStats.urlSeen++;
+
+	const Error err = hdr.theUri.oid.foreignUrl() ?
+		handleForeignEmbedOid(hdr) : handleEmbedOid(hdr);
+
+	if (!err) {
+		// "fix" new oid record
+		hdr.theUri.oid.repeat(theOid.repeat());
+		hdr.theUri.oid.hot(theOid.hot());
+		hdr.theUri.oid.ims200(theOid.ims200());
+		hdr.theUri.oid.ims304(theOid.ims304());
+		hdr.theUri.oid.reload(theOid.reload());
+		hdr.theUri.oid.get(true);
+		theOwner->selectScheme(hdr.theUri.oid);
+		theOwner->noteEmbedded(this, hdr.theUri.oid);
+	}
+
+	return err;
+}
+
+// called by BodyParsers, must not finish()
+void HttpCltXact::noteTrailerHeader(const ParseBuffer &hdr) {
+	if (ReportError(errTrailerHeader))
+		printMsg(hdr.data(), hdr.size());
+}
+
+// called by BodyParsers, must not finish()
+void HttpCltXact::noteEndOfTrailer() {
+	Should(!theRepSize.expectingWhatParsed());
+	theRepSize.expectingWhatParsed(true);
+}
+
+// called by BodyParsers, must not finish()
+Error HttpCltXact::noteReplyPart(const RepHdr &hdr) {
+	Error err;
+	++theBodyPartCount;
+	theBodyPartsSize += hdr.theContRangeLastByte - hdr.theContRangeFirstByte + Size(1);
+	Assert(theRangesSize.known() && theRangeCount.known());
+	if (theBodyPartsSize > theRangesSize ||
+		theBodyPartCount > theRangeCount)
+		err = errPartContBadCountOrSize;
+	return err;
+}
+
+Error HttpCltXact::handleEmbedOid(ReqHdr &hdr) {
+	Error err;
+	if (validRelOid(hdr.theUri.oid)) {
+		if (hdr.theUri.host)
+			err = setViserv(hdr.theUri.host, hdr.theUri.oid);
+		else
+			hdr.theUri.oid.viserv(theOid.viserv()); // relative URL
+
+		if (!err)
+			theOwner->selectTarget(hdr.theUri.oid);
+	} else {
+		err = errBadEmbedUri;
+	}
+
+	return err;
+}
+
+Error HttpCltXact::handleForeignEmbedOid(ReqHdr &hdr) {
+	Error err;
+	//if (strncmp(hdr.theUri.pathBuf, "/cgi-bin/cntmgr.pl", 18) == 0)
+	//	TheEmbedStats.scriptMgrUrlSeen++;
+	
+	if (!hdr.theUri.host) {
+		// must set host name for Client to know where to send requests
+		hdr.theUri.oid.viserv(theOid.viserv());
+		char buf[4*1024];
+		ofixedstream os(buf, sizeof(buf)-1);
+		os << "http://";
+		Oid2UrlHost(theOid, false, os);
+		os << hdr.theUri.oid.foreignUrl() << ends;
+		buf[sizeof(buf)-1] = '\0';
+		hdr.theUri.oid.foreignUrl(buf);
+	}
+
+	//if (TheEmbedStats.scriptUrlSeen % 1000 == 0) {
+	//	(clog << here << "tag url: ").write(hdr.theUri.pathBuf, hdr.theUri.pathLen);
+	//	clog << endl;
+	//}
+
+	// hdr.theUri.oid is already set
+	return err;
+}
+
+Error HttpCltXact::setViserv(const NetAddr &name, ObjId &oid) const {
+	// XXX: merge with SrvXact::setViserv() ?
+	int viserv = -1;
+	if (!TheHostMap->find(name, viserv))
+		return errForeignHostName;
+
+	if (!theOid.foreignUrl() &&
+		viserv != theOid.viserv())
+		return errSrvRedirect; // disallow for now
+
+	oid.viserv(viserv);
+	return Error();
+}
+
+bool HttpCltXact::askedPeer() const {
+	return thePeerState != peerUnknown;
+}
+
+bool HttpCltXact::usePeer() const {
+	return thePeerState == peerSome;
+}
+
+void HttpCltXact::usePeer(bool doUse) {
+	thePeerState = doUse ? peerSome : peerNone;
+}
+
+int HttpCltXact::cookiesSent() const {
+	int n(0);
+	if (theOwner->doCookies())
+		if (const HttpCookies *cookies = theOwner->cookies(theOid))
+			n = cookies->count();
+	return n;
+}
+
+int HttpCltXact::cookiesRecv() const {
+	return theRepHdr.theCookieCount;
+}
diff -ruBEN polygraph-4.3.1/src/client/HttpCltXact.h polygraph-4.3.1-mm/src/client/HttpCltXact.h
--- polygraph-4.3.1/src/client/HttpCltXact.h	2010-12-22 23:42:25.000000000 +0000
+++ polygraph-4.3.1-mm/src/client/HttpCltXact.h	2011-03-04 23:15:41.000000000 +0000
@@ -79,7 +79,7 @@
 		void makeCookies(ostream &os);
 		void makeProxyAuthHdr(ostream &os);
 		void makeOriginAuthHdr(ostream &os);
-		void makeAuthHdr(const String &header, const HttpAuthScheme scheme, Connection::NtlmAuth &ntlmState, ostream &os);
+		void makeAuthHdr(const bool proxyAuth, const String &header, const HttpAuthScheme scheme, Connection::NtlmAuth &ntlmState, ostream &os);
 		void makeAuthorization(const String &header, const HttpAuthScheme scheme, ostream &os);
 
 		void firstHandSync();
diff -ruBEN polygraph-4.3.1/src/client/KerberosAuth.cc polygraph-4.3.1-mm/src/client/KerberosAuth.cc
--- polygraph-4.3.1/src/client/KerberosAuth.cc	1970-01-01 01:00:00.000000000 +0100
+++ polygraph-4.3.1-mm/src/client/KerberosAuth.cc	2011-03-04 23:15:41.000000000 +0000
@@ -0,0 +1,438 @@
+/*
+*/
+
+#include "base/polygraph.h"
+#include "runtime/LogComment.h"
+#include "runtime/NtlmAuthState.h"
+#include "xstd/InAddress.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "client/KerberosAuth.h"
+#include "xstd/gadgets.h"
+
+#if defined(HAVE_KRB5) && defined(HAVE_GSSAPI)
+
+#include "client/GssKrbCtx.h"
+
+#ifdef HAVE_ET_COM_ERR_H
+#include <et/com_err.h>
+#elif defined(HAVE_COM_ERR_H)
+#include <com_err.h>
+#else
+#define error_message(code) krb5_get_err_text(theKrbCtx, code)
+#endif
+
+#ifndef gss_mech_spnego
+static gss_OID_desc _gss_mech_spnego =
+{6, (void *) "\x2b\x06\x01\x05\x05\x02"};
+gss_OID gss_mech_spnego = &_gss_mech_spnego;
+#endif
+
+int GssKrbXact::pton(const char *p) {
+        if (*p == '[') {
+		struct in6_addr inp;
+                //
+                // XXX ugly memory copy to remove [ ]
+                //
+                char copy[64];
+                strncpy(copy, p+1, 64);
+                strtok(copy, "]");
+                return inet_pton(AF_INET6, copy, &inp);
+        } else {
+		struct  in_addr inp;
+                return inet_pton(AF_INET, p, &inp);
+        }
+}
+
+static GssKrbXact gssEnvironment;
+static GssKrbXact::KrbEnv krbEnvironment;
+
+//
+// Setup Kerberos context for each Kerberos configuration file
+//
+GssKrbXact::KrbEnv::KrbEnv() {
+  krbUserp = NULL;
+  krbPasswdp = NULL;
+  krbServerName = NULL;
+  krbCfgPath = NULL;
+  krbSPN = NULL;
+  clearKrbCc = false;
+  theKrbCtx = NULL;
+  theKrbClt = NULL ;
+  theKrbCc = NULL;
+  theKrbErrorCode = 0;
+  theKrbCtxStruct = NULL;
+}
+
+GssKrbXact::KrbEnv::~KrbEnv() {
+  krbCleanup();
+}
+
+NtlmAuthState GssKrbXact::KrbEnv::krbSetup()
+{
+  struct krbCtxStruct *kc;
+  static krb5_get_init_creds_opt options;
+  krb5_creds theKrbMemoryCreds;
+  krb5_deltat theKrbDeltaTime = 0;
+  String theKrbMaxTinme;
+  String theKrbMemoryCache;
+  String theKrbHostName;
+  String theKrbTgt;
+
+  if (!krbServerName || !krbUserp || !krbPasswdp )
+        return kerberosError;
+
+  memset(&theKrbCreds, 0, sizeof(theKrbCreds));
+
+  kc = theKrbCtxStruct; 
+  while (kc) {
+	if (!kc->theKrbCfgPath.cmp(krbCfgPath)) {
+  		setenv("KRB5_CONFIG", krbCfgPath.cstr(), 1);
+		theKrbCtx = kc->theKrbCtx;
+		break;
+        }
+	kc = kc->theNextKrbCtx;
+  }
+  if (!kc) { 
+	kc = new krbCtxStruct;
+	kc->theKrbCfgPath = krbCfgPath;
+	
+	setenv("KRB5_CONFIG", krbCfgPath.cstr(), 1);
+	theKrbErrorCode = krb5_init_context(&kc->theKrbCtx);
+	if (krbCheckError("krb5_init_context()"))
+		return kerberosError;
+
+	// Setup ticket options - only once required
+	if (!theKrbCtxStruct) {
+		// Get a ticket valid for 7 days if kdc allows ir 
+		theKrbMaxTinme = "7d";
+		krb5_get_init_creds_opt_init(&options);
+		theKrbErrorCode = krb5_string_to_deltat((char *)theKrbMaxTinme.cstr(), &theKrbDeltaTime);
+		if (krbCheckError("krb5_string_to_deltat()"))
+			return kerberosError;
+		krb5_get_init_creds_opt_set_tkt_life(&options,theKrbDeltaTime);
+	}
+
+	kc->theNextKrbCtx = theKrbCtxStruct;
+	theKrbCtxStruct = kc;
+	theKrbCtx = kc->theKrbCtx;
+  }
+
+  theKrbHostName = krbServerName(0,krbServerName.find(':'));
+
+  theKrbMemoryCache = "MEMORY:" + krbCfgPath + "_" + krbUserp;
+  setenv("KRB5CCNAME", theKrbMemoryCache.cstr(), 1);
+
+  theKrbErrorCode = krb5_cc_resolve(theKrbCtx, theKrbMemoryCache.cstr(), &theKrbCc);
+  if (krbCheckError("krb5_cc_resolve()"))
+        return krbCleanup();
+//
+// Check if cache has already valid credentails to avoid delay through
+// krb5_get_init_creds_password call
+//
+
+  memset(&theKrbMemoryCreds, 0, sizeof(theKrbMemoryCreds));
+
+  theKrbTgt = krbSPN;
+  if (theKrbTgt.len() <= 0) {
+	if (pton(theKrbHostName.cstr())) {
+		Comment << "Warning hostname " << theKrbHostName << " is in IPV4/IPV6 address form" << endc;
+	}
+	theKrbTgt = "HTTP/" + theKrbHostName;
+  }
+  theKrbErrorCode = krb5_parse_name(theKrbCtx, theKrbTgt.cstr(), &theKrbMemoryCreds.server);
+  if (krbCheckError("krb5_parse_name(theKrbTgt)"))
+        return krbCleanup();
+
+  theKrbErrorCode = krb5_parse_name(theKrbCtx, krbUserp.cstr(), &theKrbMemoryCreds.client);
+  if (krbCheckError("krb5_parse_name(userp)"))
+        return krbCleanup();
+
+  theKrbErrorCode = krb5_cc_retrieve_cred(theKrbCtx, theKrbCc, KRB5_TC_MATCH_SRV_NAMEONLY,
+					&theKrbMemoryCreds, &theKrbCreds);
+  krb5_free_cred_contents(theKrbCtx, &theKrbMemoryCreds);
+
+//
+// get new credentials if not in cache or (nearly) expired 
+//
+  if (clearKrbCc || theKrbErrorCode || theKrbCreds.times.endtime - time(0) < 300 ) {
+  	krb5_free_cred_contents(theKrbCtx, &theKrbCreds);
+
+        theKrbErrorCode = krb5_parse_name(theKrbCtx, krbUserp.cstr(), &theKrbClt);
+	if (krbCheckError("krb5_parse_name(userp(2))"))
+        	return krbCleanup();
+
+        theKrbErrorCode = krb5_get_init_creds_password(theKrbCtx, &theKrbCreds, theKrbClt,
+                                                (char *)krbPasswdp.cstr(), NULL, 0, 0, NULL, &options);
+	if (krbCheckError("krb5_get_init_creds_password(" + krbUserp + ")"))
+        	return krbCleanup();
+
+	theKrbErrorCode = krb5_cc_initialize(theKrbCtx, theKrbCc, theKrbClt);
+	if (krbCheckError("krb5_cc_initialize()"))
+		return krbCleanup();
+
+	// store credentials in memory cache for later re-use
+	theKrbErrorCode = krb5_cc_store_cred(theKrbCtx, theKrbCc, &theKrbCreds);
+	if (krbCheckError("krb5_cc_store_cred()"))
+		return krbCleanup();
+
+  }
+
+  krbCleanup();
+  return kerberosDone;
+}
+
+int GssKrbXact::KrbEnv::krbCheckError(String theKrbFunctionName) {
+  if (theKrbErrorCode) {
+// Does not work - error_message is defined in com_err but can not be found
+//        Comment << function << "failed with error: " << error_message(theKrbErrorCode) << endc;
+	Comment << theKrbFunctionName << "failed with error: " << theKrbErrorCode << endc;
+	return (1);
+  }
+  return (0);
+}
+
+NtlmAuthState GssKrbXact::KrbEnv::krbCleanup()
+{
+  if (theKrbCtx) {
+	if (theKrbClt) {
+		krb5_free_principal(theKrbCtx, theKrbClt);
+		theKrbClt = NULL;
+	}
+	if (theKrbCc) {
+  		krb5_cc_close(theKrbCtx, theKrbCc);
+		theKrbCc = NULL;
+	}
+  	krb5_free_cred_contents(theKrbCtx, &theKrbCreds);
+  }
+  return kerberosError;
+}
+
+//
+// Setup GSS context
+//
+
+GssKrbXact::GssKrbXact() {
+  theGssContext = GSS_C_NO_CONTEXT;
+  gssServerName = GSS_C_NO_NAME;
+}
+
+void GssKrbXact::gssCleanup() {
+
+  if (theGssContext != GSS_C_NO_CONTEXT)
+	gss_delete_sec_context(&gssMinorStatus, &theGssContext, NULL);
+  if(gssServerName != GSS_C_NO_NAME)
+	gss_release_name(&gssMinorStatus, &gssServerName);
+
+  theGssContext = GSS_C_NO_CONTEXT;
+  gssServerName = GSS_C_NO_NAME;
+}
+
+int GssKrbXact::checkGssError(String gssFunctionName)
+{
+    if (GSS_ERROR(gssMajorStatus)) {
+        OM_uint32 gssMaj, gssMin;
+        OM_uint32 gssMsgCtx = 0;
+        gss_buffer_desc gssStatus;
+        String gssErrorString = NULL;
+	char *gssMsg;
+
+        gssMsgCtx = 0;
+	do { /* convert major status code (GSS-API error) to text */
+            gssMaj = gss_display_status(&gssMin, gssMajorStatus,
+                GSS_C_GSS_CODE, GSS_C_NULL_OID, &gssMsgCtx, &gssStatus);
+            if (gssMaj == GSS_S_COMPLETE && gssStatus.length > 0) {
+		gssMsg=(char *)malloc(gssStatus.length+1);
+		memcpy(gssMsg,gssStatus.value,gssStatus.length);
+		gssMsg[gssStatus.length]='\0';
+                gssErrorString = gssErrorString + gssMsg;
+		free(gssMsg);
+	    } else {
+                gssMsgCtx = 0; 
+            }
+            gss_release_buffer(&gssMin, &gssStatus);
+        } while (gssMsgCtx);
+
+        gssErrorString = gssErrorString + ". ";
+        gssMsgCtx = 0;
+        do { /* convert minor status code (underlying routine error) to text */
+            gssMaj = gss_display_status(&gssMin, gssMinorStatus,
+                GSS_C_MECH_CODE, GSS_C_NULL_OID, &gssMsgCtx, &gssStatus);
+            if (gssMaj == GSS_S_COMPLETE && gssStatus.length > 0) {
+                gssMsg=(char *)malloc(gssStatus.length+1);
+                memcpy(gssMsg,gssStatus.value,gssStatus.length);
+		gssMsg[gssStatus.length]='\0';
+                gssErrorString = gssErrorString + gssMsg;
+                free(gssMsg);
+	    } else {
+                gssMsgCtx = 0; 
+            }
+            gss_release_buffer(&gssMin, &gssStatus);
+        } while (gssMsgCtx);
+
+        Comment << gssFunctionName << " failed with error: " << gssErrorString << endc;
+        return (1);
+    }
+    return (0);
+}
+
+//
+// Do the real work
+//
+
+NtlmAuthState GssKrbXact::NegoKerberosAuthPrint(ostream & os, String gssBase64Token)
+{
+  gss_buffer_desc gssInputToken;
+  gss_buffer_desc gssOutputToken;
+
+  size_t size;
+  unsigned char gssBuf[8096]; /* enough, unless the authorisation data is very
+                                  long */
+  gssInputToken.length = 0;
+  gssInputToken.value = NULL;
+  gssOutputToken.length = 0;
+  gssOutputToken.value = NULL;
+
+  if (gssBase64Token) {
+	const char * header = gssBase64Token.cstr();
+
+	/* skip initial whitespaces */
+	while(*header && isspace((unsigned char)*header))
+		header++;
+
+	if (!*header)
+        	return kerberosError;
+
+	size = DecodeBase64(header, strlen(header), (char*)gssBuf, sizeof(gssBuf)/sizeof(gssBuf[0]));
+
+	gssInputToken.length = size;
+	memcpy(gssInputToken.value,gssBuf,size);
+  }
+
+  gssEnvironment.gssMajorStatus = gss_init_sec_context(&gssEnvironment.gssMinorStatus,
+        GSS_C_NO_CREDENTIAL, &gssEnvironment.theGssContext, gssEnvironment.gssServerName,
+        gss_mech_spnego,
+        0,
+        0,
+        GSS_C_NO_CHANNEL_BINDINGS,
+        &gssInputToken, NULL, &gssOutputToken, NULL, NULL);
+
+  if (gssEnvironment.checkGssError("gss_init_sec_context(" + krbEnvironment.krbUserp + ")"))
+        return kerberosError;
+
+  if (gssOutputToken.length > sizeof(gssBuf)) {
+        Comment << "Token length " << gssOutputToken.length << " > buffer length " << sizeof(gssBuf) <<endc;
+        return kerberosError;
+  }
+
+  memcpy((void *)gssBuf,(const void *)gssOutputToken.value,(size_t)gssOutputToken.length);
+
+  PrintBase64(os, (const char *)gssBuf, gssOutputToken.length);
+  gss_release_buffer(&gssMinorStatus, &gssInputToken);
+  gss_release_buffer(&gssMinorStatus, &gssOutputToken);
+
+  if (gssEnvironment.gssMajorStatus == GSS_S_CONTINUE_NEEDED) {
+//        If mutual authentication is wished use value from Proxy-Authentication-Info header
+//        from a 200 OK.
+//        Comment << "NegoKerberosAuthPrintT3 warning: CONTINUE NEEDED" << endc;
+        return kerberosSentT1;
+  }
+
+  return kerberosDone;
+}
+
+//
+// Do initial ticket creation
+//
+
+NtlmAuthState CltGssXact::NegoKerberosAuthPrintT1(ostream & os, String userp,
+			String passwdp, String server, String krbCfgPath, bool clearKrbCc,
+			String krbSPN)
+{
+  gss_buffer_desc gssService;
+  String theGssHostName; 
+  String theGssServiceName; 
+  
+  gssService.length = 0;
+  gssService.value = NULL;
+
+  // Initialise kerberos
+  krbEnvironment.krbUserp = userp; 
+  krbEnvironment.krbPasswdp = passwdp; 
+  krbEnvironment.krbServerName = server; 
+  krbEnvironment.krbCfgPath = krbCfgPath; 
+  krbEnvironment.krbSPN = krbSPN; 
+  krbEnvironment.clearKrbCc = clearKrbCc; 
+
+  if (krbEnvironment.krbSetup() == kerberosError)
+	return kerberosError;
+
+  // Cleanup old values
+  gssEnvironment.gssCleanup();
+
+  // Create GSS Service name 
+  theGssHostName = server(0,server.find(':'));
+
+  if (krbSPN.len() > 0) {
+	gssService.value = malloc(krbSPN.len());
+	memcpy(gssService.value,krbSPN.cstr(),krbSPN.len());
+	gssService.length = krbSPN.len();
+
+	// Import GSS Service
+	gssEnvironment.gssMajorStatus = gss_import_name(&gssEnvironment.gssMinorStatus, 
+					&gssService, (gss_OID) GSS_C_NULL_OID, &gssEnvironment.gssServerName);
+  } else {
+	theGssServiceName = "HTTP@" + theGssHostName;
+	gssService.value = malloc(theGssServiceName.len());
+	memcpy(gssService.value,theGssServiceName.cstr(),theGssServiceName.len());
+	gssService.length = theGssServiceName.len();
+
+	// Import GSS Service
+	gssEnvironment.gssMajorStatus = gss_import_name(&gssEnvironment.gssMinorStatus, 
+					&gssService, gss_nt_service_name, &gssEnvironment.gssServerName);
+  }
+
+  gss_release_buffer(&gssEnvironment.gssMinorStatus, &gssService);
+
+  if (gssEnvironment.checkGssError("gss_import_name(" + theGssServiceName + ")")) {
+	gssEnvironment.gssCleanup();
+	return kerberosError;
+  }
+
+  return gssEnvironment.NegoKerberosAuthPrint(os, NULL);
+
+}
+
+//
+// Process reply and create new response
+//
+
+NtlmAuthState CltGssXact::NegoKerberosAuthPrintT3(ostream & os, String gssBase64Token)
+{
+  return gssEnvironment.NegoKerberosAuthPrint(os, gssBase64Token);
+}
+
+#else // No Kerberos support
+
+NtlmAuthState CltGssXact::NegoKerberosAuthPrintT1(ostream & os, String userp, String passwdp,
+							String server, String path, bool clear)
+{
+        Assert(false);
+        return kerberosError;
+}
+
+NtlmAuthState CltGssXact::NegoKerberosAuthPrintT3(ostream & os, String base64Token)
+{
+        Assert(false);
+        return kerberosError;
+}
+#endif
diff -ruBEN polygraph-4.3.1/src/client/KerberosAuth.h polygraph-4.3.1-mm/src/client/KerberosAuth.h
--- polygraph-4.3.1/src/client/KerberosAuth.h	1970-01-01 01:00:00.000000000 +0100
+++ polygraph-4.3.1-mm/src/client/KerberosAuth.h	2011-03-04 23:15:41.000000000 +0000
@@ -0,0 +1,13 @@
+#ifndef __HTTP_KERBEROS_AUTH_H
+#define __HTTP_KERBEROS_AUTH_H
+
+class CltGssXact {
+        public:
+                static NtlmAuthState NegoKerberosAuthPrintT1(ostream &os, String userp, String passwdp,
+								String server, String kerberosConfigPath,
+								bool clearKerberosCache, String kerberosSPN);
+                static NtlmAuthState NegoKerberosAuthPrintT3(ostream &os, String base64Token);
+
+};
+
+#endif
diff -ruBEN polygraph-4.3.1/src/client/Makefile.am polygraph-4.3.1-mm/src/client/Makefile.am
--- polygraph-4.3.1/src/client/Makefile.am	2010-12-10 18:25:55.000000000 +0000
+++ polygraph-4.3.1-mm/src/client/Makefile.am	2011-03-04 23:15:41.000000000 +0000
@@ -93,6 +93,9 @@
 	SpnegoCodec.cc \
 	NtlmAuth.h \
 	NtlmAuth.cc \
+	GssKrbCtx.h \
+	KerberosAuth.h \
+	KerberosAuth.cc \
 	RangeCfg.h \
 	RangeCfg.cc \
 	SingleRangeCfg.h \
diff -ruBEN polygraph-4.3.1/src/pgl/RobotSym.cc polygraph-4.3.1-mm/src/pgl/RobotSym.cc
--- polygraph-4.3.1/src/pgl/RobotSym.cc	2010-12-10 18:25:55.000000000 +0000
+++ polygraph-4.3.1-mm/src/pgl/RobotSym.cc	2011-03-04 23:15:41.000000000 +0000
@@ -51,6 +51,11 @@
 static String strSocksProxies = "socks_proxies";
 static String strRecurrence = "recurrence";
 static String strSpnegoAuthRatio = "spnego_auth_ratio";
+static String strKerberosAuth = "kerberos_auth";
+static String strKerberosClearCache = "kerberos_clear_cache";
+static String strKerberosConfigPath = "kerberos_config_path";
+static String strKerberosProxySPN = "kerberos_proxy_spn";
+static String strKerberosServerSPN = "kerberos_server_spn";
 static String strReq_inter_arrival = "req_inter_arrival";
 static String strReq_methods = "req_methods";
 static String strContainerTags = "container_tags";
@@ -89,6 +94,11 @@
 	theRec->bAdd(strTime_distr, strReq_inter_arrival, 0);
 	theRec->bAdd(NumSym::TheType, strRecurrence, 0);
 	theRec->bAdd(NumSym::TheType, strSpnegoAuthRatio, 0);
+	theRec->bAdd(BoolSym::TheType, strKerberosAuth, 0);
+	theRec->bAdd(BoolSym::TheType, strKerberosClearCache, 0);
+	theRec->bAdd(StringSym::TheType, strKerberosConfigPath, 0);
+	theRec->bAdd(StringSym::TheType, strKerberosProxySPN, 0);
+	theRec->bAdd(StringSym::TheType, strKerberosServerSPN, 0);
 	theRec->bAdd(NumSym::TheType, strEmbed_recur, 0);
 	theRec->bAdd(strStringArr, strInterests, 0);
 	theRec->bAdd(strStringArr, strReq_types, 0);
@@ -179,6 +189,27 @@
 bool RobotSym::spnegoRatio(double &ratio) const {
 	return getDouble(strSpnegoAuthRatio, ratio);
 }
+
+bool RobotSym::kerberos(bool &set) const {
+	return getBool(strKerberosAuth, set);
+}
+
+bool RobotSym::kerberosClearCache(bool &set) const {
+	return getBool(strKerberosClearCache, set);
+}
+
+String RobotSym::kerberosConfigPath() const {
+        return getString(strKerberosConfigPath);
+}
+
+String RobotSym::kerberosProxySPN() const {
+        return getString(strKerberosProxySPN);
+}
+
+String RobotSym::kerberosServerSPN() const {
+        return getString(strKerberosServerSPN);
+}
+
 bool RobotSym::embedRecurRatio(double &ratio) const {
 	return getDouble(strEmbed_recur, ratio);
 }
diff -ruBEN polygraph-4.3.1/src/pgl/RobotSym.cc.orig polygraph-4.3.1-mm/src/pgl/RobotSym.cc.orig
--- polygraph-4.3.1/src/pgl/RobotSym.cc.orig	1970-01-01 01:00:00.000000000 +0100
+++ polygraph-4.3.1-mm/src/pgl/RobotSym.cc.orig	2010-12-10 18:25:55.000000000 +0000
@@ -0,0 +1,377 @@
+
+/* Web Polygraph       http://www.web-polygraph.org/
+ * (C) 2003-2006 The Measurement Factory
+ * Licensed under the Apache License, Version 2.0 */
+
+#include "pgl/pgl.h"
+
+#include "xstd/String.h"
+#include "xstd/rndDistrs.h"
+#include "xstd/TblDistr.h"
+#include "base/RndPermut.h"
+#include "pgl/PglBoolSym.h"
+#include "pgl/PglIntSym.h"
+#include "pgl/PglNumSym.h"
+#include "pgl/PglRateSym.h"
+#include "pgl/PglRec.h"
+#include "pgl/PglStringSym.h"
+#include "pgl/PglNetAddrSym.h"
+#include "pgl/PglArraySym.h"
+#include "pgl/PglSizeSym.h"
+#include "pgl/AclSym.h"
+#include "pgl/ClientBehaviorSym.h"
+#include "pgl/ContentSym.h"
+#include "pgl/DnsResolverSym.h"
+#include "pgl/SessionSym.h"
+#include "pgl/RangeSym.h"
+#include "pgl/RobotSym.h"
+
+
+const String RobotSym::TheType = "Robot";
+
+static String strAcl = "acl";
+static String strAddrArr = "addr[]";
+static String strAuth_error = "auth_error";
+static String strCredentials = "credentials";
+static String strDns_resolver = "dns_resolver";
+static String strEmbed_recur = "embed_recur";
+static String strForeign_trace = "foreign_trace";
+static String strRaw_uri_thrower = "raw_uri_thrower";
+static String strIcp_port = "icp_port";
+static String strInterests = "interests";
+static String strMinimize_new_conn = "minimize_new_conn";
+static String strOpen_conn_lmt = "open_conn_lmt";
+static String strOrigins = "origins";
+static String strPeer_http = "peer_http";
+static String strPeer_icp = "peer_icp";
+static String strPrivate_cache_cap = "private_cache_cap";
+static String strProxies = "proxies";
+static String strFtpProxies = "ftp_proxies";
+static String strHttpProxies = "http_proxies";
+static String strSocksProxies = "socks_proxies";
+static String strRecurrence = "recurrence";
+static String strSpnegoAuthRatio = "spnego_auth_ratio";
+static String strReq_inter_arrival = "req_inter_arrival";
+static String strReq_methods = "req_methods";
+static String strContainerTags = "container_tags";
+static String strReq_rate = "req_rate";
+static String strReq_types = "req_types";
+static String strSession = "session";
+static String strStringArr = "string[]";
+static String strTime_distr = "time_distr";
+static String strFloat_distr = "float_distr";
+static String strSize_distr = "size_distr";
+static String strUnique_urls = "unique_urls";
+static String strWait_xact_lmt = "wait_xact_lmt";
+static String strPipeline_depth = "pipeline_depth";
+static String strCookies_keep_lmt = "cookies_keep_lmt";
+static String strAcceptContentEncodings = "accept_content_encodings";
+static String strRanges = "ranges";
+static String strRangeArr = "Range[]";
+static String strReqBodyPauseProb = "req_body_pause_prob";
+static String strReqBodyPauseStart = "req_body_pause_start";
+static String strReqBodyRecurrence = "req_body_recurrence";
+static String strContentArr = "Content[]";
+static String strPostContents = "post_contents";
+static String strPutContents = "put_contents";
+static String strUploadContents = "upload_contents";
+static String strPassiveFtp = "passive_ftp";
+static String strSocksProb = "socks_prob";
+static String strSocksChainingProb = "socks_chaining_prob";
+
+RobotSym::RobotSym(const String &aType): AgentSym(aType) {
+	theRec->bAdd(strAddrArr, strProxies, 0);
+	theRec->bAdd(strAddrArr, strFtpProxies, 0);
+	theRec->bAdd(strAddrArr, strHttpProxies, 0);
+	theRec->bAdd(strAddrArr, strSocksProxies, 0);
+	theRec->bAdd(strAddrArr, strOrigins, 0);
+	theRec->bAdd(RateSym::TheType, strReq_rate, 0);
+	theRec->bAdd(strTime_distr, strReq_inter_arrival, 0);
+	theRec->bAdd(NumSym::TheType, strRecurrence, 0);
+	theRec->bAdd(NumSym::TheType, strSpnegoAuthRatio, 0);
+	theRec->bAdd(NumSym::TheType, strEmbed_recur, 0);
+	theRec->bAdd(strStringArr, strInterests, 0);
+	theRec->bAdd(strStringArr, strReq_types, 0);
+	theRec->bAdd(strStringArr, strReq_methods, 0);
+	theRec->bAdd(strStringArr, strContainerTags, 0);
+	theRec->bAdd(strStringArr, strAcceptContentEncodings, 0);
+	theRec->bAdd(IntSym::TheType, strPrivate_cache_cap, 0);
+	theRec->bAdd(BoolSym::TheType, strUnique_urls, 0);
+	theRec->bAdd(IntSym::TheType, strOpen_conn_lmt, 0);
+	theRec->bAdd(IntSym::TheType, strWait_xact_lmt, 0);
+	theRec->bAdd(NumSym::TheType, strMinimize_new_conn, 0);
+	theRec->bAdd(strStringArr, strCredentials, 0);
+	theRec->bAdd(NumSym::TheType, strAuth_error, 0);
+	theRec->bAdd(AclSym::TheType, strAcl, new AclSym);
+	theRec->bAdd(SessionSym::TheType, strSession, new SessionSym);
+	theRec->bAdd(IntSym::TheType, strIcp_port, 0);
+	theRec->bAdd(NetAddrSym::TheType, strPeer_http, 0);
+	theRec->bAdd(NetAddrSym::TheType, strPeer_icp, 0);
+	theRec->bAdd(DnsResolverSym::TheType, strDns_resolver, new DnsResolverSym);
+	theRec->bAdd(StringSym::TheType, strForeign_trace, 0);
+	theRec->bAdd(StringSym::TheType, strRaw_uri_thrower, 0);
+	theRec->bAdd(strFloat_distr, strPipeline_depth, 0);
+	theRec->bAdd(strFloat_distr, strCookies_keep_lmt, 0);
+	theRec->bAdd(strRangeArr, strRanges, 0);
+	theRec->bAdd(NumSym::TheType, strReqBodyPauseProb, 0);
+	theRec->bAdd(SizeSym::TheType, strReqBodyPauseStart, 0);
+	theRec->bAdd(NumSym::TheType, strReqBodyRecurrence, 0);
+	theRec->bAdd(strContentArr, strPostContents, 0);
+	theRec->bAdd(strContentArr, strPutContents, 0);
+	theRec->bAdd(strContentArr, strUploadContents, 0);
+	theRec->bAdd(NumSym::TheType, strPassiveFtp, 0);
+	theRec->bAdd(NumSym::TheType, strSocksProb, 0);
+	theRec->bAdd(NumSym::TheType, strSocksChainingProb, 0);
+}
+
+RobotSym::RobotSym(const String &aType, PglRec *aRec): AgentSym(aType, aRec) {
+}
+
+bool RobotSym::isA(const String &type) const {
+	return type == TheType || AgentSym::isA(type);
+}
+
+SynSym *RobotSym::dupe(const String &type) const {
+	if (type == ClientBehaviorSym::TheType) {
+		// cast Robot to ClientBehavior
+		ClientBehaviorSym *const cb = new ClientBehaviorSym();
+		cb->rec()->copyCommon(*theRec);
+		return cb;
+	}
+	if (isA(type))
+		return new RobotSym(this->type(), theRec->clone());
+	return AgentSym::dupe(type);
+}
+
+bool RobotSym::reqRate(double &rate) const {
+	return getRate(strReq_rate, rate);
+}
+
+bool RobotSym::reqInterArrival(RndDistr *&iad) const {
+	iad = getDistr(strReq_inter_arrival);
+	double rr = -1;
+	const bool hasRR = reqRate(rr);
+	if (hasRR && iad)
+		cerr << loc() << ": cannot specify both request rate"
+			<< " and inter-arrival distribution" << endl << xexit;
+
+	if (iad)
+		return true;
+
+	if (!hasRR)
+		return false;
+
+	if (rr <= 0)
+		return true; // iad is nil
+
+	// poisson request stream (default) is modeled using an exponential
+	// distribution with a mean of a given request rate
+	// all clients should share one rng
+	static RndGen rng(GlbPermut(rndRobotSymReqInterArrival));
+	iad = new ExpDistr(&rng, 1/rr);
+	return true;
+}
+
+bool RobotSym::recurRatio(double &ratio) const {
+	return getDouble(strRecurrence, ratio);
+}
+
+bool RobotSym::spnegoRatio(double &ratio) const {
+	return getDouble(strSpnegoAuthRatio, ratio);
+}
+bool RobotSym::embedRecurRatio(double &ratio) const {
+	return getDouble(strEmbed_recur, ratio);
+}
+
+bool RobotSym::uniqueUrls(bool &set) const {
+	return getBool(strUnique_urls, set);
+}
+
+bool RobotSym::openConnLimit(int &lmt) const {
+	return getInt(strOpen_conn_lmt, lmt);
+}
+
+bool RobotSym::waitXactLimit(int &lmt) const {
+	return getInt(strWait_xact_lmt, lmt);
+}
+
+bool RobotSym::minimizeNewConn(double &prob) const {
+	return getDouble(strMinimize_new_conn, prob);
+}
+
+SessionSym *RobotSym::session() const {
+	const SynSym *ss = getRecSym(strSession);
+	return ss ?
+		&((SessionSym&)ss->cast(SessionSym::TheType)) : 0;
+}
+
+bool RobotSym::authError(double &prob) const {
+	return getDouble(strAuth_error, prob);
+}
+
+bool RobotSym::icpPort(int &port) const {
+	return getInt(strIcp_port, port);
+}
+
+NetAddr RobotSym::peerHttp() const {
+	return getNetAddr(strPeer_http);
+}
+
+NetAddr RobotSym::peerIcp() const {
+	return getNetAddr(strPeer_icp);
+}
+
+AclSym *RobotSym::acl() const {
+	SynSymTblItem *i = 0;
+	Assert(theRec->find(strAcl, i));
+	return i->sym() ?
+		&((AclSym&)i->sym()->cast(AclSym::TheType)) : 0;
+}
+
+DnsResolverSym *RobotSym::dnsResolver() const {
+	SynSymTblItem *di = 0;
+	Assert(theRec->find(strDns_resolver, di));
+	return di->sym() ?
+		&((DnsResolverSym&)di->sym()->cast(DnsResolverSym::TheType)) : 0;
+}
+
+bool RobotSym::privCache(int &capacity) const {
+	return getInt(strPrivate_cache_cap, capacity);
+}
+
+bool RobotSym::proxies(Array<NetAddr*> &addrs) const {
+	return getNetAddrs(strProxies, addrs);
+}
+
+bool RobotSym::ftpProxies(Array<NetAddr*> &addrs) const {
+	return getNetAddrs(strFtpProxies, addrs);
+}
+
+bool RobotSym::httpProxies(Array<NetAddr*> &addrs) const {
+	return getNetAddrs(strHttpProxies, addrs);
+}
+
+bool RobotSym::socksProxies(Array<NetAddr*> &addrs) const {
+	return getNetAddrs(strSocksProxies, addrs);
+}
+
+bool RobotSym::origins(Array<NetAddr*> &addrs) const {
+	return getNetAddrs(strOrigins, addrs);
+}
+
+bool RobotSym::origins(Array<NetAddr*> &addrs, RndDistr *&selector) const {
+	SynSymTblItem *oi = 0;
+	Assert(theRec->find(strOrigins, oi));
+	if (!oi->sym())
+		return false; // undefined
+
+	// build address array and selector
+	origins(addrs);
+	ArraySym &os = (ArraySym&)oi->sym()->cast(ArraySym::TheType);
+	selector = os.makeSelector(kind() + "-origins");
+	return true;
+}
+
+String RobotSym::msgTypesField() const {
+	return strReq_types;
+}
+
+bool RobotSym::credentials(Array<String*> &creds) const {
+	return getStrings(strCredentials, creds);
+}
+
+bool RobotSym::containerTags(Array<String*> &tags) const {
+	return getStrings(strContainerTags, tags);
+}
+
+bool RobotSym::acceptedContentCodings(Array<String*> &codings) const {
+	return getStrings(strAcceptContentEncodings, codings);
+}
+
+
+bool RobotSym::interests(Array<StringSym*> &istrs, Array<double> &iprobs) const {
+	if (ArraySym *a = getArraySym(strInterests)) {
+		a->copyProbs(iprobs);
+		ArraySymExportM(StringSym, *a, StringSym::TheType, istrs);
+		return true;
+	}
+	return false;
+}
+
+RndDistr *RobotSym::interests(const TokenIdentifier &interestKinds) const {
+	return namesToDistr(strInterests, interestKinds);
+}
+
+RndDistr *RobotSym::reqMethods(const TokenIdentifier &reqMethodNames) const {
+	return namesToDistr(strReq_methods, reqMethodNames);
+}
+
+RndDistr *RobotSym::pipelineDepth() const {
+	return getDistr(strPipeline_depth);
+}
+
+String RobotSym::foreignTrace() const {
+	return getString(strForeign_trace);
+}
+
+String RobotSym::rawUriThrower() const {
+	return getString(strRaw_uri_thrower);
+}
+
+RndDistr *RobotSym::cookiesKeepLimit() const {
+	return getDistr(strCookies_keep_lmt);
+}
+
+bool RobotSym::ranges(Array<const RangeSym*> &syms, RndDistr *&sel) const {
+	if (ArraySym *a = getArraySym(strRanges)) {
+		ArraySymExportM(RangeSym, *a, RangeSym::TheType, syms);
+		Array<double> probs;
+		a->copyProbs(probs);
+		sel = TblDistr::FromDistrTable(type() + "-" + strRanges, probs);
+		return true;
+	}
+	return false;
+}
+
+bool RobotSym::reqBodyPauseProb(double &f) const {
+	return getDouble(strReqBodyPauseProb, f);
+}
+
+bool RobotSym::reqBodyPauseStart(BigSize &sz) const {
+	return getSize(strReqBodyPauseStart, sz);
+}
+
+bool RobotSym::reqBodyRecurrence(double &f) const {
+	return getDouble(strReqBodyRecurrence, f);
+}
+
+bool RobotSym::reqContents(const String &param, Array<ContentSym*> &syms, RndDistr *&sel) const {
+	if (ArraySym *a = getArraySym(param)) {
+		ArraySymExportM(ContentSym, *a, ContentSym::TheType, syms);
+		Array<double> probs;
+		a->copyProbs(probs);
+		sel = TblDistr::FromDistrTable(type() + "-" + strPostContents, probs);
+		return true;
+	}
+	return false;
+}
+
+bool RobotSym::passiveFtp(double &prob) const {
+	return getDouble(strPassiveFtp, prob);
+}
+
+bool RobotSym::socksProb(double &prob) const {
+	return getDouble(strSocksProb, prob);
+}
+
+bool RobotSym::socksChainingProb(double &prob) const {
+	return getDouble(strSocksChainingProb, prob);
+}
+
+bool RobotSym::haveReqMethods() const {
+	return getArraySym(strReq_methods);
+}
+
+bool RobotSym::haveReqTypes() const {
+	return getArraySym(strReq_types);
+}
diff -ruBEN polygraph-4.3.1/src/pgl/RobotSym.h polygraph-4.3.1-mm/src/pgl/RobotSym.h
--- polygraph-4.3.1/src/pgl/RobotSym.h	2010-12-10 18:25:55.000000000 +0000
+++ polygraph-4.3.1-mm/src/pgl/RobotSym.h	2011-03-04 23:15:41.000000000 +0000
@@ -31,6 +31,11 @@
 		bool reqInterArrival(RndDistr *&iad) const;
 		bool recurRatio(double &ratio) const;
 		bool spnegoRatio(double &ratio) const;
+		bool kerberos(bool &set) const;
+		bool kerberosClearCache(bool &set) const;
+		String kerberosConfigPath() const;
+		String kerberosProxySPN() const;
+		String kerberosServerSPN() const;
 		bool embedRecurRatio(double &ratio) const;
 		bool uniqueUrls(bool &set) const;
 		bool openConnLimit(int &lmt) const;
diff -ruBEN polygraph-4.3.1/src/runtime/NtlmAuthState.h polygraph-4.3.1-mm/src/runtime/NtlmAuthState.h
--- polygraph-4.3.1/src/runtime/NtlmAuthState.h	2007-05-12 01:04:16.000000000 +0100
+++ polygraph-4.3.1-mm/src/runtime/NtlmAuthState.h	2011-03-04 23:15:41.000000000 +0000
@@ -7,6 +7,6 @@
 #define POLYGRAPH__RUNTIME_NTLM_AUTH_STATE_H
 
 typedef enum { ntlmNone, // No NTML is used or not initiated
-		ntlmSentT1, ntlmSentT3, ntlmDone, ntlmError } NtlmAuthState;
+		ntlmSentT1, ntlmSentT3, ntlmDone, ntlmError ,kerberosSentT1, kerberosDone, kerberosError} NtlmAuthState;
 
 #endif

