Première version stable
master V1.0.0
nmorin 2 years ago
commit d597b1a5bf

@ -0,0 +1,16 @@
FROM debian:11
RUN apt update -y && apt install -y --no-install-recommends openssl apache2 php libapache2-mod-php sqlite3 php-sqlite3 && \
apt clean && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /etc/postfix/ && \
rm /var/www/html/index.html && \
openssl req -x509 -nodes -days 5000 -newkey rsa:4096 -out /etc/ssl/certs/ssl-cert-snakeoil.pem -keyout /etc/ssl/private/ssl-cert-snakeoil.key -subj "/C=FR/ST=Paris/L=Paris/O=Courtail/OU=Courtail/CN=127.0.0.1" && \
a2enmod ssl && \
a2ensite default-ssl.conf
COPY . /var/www/html/
RUN mv /var/www/html/outils/conf/php.ini /etc/php/7.4/apache2 && \
mv /var/www/html/outils/conf/apache2.conf /etc/apache2/ && \
chmod +x /var/www/html/outils/genenv.sh && \
/var/www/html/outils/genenv.sh
CMD /usr/sbin/apachectl -D FOREGROUND

@ -1,3 +1,65 @@
# Courtail
Interface WEB d'administration pour le serveur de courriel : https://doc.ycharbi.fr/index.php/Serveur_de_courriels
Interface WEB d'administration pour le serveur de courriel *Postfix* dont la documentation est disponible sur [doc.ycharbi.fr](https://doc.ycharbi.fr/index.php/Serveur_de_courriels).
## Fonctionnalités
L'objectif de Courtail est de permettre le paramétrage de certaines fonctionnalités de messagerie tel que :
* ajout, activation/désactivation, suppression des alias virtuels
* ajout, activation/désactivation, suppression des domaines
* ajout, activation/désactivation, suppression des adresses en liste noire
* changement de mot de passe des utilisateurs
* exportation/importation des alias virtuels d'un utilisateur
* exportation/importation des données (à la carte) du site
Deux types d'utilisateurs sont supportés et ont un accès différent à ces fonctionnalités :
* Administrateur : à accès à la totalité des fonctions du site et peut se faire passer momentanément pour un autre utilisateur afin de modifier ses paramètres sans avoir à s'y connecter
* Utilisateur : n'a accès qu'à ses propres alias virtuels. Il peux en créer, les activer/désactiver, les supprimer, les sauvegarder et les restaurer. Il ne peut également modifier que son propre mot de passe en fournissant son actuel
## Dépendances du projet
Pour Debian 11 (Bullseye) :
`apt install --no-install-recommends apache2 php php-sqlite3`
## Environnement de développement
L'outil est déployable rapidement dans un environnement de test ou via Docker.
Un schéma logique de celui-ci est disponible [ici](outils/schémas/Courtail-schéma.pdf).
### Via Docker
Après avoir dupliqué le projet, réaliser les commandes suivantes :
`bash ./outils/rundocker.sh`
La page WEB est disponible sur [http://127.0.0.1:8080](http://127.0.0.1:8080) et [https://127.0.0.1:4443](http://127.0.0.1:8080).
#### Utilisateurs par défaut
Plusieurs utilisateurs sont créés dans notre exemple :
|Utilisateurs |Mot de passe|Rôle |
|---------------|------------|--------------|
|demo@exemple.fr|demo |Administrateur|
|toto@exemple.fr|toto |Administrateur|
|tata@exemple.fr|tata |Utilisateur |
*La définition d'un domaine par défaut dans la section dédiée du site permet l'usage d'identifiants courts (demo = demo@exemple.fr).*
## Galeries
![Page de connexion](outils/galerie/courtail-galerie-1.png)
![Page gestion des alias virtuels](outils/galerie/courtail-galerie-2.png)
![Page gestion des domaines](outils/galerie/courtail-galerie-3.png)
![Page liste noire des destinataires](outils/galerie/courtail-galerie-4.png)
![Page liste noire des expéditeurs](outils/galerie/courtail-galerie-5.png)
![Page des paramètres](outils/galerie/courtail-galerie-6.png)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,485 @@
/*!
* Bootstrap Reboot v5.1.3 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-bg: #fff;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
direction: ltr /* rtl:ignore */;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,482 @@
/*!
* Bootstrap Reboot v5.1.3 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-bg: #fff;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
direction: ltr ;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,36 @@
/* Style concernant la confirmation de suppression d'un alias virtuel */
.confirmation-suppression {
display: none;
color: #fff;
text-align: center;
text-decoration: none;
background-color: #dc3545;
}
.confirmation-suppression:hover {
color: #fff;
background-color: #bb2d3b;
border-color: #b02a37;
}
.input-checkbox-supprimer-valeur:checked ~ .confirmation-suppression {
display: inline;
}
/*.input-label-supprimer-valeur {
color: #ffffff;
background: #e23026;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
}*/
.visuellement-cache {
position: absolute;
left: -100vw;
}
/* Sources :
* https://stackoverflow.com/questions/6019845/show-hide-div-on-click-with-css
* https://dabblet.com/gist/1506530
* https://css-tricks.com/the-checkbox-hack/
*/

@ -0,0 +1,49 @@
.champ-interrupteur {
display: flex;
overflow: hidden;
}
.champ-interrupteur input {
position: absolute !important;
clip: rect(0, 0, 0, 0);
height: 1px;
width: 1px;
border: 0;
overflow: hidden;
}
.champ-interrupteur label {
background-color: #e4e4e4;
border: 1px solid rgba(0, 0, 0, 0.2);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px rgba(255, 255, 255, 0.1);
transition: all 0.1s ease-in-out;
width: 38px;
height: 34px;
}
.champ-interrupteur label:hover {
cursor: pointer;
}
.champ-interrupteur input:checked + .interrupteur-allumer {
background-color: #0d6efd;
box-shadow: none;
}
.champ-interrupteur input:checked + .interrupteur-eteint {
background-color: #dc3545;
box-shadow: none;
}
.champ-interrupteur label:first-of-type {
border-radius: 4px 0 0 4px;
}
.champ-interrupteur label:last-of-type {
border-radius: 0 4px 4px 0;
}
.champ-interrupteur.disabled {
pointer-events: none;
opacity: .65;
}

@ -0,0 +1,115 @@
body {
background-color: #f8f9fa;
margin-bottom: 100px;
}
#contenu_auth {
max-width: 380px;
margin: auto;
}
#contenu_auth #envoyer {
width: 100%;
}
#logo-accueil {
text-align: center;
margin: 50px;
}
#logo-accueil img {
width: 200px;
}
#logo-accueil h1 {
color: #FFF;
font-size: 40pt;
}
.alert img {
height: 1.5em;
margin-right: 0.3em;
}
.alert {
padding: 10px;
}
.bouton-flotant {
position: fixed;
bottom: 20px;
right: 20px;
background-color: #0d6efd;
color: white;
border-radius: 50%;
width: 60px;
height: 60px;
border: 2px solid #0d6efd;
animation-name: zoom;
animation-duration: .2s;
}
@keyframes zoom {
0% {
transform: scale(0, 0);
}
100% {
transform: scale(1, 1);
}
}
@keyframes dezoom {
0% {
transform: scale(1, 1);
}
100% {
transform: scale(0, 0);
}
}
.bouton-flotant.desactiver{
animation-name: dezoom;
animation-duration: .2s;
animation-fill-mode: forwards;
}
.contour-pastille {
margin-top: 5px;
}
.interieur-pastille {
margin: 10px;
}
.titre-pastille {
width: calc(100% - 122px);
text-align: center;
padding-top: 4px;
padding-left: 10px;
padding-right: 10px;
}
.bouton-ajout{
padding: .375rem .475rem;
}
.alert {
margin-bottom: 0;
}
.interieur-pastille-bleu {
background-color: #cfe2ff;
}
.chevron-dst {
height: 1em;
padding-bottom: 0.1em;
}
.fond-arc-noire{
background-color: #343a40;
}
.max-hauteur-pastille{
height: calc(100% - 1.5em);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,34 @@
/*
* Page de fonctions JavaScript afférentes aux filtres de chaînes de carractères.
**/
/*
* Fonction permettant de filtrer la liste des alias virtuels d'un utilisateur au file des caractères entrés dans le formulaire de création idoine.
**/
function filtreValeurs() {
// Déclaration des variables
var input, filter, divgroupe, divunitaire, a, i, txtValue;
input = document.getElementById('nom_pour_filtre');
filter = input.value.toUpperCase();
divgroupe = document.getElementById("liste_pour_filtre");
divunitaire = divgroupe.getElementsByClassName('col-md-6');
// Pour chaque "divunitaire", comparer le contenu (actualisé à chaque frappe) du "<input>" "nom_alias" avec le texte du "<p>" qu'il contient
// Si le contenu du champ de texte est vide, n'en masquer aucun. Si le motif correspond, masquer les autres
for (i = 0; i < divunitaire.length; i++) {
a = divunitaire[i].getElementsByTagName("span")[0];
txtValue = a.textContent || a.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
divunitaire[i].style.display = "";
} else {
divunitaire[i].style.display = "none";
}
}
}
/*
* Fonction permettant de valider (soumettre le formulaire) le choix de l'utilisateur pour en administrer les possessions.
**/
function autoSoumission() {
document.getElementById("form_choix_utilisateur").submit();
}

