[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#414204: [patch] Interactive digest and basic authentication for apt-get



Package: apt
Version: 0.6.46.5
Severity: wishlist
Tags: patch

This patch adds interactive digest and basic access authentication for
proxy and www to apt-get (user is requested to enter username and
password in the console if the default proxy username and password
specified in http_proxy or apt.conf are missing and authentication
failed). The -q option makes apt-get automatically fail should it
request interactive authentication.

Extra care was taken not to break other programs that use libapt-pkg,
like synaptic and aptitude, so it should be OK if you recompile them
with this patch applied (even though they won't be able to
interactively request username and password, at least digest mode
*should* work).

This patch is based on my previous patch that implemented only HTTP
basic authentication (bug #409830 in
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=409830) and it was
extended to include digest mode. So, most details in that bug report
about patch implementation are still relevant for this one.


Cheers,
Pedro.

--
Pedro de Medeiros - Ciência da Computação - Universidade de Brasília
Home Page: http://www.nonseq.net - Linux User No.: 234250
=== modified file 'apt-pkg/acquire-method.cc'
--- apt-pkg/acquire-method.cc	2007-02-12 21:16:53 +0000
+++ apt-pkg/acquire-method.cc	2007-03-06 15:00:48 +0000
@@ -231,6 +231,63 @@
       QueueBack = Queue;
 }
 									/*}}}*/
+// AcqMethod::RequestAuth - Request Authentication			/*{{{*/
+// ---------------------------------------------------------------------
+/* This sends a 402 Authenticate message to the APT and waits for it
+   to be ackd */
+bool pkgAcqMethod::RequestAuth(string Site,string Realm,string &User,string &Pass,bool Proxy)
+{
+   char S[1024];
+
+   snprintf(S,sizeof(S),"402 Authenticate\nSite: %s\nRealm: %s\nUser: %s\n"
+	    "Password: %s\n", Site.c_str(), Realm.c_str(), User.c_str(),
+	    Pass.c_str());
+   if (Proxy)
+      strcat(S,"Proxy: true\n");
+   strcat(S,"\n");
+   if (write(STDOUT_FILENO,S,strlen(S)) != (signed)strlen(S))
+      exit(100);
+   
+   vector<string> MyMessages;
+   
+   /* Here we read messages until we find a 602, each non 602 message is
+      appended to the main message list for later processing */
+   while (1)
+   {
+      if (WaitFd(STDIN_FILENO) == false)
+	 return false;
+      
+      if (ReadMessages(STDIN_FILENO,MyMessages) == false)
+	 return false;
+
+      string Message = MyMessages.front();
+      MyMessages.erase(MyMessages.begin());
+      
+      // Fetch the message number
+      char *End;
+      int Number = strtol(Message.c_str(),&End,10);
+      if (End == Message.c_str())
+      {	 
+	 cerr << "Malformed message!" << endl;
+	 exit(100);
+      }
+
+      // Change ack
+      if (Number == 602)
+      {
+	 if (StringToBool(LookupTag(Message,"Fail"),false) == false)
+	 {
+	    User = LookupTag(Message,"User");
+	    Pass = LookupTag(Message,"Password");
+	    return true;
+	 }
+	 else
+	    return false;
+      }
+      Messages.push_back(Message);
+   }   
+}
+									/*}}}*/
 // AcqMethod::MediaFail - Syncronous request for new media		/*{{{*/
 // ---------------------------------------------------------------------
 /* This sends a 403 Media Failure message to the APT and waits for it

=== modified file 'apt-pkg/acquire-method.h'
--- apt-pkg/acquire-method.h	2007-02-12 21:16:53 +0000
+++ apt-pkg/acquire-method.h	2007-03-05 17:46:59 +0000
@@ -67,6 +67,7 @@
    void URIStart(FetchResult &Res);
    void URIDone(FetchResult &Res,FetchResult *Alt = 0);
    bool MediaFail(string Required,string Drive);
+   bool RequestAuth(string Site,string Realm,string &User,string &Pass,bool Proxy);
    virtual void Exit() {};
 
    public:

=== modified file 'apt-pkg/acquire-worker.cc'
--- apt-pkg/acquire-worker.cc	2007-02-12 21:16:53 +0000
+++ apt-pkg/acquire-worker.cc	2007-03-09 21:24:16 +0000
@@ -321,6 +321,14 @@
 	 _error->Error("Method %s General failure: %s",Access.c_str(),LookupTag(Message,"Message").c_str());
 	 break;
 	 
+	 // 402 Authentication Required
+	 case 402:
+	 if (Authenticate(Message) == false)
+	    _error->Error("Method %s Authentication failure: \"%s\" in %s",
+			  Access.c_str(), LookupTag(Message,"Realm").c_str(),
+			  LookupTag(Message,"Site").c_str());
+	 break;
+	 
 	 // 403 Media Change
 	 case 403:
 	 MediaChange(Message); 
@@ -565,3 +573,34 @@
    Status = string();
 }
 									/*}}}*/
