next   previous   contents

Parsing del file di configurazione

 Questa fase permette di memorizzare in variabili, fondamentali o derivate, informazioni specificate nel file di configurazione, quali l'interfaccia di rete su cui intercettare i pacchetti di rete, l'intervallo di porte su cui lavorare, le possibili sequenze di knock etc.
 L'operazione risulta del tutto simile sia per servknock che per Module Knock; per quest'ultimo tuttavia, i parametri di configurazione riguardanti il sistema di port knocking sono una sottosezione del file di configurazione di Hogwash.

###############################
# Portknocking server
###############################

iface=eth0
# blowfish | rijndael | twofish | xtea
cypher=rijndael
password=raisingf3ar
iv=kingsofmetallivingontheroad
span=7000-7100,7500-7655
logfile=/var/log/hogwash/knockserv.log

#open ssh port
<seq>
name=ssh_open
seq_timeout=40
sequence=UDP:222,ICMP,TCP:56,UDP:22,TCP:99
tcp_flags=syn,ack,psh
command=iptables -A INPUT -s $IP -p tcp --dport 22 -j ACCEPT
</seq>

#close ssh port
<seq>
name=ssh_close
seq_timeout=40
sequence=UDP:212,TCP:149,ICMP,UDP:42,TCP:199,TCP:180
tcp_flags=syn,psh,fin
command=iptables -D INPUT -s $IP -p tcp --dport 22 -j ACCEPT
</seq>

#open apache port
<seq>
name=apache_dnat
sequence=TCP:90,UDP:44,ICMP
tcp_flags=syn,ack,psh
seq_timeout=40
command=iptables -A PREROUTING -t nat -d 192.168.0.1 -p tcp --dport 80 -s $IP
-j DNAT --to-destination 192.168.0.4:80
cmd_timeout=40
stop_command=iptables -D PREROUTING -t nat -d 192.168.0.1 -p tcp --dport 80 -s $IP
-j DNAT --to-destination 192.168.0.4:80
</seq>

 Come e' possibile vedere dall'esempio, le opzioni di configurazione si dividono in due parti: le prime righe definiscono alcuni aspetti generali, come l'interfaccia di rete su cui monitorare il traffico, il cifrario e la password da utilizzare per la generazione della chiave simmetrica, usata poi per il crittaggio delle sequenze di knock, l'intervallo di porte su cui lavorare ed il file di log da utilizzare.
 I demarcatori <seq> e </seq> racchiudono invece al loro interno una sequenza di knock valida. Per ogni sequenza di knock e' possibile definire i seguenti parametri:

- name: nome della sequenza
- sequence: la sequenza di knock , definita come una lista di coppie <protocol>:<destination port>. Nel caso il protocollo del pacchetto sia di tipo ICMP ovviamente non e' necessario specificare la porta di destinazione.
- seq_timeout: intervallo di tempo entro il quale la sequenza di knock deve essere completata dal richiedente.
- tcp_flag: specifica quali flag devono essere attivati in ogni pacchetto TCP presente nella sequenza.
- command: il comando da eseguire al riconoscimento della sequenza di knock. Possono esserci piu' righe che cominciano con command, in modo da eseguire due o piu' comandi.
- cmd_timeout: l'intervallo di tempo dopo il quale eseguire lo stop command.
- stop_command: il comando da eseguire allo scadere dell'intervallo di tempo cmd_timeout. Anche in questo caso e' possibile definire piu' di un comando di stop.

Per ogni riga viene quindi eliminata l'eventuale presenza di caratteri di spaziatura o di tabulazione per mezzo della funzione trim; per il parsing delle righe viene utilizzata la funzione di libreria strncasecmp(const char *s1, const char *s2, size_t n), che effettua un confronto case-insensitive fra le stringhe puntate da s1 ed s2 fino all'n-esimo carattere: in questo modo e' possibile decifrare la parte sinistra della riga, fino al carattere '=', che specifica quale sia l'opzione che la riga in questione definisce. Viene riportato un esempio in cui viene analizzata la stringa che definisce l'interfaccia su cui verra' analizzato il traffico di rete:

if (strncasecmp(Arg, "iface=",6)==0)
{
  snprintf(IfaceName, 20, Arg+6);
  #ifdef DEBUG
   printf("\nListening interface \"%s\"\n",IfaceName);
  #endif
  err = get_interface_address (IfaceName, IfaceIp, sizeof(IfaceIp));
  if (err)
  {
   printf("Can't get interface address of %s\n", IfaceName);
   exit (1) ;
  }
  #ifdef DEBUG
   printf("Ip interface %s\n", IfaceIp);
  #endif
  return TRUE;
}

 Piu' complesso e' invece il caso in cui si effettua l'analisi della riga che inizia con “span=”, che definisce il port span, cioe' l'intervallo o gli intervalli di porte su cui il modulo dovra' analizzare il traffico di rete. La parte destra di questa riga contiene quindi dei numeri di porte ed ha una forma di questo tipo:
<p1> - <p2> , <p3> - <p4> , ... , <pn-1> - <pn>
con p1 < p2 < p3 < p4 < ...< pn-1 < pn.
 Ogni <pi> - <pi-1> definisce un sottointervallo. Inoltre pn – p1 deve essere necessariamente minore di 256.
 Per la memorizzazione di questa informazione viene utilizzata una lista concatenata di elementi portspan_t definiti come:

/* port span */
typedef struct portspan {
  unsigned int minrange;
  unsigned int maxrange;
} portspan_t;

 Per far questo si ricorre all'utilizzo della funzionestrsep(char **stringp, const char *delim). Questa funzione ritorna i token della stringa puntata da*stringp, delimitati dal carattere puntato da delim. Occorre quindi impostare un ciclo che estragga dalla stringa le varie sottostringhe che specificano i sottointervalli: while((temp = strsep(&Arg, ","))).
 Per estrarre la porta iniziale e finale del range viene ancora usata strsep, passandole questa volta come parametri l'indirizzo di temp ed il carattere - . Le stringhe rappresentati i numeri di porta vengono poi converite con atoi, ed assegnate a port_range->minrange e port_range->maxrange. Una volta assegnati i valori e verificato che la somma di tutti gli intervalli non superi 255, l'elemento portspan_t appena allocato viene aggiunto alla lista portspan_list.
 Essendo all'interno di un ciclo while, queste operazioni vengono ripetute per tutti i range di porte specificati sulla linea del file di configurazione. Al termine del ciclo portspan_list conterra' un'elemento portspan_t ( al cui interno sono definite la porta iniziale e finale dell'intervallo ) per ogni range di porte che deve essere monitorato.

else if (strncasecmp(Arg, "span=",5)==0)
{
  char *temp;
  char *min;
  char *max;
  snprintf(Arg, strlen(Arg), Arg+5);
  #ifdef DEBUG
    printf("The port span is ");
  #endif
  while((temp = strsep(&Arg, ",")))
  {
    port_range = malloc(sizeof(portspan_t));
    if(port_range == NULL)
    {
      printf("Error: can't alloc portspan structure\n");
      exit(1);
    }
  min = strsep(&temp, "-");
  min =trim(min);
  port_range->minrange=atoi(min);
  max= strsep(&temp, "-");
  max =trim(max);
  port_range->maxrange=atoi(max);
  range_count+=(port_range->maxrange - port_range->minrange);
  if (range_count>255)
  {
    printf("\n portspan error > 255 \n");
    exit(1);
  }
  portspan_list=list_add(portspan_list, port_range);
  #ifdef DEBUG
    printf("%d)%d-%d\t",list_count(portspan_list),port_range->minrange,port_range->maxrange);
  #endif
  }
  #ifdef DEBUG
    printf("\n");
  #endif
  return TRUE;
}

 Un altro caso non banale e' il parsing della sezione relativa alla sequenza di knock. Le sequenze di knock sono delimitate dai tag <seq> e </seq>. Quando il tag <seq> viene individuato, e' necessario allocare una nuovo elemento knock_t, in grado di contenere le informazioni relative alla sequenza di knock; viene settato inoltre il flag in_seq_flag, al fine di segnalare che si e' entrati nella fase di parsing di una sequenza.