@ -0,0 +1,27 @@
/*
* Page de fonctions JavaScript afférentes à l'affichage dynamique d'éléments.
**/
/*
* Fonction permettant d'afficher et de masquer le bouton d'enregistrement d'un changement d'état pour les alias virtuels, domaines et entrées de liste noire d'un utilisateur.
**/
function activationBoutonFlotant(lui) {
const listedesentrees = [];
if (listedesentrees.find(function(valeur) {return valeur == lui.name;})) {
for( var i = 0; i < listedesentrees.length; i++){
if ( listedesentrees[i] === lui.name) {
listedesentrees.splice(i, 1);
break;
}
}
} else {
listedesentrees.push(lui.name);
};
if (listedesentrees.length > 0){
document.getElementById("enregistrer-interrupteur").classList.remove("desactiver");
document.getElementById("enregistrer-interrupteur").style.display = "block";
}else{
document.getElementById("enregistrer-interrupteur").classList.add("desactiver");
}
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#664d03" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-triangle"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 419 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#842029" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-triangle"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 419 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#0f5132" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-circle"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>

After

Width:  |  Height:  |  Size: 323 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg>

After

Width:  |  Height:  |  Size: 270 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-down-right"><polyline points="15 10 20 15 15 20"></polyline><path d="M4 4v7a4 4 0 0 0 4 4h12"></path></svg>

After

Width:  |  Height:  |  Size: 318 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>

After

Width:  |  Height:  |  Size: 357 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-send"><rect style="fill:#343a40;stroke-width:1.99937;stroke:none" id="rect340" width="24" height="24" x="0" y="0" ry="2.4611638" /><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>

After

Width:  |  Height:  |  Size: 435 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#084298" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>

After

Width:  |  Height:  |  Size: 342 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>

After

Width:  |  Height:  |  Size: 371 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mail"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>

After

Width:  |  Height:  |  Size: 354 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 296 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-save"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>

After

Width:  |  Height:  |  Size: 384 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-send"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>

After

Width:  |  Height:  |  Size: 309 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash-2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 440 B

@ -0,0 +1,60 @@
<?php
/**
* Page importé intégrant le menu de navigation dans la page d'administration centrale.
*/
if (isset($_GET['page']) && !empty($_GET['page'])) {
?>
<nav class="navbar navbar-expand-lg navbar-dark fond-arc-noire">
<div class="container-fluid">
<a class="navbar-brand" href="/pages/gestion/administration.php?page=accueil"><img src="/fichiers/svg/send.svg" alt="" width="24" class="d-inline-block line-text-top me-1">Courtail</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link <?php if ($_GET['page'] == 'accueil' || $_GET['page'] == 'alias') {echo 'active';} ?>" href="/pages/gestion/administration.php?page=alias">Alias</a>
</li>
<?php if (testPrivileges()=="administrateur") { ?>
<li class="nav-item">
<a class="nav-link <?php if ($_GET['page'] == 'domaines') {echo 'active';} ?>" href="/pages/gestion/administration.php?page=domaines">Domaines</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle <?php if ($_GET['sousPage'] == 'destinataires' || $_GET['sousPage'] == 'expediteurs') {echo 'active';} ?>" href="/pages/gestion/administration.php?page=listes_noires&sousPage=destinataires" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Listes noires
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="/pages/gestion/administration.php?page=listes_noires&sousPage=destinataires">Destinataires</a></li>
<li><a class="dropdown-item" href="/pages/gestion/administration.php?page=listes_noires&sousPage=expediteurs">Expéditeurs</a></li>
</ul>
</li>
<?php } ?>
</ul>
</div>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link <?php if ($_GET['page'] == 'parametres') {echo 'active';} ?>" href="/pages/gestion/administration.php?page=parametres">Paramètres</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/services/traitements/utilisateurs/deconnexion.php">Déconnexion</a>
</li>
</ul>
</div>
</div>
</nav>
<?php
}
/*if (isset($_GET['message']) && !empty($_GET['message'])) {
switch ($_GET['message']) {
case "bienvenue":
echo "<div class=\"alert alert-success\" role=\"alert\">Bienvenue ".$_SESSION['nom_utilisateur'].". Vous êtes authentifié en tant qu'".$_SESSION['privilege_utilisateur'].".</div>";
break;
}
}*/
?>

@ -0,0 +1,13 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Courtail - Portail des courriels</title>
<link href="/fichiers/css/bootstrap.min.css" rel="stylesheet">
<link href="/fichiers/css/confirmation.css" rel="stylesheet">
<link href="/fichiers/css/personalisation.css" rel="stylesheet">
<link href="/fichiers/css/interrupteurs.css" rel="stylesheet">
<link rel="icon" href="/fichiers/svg/favicon.svg" />
</head>
<body>

@ -0,0 +1,4 @@
<script src="/fichiers/js/bootstrap.bundle.min.js"></script>
<script src="/fichiers/js/formulaire.js"></script>
</body>
</html>

@ -0,0 +1,11 @@
<?php
/**
* Page par défaut du serveur WEB.
* Redirige automatiquement le visiteur sur la page d'accueil.
* Cette redirection permet d'ajuster la page de destination sans toucher à la configuration du serveur.
*/
header('Location: /pages/visiteurs/formulaire_accueil.php');
?>

@ -0,0 +1,4 @@
Deny from all
<IfModule mod_autoindex.c>
Options -Indexes
</ifModule>

@ -0,0 +1,227 @@
# This is the main Apache server configuration file. It contains the
# configuration directives that give the server its instructions.
# See http://httpd.apache.org/docs/2.4/ for detailed information about
# the directives and /usr/share/doc/apache2/README.Debian about Debian specific
# hints.
#
#
# Summary of how the Apache 2 configuration works in Debian:
# The Apache 2 web server configuration in Debian is quite different to
# upstream's suggested way to configure the web server. This is because Debian's
# default Apache2 installation attempts to make adding and removing modules,
# virtual hosts, and extra configuration directives as flexible as possible, in
# order to make automating the changes and administering the server as easy as
# possible.
# It is split into several files forming the configuration hierarchy outlined
# below, all located in the /etc/apache2/ directory:
#
# /etc/apache2/
# |-- apache2.conf
# | `-- ports.conf
# |-- mods-enabled
# | |-- *.load
# | `-- *.conf
# |-- conf-enabled
# | `-- *.conf
# `-- sites-enabled
# `-- *.conf
#
#
# * apache2.conf is the main configuration file (this file). It puts the pieces
# together by including all remaining configuration files when starting up the
# web server.
#
# * ports.conf is always included from the main configuration file. It is
# supposed to determine listening ports for incoming connections which can be
# customized anytime.
#
# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/
# directories contain particular configuration snippets which manage modules,
# global configuration fragments, or virtual host configurations,
# respectively.
#
# They are activated by symlinking available configuration files from their
# respective *-available/ counterparts. These should be managed by using our
# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See
# their respective man pages for detailed information.
#
# * The binary is called apache2. Due to the use of environment variables, in
# the default configuration, apache2 needs to be started/stopped with
# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not
# work with the default configuration.
# Global configuration
#
#
# ServerRoot: The top of the directory tree under which the server's
# configuration, error, and log files are kept.
#
# NOTE! If you intend to place this on an NFS (or otherwise network)
# mounted filesystem then please read the Mutex documentation (available
# at <URL:http://httpd.apache.org/docs/2.4/mod/core.html#mutex>);
# you will save yourself a lot of trouble.
#
# Do NOT add a slash at the end of the directory path.
#
#ServerRoot "/etc/apache2"
#
# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
#
#Mutex file:${APACHE_LOCK_DIR} default
#
# The directory where shm and other runtime files will be stored.
#
DefaultRuntimeDir ${APACHE_RUN_DIR}
#
# PidFile: The file in which the server should record its process
# identification number when it starts.
# This needs to be set in /etc/apache2/envvars
#
PidFile ${APACHE_PID_FILE}
#
# Timeout: The number of seconds before receives and sends time out.
#
Timeout 300
#
# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On
#
# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
#
MaxKeepAliveRequests 100
#
# KeepAliveTimeout: Number of seconds to wait for the next request from the
# same client on the same connection.
#
KeepAliveTimeout 5
# These need to be set in /etc/apache2/envvars
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}
#
# HostnameLookups: Log the names of clients or just their IP addresses
# e.g., www.apache.org (on) or 204.62.129.132 (off).
# The default is off because it'd be overall better for the net if people
# had to knowingly turn this feature on, since enabling it means that
# each client request will result in AT LEAST one lookup request to the
# nameserver.
#
HostnameLookups Off
# ErrorLog: The location of the error log file.
# If you do not specify an ErrorLog directive within a <VirtualHost>
# container, error messages relating to that virtual host will be
# logged here. If you *do* define an error logfile for a <VirtualHost>
# container, that host's errors will be logged there and not here.
#
ErrorLog ${APACHE_LOG_DIR}/error.log
#
# LogLevel: Control the severity of messages logged to the error_log.
# Available values: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the log level for particular modules, e.g.
# "LogLevel info ssl:warn"
#
LogLevel warn
# Include module configuration:
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
# Include list of ports to listen on
Include ports.conf
# Sets the default security model of the Apache2 HTTPD server. It does
# not allow access to the root filesystem outside of /usr/share and /var/www.
# The former is used by web applications packaged in Debian,
# the latter may be used for local directories served by the web server. If
# your system is serving content from a sub-directory in /srv you must allow
# access here, or in any related virtual host.
<Directory />
Options FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /usr/share>
AllowOverride None
Require all granted
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride all
Require all granted
</Directory>
#<Directory /srv/>
# Options Indexes FollowSymLinks
# AllowOverride None
# Require all granted
#</Directory>
# AccessFileName: The name of the file to look for in each directory
# for additional configuration directives. See also the AllowOverride
# directive.
#
AccessFileName .htaccess
#
# The following lines prevent .htaccess and .htpasswd files from being
# viewed by Web clients.
#
<FilesMatch "^\.ht">
Require all denied
</FilesMatch>
#
# The following directives define some format nicknames for use with
# a CustomLog directive.
#
# These deviate from the Common Log Format definitions in that they use %O
# (the actual bytes sent including headers) instead of %b (the size of the
# requested file), because the latter makes it impossible to detect partial
# requests.
#
# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended.
# Use mod_remoteip instead.
#
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
# Include of directories ignores editors' and dpkg's backup files,
# see README.Debian for details.
# Include generic snippets of statements
IncludeOptional conf-enabled/*.conf
# Include the virtual host configurations:
IncludeOptional sites-enabled/*.conf
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

@ -0,0 +1,16 @@
#!/bin/bash
chemin_script=$(dirname "$0")
mkdir -p /etc/postfix/bdd
rm -f /etc/postfix/bdd/postfix.sqlite
# Création des tables
echo "CREATION TABLES"
sqlite3 /etc/postfix/bdd/postfix.sqlite < "${chemin_script}"/postfix_tables.sql
# Création des données
echo "CREATION DONNÉES"
sqlite3 /etc/postfix/bdd/postfix.sqlite < "${chemin_script}"/postfix_données.sql
chown -R www-data: /etc/postfix/bdd

Binary file not shown.

@ -0,0 +1,34 @@
-- Alias (ne doivent pas apparaître)
INSERT INTO postfix_alias (alias,destination,active) VALUES ("toto","test1",1);
INSERT INTO postfix_alias (alias,destination,active) VALUES ("tata","test2",0);
INSERT INTO postfix_alias (alias,destination,active) VALUES ("titi","test3",1);
-- Alias virtuels
INSERT INTO postfix_alias_virtuels (destination,courriel,active) VALUES ("toto@exemple.fr","comptable@exemple.fr",1);
INSERT INTO postfix_alias_virtuels (destination,courriel,active) VALUES ("toto@exemple.fr","medecin@exemple.fr",0);
INSERT INTO postfix_alias_virtuels (destination,courriel,active) VALUES ("toto@exemple.fr","supermacher@exemple.fr",1);
INSERT INTO postfix_alias_virtuels (destination,courriel,active) VALUES ("tata@exemple.fr","fleuriste@exemple.fr",0);
INSERT INTO postfix_alias_virtuels (destination,courriel,active) VALUES ("tata@exemple.fr","coiffeur@exemple.fr",0);
INSERT INTO postfix_alias_virtuels (destination,courriel,active) VALUES ("titi@exemple.fr","promos@exemple.fr",1);
-- Domaines secondaires
INSERT INTO postfix_domaines (domaine,active,defaut) VALUES ("exemple.fr",1,0);
INSERT INTO postfix_domaines (domaine,active,defaut) VALUES ("toto.fr",1,0);
INSERT INTO postfix_domaines (domaine,active,defaut) VALUES ("tata.fr",1,0);
INSERT INTO postfix_domaines (domaine,active,defaut) VALUES ("titi.fr",0,0);
-- Listes noires destinataires
INSERT INTO postfix_liste_noire_destinataires (courriel,action,active) VALUES ("root@mail.exemple.fr","REJECT",1);
INSERT INTO postfix_liste_noire_destinataires (courriel,action,active) VALUES ("git@mail.exemple.fr","REJECT",1);
INSERT INTO postfix_liste_noire_destinataires (courriel,action,active) VALUES ("liste@mail.exemple.fr","REJECT",1);
-- Listes noires expéditeurs
INSERT INTO postfix_liste_noire_expediteurs (courriel,code_retour,message,active) VALUES ("tata@exemple.fr",554,"Parle à ma main, ma tête est malade.",0);
INSERT INTO postfix_liste_noire_expediteurs (courriel,code_retour,message,active) VALUES ("bob@titi.fr",554,"Parle à ma main, ma tête est malade.",1);
INSERT INTO postfix_liste_noire_expediteurs (courriel,code_retour,message,active) VALUES ("roland@tata.fr",554,"Parle à ma main, ma tête est malade.",1);
-- Listes utilisateurs
INSERT INTO postfix_utilisateurs (utilisateur,mot_de_passe,nom_complet,rep_perso,uid,gid,privilege,prefixe,active) VALUES ("demo@exemple.fr","{SHA512-CRYPT}$6$UXHkG3wTNiXZthSO$JVK6Qubf0JdCTFNlsYr./61wPNqxre3N4xGbMQHl264F9om84F2QbrqzI.pQj36npoHifUElvZNNhOw6T/9Rp0","Démo","exemple.fr/demo/",3000,3000,"administrateur","",1);
INSERT INTO postfix_utilisateurs (utilisateur,mot_de_passe,nom_complet,rep_perso,uid,gid,privilege,active) VALUES ("toto@exemple.fr","{SHA512-CRYPT}$6$bhI1RyJYs3jZFEPT$bfZC1tupWAWVAK34zfOxoAXhJvfKM83tZy9doLURsws9jXS3XvBISxtWxiXPKZ3Nex3MXoOPNFzw7iy8PiWMN.","Toto Dupont","exemple.fr/toto/",3000,3000,"administrateur",1);
INSERT INTO postfix_utilisateurs (utilisateur,mot_de_passe,nom_complet,rep_perso,uid,gid,privilege,prefixe,active) VALUES ("tata@tata.fr","{SHA512-CRYPT}$6$9E.KRufz4Gd.dBhW$KUdFnBg8aiPbYarKiDc4cD6NC.WsYanXBqhZEpCXJndR4SlJn3eVVo7HirXzpYaggihBgdUScS6wswG.AfOKd/","Tata Dupuis","tata.fr/tata/",3000,3000,"utilisateur","ta.",1);

@ -0,0 +1,43 @@
CREATE TABLE IF NOT EXISTS postfix_alias (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alias TEXT NOT NULL UNIQUE,
destination TEXT NOT NULL,
active INTEGER
);
CREATE TABLE IF NOT EXISTS postfix_alias_virtuels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
courriel TEXT NOT NULL UNIQUE,
destination TEXT NOT NULL,
active INTEGER
);
CREATE TABLE IF NOT EXISTS postfix_domaines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
domaine TEXT NOT NULL UNIQUE,
defaut BOOLEAN NOT NULL CHECK (defaut IN (0, 1)),
active INTEGER
);
CREATE TABLE IF NOT EXISTS postfix_liste_noire_destinataires (
id INTEGER PRIMARY KEY AUTOINCREMENT,
courriel TEXT NOT NULL UNIQUE,
action TEXT NOT NULL,
active INTEGER
);
CREATE TABLE IF NOT EXISTS postfix_liste_noire_expediteurs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
courriel TEXT NOT NULL UNIQUE,
code_retour INTEGER NOT NULL,
message TEXT NOT NULL,
active INTEGER
);
CREATE TABLE postfix_utilisateurs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
utilisateur TEXT NOT NULL UNIQUE,
mot_de_passe TEXT NOT NULL,
nom_complet TEXT,
rep_perso TEXT NOT NULL,
uid INTEGER NOT NULL,
gid INTEGER NOT NULL,
privilege TEXT NOT NULL,
prefixe TEXT,
active INTEGER
);

@ -0,0 +1,5 @@
#!/bin/sh
dockerfile="$(dirname ${0})/../"
docker build -t courtail "${dockerfile}"
docker run -it -p 8080:80 -p 4443:443 -v /etc/localtime:/etc/localtime:ro courtail:latest

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 83 KiB

@ -0,0 +1,68 @@
<?php
/**
* Page centrale de gestion des fonctionnalités du serveur de courriel.
* C'est de cette page qu'est appelé l'ensemble des fonctionnalités du portail.
*/
session_start();
require_once($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/sessions/authentification.php");
validationCookie($pdo);
require_once($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/sessions/privileges.php");
testPrivileges();
require_once($_SERVER["DOCUMENT_ROOT"]."/inclusions/entete.php");
require_once($_SERVER["DOCUMENT_ROOT"]."/inclusions/barre_menu.php");
require_once($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/messages/messages.php");
/**
* Affichage d'une bannière d'état sur l'action précedemment effectuée.
*/
if (isset($_GET['erreur']) && !empty($_GET['erreur'])) {
retourneMessage('erreurs',$_GET['erreur']);
}
elseif (isset($_GET['succes']) && !empty($_GET['succes'])) {
retourneMessage('succes',$_GET['succes']);
}
elseif (isset($_GET['message']) && !empty($_GET['message'])) {
retourneMessage('messages',$_GET['message']);
}
echo "<div class=\"container-fluid\">";
/**
* Permet d'importer les fonctions de la page demandée par l'utilisateur sur le portail d'administration.
*/
if (isset($_GET['page']) && !empty($_GET['page'])) {
switch ($_GET['page']) {
case 'accueil':
require_once($_SERVER["DOCUMENT_ROOT"]."/services/gestion/alias_virtuels.php");
break;
case 'alias':
require_once($_SERVER["DOCUMENT_ROOT"]."/services/gestion/alias_virtuels.php");
break;
case 'domaines':
require_once($_SERVER["DOCUMENT_ROOT"]."/services/gestion/domaines.php");
break;
case 'listes_noires':
require_once($_SERVER["DOCUMENT_ROOT"]."/services/gestion/listes_noires.php");
break;
case 'parametres':
require_once($_SERVER["DOCUMENT_ROOT"]."/services/gestion/parametres.php");
break;
default:
echo "<h1>Cette page n'existe pas.</h1>";
break;
}
}
else {
echo "<h1>Cette page n'existe pas.</h1>";
}
echo "</div>";
require_once($_SERVER["DOCUMENT_ROOT"]."/inclusions/pied.php");
?>

@ -0,0 +1,267 @@
<?php
/**
* Page de redirection vers la page d'accueil.
* Elle est appelée par la page d'identification lorsqu'un visiteur n'est pas parvenu à s'identifier.
* Elle ajoute à cette redirection un temps d'attente pour limiter la fréquence des tentatives ainsi qu'une variable dans l'URL permettant l'affichage d'un message d'explication sur la redirection.
*/
header ("Refresh: 5;URL=/pages/visiteurs/formulaire_accueil.php?erreur=17");
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Courtail - Portail des courriels</title>
<link rel="icon" href="/fichiers/svg/favicon.svg" />
<style>
/* Base */
body {
margin: 0;
padding: 0;
font-family: sans-serif;
font-size: 40px;
color: #FFF;
background-color: #343a40;
text-align: center; }
.chargements{
position: absolute;
top:40%;
margin: 0 auto;
width: 100%;
}
.section {
height: 100%;
line-height: 100%; }
.section-2 {
}
.section-3 {
background-color: #e74c3c; }
.section-4 {
background-color: #8e44ad; }
.chargement {
width: 50px;
height: 50px;
display: inline-block;
vertical-align: middle;
position: relative; }
/* chargements */
.chargement-quart {
border-radius: 50px;
border: 6px solid rgba(255, 255, 255, 0.4); }
.chargement-quart:after {
content: '';
position: absolute;
top: -6px;
left: -6px;
bottom: -6px;
right: -6px;
border-radius: 50px;
border: 6px solid transparent;
border-top-color: #FFF;
-webkit-animation: spin 1s linear infinite;
-moz-animation: spin 1s linear infinite;
animation: spin 1s linear infinite; }
.chargement-double {
border-radius: 50px;
border: 6px solid transparent;
border-top-color: #FFF;
border-bottom-color: #FFF;
-webkit-animation: spin 1s linear infinite;
-moz-animation: spin 1s linear infinite;
animation: spin 1s linear infinite; }
.chargement-double:after {
content: '';
position: absolute;
top: 5px;
left: 5px;
bottom: 5px;
right: 5px;
border-radius: 50px;
border: 6px solid transparent;
border-top-color: #FFF;
border-bottom-color: #FFF;
opacity: 0.6;
-webkit-animation: spinreverse 2s linear infinite;
-moz-animation: spinreverse 2s linear infinite;
animation: spinreverse 2s linear infinite; }
.chargement-cercles {
border-radius: 50px;
border: 3px solid transparent;
border-top-color: #FFF;
-webkit-animation: spin 1s linear infinite;
-moz-animation: spin 1s linear infinite;
animation: spin 1s linear infinite; }
.chargement-cercles:before, .chargement-cercles:after {
content: '';
position: absolute;
top: 5px;
left: 5px;
bottom: 5px;
right: 5px;
border-radius: 50px;
border: 3px solid transparent;
border-top-color: #FFF;
opacity: 0.8;
-webkit-animation: spin 10s linear infinite;
-moz-animation: spin 10s linear infinite;
animation: spin 10s linear infinite; }
.chargement-cercles:before {
top: 12px;
left: 12px;
bottom: 12px;
right: 12px;
opacity: .5;
-webkit-animation: spin 5s linear infinite;
-moz-animation: spin 5s linear infinite;
animation: spin 5s linear infinite; }
.chargement-barres:before, .chargement-barres:after,
.chargement-barres span {
content: '';
display: block;
position: absolute;
left: 0px;
top: 0;
width: 10px;
height: 30px;
background-color: #FFF;
-webkit-animation: grow 1s linear infinite;
-moz-animation: grow 1s linear infinite;
animation: grow 1s linear infinite; }
.chargement-barres:after {
left: 15px;
-webkit-animation-delay: -0.66s;
-moz-animation-delay: -0.66s;
animation-delay: -0.66s; }
.chargement-barres span {
left: 30px;
-webkit-animation-delay: -0.33s;
-moz-animation-delay: -0.33s;
animation-delay: -0.33s; }
/* Animations */
@-webkit-keyframes spin {
from {
-webkit-transform: rotate(0deg); }
to {
-webkit-transform: rotate(360deg); } }
@-moz-keyframes spin {
from {
-moz-transform: rotate(0deg); }
to {
-moz-transform: rotate(360deg); } }
@keyframes spin {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg); }
to {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg); } }
@-webkit-keyframes spinreverse {
from {
-webkit-transform: rotate(0deg); }
to {
-webkit-transform: rotate(-360deg); } }
@-moz-keyframes spinreverse {
from {
-moz-transform: rotate(0deg); }
to {
-moz-transform: rotate(-360deg); } }
@keyframes spinreverse {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg); }
to {
-webkit-transform: rotate(-360deg);
-moz-transform: rotate(-360deg);
-ms-transform: rotate(-360deg);
-o-transform: rotate(-360deg);
transform: rotate(-360deg); } }
@-webkit-keyframes grow {
0% {
-webkit-transform: scaleY(0);
opacity: 0; }
50% {
-webkit-transform: scaleY(1);
opacity: 1; }
100% {
-webkit-transform: scaleY(0);
opacity: 0; } }
@-moz-keyframes grow {
0% {
-moz-transform: scaleY(0);
opacity: 0; }
50% {
-moz-transform: scaleY(1);
opacity: 1; }
100% {
-moz-transform: scaleY(0);
opacity: 0; } }
@keyframes grow {
0% {
-webkit-transform: scaleY(0);
-moz-transform: scaleY(0);
-ms-transform: scaleY(0);
-o-transform: scaleY(0);
transform: scaleY(0);
opacity: 0; }
50% {
-webkit-transform: scaleY(1);
-moz-transform: scaleY(1);
-ms-transform: scaleY(1);
-o-transform: scaleY(1);
transform: scaleY(1);
opacity: 1; }
100% {
-webkit-transform: scaleY(0);
-moz-transform: scaleY(0);
-ms-transform: scaleY(0);
-o-transform: scaleY(0);
transform: scaleY(0);
opacity: 0; } }
</style>
</head>
<body>
<div class="chargements">
<section class="section section-2">
<span class="chargement chargement-double"></span>
Traitement...
</section>
</div>
</body>
</html>

