/* Socks Server 5
 * Copyright (C) 2002 - 2006 by Matteo Ricchetti - <matteo.ricchetti@libero.it>

 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/*
 * $Id: SS5Mod_authentication.c, Ver 3.7 08-2005 20:00:00 Matteo Ricchetti Exp $
 */

#include"SS5Main.h"
#include"SS5Mod_authentication.h"
#include"SS5Mod_log.h"
#include"SS5Basic.h"
#include"SS5ExternalProgram.h"
#include"SS5Pam.h"

S5RetCode InitModule( struct _module *m )
{
  m->Authentication = Authentication;
  m->FreeAuthCache  = FreeAuthCache;
 
  return OK;
}

S5RetCode Authentication( struct _SS5MethodInfo *mi, struct _SS5ClientInfo *ci, struct _SS5BasicData *bd, struct _SS5AuthInfo *ai )
{
  register unsigned int idx;

  S5RetCode err  = ERR;
  S5RetCode err2 = ERR;

  char logString[256];

  pid_t pid;

  /*
   *    Get child/thread pid
   */
  if( NOTTHREADED() )
    pid = getpid();
  else
    pid = (unsigned int)pthread_self();

  memset(ai,0,sizeof(struct _SS5AuthInfo));

  strncpy(ai->Username,"\"\"",strlen("\"\""));

  if( mi->Method == NOAUTH ) {
    strncpy(ai->Username,"\"\"",strlen("\"\""));
    return OK;
  }
  else if( (mi->Method == USRPWD) || (mi->Method == FAKEPWD) ) {

    if( recv(ci->Socket,bd->Request,sizeof(bd->Request),0) <= 0 ) {
      ERRNO(pid)
      return ERR;
    }
  /*
   *    Check for buffer overflow
   */
   if( ((unsigned char)bd->Request[1] == 0) || ((unsigned char)bd->Request[1] >= sizeof(ai->Username)) )
     return ERR;

   if( ((unsigned char)bd->Request[2+(unsigned char)bd->Request[1]] == 0)
       || ((unsigned char)bd->Request[2+(unsigned char)bd->Request[1]] >= sizeof(ai->Password)) )
     return ERR;

   /*
    *    Get credentials
    */
    for(idx = 0; idx < (unsigned char)bd->Request[1]; idx++)
      ai->Username[idx] = bd->Request[idx+2];
    ai->Username[idx] = '\0';

    for(idx = 0;idx < (unsigned char)bd->Request[2+(unsigned char)bd->Request[1]]; idx++)
      ai->Password[idx] = bd->Request[3+bd->Request[1]+idx];
    ai->Password[idx] = '\0';
    /*
     *    Look for username/password into authentication cache
     */
    if( THREADED() ) {
      if( SS5SocksOpt.AuthCacheAge ) {
        err2 = GetAuthCache(ai->Username,ai->Password);

        if( err2 == ERR_EXPIRED ) {
          /*
           *    Update the entry into authentication cache
           */
          UpdateAuthCache(ai->Username,ai->Password);

          if( VERBOSE() ) {
            snprintf(logString,256 - 1,"[%u] [VERB] Cache authentication expired for user %s.",pid,ai->Username);
            SS5Modules.mod_logging.Logging(logString);
          }
        }
      }
    }
    
    if( err2 <= ERR ) {

      if( mi->Method != FAKEPWD ) {
        /*
         *    Evaluate how to handle basic autentication:
         *    1. using an External Program (EAP)     
         *    2. using PAM 
         *    3. using local file /etc/opt/ss5/ss5.passwd
         */
        switch( SS5SocksOpt.Authentication ) {
          case EAP_AUTHENTICATION:    err = S5AuthProgramCheck(ai, pid);    break;
  	  case PAM_AUTHENTICATION:    err = S5PamCheck(ai);                 break;
          case FILE_AUTHENTICATION:   
            if( S5PwdFileOpen(pid) ) {
              err = S5PwdFileCheck(ai);
              S5PwdFileClose(pid);
            }
          break;
        }
      }
      else {
        err = OK;
      }
  
      if( err ) {
        if( THREADED() ) {
          if( SS5SocksOpt.AuthCacheAge ) {
            /*
             * Add new entry into authentication cache
             */
            AddAuthCache(ai->Username,ai->Password);
            if( VERBOSE() ) {
              snprintf(logString,256 - 1,"[%u] [VERB] Cache authentication updated for user %s.",pid,ai->Username);
              SS5Modules.mod_logging.Logging(logString);
            }
          }
        }

        bd->Response[0] = 1; 
        bd->Response[1] = 0; /*    Basic success    */
        if( send(ci->Socket,bd->Response,sizeof(bd->Response),SS5_SEND_OPT) == -1) {
          ERRNO(pid)
          return ERR;
        }
        return OK;
      }
      else {
        bd->Response[0] = 1; 
        bd->Response[1] = 1; /*    Basic failed    */
    
        if( send(ci->Socket,bd->Response,sizeof(bd->Response),SS5_SEND_OPT) == -1) {
          ERRNO(pid)
          return ERR;
        }
      }
    }
    else if( THREADED() ) {
      if( SS5SocksOpt.AuthCacheAge ) {
        /*
         *    Entry in cache
         */
        if( VERBOSE() ) {
          snprintf(logString,256 - 1,"[%u] [VERB] Cache authentication verified for user %s.",pid,ai->Username);
          SS5Modules.mod_logging.Logging(logString);
        }

        bd->Response[0] = 1; 
        bd->Response[1] = 0; /*    Basic success    */
    
        if( send(ci->Socket,bd->Response,sizeof(bd->Response),SS5_SEND_OPT) == -1) {
          ERRNO(pid)
          return ERR;
        }
        return OK;
      }
    }
  }
  return ERR;
} 


