Vous êtes ici:

Menu


Stacks Image 9724
C'est le troisième et dernier article sur les services passifs. Cette fois-ci nous allons utiliser le REST API de Centreon.La méthode que nous utiliserons s'appelle submit avec l'objet centreon_submit_results. L'objet de ce tutoriel est de vous décrire la façon de remonter une information vers un service. Je vous conseille de lire la documentation officielle sur les REST API https://documentation-fr.centreon.com/docs/centreon/en/latest/api/api_rest/index.html. Point important, il est très fortement conseillé de mettre en place le protocole http sécurisé (https) pour chiffrer la communication entre le client et le serveur (voir le chapitre 8). Maj du 17 juillet, les ACL ne sont pas prises en compte pour un utilisateur non-admin.

1 Prérequis

Nous aurons besoin d'un serveur Centreon opérationnel. Pour notre exemple, nous utiliserons un serveur Debian. Pour utiliser l'API Rest en ligne de commande, nous aurons besoin de l'utilitaire suivant : curl. curl est un utilitaire destiné à récupérer des informations d'une ressource accessible grâce à une URL. Il peut être utilisé comme client REST, ce qui nous intéresse dans notre cas.
Il est temps d'installer cet utilitaire sur le serveur Debian :
apt-get install jq curl

2 création de l'utilisateur pour le Rest API

Dans un environnement de production, nous utiliserons un user spécifique. Tout d'abord, créez votre utilisateur.
Stacks Image 37000
C'est le minimum vital pour créer un utilisateur, définissez un mot de passe. Dans l'onglet Centreon Authentication, activez obligatoirement l'API pour la configuration. L'autre option est optionnelle.
Stacks Image 37005
Sauvegardez et votre user est prêt pour les REST Api Centreon.

3 Test d'authentification

Pour utiliser les requêtes de l'API Rest, nous allons nous authentifier pour récupérer un jeton (Token). Celui-ci nous servira pour effectuer toutes les opérations de requêtes qui suivront. Ce Token a une durée de validité de l'orde d'une dizaine de minutes. Voici la commande pour obtenir ce jeton.
curl -s -d "username=admin&password=password" -H "Content-Type: application/x-www-form-urlencoded" -X POST http://192.168.0.250/centreon/api/index.php?action=authenticate
Description des paramètres
-s : mode silence; on n'affiche pas la progression des données
-d : on envoie des données avec la commande POST, dans notre cas l'identifiant du contact et son mot de passe
-H : on ajoute un Header à l'envoie des données
-X : méthode de la requête POST dans notre cas

Notre serveur de supervision a pour adresse IP 192.168.0.250.
Vous obtiendrez ce jeton comme ci-dessous :
{"authToken":"NWEwODhmODA1ZjllMDUuODA4MjAxNTY="}
Pour effectuer une requête, il faut récupérer le résultat situé à droite en enlevant les doubles-quotes.
Stacks Image 37034
Protéger votre token
Attention, le token peut comporter des suites de caractères spéciaux comme ceux-ci : {"authToken":"rSRl\/\/gAniKn5HDebToYgf19Q03RMZmVGl3J2z\/dx10="}. SI vous ne protégez pas votre token avec des simples quotes, vous aurez droit à un message d'erreur.
curl -s 'http://192.168.1.99/centreon/api/index.php?object=centreon_realtime_services&action=list&status=critical' -H 'Content-Type: application/json' -H 'centreon-auth-token: rSRl\/\/gAniKn5HDebToYgf19Q03RMZmVGl3J2z\/dx10='
"Forbidden"