@ -0,0 +1,69 @@
<?php
/**
* Page d'accueil du portail.
* Impose une authentification du visiteur pour continuer.
* Redirige les tentative d'identification pour traitement et test de légitimité.
*/
session_start();
require_once($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/sessions/authentification.php");
validationCookie($pdo);
if (isset($_SESSION['id_utilisateur']) && isset($_SESSION['pseudo_utilisateur']) && isset($_SESSION['nom_utilisateur']) && isset($_SESSION['privilege_utilisateur']) && !empty($_SESSION['id_utilisateur']) && !empty($_SESSION['pseudo_utilisateur']) && !empty($_SESSION['nom_utilisateur']) && !empty($_SESSION['privilege_utilisateur'])) {
header ("location: /pages/gestion/administration.php?page=accueil");
die();
}
require_once($_SERVER["DOCUMENT_ROOT"]."/inclusions/entete.php");
require_once($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/messages/messages.php");
?>
<div id="contenu" class="contenu container">
<div id="logo-accueil" class="logo">
<img src="/fichiers/svg/send.svg" alt="courriel">
<h1>Courtail</h1>
</div>
<div class="card" id="contenu_auth">
<div class="card-body">
<?php
/**
* Affichage d'une bannière d'état sur l'action précedemment effectuée.
*/
if (isset($_GET['erreur']) && !empty($_GET['erreur'])) {
retourneMessage('erreurs',$_GET['erreur']);
}
elseif (isset($_GET['succes']) && !empty($_GET['succes'])) {
retourneMessage('succes',$_GET['succes']);
}
?>
<form method="post" action="/services/traitements/utilisateurs/identification.php">
<div class="form-floating mb-3 mt-3">
<input type="text" name="identifiant" class="form-control" id="identifiant" placeholder="nom@example.fr" autofocus required/>
<label for="identifiant">Votre nom d'utilisateur</label>
</div>
<div class="form-floating mb-3">
<input type="password" name="mdp" class="form-control" id="mdp" placeholder="Mot de passe" required/>
<label for="mdp">Votre mot de passe</label>
</div>
<div class="form-check mb-3">
<input type="checkbox" name="souvenir-session" id="souvenir-session" class="form-check-input" checked/>
<label for="souvenir-session" class="form-check-label">Se souvenir de moi</label>
</div>
<button id="envoyer" type="submit" class="btn btn-primary">envoyer</button>
</form>
</div>
</div>
</div>
<script>
const body = document.querySelector('body');
body.classList.add("fond-arc-noire");
</script>
<?php
require_once($_SERVER["DOCUMENT_ROOT"]."/inclusions/pied.php");
?>

@ -0,0 +1,157 @@
<?php
/**
* Page de fonctions permettant la gestion des alias virtuels.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/connexion.php");
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/sessions/privileges.php");
/**
* Fonction listant les alias virtuels d'un utilisateur.
*/
function listeAliasVirtuelsPostfix($pdo) {
try {
// Filtre des alias virtuels de l'utilisateur courant
$req = 'SELECT * FROM postfix_alias_virtuels WHERE destination=:destination ORDER BY courriel ASC';
$sql=$pdo->prepare($req);
$sql->bindValue(':destination',$_SESSION['pseudo_utilisateur']);
$sql->execute();
$liste_alias_virtuels = $sql->fetchAll(PDO::FETCH_ASSOC);
return $liste_alias_virtuels;
}
catch (\Exception $e) {
die ("Erreur de requête de selection des alias virtuels : ".$e->getMessage());
}
}
/**
* Fonction listant tous les alias virtuels de la base.
*/
function listeTousAliasVirtuelsPostfix($pdo) {
try {
$req = 'SELECT * FROM postfix_alias_virtuels';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_alias_virtuels = $sql->fetchAll(PDO::FETCH_ASSOC);
return $liste_alias_virtuels;
}
catch (\Exception $e) {
die ("Erreur de requête de selection des alias virtuels : ".$e->getMessage());
}
}
/**
* Fonction d'ajout d'un alias virtuel.
*/
function ajoutAliasVirtuelsPostfix($pdo,$nom_alias_virtuel,$choix_domaine_alias_virtuel) {
try {
// Insertion de l'alias virtuel entré par l'utilisateur et lié avec son adresse
$req='INSERT INTO postfix_alias_virtuels (courriel,destination,active) VALUES (:alias_virtuel,:courriel,1)';
$sql=$pdo->prepare($req);
$sql->bindValue(':courriel',$_SESSION['pseudo_utilisateur']);
$sql->bindValue(':alias_virtuel',substr(htmlspecialchars(retournePrefixeUtilisateurPostfix($pdo,$_SESSION['pseudo_utilisateur']).$nom_alias_virtuel."@".$choix_domaine_alias_virtuel),0,100));
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="23000") {
// Code de retour envoyé par le pilote PDO SQLite signifiant que la contrainte "UNIQUE" du champ "courriel" de la table "postif_alias_virtuels" est violée. L'alias envoyé via le formulaire par l'utilisateur a déjà été renseigné dans la base
header ("Location: /pages/gestion/administration.php?page=alias&erreur=3");
die();
}
elseif ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base de données n'est accessible qu'en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=alias&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête d'ajout d'alias virtuel : ".$e->getMessage());
}
}
}
/**
* Fonction d'activation/désactivation d'un alias virtuel.
* Suite à une soumission du formulaire dédié, cette fonction va traiter l'ensemble des alias virtuels d'un utilisateur.
* Elle occasionnera peut-être des baisses de performance si un très grand nombre d'alias virtuels existe pour un utilisateur donné (le traitement n'intervenant pas spécifiquement pour l'entrée modifiée).
*/
function modifEtatAliasVirtuelsPostfix($pdo,$retour_form) {
try {
$compteModifs=0;
foreach ($retour_form as $id_alias_virtuel => $etat_alias_virtuel) {
$req='UPDATE postfix_alias_virtuels SET active=:etat_alias_virtuel WHERE id=:id_alias_virtuel AND destination=:pseudo_utilisateur';
$sql=$pdo->prepare($req);
$sql->bindValue(':etat_alias_virtuel',$etat_alias_virtuel);
$sql->bindValue(':id_alias_virtuel',$id_alias_virtuel);
$sql->bindValue(':pseudo_utilisateur',$_SESSION['pseudo_utilisateur']);
$sql->execute();
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=alias&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de modification d'état pour les alias virtuels : ".$e->getMessage());
}
}
}
/**
* Fonction de suppression d'un alias virtuel.
*/
function supprAliasVirtuelsPostfix($pdo,$suppr_alias_virtuel) {
try {
$req='DELETE FROM postfix_alias_virtuels WHERE id=:id_alias_virtuel AND destination=:pseudo_utilisateur';
$sql=$pdo->prepare($req);
$sql->bindValue(':id_alias_virtuel',$suppr_alias_virtuel);
$sql->bindValue(':pseudo_utilisateur',$_SESSION['pseudo_utilisateur']);
$sql->execute();
if($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=alias&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de suppression d'un alias virtuel : ".$e->getMessage());
}
}
}
?>