+// Worker::Authenticate - Request authentication			/*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool pkgAcquire::Worker::Authenticate(string Message)
+{
+   string Site = LookupTag(Message,"Site");
+   string Realm = LookupTag(Message,"Realm");
+   string User = LookupTag(Message, "User");
+   string Pass = LookupTag(Message, "Password");
+   bool Proxy = StringToBool(LookupTag(Message,"Proxy"),false);
+   char S[300];
+   bool Ret = true;
+
+   if (Log == 0 || Log->Authenticate(Site,Realm,User,Pass,Proxy) == false)
+   {
+      snprintf(S,sizeof(S),"602 Authenticated\nFailed: true\n\n");
+      Ret = false;
+   }
+   else
+   {
+      snprintf(S,sizeof(S),"602 Authenticated\nUser: %s\nPassword: %s\n\n",
+	       User.c_str(), Pass.c_str());
+   }
+
+   if (Debug == true)
+      clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
+   OutQueue += S;
+   OutReady = true;
+   return Ret;
+}
+									/*}}}*/

=== modified file 'apt-pkg/acquire-worker.h'
--- apt-pkg/acquire-worker.h	2007-02-12 21:16:53 +0000
+++ apt-pkg/acquire-worker.h	2007-02-14 21:04:04 +0000
@@ -62,6 +62,7 @@
    bool Capabilities(string Message);
    bool SendConfiguration();
    bool MediaChange(string Message);
+   bool Authenticate(string Message);
    
    bool MethodFailure();
    void ItemDone();

=== modified file 'apt-pkg/acquire.cc'
--- apt-pkg/acquire.cc	2007-02-12 21:16:53 +0000
+++ apt-pkg/acquire.cc	2007-03-09 18:28:01 +0000
@@ -883,3 +883,13 @@
    FetchedBytes += Size - Resume;
 }
 									/*}}}*/
+// AcquireStatus::Authenticate - Called to authenticate			/*{{{*/
+// ---------------------------------------------------------------------
+/* This is used to fetch a username and password from the user */
+bool pkgAcquireStatus::Authenticate(string Site,string Realm,string &User,string &Pass,bool Proxy)
+{
+   /* The default behavior for all clients is to refuse to authenticate
+      interactively; this preserves backward compatibility. */
+   return false;
+}
+									/*}}}*/

=== modified file 'apt-pkg/acquire.h'
--- apt-pkg/acquire.h	2007-02-12 21:16:53 +0000
+++ apt-pkg/acquire.h	2007-03-05 17:50:55 +0000
@@ -264,7 +264,10 @@
       
    // Called by items when they have finished a real download
    virtual void Fetched(unsigned long Size,unsigned long ResumePoint);
-   
+
+   // Called to authenticate
+   virtual bool Authenticate(string Site,string Realm,string &User,string &Pass,bool Proxy);
+
    // Called to change media
    virtual bool MediaChange(string Media,string Drive) = 0;
    

=== modified file 'cmdline/acqprogress.cc'
--- cmdline/acqprogress.cc	2007-02-12 21:16:54 +0000
+++ cmdline/acqprogress.cc	2007-03-09 21:28:54 +0000
@@ -18,6 +18,8 @@
     
 #include <stdio.h>
 #include <signal.h>
+#include <termios.h>
+#include <unistd.h>
 #include <iostream>
 									/*}}}*/
 
@@ -287,3 +289,97 @@
    return bStatus;
 }
 									/*}}}*/