//inizio sequenza
else if (strncasecmp(Arg, "<seq>",5)==0)
     {//trova nuova seq
       in_seq_flag=1;
       #ifdef DEBUG
         printf("\nSequence found\n");
       #endif
       knock = malloc(sizeof(knock_t));
       if(knock == NULL)
       {
         printf("Error: can't alloc knock structure\n");
         exit(1);
       }
       return TRUE;
     }///trova nuova seq

 Quando invece viene trovata una riga con il marcatore </seq> la definizione di una sequenza e' terminata: all'intero in_seq_flag viene assegnato il valore 0, in modo tale da non entrare nella fase di parsing di una sequenza di knock finche' non venga trovato un nuovo tag <seq>

if(strncasecmp(Arg, "</seq>",6)==0)
{
  [...]
  in_seq_flag=0;
  return TRUE;
}

 I vari campi dell'elemento knock_t, allocato in precedenza, vegono poi inizializzati con dei valori di default una volta trovato il nome della sequenza; l'elemento, puntato da knock e' quindi aggiunto alla lista delle sequenze di knock valide puntata da knocks_list per mezzo della chiamata list_add(knocks_list, knock);

//nome della sequenza
if(strncasecmp(Arg, "name=",5)==0)
{
  snprintf(knock->name, sizeof(knock->name), Arg+5);
  knock->seqcount = 0;
  knock->seq_timeout = SEQ_TIMEOUT;
  knock->start_command_list = NULL;
  knock->cmd_timeout = CMD_TIMEOUT;
  knock->stop_command_list = NULL;
  knocks_list = list_add(knocks_list, knock);
  #ifdef DEBUG
    printf("sequence %d) %s\n ",list_count(knocks_list), knock->name);
  #endif
  return TRUE;
}

Una volta aggiunto l'elemento alla lista, e' necessario estrarre dal file di configurazione i valori delle varie porte che costituiscono la sequenza di knock. Come gia' specificato in precedenza la sequenza e' definita per mezzo una lista di coppie <protocol>:<port number> separate da virgole. Tramite un ciclo while e la funzione strsep si ricavano quindi le coppie. La funzione strsep, viene poi riutilizzata specificando come carattere separatore ':', al fine di ricavare le due informazioni, che vengono memorizzate in knock->protocol[knock->seqcount] e knock->sequence[knock->seqcount]. Nel caso la sequenza contenga un pacchetto di tipo ICMP, la porta di destinazione non e' ovviamente specificata, e viene quindi inserito il valore 0 all'interno del array sequence.
Importante e' l'utilizzo dell'intero seqcount, campo della struttura knock_t, incrementato ad ogni ciclo ed utilizzato come cursore per segnalare in che posizione si e' arrivati all'interno dei vettori protocol e sequence. Nel caso questa variabile assuma un valore superiore a SEQ_MAX, la sequenza di knock specificata nel file di configurazione e' troppo lunga e quindi non ammissibile.

else if(strncasecmp(Arg, "sequence=",9)==0)
{//parse knock sequence
  char *num;
  char *protocol;
  char *port;
  //int i;
  snprintf(Arg, strlen(Arg), Arg+9);
  while((num = strsep(&Arg, ",")))
  {
    if(knock->seqcount >= SEQ_MAX)
    {
      printf("Config Error in knock %s: too many ports in knock sequence\n",knock->name);
      exit(1);
    }
    protocol = strsep(&num, ":");
    protocol =strtoupper(trim(protocol));
    if(!strcmp(protocol, "TCP"))
      knock->protocol[knock->seqcount] = IPPROTO_TCP;
    else if(!strcmp(protocol, "UDP"))
      knock->protocol[knock->seqcount] = IPPROTO_UDP;
    else if(!strcmp(protocol, "ICMP"))
      knock->protocol[knock->seqcount] = IPPROTO_ICMP;
    else
    {
      printf("Config Error in knock %s: unknown protocol\n",knock->name);
      exit(1);
    }

    if((port = strsep(&num, ":")))
    {
      port = trim(port);
      knock->sequence[knock->seqcount] = (unsigned char)atoi(port);
    }
    else
    {
    /* icmp, no destination port */
      knock->sequence[knock->seqcount] = 0;
    }
    knock->seqcount++;
  }
  return TRUE;
}///parse knock


next   previous   contents