/*
This file is part of Anastasis
Copyright (C) 2021 Anastasis SARL
Anastasis 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 3, or (at your option) any later version.
Anastasis 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
Anastasis; see the file COPYING.GPL. If not, see
*/
/**
* @file reducer/validation_ES_DNI.c
* @brief validation logic for Spanish Documento Nacional de Identidad numbers, and Número de Identificación de Extranjeros
* @author Christian Grothoff
*
* Examples:
* 12345678Z, 39740191D, 14741806W, X8095495R
*/
#include
#include
#include
/**
* Function to validate a Spanish CIF number.
*
* @param civ number to validate
* @return true if validation passed, else false
*/
static bool
validate_cif (const char *cif)
{
size_t slen = strlen (cif);
char letter = cif[0];
const char *number = &cif[1];
char control = cif[slen - 1];
unsigned int sum = 0;
if (9 != slen)
return false;
for (unsigned int i = 0; i < slen - 2; i++)
{
unsigned int n = number[i] - '0';
if (n >= 10)
return false;
if (0 == (i % 2))
{
n *= 2;
sum += n < 10 ? n : n - 9;
}
else
{
sum += n;
}
}
sum %= 10;
if (0 != sum)
sum = 10 - sum;
{
char control_digit = "0123456789"[sum];
char control_letter = "JABCDEFGHI"[sum];
switch (letter)
{
case 'A':
case 'B':
case 'E':
case 'H':
return control == control_digit;
case 'N':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'W':
return control == control_letter;
default:
return (control == control_letter) ||
(control == control_digit);
}
}
}
/**
* Function to validate a Spanish DNI number.
*
* See https://www.ordenacionjuego.es/en/calculo-digito-control
*
* @param dni_number number to validate (input)
* @return true if validation passed, else false
*/
bool
ES_DNI_check (const char *dni_number)
{
const char map[] = "TRWAGMYFPDXBNJZSQVHLCKE";
unsigned int num;
char chksum;
unsigned int fact;
char dummy;
if (strlen (dni_number) < 8)
return false;
switch (dni_number[0])
{
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
/* CIF: [A-W]\d{7}[0-9A-J] */
/* CIF is for companies, we only take those
of individuals here! */
return false; /* CIV is not allowed! */
case 'M':
/* special NIE, with CIF validation (?),
but for individuals, see
https://www.strongabogados.com/tax-id-spain.php */
return validate_cif (dni_number);
case 'X':
case 'Y':
case 'Z':
/* NIE */
fact = dni_number[0] - 'X';
/* 7 or 8 digits */
if (2 == sscanf (&dni_number[1],
"%8u%c%c",
&num,
&chksum,
&dummy))
{
num += fact * 100000000;
}
else if (2 == sscanf (&dni_number[1],
"%7u%c%c",
&num,
&chksum,
&dummy))
{
num += fact * 10000000;
}
else
{
return false;
}
break;
default:
fact = 0;
/* DNI */
if (2 != sscanf (dni_number,
"%8u%c%c",
&num,
&chksum,
&dummy))
return false;
break;
}
if (map[num % 23] != chksum)
return false;
return true;
}