+// AcqTextStatus::Authenticate - Authenticate the user			/*{{{*/
+// ---------------------------------------------------------------------
+/* Prompt for a username and password */
+bool AcqTextStatus::Authenticate(string Site,string Realm,string &User,string &Pass,bool Proxy)
+{
+   if (Quiet > 0)
+      return false;
+
+   vector<AuthRecord>::iterator AuthItem;
+   for (AuthItem = AuthList.begin(); AuthItem != AuthList.end(); AuthItem++)
+   {
+      if (AuthItem->Site == Site && AuthItem->Realm == Realm)
+      {
+	 // Update username and password if they don't match
+	 if (AuthItem->User != User || AuthItem->Password != Pass)
+	 {
+	    User = AuthItem->User;
+	    Pass = AuthItem->Password;
+	    return true;
+	 }
+      }
+   }
+
+   cout << '\r' << BlankLine << '\r';
+   if (Proxy)
+      ioprintf(cout,_("Enter username and password for proxy \"%s\" at %s\n"),
+	       Realm.c_str(),Site.c_str());
+   else
+      ioprintf(cout,_("Enter username and password for \"%s\" at %s\n"),
+	       Realm.c_str(),Site.c_str());
+   ioprintf(cout,_("Username: "));
+   cout << flush;
+
+   char S[1024];
+   char C = 0;
+   size_t idx = 0;
+   while (C != '\n' && C != '\r' && idx < (sizeof(S) - 1))
+   {
+      read(STDIN_FILENO,&C,1);
+      S[idx++] = C;
+   }
+   S[--idx] = '\0';
+   User = S;
+
+   ioprintf(cout,_("Password: "));
+   cout << flush;
+
+   // Turn off echo for entering the password
+   struct termios TermIO;
+   tcgetattr(STDIN_FILENO, &TermIO);
+
+   struct termios TermIO_noecho;
+   TermIO_noecho = TermIO;
+   TermIO_noecho.c_lflag &= !ECHO;
+   tcsetattr(STDIN_FILENO, TCSANOW, &TermIO_noecho);
+
+   C = 0;
+   idx = 0;
+   while (C != '\n' && C != '\r' && idx < (sizeof(S) - 1))
+   {
+      read(STDIN_FILENO,&C,1);
+      S[idx++] = C;
+   }
+   S[--idx] = '\0';
+   Pass = S;
+
+   // Turn echo back on
+   tcsetattr(STDIN_FILENO, TCSANOW, &TermIO);
+
+   ioprintf(cout,"\n");
+   cout << flush;
+
+   if (AuthItem == AuthList.end())
+   {
+      // Save new credentials
+      AuthRecord NewAuth;
+
+      NewAuth.Site = Site;
+      NewAuth.Realm = Realm;
+      NewAuth.User = User;
+      NewAuth.Password = Pass;
+      AuthList.push_back(NewAuth);
+   }
+   else
+   {
+      // Update stored user and password
+      AuthItem->User = User;
+      AuthItem->Password = Pass;
+   }
+
+   Update = true;
+   return true;
+}
+

=== modified file 'cmdline/acqprogress.h'
--- cmdline/acqprogress.h	2007-02-12 21:16:54 +0000
+++ cmdline/acqprogress.h	2007-03-05 17:55:46 +0000
@@ -18,10 +18,21 @@
    char BlankLine[1024];
    unsigned long ID;
    unsigned long Quiet;
+
+   struct AuthRecord
+   {
+      string Site;
+      string Realm;
+      string User;
+      string Password;
+   };
+
+   vector<AuthRecord> AuthList;
    
    public:
    
    virtual bool MediaChange(string Media,string Drive);
+   virtual bool Authenticate(string Site,string Realm,string &User,string &Pass,bool Proxy);
    virtual void IMSHit(pkgAcquire::ItemDesc &Itm);
    virtual void Fetch(pkgAcquire::ItemDesc &Itm);
    virtual void Done(pkgAcquire::ItemDesc &Itm);

=== modified file 'methods/http.cc'
--- methods/http.cc	2007-02-12 21:16:55 +0000
+++ methods/http.cc	2007-03-07 22:13:54 +0000
@@ -39,6 +39,7 @@
 #include <errno.h>
 #include <string.h>
 #include <iostream>
+#include <sstream>
 #include <apti18n.h>
 
 // Internet stuff
@@ -57,6 +58,7 @@
 time_t HttpMethod::FailTime = 0;
 unsigned long PipelineDepth = 10;
 unsigned long TimeOut = 120;
+bool ChokePipe = true;
 bool Debug = false;
 
 
@@ -297,7 +299,7 @@
 // ServerState::Open - Open a connection to the server			/*{{{*/
 // ---------------------------------------------------------------------
 /* This opens a connection to the server. */
