Blame src/HttpAuth.cc

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
}