/*
   skipfish - form authentication
   ------------------------------

   Author: Niels Heinen <heinenn@google.com>

   Copyright 2012 by Google Inc. All Rights Reserved.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

*/

#include <string.h>

#define _VIA_AUTH_C

#include "debug.h"
#include "config.h"
#include "types.h"
#include "http_client.h"
#include "database.h"
#include "crawler.h"
#include "analysis.h"
#include "auth.h"

u8 *auth_form;                          /* Auth form location           */
u8 *auth_form_target;                   /* Auth form submit target      */
u8 *auth_user;                          /* User name                    */
u8 *auth_user_field;                    /* Username field id            */
u8 *auth_pass;                          /* Password                     */
u8 *auth_pass_field;                    /* Password input field id      */
u8 *auth_verify_url;                    /* Auth verify URL              */
u8 auth_state;                          /* Stores the auth state        */

void authenticate() {

  struct http_request* req;

  DEBUGC(L1, "*- Authentication starts\n");

  if (!auth_form || !auth_user || !auth_pass)
    return;

  struct pivot_desc *fake = ck_alloc(sizeof(struct pivot_desc));
  fake->type     = PIVOT_FILE;
  fake->state    = PSTATE_FETCH;

  /* When in session, do nothing */
  if (auth_state != ASTATE_NONE)
    return;

  auth_state = ASTATE_START;
  req = ck_alloc(sizeof(struct http_request));

  /* Create a request struct. Note that in this case, we don't care about
   * whether the URL is whitelisted or not */
  if (parse_url(auth_form, req, NULL))
    FATAL("Auth form URL could not be parsed\n");

  req->pivot = fake;
  req->callback = submit_auth_form;

  async_request(req);

}

/* Helper function to find form fields to put the credentials in */

static u8 find_and_set_field(struct param_array *par, const char **test_fields,
                             u32 par_type, u8* par_value) {

  u32 i, k;

  /* Try to find the field */
  for (i=0; i<par->c; i++) {
    if (!par->n[i] || par->t[i] != par_type) continue;

    /* Match it with the strings */
    for (k=0; test_fields[k]; k++) {
      if (inl_strcasestr(par->n[i], (u8*)test_fields[k])) {
        DEBUGC(L1, "*-- Authentication - found login field: %s\n", par->n[i]);
        if (par->v[i]) ck_free(par->v[i]);
        par->v[i] = ck_strdup(par_value);
        return 1;
      }
    }
  }

  /* None found..*/
  return 0;
}


/* Main function to submit the authentication, login form. This function
   will try find the right form and , unless form fields are specified on
   command-line, try to find the right fields in order to store the username
   and password. */

u8 submit_auth_form(struct http_request* req,
                    struct http_response* res) {

  u8 *form, *form_ptr;
  u8 *vurl = NULL;
  u8 is_post = 1;
  u8 par_type = PARAM_POST;
  struct http_request* n = NULL;

  DEBUG_CALLBACK(req, res);

  /* Loop over the forms till we get our password form */
  form_ptr = res->payload;

  do {

    form = inl_strcasestr(form_ptr, (u8*)"<form");
    if (!form) break;

    /* Update the pointer for the next form */
    form_ptr += 5;

    if (auth_form_target)
      vurl = ck_strdup(auth_form_target);

    n = make_form_req(req, NULL, res, form, vurl);
    if (!n)
      FATAL("No auth form found\n");

    is_post = (n->method && !strcmp((char*)n->method, "POST"));
    par_type = is_post ? PARAM_POST : PARAM_QUERY;

    n->pivot = req->pivot;
    collect_form_data(n, req, res, form, is_post);

    /* If the form field was specified per command-line, we'll check if
       it's present. When it's not present: move on to next form.

       Now when no form field was specified via command-line: try
       to find one by using the strings from the "user_fields" array
       (defined in auth.h).
     */
    if (auth_user_field) {
      if(!get_value(par_type, auth_user_field, 0, &n->par))
        continue;

      set_value(par_type, auth_user_field, ck_strdup(auth_user), 0, &n->par);
      DEBUGC(L1, "*-- Authentication - auth_user field set (%s)\n",
             auth_user_field);
    } else if (!find_and_set_field(&n->par, user_fields, par_type, auth_user)) {
      continue;
    }

    if (auth_pass_field) {
      if(!get_value(par_type, auth_pass_field, 0, &n->par))
        continue;

      set_value(par_type, auth_pass_field, ck_strdup(auth_pass), 0, &n->par);
      DEBUGC(L1, "*-- Authentication - auth_pass field set (%s)\n",
             auth_pass_field);
    } else if (!find_and_set_field(&n->par, pass_fields, par_type, auth_pass)) {
      continue;
    }

    /* If we get here: credentials are set */
    n->callback = auth_form_callback;
    DEBUGC(L1, "*-- Submitting authentication form\n");
#ifdef LOG_STDERR
    dump_http_request(n);
#endif
    async_request(n);
    auth_state = ASTATE_SEND;
    break;

  } while (form);

  if (auth_state != ASTATE_SEND)
    DEBUGC(L1, "*-- Could not login. Please check the URL and form fields\n");

  return 0;
}