-bool ServerState::Open()
+bool ServerState::Open(bool ReadProxy)
 {
    // Use the already open connection if possible.
    if (ServerFd != -1)
@@ -308,29 +310,34 @@
    Out.Reset();
    Persistent = true;
    
-   // Determine the proxy setting
-   if (getenv("http_proxy") == 0)
+   // Reuse the already set credentials if possible.
+   if (ReadProxy || Proxy.User.empty() || Proxy.Password.empty())
    {
-      string DefProxy = _config->Find("Acquire::http::Proxy");
-      string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
-      if (SpecificProxy.empty() == false)
-      {
-	 if (SpecificProxy == "DIRECT")
+      // Determine the proxy setting
+      if (getenv("http_proxy") == 0)
+      {
+         string DefProxy = _config->Find("Acquire::http::Proxy");
+         string SpecificProxy = _config->Find("Acquire::http::Proxy::" +
+                                              ServerName.Host);
+         if (SpecificProxy.empty() == false)
+         {
+	    if (SpecificProxy == "DIRECT")
+	       Proxy = "";
+	    else
+	       Proxy = SpecificProxy;
+         }   
+         else
+	    Proxy = DefProxy;
+      }
+      else
+         Proxy = getenv("http_proxy");
+   
+      // Parse no_proxy, a , separated list of domains
+      if (getenv("no_proxy") != 0)
+      {
+         if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
 	    Proxy = "";
-	 else
-	    Proxy = SpecificProxy;
-      }   
-      else
-	 Proxy = DefProxy;
-   }
-   else
-      Proxy = getenv("http_proxy");
-   
-   // Parse no_proxy, a , separated list of domains
-   if (getenv("no_proxy") != 0)
-   {
-      if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
-	 Proxy = "";
+      }
    }
    
    // Determine what host and port to use based on the proxy settings
@@ -368,8 +375,8 @@
 									/*}}}*/
 // ServerState::RunHeaders - Get the headers before the data		/*{{{*/
 // ---------------------------------------------------------------------
-/* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
-   parse error occured */
+/* Returns 0 if things are OK, 1 if an IO error occursed, 2 if a header
+   parse error occured and 3 if digest authentication required */
 int ServerState::RunHeaders()
 {
    State = Header;
@@ -510,6 +517,174 @@
    return Owner->Flush(this) && !_error->PendingError();
 }
 									/*}}}*/
+// ServerState::ProxyLine - Process a proxy line			/*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool ServerState::ParseAuth(string Line, AuthInfo &Auth)
+{
+   AuthInfo AuthTmp;
+   string::size_type SplitPoint = Line.find(' ');
+   string AuthTypeStr = Line.substr(0,SplitPoint);
+
+   // Test authentication type
+   if (stringcasecmp(AuthTypeStr,"Digest") == 0)
+      AuthTmp.Type = AuthInfo::Digest;
+   else if (stringcasecmp(AuthTypeStr,"Basic") != 0)
+      return _error->Error(_("Authentication method not supported"));
+   Line.erase(0, SplitPoint + 1);
+
+   string RealmStr;
+   string NonceStr;
+   string QOPStr;
+   string Unknown;
+   string AlgoStr;
+   string OpaqueStr;
+
+   // Divide line in comma-separated substrings
+   string::size_type FirstPos = Line.find_first_not_of(',',0);
+   string::size_type LastPos = Line.find_first_of(',',FirstPos);
+
+   while (FirstPos != string::npos || LastPos != string::npos)
+   {
+      string Val = Line.substr(FirstPos,LastPos - FirstPos);
+      // Remove spaces
+      string::size_type RightmostSpace = Val.find_last_not_of(' ');
+      if (RightmostSpace != string::npos)
+      {
+	 Val.erase(RightmostSpace + 1);
+	 string::size_type LeftmostSpace = Val.find_first_not_of(' ');
+	 if (LeftmostSpace != string::npos)
+	    Val.erase(0,LeftmostSpace);
+      }
+      else Val.erase(Val.begin(),Val.end());
+      // Set string according to attribute name
+      string *S;
+      if (stringcasecmp(Val.c_str(),Val.c_str()+6,"realm=") == 0)
+      {
+	 if (Debug)
+	    clog << "Receiving realm (";
+	 S = &RealmStr;
+      }
+      else if (stringcasecmp(Val.c_str(),Val.c_str()+6,"nonce=") == 0)
+      {
+	 if (Debug)
+	    clog << "Receiving nonce (";
+	 S = &NonceStr;
+      }
+      else if (stringcasecmp(Val.c_str(), Val.c_str()+4,"qop=") == 0)
+      {
+	 // Got a list for quality-of-protection, parsing it properly
+	 if (Val.substr(4, 1) == "\"")
+	 {
+	    string::size_type ListStart = Line.find_first_of('"',FirstPos);
+	    string::size_type ListEnd = Line.find_first_of('"',ListStart + 1);
+
+	    if (ListEnd == string::npos)
+	       Val = "";
+	    else if (ListStart != ListEnd)
+	       Val = Line.substr(ListStart + 1,ListEnd - ListStart - 1);
+	 }
+	 if (Debug)
+	    clog << "Receiving qop (";
+	 S = &QOPStr;
+      }
+      else if (stringcasecmp(Val.c_str(),Val.c_str()+10,"algorithm=") == 0)
+      {
+	 if (Debug)
+	    clog << "Receiving algorithm (";
+	 S = &AlgoStr;
+      }
+      else if (stringcasecmp(Val.c_str(),Val.c_str()+7,"opaque=") == 0)
+      {
+	 if (Debug)
+	    clog << "Receiving opaque (";
+	 S = &OpaqueStr;
+      }
+      else S = NULL;
+
+      if (S != NULL)
+      {
+	 // Capture string (removing quotes if necessary)
+	 string::size_type EndQuote = Val.find_last_of('"');
+	 string::size_type BeginQuote = Val.find_first_of('"');
+	 if (EndQuote != BeginQuote)
+	    S->assign(Val.substr(BeginQuote + 1,EndQuote - BeginQuote - 1));
+	 else if (Val.empty() == false)
+	 {
+	    string::size_type StartVal = Val.find_first_of('=');
+	    S->assign(Val.substr(StartVal + 1));
+	 }
+	 else
+	    S->assign("");
+	 if (Debug)
+            clog << "'" << *S << "')" << endl;
+      }
+      FirstPos = Line.find_first_not_of(',',LastPos);
+      LastPos = Line.find_first_of(',',FirstPos);
+   }
+   AuthTmp.Realm = RealmStr;
+   if (AuthTmp.Type == AuthInfo::Digest)
+   {
+      AuthTmp.Nonce = NonceStr;
+      // Try to agree on quality-of-protection, fail otherwise
+      if (QOPStr.empty() == false)
+      {
+	 bool GotAuth = false;
+
+	 // Divide line in comma-separated substrings
+	 string::size_type FirstPos = QOPStr.find_first_not_of(',',0);
+	 string::size_type LastPos = QOPStr.find_first_of(',',FirstPos);
+
+	 while (FirstPos != string::npos || LastPos != string::npos)
+	 {
+	    string Tmp = QOPStr.substr(FirstPos,LastPos - FirstPos);
+	    /* FIXME: AuthInt not implemented yet.
+	    if (stringcasecmp(Tmp, "auth-int") == 0)
+	    {
+	       AuthTmp.QOP = AuthInfo::AuthInt;
+	       GotAuth = true;
+	       break;
+	    }
+	    else */
+	    if (stringcasecmp(Tmp,"auth") == 0)
+	    {
+	       AuthTmp.QOP = AuthInfo::Auth;
+	       GotAuth = true;
+	       break;
+	    }
+	    FirstPos = QOPStr.find_first_not_of(',',LastPos);
+	    LastPos = QOPStr.find_first_of(',',FirstPos);
+	 }
+	 if (GotAuth != true)
+	    return _error->Error(_("Unsupported quality of protection"));
+      }
+      // Try to agree on common algorithm or fail if it is not recognized
+      if (AlgoStr.empty() == false)
+      {
+	 if (stringcasecmp(AlgoStr, "MD5-Sess") == 0)
+	    AuthTmp.Algorithm = AuthInfo::MD5Sess;
+	 else if (stringcasecmp(AlgoStr, "MD5") != 0)
+	    return _error->Error(_("Authentication algorithm not supported"));
+      }
+      if (QOPStr.empty() == false)
+      {
+	 ostringstream Tmp;
+	 MD5Summation CNonce;
+
+	 // This is probably good enough for a cnonce
+	 Tmp << AuthTmp.NonceCount << ':' << AuthTmp.Nonce << ':'
+	     << time(NULL) << ':' << rand();
+	 CNonce.Add(Tmp.str().c_str());
+         AuthTmp.Cnonce = CNonce.Result().Value().substr(0,16);
+      }
+   }
+   // Digest authentication is preferable if available
+   if (Auth.Realm.empty() == true || (AuthTmp.Type == AuthInfo::Digest &&
+				      Auth.Type == AuthInfo::Basic))
+      Auth = AuthTmp;
+   return true;
+}
+									/*}}}*/
 // ServerState::HeaderLine - Process a header line			/*{{{*/
 // ---------------------------------------------------------------------
 /* */
@@ -592,6 +767,20 @@
       HaveContent = true;
       return true;
    }
