|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* lftp - file transfer program
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* Copyright (c) 2016 by Alexander V. Lukyanov (lav@yars.free.net)
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* This program is free software; you can redistribute it and/or modify
|
|
Packit |
8f70b4 |
* it under the terms of the GNU General Public License as published by
|
|
Packit |
8f70b4 |
* the Free Software Foundation; either version 3 of the License, or
|
|
Packit |
8f70b4 |
* (at your option) any later version.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* This program is distributed in the hope that it will be useful,
|
|
Packit |
8f70b4 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
8f70b4 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit |
8f70b4 |
* GNU General Public License for more details.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* You should have received a copy of the GNU General Public License
|
|
Packit |
8f70b4 |
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#include <config.h>
|
|
Packit |
8f70b4 |
#include "HttpAuth.h"
|
|
Packit |
8f70b4 |
#include "md5.h"
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
xarray_p<HttpAuth> HttpAuth::cache;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
HttpAuth::Challenge::Challenge(const char *p_chal)
|
|
Packit |
8f70b4 |
: scheme_code(HttpAuth::NONE)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
const char *end=p_chal+strlen(p_chal);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
// challenge = auth-scheme 1*SP 1#auth-param
|
|
Packit |
8f70b4 |
const char *space=strchr(p_chal,' ');
|
|
Packit |
8f70b4 |
if(!space || space==p_chal)
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
scheme.nset(p_chal,space-p_chal).c_ucfirst();
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
// auth-param = token "=" ( token | quoted-string )
|
|
Packit |
8f70b4 |
const char *auth_param=space+1;
|
|
Packit |
8f70b4 |
while(auth_param
|
|
Packit |
8f70b4 |
const char *eq=strchr(auth_param,'=');
|
|
Packit |
8f70b4 |
xstring& key=xstring::get_tmp(auth_param,eq-auth_param).c_lc();
|
|
Packit |
8f70b4 |
SetParam(key,HttpHeader::extract_quoted_value(eq+1,&space));
|
|
Packit |
8f70b4 |
while(space
|
|
Packit |
8f70b4 |
++space;
|
|
Packit |
8f70b4 |
auth_param=space;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(scheme.eq("Basic"))
|
|
Packit |
8f70b4 |
scheme_code=HttpAuth::BASIC;
|
|
Packit |
8f70b4 |
else if(scheme.eq("Digest"))
|
|
Packit |
8f70b4 |
scheme_code=HttpAuth::DIGEST;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
bool HttpAuth::New(target_t t,const char *p_uri,Challenge *p_chal,const char *p_user,const char *p_pass)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
Ref<Challenge> chal(p_chal);
|
|
Packit |
8f70b4 |
Ref<HttpAuth> auth;
|
|
Packit |
8f70b4 |
switch(chal->GetSchemeCode()) {
|
|
Packit |
8f70b4 |
case BASIC:
|
|
Packit |
8f70b4 |
auth=new HttpAuthBasic(t,p_uri,chal.borrow(),p_user,p_pass);
|
|
Packit |
8f70b4 |
break;
|
|
Packit |
8f70b4 |
case DIGEST:
|
|
Packit |
8f70b4 |
auth=new HttpAuthDigest(t,p_uri,chal.borrow(),p_user,p_pass);
|
|
Packit |
8f70b4 |
break;
|
|
Packit |
8f70b4 |
case NONE:
|
|
Packit |
8f70b4 |
return false;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(!auth->IsValid())
|
|
Packit |
8f70b4 |
return false;
|
|
Packit |
8f70b4 |
CleanCache(t,p_uri,p_user);
|
|
Packit |
8f70b4 |
cache.append(auth.borrow());
|
|
Packit |
8f70b4 |
return true;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
bool HttpAuth::Matches(target_t t,const char *p_uri,const char *p_user)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(this->target!=t)
|
|
Packit |
8f70b4 |
return false;
|
|
Packit |
8f70b4 |
if(this->user.ne(p_user))
|
|
Packit |
8f70b4 |
return false;
|
|
Packit |
8f70b4 |
if(!uri.prefixes(p_uri))
|
|
Packit |
8f70b4 |
return false;
|
|
Packit |
8f70b4 |
return true;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
void HttpAuth::CleanCache(target_t t,const char *p_uri,const char *p_user)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
for(int i=cache.length()-1; i>=0; i--) {
|
|
Packit |
8f70b4 |
if(cache[i]->Matches(t,p_uri,p_user))
|
|
Packit |
8f70b4 |
cache.remove(i);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
HttpAuth *HttpAuth::Get(target_t t,const char *p_uri,const char *p_user)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
for(int i=cache.length()-1; i>=0; i--) {
|
|
Packit |
8f70b4 |
if(cache[i]->Matches(t,p_uri,p_user))
|
|
Packit |
8f70b4 |
return cache[i];
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
xstring& HttpAuth::append_quoted(xstring& s,const char *n,const char *v)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!v)
|
|
Packit |
8f70b4 |
return s;
|
|
Packit |
8f70b4 |
if(s.length()>0 && s.last_char()!=' ')
|
|
Packit |
8f70b4 |
s.append(',');
|
|
Packit |
8f70b4 |
s.append(n).append('=');
|
|
Packit |
8f70b4 |
return HttpHeader::append_quoted_value(s,v);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
void HttpAuthBasic::MakeHeader()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
xstring& auth=xstring::get_tmp(user).append(':').append(pass);
|
|
Packit |
8f70b4 |
char *buf64=string_alloca(base64_length(auth.length())+1);
|
|
Packit |
8f70b4 |
base64_encode(auth,buf64,auth.length());
|
|
Packit |
8f70b4 |
header.SetValue(auth.set("Basic ").append(buf64));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
void HttpAuthDigest::MakeHA1()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
const xstring& realm=chal->GetParam("realm");
|
|
Packit |
8f70b4 |
const xstring& nonce=chal->GetParam("nonce");
|
|
Packit |
8f70b4 |
if(!realm || !nonce)
|
|
Packit |
8f70b4 |
return; // required
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
// generate random client nonce
|
|
Packit |
8f70b4 |
cnonce.truncate();
|
|
Packit |
8f70b4 |
for(int i=0; i<8; i++)
|
|
Packit |
8f70b4 |
cnonce.appendf("%02x",unsigned(random()/13%256));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
struct md5_ctx ctx;
|
|
Packit |
8f70b4 |
md5_init_ctx (&ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (user, user.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (realm, realm.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (pass, pass.length(), &ctx;;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
xstring buf;
|
|
Packit |
8f70b4 |
buf.get_space(MD5_DIGEST_SIZE);
|
|
Packit |
8f70b4 |
md5_finish_ctx (&ctx, buf.get_non_const());
|
|
Packit |
8f70b4 |
buf.set_length(MD5_DIGEST_SIZE);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(chal->GetParam("algorithm").eq("MD5-sess")) {
|
|
Packit |
8f70b4 |
md5_init_ctx (&ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (buf, buf.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (nonce, nonce.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (cnonce, cnonce.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_finish_ctx (&ctx, buf.get_non_const());
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
// lower-case hex encoding
|
|
Packit |
8f70b4 |
HA1.truncate();
|
|
Packit |
8f70b4 |
buf.hexdump_to(HA1);
|
|
Packit |
8f70b4 |
HA1.c_lc();
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
bool HttpAuthDigest::Update(const char *p_method,const char *p_uri,const char *entity_hash)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
const xstring& qop_options=chal->GetParam("qop");
|
|
Packit |
8f70b4 |
xstring qop;
|
|
Packit |
8f70b4 |
if(qop_options) {
|
|
Packit |
8f70b4 |
// choose qop
|
|
Packit |
8f70b4 |
char *qop_options_split=alloca_strdup(qop_options);
|
|
Packit |
8f70b4 |
for(char *qop1=strtok(qop_options_split,","); qop1; qop1=strtok(NULL,",")) {
|
|
Packit |
8f70b4 |
if(!strcmp(qop1,"auth-int") && entity_hash) {
|
|
Packit |
8f70b4 |
qop.set(qop1);
|
|
Packit |
8f70b4 |
break;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(!strcmp(qop1,"auth")) {
|
|
Packit |
8f70b4 |
qop.set(qop1);
|
|
Packit |
8f70b4 |
if(!entity_hash)
|
|
Packit |
8f70b4 |
break;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(qop_options && !qop)
|
|
Packit |
8f70b4 |
return false; // no suitable qop found
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
// calculate H(A2)
|
|
Packit |
8f70b4 |
struct md5_ctx ctx;
|
|
Packit |
8f70b4 |
md5_init_ctx (&ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (p_method, strlen(p_method), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (p_uri, strlen(p_uri), &ctx;;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(qop.eq("auth-int")) {
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (entity_hash, strlen(entity_hash), &ctx;;
|
|
Packit |
8f70b4 |
};
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
xstring buf;
|
|
Packit |
8f70b4 |
buf.get_space(MD5_DIGEST_SIZE);
|
|
Packit |
8f70b4 |
md5_finish_ctx (&ctx, buf.get_non_const());
|
|
Packit |
8f70b4 |
buf.set_length(MD5_DIGEST_SIZE);
|
|
Packit |
8f70b4 |
xstring HA2;
|
|
Packit |
8f70b4 |
buf.hexdump_to(HA2);
|
|
Packit |
8f70b4 |
HA2.c_lc();
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
// calculate response
|
|
Packit |
8f70b4 |
md5_init_ctx (&ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (HA1, HA1.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
const xstring& nonce=chal->GetParam("nonce");
|
|
Packit |
8f70b4 |
md5_process_bytes (nonce, nonce.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
char nc_buf[9];
|
|
Packit |
8f70b4 |
if(qop) {
|
|
Packit |
8f70b4 |
sprintf(nc_buf,"%08x",++nc);
|
|
Packit |
8f70b4 |
md5_process_bytes (nc_buf, strlen(nc_buf), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (cnonce, cnonce.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (qop, qop.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_process_bytes (":", 1, &ctx;;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
md5_process_bytes (HA2, HA2.length(), &ctx;;
|
|
Packit |
8f70b4 |
md5_finish_ctx (&ctx, buf.get_non_const());
|
|
Packit |
8f70b4 |
xstring digest;
|
|
Packit |
8f70b4 |
buf.hexdump_to(digest);
|
|
Packit |
8f70b4 |
digest.c_lc();
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
xstring auth("Digest ");
|
|
Packit |
8f70b4 |
append_quoted(auth,"username",user);
|
|
Packit |
8f70b4 |
append_quoted(auth,"realm",chal->GetParam("realm"));
|
|
Packit |
8f70b4 |
append_quoted(auth,"nonce",nonce);
|
|
Packit |
8f70b4 |
append_quoted(auth,"uri",p_uri);
|
|
Packit |
8f70b4 |
append_quoted(auth,"response",digest);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
append_quoted(auth,"algorithm",chal->GetParam("algorithm"));
|
|
Packit |
8f70b4 |
append_quoted(auth,"opaque",chal->GetParam("opaque"));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(qop) {
|
|
Packit |
8f70b4 |
auth.append(",qop=").append(qop);
|
|
Packit |
8f70b4 |
append_quoted(auth,"cnonce",cnonce);
|
|
Packit |
8f70b4 |
auth.append(",nc=").append(nc_buf);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
header.SetValue(auth);
|
|
Packit |
8f70b4 |
return true;
|
|
Packit |
8f70b4 |
}
|