@ -0,0 +1,17 @@
<?php
/**
* Connexion à la base SQLite3.
*/
$base = "/etc/postfix/bdd/postfix.sqlite";
try{
$pdo = new PDO("sqlite:$base");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch (Exception $e){
die ("Erreur de connexion à la base \"$base\" : ".$e->getMessage());
}
?>

@ -0,0 +1,197 @@
<?php
/**
* Page de fonctions concernant la gestion des domaines.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/connexion.php");
/**
* Fonction listant les domaines.
*/
function listeDomainesPostfix($pdo) {
try {
$req = 'SELECT * FROM postfix_domaines ORDER BY defaut DESC';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_domaines = $sql->fetchAll(PDO::FETCH_ASSOC);
return $liste_domaines;
}
catch (\Exception $e) {
die ("Erreur de requête de selection des domaines pour \"$base\" : ".$e->getMessage());
}
}
/**
* Fonction affichant le domaine par défaut.
*/
function retourneDomaineDefautPostfix($pdo) {
try {
$req = 'SELECT domaine FROM postfix_domaines WHERE defaut=1 LIMIT 1';
$sql=$pdo->prepare($req);
$sql->execute();
$result = $sql ->fetchAll(PDO::FETCH_ASSOC);
$comptage = count($result);
if($comptage == 1){
$domaine_defaut = $result;
return $domaine_defaut[0]['domaine'];;
}
else {
$domaine_defaut = "";
return $domaine_defaut;
}
}
catch (\Exception $e) {
die ("Erreur de requête de selection des domaines pour \"$base\" : ".$e->getMessage());
}
}
/**
* Fonction changeant le domaine par défaut.
*/
function changeDomaineDefautPostfix($pdo,$choix_domaine_defaut) {
try {
// suppression du status de domaine par défaut sur tous les domaines pour s'assurer de l'unicité de ce paramètre à la requête suivante
$req = 'UPDATE postfix_domaines SET defaut=0';
$sql=$pdo->prepare($req);
$sql->execute();
// Définition du domaine renseigné par l'utilisateur et activation de celui-ci (au cas-où il ne l'était pas)
$req = 'UPDATE postfix_domaines SET defaut=1, active=1 WHERE domaine=:domaine';
$sql=$pdo->prepare($req);
$sql->bindValue(':domaine',$choix_domaine_defaut);
$sql->execute();
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=domaines&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de modification de préférence pour le domaine par défaut : ".$e->getMessage());
}
}
}
/**
* Fonction d'ajout d'un domaine.
*/
function ajoutDomainePostfix($pdo,$nom_domaine) {
try {
// Insertion du domaine entré par l'administrateur
$req='INSERT INTO postfix_domaines (domaine,defaut,active) VALUES (:domaine,0,1)';
$sql=$pdo->prepare($req);
$sql->bindValue(':domaine',substr(htmlspecialchars($nom_domaine),0,100));
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite.
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="23000") {
// Code de retour envoyé par le pilote PDO SQLite signifiant que la contrainte "UNIQUE" du champ "domaine" de la table "postfix_domaines" est violée. Le domaine envoyé via le formulaire par l'administrateur a déjà été renseigné dans la base
header ("Location: /pages/gestion/administration.php?page=domaines&erreur=7");
die();
}
elseif ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base de données n'est accessible qu'en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=domaines&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête d'ajout d'un domaine : ".$e->getMessage());
}
}
}
/**
* Fonction d'activation/désactivation d'un domaine.
* Suite à une soumission du formulaire dédié, cette fonction va traiter l'ensemble des domaines d'un utilisateur.
* Elle occasionnera peut-être des baisses de performance si un très grand nombre de domaines existe pour un utilisateur donné (le traitement n'intervenant pas spécifiquement pour l'entrée modifiée).
*/
function modifEtatDomainesPostfix($pdo,$retour_form) {
try {
$compteModifs=0;
foreach ($retour_form as $id_domaine => $etat_domaine) {
$req='UPDATE postfix_domaines SET active=:etat_domaine WHERE id=:id_domaine AND defaut!=1';
$sql=$pdo->prepare($req);
$sql->bindValue(':etat_domaine',$etat_domaine);
$sql->bindValue(':id_domaine',$id_domaine);
$sql->execute();
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=domaines&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de modification d'état pour les domaines : ".$e->getMessage());
}
}
}
/**
* Fonction de suppression d'un domaine.
*/
function supprDomainesPostfix($pdo,$suppr_domaine) {
try {
$req='DELETE FROM postfix_domaines WHERE id=:id_domaine AND defaut!=1';
$sql=$pdo->prepare($req);
$sql->bindValue(':id_domaine',$suppr_domaine);
$sql->execute();
if($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=domaines&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de suppression d'un domaine : ".$e->getMessage());
}
}
}
?>