+
+   if (stringcasecmp(Tag,"Proxy-Authenticate:") == 0)
+   {
+      if (ParseAuth(Val, ProxyAuth) == false)
+	 return _error->Error(_("Bad header line"));
+      return true;
+   }
+
+   if (stringcasecmp(Tag,"WWW-Authenticate:") == 0)
+   {
+      if (ParseAuth(Val, Auth) == false)
+	 return _error->Error(_("Bad header line"));
+      return true;
+   }
    
    if (stringcasecmp(Tag,"Content-Range:") == 0)
    {
@@ -631,7 +820,59 @@
    return true;
 }
 									/*}}}*/
-
+// HttpMethod::DigestEncode - Encode digest elements			/*{{{*/
+// ---------------------------------------------------------------------
+/* This append digest elements to the outbound buffer */
+string HttpMethod::DigestEncode(AuthInfo &Auth, URI &Uri, URI &Resource)
+{
+   ostringstream Stream;
+   MD5Summation A1;
+   MD5Summation A2;
+   MD5Summation Response;
+   ostringstream Req;
+
+   Req << "username=\"" << Uri.User << "\", nonce=\"" << Auth.Nonce
+       << "\", uri=\"" << Resource.Path << '"';
+
+   Stream << Uri.User << ':' << Auth.Realm << ':' << Uri.Password;
+   A1.Add(Stream.str().c_str());
+
+   Stream.str("");
+   Stream << "GET:" << Resource.Path;
+   A2.Add(Stream.str().c_str());
+
+   Stream.str("");
+   Stream << A1.Result().Value() << ':';
+   if (Auth.QOP == AuthInfo::Unspec)
+   {
+      Req << ", realm=\"" << Auth.Realm << '"';
+      Stream << Auth.Nonce << ':' << A2.Result().Value();
+   }
+   else
+   {
+      ostringstream NCStream;
+      char *Table[] = {"", "auth", "auth-int"};
+      char *QOP = Table[Auth.QOP - AuthInfo::Unspec];
+
+      // Format NonceCount
+      NCStream.width(8);
+      NCStream.fill('0');
+      NCStream << hex << ++Auth.NonceCount;
+
+      Stream << Auth.Nonce << ':' << NCStream.str() << ':' << Auth.Cnonce
+	     << ':' << QOP << ':' << A2.Result().Value();
+
+      Req << ", qop=" << QOP << ", realm=\"" << Auth.Realm << "\", cnonce=\""
+	  << Auth.Cnonce << "\", nc=" << NCStream.str();
+   }
+   Response.Add(Stream.str().c_str());
+   Req << ", response=\"" << Response.Result().Value();
+   if (Auth.Opaque.empty() == false)
+      Req << "\", opaque=\"" << Auth.Opaque;
+   Req << '"';
+   return Req.str();
+}
+									/*}}}*/
 // HttpMethod::SendReq - Send the HTTP request				/*{{{*/
 // ---------------------------------------------------------------------
 /* This places the http request in the outbound buffer */
