PAM: Module und Applikationen entwickeln
Einführung
Module
- PAM Module sind Shared Libraries
- Implementation des Linux-PAM Module Provider Interface
- spezifiziert in
/usr/include/security/pam_modules.h
- Vorgehen:
Konventionen
PAM-Handle
- PAM-Modul arbeitet in einem Kontext
- Kontext wird definiert durch PAM-Handle
- PAM-Handle dient der Kommunikation (Datenaustauch)
- mit libpam
- der Funktionen eines Modules untereinander
- mit anderen Modulen
- mit der Applikation
- PAM-Handle: "blinde Struktur" - private Daten
- PAM-Handle gehört zur Applikation und wird dort initialisiert
- Funktionen benutzen einen Pointer
pamh vom Typ pam_handle_t
Datenaustausch (modulintern)
- Shared Library: keine statischen Variablen
- Funktionen
pam_set_data() und pam_get_data() realisieren Datenaustausch über pamh
- frei wählbarer Name zur Referenzierung der Daten (Namenskonflikte mit anderen Modulen beachten!)
- keine Struktur der Daten vorgegeben
- Modul kann Funktion definieren, die beim Freigeben/Löschen der Daten gerufen wird (
cleanup())
PAM-Items
- Datenaustausch zwischen Applikation und Modulen
- Funktionen
pam_set_item() und pam_get_item() realisieren Datenaustausch über pamh
- libpam definiert
item_type (PAM_USER, PAM_SERVICE, PAM_RHOST, PAM_CONV, ...)
User
- welcher Nutzer soll authentifiziert werden
- Funktion
pam_get_user() liefert das Loginkennzeichen
- definiert durch Applikation bei
pam_start() oder
- aus PAM-Item
PAM_USER oder
- durch Konversationsfunktion über
pam_conv
-
PAM_USER wird gesetzt
Konversationsfunktion
- Applikation stellt eine Funktion (callback) bereit, die vom Module gerufen werden kann, um mit dem Nutzer zu kommunizieren
- typisch: Passworteingabe
- Aufruf der Konversationsfunktion entsprechend Konventionen
#include <security/pam_appl.h>
struct pam_message {
int msg_style;
const char *msg;
};
struct pam_response {
char *resp;
int resp_retcode;
};
struct pam_conv {
int (*conv)(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr);
void *appdata_ptr;
};
- eine Applikation übergibt callback in Struktur
pam_conv während pam_start()
- Funktion
*conv() übernimmt vom rufenden Modul drei Argumente
-
num_msg - Anzahl von Nachrichten des Moduls an die Applikation, Anzahl erwarteter Antworten
-
msg -
num_msg Tupel: Stil der Nachricht (PAM_PROMPT_ECHO_OFF, PAM_PROMPT_ECHO_ON, PAM_ERROR_MSG, PAM_TEXT_INFO, PAM_BINARY_PROMPT, PAM_BINARY_MSG) und die Nachricht selbst
-
resp -
num_msg Antworten, resp_retcode ist ungenutzt (immer Wert 0)
- Achtung: die Applikation sollte Speicher für Antworten mit
malloc() anfordern, der Modul muss diesen Speicher mit free() freigeben
-
appdata_ptr
- kann benutzt werden, um Daten der Applikation zu referenzieren
- Modul kennt diese Daten nicht und kann sie nicht interpretieren (da applikationsabhängig)
- Konvention: Modul übergibt
appdata_ptr beim callback
- Verwendung: siehe mod_auth_pam
- Rückkehrwerte:
PAM_SUCCESS, PAM_CONV_ERR, PAM_BUF_ERR, ...
Code-Beispiel
#include <security/pam_module.h>
#include <security/pam_appl.h>
int prompt_password(struct pam_conv *pam_convp, char **response, int echo, const char *prompt)
{
struct pam_message mesg;
const struct pam_message *mesgp = &mesg;
struct pam_response *resp = NULL;
int errcode;
if (pam_convp == NULL || pam_convp->conv == NULL || response == NULL)
return PAM_CONV_ERR;
*response = NULL;
mesg.msg_style = echo ? PAM_PROMPT_ECHO_ON : PAM_PROMPT_ECHO_OFF;
mesg.msg = prompt;
errcode = (*(pam_convp->conv))(1, &mesgp, &resp, pam_convp->appdata_ptr);
if (resp) {
*response = resp->resp;
free(resp); /* but not resp->resp */
}
return errcode;
}
int pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
...
struct pam_conv * pam_convp = NULL;
int errorcode;
char * password = NULL;
char * username;
...
errcode = pam_get_user(pamh, (const char **) &username, NULL);
if (errcode != PAM_SUCCESS) {
return(errcode);
}
...
(void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &password);
if (password == NULL) {
if ((pam_get_item(pamh, PAM_CONV, (const void **) &pam_convp) != PAM_SUCCESS) ||
pam_convp == NULL || pam_convp->conv == NULL ) {
mylog(LOG_ERR, "Can not prompt for password (no conversation function");
return PAM_AUTH_ERR;
}
errcode = prompt_password(pam_convp, &password, 0, "Passwort: ");
if (errcode != PAM_SUCCESS || password == NULL) {
mylog(LOG_ERR, "Can not get password");
return PAM_AUTH_ERR;
}
if (password[0] == '\0') {
mylog(LOG_ERR, "Empty password is not allowed");
return PAM_NEW_AUTHTOK_REQD;
}
/* store the password for future reference */
(void) pam_set_item(pamh, PAM_AUTHTOK, password);
}
...
}
Authentication API's
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv);
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
int argc, const char **argv);
-
pam_sm_authenticate() - Authentisierung des Nutzers
-
pam_sm_setcred() - Übergabe von Credentials an Applikation
- verschiedene Teilfunktionen als
flags:
-
PAM_ESTABLISH_CRED - Credentials setzen
-
PAM_DELETE_CRED - Credentials löschen
-
PAM_REINITIALIZE_CRED - Credentials reinitialisieren
-
PAM_REFRESH_CRED - Lifetime der Credentials verlängern
- sollte immer gerufen werden nachdem Nutzer authentisiert ist, aber bevor Session eingerichtet wird
- Leidige Erfahrung: Einige Applikationen rufen es gar nicht, unvollständig, unkorrekt oder zum falschen Zeitpunkt
Account Management API's
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
int argc, const char **argv);
-
pam_sm_acct_mgmt() - Autorisierung
- ist es dem Nutzer erlaubt sich anzumelden (zu dieser Zeit, über dieses Terminal, ...)?
- muss das Passwort geändert werden?
Session Management API's
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char **argv);
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
int argc, const char **argv);
- eröffnen und schliessen einer Sitzung kann in unterschiedlichen Applikationen geschehen
- nur Informationen aus
pam_get_item() benutzen
Password Management API's
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
int argc, const char **argv);
- Passwort-Änderung
- ggf. nur, wenn Gültigkeit abgelaufen (
flags: PAM_CHANGE_EXPIRED_AUTHTOK)
Argumentübergabe
-
flags: individuelle Flags, OR-verküpft, immer: PAM_SILENT (Modul soll keine Text-Nachrichten an Applikation senden)
- Argumentübergabe über
argc und argv (wie üblich), jedoch argv[0] ist das erste Argument (und nicht Modulname o.dgl.)
Hinweise
- Module sollten immer alle sechs Funktionen implementieren und alle Flags korrekt auswerten
- ggf. nur
return
-
PAM_SUCCESS
-
PAM_IGNORE
- =PAM_SERVICE_ERR
- Speicher-Management
- dynamisch allokierten Speicher löschen(!) und freigeben
-
cleanup() für pam_[gs]et_data()
- keine statischen Variablen
- Argumente
- alle Funktionen sollten alle Argumente auswerten
- vereinfacht die Konfiguration
- nicht benötigte Argumente einfach ignorieren
- unbekannte Argumente, falsche Syntax, ... per
syslog() melden - nicht an die Applikation (und damit an den Nutzer)
Module erzeugen
gcc -shared -Xlinker -x -o pam_module.so pam_module.o -lwhatever
Applikationen
- für viele Applikationen ist Verwendung von PAM eine von mehreren Möglichkeiten
- auswählen beim compilieren (
configure)
- Konfigparameter zur Laufzeit (Konfigfile)
- Mischmasch
- Quellcode oft für verschiedene Plattformen
- API deklariert in
/usr/include/security/pam_appl.h
PAM-Nutzung initialisieren
extern int pam_start(const char *service_name, const char *user,
const struct pam_conv *pam_conversation,
pam_handle_t **pamh);
-
service_name - identifiziert PAM-Konfiguration
- festen Namen benutzen
- evtl. in Konfig-File der Applikation einstellen
- NICHT: Name des ausführbaren Files (
argv[0])
-
user - zu authentisierender Nutzer
-
pam_conversation - Konversations-Struktur, siehe oben
-
pamh - Zeiger auf PAM-Handle
- initialisiert PAM-Library und PAM-Handle
- liest Konfiguration
- eine Applikation bemerkt Änderungen im Konfig-File nur bei
pam_start()
- sollte
PAM_SUCCESS liefern
PAM-Nutzung beenden
extern int pam_end(pam_handle_t *pamh, int pam_status);
-
pamh - PAM-Handle, initialisiert durch
pam_start()
-
pam_status - End-Status der Applikation
- wird an
cleanup() Funktionen der einzelnen Module übermittelt
- unter normalen Bedingungen:
PAM_SUCCESS
Datenaustausch mit Modulen
- über PAM-ITEMs (siehe oben)
- Applikationen können die Funktionen
pam_[gs]_item() nutzen
- bestimmte Items sollte die Applikation immer setzen (falls sinnvoll)
-
PAM_RUSER
-
PAM_TTY
-
PAM_RHOST
- weiteres Items können "optional" gesetzt werden
-
PAM_USER_PROMPT
-
PAM_USER
- sollte schon bei
pam_start() gesetzt werden
- kann von Modulen verändert werden
- durch
pam_get_user()
- durch
pam_set_item()
-
PAM_SERVICE
Konversationsfunktion
- Applikation muss Konversationsfunktion bereitstellen (siehe oben)
Authentication API's
extern int pam_authenticate(pam_handle_t *pamh, int flags);
extern int pam_setcred(pam_handle_t *pamh, int flags);
Account Management API's
extern int pam_acct_mgmt(pam_handle_t *pamh, int flags);
Session Management API's
extern int pam_open_session(pam_handle_t *pamh, int flags);
extern int pam_close_session(pam_handle_t *pamh, int flags);
Password Management API's
extern int pam_chauthtok(pam_handle_t *pamh, int flags);
Code-Beispiel
#include <security/pam_appl.h>
static pam_handle_t *pamh;
static const char *pam_service = "";
static char* user;
static int my_conv(int num_msg, const struct pam_message **msg,
struct pam_response **response, void *appdata_ptr);
static struct pam_conv pam_conv = { &my_conv, NULL };
static char * my_askuser(const struct pam_message *msg)
{
/* dialogue with the user
* ask him/her using msg->msg_style and msg->msg
*/
...
}
static int my_conv(int num_msg, const struct pam_message **msg,
struct pam_response **response, void *appdata_ptr)
{
int i;
char * resp;
if (response) {
*response = (struct pam_response*) malloc (num_msg * sizeof(struct pam_response));
if (*response == NULL) return PAM_BUF_ERR;
}
for (i = 0; i < num_msg; i++) {
response[i].resp_retcode = 0;
resp = my_askuser(msg[i]);
if (resp == NULL) {
return PAM_CONV_ERR;
} else
response[i].resp = x_strdup(resp);
}
}
return PAM_SUCCESS;
}
static void pam_init()
{
if((pam_start(pam_service, user, &pam_conv, &pamh)) != PAM_SUCCESS) {
/* error handling: pam_start failed */
exit(ERROR);
}
}
static void authenticate()
{
int ret;
ret = pam_authenticate(pamh, NULL);
if (ret != PAM_SUCCESS) {
/* error handling: authentication failed */
pam_end(pamh, ret);
exit(ERROR);
}
}
...
main(int argc, char* argv[])
{
...
pam_init();
authenticate();
...
pam_end(pamh, PAM_SUCCESS);
}
- Hinweis: welche Funktionen der PAM-API eine Applikation ruft, bleibt dem Autor überlassen
Testen
- während Modul-Entwicklung
- für den Einsatz
- alle Szenarien berücksichtigen
- Verhalten im Fehlerfall testen
- falsche Eingaben, Fehlbedienungen
- Logs überprüfen: werden Passworte in Logs geschrieben?
- Zusammenspiel unbekannter Module/unbekannter Applikationen
Links