, , Exécuter ACS à partir de votre partition

Vous voulez exécuter ACS à partir de votre IBMI

Exemple : la nouvelle fonction de génération des fichiers XLS

VALUES SYSTOOLS.GENERATE_SPREADSHEET(
PATH_NAME => ‘/home/plb/liste_options.xls’,
FILE_NAME => ‘QAUOOPT’,
LIBRARY_NAME => ‘QGPL’);

Le répertoire /QIBM/ProdData/Access/ACS est en *PUBLIC *EXCLUDE par défaut.
Voici une solution pour ouvrir en gardant la main sur les utilisateurs qui auront droit à cette possibilité

Création de la liste d’autorisation

CRTAUTL AUTL(ACS)
TEXT(‘Exécution ACS sur IBMi’)

On considère que votre installation est par défaut, on applique la liste dessus

CHGAUT OBJ(‘/QIBM/ProdData/Access/ACS’) AUTL(ACS) SUBTREE(*ALL)

Vous pouvez éditer vos utilisateurs

EDTAUTL AUTL(ACS)

Remarque:

Vous pouvez gérer les droits par groupe, mais le plus efficace c’est par utilisateur, vous voyez ainsi qui est autorisé directement.

L’idée n’est pas de rester sur 5250, mais on voit bien que la transition sera longue et pas toujours indolore.
il existe des solutions de rewamping chez plusieurs éditeurs

Mais il existe un produit méconnu chez IBM qui s’appelle IBM i Access – Mobile (5770XH2)

Vous pouvez le télécharger sur le site ESS d’IBM

Voici la procédure à suivre pour l’installer est ici

https://www.ibm.com/support/pages/ibm-i-access-mobile

Une fois votre instance démarrée vous pouvez accèder par l’URL
http://<votre partition>:2011/iamobile/

On se concentrera directement sur la partie 5250 , vous devrez indiquer ce lien

http://<votre partition>:2011/iamobile/iWAStartSession

vous avez un premier écran

puis la mire d’ouverture

et ici un wrkactjob par exemple

Vous avez de nombreuses options d’affichage

Conclusion :

C’est une solution qui peut répondre pour quelques utilisateurs nomades, qui se connectent occasionnellement.
Vous n’avez pas de clients à déployer et vous utilisez des mécanismes de gestion purement #IBMi

, Profil *DISABLED

Souvent j’entends , « Oui le profil existe encore, mais il est désactivé »

Attention, ce status n’est pris en compte que sur certains protocoles , comme 5250 par exemple

Si vous voulez utiliser cette notion sur d’autres protocoles, vous devrez le gérer vous même , voici un exemple pour le protocole DRDA

On va utiliser un programme d’exit, attention pour DRDA c’est dans les attributs réseaux que vous pouvez le déclarer.

Voici la procédure à suivre pour la prise en compte

CHGNETA    DDMACC(EXPLOIT/DRDAEXIT) 
ENDTCPSVR  SERVER(*DDM)             
DLYJOB 30                           
STRTCPSVR  SERVER(*DDM) 

Voici le programme de contrôle DRDAEXIT

pgm parm(&return &data) /*---------------------------------------*/    
/* ce programme vérifie que l'utilisateur de connexion n'est pas */    
/* desactivé                                                     */    
/* Mise en Oeuvre                                                */    
/*           CHGNETA    DDMACC(EXPLOIT/DRDAEXIT)                 */    
/*---------------------------------------------------------------*/    
             DCLPRCOPT  USRPRF(*OWNER)                                 
dcl &return *char 1                                                    
dcl &data *char 200                                                    
dcl &status *char 10                                                   
             DCL        VAR(&USER) TYPE(*CHAR) STG(*DEFINED) LEN(10) + 
                          DEFVAR(&DATA 1)                              
             DCL        VAR(&APP ) TYPE(*CHAR) STG(*DEFINED) LEN(10) + 
                          DEFVAR(&DATA 11)                             
             DCL        VAR(&func) TYPE(*CHAR) STG(*DEFINED) LEN(10) + 
                          DEFVAR(&DATA 21)                             
/* si profil desactivé on refuse */                                    
             RTVUSRPRF  USRPRF(&USER) STATUS(&STATUS)                  
             if cond(&user = '*DISABLED') then(do)                     
             chgvar &return '0' 
             enddo                  
             else do                
             chgvar &return '1'     
             enddo                  