@@ -708,13 +949,27 @@
    }
 
    if (Proxy.User.empty() == false || Proxy.Password.empty() == false)
-      Req += string("Proxy-Authorization: Basic ") + 
-          Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n";
-
-   if (Uri.User.empty() == false || Uri.Password.empty() == false)
-      Req += string("Authorization: Basic ") + 
-          Base64Encode(Uri.User + ":" + Uri.Password) + "\r\n";
-   
+   {
+      if (ProxyAuth.Type == AuthInfo::Digest)
+	 Req += string("Proxy-Authorization: Digest ") +
+	     DigestEncode(ProxyAuth, Proxy, Uri) + "\r\n";
+      else
+	 Req += string("Proxy-Authorization: Basic ") + 
+	     Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n";
+   }
+
+   if (Server->ServerName.User.empty() == false ||
+       Server->ServerName.Password.empty() == false)
+   {
+      if (Server->Auth.Type == AuthInfo::Digest)
+	 Req += string("Authorization: Digest ") +
+	     DigestEncode(Server->Auth, Server->ServerName, Uri) + "\r\n";
+      else
+         Req += string("Authorization: Basic ") + 
+             Base64Encode(Server->ServerName.User + ":" +
+			  Server->ServerName.Password) + "\r\n";
+   }
+
    Req += "User-Agent: Debian APT-HTTP/1.3 ("VERSION")\r\n\r\n";
    
    if (Debug == true)
