--- Begin Message ---
- To: submit@bugs.debian.org
- Subject: [patch] Interactive proxy authentication for apt-get
- From: "Pedro de Medeiros" <pedrovmm@gmail.com>
- Date: Mon, 5 Feb 2007 17:56:20 -0200
- Message-id: <38511efb0702051156w53e0064cq25a4ae74304e6b64@mail.gmail.com>
Package: apt
Version: 0.6.46.4
Severity: wishlist
Tags: patch
By default, apt-get 'hammers' the proxy when you call 'apt-get update'
with a wrong password, because of the way that apt-get methods are
called independently. The problem is that some proxys will block an
user account if they are constantly given wrong passwords.
To solve this, more feedback between APT and the HTTP method is
necessary and also some locking and buffering to stop each spawned
HTTP method from requesting username and password.
The "402 Request authentication" message sent from the HTTP method to
APT needs more data to function properly. For instance, HTTP
authentication shows the site address and a "Realm" string to the user
before authentication, according to RFC 2617. The HTTP method may also
send the previous username and password to APT to compare with a
stored value and then decide if the user should be asked again for a
new username and password.
I included a patch that implements this feature.
diff -ru apt-0.6.46.4/apt-pkg/acquire.cc apt-0.6.46.4new/apt-pkg/acquire.cc
--- apt-0.6.46.4/apt-pkg/acquire.cc 2006-12-04 12:37:34.000000000 -0200
+++ apt-0.6.46.4new/apt-pkg/acquire.cc 2007-02-02 18:20:28.000000000 -0200
@@ -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 Desc,string &User,string &Pass)
+{
+ /* The default behavior for all clients is to refuse to authenticate
+ interactively; this preserves backwards compatibility. */
+ return false;
+}
+ /*}}}*/
diff -ru apt-0.6.46.4/apt-pkg/acquire.h apt-0.6.46.4new/apt-pkg/acquire.h
--- apt-0.6.46.4/apt-pkg/acquire.h 2006-12-04 12:37:34.000000000 -0200
+++ apt-0.6.46.4new/apt-pkg/acquire.h 2007-02-02 18:21:10.000000000 -0200
@@ -267,6 +267,9 @@
// Called to change media
virtual bool MediaChange(string Media,string Drive) = 0;
+
+ // Called to authenticate
+ virtual bool Authenticate(string Site,string Desc,string &User,string &pass);
// Each of these is called by the workers when an event occures
virtual void IMSHit(pkgAcquire::ItemDesc &/*Itm*/) {};
diff -ru apt-0.6.46.4/apt-pkg/acquire-method.cc apt-0.6.46.4new/apt-pkg/acquire-method.cc
--- apt-0.6.46.4/apt-pkg/acquire-method.cc 2006-12-04 12:37:34.000000000 -0200
+++ apt-0.6.46.4new/apt-pkg/acquire-method.cc 2007-02-05 16:52:55.000000000 -0200
@@ -231,6 +231,61 @@
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 Desc,string &User,string &Pass)
+{
+ char S[1024];
+ snprintf(S,sizeof(S),"402 Authenticate\nSite: %s\nDesc: %s\nUsername: %s\n"
+ "Password: %s\n\n", Site.c_str(), Desc.c_str(), User.c_str(),
+ Pass.c_str());
+
+ 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
diff -ru apt-0.6.46.4/apt-pkg/acquire-method.h apt-0.6.46.4new/apt-pkg/acquire-method.h
--- apt-0.6.46.4/apt-pkg/acquire-method.h 2006-12-04 12:37:34.000000000 -0200
+++ apt-0.6.46.4new/apt-pkg/acquire-method.h 2007-02-02 18:16:02.000000000 -0200
@@ -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 Description,string &User,string &Pass);
virtual void Exit() {};
public:
diff -ru apt-0.6.46.4/apt-pkg/acquire-worker.cc apt-0.6.46.4new/apt-pkg/acquire-worker.cc
--- apt-0.6.46.4/apt-pkg/acquire-worker.cc 2006-12-04 12:37:34.000000000 -0200
+++ apt-0.6.46.4new/apt-pkg/acquire-worker.cc 2007-02-02 18:33:04.000000000 -0200
@@ -320,6 +320,11 @@
case 401:
_error->Error("Method %s General failure: %s",Access.c_str(),LookupTag(Message,"Message").c_str());
break;
+
+ //402 Authentication Required
+ case 402:
+ Authenticate(Message);
+ break;
// 403 Media Change
case 403:
@@ -565,3 +570,26 @@
Status = string();
}
/*}}}*/
+// Worker::Authenticate - Request authentication /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool pkgAcquire::Worker::Authenticate(string Message)
+{
+ string Site = LookupTag(Message,"Site");
+ string Desc = LookupTag(Message,"Desc");
+ string User = LookupTag(Message, "Username");
+ string Pass = LookupTag(Message, "Password");
+
+ if (Log != 0)
+ Log->Authenticate(Site,Desc,User,Pass);
+
+ char S[300];
+ 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 true;
+}
+ /*}}}*/
diff -ru apt-0.6.46.4/apt-pkg/acquire-worker.h apt-0.6.46.4new/apt-pkg/acquire-worker.h
--- apt-0.6.46.4/apt-pkg/acquire-worker.h 2006-12-04 12:37:34.000000000 -0200
+++ apt-0.6.46.4new/apt-pkg/acquire-worker.h 2007-01-25 16:32:48.000000000 -0200
@@ -62,6 +62,7 @@
bool Capabilities(string Message);
bool SendConfiguration();
bool MediaChange(string Message);
+ bool Authenticate(string Message);
bool MethodFailure();
void ItemDone();
diff -ru apt-0.6.46.4/cmdline/acqprogress.cc apt-0.6.46.4new/cmdline/acqprogress.cc
--- apt-0.6.46.4/cmdline/acqprogress.cc 2006-12-04 12:37:35.000000000 -0200
+++ apt-0.6.46.4new/cmdline/acqprogress.cc 2007-02-02 18:38:38.000000000 -0200
@@ -18,6 +18,8 @@
#include <stdio.h>
#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
#include <iostream>
/*}}}*/
@@ -27,7 +29,8 @@
// ---------------------------------------------------------------------
/* */
AcqTextStatus::AcqTextStatus(unsigned int &ScreenWidth,unsigned int Quiet) :
- ScreenWidth(ScreenWidth), Quiet(Quiet)
+ ScreenWidth(ScreenWidth), Quiet(Quiet), ProxyUser(), ProxyPass(),
+ ProxyRealm()
{
}
/*}}}*/
@@ -287,3 +290,70 @@
return bStatus;
}
/*}}}*/
+// AcqTextStatus::Authenticate - Authenticate the user /*{{{*/
+// ---------------------------------------------------------------------
+/* Prompt for a username and password */
+bool AcqTextStatus::Authenticate(string Site,string Desc,string &User,string &Pass)
+{
+ if (Quiet)
+ return false;
+
+ string Realm = Site + Desc;
+ // Failed with current username and password, ask for them again
+ if (ProxyRealm.empty() ||
+ (User == ProxyUser && Pass == ProxyPass && Realm == ProxyRealm))
+ {
+ ProxyRealm = Realm;
+ cout << '\r' << BlankLine << '\r';
+ ioprintf(cout,_("Enter username and password for proxy %s at %s\n"),
+ Desc.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';
+ ProxyUser = 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';
+ ProxyPass = S;
+
+ // Turn echo back on
+ tcsetattr(STDIN_FILENO, TCSANOW, &TermIO);
+
+ ioprintf(cout,"\n");
+ cout << flush;
+ }
+
+ User = ProxyUser;
+ Pass = ProxyPass;
+ Update = true;
+ return true;
+}
+ /*}}}*/
+
diff -ru apt-0.6.46.4/cmdline/acqprogress.h apt-0.6.46.4new/cmdline/acqprogress.h
--- apt-0.6.46.4/cmdline/acqprogress.h 2006-12-04 12:37:35.000000000 -0200
+++ apt-0.6.46.4new/cmdline/acqprogress.h 2007-02-02 18:37:08.000000000 -0200
@@ -18,10 +18,14 @@
char BlankLine[1024];
unsigned long ID;
unsigned long Quiet;
+ string ProxyUser;
+ string ProxyPass;
+ string ProxyRealm;
public:
virtual bool MediaChange(string Media,string Drive);
+ virtual bool Authenticate(string Site,string Desc,string &User,string &Pass);
virtual void IMSHit(pkgAcquire::ItemDesc &Itm);
virtual void Fetch(pkgAcquire::ItemDesc &Itm);
virtual void Done(pkgAcquire::ItemDesc &Itm);
diff -ru apt-0.6.46.4/methods/http.cc apt-0.6.46.4new/methods/http.cc
--- apt-0.6.46.4/methods/http.cc 2006-12-04 12:37:36.000000000 -0200
+++ apt-0.6.46.4new/methods/http.cc 2007-02-05 16:25:49.000000000 -0200
@@ -295,7 +295,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)
@@ -306,29 +306,33 @@
Out.Reset();
Persistent = true;
- // Determine the proxy setting
- if (getenv("http_proxy") == 0)
+ 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)
+ // Determine the proxy setting
+ if (getenv("http_proxy") == 0)
{
- if (SpecificProxy == "DIRECT")
- Proxy = "";
- else
- Proxy = SpecificProxy;
- }
+ 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 = DefProxy;
- }
- else
- Proxy = getenv("http_proxy");
+ 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 = "";
+ // 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
@@ -590,6 +594,17 @@
HaveContent = true;
return true;
}
+
+ if (stringcasecmp(Tag,"Proxy-Authenticate:") == 0)
+ {
+ string::size_type SplitPoint = Val.find(' ');
+ string RealmStr = Val.substr(SplitPoint + 1,
+ Val.length() - SplitPoint - 1);
+ AuthType = (Val.substr(0, SplitPoint) == "Basic") ? Basic : Digest;
+ SplitPoint = RealmStr.find('=') + 1;
+ Realm = RealmStr.substr(SplitPoint, RealmStr.length() - SplitPoint);
+ return true;
+ }
if (stringcasecmp(Tag,"Content-Range:") == 0)
{
@@ -898,7 +913,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
@@ -909,7 +925,29 @@
Res.LastModified = Queue->LastModified;
return 1;
}
-
+
+ // Proxy Authentication
+ if (Srv->Result == 407)
+ {
+ string AuthUser = Proxy.User;
+ string AuthPass = Proxy.Password;
+ string Site;
+ // Strip Proxy from failed username and password.
+ Proxy.User.clear();
+ Proxy.Password.clear();
+ Site = Proxy;
+#ifdef WITH_SSL
+ if (Proxy.Access == "https")
+ Site += string(_(" (secure)"));
+#endif
+ if (RequestAuth(Site, Srv->Realm, AuthUser, AuthPass) == false)
+ exit(100);
+
+ Proxy.User = AuthUser;
+ Proxy.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)
@@ -1034,11 +1072,20 @@
return true;
}
/*}}}*/
+// HttpMethod::Authorization - Handle authorization messages /*{{{*/
+// ---------------------------------------------------------------------
+bool HttpMethod::Authorization(string Message)
+{
+ return false;
+}
+ /*}}}*/
// HttpMethod::Loop - Main loop /*{{{*/
// ---------------------------------------------------------------------
/* */
int HttpMethod::Loop()
{
+ bool ReadProxy = true;
+
signal(SIGTERM,SigTerm);
signal(SIGINT,SigTerm);
@@ -1083,7 +1130,7 @@
QueueBack = Queue;
// Connnect to the host
- if (Server->Open() == false)
+ if (Server->Open(ReadProxy) == false)
{
Fail(true);
delete Server;
@@ -1209,13 +1256,18 @@
File = 0;
break;
}
-
+
+ // Need authorization, wait for answer from the frontend.
+ case 6:
+ break;
+
default:
Fail(_("Internal error"));
break;
}
FailCounter = 0;
+ ReadProxy = false;
}
return 0;
diff -ru apt-0.6.46.4/methods/http.h apt-0.6.46.4new/methods/http.h
--- apt-0.6.46.4/methods/http.h 2006-12-04 12:37:36.000000000 -0200
+++ apt-0.6.46.4new/methods/http.h 2007-02-02 12:51:13.000000000 -0200
@@ -99,6 +99,8 @@
enum {Chunked,Stream,Closes} Encoding;
enum {Header, Data} State;
bool Persistent;
+ enum {Basic,Digest} AuthType;
+ string Realm;
// This is a Persistent attribute of the server itself.
bool Pipeline;
@@ -119,7 +121,7 @@
int RunHeaders();
bool RunData();
- bool Open();
+ bool Open(bool ReadProxy);
bool Close();
ServerState(URI Srv,HttpMethod *Owner);
@@ -136,7 +138,9 @@
virtual bool Fetch(FetchItem *);
virtual bool Configuration(string Message);
+ virtual bool Authorization(string Message);
+ bool FirstReq;
// In the event of a fatal signal this file will be closed and timestamped.
static string FailFile;
static int FailFd;
@@ -155,6 +159,7 @@
{
File = 0;
Server = 0;
+ FirstReq = true;
};
};
--- End Message ---