\documentclass{howto}
\usepackage{ltxmarkup}

\title{pam\_python}

\makeindex

\author{Russell Stuart}
\authoraddress{
        Lube Mobile PTY LTD \\
        Email: \email{russell-pampython@stuart.id.au}
}


\begin{document}

\maketitle

\begin{abstract}
\noindent
\module{Pam_python} is a PAM module that runs the Python interpreter,
and so allows PAM modules to be written in Python.
\end{abstract}

\tableofcontents


\section{Introduction}\label{intro}

  The \module{pam_python} PAM module
  runs the Python source file (aka Python PAM module) it is given
  in the Python interpreter,
  making the PAM module API available to it.
  This document describes the how the PAM Module API
  is exposed to the Python PAM module.
  It does not describe how to use the API.
  You must read the
  \citetitle[http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/]{PAM module writers guide}
  to learn how to do that.
  To re-iterate: this document does not tell you how to write PAM modules,
  it only tells you how to access that the PAM module API from Python.

  Writing PAM modules from Python incurs a large performance penalty
  and requires Python to be installed,
  so it is not the best option for writing modules that will be used widely.
  On the other hand memory allocation / corruption problems
  can not be caused by bad Python code,
  and a Python module is generally shorter and easier to write
  than its C equivalent.
  This makes it ideal for the system administrator
  who just wants to make use of the the PAM API for his own ends
  while minimising the risk of introducing memory corruption problems
  into every program using PAM.

\section{Configuring PAM}\label{configuring}

  Tell PAM to use a Python PAM module in the usual way:
  add a rule to your PAM configuration.
  The PAM administrators manual gives the syntax of a rule as:

\begin{verbatim}
service type control module-path module-arguments
\end{verbatim}

  The first three parameters are the same for all PAM modules,
  and so aren't any different for \module{pam_python}.
  The \var{module-path} is the path to pam_python.so.
  Like all paths PAM modules it is relative to the default PAM module directory
  so is usually just the string \code{pam_python.so}.
  The first \var{module-argument} is the path to the Python PAM module.
  If it doesn't start with a / it is relative to the \code{/lib/security}.
  All \var{module-arguments}, including the path name to the Python PAM module
  are passed to it.

\section{Python PAM modules}\label{module}

  When a PAM handle created by the applications call to
  \method{pam_start(3)} first uses a Python PAM module,
  \module{pam_python} invokes it using Python's \code{execfile} function.  
  The following variables are passed to the invoked module
  in its global namespace:

  \begin{datadesc}{__builtins__}
    The usual Python \code{__builtins__}.
  \end{datadesc}

  \begin{datadesc}{__file__}
    The absolute path name to the Python PAM module.
  \end{datadesc}

  As described in the PAM Module Writers Guide,
  PAM interacts with your module by calling its methods.
  Each \code{type} in the PAM configuration rules results
  in one or more methods being called.
  The Python PAM module must define the methods that will be called
  by each rule \code{type} it can be used with.
  Those methods are:

  \begin{methoddesc}{pam_sm_acct_mgmt}{pamh, flags, args}
    The service module's implementation of the
    \method{pam_acct_mgmt(3)} interface.
  \end{methoddesc}

  \begin{methoddesc}{pam_sm_authenticate}{pamh, flags, args}
    The service module's implementation of the
    \method{pam_authenticate(3)} interface.
  \end{methoddesc}

  \begin{methoddesc}{pam_sm_close_session}{pamh, flags, args}
    The service module's implementation of the
    \method{pam_close_session(3)} interface.
  \end{methoddesc}

  \begin{methoddesc}{pam_sm_chauthtok}{pamh, flags, args}
    The service module's implementation of the
    \method{pam_chauthtok(3)} interface.
  \end{methoddesc}

  \begin{methoddesc}{pam_sm_open_session}{pamh, flags, args}
    The service module's implementation of the
    \method{pam_open_session(3)} interface.
  \end{methoddesc}

  \begin{methoddesc}{pam_sm_setcred}{pamh, flags, args}
    The service module's implementation of the \method{pam_setcred(3)} interface.
  \end{methoddesc}

  The arguments and return value of all these methods are the same.
  The \var{pamh} parameter is an instance of the \class{PamHandle} class.
  It is used to interact with PAM and is described in the next section.
  The remaining arguments are as described
  in the PAM Module Writers Guide.
  All functions must return an integer, eg \constant{pamh.PAM_SUCCESS}.
  The valid return codes for each function
  are defined PAM Module Writers Guide.  
  If the Python method isn't present
  \module{pam_python} will return \constant{pamh.PAM_SYMBOL_ERR} to PAM;
  if the method or doesn't return an integer or throws an exception
  \constant{pamh.PAM_SERVICE_ERR} is returned.

  There is one other method that can be defined by the Python PAM module.
  Its optional:

  \begin{methoddesc}{pam_sm_end}{pamh}
     If present this will be called when the application calls
     \method{pam_end(3)}.
     If not present nothing happens.
     The parameter \var{pamh} is the \class{PamHandle} object.
     The return value is ignored.
  \end{methoddesc}