@@ -900,7 +1155,8 @@
      1 - IMS hit
      3 - Unrecoverable error 
      4 - Error with error content page
-     5 - Unrecoverable non-server error (close the connection) */
+     5 - Unrecoverable non-server error (close the connection)
+     6 - Authorization required */
 int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
 {
    // Not Modified
@@ -912,6 +1168,41 @@
       return 1;
    }
    
+   // WWW and proxy authentication
+   if (Srv->Result == 401 || Srv->Result == 407)
+   {
+      bool IsProxy = (Srv->Result == 407);
+      AuthInfo &Auth = IsProxy ? ProxyAuth : Srv->Auth;
+      URI &AuthUri = IsProxy ? Proxy : Srv->ServerName;
+      string AuthUser = AuthUri.User;
+      string AuthPass = AuthUri.Password;
+
+      // First try default authentication.
+      if (DefaultAuthUsed == false && (AuthUser.empty() == false ||
+				       AuthPass.empty() == false))
+      {
+	 DefaultAuthUsed = true;
+	 return 6;
+      }
+
+      // Strip Authentication Request from failed username and password.
+      AuthUri.User.clear();
+      AuthUri.Password.clear();
+      string Host = AuthUri;
+#ifdef WITH_SSL
+      if (AuthUri->Access == "https")
+	 Host += string(_(" (secure)"));
+#endif
+      if (RequestAuth(Host, Auth.Realm, AuthUser, AuthPass, IsProxy) == false)
+	 exit(100);
+      if (Debug)
+         clog << "RequestAuth('" << Host << "', '" << Auth.Realm << "', '"
+	      << AuthUser << "', '" << AuthPass << "')" << endl;
+      AuthUri.User = AuthUser;
+      AuthUri.Password = AuthPass;
+      return 6;
+   }
+   
    /* We have a reply we dont handle. This should indicate a perm server
       failure */
    if (Srv->Result < 200 || Srv->Result >= 300)
@@ -1003,10 +1294,20 @@
       // If pipelining is disabled, we only queue 1 request
       if (Server->Pipeline == false && Depth >= 0)
 	 break;
+
+      if (ChokePipe == true && Depth >= 0)
+      {
+	 ChokePipe = false;
+	 break;
+      }
       
       // Make sure we stick with the same server
       if (Server->Comp(I->Uri) == false)
+      {
+	 ChokePipe = true;
 	 break;
+      }
+
       if (QueueBack == I)
 	 Tail = true;
       if (Tail == true)
@@ -1036,6 +1337,13 @@
    return true;
 }
 									/*}}}*/
+// HttpMethod::Authorization - Handle authorization messages		/*{{{*/
+// ---------------------------------------------------------------------
+bool HttpMethod::Authorization(string Message)
+{
+   return false;
+}
+									/*}}}*/
 // HttpMethod::Loop - Main loop						/*{{{*/
 // ---------------------------------------------------------------------
 /* */
@@ -1047,45 +1355,50 @@
    Server = 0;
    
    int FailCounter = 0;
+   bool ReadProxy = true;
+   bool Retry = false;
    while (1)
    {      
-      // We have no commands, wait for some to arrive
-      if (Queue == 0)
+      if (Retry == false)
       {
-	 if (WaitFd(STDIN_FILENO) == false)
-	    return 0;
-      }
+         // We have no commands, wait for some to arrive
+         if (Queue == 0)
+         {
+	    if (WaitFd(STDIN_FILENO) == false)
+	       return 0;
+         }
       
-      /* Run messages, we can accept 0 (no message) if we didn't
-         do a WaitFd above.. Otherwise the FD is closed. */
-      int Result = Run(true);
-      if (Result != -1 && (Result != 0 || Queue == 0))
-	 return 100;
+         /* Run messages, we can accept 0 (no message) if we didn't
+            do a WaitFd above.. Otherwise the FD is closed. */
+         int Result = Run(true);
+         if (Result != -1 && (Result != 0 || Queue == 0))
+	    return 100;
 
-      if (Queue == 0)
-	 continue;
-      
-      // Connect to the server
-      if (Server == 0 || Server->Comp(Queue->Uri) == false)
-      {
-	 delete Server;
-	 Server = new ServerState(Queue->Uri,this);
+         if (Queue == 0)
+	    continue;
+      
+         // Connect to the server
+         if (Server == 0 || Server->Comp(Queue->Uri) == false)
+         {
+	    delete Server;
+	    Server = new ServerState(Queue->Uri,this);
+         }
+      
+         /* If the server has explicitly said this is the last connection
+            then we pre-emptively shut down the pipeline and tear down 
+	    the connection. This will speed up HTTP/1.0 servers a tad
+	    since we don't have to wait for the close sequence to
+            complete */
+         if (Server->Persistent == false)
+	    Server->Close();
+      
+         // Reset the pipeline
+         if (Server->ServerFd == -1)
+	    QueueBack = Queue;	 
       }
-      
-      /* If the server has explicitly said this is the last connection
-         then we pre-emptively shut down the pipeline and tear down 
-	 the connection. This will speed up HTTP/1.0 servers a tad
-	 since we don't have to wait for the close sequence to
-         complete */
-      if (Server->Persistent == false)
-	 Server->Close();
-      
-      // Reset the pipeline
-      if (Server->ServerFd == -1)
-	 QueueBack = Queue;	 
 	 
       // Connnect to the host
-      if (Server->Open() == false)
+      if (Server->Open(ReadProxy) == false)
       {
 	 Fail(true);
 	 delete Server;
@@ -1093,8 +1406,17 @@
 	 continue;
       }
 
-      // Fill the pipeline.
-      Fetch(0);
+      if (Retry == false)
+      {
+         // Fill the pipeline.
+         Fetch(0);
+      }
+      else
+      {
+	 // Retry last request.
+         SendReq(Queue,Server->Out);
+	 Retry = false;
+      }
       
       // Fetch the next URL header data from the server.
       switch (Server->RunHeaders())
@@ -1212,11 +1534,19 @@
 	    break;
 	 }
 	 