/* ***************************** HASH for AUTHENTICATION CACHE **************************** */
inline S5Limit S5AuthCacheHash( char *u, char *p )
{
  register unsigned int idx;
  register unsigned int len;
  register long int hashVal = 0;
  char s[128];

  snprintf(s,sizeof(s) - 1,"%s%s",u,p);

  len=strlen(s);
  for(idx=0; idx<len;idx++)
    hashVal= 37*hashVal + s[idx];

  hashVal %= MAXAUTHCACHELIST;
  if(hashVal < 0)
    hashVal +=MAXAUTHCACHELIST;

  return hashVal;

}

S5RetCode GetAuthCache(char *u, char *p)
{
  register unsigned int index;
  struct _S5AuthCacheNode *node;

    index=S5AuthCacheHash( u, p );

    if( S5AuthCacheList[index]!= NULL ) {
      node=S5AuthCacheList[index];
      do {
        if( STREQ(u,node->Usr,sizeof(node->Usr)) && STREQ(p,node->Pwd,sizeof(node->Pwd)) ) {
          if( node->ttl > time(NULL) )
            return OK;
          else
            return ERR_EXPIRED;
        }
        node=node->next;
      } while(node != NULL );
    }

  return ERR;
}

S5RetCode AddAuthCache(char *u, char *p )
{
  register unsigned int index;
  struct _S5AuthCacheNode *node;

  index=S5AuthCacheHash( u, p );

  if( S5AuthCacheList[index]== NULL ) {
    S5AuthCacheList[index]=(struct _S5AuthCacheNode *)calloc(1,sizeof(struct _S5AuthCacheNode));
    strncpy(S5AuthCacheList[index]->Usr,u,sizeof(S5AuthCacheList[index]->Usr));
    strncpy(S5AuthCacheList[index]->Pwd,p,sizeof(S5AuthCacheList[index]->Pwd));
    S5AuthCacheList[index]->ttl=(time(NULL) + SS5SocksOpt.AuthCacheAge);
    S5AuthCacheList[index]->next=NULL;
  }
  else {
    node=S5AuthCacheList[index];
    while( node->next != NULL ){
      node=node->next;
    }
    node->next=(struct _S5AuthCacheNode *)calloc(1,sizeof(struct _S5AuthCacheNode));
    node->next->ttl=(time(NULL) + SS5SocksOpt.AuthCacheAge);
    strncpy(node->next->Usr,u,sizeof(S5AuthCacheList[index]->Usr));
    strncpy(node->next->Pwd,p,sizeof(S5AuthCacheList[index]->Pwd));
    node->next->next=NULL;
  }
  return OK;
}

S5RetCode UpdateAuthCache(char *u, char *p)
{
  register unsigned int index;
  struct _S5AuthCacheNode *node;

    index=S5AuthCacheHash( u, p );

    if( S5AuthCacheList[index]!= NULL ) {
      node=S5AuthCacheList[index];
      do {
        if( STREQ(u,node->Usr,sizeof(node->Usr)) && STREQ(p,node->Pwd,sizeof(node->Pwd)) ) {
          node->ttl=(time(NULL) + SS5SocksOpt.AuthCacheAge);
          return OK;
        }
        node=node->next;
      } while(node != NULL );
    }

  return ERR;
}



S5RetCode FreeAuthCache( struct _S5AuthCacheNode **node )
{
  struct _S5AuthCacheNode *lnode;
  struct _S5AuthCacheNode *lnode_prev=NULL;

  lnode=*node;

  if( lnode != NULL ) {
    do {
      while( lnode->next != NULL ) {
        lnode_prev=lnode;
        lnode=lnode->next;
      }
      free(lnode);
      if( lnode_prev != NULL ) {
        lnode_prev->next=NULL;
        lnode=lnode_prev;
        lnode_prev=NULL;
      }
      else
        lnode=NULL;
    } while( (lnode) != NULL );
  }
  *node=NULL;
  
  return OK;

}

S5RetCode S5BrowseAuthCacheList( struct _S5AuthCacheNode *node )
{
  struct _S5AuthCacheNode *lnode;
  int found=0;

  lnode=node;
  do {
    if(lnode != NULL ) {
      printf("Usr: %s Pwd: %s\n",lnode->Usr,lnode->Pwd);
      lnode=lnode->next;
      found++;
    }
  } while( lnode != NULL );

  return found;
}



syntax highlighted by Code2HTML, v. 0.9.1