En encadrant votre token de simple quote, vous éviterez ce problème.
curl -s 'http://192.168.1.99/centreon/api/index.php?object=centreon_realtime_services&action=list&status=critical' -H 'Content-Type: application/json' -H 'centreon-auth-token: 'rSRl\/\/gAniKn5HDebToYgf19Q03RMZmVGl3J2z\/dx10=''
[{"host_id":"91","name":"srv-mysql-01","description":"disk-\/usr","service_id":"590","state":"2","state_type":"1","output":"Disk \/usr - used : 152.87 Go - size : 158.00 Go - percent : 96 %\n","perfdata":"used=164145131639o;135720966554;152686087373;0;169651208192 size=169651208192o","max_check_attempts":"3","check_attempt":"3","last_check":"1563179799","last_state_change":"1563177879","last_hard_state_change":"1563177999","acknowledged":"0","criticality":null},{"host_id":"34","name":"mbi1904","description":"DWH-db-content","service_id":"310","state":"2","state_type":"1","output":"[Table mod_bi_hgmonthavailability: EMPTY] [Table mod_bi_hgservicemonthavailability: EMPTY] [Table mod_bi_metricmonthcapacity: EMPTY] [Table mod_bi_metriccentiledailyvalue: EMPTY]\n","perfdata":"","max_check_attempts":"3","check_attempt":"3","last_check":"1563172116","last_state_change":"1563084698","last_hard_state_change":"1563084698","acknowledged":"1","criticality":null}]

4 Configuration de Centreon

Il reste à réaliser la configuration de notre service dans Centreon. Nous aurons besoin d’un service passif et nous utiliserons une version 19.04 avec les plugins pack free.

4.1 Modèle de service passif

Nous utiliserons le modèle déjà créé par les plugins-pack, le modèle generic-passive-service-custom. Les paramètres de ce modèle hérite du modèle en lecture seule generic-passive-service.
Stacks Image 19641

4.1a Création d'un modèle de service passif

Pour info, si vous n'avez pas les plugins pack, les commandes clapi pour créer un modèle de service passif
Centreon-Clapi

centreon -u admin -p password -o STPL -a add -v "service-generique-passif;service-generique-passif;"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;check_period;24x7"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;check_command;check_centreon_dummy"
centreon -u admin -p password -o STPL -a setparam -v 'service-generique-passif;check_command_arguments;!0!OK'
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;max_check_attempts;1"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;normal_check_interval;1"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;retry_check_interval;1"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;active_checks_enabled;0"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;passive_checks_enabled;1"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;notifications_enabled;1"
centreon -u admin -p password -o STPL -a addcontactgroup -v "service-generique-passif;Supervisors"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;notification_interval;0"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;notification_period;24x7"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;notification_options;w,c,r,f,s"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;first_notification_delay;0"
centreon -u admin -p password -o STPL -a setparam -v "service-generique-passif;service_check_freshness;1"

4.2 le service passif test_restapi

4.2.a configuration

Nous pourrions créer un template de service restapi mais pour notre exemple, il sera plus simple de créer directement un service. Nous appellerons ce service test_restapi et il sera associé à l’hôte testbuster.
Stacks Image 19700
création du service test_nsca

4.2.b application de la configuration

Une fois la configuration appliquée sur votre serveur, vous pouvez visualiser le service dans la page Monitoring Services.
Stacks Image 10457
Le service est mode pending et restera dans ce mode tant qu’une opération d’acquittement ou d’envoi de commande Rest API ne soient pas réalisés.
Stacks Image 19773
Acquittez le service.
Stacks Image 19786
Votre service est opérationnel.

5 Premier test

Nous allons envoyer du serveur Debian, les informations au service passif test_restapi. Cette opération se fera en deux fois : authentification et envoie des données.

5.1 état warning

Récupération du token
curl -s -d "username=userapi&password=centreon" -H "Content-Type: application/x-www-form-urlencoded" -X POST http://192.168.1.99/centreon/api/index.php?action=authenticate
{"authToken":"+Uu5BmRb9\/Y\/ijak0BmI9pB6ubfIxHMTe8XFieIQD3k="}
On envoie les informations sous la forme json, le champ perfdata est optionnel et permet d'obtenir des graphes de performances. J'utilise la commande date pour obtenir un timestamp Unix afin d'initialiser le date de vérification.
curl -X POST 'http://192.168.1.99/centreon/api/index.php?action=submit&object=centreon_submit_results' -H 'Content-Type: application/json'  -H 'centreon-auth-token: '+Uu5BmRb9\/Y\/ijak0BmI9pB6ubfIxHMTe8XFieIQD3k='' -d '{ "results": [{"updatetime": "'`date +%s`'","host": "testbuster","service": "test_restapi","status": "1","output": "The service is in WARNING state","perfdata": "perf=20" }]}'
{"results":[{"code":202,"message":"The status send to the engine"}]}
Vous trouverez dans les logs du moteur
[1563209510] [786] EXTERNAL COMMAND: PROCESS_SERVICE_CHECK_RESULT;testbuster;test_restapi;1;The service is in WARNING state|perf=20
[1563209512] [786] PASSIVE SERVICE CHECK: testbuster;test_restapi;1;The service is in WARNING state
[1563209512] [786] SERVICE ALERT: testbuster;test_restapi;WARNING;HARD;1;The service is in WARNING state
Stacks Image 19830