endpgm  

Remarque :

Vous pourrez ajouter d’autres contrôles , par exemple, par rapport au planning d’activation des profils
Votre programme devra être compiler en adoption de droit, avec un profil de droit *SECADM pour avoir droit à la commande RTVUSRPRF

, Intégrité des objets sur votre partition

Sur votre IBMi en version 40 de sécurité, vous pouvez signer vos objets !

Mais, êtes vous sur que tous les objets sur votre machine sont sains

Vous avez une commande qui permet de faire ce contrôle CHKOBJITV.
Demander une sortie, attention ca prendra un certain temps

Exemple :

SBMJOB CMD(CHKOBJITG USRPRF(*ALL) OUTFILE(votre bib/CHKOBJITG))
JOB(CHKOBJITG)
JOBQ(QSYSNOMAX)

Vous pouvez retrouver ces violations c’est la zone aiviol

Sur les programmes , programmes de service etc …

ALTERED – L’objet a été falsifié.
BADSIG – L’objet a une signature numérique qui n’est pas valide.
DMN – Le domaine n’est pas correct pour le type d’objet.
PGMMOD – L’objet exécutable a été falsifié.
BADLIBUPDA – L’attribut de protection de la bibliothèque n’est pas défini correctement.
SCANFSFAIL – L’objet a été analysé par un programme de sortie lié à l’analyse et, au moment de cette dernière demande d’analyse, l’objet a échoué à l’analyse.
Si une violation est enregistrée pour un module de microcode sous licence, le nom de l’objet sera le nom RU à 8 caractères, où le nom RU est le nom d’unité remplaçable du module de microcode sous licence, le nom de la bibliothèque sera vide et le type d’objet sera *LIC. Si une violation de ce type est rencontrée, contactez votre représentant de service pour récupérer.

Sur les fichiers

NOSIG – L’objet peut être signé mais ne possède pas de signature numérique.
NOTCHECKED – L’objet ne peut pas être vérifié, il est en mode débogage, enregistré avec de l’espace de stockage libéré ou compressé.
NOTTRANS – L’objet n’a pas été converti au format actuel ou n’est pas compatible avec la version, la version et le niveau de modification actuels.

Si après votre analyse vous avez des objets ALTERED ou BADSIG essayez de comprendre ce que c’est
Vous aurez beaucoup de NOTTRANS, ils ne sont pas très grave ?

Comment s’en prémunir pour ne plus injecter d’objet non conformes ?

il y a 3 valeurs systèmes à régler

QVFYOBJRST Vérification de l’objet à la restauration

QFRCCVNRST Forçage de la conversion à la restauration

QALWOBJRST Option de restauration des objets sensibles

Voici l’ordre dans lequel , elles s’enchainent

Attention faites des tests

Vous pourrez alors suivre par les audits ces violations
par exemple les types GR,OR,RA,RJ,RO,RP,RQ,RU,RZ sur les restaurations mais pas que …

Conclusion:

Pas de panique, mais on peut trouver des choses bizarres demandez des explications à vos fournisseurs de logiciels
et attention cependant aux objets de domaine *SYSTEM, si vous en avez dans la liste vous devez agir

, , Utilisez DRDA sur #IBMi

Dans ACS vous avez des exemples comme si dessous

Vous indiquer le nom de votre base de données distantes et vous exécuter votre requête sur le système distant.

derrière cette requête ce cache un protocole nommé DRDA , comme ODBC il permet de ce connecté à une base de donnée distante.

Nous allons voir comment le mettre en œuvre .

sur le système source
Vous devez créer une entrée pour la base de données

le plus simple c’est de passer par la commande WRBRDBDIRE , vous ajouterez une connexion IP à votre système distant.

Sur le système cible
Vous devez paramétrer le service par la commande CHGDDMTCPA , il faut avoir le même mode d’authentification que la base de données distante, par défaut user + mot de passe
vous devez démarrer le service STRTCPSVR *DDM

voila c’est tout
vous pouvez à partir de votre système source faire un connect SQL sur votre système cible si vous avez un mot de passe.

Si vous ne voulez pas renseigner de mot de passe comme dans les exemples ACS vous allez devoir utiliser sur votre système source les postes poste d’authentification serveur.
Pour les ajouter vous avez la commande ADDSVRAUTE, vous devrez également avoir mis la valeur système QRETSVRSEC à ‘1’ pour que vos mots de passe soit enregistrés