@ -0,0 +1,323 @@
<?php
/**
* Page de fonctions permettant la la sauvegarde et la restauration des données de la base de la messagerie.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/connexion.php");
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/sessions/privileges.php");
/**
* Fonction générant un fichier CSV avec le contenu des alias virtuels d'un utilisateur.
* Ce fichier est créé à la volée et se popose à l'utilisateur pour téléchargement.
*/
function exportAliasVirtuelsUtilisateur($pdo,$utilisateur) {
try {
// Filtre des alias virtuels de l'utilisateur courant
$req = 'SELECT * FROM postfix_alias_virtuels WHERE destination=:destination';
$sql=$pdo->prepare($req);
$sql->bindValue(':destination',$utilisateur);
$sql->execute();
$liste_alias_virtuels = $sql->fetchAll(PDO::FETCH_ASSOC);
$nom_fichier_csv="Alias-virtuels_".$utilisateur."_".date('d-m-Y').".csv";
// Entêtes HTTP permettant le téléchargement à la volée du CSV
header("Content-Disposition: attachment; filename=\"$nom_fichier_csv\"");
header("Content-Type: text/csv");
foreach ($liste_alias_virtuels as $alias_virtuel => $champ_alias_virtuel) {
echo implode(":", $champ_alias_virtuel)."\n";
}
// Le fait de tuer la page enclenche la demande de téléchargement à l'utilisateur qui ne quitte visuellement jamais la page des paramètres
die();
}
catch (\Exception $e) {
die ("Erreur de requête d'export des alias virtuels utilisateur en CSV : ".$e->getMessage());
}
}
/**
* Fonction générant un fichier CSV avec le contenu de la base Sqlite demandé par un administrateur.
* Ce fichier est créé à la volée et se popose à l'administrateur pour téléchargement.
*/
function exportDonneesMessagerie($pdo,$types_donnees_a_exporter) {
try {
$nom_fichier_csv="Données_Courtail_".date('d-m-Y').".csv";
// Entêtes HTTP permettant le téléchargement à la volée du CSV
header("Content-Disposition: attachment; filename=\"$nom_fichier_csv\"");
header("Content-Type: text/csv");
foreach ($types_donnees_a_exporter as $cle_donnees_a_exporter => $type_donnee_a_exporter) {
switch ($type_donnee_a_exporter) {
case "alias_virtuels":
$req = 'SELECT * FROM postfix_alias_virtuels';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_alias_virtuels = $sql->fetchAll(PDO::FETCH_ASSOC);
echo "#~// Alias_vituels\n";
foreach ($liste_alias_virtuels as $alias_virtuel => $champ_alias_virtuel) {
echo implode(":", $champ_alias_virtuel)."\n";
}
break;
case "domaines":
$req = 'SELECT * FROM postfix_domaines';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_domaines = $sql->fetchAll(PDO::FETCH_ASSOC);
echo "#~// Domaines\n";
foreach ($liste_domaines as $domaine => $champ_domaine) {
echo implode(":", $champ_domaine)."\n";
}
break;
case "liste_noire_destinataires":
$req = 'SELECT * FROM postfix_liste_noire_destinataires';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_liste_noire_destinataires = $sql->fetchAll(PDO::FETCH_ASSOC);
echo "#~// Liste_noire_destinataires\n";
foreach ($liste_liste_noire_destinataires as $liste_noire_destinataire => $champ_liste_noire_destinataire) {
echo implode(":", $champ_liste_noire_destinataire)."\n";
}
break;
case "liste_noire_expediteurs":
$req = 'SELECT * FROM postfix_liste_noire_expediteurs';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_liste_noire_expediteurs = $sql->fetchAll(PDO::FETCH_ASSOC);
echo "#~// Liste_noire_expéditeur\n";
foreach ($liste_liste_noire_expediteurs as $liste_noire_expediteur => $champ_liste_noire_expediteur) {
echo implode(":", $champ_liste_noire_expediteur)."\n";
}
break;
case "utilisateurs":
$req = 'SELECT * FROM postfix_utilisateurs';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_utilisateurs = $sql->fetchAll(PDO::FETCH_ASSOC);
echo "#~// Utilisateurs\n";
foreach ($liste_utilisateurs as $liste_utilisateur => $champ_utilisateur) {
echo implode(":", $champ_utilisateur)."\n";
}
break;
}
}
// Le fait de tuer la page enclenche la demande de téléchargement à l'utilisateur qui ne quitte visuellement jamais la page des paramètres
die();
}
catch (\Exception $e) {
die ("Erreur de requête d'export des données de la base en CSV : ".$e->getMessage());
}
}
/**
* Fonction important un fichier d'export d'alias virtuels utilisateur en CSV vers la base de données.
*/
function importAliasVirtuelsUtilisateur($pdo,$alias_virtuel,$utilisateur,$active) {
try {
// Insertion de l'alias virtuels parcouru dans le fichier CSV soumis pas l'utilisateur
// Les contrôles de données ont étés effectués dans la page de traitement
$req = 'INSERT INTO postfix_alias_virtuels (courriel,destination,active) VALUES (:alias_virtuel,:destination,:active)';
$sql=$pdo->prepare($req);
$sql->bindValue(':alias_virtuel',htmlspecialchars($alias_virtuel));
$sql->bindValue(':destination',htmlspecialchars($utilisateur));
$sql->bindValue(':active',$active);
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="23000") {
// Code de retour envoyé par le pilote PDO SQLite signifiant que la contrainte "UNIQUE" du champ "courriel" de la table "postif_alias_virtuels" est violée. Dans la mesure ou un contrôle d'unicité est effectué par la page de traitement, cette erreur signifie qu'un bogue est présent dans celui-ci
header ("Location: /pages/gestion/administration.php?page=alias&erreur=3");
die();
}
elseif ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base de données n'est accessible qu'en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=alias&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête d'import des alias virtuels utilisateur en CSV : ".$e->getMessage());
}
}
}
/**
* Fonction important les données sauvegardées dans un fichier CSV par un administrateur avec la fonction exportDonneesMessagerie().
*/
function importDonneesMessagerie($pdo,$type_donnees_csv,$donnees_csv) {
try {
switch ($type_donnees_csv) {
case "alias_virtuels":
$req = 'INSERT INTO postfix_alias_virtuels (courriel,destination,active) VALUES (:alias_virtuel,:destination,:active)';
$sql=$pdo->prepare($req);
$sql->bindValue(':alias_virtuel',htmlspecialchars($donnees_csv[1]));
$sql->bindValue(':destination',htmlspecialchars($donnees_csv[2]));
$sql->bindValue(':active',$donnees_csv[3]);
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
break;
case "domaines":
$req = 'INSERT INTO postfix_domaines (domaine,defaut,active) VALUES (:domaine,:defaut,:active)';
$sql=$pdo->prepare($req);
$sql->bindValue(':domaine',htmlspecialchars($donnees_csv[1]));
$sql->bindValue(':defaut',htmlspecialchars($donnees_csv[2]));
$sql->bindValue(':active',$donnees_csv[3]);
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
break;
case "liste_noire_destinataires":
$req = 'INSERT INTO postfix_liste_noire_destinataires (courriel,action,active) VALUES (:courriel,:action,:active)';
$sql=$pdo->prepare($req);
$sql->bindValue(':courriel',htmlspecialchars($donnees_csv[1]));
$sql->bindValue(':action',htmlspecialchars($donnees_csv[2]));
$sql->bindValue(':active',$donnees_csv[3]);
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
break;
case "liste_noire_expediteurs":
$req = 'INSERT INTO postfix_liste_noire_expediteurs (courriel,code_retour,message,active) VALUES (:courriel,:code_retour,:message,:active)';
$sql=$pdo->prepare($req);
$sql->bindValue(':courriel',htmlspecialchars($donnees_csv[1]));
$sql->bindValue(':code_retour',htmlspecialchars($donnees_csv[2]));
$sql->bindValue(':message',htmlspecialchars($donnees_csv[3]));
$sql->bindValue(':active',$donnees_csv[4]);
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
break;
case "utilisateurs":
$req = 'INSERT INTO postfix_utilisateurs (utilisateur,mot_de_passe,nom_complet,rep_perso,uid,gid,privilege,prefixe,active) VALUES (:utilisateur,:mot_de_passe,:nom_complet,:rep_perso,:uid,:gid,:privilege,:prefixe,:active)';
$sql=$pdo->prepare($req);
$sql->bindValue(':utilisateur',htmlspecialchars($donnees_csv[1]));
$sql->bindValue(':mot_de_passe',htmlspecialchars($donnees_csv[2]));
$sql->bindValue(':nom_complet',htmlspecialchars($donnees_csv[3]));
$sql->bindValue(':rep_perso',$donnees_csv[4]);
$sql->bindValue(':uid',$donnees_csv[5]);
$sql->bindValue(':gid',$donnees_csv[6]);
$sql->bindValue(':privilege',$donnees_csv[7]);
$sql->bindValue(':prefixe',$donnees_csv[8]);
$sql->bindValue(':active',$donnees_csv[9]);
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
break;
}
}
catch (\Exception $e) {
die ("Erreur de requête d'import des données de la base en CSV : ".$e->getMessage());
}
}
/**
* Fonction de purge des tables permettant une importation fidèle des données contenues dans un ficher d'export CSV généré par la fonction exportDonneesMessagerie().
*/
function purgeTable($pdo,$table) {
try {
switch ($table) {
case "postfix_alias_virtuels":
$sql = $pdo->prepare('DELETE FROM postfix_alias_virtuels');
$sql->execute();
$sql = $pdo->prepare('UPDATE sqlite_sequence SET seq = 0 WHERE name = "postfix_alias_virtuels"');
$sql->execute();
$sql = $pdo->prepare('VACUUM');
$sql->execute();
break;
case "postfix_domaines":
$sql = $pdo->prepare('DELETE FROM postfix_domaines');
$sql->execute();
$sql = $pdo->prepare('UPDATE sqlite_sequence SET seq = 0 WHERE name = "postfix_domaines"');
$sql->execute();
$sql = $pdo->prepare('VACUUM');
$sql->execute();
break;
case "postfix_liste_noire_destinataires":
$sql = $pdo->prepare('DELETE FROM postfix_liste_noire_destinataires');
$sql->execute();
$sql = $pdo->prepare('UPDATE sqlite_sequence SET seq = 0 WHERE name = "postfix_liste_noire_destinataires"');
$sql->execute();
$sql = $pdo->prepare('VACUUM');
$sql->execute();
break;
case "postfix_liste_noire_expediteurs":
$sql = $pdo->prepare('DELETE FROM postfix_liste_noire_expediteurs');
$sql->execute();
$sql = $pdo->prepare('UPDATE sqlite_sequence SET seq = 0 WHERE name = "postfix_liste_noire_expediteurs"');
$sql->execute();
$sql = $pdo->prepare('VACUUM');
$sql->execute();
break;
case "postfix_utilisateurs":
$sql = $pdo->prepare('DELETE FROM postfix_utilisateurs');
$sql->execute();
$sql = $pdo->prepare('UPDATE sqlite_sequence SET seq = 0 WHERE name = "postfix_utilisateurs"');
$sql->execute();
$sql = $pdo->prepare('VACUUM');
$sql->execute();
break;
}
}
catch (\Exception $e) {
die ("Erreur de requête de purge d'une table : ".$e->getMessage());
}
}
?>

@ -0,0 +1,261 @@
<?php
/**
* Page de fonctions concernant les entrées de la liste noire des destinataires.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/connexion.php");
/**
* Fonction listant les entrées de la liste noire des destinataires.
*/
function listeEntreeListeNoireDstPostfix($pdo) {
try {
$req = 'SELECT * FROM postfix_liste_noire_destinataires';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_entree_liste_noire_dst = $sql->fetchAll(PDO::FETCH_ASSOC);
return $liste_entree_liste_noire_dst;
}
catch (\Exception $e) {
die ("Erreur de requête de selection des entrées de liste noire destinataires pour \"$base\" : ".$e->getMessage());
}
}
/**
* Fonction listant les entrées de la liste noire des expéditeurs.
*/
function listeEntreeListeNoireExpPostfix($pdo) {
try {
$req = 'SELECT * FROM postfix_liste_noire_expediteurs';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_entree_liste_noire_exp = $sql->fetchAll(PDO::FETCH_ASSOC);
return $liste_entree_liste_noire_exp;
}
catch (\Exception $e) {
die ("Erreur de requête de selection des entrées de liste noire expéditeurs : ".$e->getMessage());
}
}
/**
* Fonction d'ajout d'une entrée de liste noir destinataire.
*/
function ajoutEntreeListeNoireDstPostfix($pdo,$courriel_entree_liste_noire_dst,$action_entree_liste_noire_dst) {
try {
// Insertion de l'entrée de liste noire de destinataires renseignée par l'administrateur
$req='INSERT INTO postfix_liste_noire_destinataires (courriel,action,active) VALUES (:courriel,:action,1)';
$sql=$pdo->prepare($req);
$sql->bindValue(':courriel',substr(htmlspecialchars($courriel_entree_liste_noire_dst),0,100));
$sql->bindValue(':action',$action_entree_liste_noire_dst);
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="23000") {
// Code de retour envoyé par le pilote PDO SQLite signifiant que la contrainte "UNIQUE" du champ "courriel" de la table "postfix_liste_noire_destinataires" est violée. L'adresse de destination envoyé via le formulaire par l'utilisateur a déjà été renseignée dans la base
header ("Location: /pages/gestion/administration.php?page=listes_noires&sousPage=destinataires&erreur=12");
die();
}
elseif ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base de données n'est accessible qu'en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=listes_noires&sousPage=destinataires&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête d'ajout de l'entrée de liste noire de destinataires : ".$e->getMessage());
}
}
}
/**
* Fonction d'ajout d'une entrée de liste noir expéditeurs.
*/
function ajoutEntreeListeNoireExpPostfix($pdo,$courriel_entree_liste_noire_exp,$code_retour_entree_liste_noire_exp) {
try {
// Insertion de l'entrée de liste noire d'expéditeurs renseignée par l'administrateur
$req='INSERT INTO postfix_liste_noire_expediteurs (courriel,code_retour,message,active) VALUES (:courriel,:code_retour,"Votre message a été rejeté par le serveur car votre adresse est en liste noire.",1)';
$sql=$pdo->prepare($req);
$sql->bindValue(':courriel',substr(htmlspecialchars($courriel_entree_liste_noire_exp),0,100));
$sql->bindValue(':code_retour',$code_retour_entree_liste_noire_exp);
$sql->execute();
if ($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="23000") {
// Code de retour envoyé par le pilote PDO SQLite signifiant que la contrainte "UNIQUE" du champ "courriel" de la table "postfix_liste_noire_expediteurs" est violée. L'adresse de destination envoyé via le formulaire par l'utilisateur a déjà été renseignée dans la base
header ("Location: /pages/gestion/administration.php?page=listes_noires&sousPage=expediteurs&erreur=16");
die();
}
elseif ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base de données n'est accessible qu'en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=listes_noires&sousPage=expediteurs&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête d'ajout de l'entrée de liste noire d'expéditeurs : ".$e->getMessage());
}
}
}
/**
* Fonction d'activation/désactivation d'une entrée dans la liste noire des destinataires.
* Suite à une soumission du formulaire dédié, cette fonction va traiter l'ensemble des entrées de la liste noire des destinataires du serveur.
* Elle occasionnera peut-être des baisses de performance si un très grand d'entrées existent (le traitement n'intervenant pas spécifiquement pour l'entrée modifiée).
*/
function modifEntreeListeNoireDstPostfix($pdo,$retour_form) {
try {
$compteModifs=0;
foreach ($retour_form as $id_entree_liste_noire_dst => $etat_entree_liste_noire_dst) {
$req='UPDATE postfix_liste_noire_destinataires SET active=:etat_entree_liste_noire_dst WHERE id=:id_entree_liste_noire_dst';
$sql=$pdo->prepare($req);
$sql->bindValue(':etat_entree_liste_noire_dst',$etat_entree_liste_noire_dst);
$sql->bindValue(':id_entree_liste_noire_dst',$id_entree_liste_noire_dst);
$sql->execute();
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=listes_noires&sousPage=destinataires&erreur=10");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de modification d'état pour l'entrée de liste noire de destinataires : ".$e->getMessage());
}
}
}
/**
* Fonction d'activation/désactivation d'une entrée dans la liste noire des expéditeurs.
* Suite à une soumission du formulaire dédié, cette fonction va traiter l'ensemble des entrées de la liste noire des expéditeurs du serveur.
* Elle occasionnera peut-être des baisses de performance si un très grand d'entrées existent (le traitement n'intervenant pas spécifiquement pour l'entrée modifiée).
*/
function modifEntreeListeNoireExpPostfix($pdo,$retour_form) {
try {
$compteModifs=0;
foreach ($retour_form as $id_entree_liste_noire_exp => $etat_entree_liste_noire_exp) {
$req='UPDATE postfix_liste_noire_expediteurs SET active=:etat_entree_liste_noire_exp WHERE id=:id_entree_liste_noire_exp';
$sql=$pdo->prepare($req);
$sql->bindValue(':etat_entree_liste_noire_exp',$etat_entree_liste_noire_exp);
$sql->bindValue(':id_entree_liste_noire_exp',$id_entree_liste_noire_exp);
$sql->execute();
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=listes_noires&sousPage=expediteurs&erreur=14");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de modification d'état pour l'entrée de liste noire d'expéditeurs : ".$e->getMessage());
}
}
}
/**
* Fonction de suppression d'une entrée de liste noire de destinataires.
*/
function supprEntreeListeNoireDstPostfix($pdo) {
try {
$req='DELETE FROM postfix_liste_noire_destinataires WHERE id=:id_entree_liste_noire_dst';
$sql=$pdo->prepare($req);
$sql->bindValue(':id_entree_liste_noire_dst',$_GET['supprEntreeListeNoireDst']);
$sql->execute();
if($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=listes_noires&sousPage=destinataires&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de suppression d'une entrée de liste noire de destinataires : ".$e->getMessage());
}
}
}
/**
* Fonction de suppression d'une entrée de liste noire d'expéditeurs.
*/
function supprEntreeListeNoireExpPostfix($pdo) {
try {
$req='DELETE FROM postfix_liste_noire_expediteurs WHERE id=:id_entree_liste_noire_exp';
$sql=$pdo->prepare($req);
$sql->bindValue(':id_entree_liste_noire_exp',$_GET['supprEntreeListeNoireExp']);
$sql->execute();
if($sql->rowCount() == 1){
return true;
}
else {
return false;
}
}
catch (\Exception $e) {
// Récupération du code de retour de la commande SQLite
$code_retour=$sql->errorInfo();
if ($code_retour[0]=="HY000") {
// Code de retour envoyé par le pilote PDO SQLite lorsque la base est accessible en lecture seule à www-data
header ("Location: /pages/gestion/administration.php?page=listes_noires&sousPage=expediteurs&erreur=4");
die();
}
else {
// En cas d'erreur non gérée, le script s'arrête avec un message d'erreur à destination de l'administrateur du serveur
die ("Erreur de requête de suppression d'une entrée de liste noire d'expéditeurs : ".$e->getMessage());
}
}
}
?>