5.2 état critique

Récupération du token
curl -s -d "username=userapi&password=centreon" -H "Content-Type: application/x-www-form-urlencoded" -X POST http://192.168.1.99/centreon/api/index.php?action=authenticate
{"authToken":"ZHrlieTX\/Z4e3F+NhHVsyM1ZbZhMzCQQRLXysNuVM6A="}
On envoie les informations sous la forme json, le champ perfdata est optionnel et permet d'obtenir des graphes de performances. J'utilise la commande date pour obtenir un timestamp Unix afin d'initialiser le date de vérification.
curl -X POST 'http://192.168.1.99/centreon/api/index.php?action=submit&object=centreon_submit_results' -H 'Content-Type: application/json'  -H 'centreon-auth-token: 'ZHrlieTX\/Z4e3F+NhHVsyM1ZbZhMzCQQRLXysNuVM6A='' -d '{ "results": [{"updatetime": "'`date +%s`'","host": "testbuster","service": "test_restapi","status": "2","output": "The service is in CRITICAL state","perfdata": "perf=200" }]}'
{"results":[{"code":202,"message":"The status send to the engine"}]}
Vous trouverez dans les logs du moteur
[1563209754] [786] EXTERNAL COMMAND: PROCESS_SERVICE_CHECK_RESULT;testbuster;test_restapi;2;The service is in CRITICAL state|perf=200
[1563209757] [786] PASSIVE SERVICE CHECK: testbuster;test_restapi;2;The service is in CRITICAL state
[1563209757] [786] SERVICE ALERT: testbuster;test_restapi;CRITICAL;HARD;1;The service is in CRITICAL state
Stacks Image 19858

5.3 état OK

Récupération du token
curl -s -d "username=userapi&password=centreon" -H "Content-Type: application/x-www-form-urlencoded" -X POST http://192.168.1.99/centreon/api/index.php?action=authenticate
{"authToken":"8dTzREtjKYwejbZYnbo1Wqds8dIN5Z9KhMaWxbeBtes="}
curl -X POST 'http://192.168.1.99/centreon/api/index.php?action=submit&object=centreon_submit_results' -H 'Content-Type: application/json'  -H 'centreon-auth-token: '8dTzREtjKYwejbZYnbo1Wqds8dIN5Z9KhMaWxbeBtes='' -d '{ "results": [{"updatetime": "'`date +%s`'","host": "testbuster","service": "test_restapi","status": "0","output": "The service is in OK state","perfdata": "perf=1" }]}'
{"results":[{"code":202,"message":"The status send to the engine"}]}
Vous trouverez dans les logs du moteur
On envoie les informations sous la forme json, le champ perfdata est optionnel et permet d'obtenir des graphes de performances. J'utilise la commande date pour obtenir un timestamp Unix afin d'initialiser le date de vérification.
[1563210632] [786] EXTERNAL COMMAND: PROCESS_SERVICE_CHECK_RESULT;testbuster;test_restapi;0;The service is in OK state|perf=1
[1563210632] [786] PASSIVE SERVICE CHECK: testbuster;test_restapi;0;The service is in OK state
[1563210632] [786] SERVICE ALERT: testbuster;test_restapi;OK;HARD;1;The service is in OK state
Stacks Image 19845

6 Création d'un script send_apirest.sh

Pour envoyer les données à un service passif, je vous propose un script qui vous simplifiera la vie.
#!/bin/bash
# send_apirest.sh
# version 1.00
# date 16/07/2019