\section{The PamHandle Class}\label{pamhandle}

  An instance of this class is automatically created
  for a Python PAM module when it is first referenced,
  (ie when it is \code{execfile}'ed).
  It is the first argument to every Python method called by PAM.
  It is destroyed automatically when \method{pam_end(3)} is called,
  right after the \code{execfile}'ed module is destroyed.
  If any method fails, or any access to a member fails
  a \exception{PamHandle.exception} exception will be thrown.
  It contains the following members:

  \begin{datadesc}{PAM_???}
    All the \constant{PAM_???} constants
    defined in the PAM include files are available.
    They are all read-only \class{int}'s.
  \end{datadesc}

  \begin{datadesc}{authtok}
    The \constant{PAM_AUTHTOK} PAM item.
    Reading this results in a call \method{pam_get_item(PAM_AUTHTOK)},
    writing it results in a call \method{pam_set_item(PAM_AUTHTOK, value)}.
    Its value will be either a \class{string}
    or \constant{None} for the C value \constant{NULL}.
  \end{datadesc}

  \begin{datadesc}{env}
    This is a mapping representing the PAM environment.
    \module{pam_python} implements accesses and changes to it
    via the PAM methods \method{pam_getenv()}, \method{pam_putenv()}
    and \method{pam_getenvlist()}.
    The PAM environment only supports \class{string} keys and values,
    and the keys may not be blank or contain '='.
  \end{datadesc}

  \begin{datadesc}{exception}
    The exception raised by methods defined here if they fail.
    It is a subclass of \class{StandardError}.
    Instances contain the member \constant{pam_result},
    which is the error code returned by PAM.
    The description is the PAM error message.
  \end{datadesc}

  \begin{datadesc}{libpam_version}
    The version of PAM in use, an integer.
  \end{datadesc}

  \begin{datadesc}{pamh}
    The PAM handle, as read-only \class{int}.
    Possibly useful during debugging.
  \end{datadesc}

  \begin{datadesc}{oldauthtok}
    The \constant{PAM_OLDAUTHTOK} PAM item.
    Reading this results in a call \method{pam_get_item(PAM_OLDAUTHTOK)},
    writing it results in a call \method{pam_set_item(PAM_OLDAUTHTOK, value)}.
    Its value will be either a \class{string}
    or \constant{None} for the C value \constant{NULL}.
  \end{datadesc}

  \begin{datadesc}{rhost}
    The \constant{PAM_RHOST} PAM item.
    Reading this results in a call \method{pam_get_item(PAM_RHOST)},
    writing it results in a call \method{pam_set_item(PAM_RHOST, value)}.
    Its value will be either a \class{string}
    or \constant{None} for the C value \constant{NULL}.
  \end{datadesc}

  \begin{datadesc}{ruser}
    The \constant{PAM_RUSER} PAM item.
    Reading this results in a call \method{pam_get_item(PAM_RUSER)},
    writing it results in a call \method{pam_set_item(PAM_RUSER, value)}.
    Its value will be either a \class{string}
    or \constant{None} for the C value \constant{NULL}.
  \end{datadesc}

  \begin{datadesc}{service}
    The \constant{PAM_SERVICE} PAM item.
    Reading this results in a call \method{pam_get_item(PAM_SERVICE)},
    writing it results in a call \method{pam_set_item(PAM_SERVICE, value)}.
    Its value will be either a \class{string}
    or \constant{None} for the C value \constant{NULL}.
  \end{datadesc}

  \begin{datadesc}{tty}
    The \constant{PAM_TTY} PAM item.
    Reading this results in a call \method{pam_get_item(PAM_TTY)},
    writing it results in a call \method{pam_set_item(PAM_TTY, value)}.
    Its value will be either a \class{string}
    or \constant{None} for the C value \constant{NULL}.
  \end{datadesc}

  \begin{datadesc}{user}
    The \constant{PAM_USER} PAM item.
    Reading this results in a call \method{pam_get_item(PAM_USER)},
    writing it results in a call \method{pam_set_item(PAM_USER, value)}.
    Its value will be either a \class{string}
    or \constant{None} for the C value \constant{NULL}.
  \end{datadesc}

  \begin{datadesc}{user_prompt}
    The \constant{PAM_USER_PROMPT} PAM item.
    Reading this results in a call \method{pam_get_item(PAM_USER_PROMPT)},
    writing it results in a call \method{pam_set_item(PAM_USER_PROMPT, value)}.
    Its value will be either a \class{string}
    or \constant{None} for the C value \constant{NULL}.
  \end{datadesc}

  The following methods are available:

  \begin{methoddesc}[PamHandle]{Message}{msg_style,msg}
    Creates an instance of the \class{PamMessage} class.
    Instances of this class can be passed to the \method{conversation()} method.
    They are the equivalent of the C API's \code{struct pam_message} type,
    and have the same members.
    The arguments become the instance members of the same name.
    Instances are immutable.
  \end{methoddesc}

  \begin{methoddesc}[PamHandle]{Response}{resp,ret_code}
    Creates an instance of the \class{PamResponse} class.
    Instances of this class are returned by the \method{conversation()} method.
    They are the equivalent of the C API's \code{struct pam_response} type,
    and have the same members.
    The arguments become the instance members of the same name.
    Instances are immutable.
  \end{methoddesc}

  \begin{methoddesc}[PamHandle]{conversation}{prompts}
    Calls the function defined by the \constant{PAM_CONV} item.
    The \var{prompts} argument is a \class{Message} object
    or a \class{list} them.
    You don't have to pass an actual \class{Message} object,
    any class that defines a \class{string} member \member{msg}
    and a \class{int} member \member{msg_style} will do.
    These members are used to initialise the \code{struct pam_message}
    members of the same name.
    It returns either a single \class{Response} object if a single
    \class{Message} was passed,
    or a \class{list} of them of the same length as the \class{list} passed.
    These \class{Response} objects contain the data the user entered.
  \end{methoddesc}

  \begin{methoddesc}[PamHandle]{fail_delay}{delay}
    This results in a call to \method{pam_fail_delay()},
    which sets the maximum random delay after an authentication failure
    to \var{delay} milliseconds.
  \end{methoddesc}

  \begin{methoddesc}[PamHandle]{get_user}{\optional{prompt}}
    This results in a call to \method{pam_get_user()},
    which returns the current user name (a \class{string})
    or \constant{None} if \method{pam_get_user()} return \constant{NULL}.
    If not known it asks the PAM application for the user name,
    giving it the \class{string} \var{prompt} parameter
    to prompt the user to enter it.
  \end{methoddesc}

  \begin{methoddesc}[PamHandle]{strerror}{errnum}
    This results in a call to \method{pam_strerror()},
    which returns a \class{string} description
    of the \class{int} PAM reurn value \var{errnum}.
  \end{methoddesc}

  There is no interface provided for PAM's
  \method{pam_get_data()} and \method{pam_set_data()} methods.
  There are two reasons for this.
  Firstly those two methods are provided so C code can have private storage
  local to the PAM handle.
  A Python PAM Module can use own module name space to do the same job,
  and its easier to do so.
  But more importantly its safer because there is no type-safe way
  of providing access to the facility from Python.

\section{Diagnostics, Debugging, Bugs}\label{diagnostics}

  The way \module{pam_python} operates
  will be foreign to most Python programmers.
  It embeds Python into existing programs, primarily ones written in C.
  This means some things, like debugging and diagnostics, are done
  differently to a normal Python program.

\subsection{Diagnostics}\label{return-values}

  If \module{pam_python} returns something
  other than \constant{PAM_SUCCESS} to PAM
  a message will be written to the \code{syslog} \code{LOG_AUTHPRIV} facility.
  The only exception to this is when \module{pam_python}
  is passing on the return value
  from a Python \method{pam_sm_...()} entry point -
  nothing is logged in that case.
  So, if your Python PAM Module is failing in mysterious ways check syslog.
  The diagnostic or traceback Python would normally print to \constant{stderr}
  will be in there.

  The PAM result codes returned directly by \module{pam_python} are:

  \begin{datadesc}{PAM_BUF_ERR}
    Memory allocation failed.
  \end{datadesc}

  \begin{datadesc}{PAM_MODULE_UNKNOWN}
    The Python PAM module name wasn't supplied.
  \end{datadesc}

  \begin{datadesc}{PAM_OPEN_ERR}
    The Python PAM module could not be opened.
  \end{datadesc}

  \begin{datadesc}{PAM_SERVICE_ERR}
    A Python exception was thrown,
    unless it was because of a memory allocation failure.
  \end{datadesc}

  \begin{datadesc}{PAM_SYMBOL_ERR}
    A \method{pam_sm_...()} called by PAM
    wasn't defined by the Python PAM module.
  \end{datadesc}

\subsection{Debugging}\label{debugging}

  If you have Python bindings for the PAM Application library
  then you can write test units in Python
  and use Pythons \module{pdb} module debug a Python PAM module.
  This is how \module{pam_python} was developed.

  I used \citetitle[http://www.pangalactic.org/PyPAM/]{PyPAM}
  for the Python Application library bindings.
  Distributions often package it as \code{python-pam}.
  To set breakpoints in \module{pdb} either wait until
  PAM has loaded your module,
  or \code{import} it before you start debugging.

\subsection{Bugs}\label{bugs}

  There are several design decisions you may stumble across
  when using \module{pam_python}.
  One is that the Python PAM module is isolated
  from the rest of the Python environment.
  This differs from a \code{import}'ed Python module,
  where regardless of how many times a module is imported
  there is only one copy that shares the one global name space.
  So, for example, if you \code{import} your Python PAM module
  and then debug it as suggested above then there will be 2 copies
  of your Python PAM module in memory -
  the imported one and the one PAM is using. 
  If the PAM module sets a global variable
  you won't see it in the \class{import}'ed one.
  Indeed, obtaining any sort of handle to the module the PAM is using
  is near impossible.
  This means the debugger can inspect variables in the module
  only when a breakpoint has one of the modules functions
  in its backtrace.

  There are a few of reasons for this.
  Firstly, the PAM Module Writers Guide
  says this is the way it should be,
  so \module{pam_python} encourages it.
  Secondly, if a PAM application is using a Python PAM Module
  its important the PAM module remains as near to invisible as possible
  to avoid conflicts.
  Finally, and most importantly,
  references to objects constructed by the Python PAM module
  must never leak.
  This is because the destructors to those objects are C functions
  that live in \module{pam_python},
  and those destructors are called when all references to the objects are gone.
  When \method{pam_end(3)} is called \module{pam_python} is unloaded,
  and with it goes the destructor code.
  Should a reference to an object defined by \module{pam_python}
  exist after \method{pam_end(3)} returns
  the call to destructor will result in a jump to a non-existent address
  causing a \code{SIGSEGV}.

  Another potential trap is the initialisation and finalisation
  of the Python interpreter itself.
  Calling the interpreter's finalisation routine while it is in use
  would I imagine be a big no-no.
  If \module{pam_python} has to initialise the interpreter
  (by calling \method{Py_Initialize()})
  then it will call its finaliser (\method{Py_Finalize()}
  when the last Python PAM module is destroyed.
  This is heuristic works in most scenarios.
  One example where is won't work is a sequence like:
  \code{start-python-pam-module; application-initialises-interpreter;
  stop-python-pam-module; application-stops-interpreter}.
  This is doomed to fail.

\section{An example}\label{example}

  This is one of the examples provided by the package:

\verbatiminput{pam_permit.py}

  Assuming it and \code{pam_python.so} are in the directory \code{/lib/security}
  adding these rules to \code{/etc/pam.conf} would run it:

\begin{verbatim}
login account   requisite   pam_python.so pam_accept.py
login auth      requisite   pam_python.so pam_accept.py
login password  requisite   pam_python.so pam_accept.py
login session   requisite   pam_python.so pam_accept.py
\end{verbatim}

\end{document}