@ -0,0 +1,165 @@
<?php
/**
* Page de fonctions permettant la gestion des utilisateurs.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/connexion.php");
/**
* Fonction listant les utilisateurs.
*/
function listeUtilisateursPostfix($pdo) {
try {
$req = 'SELECT utilisateur FROM postfix_utilisateurs';
$sql=$pdo->prepare($req);
$sql->execute();
$liste_utilisateurs = $sql->fetchAll(PDO::FETCH_ASSOC);
return $liste_utilisateurs;
}
catch (\Exception $e) {
die ("Erreur de requête de selection des utilisateurs : ".$e->getMessage());
}
}
/**
* Fonction vérifiant le mot de passe actuel d'un utilisateur.
*/
function verificationMotDePasseUtilisateurPostfix($pdo,$identifiant,$mdp) {
try {
$req = 'SELECT mot_de_passe FROM postfix_utilisateurs WHERE utilisateur=:identifiant';
$sql = $pdo->prepare($req);
$sql->bindValue(':identifiant', $identifiant);
$sql->execute();
$result = $sql ->fetchAll(PDO::FETCH_ASSOC);
$comptage = count($result);
}
catch(PDOException $e) {
die ("Erreur lors du traitement de la requête de vérification du mot de passe utilisateur : " . $e->getMessage());
}
if ($comptage == 1) {
$mot_de_passe_base=explode('$', $result['0']['mot_de_passe']);
$mot_de_passe_form=crypt(trim("$mdp"), "$".$mot_de_passe_base[1]."$".$mot_de_passe_base[2]."$");
if ("$".$mot_de_passe_base[1]."$".$mot_de_passe_base[2]."$".$mot_de_passe_base[3]==$mot_de_passe_form) {
// Si le mot de passe renseigné n'est le bon, le visiteur est redirigé vers la page d'accueil avec un message explicatif
return true;
}
else {
return false;
}
}
else {
return false;
}
}
/**
* Fonction changeant le mot de passe d'un utilisateur.
*/
function changeMotDePasseUtilisateurPostfix($pdo,$utilisateur,$nouveau_mdp,$confirmation_mdp) {
if ($nouveau_mdp===$confirmation_mdp) {
$caracters = '.0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$chaine_aleatoire = '';
// Génération d'un sel à 16 caractères
for ($i = 0; $i < 16; $i++) {
$index = rand(0, strlen($caracters) - 1);
$chaine_aleatoire .= $caracters[$index];
}
// Construction d'un mot de passe haché compatible avec Dovecot
$mot_de_passe_hachis='{SHA512-CRYPT}'.crypt(trim($nouveau_mdp),'$6$'.$chaine_aleatoire);
try {
$req = 'UPDATE postfix_utilisateurs SET mot_de_passe=:nouveau_mdp WHERE utilisateur=:utilisateur';
$sql=$pdo->prepare($req);
$sql->bindValue(':utilisateur', $utilisateur);
$sql->bindValue(':nouveau_mdp', $mot_de_passe_hachis);
$sql->execute();
}
catch (\Exception $e) {
die ("Erreur de requête de changement de mot de passe utilisateur : ".$e->getMessage());
}
return true;
}
else {
return false;
}
}
/**
* Fonction listant le préfixe de l'utilisateur courant.
*/
function retournePrefixeUtilisateurPostfix($pdo,$utilisateur) {
try {
$req = 'SELECT prefixe FROM postfix_utilisateurs WHERE utilisateur=:utilisateur';
$sql=$pdo->prepare($req);
$sql->bindValue(':utilisateur', $utilisateur);
$sql->execute();
$prefixe_utilisateurs = $sql->fetchAll();
if (isset($prefixe_utilisateurs[0]['prefixe'])) {
return $prefixe_utilisateurs[0]['prefixe'];
}
else {
$prefixe_utilisateurs[0]['prefixe']="";
return $prefixe_utilisateurs[0]['prefixe'];
}
}
catch (\Exception $e) {
die ("Erreur de requête de changement de préfixe utilisateur : ".$e->getMessage());
}
}
/**
* Fonction changeant le préfixe d'un utilisateur.
*/
function changePrefixeUtilisateurPostfix($pdo,$utilisateur,$nouveau_prefixe) {
try {
$req = 'UPDATE postfix_utilisateurs SET prefixe=:nouveau_prefixe WHERE utilisateur=:utilisateur';
$sql=$pdo->prepare($req);
$sql->bindValue(':utilisateur', $utilisateur);
$sql->bindValue(':nouveau_prefixe', $nouveau_prefixe);
$sql->execute();
return true;
}
catch (\Exception $e) {
die ("Erreur de requête de changement de préfixe utilisateur : ".$e->getMessage());
}
}
/**
* Fonction vérifiant que l'utilisateur connecté au site existe toujours dans la base de données.
*/
function verifUtilisateursExiste($pdo,$utilisateur) {
try {
$req = 'SELECT utilisateur FROM postfix_utilisateurs WHERE utilisateur=:utilisateur';
$sql=$pdo->prepare($req);
$sql->bindValue(':utilisateur', $utilisateur);
$sql->execute();
$liste_utilisateurs = $sql->fetchAll(PDO::FETCH_ASSOC);
if (count($liste_utilisateurs) === 1) {
return TRUE;
}
else {
return FALSE;
}
}
catch (\Exception $e) {
die ("Erreur de requête de vérification d'existence de l'utilisateur connecté : ".$e->getMessage());
}
}
?>

@ -0,0 +1,79 @@
<?php
/**
* Fonction affichant les messages de retour des traitements site lorsqu'elle est interrogée.
*/
function retourneMessage($type_message,$num_message) {
if ($type_message=="succes" || $type_message=="erreurs" || $type_message=="messages") {
if (isset($_SESSION) && !empty($_SESSION['pseudo_utilisateur']) || !empty($_SESSION['nom_utilisateur']) || !empty($_SESSION['privilege_utilisateur'])) {
$pseudo_utilisateur=$_SESSION['pseudo_utilisateur'];
$nom_utilisateur=$_SESSION['nom_utilisateur'];
$privilege_utilisateur=$_SESSION['privilege_utilisateur'];
}
else {
$pseudo_utilisateur="visiteur";
$nom_utilisateur="visiteur";
$privilege_utilisateur="visiteur";
}
$liste_messages = array (
"succes" => array(
1 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Alias virtuel créé avec succès.</div>",
2 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Alias virtuel modifiés avec succès.</div>",
3 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Alias virtuel supprimé avec succès.</div>",
4 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Utilisateur changé en ".$pseudo_utilisateur." avec succès.</div>",
5 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Domaine créé avec succès.</div>",
6 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Domaine modifiés avec succès.</div>",
7 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Domaine supprimé avec succès.</div>",
8 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Domaine défini par défaut avec succès.</div>",
9 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> L'entrée de liste noire destiataires a été créée avec succès.</div>",
10 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> L'état de l'entrée de liste noire destiataires a été modifié avec succès.</div>",
11 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> L'entrée de liste noire destinataires a été supprimée avec succès.</div>",
12 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> L'entrée de liste noire expéditeurs a été créée avec succès.</div>",
13 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> L'état de l'entrée de liste noire expéditeurs a été modifié avec succès.</div>",
14 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> L'entrée de liste noire expéditeurs a été supprimée avec succès.</div>",
15 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Vous avez bien été déconnecté.</div>",
16 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Le mot de passe de $pseudo_utilisateur a bien été changé.</div>",
17 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Le préfixe de $pseudo_utilisateur a bien été changé.</div>",
18 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Vos données ont bien étés importés. Les doublons ainsi que les données erronées ont étés ignorés.</div>",
19 => "<div class=\"alert alert-success\" role=\"alert\"><img src=\"/fichiers/svg/check-circle.svg\"/> Vos données ont bien étés importés. Veuillez vous ré-identifier.</div>"),
"erreurs" => array(
1 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Aucun alias virtuel supprimé.</div>",
2 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Aucun alias virtuel créé car une donnée est erronée ou manquante.</div>",
3 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'alias virtuel est déjà existant.</div>",
4 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Code d'erreur générique (HY000) signifiant probablement que la base SQLite n'est pas accessible en écriture. Il faut que la base et le répertoire <i>bdd</i> appartiennent à <i>www-data</i>.</div>",
5 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Aucun domaine supprimé.</div>",
6 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Aucun domaine créé car une donnée est erronée ou manquante.</div>",
7 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Le domaine est déjà existant.</div>",
8 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Le domaine par défaut n'a pas été modifié.</div>",
9 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'entrée de liste noire destinataires n'a pas été ajouté car une donnée est erronée ou manquante.</div>",
10 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'état de l'entrée de liste noire destinataires n'a pas été modifié.</div>",
11 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'entrée de liste noire destinataires n'a pas été supprimée.</div>",
12 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'entrée de liste noire destinataires n'a pas été ajouté car elle est déjà existante.</div>",
13 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'entrée de liste noire expéditeurs n'a pas été ajouté car une donnée est erronée ou manquante.</div>",
14 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'état de l'entrée de liste noire expéditeurs n'a pas été modifié.</div>",
15 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'entrée de liste noire expéditeurs n'a pas été supprimée.</div>",
16 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'entrée de liste noire expéditeurs n'a pas été ajouté car elle est déjà existante.</div>",
17 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Identifiant ou mot de passe incorrecte.</div>",
18 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Vous n'avez pas les privilèges nécessaires.</div>",
19 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Erreur non gérée.</div>",
20 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> L'ancien mot de passe renseigné est erroné.</div>",
21 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Le mot de passe de confimation n'est pas identique au nouveau mot de passe.</div>",
22 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Une erreur s'est produite lors de l'envoi du fichier. Il doit être de type CSV de moins de 2 Mo et avoir été généré par Courtail.</div>",
23 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Votre fichier d'import CSV est plus grand que 2 Mo.</div>",
24 => "<div class=\"alert alert-danger\" role=\"alert\"><img src=\"/fichiers/svg/alert-triangle.svg\"/> Auncun type de donnée n'a été spécifié pour l'importation.</div>"),
"messages" => array(
"bienvenue" => "<div class=\"alert alert-primary\" role=\"alert\"><img src=\"/fichiers/svg/info.svg\"/> Bienvenue ".$nom_utilisateur.". Vous êtes authentifié en tant qu'".$privilege_utilisateur.".</div>")
);
$num_message=htmlspecialchars($num_message);
if (isset($liste_messages[$type_message][$num_message])) {
echo $liste_messages[$type_message][$num_message];
}
else {
echo $liste_messages['erreurs']['19'];
}
}
}
?>