# Usage info
show_help() {
cat << EOF
Usage: ${0##*/} -u=<user centreon> -p=<passwd centreon> -r=<url Rest API Centreon> -H=<Host> -s=<Service> -S=<Status> -o=<Output> [ -d=<Perfadata> -i=<yes/no> ]
This program send submit to Centreon Rest API
    -u|--user User Centreon.
    -p|--password Password Centreon.
    -r|--url url Rest API Centreon ex : http(s)://<ip centreon>
    -H|--Hostname Hostname of service
    -s|--service passive service
    -S|--Status Service status 0 Ok,1 Warning,2 Critical,3 Unknown
    -o|--output Service message
    -d|--perfdata Service perfdata optionnal
    -i|--insecure for only https optionnal
    -h|--help     help
EOF
}

for i in "$@"
do
  case $i in
    -u=*|--user=*)
      USER_CENTREON="${i#*=}"
      shift # past argument=value
      ;;
    -p=*|--password=*)
      PWD_CENTREON="${i#*=}"
      shift # past argument=value
      ;;
    -r=*|--url=*)
      URL="${i#*=}"
      shift # past argument=value
      ;;
    -H=*|--Hostname=*)
      HOSTNAME="${i#*=}"
      shift # past argument=value
      ;;
    -s=*|--service=*)
      SERVICE="${i#*=}"
      shift # past argument=value
      ;;
    -S=*|--Status=*)
      STATUS="${i#*=}"
      shift # past argument=value
      ;;
    -o=*|--output=*)
      OUTPUT="${i#*=}"
      shift # past argument=value
      ;;
    -d=*|--perfdata=*)
      PERFDATA="${i#*=}"
      shift # past argument=value
      ;;
    -i=*|--insecure=*)
      INSECURE="${i#*=}"
      shift # past argument=value
      ;;
    -h|--help)
      show_help
      exit 2
      ;;
    *)
            # unknown option
    ;;
  esac
done

# Check for missing parameters
if [[ -z "$USER_CENTREON" ]] || [[ -z "$PWD_CENTREON" ]] || [[ -z "$URL" ]] || [[ -z "$HOSTNAME" ]] || [[ -z "$SERVICE" ]] || [[ -z "$STATUS" ]] || [[ -z "$OUTPUT" ]]; then
    echo "Missing parameters!"
    show_help
    exit 2
fi

# Check yes/no
if [[ $INSECURE =~ ^[yY][eE][sS]|[yY]$ ]]; then
  INSECURE="--insecure "
else
  INSECURE=""
fi

CURL="/usr/bin/curl"
JQ="/usr/bin/jq"
SED="/bin/sed"

TOKEN=`$CURL -s $INSECURE -s -d "username=$USER_CENTREON&password=$PWD_CENTREON" -H "Content-Type: application/x-www-form-urlencoded" -X POST $URL/centreon/api/index.php?action=authenticate | $JQ '.["authToken"]'| $SED -e 's/^"//' -e 's/"$//'`

TIMESTAMP=`date +%s`

if [[ -z "$PERFDATA" ]]; then
   RESULT=`$CURL -s $INSECURE -X POST $URL'/centreon/api/index.php?action=submit&object=centreon_submit_results' -H 'Content-Type: application/json'  -H 'centreon-auth-token: '${TOKEN}'' -d '{ "results": [{"updatetime": "'$TIMESTAMP'","host": "'"${HOSTNAME}"'","service": "'"${SERVICE}"'","status": "'$STATUS'","output": "'"${OUTPUT}"'" }]}'`
else
   RESULT=`$CURL -s $INSECURE -X POST $URL'/centreon/api/index.php?action=submit&object=centreon_submit_results' -H 'Content-Type: application/json'  -H 'centreon-auth-token: '${TOKEN}'' -d '{ "results": [{"updatetime": "'$TIMESTAMP'","host": "'"${HOSTNAME}"'","service": "'"${SERVICE}"'","status": "'$STATUS'","output": "'"${OUTPUT}"'","perfdata": "'"${PERFDATA}"'" }]}'`