+	 // Got authentication, rerun fetching routine
+	 case 6:
+	 Server->In.Reset();
+	 SendReq(Queue,Server->Out);
+	 Retry = true;
+	 break;
+
 	 default:
 	 Fail(_("Internal error"));
 	 break;
       }
       
+      ReadProxy = false;
       FailCounter = 0;
    }
    

=== modified file 'methods/http.h'
--- methods/http.h	2007-02-12 21:16:55 +0000
+++ methods/http.h	2007-03-07 22:12:26 +0000
@@ -83,6 +83,22 @@
    ~CircleBuf() {delete [] Buf; delete Hash;};
 };
 
+struct AuthInfo
+{
+   string Domain;
+   string Path;
+   string Realm;
+   string Nonce;
+   string Cnonce;
+   string Opaque;
+   int NonceCount;
+   bool Stale;
+   enum {Unspec,Auth,AuthInt} QOP;
+   enum {Basic,Digest} Type;
+   enum {MD5,MD5Sess} Algorithm;
+   AuthInfo() : NonceCount(0), Stale(false), QOP(Unspec), Type(Basic), Algorithm(MD5) {};
+};
+
 struct ServerState
 {
    // This is the last parsed Header Line
@@ -110,7 +126,9 @@
    CircleBuf Out;
    int ServerFd;
    URI ServerName;
+   AuthInfo Auth;
   
+   bool ParseAuth(string Line, AuthInfo &Auth);
    bool HeaderLine(string Line);
    bool Comp(URI Other) {return Other.Host == ServerName.Host && Other.Port == ServerName.Port;};
    void Reset() {Major = 0; Minor = 0; Result = 0; Size = 0; StartPos = 0;
@@ -119,7 +137,7 @@
    int RunHeaders();
    bool RunData();
    
-   bool Open();
+   bool Open(bool ReadProxy);
    bool Close();
    
    ServerState(URI Srv,HttpMethod *Owner);
@@ -129,6 +147,7 @@
 class HttpMethod : public pkgAcqMethod
 {
    void SendReq(FetchItem *Itm,CircleBuf &Out);
+   string DigestEncode(AuthInfo &Auth, URI &Uri, URI &Resource);
    bool Go(bool ToFile,ServerState *Srv);
    bool Flush(ServerState *Srv);
    bool ServerDie(ServerState *Srv);
@@ -136,6 +155,7 @@
 
    virtual bool Fetch(FetchItem *);
    virtual bool Configuration(string Message);
+   virtual bool Authorization(string Message);
    
    // In the event of a fatal signal this file will be closed and timestamped.
    static string FailFile;
@@ -148,6 +168,7 @@
 
    FileFd *File;
    ServerState *Server;
+   bool DefaultAuthUsed;
    
    int Loop();
    
@@ -155,9 +176,11 @@
    {
       File = 0;
       Server = 0;
+      DefaultAuthUsed = false;
    };
 };
 
 URI Proxy;
+AuthInfo ProxyAuth;
 
 #endif

Reply to: