/*
 * AIDE (Advanced Intrusion Detection Environment)
 *
 * Copyright (C) 1999-2006, 2010-2011, 2013, 2015-2016, 2019-2025 Rami Lehti,
 *               Pablo Virolainen, Richard van den Berg, Hannes von Haugwitz
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "aide.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <zlib.h>
#include "attributes.h"
#include "conf_ast.h"
#include "config.h"
#include "errorcodes.h"
#include "hashsum.h"
#include "list.h"
#include "rx_rule.h"
#include "url.h"
#include "commandconf.h"
#include "conf_lex.h"
#include "log.h"
#include "conf_yacc.h"
#include "db.h"
#include "db_config.h"
#include "report.h"
#include "symboltable.h"
#include "md.h"
#include "util.h"
#include "conf_eval.h"
#include "seltree.h"
/*for locale support*/
#include "locale-aide.h"
/*for locale support*/
#ifdef WITH_CURL
#include "fopen.h"
#endif
#ifdef WITH_E2FSATTRS
#include "e2fsattrs.h"
#endif
#include <math.h>
#include <errno.h>
#include <unistd.h>

#define BUFSIZE 4096
#define ZBUFSIZE 16384

url_t* parse_url(char* val, int linenumber, char* filename, char* linebuf)
{
  url_t* u=NULL;
  char* r=NULL;
  char* val_copy=NULL;
  int i=0;

  u=checked_malloc(sizeof(url_t));

  /* We don't want to modify the original hence strdup(val) */
  val_copy=checked_strdup(val);
  for(r=val_copy;r[0]!=':'&&r[0]!='\0';r++);

  if(r[0]!='\0'){
    r[0]='\0';
    r++;
  }

  u->type = get_url_type(val_copy);
  if (u->type) {
  switch (u->type) {
  case url_file : {
    if(r[0]=='/'&&(r+1)[0]=='/'&&(r+2)[0]=='/'){
      u->value=checked_strdup(r+2);
      break;
    }
    if(r[0]=='/'&&(r+1)[0]=='/'&&(r+2)[0]!='/'){
      char* t=r+2;
      r+=2;
      for(i=0;r[0]!='/'&&r[0]!='\0';r++,i++);
      if(r[0]=='\0'){
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "invalid file-URL '%s': no path after hostname", val)
    free(val_copy);
    free(u);
    return NULL;
      }
      if( (strcmp(t,"localhost") != 0) && !( conf->hostname && strcmp(t,conf->hostname)==0)){
          LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "invalid file-URL '%s': cannot use hostname other than 'localhost' or '%s'", val, conf->hostname);
          free(u);
          free(val_copy);
          return NULL;
      }
      u->value=checked_strdup(r);
      r[0]='\0';

      break;
    }
    u->value=checked_strdup(r);
    break;
  }
  case url_ftp :
  case url_https :
  case url_http : {
#ifdef WITH_CURL
    u->value=checked_strdup(val);
#else
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "%s", "http, https and ftp URL support not compiled in, recompile AIDE with '--with-curl'")
    free(val_copy);
    free(u);
    return NULL;
#endif /* WITH CURL */
    break;
  }
  case url_fd:
  case url_stdin:
  case url_stdout:
  case url_stderr: {
    u->value=checked_strdup(r);
    break;
  }
  case url_syslog : {
#ifdef HAVE_SYSLOG
    u->value=checked_strdup(r);
#else
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "%s", "syslog url support not compiled in, recompile AIDE with syslog support")
    free(val_copy);
    free(u);
    return NULL;
#endif
    break;
  }
  }
  } else {
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "unknown URL-type: '%s'", val_copy);
    free(val_copy);
    free(u);
    return NULL;
  }

  free(val_copy);

  u->raw = checked_strdup(val);

  return u;
}

int parse_config(char *before, char *config, char* after) {
    if(before==NULL && after==NULL && (config==NULL||strcmp(config,"")==0)){
      log_msg(LOG_LEVEL_ERROR,_("missing configuration (use '--config' '--before' or '--after' command line parameter)"));
      return RETFAIL;
    }

    ast* config_ast = NULL;
    if (before) {
        conf_lex_string("(--before)", before);
        if(confparse(&config_ast)){
          return RETFAIL;
        }
        conf_lex_delete_buffer();
        eval_config(config_ast, 0, NULL);
        deep_free(config_ast);
        config_ast = NULL;
    }
    if (config) {
        conf_lex_file(config);
        if(confparse(&config_ast)){
          return RETFAIL;
        }
        conf_lex_delete_buffer();
        eval_config(config_ast, 0, NULL);
        deep_free(config_ast);
        config_ast = NULL;
    }
    if (after) {
        conf_lex_string("(--after)", after);
        if(confparse(&config_ast)){
          return RETFAIL;
        }
        conf_lex_delete_buffer();
        eval_config(config_ast, 0, NULL);
        deep_free(config_ast);
        config_ast = NULL;
    }
  return RETOK;
}

int conf_input_wrapper(char* buf, int max_size, FILE* in)
{
  int retval=0;
  retval=fread(buf,1,max_size,in);
  return retval;
}

void do_define(char* name, char* value, int linenumber, char* filename, char* linebuf)
{
  symba* s=NULL;
  list* l=NULL;

  if(!(l=list_find(name,conf->defsyms))){
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, "define '%s' with value '%s'", name, value)
    s=(symba*)checked_malloc(sizeof(symba));
    s->name=checked_strdup(name);
    s->value=value;
    conf->defsyms=list_append(conf->defsyms,(void*)s);
  }
  else {
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, "redefine '%s' with value '%s' (previous value: '%s')", name, value, ((symba*)l->data)->value)
    free(((symba*)l->data)->value);
    ((symba*)l->data)->value=NULL;
    ((symba*)l->data)->value=value;
  }
}

void do_undefine(char* name, int linenumber, char* filename, char* linebuf)
{
  list*r=NULL;

  if((r=list_find(name,conf->defsyms))){
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, "undefine '%s' (value: '%s')", name, ((symba*)r->data)->value)
    free(((symba*)r->data)->name);
    free(((symba*)r->data)->value);
    free((symba*)r->data);
    r->data=NULL;
    conf->defsyms=list_delete_item(r);
  } else {
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, "variable '%s' to be undefined not found", name);
  }
}

bool add_rx_rule_to_tree(char* rx, char* rule_prefix, rx_restriction_t restriction, DB_ATTR_TYPE attr, int type, seltree *tree, int linenumber, char* filename, char* linebuf) {

    rx_rule* r=NULL;

    bool retval = false;

    char *attr_str = NULL;
    char *rs_str = NULL;

    char *regex;
    char *node_path = NULL;
    if (rule_prefix) {
        int length = strlen(rule_prefix)+strlen(rx);
        regex = checked_malloc(length+1);
        strncpy(regex, rule_prefix, length+1);
        strncat(regex, rx, length-strlen(rule_prefix)+1);
        log_msg(LOG_LEVEL_DEBUG, "prepend regex '%s' with prefix '%s': '%s'", rx, rule_prefix, regex);
    } else {
        regex = checked_strdup(rx);
    }

    if ((r = add_rx_to_tree(regex, restriction, type, tree, linenumber, filename, linebuf, &node_path)) == NULL) {
        retval = false;
    }else {
        r->prefix = rule_prefix;

        attr &= validate_hashes(attr, linenumber, filename, linebuf);

        DB_ATTR_TYPE unsupported_attrs = attr&
            (0
#ifndef WITH_ACL
             |ATTR(attr_acl)
#endif
#ifndef WITH_SELINUX
             |ATTR(attr_selinux)
#endif
#ifndef WITH_XATTR
             |ATTR(attr_xattrs)
#endif
#ifndef WITH_E2FSATTRS
             |ATTR(attr_e2fsattrs)
#endif
#ifndef WITH_CAPABILITIES
             |ATTR(attr_capabilities)
#endif
#ifndef HAVE_FSTYPE
             |ATTR(attr_fs_type)
#endif
            )
            ;
        if (unsupported_attrs) {
            char *str;
            LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_WARNING, "ignoring not compiiled-in attribute(s): %s", str = diff_attributes(0, unsupported_attrs));
            free(str);
            attr &= ~unsupported_attrs;
        }

        r->attr=attr;
        if (attr&ATTR(attr_sizeg)) {
            log_msg(LOG_LEVEL_WARNING, "%s:%d: Using 'S' attribute is DEPRECATED and will be removed in the release after next. Update your config and use 'growing+s' instead (line: '%s')", filename, linenumber, linebuf);
        }
        if (attr&ATTR(attr_compressed) && !(attr&get_hashes(false))) {
            log_msg(LOG_LEVEL_WARNING, "%s:%d: ignore 'comprressed' attribute (no hashsum attributes are set) (line: '%s')", filename, linenumber, linebuf);
        }
        conf->db_out_attrs |= attr;

        LOG_CONFIG_FORMAT_LINE_PREFIX(LOG_LEVEL_CONFIG, "add %s '%s%s %s %s' to node '%s'", get_rule_type_long_string(type), get_rule_type_char(type), r->rx, rs_str = get_restriction_string(r->restriction), attr_str = diff_attributes(0, r->attr), node_path)
        free(rs_str);
        free(attr_str);

        retval = true;
    }
    return retval;
}

DB_ATTR_TYPE do_groupdef(char* group,DB_ATTR_TYPE value)
{
  log_msg(LOG_LEVEL_DEBUG, "define attribute group '%s' with value %llu", group, value);
  list* r=NULL;
  symba* s=NULL;

  if((r=list_find(group,conf->groupsyms))){
      DB_ATTR_TYPE prev_value = ((symba*)r->data)->ival;
      ((symba*)r->data)->ival=value;
      return prev_value;
  }
  /* This is a new group */
  s=checked_malloc(sizeof(symba));
  s->name=checked_strdup(group);
  s->ival=value;
  conf->groupsyms=list_append(conf->groupsyms,(void*)s);
  return 0;
}