@ -0,0 +1,133 @@
<?php
/**
* Page de fonctions permettant d'identifier la légitimité d'un utilisateur.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/connexion.php");
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/domaines.php");
/*
* Fonction d'authentification des utilisateurs.
* Elle récupère le couple identifiant/mot de passe renseigné par le visiteur sur la page d'accueil.
* Elle compare ces information avec la ligne correspondante dans la base de donnée (en prenant l'adresse de courriel comme sélecteur).
* Elle hache le mot de passe du formulaire et le compare à celui de l'entrée correspondante en base.
*/
function validationIdentifiants($pdo,$identifiant,$mdp) {
try {
$req = 'SELECT * FROM postfix_utilisateurs WHERE utilisateur=:identifiant';
if (!strpos($identifiant, '@')) {
$identifiant.="@".retourneDomaineDefautPostfix($pdo);
}
$sql = $pdo->prepare($req);
$sql->bindValue(':identifiant', htmlspecialchars($identifiant));
$sql->execute();
$result = $sql ->fetchAll(PDO::FETCH_ASSOC);
$comptage = count($result);
}
catch(PDOException $e) {
die ("Erreur lors du traitement de la requête : " . $e->getMessage());
}
if ($comptage == 1) {
/*
* Les paramètres de la fonction crypt de $mot_de_passe_form sont composés du mot de passe issue du formulaire accueil ainsi que du sel.
* Les signes dollars sont des séparateurs. $mot_de_passe_base[1]=6=SHA512. $mot_de_passe_base[2]=au sel du mot de passe contenu dans la base.
* Les paramètres de sel sont issus du mot de passe haché contenu dans la base.
* L'opération de vérification s'adapte ainsi automatiquement aux algoritmes utilisés par Dovecot lors du hachage du mot de passe utilisateur.
*/
$mot_de_passe_base=explode('$', $result['0']['mot_de_passe']);
$mot_de_passe_form=crypt(trim("$mdp"), "$".$mot_de_passe_base[1]."$".$mot_de_passe_base[2]."$");
if ("$".$mot_de_passe_base[1]."$".$mot_de_passe_base[2]."$".$mot_de_passe_base[3]!=$mot_de_passe_form) {
// Si le mot de passe renseigné n'est le bon, le visiteur est redirigé vers la page d'accueil avec un message explicatif
header('Location: /pages/visiteurs/echec_auth.php');
die();
}
return $result;
}
else
return false;
}
/*
* Fonction d'initialisation des valeurs de session.
* Elle utilise les informations de l'utilisateur en base de données pour initialiser une session personnalisée.
*/
function initValeursSession($result) {
$_SESSION['id_utilisateur'] = $result[0]['id'];
$_SESSION['pseudo_utilisateur'] = $result[0]['utilisateur'];
$_SESSION['nom_utilisateur'] = $result[0]['nom_complet'];
$_SESSION['privilege_utilisateur'] = $result[0]['privilege'];
$_SESSION['pseudo_connexion_utilisateur'] = $result[0]['utilisateur'];
}
/*
* Fonction de création d'un cookie de connexion automatique.
* Si la case "Se souvenir de moi" est cochée au formulaire de connexion, un cookie d'une durée d'un mois est créé.
*/
function creerCookie($result) {
$cookie_params = array (
'expires' => time() + 60*60*24*30,
'path' => '/',
'domain' => $_SERVER['HTTP_HOST'],
'secure' => false, // Mettre à true pour ne le créer qu'en HTTPS (le faire également dans "deconnexion.php" et au commentaire "Prolongement de la durée du cookie." plus bas).
'httponly' => true,
'samesite' => 'Strict'
);
setcookie('souvenir-session', htmlspecialchars($_SESSION['pseudo_utilisateur']).'|--@--|'.hash('sha3-512', $result['0']['id'].$result['0']['utilisateur'].$result['0']['mot_de_passe']), $cookie_params);
}
/*
* Fonction de validation du cookie de connexion automatique.
* Vérifie la légitimité du cookie de connexion de l'utilisateur via les informations en base de données.
* Si légitime, une session PHP est initialisée et le cookie est renouvelé pour un mois supplémentaire.
* Si non légitime, la fonction ne fait rien. Le comportement du site s'en remet alors à la seule existence d'une session PHP.
*/
function validationCookie($pdo) {
if (isset($_COOKIE['souvenir-session'])) {
if (!isset($_SESSION['id_utilisateur'])) {
$donnees_cookie=explode('|--@--|', $_COOKIE['souvenir-session']);
try {
$req = 'SELECT * FROM postfix_utilisateurs WHERE utilisateur=:identifiant';
$sql = $pdo->prepare($req);
$sql->bindValue(':identifiant', htmlspecialchars($donnees_cookie['0']));
$sql->execute();
$result = $sql ->fetchAll(PDO::FETCH_ASSOC);
$comptage = count($result);
}
catch(PDOException $e) {
die ("Erreur lors du traitement de la requête : " . $e->getMessage());
}
if ($comptage == 1) {
if (hash('sha3-512', $result['0']['id'].$result['0']['utilisateur'].$result['0']['mot_de_passe'])===$donnees_cookie['1']) {
// Cookie valide. On initialise les variables de sessions pour le bon fonctionnement du site.
initValeursSession($result);
}
else {
// Cookie invalide (probablement modifié par l'utilisateur).
return false;
}
return $result;
}
else
return false;
}
// Prolongement de la durée du cookie.
setcookie('souvenir-session', $_COOKIE['souvenir-session'], ['expires' => time() + 60*60*24*30, 'path' => '/', 'domain' => $_SERVER['HTTP_HOST'], 'secure' => false, 'httponly' => true, 'samesite' => 'strict']);
}
else
return false;
}
?>

@ -0,0 +1,26 @@
<?php
/**
* Page de fonctions permettant la vérification des permission d'un utilisateur ou d'un visiteur.
*/
/**
* Fonction testant le niveau de privilège d'un utilisateur authentifié ou non.
* Elle vérifie le privilège de l'utilisateur connecté et retourne la valeur adéquate.
* Si un visiteur ou un utilisateur ne remplis pas la bonne condition, il est redirigé à la page d'accueil avec un message expllicatif.
* Cette fonction devrait être appelée dans toute page de traitement.
*/
function testPrivileges(){
if (isset($_SESSION['id_utilisateur']) && !empty($_SESSION['id_utilisateur']) && $_SESSION['privilege_utilisateur']=="administrateur") {
return "administrateur";
}
elseif (isset($_SESSION['id_utilisateur']) && !empty($_SESSION['id_utilisateur']) && $_SESSION['privilege_utilisateur']=="utilisateur") {
return "utilisateur";
}
else {
header("Location: /pages/visiteurs/formulaire_accueil.php?erreur=18");
die();
}
}
?>

@ -0,0 +1,154 @@
<?php
/**
* Page de gestion importé par la page d'administration centrale permettant la gestion des alias virtuels.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/domaines.php");
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/utilisateurs.php");
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/alias_virtuels.php");
$liste_utilisateurs = listeUtilisateursPostfix($pdo);
$liste_domaines = listeDomainesPostfix($pdo);
$liste_alias_virtuels = listeAliasVirtuelsPostfix($pdo);
/**
* Affiche le formulaire de selection d'un utilisateur si celui connecté possède les privilèges administrateur.
*/
if (testPrivileges()==="administrateur") {
echo <<<_HEREDOC_
<div class="row">
<div class="col-12 col-md-6">
<div class="card mt-4 max-hauteur-pastille">
<div class="card-body">
<h5 class="card-title">Choix de l'adresse</h5>
<form id="form_choix_utilisateur" action="/services/traitements/utilisateurs/changeUtilisateur.php" method="post">
<select class="form-select" name="choix_utilisateur" onchange="autoSoumission(this);">
_HEREDOC_;
/**
* Remplis un menu déroulant permettant le choix de l'utilisateur par l'administrateur.
* Ce choix permettra de modifier les données y étant rattachés.
* La valeur par défaut du menu est positionnée sur le compte administrateur actuellement connecté.
*/
foreach ($liste_utilisateurs as $utilisateur_bdd => $utilisateur) {
if ($utilisateur['utilisateur']===$_SESSION['pseudo_utilisateur']) {
echo "<option value=\"".$utilisateur['utilisateur']."\" selected>".$utilisateur['utilisateur']."</option>\n";
}
elseif ($_SESSION['privilege_utilisateur']==="administrateur") {
echo "<option value=\"".$utilisateur['utilisateur']."\">".$utilisateur['utilisateur']."</option>\n";
}
}
echo <<<_HEREDOC_
</select>
<input type="hidden" name="page_origine" value="alias">
</form>
</div>
</div>
</div>
_HEREDOC_;
}
?>
<div class="col-12<?php if ($_SESSION['privilege_utilisateur']==="administrateur") {echo' col-md-6';}?>">
<div class="card mt-4 max-hauteur-pastille">
<div class="card-body">
<h5 class="card-title">Ajouter un alias virtuel</h5>
<form action="/services/traitements/alias_virtuels/ajoutAliasVirtuels.php" method="post">
<div class="input-group">
<?php
/**
* Ajout d'un alias virtuel.
*/
$prefixe_utilisateur=retournePrefixeUtilisateurPostfix($pdo,$_SESSION['pseudo_utilisateur']);
if (!empty($prefixe_utilisateur)) {
echo "<span class=\"input-group-text\">$prefixe_utilisateur</span>";
}
?>
<input type="text" class="form-control" id="nom_pour_filtre" name="nom_alias_virtuel" placeholder="alias" onkeyup="filtreValeurs();" required>
<span class="input-group-text">@</span>
<select class="form-select" name="choix_domaine_alias_virtuel">
<?php
try {
foreach ($liste_domaines as $domaine_bdd => $domaine) {
echo "<option value=\"".$domaine['domaine']."\">".$domaine['domaine']."</option>\n";
}
}
catch (\Exception $e) {
die ("Erreur de requête de selection du domaine pour \"$base\" : ".$e->getMessage());
}
?>
</select>
<!--<select name="choix_domaine_alias" onchange='document.getElementById("domaine_alias").innerHTML = this.options[this.selectedIndex].text'>
<option value=\"".$domaine['domaine']."\">".$domaine['domaine']."</option>
</select>
<p id="domaine_alias"><?php //echo $liste_domaines[0]["domaine"]; ?></p>-->
<button class="btn btn-primary bouton-ajout" type="submit"><img src="/fichiers/svg/plus.svg"/></button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Alias de <?php echo $_SESSION['pseudo_utilisateur']; ?></h5>
<form class="" action="/services/traitements/alias_virtuels/editEtatAliasVirtuels.php" method="post">
<div class="row" id="liste_pour_filtre">
<?php
/**
* Liste des alias virtuels.
*/
try {
foreach ($liste_alias_virtuels as $alias_virtuels_cles => $alias_virtuels_valeurs) {
if (isset($alias_virtuels_valeurs['active']) && $alias_virtuels_valeurs['active']=="1") {
$etat_interrupteur_allumer="checked";
$etat_interrupteur_eteint="";
} else {
$etat_interrupteur_eteint="checked";
$etat_interrupteur_allumer="";
}
echo <<<_HEREDOC_
<div class="col-md-6 col-xl-4">
<div class="card contour-pastille">
<div class="d-flex interieur-pastille">
<div class="champ-interrupteur">
<input type="radio" name="{$alias_virtuels_valeurs['id']}" id="allumer{$alias_virtuels_valeurs['id']}" onchange="activationBoutonFlotant(this)" value="1" {$etat_interrupteur_allumer}>
<label for="allumer{$alias_virtuels_valeurs['id']}" class="interrupteur-allumer"></label>
<input type="radio" name="{$alias_virtuels_valeurs['id']}" id="eteint{$alias_virtuels_valeurs['id']}" onchange="activationBoutonFlotant(this)" value="0" {$etat_interrupteur_eteint}>
<label for="eteint{$alias_virtuels_valeurs['id']}" class="interrupteur-eteint"></label>
</div>
<div class="titre-pastille">
<span>{$alias_virtuels_valeurs['courriel']}</span>
</div>
<div>
<label class="input-label-supprimer-valeur btn btn-danger btn-sm" for="supprimer-valeur-{$alias_virtuels_valeurs['id']}"><img src="/fichiers/svg/trash-2.svg"></label>
</div>
</div>
<input type="checkbox" class="input-checkbox-supprimer-valeur visuellement-cache" id="supprimer-valeur-{$alias_virtuels_valeurs['id']}">
<a class="confirmation-suppression card-footer" href="/services/traitements/alias_virtuels/supprAliasVirtuels.php?supprAliasVirtuel={$alias_virtuels_valeurs['id']}">Confirmer la suppression</a>
</div>
</div>
_HEREDOC_;
}
}
catch (\Exception $e) {
die ("Erreur de requête de selection utilisateurs pour \"$base\" : ".$e->getMessage());
}
?>
</div>
<button id="enregistrer-interrupteur" style="display: none;" class="bouton-flotant desactiver" type="submit"><img src="/fichiers/svg/save.svg"/></button>
</form>
</div>
</div>
<script src="/fichiers/js/filtre.js"></script>