il est conseillé d’ajouter un poste générique, par exemple QDDMDRDASERVER en indiquant un user et un mot de passe du système cible !

il n’y a pas de commande WRKSRVAUTE mais vous pouvez en trouver une ici https://github.com/Plberthoin/PLB/tree/master/GTOOLS/

Exemple :

A partir de ce moment la mot de passe sera passé directement.

Vous pouvez facilement, par des services sql comparer 2 partitions (valeurs systèmes, fonctions , etc …)

Remarques

Les noms doivent être en majuscule
il est conseillé de mettre un programme d’exit de contrôle
Attention, vous pouvez vous connecter avec un utilisateur *disabled
Les fichier DDM sur IP s’appuient sur cette technologie

, , , Connaitre la bibliothèque du programme en cours

Vous voulez connaitre la bibliothèque d’un programme en cours d’exécution, pour ajouter cette bibliothèque par exemple, pour contextualiser un exit programme, un watcher, un trigger ou pour limiter un environnement prod, versus dev.
Le tout, sans harcoder une bibliothèque qui figera votre code et vos environnements.

Voici 2 exemples

En RPGLE

dcl-ds *N PSDS ;                  
  bibli_du_pgm CHAR(10) POS(81);  
  nom_du_pgm CHAR(10) POS(1);     
 End-ds ;                          
dcl-s present ind ;
// on tente d'ajouter la bibliothèque
 exec sql                                                                  
 call qcmdexc('Addlible ' concat :bibli_du_pgm concat ' *FIRST') ;         
if sqlcode = 0 ;
  present = *on ;
endif ;
// votre traitement ici
// on enlève si on a ajouté 
if present = *on ;
 exec sql                                                                  
 call qcmdexc('Rmvlible ' concat :bibli_du_pgm ) ;         
endif ;

En CLLE

PGM                                                    
            DCL        VAR(&DATA) TYPE(*CHAR) LEN(80)  
            DCL        VAR(&LIB) TYPE(*CHAR) LEN(10)   
            DCL        VAR(&PGM) TYPE(*CHAR) LEN(10)   
            DCL        VAR(&TEMOIN) TYPE(*LGL)
 /* Paramétrage de l'appel */                          
            CHGVAR     VAR(%BIN(&DATA  1 4)) VALUE(80) 
            CHGVAR     VAR(%BIN(&DATA  5 4)) VALUE(80) 
            CHGVAR     VAR(%BIN(&DATA  9 4)) VALUE( 0) 
            CHGVAR     VAR(%BIN(&DATA 13 4)) VALUE( 0) 
 /* Appel de la procédure */                           
            CALLPRC    PRC('_MATPGMNM') PARM(&DATA)    
 /* Extraction des informations  */                    
            chgvar &pgm %SST(&DATA 51 10)              
            chgvar &lib %SST(&DATA 19 10) 
/* ajout de la bibliothèque */
ADDLIBLE &LIB *FIRST
monmsg cpf2103 exec(do)
chgvar &temoin '1'
enddo 
/* Votre traitement ici */
/* on enlève si on a ajouté */
if cond(*not &temoin) then(do)
RMVLIBLE &LIB
enddo          
ENDPGM          

Remarque :

On a mis également le programme en cours dans les exemples

On a mis le code pour enlever la bibliothèque après le traitement, uniquement si c’est notre programme qui l’a ajouté.


En RPGLE si vous avez un fichier vous devrez déclarer votre fichier en USROPN et ouvrir le fichier par un OPEN, après avoir ajouté la bibliothèque

, CPF2225 sur CHGUSRPRF

Il est possible que vous receviez ce message sur un changement de profil ou sur une suppression (DLTUSRPRF ou CHGUSRPRF)

Ca signifie qu’un autre utilisateur verrouille l’AUT (Authorized User Table) par une autre commande liée à la sécurité.

Cette objet s’appelle QSYUPTBL de la bibliothèque QSYS et il est de Type *AUT.

le premier reflexe serait de faire :

==>

WRKOBJLCK OBJ(QSYS/QSYUPTBL)
OBJTYPE(*AUT)

Mais le type *AUT n’est pas supporté dans la commande (sniff !)

Vous devrez donc passer la commande suivante

==>CALL QTNDSPLS (‘QSYS/QSYUPTBL’ *AUT)