fi
echo $RESULT
Test sans données de performances
./send_apirest.sh -u=userapi -p=centreon -r=http://192.168.1.99 -H=testbuster -s=test_restapi -S=0 -o="The service is OK state"
{"results":[{"code":202,"message":"The status send to the engine"}]}
Test avec données de performances
./send_apirest.sh -u=userapi -p=centreon -r=http://192.168.1.99 -H=testbuster -s=test_restapi -S=1 -o="The service is WARNING state" -d="perf=100"
{"results":[{"code":202,"message":"The status send to the engine"}]}

7 Test avec un poller distant

Afin de bien comprendre le fonctionnement du Rest API Centreon avec un poller distant, voici un petit test. Nous avons un Central et un poller distant. Celui-ci supervise le serveur webmailserver. Cette hôte comprend un service passif TRAP_LINUX. Nous allons envoyer une information par l'intermédiaire d'une commande Rest API. Cette commande sera envoyé au Central.
./send_apirest.sh -u=admin -p=password -r=http://192.168.1.250 -H=webmailserver -s=TRAP_LINUX -S=1 -o="The service is WARNING state"
{"results":[{"code":202,"message":"The status send to the engine"}]}
Le processus centcore étant arrêté, on peut lire le fichier centcore.cmd. La commande externe est en attente pour être envoyé sur le poller distant.
cat /var/lib/centreon/centcore.cmd
EXTERNALCMD:2:[1563265647] PROCESS_SERVICE_CHECK_RESULT;webmailserver;TRAP_LINUX;1;The service is WARNING state|
Relançons le service centcore. La commande sera envoyée automatiquement sur le bon poller. Voici les logs de ce poller.
[1563265041] [359] EXTERNAL COMMAND: PROCESS_SERVICE_CHECK_RESULT;webmailserver;TRAP_LINUX;1;The service is WARNING state|
[1563265041] [359] PASSIVE SERVICE CHECK: webmailserver;TRAP_LINUX;1;The service is WARNING state
[1563265041] [359] SERVICE ALERT: webmailserver;TRAP_LINUX;WARNING;HARD;1;The service is WARNING state
Et le résultat sur l'IHM de Centreon.
Stacks Image 37083

8 La sécurité avec les Rest API

Avec les Rest API, nous utilisons le protocole http. Le problème avec ce protocole (port 80) est qu'il affiche toute la communication non-chiffrée. Avec un simple tcpdump, il est possible de récupérer le couple user/password ou le token. Je vous dit pas la tête de votre RSI si vous utilisez ce moyen de communication sur Internet. Exemple, on écoute les flux à destination du serveur Central :
# tcpdump -i ens18 dst 192.168.1.99 -v -A
Voici un extrait des informations affichées en clair
16:33:09.020157 IP (tos 0x0, ttl 64, id 23564, offset 0, flags [DF], proto TCP (6), length 274)
    vmbuster-1.home.48412 > centreon1910.home.http: Flags [P.], cksum 0x8507 (incorrect -> 0xc007), seq 0:222, ack 1, win 229, options [nop,nop,TS val 3087994613 ecr 92228622], length 222: HTTP, length: 222
	POST /centreon/api/index.php?action=authenticate HTTP/1.1
	Host: 192.168.1.99
	User-Agent: curl/7.64.0
	Accept: */*
	Content-Type: application/x-www-form-urlencoded
	Content-Length: 34

	username=userapi&password=centreon[!http]
…………..
16:33:09.398239 IP (tos 0x0, ttl 64, id 3793, offset 0, flags [DF], proto TCP (6), length 486)
    vmbuster-1.home.48414 > centreon1910.home.http: Flags [P.], cksum 0x85db (incorrect -> 0xec93), seq 0:434, ack 1, win 229, options [nop,nop,TS val 3087994991 ecr 92228993], length 434: HTTP, length: 434
	POST /centreon/api/index.php?action=submit&object=centreon_submit_results HTTP/1.1
	Host: 192.168.1.99
	User-Agent: curl/7.64.0
	Accept: */*
	Content-Type: application/json
	centreon-auth-token: TY1j1dbim5IfK4/GE02qSQFegnZGKtaO5uN3OW4CStE=
	Content-Length: 170

	{ "results": [{"updatetime": "1563287589","host": "testbuster","service": "test_restapi","status": "1","output": "The service is WARNING state","perfdata": "perf=100" }]}[!http]