DB_ATTR_TYPE get_groupval(char* group)
{
  list* r=NULL;

  if((r=list_find(group,conf->groupsyms))){
    return (((symba*)r->data)->ival);
  }
  return DB_ATTR_UNDEF;
}

bool do_dbdef(DB_TYPE dbtype ,char* val, int linenumber, char* filename, char* linebuf)
{
  url_t* u=NULL;
  database *db = NULL;
  char *db_option_name = NULL;

  switch(dbtype) {
  case DB_TYPE_IN: {
    db_option_name = "database_in";
    db = &(conf->database_in);
    break;
  }
  case DB_TYPE_OUT: {
    db_option_name = "database_out";
    db = &(conf->database_out);
    break;
  }
  case DB_TYPE_NEW: {
    db_option_name = "database_new";
    db = &(conf->database_new);
    break;
  }
  }

  if(db->url == NULL){
    if ((u=parse_url(val, linenumber, filename, linebuf)) != NULL) {
    /* FIXME Check the URL if you add support for databases that cannot be 
     * both input and output urls */
    switch (dbtype) {
    case DB_TYPE_IN:
    case DB_TYPE_NEW:{
      switch (u->type) {
          case url_stdout:
          case url_stderr:
          case url_syslog: {
              LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': unsupported URL-type: '%s'", db_option_name, get_url_type_string(u->type))
              return false;
          }
          case url_stdin:
          case url_ftp:
          case url_http:
          case url_https:
          case url_fd:
          case url_file:
                /* url type is supported */
                break;
        }
        break;
    }
    case DB_TYPE_OUT: {
      switch (u->type) {
          case url_stdin:
          case url_stderr:
          case url_syslog: {
              LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': unsupported URL-type: '%s'", db_option_name, get_url_type_string(u->type))
              return false;
          }
          case url_stdout:
          case url_ftp:
          case url_http:
          case url_https:
          case url_fd:
          case url_file:
                /* url type is supported */
                break;
        }
        break;
    }
    }
    db->url = u;
    db->linenumber = linenumber;
    db->filename = filename;
    db->linebuf = linebuf?checked_strdup(linebuf):NULL;
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, "set '%s' option to '%s'", db_option_name, u->raw)
    } else {
        return false;
    }
  } else {
    LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, "'%s' option already set to '%s' (ignore new value '%s')", db_option_name, (db->url)->raw, val);
  }
  return true;
}

bool do_repurldef(char* val, int linenumber, char* filename, char* linebuf) {
    url_t* u = parse_url(val, linenumber, filename, linebuf);
    return add_report_url(u, linenumber, filename, linebuf);
}

bool do_reportlevel(char* val, int linenumber, char* filename, char* linebuf) {
  REPORT_LEVEL report_level = REPORT_LEVEL_UNKNOWN;

  report_level = get_report_level(val);
  if (report_level) {
      conf->report_level = report_level;
      LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, "set 'report_level' option to '%s' (raw: %d)", val, report_level)
      return true;
  } else {
      LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "invalid report level: '%s'", val);
      return false;
  }
}

bool do_rootprefix(char* val, int linenumber, char* filename, char* linebuf) {
    if (conf->root_prefix == NULL) {
        conf->root_prefix=val;
        conf->root_prefix_length=strlen(conf->root_prefix);
        if (conf->root_prefix_length && conf->root_prefix[conf->root_prefix_length-1] == '/') {
            conf->root_prefix[--conf->root_prefix_length] = '\0';
            LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, "removed trailing '/' from root prefix: '%s'", conf->root_prefix);
        }
        LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, "set 'root_prefix' option to '%s'", conf->root_prefix)
        return true;
    } else {
        LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, "'root_prefix' option already set to '%s' (ignore new value '%s')", conf->root_prefix, val);
        return false;
    }
}

long do_num_workers(const char *str) {
    char *err;
    long number = strtol(str,&err,10);
    if (err[0] == '%' && err[1] == '\0') {
        if (number >= 0 && number <= 100) {
            long num_of_processors = sysconf(_SC_NPROCESSORS_ONLN);
            return ceill(num_of_processors * number / 100.0);
        } else {
            return -1;
        }
    } else if(*err != '\0' || number < 0 || errno == ERANGE) {
        return -1;
    }
    return number;
}

#ifdef WITH_E2FSATTRS
void do_report_ignore_e2fsattrs(char* val, int linenumber, char* filename, char* linebuf) {
    conf->report_ignore_e2fsattrs = 0UL;
    if (strcmp(val, "0") != 0) {
        while (*val) {
            unsigned long flag = e2fsattrs_get_flag(*val);
            if (flag) {
                conf->report_ignore_e2fsattrs |= flag;
            } else {
                 LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, "ignore invalid ext2 file attribute: '%c'", *val)
            }
            val++;
        }
    }
}
#endif
