Springe zum Hauptinhalt

Archiv

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
    • pam_interpose.c

Links