@ -0,0 +1,137 @@
<?php
/**
* Page de gestion importé par la page d'administration centrale permettant la gestion des domaines.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/sessions/privileges.php");
if (testPrivileges()!=="administrateur") {
header("Location: /pages/visiteurs/formulaire_accueil.php?erreur=18");
die();
}
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/domaines.php");
$liste_domaines = listeDomainesPostfix($pdo);
?>
<!-- Affichage et changement du domaine par défaut -->
<div class="row">
<div class="col-12 col-md-6">
<div class="card mt-4 max-hauteur-pastille">
<div class="card-body">
<h5 class="card-title">Domaine par défaut</h5>
<?php
$domaine_defaut = retourneDomaineDefautPostfix($pdo);
if (empty($domaine_defaut)) {
echo <<<_HEREDOC_
<div class="alert alert-warning mb-2" role="alert"><img src="/fichiers/svg/alert-triangle-warning.svg"/>
Aucun domaine n'est défini par défaut. Celui-ci permet l'utilisation d'identifiants de connexion courts et le pré-sélectionne lors des créations d'alias virtuels.
</div>
_HEREDOC_;
}
?>
<form action="/services/traitements/domaines/changeDomaineDefaut.php" method="post">
<div class="input-group">
<select class="form-select" name="choix_domaine_defaut">
<?php
// Génération de la liste des domaines existants
try {
foreach ($liste_domaines as $domaine_cles => $domaine_valeurs) {
echo "<option value=\"".$domaine_valeurs['domaine']."\">".$domaine_valeurs['domaine']."</option>\n";
}
}
catch (\Exception $e) {
die ("Erreur de requête de selection du domaine pour \"$base\" : ".$e->getMessage());
}
?>
</select>
<!-- Définition du domaine sélectionné comme étant par défaut -->
<button class="btn btn-primary" type="submit">Définir par défaut</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card mt-4 max-hauteur-pastille">
<div class="card-body">
<h5 class="card-title">Ajouter un domaine</h5>
<form action="/services/traitements/domaines/ajoutDomaines.php" method="post">
<div class="input-group">
<input type="text" class="form-control" id="nom_pour_filtre" name="nom_domaine" onkeyup="filtreValeurs();" placeholder="domaine.tld" required/>
<button class="btn btn-primary bouton-ajout" type="submit"><img src="/fichiers/svg/plus.svg"/></button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="card mt-4 max-hauteur-pastille">
<div class="card-body">
<h5 class="card-title">Domaines du serveur de messagerie</h5>
<form class="" action="/services/traitements/domaines/editEtatDomaines.php" method="post">
<div class="row" id="liste_pour_filtre">
<?php
/**
* Liste des domaines.
*/
try {
foreach ($liste_domaines as $liste_domaines_cles => $liste_domaines_valeurs) {
if (isset($liste_domaines_valeurs['active']) && $liste_domaines_valeurs['active']=="1") {
$etat_interrupteur_allumer="checked";
$etat_interrupteur_eteint="";
} else {
$etat_interrupteur_eteint="checked";
$etat_interrupteur_allumer="";
}
if (isset($liste_domaines_valeurs['defaut']) && $liste_domaines_valeurs['defaut']=="1") {
$etat_bouton="disabled";
$bordure_pastille="border-primary interieur-pastille-bleu";
$post_texte=" <span class=\"text-primary\">(défaut)</span>";
} else {
$etat_bouton="";
$bordure_pastille="";
$post_texte="";
}
echo <<<_HEREDOC_
<div class="col-md-6 col-xl-4">
<div class="card contour-pastille {$bordure_pastille}">
<div class="d-flex interieur-pastille">
<div class="champ-interrupteur {$etat_bouton}">
<input type="radio" name="{$liste_domaines_valeurs['id']}" id="allumer{$liste_domaines_valeurs['id']}" onchange="activationBoutonFlotant(this)" value="1" {$etat_interrupteur_allumer}>
<label for="allumer{$liste_domaines_valeurs['id']}" class="interrupteur-allumer"></label>
<input type="radio" name="{$liste_domaines_valeurs['id']}" id="eteint{$liste_domaines_valeurs['id']}" onchange="activationBoutonFlotant(this)" value="0" {$etat_interrupteur_eteint}>
<label for="eteint{$liste_domaines_valeurs['id']}" class="interrupteur-eteint"></label>
</div>
<div class="titre-pastille">
<span>{$liste_domaines_valeurs['domaine']}{$post_texte}</span>
</div>
<div>
<label class="input-label-supprimer-valeur btn btn-danger btn-sm {$etat_bouton}" for="supprimer-valeur-{$liste_domaines_valeurs['id']}"><img src="/fichiers/svg/trash-2.svg"></label>
</div>
</div>
<input type="checkbox" class="input-checkbox-supprimer-valeur visuellement-cache" id="supprimer-valeur-{$liste_domaines_valeurs['id']}">
<a class="confirmation-suppression card-footer" href="/services/traitements/domaines/supprDomaines.php?supprDomaine={$liste_domaines_valeurs['id']}">Confirmer la suppression</a>
</div>
</div>
_HEREDOC_;
}
}
catch (\Exception $e) {
die ("Erreur de requête de selection utilisateurs pour \"$base\" : ".$e->getMessage());
}
?>
</div>
<button id="enregistrer-interrupteur" style="display: none;" class="bouton-flotant desactiver" type="submit"><img src="/fichiers/svg/save.svg"/></button>
</form>
</div>
</div>
<script src="/fichiers/js/filtre.js"></script>

@ -0,0 +1,173 @@
<?php
/**
* Page de gestion importé par la page d'administration centrale permettant la gestion des listes noires.
*/
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/sessions/privileges.php");
if (testPrivileges()!=="administrateur") {
header("Location: /pages/visiteurs/formulaire_accueil.php?erreur=18");
die();
}
require_once ($_SERVER["DOCUMENT_ROOT"]."/services/fonctions/bdd/listes_noires.php");
$liste_entrees_liste_noires_dst=listeEntreeListeNoireDstPostfix($pdo);
$liste_entrees_liste_noires_exp=listeEntreeListeNoireExpPostfix($pdo);
/**
* Permet d'inclure la page d'administration des listes noires déstinataires ou expéditeurs dédiée.
*/
if (isset($_GET['sousPage']) && !empty($_GET['sousPage'])) {
switch ($_GET['sousPage']) {
case 'destinataires':
echo <<<_HEREDOC_
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Ajouter une entrée dans la liste noire des destinataires</h5>
<form action="/services/traitements/liste_noire_dst/ajoutEntreeListeNoireDst.php" method="post">
<div class="input-group">
<input type="text" class="form-control" id="nom_pour_filtre" name="courriel_entree_liste_noire_dst" placeholder="utilisateur@domaine.tld" onkeyup="filtreValeurs();" required>
<select class="form-select" name="choix_action_entree_liste_noire_dst">
<option value="REJECT">REJECT</option>
<option value="DEFER">DEFER</option>
</select>
<button class="btn btn-primary bouton-ajout" type="submit"><img src="/fichiers/svg/plus.svg"/></button>
</div>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Entrées de la liste noire des destinataires du serveur de messagerie</h5>
<form class="" action="/services/traitements/liste_noire_dst/editEntreeListeNoireDst.php" method="post">
<div class="row" id="liste_pour_filtre">
_HEREDOC_;
/**
* Liste des entrées de la liste noire des destinataires.
*/
try {
foreach ($liste_entrees_liste_noires_dst as $liste_entrees_liste_noires_dst_cles => $liste_entrees_liste_noires_dst_valeurs) {
if (isset($liste_entrees_liste_noires_dst_valeurs['active']) && $liste_entrees_liste_noires_dst_valeurs['active']=="1") {
$etat_interrupteur_allumer="checked";
$etat_interrupteur_eteint="";
} else {
$etat_interrupteur_eteint="checked";
$etat_interrupteur_allumer="";
}
echo <<<_HEREDOC_
<div class="col-md-6 col-xl-4">
<div class="card contour-pastille">
<div class="d-flex interieur-pastille">
<div class="champ-interrupteur">
<input type="radio" name="{$liste_entrees_liste_noires_dst_valeurs['id']}" id="allumer{$liste_entrees_liste_noires_dst_valeurs['id']}" onchange="activationBoutonFlotant(this)" value="1" {$etat_interrupteur_allumer}>
<label for="allumer{$liste_entrees_liste_noires_dst_valeurs['id']}" class="interrupteur-allumer"></label>
<input type="radio" name="{$liste_entrees_liste_noires_dst_valeurs['id']}" id="eteint{$liste_entrees_liste_noires_dst_valeurs['id']}" onchange="activationBoutonFlotant(this)" value="0" {$etat_interrupteur_eteint}>
<label for="eteint{$liste_entrees_liste_noires_dst_valeurs['id']}" class="interrupteur-eteint"></label>
</div>
<div class="titre-pastille">
<span>{$liste_entrees_liste_noires_dst_valeurs['courriel']} <img class="chevron-dst" src="/fichiers/svg/chevron-right.svg" /> <span class="badge fond-arc-noire">{$liste_entrees_liste_noires_dst_valeurs['action']}</span></span>
</div>
<div>
<label class="input-label-supprimer-valeur btn btn-danger btn-sm" for="supprimer-valeur-{$liste_entrees_liste_noires_dst_valeurs['id']}"><img src="/fichiers/svg/trash-2.svg"></label>
</div>
</div>
<input type="checkbox" class="input-checkbox-supprimer-valeur visuellement-cache" id="supprimer-valeur-{$liste_entrees_liste_noires_dst_valeurs['id']}">
<a class="confirmation-suppression card-footer" href="/services/traitements/liste_noire_dst/supprEntreeListeNoireDst.php?supprEntreeListeNoireDst={$liste_entrees_liste_noires_dst_valeurs['id']}">Confirmer la suppression</a>
</div>
</div>
_HEREDOC_;
}
}
catch (\Exception $e) {
die ("Erreur de requête de selection utilisateurs pour \"$base\" : ".$e->getMessage());
}
echo <<<_HEREDOC_
</div>
<button id="enregistrer-interrupteur" style="display: none;" class="bouton-flotant desactiver" type="submit"><img src="/fichiers/svg/save.svg"/></button>
</form>
</div>
</div>
_HEREDOC_;
break;
case 'expediteurs':
echo <<<_HEREDOC_
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Ajouter une entrée dans la liste noire des expéditeurs</h5>
<form action="/services/traitements/liste_noire_exp/ajoutEntreeListeNoireExp.php" method="post">
<div class="input-group">
<input type="text" class="form-control" id="nom_pour_filtre" name="courriel_entree_liste_noire_exp" placeholder="utilisateur@domaine.tld" onkeyup="filtreValeurs();" required>
<select class="form-select" name="choix_code_retour_entree_liste_noire_exp">
<option value="554">REJECT</option>
</select>
<button class="btn btn-primary bouton-ajout" type="submit"><img src="/fichiers/svg/plus.svg"/></button>
</div>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Entrées de la liste noire des expéditeurs du serveur de messagerie</h5>
<form class="" action="/services/traitements/liste_noire_exp/editEntreeListeNoireExp.php" method="post">
<div class="row" id="liste_pour_filtre">
_HEREDOC_;
/**
* Liste des entrées de la liste noire des destinataires.
*/
try {
foreach ($liste_entrees_liste_noires_exp as $liste_entrees_liste_noires_exp_cles => $liste_entrees_liste_noires_exp_valeurs) {
if (isset($liste_entrees_liste_noires_exp_valeurs['active']) && $liste_entrees_liste_noires_exp_valeurs['active']=="1") {
$etat_interrupteur_allumer="checked";
$etat_interrupteur_eteint="";
} else {
$etat_interrupteur_eteint="checked";
$etat_interrupteur_allumer="";
}
echo <<<_HEREDOC_
<div class="col-md-6 col-xl-4">
<div class="card contour-pastille">
<div class="d-flex interieur-pastille">
<div class="champ-interrupteur">
<input type="radio" name="{$liste_entrees_liste_noires_exp_valeurs['id']}" id="allumer{$liste_entrees_liste_noires_exp_valeurs['id']}" onchange="activationBoutonFlotant(this)" value="1" {$etat_interrupteur_allumer}>
<label for="allumer{$liste_entrees_liste_noires_exp_valeurs['id']}" class="interrupteur-allumer"></label>
<input type="radio" name="{$liste_entrees_liste_noires_exp_valeurs['id']}" id="eteint{$liste_entrees_liste_noires_exp_valeurs['id']}" onchange="activationBoutonFlotant(this)" value="0" {$etat_interrupteur_eteint}>
<label for="eteint{$liste_entrees_liste_noires_exp_valeurs['id']}" class="interrupteur-eteint"></label>
</div>
<div class="titre-pastille">
<span><span class="badge fond-arc-noire">{$liste_entrees_liste_noires_exp_valeurs['code_retour']}</span> <img class="chevron-dst" src="/fichiers/svg/chevron-right.svg" /> {$liste_entrees_liste_noires_exp_valeurs['courriel']}</span>
</div>
<div>
<label class="input-label-supprimer-valeur btn btn-danger btn-sm" for="supprimer-valeur-{$liste_entrees_liste_noires_exp_valeurs['id']}"><img src="/fichiers/svg/trash-2.svg"></label>
</div>
</div>
<input type="checkbox" class="input-checkbox-supprimer-valeur visuellement-cache" id="supprimer-valeur-{$liste_entrees_liste_noires_exp_valeurs['id']}">
<a class="confirmation-suppression card-footer" href="/services/traitements/liste_noire_exp/supprEntreeListeNoireExp.php?supprEntreeListeNoireExp={$liste_entrees_liste_noires_exp_valeurs['id']}">Confirmer la suppression</a>
</div>
</div>
_HEREDOC_;
}
}
catch (\Exception $e) {
die ("Erreur de requête de selection utilisateurs pour \"$base\" : ".$e->getMessage());
}
echo <<<_HEREDOC_
</div>
<button id="enregistrer-interrupteur" style="display: none;" class="bouton-flotant desactiver" type="submit"><img src="/fichiers/svg/save.svg"/></button>
</form>
</div>
</div>
_HEREDOC_;
break;
default:
echo " <h1>Cette page n'existe pas.</h1>";
break;
}
}
?>
<script src="/fichiers/js/filtre.js"></script>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save