qui vous indiquera qui tient cette table :

Exemple

‘STATUS OF ALL JOBS FOR THE ABOVE SPACE LOCATION:        
                                                         
158965/PLB/QPADEV0001                         LSRD  HELD  

Vous devrez déterminer si c’est normal , sinon il faudra arrêter le travail fautif

Pour en savoir plus :

https://www.ibm.com/support/pages/cpf2225-received-running-security-commands

Remarque :

Vous n’avez pas intérêt à faire tourner 2 process de gestion de profil en même temps

, , Protéger APPEL / SYSTEME

Vous voulez protéger vos sessions 5250 de la possibilité de faire un Appel systéme

Vous devez mettre en place un programme d’exit (8 possibles)

QIBM_QWT_SYSREQPGMS

Vous devez ensuite indiquer sur chaque profil les programmes à utiliser

Schéma ci dessous

L’utilisateur quand il appuiera sur APP SYST le programme PGM1 sera appelé

Programme Exit ici le 1 , nom du programme APPSYS



**free
     //  programme QIBM_QWT_SYSREQPGMS contrôle d'accès à la touche
     //  ATTN REQUEST
     // l'utilisateur à ce programme de contrôle son profil il s'exécute
     //     et il n'a pas le droit
      ctl-opt
      DFTACTGRP(*NO) ;
  Dcl-Pi *N;
    Reponse            int(10);
  //                            1 ok
  //                            0 ko
    data               Char(128);
  End-Pi;
  //
    Reponse = 0;
    *inlr = *on ;
GDATA_QRPGLESRC_APPSYS.TXT
Affichage de GDATA_QRPGLESRC_APPSYS.TXT en cours...

Ce programme est simple , il interdit s’il est appelé

Pour ajouter ce programme :

ADDEXITPGM EXITPNT(QIBM_QWT_SYSREQPGMS)

FORMAT(SREQ0100)
PGMNBR(1)
PGM(Votrelib/APPSYS)
REPLACE(NO)

Dans ==>WRKREGINF pour contrôle

Programme de mise à jour des profils qui devront être concernés par le contrôle

**free
//     Programme exit pour protéger appel système
//     exit PGM   QIBM_QWT_SYSREQPGMS
//     Pour que ce programme ce déclenche il faut que vous
//     l'indiquiez au niveau du profil
//     8 programmes possibles ici le 1 correspond au PGMNBR(1)
//     vous devez utiliser l'API  QWTSETPX
ctl-opt
  DFTACTGRP(*NO) ;
// paramètre recu le profil à protéger
Dcl-Pi *N;
  P_user             char(10);
End-Pi;
// prototypage de l'API de mise à jour
Dcl-PR QWTSETPX ExtPgm( 'QWTSETPX');
  nbrent  int(10)     ;
  flags   char(32)    ;
  format  char(8)    ;
  user    char(10)   ;
  erreur  char(32)   ;
End-PR;
// Variables de travail
dcl-s wnbrent  int(10)     ;
dcl-s wflags   char(32)    ;
dcl-s wformat  char(8)    ;
dcl-s werreur  char(32)    ;
// constantes figuratives
dcl-s inact    char(04)  inz(x'00000000') ;
dcl-s actif    char(04)  inz(x'00000001') ;
// Appel du programme
  wformat = 'SREQ0100' ;
  wnbrent = x'00000004' ;
  %subst(wflags :1 : 4) = actif       ;   <<<<< ici
  %subst(wflags :5 : 4) = inact       ;
  %subst(wflags :9 : 4) = inact       ;
  %subst(wflags :13 : 4) = inact       ;
  %subst(wflags :17 : 4) = inact       ;
  %subst(wflags :21 : 4) = inact       ;
  %subst(wflags :25 : 4) = inact       ;
  %subst(wflags :29 : 4) = inact       ;
  QWTSETPX(wnbrent:wflags:wformat:p_user:werreur) ;
  *inlr = *on ;

Programme pour voir les programmes du profil

**free
//
// Lecture des informations sur les profils pour appel système
// sur exit pgm  QIBM_QWT_SYSREQPGMS
// Rappel 8 possibilités qui correspondent au PGMNBR de l'exit PGM
//
ctl-opt
  DFTACTGRP(*NO) ;
// paramétre le profil
Dcl-Pi *N;
  P_user             char(10);