/* After submitting the form and receiving a response, this is called */

u8 auth_form_callback(struct http_request* req,
                      struct http_response* res) {

  DEBUG_CALLBACK(req, res);
  DEBUGC(L1, "*-- Received form response\n");

  /* Parse the payload which will make sure cookies are stored. */
  content_checks(req, res);

  /* Compare an authenticated and anonymous request to the verification URL. The
   * response should be different in order to determine that we are indeed
   * authenticated */

  if (!auth_verify_url) {
    auth_state = ASTATE_DONE;
    return 0;
  }

  auth_state = ASTATE_VERIFY;
  auth_verify_tests(req->pivot);

  return 0;
}

/* Sends two requests to the verification URL. The first request is
   authenticated (or should be) while the second request is anonymous */

u8 auth_verify_tests(struct pivot_desc* pivot) {

  /* When we have no verification URL or the scan is no authenticated:
     return */

  DEBUG("In auth verify\n");

  if (!auth_verify_url || (auth_state != ASTATE_DONE &&
                           auth_state != ASTATE_VERIFY))
    return 1;

  u8* vurl = ck_strdup(auth_verify_url);
  struct http_request *n = ck_alloc(sizeof(struct http_request));

  n->pivot = pivot;
  if (parse_url(vurl, n, NULL))
    FATAL("Unable to parse verification URL: %s\n", vurl);

  /* One: authenticated request  */
  n->callback = auth_verify_checks;
  n->user_val = 0;
  async_request(n);

  /* Two: anonymous request */
  n = req_copy(n, pivot, 1);
  n->no_cookies = 1;
  n->user_val = 1;
  n->callback = auth_verify_checks;
  async_request(n);

  return 0;
}

/* Receives two requests to the verification URL. If there is a difference, than
   we'll trust that it's because one request was authenticated while the other
   wasn't */

u8 auth_verify_checks(struct http_request* req, struct http_response* res) {
  DEBUG_CALLBACK(req, res);

  if (FETCH_FAIL(res)) {
    handle_error(req, res, (u8*)"during auth verification tests", 0);
    return 0;
  }

  req->pivot->misc_req[req->user_val] = req;
  req->pivot->misc_res[req->user_val] = res;

  /* We need two responses */
  if ((++req->pivot->misc_cnt) != 2) return 1;

  /* Compare the two response. The authenticates response should be
     different to the anonymous request */

  if (same_page(&MRES(0)->sig, &MRES(1)->sig)) {
    DEBUGC(L1, "*- Unable to verify authentication using provided URL.\n");
    dump_signature(&MRES(0)->sig);
    dump_signature(&MRES(1)->sig);
    auth_state = ASTATE_FAIL;
  }


  destroy_misc_data(req->pivot, req);

  /* Re-authenticate upon failure */
  if (auth_state == ASTATE_FAIL) {
    authenticate();
    DEBUG("* Going to re-authenticate\n");
  } else {
    auth_state = ASTATE_DONE;
    DEBUGC(L1, "*- Authenticated\n");
  }

  return 0;
}
