/* -*- Mode: c++; -*- */
/*  --------------------------------------------------------------------
 *  Filename:
 *    operator-authenticate.cc
 *  
 *  Description:
 *    Implementation of the AUTHENTICATE command.
 *
 *  Authors:
 *    Andreas Aardal Hanssen <andreas-binc curly bincimap spot org>
 *
 *  Bugs:
 *
 *  ChangeLog:
 *
 *  --------------------------------------------------------------------
 *  Copyright 2002-2005 Andreas Aardal Hanssen
 *
 *  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 Street #330, Boston, MA 02111-1307, USA.
 *  --------------------------------------------------------------------
 */
#include <string>

#include "authenticate.h"
#include "depot.h"
#include "io.h"
#include "session.h"
#include "operators.h"
#include "recursivedescent.h"
#include "base64.h"
#include "convert.h"

using namespace ::std;
using namespace Binc;

//----------------------------------------------------------------------
AuthenticateOperator::AuthenticateOperator(void)
{
}

//----------------------------------------------------------------------
AuthenticateOperator::~AuthenticateOperator(void)
{
}

//----------------------------------------------------------------------
const string AuthenticateOperator::getName(void) const
{
  return "AUTHENTICATE";
}

//----------------------------------------------------------------------
int AuthenticateOperator::getState(void) const
{
  return Session::NONAUTHENTICATED;
}

//------------------------------------------------------------------------
Operator::ProcessResult AuthenticateOperator::process(Depot &depot, 
						      Request &command)
{
  Session &session = Session::getInstance();
  IO &com = IOFactory::getInstance().get(1);

  string authtype = command.getAuthType();
  uppercase(authtype);

  string username;
  string password;

  bool allowplain = (session.globalconfig["Authentication"]["allow plain auth in non ssl"] == "yes");
    

  // for now, we only support LOGIN.
  if (authtype == "LOGIN") {
    // we only allow this type of authentication over a plain
    // connection if it is passed as argument or given in the conf
    // file.
    if (!session.command.ssl && session["sslmode"] != "yes" && !allowplain && !getenv("ALLOWPLAIN")) {
      session.setLastError("Plain text password authentication"
			   " is disallowed. Please try enabling SSL"
			   " or TLS in your mail client.");
      return NO;
    }
    
    com << "+ " << base64encode("User Name") << endl;
    com.flushContent();

    // Read user name
    for (;;) {
      int c = com.readChar();
      if (c == -1)
	return BAD;

      if ((char) c == '\n')
	break;

      username += c;
    }
    
    if (username != "" && username[0] == '*') {
      session.setLastError("Authentication cancelled by user");
      return NO;
    }
    
    com << "+ " << base64encode("Password") << endl;
    com.flushContent();

    // Read password    
    for (;;) {
      int c = com.readChar();
      if (c == -1)
	return BAD;

      if ((char)c == '\n')
	break;
       
      password += c;
    }
    
    if (password != "" && password[0] == '*') {
      session.setLastError("Authentication cancelled by user");
      return NO;
    }

    username = base64decode(username);
    password = base64decode(password);

  } else if (authtype == "PLAIN") {
    // we only allow this type of authentication over an SSL encrypted
    // connection.
    if (!session.command.ssl && session["sslmode"] != "yes" && !allowplain && !getenv("ALLOWPLAIN")) {
      session.setLastError("Plain text password authentication"
			   " is disallowed. Please try enabling SSL"
			   " or TLS in your mail client.");
      return NO;
    }

    com << "+ " << endl;
    com.flushContent();
    
    string b64;
    for (;;) {
      int c = com.readChar();
      if (c == -1) {
	session.setLastError("unexpected EOF");
	return BAD;
      }

      if ((char) c == '\r') {
	com.readChar();
	break;
      }
      
      b64 += (char) c;
    }

    if (b64.size() >= 1 && b64[0] == '*') {
      session.setLastError("Authentication cancelled by user");
      return NO;
    }

    string plain = base64decode(b64);
    string::size_type pos;
    if ((pos = plain.find('\0')) == string::npos) {
      session.setLastError("Authentication failed. In PLAIN mode,"
			   " there must be at least two null characters"
			   " in the input string, but none were found");
      return NO;
    }

    plain = plain.substr(pos + 1);
    if ((pos = plain.find('\0')) == string::npos) {
      session.setLastError("Authentication failed. In PLAIN mode,"
			   " there must be at least two null characters"
			   " in the input string, but only one was found");
      return NO;
    }

    username = plain.substr(0, pos);
    password = plain.substr(pos + 1);
  } else {
    session.setLastError("The authentication method " 
			 + toImapString(authtype) + " is not supported."
			 " Please try again with a different method."
			 " There is built in support for \"PLAIN\""
			 " and \"LOGIN\".");
    return NO;
  }

  putenv(strdup(("BINCIMAP_LOGIN=AUTHENTICATE+" + command.getTag()).c_str()));

  // the authenticate function calls a stub which does the actual
  // authentication. the function returns 0 (success), 1 (internal
  // error) or 2 (failed)
  switch (authenticate(depot,
		       (const string &)username,
		       (const string &)password)) {
  case 1:
    session.setLastError("An internal error occurred when you attempted"
			 " to log in to the IMAP server. Please contact"
			 " your system administrator.");
    return NO;
  case 2:
    session.setLastError("Login failed. Either your user name"
			 " or your password was wrong. Please try again,"
			 " and if the problem persists, please contact"
			 " your system administrator.");
    return NO;
  case 3:    
    com << "* BYE Timeout after " << session.idletimeout
	<< " seconds of inactivity." << endl;
    break;
  case -1:
    com << "* BYE The server died unexpectedly. Please contact "
      "your system administrator for more information." << endl;
    break;

  }

  // auth was ok. go to logout state
  session.setState(Session::LOGOUT);
  return NOTHING;
}


//----------------------------------------------------------------------
Operator::ParseResult AuthenticateOperator::parse(Request &c_in) const
{
  Session &session = Session::getInstance();

  if (c_in.getUidMode())
    return REJECT;

  Operator::ParseResult res;

  if ((res = expectSPACE()) != ACCEPT) {
    session.setLastError("Expected single SPACE after AUTHENTICATE");
    return res;
  }

  string authtype;
  if ((res = expectAtom(authtype)) != ACCEPT) {
    session.setLastError("Expected auth_type after AUTHENTICATE SPACE");
    return ERROR;
  }

  if ((res = expectCRLF()) != ACCEPT) {
    session.setLastError("Expected CRLF after AUTHENTICATE SPACE auth_type");
    return res;
  }

  c_in.setAuthType(authtype);

  c_in.setName("AUTHENTICATE");
  return ACCEPT;
}


syntax highlighted by Code2HTML, v. 0.9.1