End-Pi;
// API de lecture des postes
Dcl-PR QWTRTVPX ExtPgm( 'QWTRTVPX');
  rcvvar  char(40)     ;
  rcvlen  char(4)    ;
  format  char(8)    ;
  user    char(10)   ;
  erreur  char(32)   ;
End-PR;
// déclaration des variables de travail
dcl-s wrcvlen  char(4) inz(x'00000028') ;
dcl-s wrcvvar char(40) inz(' ')  ;
dcl-s wformat  char(8)    ;
dcl-s werreur  char(32)    ;
dcl-s wflags   char(32)    ;
dcl-s wnbpos   int(10)     ;
// constantes figuratives
dcl-s inact    char(04)  inz(x'00000000') ;
dcl-s actif    char(04)  inz(x'00000001') ;
dcl-s msg      char(50)  inz(' ') ;
dcl-s i        int(10)  inz(0) ;
// Appel de l'API
  wformat = 'SREQ0100' ;
  QWTRTVPX(wrcvvar:wrcvlen:wformat:p_user:werreur) ;
  wnbpos  = 32;  // 8 * 4
// extraction des informations pour les 8 programmes
  wflags = %subst(wrcvvar : 9 : 32) ;
  for i = 1 by 4 to wnbpos   ;
    if  %subst(wflags : i : 4) =  actif ;
      msg = %trim(msg) + '*ON'                    ;
    else ;
      msg = %trim(msg) + '*OFF'                  ;
    endif;
  endfor ;
  // affichage du résulat
  dsply msg  ;
  *inlr = *on ;

Remarque :

L’utilisateur ne reçoit aucun message , mais rien ne se passe

Attention, il ne faut pas le mettre sur tous les profils, mais uniquement ceux qui le nécessitent.

Par exemple une fenêtre bloquante de ressaisie de mot de passe pour une option sensible.

Vous pouvez faire la même chose pour le programme ATTN …

, , , Utilisez les journaux Système

IBM fourni un certain nombre de journaux systèmes que vous pouvez Analyser, la plus part sont dans QUSRSYS et d’autres sont dans QSYS.

Un petit lien ici pour avoir une liste

https://www.ibm.com/docs/fr/i/7.5?topic=journals-working-supplied

Premier exemple, Analyse de l’ajustement des pools mémoires

Mise en œuvre

Valeur système QPFRADJ doit être à 3 ou 2

Vous devez créer le récepteur et le journal

CRTJRNRCV JRNRCV(QUSRSYS/QPFRADJ)
CRTJRN JRN(QSYS/QPFRADJ) JRNRCV(QUSRSYS/QPFRADJ)


Analyse par les fichiers supports ( c’est des fichiers modèles qui sont dans QSYS )
CRTDUPOBJ OBJ(QAWCTPJE) FROMLIB(QSYS) OBJTYPE(*FILE) TOLIB(Votrebib) NEWOBJ(QPFRADJTP)

DSPJRN JRN(QSYS/QPFRADJ) ENTTYP(TP) OUTPUT(*OUTFILE) OUTFILE(Votrebib/QPFRADJTP)

pour analyser le suivi ici du pour des travaux interactifs

SELECT TPPNAM, TPFLG1, TPCSIZ, TPCRES, TPCACT, TPDFLT, TPNFLT,
TPWI, TPAW, TPCJOB, TPAJOB, TPNSIZ, TPNACT
FROM Votrebib/QPFRADJTP
WHERE TPPNAM = ‘*INTERACT’
order by TPDATE, TPTIME

Deuxième exemple, voir les ports filtrés sur votre partition

Analyse par services SQL

create table votrebib.analyse as(
WITH Log_Port AS (
SELECT CAST(ENTRY_DATA AS VARCHAR(1000)) AS entry
FROM TABLE (
QSYS2.DISPLAY_JOURNAL(‘QUSRSYS’, ‘QIPFILTER’, JOURNAL_ENTRY_TYPES => ‘TF’)
)
)
SELECT SUBSTR(entry, 1, 10) AS line,
SUBSTR(entry, 29, 15) AS AdrSrcIp,
SUBSTR(entry, 44, 5) AS SrcPort,
SUBSTR(entry, 49, 15) AS AdrDestIp,
SUBSTR(entry, 64, 5) AS DestPort
FROM Log_Port
) WITH DATA;

Remarques