Au vu des informations affichées en gras, il est impératif de passer en https (port 443) lorsque vous utilisez les Rest API. En voici la preuve sur un autre serveur Central en https.
16:25:03.654710 IP (tos 0x0, ttl 64, id 7863, offset 0, flags [DF], proto TCP (6), length 569)
    vmsocle.duchmol.net.58328 > 10.0.3.70.https: Flags [P.], cksum 0x1d02 (incorrect -> 0x167f), seq 0:517, ack 1, win 229, options [nop,nop,TS val 1467254 ecr 5850416], length 517
E..9..@.@..1
...
..F....f.}...m............
..cv.YE0..............p..}.M.L....?...b.bXq.....:....v.0.,.(.$...
.....k.j.9.8.....2...*.&.......=.5.../.+.'.#...    .....g.@.3.2.....E.D.1.-.).%.......<./...A.............
.....].........
.4.2.............    .
..................................... .........................................................................................................................................................................................................................................................................................
16:25:03.661093 IP (tos 0x0, ttl 64, id 7864, offset 0, flags [DF], proto TCP (6), length 52)
    vmsocle.duchmol.net.58328 > 10.0.3.70.https: Flags [.], cksum 0x1afd (incorrect -> 0x4414), ack 1390, win 251, options [nop,nop,TS val 1467256 ecr 5850427], length 0
E..4..@.@..6
...
..F....f.....sV...........
..cx.YE;
16:25:03.663406 IP (tos 0x0, ttl 64, id 7865, offset 0, flags [DF], proto TCP (6), length 246)
    vmsocle.duchmol.net.58328 > 10.0.3.70.https: Flags [P.], cksum 0x1bbf (incorrect -> 0xa54d), seq 517:711, ack 1390, win 251, options [nop,nop,TS val 1467256 ecr 5850427], length 194
E.....@.@..s
...
..F....f.....sV...........
..cx.YE;............A....~L..E...5.i......)...    E.....[....0.8.Ok...a.;.V]GI.s,..l..........4.E2.........."\ci;..8..N.     
..x..
....^........4ac..!....bH..........(N..~......A..u$..gMY.G!0..k.....T.BRqrO.
16:25:03.665419 IP (tos 0x0, ttl 64, id 7866, offset 0, flags [DF], proto TCP (6), length 298)
    vmsocle.duchmol.net.58328 > 10.0.3.70.https: Flags [P.], cksum 0x1bf3 (incorrect -> 0xe2ed), seq 711:957, ack 1441, win 251, options [nop,nop,TS val 1467257 ecr 5850431], length 246
E..*..@.@..>
...
..F....f.....s............
..cy.YE?.....N..~..........5..S.S...d..._fk..0Z.{.....U...?.t.....O.......a.........    ..<..p\i.R.........v.A....T+V9<.z..(z.y.........='.....)....t.t..D.<.}......../.a.XX....`..\<........^.Q.#...+...C..>..<...w..(m.....e...$.......4.6.cA.`.;1...@..;..z:}.
16:25:03.702235 IP (tos 0x0, ttl 64, id 7867, offset 0, flags [DF], proto TCP (6), length 83)
    vmsocle.duchmol.net.58328 > 10.0.3.70.https: Flags [P.], cksum 0x1b1c (incorrect -> 0x2c7d), seq 957:988, ack 1750, win 273, options [nop,nop,TS val 1467266 ecr 5850468], length 31
E..S..@.@...
...
..F....f.....t............
..c..YEd.....N..~.......9...$....s.*...

La connexion est entièrement chiffrée, on ne peut plus voir le mot de passe et token en clair. J'ai rajouté une option (i ou insecure) pour les certificats non authentifié par un tiers de confiance (autorité de Certification de confiance) pour mon script send_apirest.sh
Ainsi ce termine ce tutoriel sur les API Rest et les services passifs.
 Vous êtes ici:

Nous utilisons des cookies pour nous permettre de mieux comprendre comment le site est utilisé. En continuant à utiliser ce site, vous acceptez cette politique.