Certains journaux sont en standard , d’autres devront être démarrés
Si vous n’analysez pas ne les démarrer pas
Pensez à faire le ménage dans les récepteurs si vous les démarrez

Merci à Sylvain pour ca suggestion

, , Trigger sur insert

Vous voulez créer un trigger qui vous indique la création d’un enregistrement dans un fichier par exemple pour superviser, dans notre exemple on enverra un email , il est conseillé de faire un fichier de paramétrage

En CLLE soit le programme Alerte_msg


             PGM        PARM(&BUFFER &BUFLEN)             
/* Paramètres */                                          
             DCL        VAR(&BUFFER) TYPE(*CHAR) LEN(200) 
             DCL        VAR(&BUFLEN) TYPE(*CHAR) LEN(4)   
/* Variables de travail */                                
             DCL        VAR(&USR) TYPE(*CHAR) LEN(10)     
             DCL        VAR(&JOB) TYPE(*CHAR) LEN(10)     
             DCL        VAR(&NBR) TYPE(*CHAR) LEN(06)
             DCL        VAR(&EMAIL) TYPE(*CHAR) LEN(50)
             DCL        VAR(&SUJET) TYPE(*CHAR) LEN(100)
             DCL        VAR(&NOTES) TYPE(*CHAR) LEN(200)
             RTVJOBA    JOB(&JOB) USER(&USR) NBR(&NBR)    
             CHGVAR     VAR(&EMAIL) VALUE('votre@mail.fr')
             CHGVAR     VAR(&SUJET) VALUE('Enregistrement crée')
             CHGVAR     VAR(&NOTES) VALUE('Job :' +
                          *BCAT &NBR *TCAT '/' *TCAT &USR *TCAT '/' +
                          *TCAT &USR)                                    
 SNDSMTPEMM RCP((&EMAIL)) SUBJECT(&SUJET) NOTE(&NOTES) +
             CONTENT(*HTML)            
  MONMSG     MSGID(CPF0000) EXEC(GOTO CMDLBL(ERREUR))
goto fin                                                           
/* Gestion des erreurs  */                                         
erreur:                                                            
             SNDUSRMSG  MSG('Envoi email impossible pour msgint ' +
                          *bcat &job *bcat &usr *bcat &nbr ) +     
                          MSGTYPE(*INFO)                           
fin:                                                               
ENDPGM 

Pour attacher votre programme et enregistrer votre trigger

 ADDPFTRG   FILE(&LIB/REP_VALID) TRGTIME(*AFTER)     
              TRGEVENT(*INSERT) PGM(&LIB/ALERT_MSG) 
              TRGLIB(&LIB) 

&lib sera le nom de votre bibliothèque

En SQL ca créera un programme CEE et ca l’associera au trigger

CREATE OR REPLACE TRIGGER ALERTE_MSG                                   
 AFTER  INSERT ON REP_VALID                                            
 REFERENCING NEW AS N                                                  
 FOR EACH ROW                                                          
 MODE DB2ROW                                                           
-- email destinataire                                                  
 BEGIN                                                                 
DECLARE W_EMAIL CHAR(50);                                              
DECLARE W_SUJET CHAR(100);                                             
DECLARE W_NOTES CHAR(200);                                             
DECLARE EXIT HANDLER FOR SQLSTATE '38501'                              
 RESIGNAL SQLSTATE '38501' SET MESSAGE_TEXT = 'ENVOI MAIL IMPOSSIBLE.';
SET W_NOTES = 'Job : ' concat trim(N.REPNBR)                  
concat '/' concat trim(N.REPUSER) concat '/' concat trim(N.REPJOB) ;
SET W_EMAIL = 'votre@email.fr' ;                 
SET W_SUJET = 'Enregistrement crée' ;   
CALL QCMDEXC('SNDSMTPEMM RCP((''' concat trim(w_email) concat           
''')) SUBJECT(''' concat trim(replace(w_sujet , '''', '"'))             
concat ''') NOTE('''                                                    
concat trim(replace(W_NOTES , '''' , '"')) concat''') CONTENT(*HTML)') ;
END;  

Remarques :

Dans les 2 cas si l’utilisateur n’est pas inscrit à la liste de distribution votre email ne sera pas envoyé
c’est plus simple de gérer l’erreur en CLP.
Si vous devez accéder aux données du buffer ca sera plus rapide et plus simple en SQL ici n.zone

C’est des triggers après , puisque l’information doit être écrite dans tous les cas .