#+TITLE: How BSD Authentication Works
#+DATE: 2020-11-02T16:49:46-05:00
#+DRAFT: true
#+SHOWTOC: true
#+DESCRIPTION:
#+TAGS[]: openbsd
#+KEYWORDS[]: openbsd
#+SLUG:
#+SUMMARY:

#+ATTR_HTML: :title OpenBSD Internals
#+ATTR_HTML: :alt OpenBSD mascot cutaway view with spinning gears inside
[[file:openbsd_internals.gif]]

* History
  :PROPERTIES:
  :CUSTOM_ID: history
  :END:

  The way OpenBSD authenticates users is quite different from other
  Unix-like operating systems. Most systems from AIX to Solaris,
  including Linux, the other BSDs, and MacOS, use a framework called
  [[https://en.wikipedia.org/wiki/Pluggable_authentication_module][Pluggable Authentication Module]] (PAM). The two main implementations
  are [[http://www.linux-pam.org/][Linux PAM]] and [[https://www.openpam.org/][OpenPAM]]. PAM modules are created as dynamically
  loaded shared objects, which communicate using a combination of
  common and implementation specific interfaces ([[https://linux.die.net/man/3/pam][Linux-PAM]] and
  [[https://www.freebsd.org/cgi/man.cgi?query=pam&apropos=0&sektion=3&manpath=FreeBSD+12.1-RELEASE+and+Ports&arch=default&format=html][OpenPAM]]). It's configured using the [[https://linux.die.net/man/5/pam.d][pam.d]] directory and [[https://www.freebsd.org/cgi/man.cgi?query=pam.conf&sektion=5&apropos=0&manpath=FreeBSD+12.1-RELEASE+and+Ports][pam.conf]]
  file. While it can be flexible, it's highly complex and very easy to
  mis-configure, leaving you open to strange and hard to track down
  authentication bugs. On top of that, the fact that it's a shared
  library means that any vulnerability in a poorly vetted
  authentication module gives attackers direct access to the internals
  of your application. Author Michael W. Lucas said it best when he
  described PAM as [[https://www.youtube.com/watch?v=-CXp3byvI1g][unstandardized black magic]].

  OpenBSD on the other hand uses a mechanism called BSD
  Authentication. It was originally developed for a now-defunct
  proprietary operating system called [[https://en.wikipedia.org/wiki/BSD/OS][BSD/OS]] by [[https://en.wikipedia.org/wiki/Berkeley_Software_Design][Berkeley Software
  Design Inc.]], who later donated the system. It was then adopted by
  OpenBSD in release 2.9. BSD Auth is comparatively much simpler than
  PAM. Modules or, authentication "styles", are instead stand alone
  applications or scripts that communicate over IPC. The module has no
  ability to interfere with the parent and can very easily revoke
  permissions using [[https://man.openbsd.org/pledge][=pledge(2)=]] or [[https://man.openbsd.org/unveil][=unveil(2)=]]. The BSD Authentication
  system of configured through [[https://man.openbsd.org/login.conf][=login.conf(5)=]].

* Why
  :PROPERTIES:
  :CUSTOM_ID: why
  :END:

  There isn't much on the internet about how to use BSD
  Authentication. I was curious about how the internals worked, and I
  figured someone else might be too :^)

* Documentation
  :PROPERTIES:
  :CUSTOM_ID: documentation
  :END:

  All of the high level authentication functions are described in
  [[https://man.openbsd.org/authenticate][=authenticate(3)=]], with the lower level functions being described in
  [[https://man.openbsd.org/auth_subr][=auth_subr(3)=]].

  Click on any function prototype in this post to see its definition.

  All code snippets from this blog post belong to the OpenBSD
  contributors. Please see the [[#copyright][Copyright]] section for details.

* BSD Auth Modules
  :PROPERTIES:
  :CUSTOM_ID: modules
  :END:

  Modules are located in =/usr/libexec/auth/= with the naming
  convention =login_<style>=. They accept arguments in the following
  form.

  #+BEGIN_SRC shell
  login_<style> [-s service] [-v key=value] user [class]
  #+END_SRC

  - =<style>= is the authentication method. This could be =passwd=,
    =radius=, =skey=, =yubikey=, etc. There's more information about
    available styles in [[https://man.openbsd.org/login.conf][=login.conf(5)=]] under the [[https://man.openbsd.org/login.conf#AUTHENTICATION][=AUTHENTICATION=]]
    header.
  - =service= is the service type. Typically authentication methods
    will accept one of three values here: =login=, =challenge=, or
    =response=. =login= is the default if it's not specified, and is
    used to let the module know to interact with the user directly
    through =stdin= and =stdout=, while =challenge= and =response= are
    used to pass messages back and forth through the BSD Auth API.
    Each style's man page will have more details on these.
  - =-v key=value= is an optional argument. There can be more than one
    arguments in this style. This is used to pass extra data to the
    program under certain circumstances.
  - =user= is the name of the user to be authenticated.
  - =class= is optional and specifies the login class to use for the
    user.

  =login= and =su= pass in extra data as =-v= flags.

  #+CAPTION: Taken from [[https://man.openbsd.org/login.conf][=login.conf(5)=]]
  #+BEGIN_SRC
  The login(1) program provides the following through the -v option:

     auth_type       The type of authentication to use.

     fqdn            The hostname provided to login by the -h option.

     hostname        The name login(1) will place in the utmp file for the
                     remote hostname.

     local_addr      The local IP address given to login(1) by the -L option.

     lastchance      Set to "yes" when a user's password has expired but the
                     user is being given one last chance to login and update
                     the password.

     login           This is a new login session (as opposed to a simple
                     identity check).

     remote_addr     The remote IP address given to login(1) by the -R option.

     style           The style of authentication used for this user (see
                     approval scripts below).

     The su(1) program provides the following through the -v option:

     wheel           Set to either "yes" or "no" to indicate if the user is in
                     group wheel when they are trying to become root.  Some
                     authentication types require the user to be in group
                     wheel when using the su(1) program to become super user.
  #+END_SRC

  The auth module communicates with its caller through what's called
  the "back channel" on file descriptor 3.

  Some modules require an extra file descriptor to be passed in for
  stateful challenge/response authentication. In these cases, an extra
  =-v fd=4= argument will be passed. Theoretically this =fd= can be
  any number, but in practice =fd=4= is hard-coded.


  Most modules also have a hidden flag =-d=, which sets the back
  channel do =stdio=, presumably for debugging purposes.

  The simplest way to authenticate a user with BSD Auth is by using
  [[#auth_userokay][=auth_userokay=]].

** TODO How are these configured in login.conf?
* Approval Scripts
  :PROPERTIES:
  :CUSTOM_ID: approval
  :END:

  Approval scripts can be much simpler than the full login modules
  used by the other functions. They are given the same back-channel as
  auth modules, but should not explicitly authenticate or revoke
  users. They should exit with a zero status for approval, or non-zero
  status to signal disapproval.

  Approval scrips receive arguments in the following form.
  #+begin_src shell
  approve [-v name=value] username class service
  #+end_src

  It can also receive extra key-value =-v= arguments in the same format as
  [[#modules][auth modules]]. More information is available in the [[https://man.openbsd.org/login.conf#APPROVAL][=APPROVAL=]]
  section of the =login.conf= man page.

  Approval scripts are run using [[#auth_approval][=auth_approval=]].
** TODO How are these configured in login.conf?

* auth_userokay
  :PROPERTIES:
  :CUSTOM_ID: auth_userokay
  :END:

  [[https://man.openbsd.org/authenticate.3#auth_userokay][=auth_userokay=]] is the highest level function, and easiest to use.
  It takes four character arrays as arguments, =name=, =style=,
  =type=, and =password=. It returns either a =0= for failure, of a
  non-zero value for success.

  This function lives inside =/lib/libc/gen/authenticate.c=

  @@html: <details> <summary> @@
  #+BEGIN_SRC c
  int auth_userokay(char *name, char *style, char *type, char *password);
  #+END_SRC
  @@html: </summary> @@
  #+begin_src c
  {
      auth_session_t *as;

      as = auth_usercheck(name, style, type, password);

      return (as != NULL ? auth_close(as) : 0);
  }
  #+end_src
  @@html: </details> @@

  - =name= is the name of the user to be authenticated
  - =style= is the login method to be used
    - If =style= is =NULL=, the user's default login style will be
      used. By default this is =passwd= on normal accounts.
    - The style can be one of the installed authentication methods, like
      =passwd=, =radius=, =skey=, =yubikey=, etc.
    - There's more information about available styles in =login.conf(5)=
    - Styles can also be installed through BSD Auth module packages
  - =type= is the authentication type
    - Types are defined in =login.conf= and define a group of allowed
      auth styles
    - If =type= is =NULL=, use the auth type for the user's login
      class. The default type is =auth-default=, which allows
      =psaswd= and =skey= auth methods.
    - There's more information about how to add methods in =login.conf(5)=
  - =password= is the password to test
    - If =password= is =NULL=, then the user is interactively
      prompted. This is required for auth styles using
      challenge-response methods.
    - If =password= is specified, then it's non-interactively tested

  =auth_userokay= is just a wrapper around [[#auth_usercheck][=auth_usercheck=]] that takes
  care of closing the session using [[#auth_close][=auth_close=]], and returning the
  resulting value.

* auth_session_t
  :PROPERTIES:
  :CUSTOM_ID: auth_session_t
  :END:
  =auth_session_t= is the main data structure used to represent the
  authentication session.

  #+BEGIN_SRC c
  struct auth_session_t {
      char    *name;                 /* name of use being authenticated */
      char    *style;                /* style of authentication used */
      char    *class;                /* class of user */
      char    *service;              /* type of service being performed */
      char    *challenge;            /* last challenge issued */
      int     flags;                 /* see below */
      struct  passwd *pwd;           /* password entry for user */
      struct  timeval now;           /* time of authentication */

      int     state;                 /* authenticated state */

      struct  rmfiles *rmlist;       /* list of files to remove on failure */
      struct  authopts *optlist;     /* list of options to scripts */
      struct  authdata *data;        /* additional data to send to scripts */

      char    spool[MAXSPOOLSIZE];   /* data returned from login script */
      int     index;                 /* how much returned thus far */

      int     fd;                    /* connection to authenticator */

      va_list ap0;                   /* argument list to auth_call */
      va_list ap;                    /* additional arguments to auth_call */
  };
  #+END_SRC

  Where =MAXSPOOLSIZE=, =authdata=, =authopts=, and =rmfiles= are defined as

  #+BEGIN_SRC c
  #define	MAXSPOOLSIZE	(8*1024)	/* Spool up to 8K of back info */

  struct rmfiles {
      struct rmfiles  *next;
      char            *file;
  };

  struct authopts {
      struct authopts *next;
      char            *opt;
  };

  struct authdata {
      struct  authdata *next;
      void    *ptr;
      size_t   len;
  };
  #+END_SRC

  There are several functions which get used to operate on
  =auth_session_t= to keep it opaque.
** auth_setdata
   :PROPERTIES:
   :CUSTOM_ID: auth_setdata
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   int auth_setdata(auth_session_t *as, void *ptr, size_t len)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       struct authdata *data, *dp;

       if (len <= 0)
           return (0);

       if ((data = malloc(sizeof(*data) + len)) == NULL)
           return (-1);

       data->next = NULL;
       data->len = len;
       data->ptr = data + 1;
       memcpy(data->ptr, ptr, len);

       if (as->data == NULL)
           as->data = data;
       else {
           for (dp = as->data; dp->next != NULL; dp = dp->next)
               ;
           dp->next = data;
       }
       return (0);
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_setdata~2][=auth_setdata=]] allocates and initializes a new =authdata= struct,
   storing a copy of the data from =*ptr= and =len=. It then point the
   =next= field on the last =authdata= struct in =*as= to its
   location. It returns =0= on success.

** auth_setitem / auth_getitem
   :PROPERTIES:
   :CUSTOM_ID: auth_setitem
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   int auth_setitem(auth_session_t *as, auth_item_t item, char *value)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       if (as == NULL) {
           errno = EINVAL;
           return (-1);
       }

       switch (item) {
       case AUTHV_ALL:
           if (value != NULL) {
               errno = EINVAL;
               return (-1);
           }
           auth_setitem(as, AUTHV_CHALLENGE, NULL);
           auth_setitem(as, AUTHV_CLASS, NULL);
           auth_setitem(as, AUTHV_NAME, NULL);
           auth_setitem(as, AUTHV_SERVICE, NULL);
           auth_setitem(as, AUTHV_STYLE, NULL);
           auth_setitem(as, AUTHV_INTERACTIVE, NULL);
           return (0);

       case AUTHV_CHALLENGE:
           if (value == as->challenge)
               return (0);
           if (value != NULL && (value = strdup(value)) == NULL)
               return (-1);
           free(as->challenge);
           as->challenge = value;
           return (0);

       case AUTHV_CLASS:
           if (value == as->class)
               return (0);
           if (value != NULL && (value = strdup(value)) == NULL)
               return (-1);
           free(as->class);
           as->class = value;
           return (0);

       case AUTHV_NAME:
           if (value == as->name)
               return (0);
           if (value != NULL && !_auth_validuser(value)) {
               errno = EINVAL;
               return (-1);
           }
           if (value != NULL && (value = strdup(value)) == NULL)
               return (-1);
           free(as->name);
           as->name = value;
           return (0);

       case AUTHV_SERVICE:
           if (value == as->service)
               return (0);
           if (value == NULL || strcmp(value, defservice) == 0)
               value = defservice;
           else if ((value = strdup(value)) == NULL)
               return (-1);
           if (as->service && as->service != defservice)
               free(as->service);
           as->service = value;
           return (0);

       case AUTHV_STYLE:
           if (value == as->style)
               return (0);
           if (value == NULL || strchr(value, '/') != NULL ||
               (value = strdup(value)) == NULL)
               return (-1);
           free(as->style);
           as->style = value;
           return (0);

       case AUTHV_INTERACTIVE:
           if (value == NULL)
               as->flags &= ~AF_INTERACTIVE;
           else
               as->flags |= ~AF_INTERACTIVE;
           return (0);

       default:
           errno = EINVAL;
           return (-1);
       }
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_setitem][=auth_setitem=]] is used to set one of several different fields of
   =as= to =value=. Depending on the value of =item=, it can be the
   =challenge=, =class=, =name=, =service=, =style=, or =interactive=
   field. If =*value= is =NULL=, it clears that field. If =item= is
   =AUTHV_ALL= and =*value= is =NULL=, all fields are cleared. It
   returns =0= on success.

   #+CAPTION: Taken from [[https://man.openbsd.org/auth_subr.3#auth_getitem][=auth_subr(3)=]]
   #+begin_src text
   AUTH_CHALLENGE
       The latest challenge, if any, set for the session.

   AUTH_CLASS
       The class of the user, as defined by the /etc/login.conf file.
       This value is not directly used by BSD Authentication, rather, it
       is passed to the login scripts for their possible use.

   AUTH_INTERACTIVE
       If set to any value, then the session is tagged as interactive. If
       not set, the session is not interactive. When the value is
       requested it is always either NULL or “True”. The auth subroutines
       may choose to provide additional information to standard output or
       standard error when the session is interactive. There is no
       functional change in the operation of the subroutines.

   AUTH_NAME
       The name of the user being authenticated. The name should include
       the instance, if any, that is being requested.

   AUTH_SERVICE
       The service requesting the authentication. Initially it is set to
       the default service which provides the traditional interactive
       service.

   AUTH_STYLE
       The style of authentication being performed, as defined by the
       /etc/login.conf file. The style determines which login script
       should actually be used.
   #+end_src


   @@html: <details> <summary> @@
   #+begin_src c
   char *auth_getitem(auth_session_t *as, auth_item_t item)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       if (as != NULL) {
           switch (item) {
           case AUTHV_CHALLENGE:
               return (as->challenge);
           case AUTHV_CLASS:
               return (as->class);
           case AUTHV_NAME:
               return (as->name);
           case AUTHV_SERVICE:
               return (as->service ? as->service : defservice);
           case AUTHV_STYLE:
               return (as->style);
           case AUTHV_INTERACTIVE:
               return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
           default:
               break;
           }
       }
       return (NULL);
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_getitem][=auth_getitem=]] is used to return the value of the fields listed above.

*** auth_item_t
    :PROPERTIES:
    :CUSTOM_ID: auth_item_t
    :END:

    =auth_item_t= is an enum defined in =/include/bsd_auth.h=.

    #+begin_src c
    typedef enum {
        AUTHV_ALL,
        AUTHV_CHALLENGE,
        AUTHV_CLASS,
        AUTHV_NAME,
        AUTHV_SERVICE,
        AUTHV_STYLE,
        AUTHV_INTERACTIVE
    } auth_item_t;
    #+end_src

** auth_setoption
   :PROPERTIES:
   :CUSTOM_ID: auth_setoption
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   int auth_setoption(auth_session_t *as, char *n, char *v)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       struct authopts *opt;
       size_t len = strlen(n) + strlen(v) + 2;
       int ret;

       if ((opt = malloc(sizeof(*opt) + len)) == NULL)
           return (-1);

       opt->opt = (char *)(opt + 1);

       ret = snprintf(opt->opt, len, "%s=%s", n, v);
       if (ret < 0 || ret >= len) {
           free(opt);
           errno = ENAMETOOLONG;
           return (-1);
       }
       opt->next = as->optlist;
       as->optlist = opt;
       return(0);
   }

   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_setoption][=auth_setoption=]] initializes a new =authopts= struct, and sets the
   =*opt= field to a string formatted as =sprintf("%s=%s", n, v)=. It
   then point the =*next= field on the last =authopts= struct in =*as=
   to its location. It returns =0= on success.

** auth_setstate / auth_getstate
   :PROPERTIES:
   :CUSTOM_ID: auth_setstate
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   void	auth_setstate(auth_session_t *as, int s)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   { as->state = s; }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_setstate][=auth_setstate=]] sets the =state= of =*as= to =s=.

   @@html: <details> <summary> @@
   #+begin_src c
   int	auth_getstate(auth_session_t *as)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   { return (as->state); }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_getstate][=auth_getstate=]] return the =state= of =*as=.

** auth_setpwd / auth_getpwd
   :PROPERTIES:
   :CUSTOM_ID: auth_setpwd
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   int auth_setpwd(auth_session_t *as, struct passwd *pwd)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       struct passwd pwstore;
       char *instance, pwbuf[_PW_BUF_LEN];

       if (pwd == NULL && as->pwd == NULL && as->name == NULL)
           return (-1);		/* true failure */

       if (pwd == NULL) {
           /*
            * If we were not passed in a pwd structure we need to
            * go find one for ourself.  Always look up the username
            * (if it is defined) in the passwd database to see if there
            * is an entry for the user.  If not, either use the current
            * entry or simply return a 1 which implies there is
            * no user by that name here.  This is not a failure, just
            * a point of information.
            */
           if (as->name == NULL)
               return (0);
           getpwnam_r(as->name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
           if (pwd == NULL) {
               instance = strchr(as->name, '/');
               if (instance == NULL)
                   return (as->pwd ? 0 : 1);
               if (strcmp(instance, "/root") == 0) {
                   getpwnam_r(instance + 1, &pwstore, pwbuf,
                       sizeof(pwbuf), &pwd);
               }
               if (pwd == NULL)
                   return (as->pwd ? 0 : 1);
           }
       }
       if ((pwd = pw_dup(pwd)) == NULL)
           return (-1);		/* true failure */
       if (as->pwd) {
           explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
           free(as->pwd);
       }
       as->pwd = pwd;
       return (0);
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_setpwd][=auth_setpwd=]] is used to retrieve and set the [[https://man.openbsd.org/man3/getpwnam.3][password database]]
   entry in =as= if one isn't already set.

   If a passwd entry is passed in through =pwd=, it uses that to set
   =as->pwd=. If =pwd= is =NULL=, it tries to find the passwd entry
   associated with =as->name=. If it finds one, it sets =as->pwd= and
   returns =0=. If there is no entry with that username, it returns
   =1=.

   @@html: <details> <summary> @@
   #+begin_src c
   struct passwd *auth_getpwd(auth_session_t *as)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   { return (as->pwd); }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_getpwd][=auth_getpwd=]] returns =as->pwd=.

** auth_set_va_list
   :PROPERTIES:
   :CUSTOM_ID: auth_set_va_list
   :END:

   @@html: <details> <summary> @@
   #+begin_src c
   void auth_set_va_list(auth_session_t *as, va_list ap)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   { va_copy(as->ap, ap); }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_set_va_list][=auth_set_va_list=]] copies =ap= to the =ap= field in =*as=

** auth_clrenv
   :PROPERTIES:
   :CUSTOM_ID: auth_clrenv
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   void auth_clrenv(auth_session_t *as)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       char *line;

       for (line = as->spool; line < as->spool + as->index;) {
           if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
               if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
                   line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
               }
           } else
           if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
               if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
                   line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
               }
           }
           while (*line++)
               ;
       }
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_clrenv][=auth_clrenv=]] removes all lines containing =BI_SETENV= and
   =BI_UNSETENV= from =as->spool=. This is explained under the
   [[#auth_call][=auth_call=]] section.

** auth_clroption
   :PROPERTIES:
   :CUSTOM_ID: auth_clroption
   :END:

   @@html: <details> <summary> @@
   #+begin_src c
   void auth_clroption(auth_session_t *as, char *option)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       struct authopts *opt, *oopt;
       size_t len;

       len = strlen(option);

       if ((opt = as->optlist) == NULL)
           return;

       if (strncmp(opt->opt, option, len) == 0 &&
           (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
           as->optlist = opt->next;
           free(opt);
           return;
       }

       while ((oopt = opt->next) != NULL) {
           if (strncmp(oopt->opt, option, len) == 0 &&
               (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
               opt->next = oopt->next;
               free(oopt);
               return;
           }
           opt = oopt;
       }
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_clroption][=auth_clroption=]] removes the option named =option= from =as=.

** auth_clroptions
   :PROPERTIES:
   :CUSTOM_ID: auth_clroptions
   :END:

   @@html: <details> <summary> @@
   #+begin_src c
   void auth_clroptions(auth_session_t *as)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       struct authopts *opt;

       while ((opt = as->optlist) != NULL) {
           as->optlist = opt->next;
           free(opt);
       }
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_clroptions][=auth_clroptions=]] clears all options from =as=.

** auth_setenv
   :PROPERTIES:
   :CUSTOM_ID: auth_setenv
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   void auth_setenv(auth_session_t *as)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       char *line, *name;

       /*
        ,* Set any environment variables we were asked for
        ,*/
           for (line = as->spool; line < as->spool + as->index;) {
           if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
               if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
                   /* only do it once! */
                   line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
                   line += sizeof(BI_SETENV) - 1;
                   for (name = line;
                       isblank((unsigned char)*name); ++name)
                       ;
                   for (line = name;
                       ,*line && !isblank((unsigned char)*line);
                       ++line)
                       ;
                   if (*line)
                       ,*line++ = '\0';
                   for (; isblank((unsigned char)*line); ++line)
                       ;
                   if (*line != '\0' && setenv(name, line, 1))
                       warn("setenv(%s, %s)", name, line);
               }
           } else
           if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
               if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
                   /* only do it once! */
                   line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
                   line += sizeof(BI_UNSETENV) - 1;
                   for (name = line;
                       isblank((unsigned char)*name); ++name)
                       ;
                   for (line = name;
                       ,*line && !isblank((unsigned char)*line);
                       ++line)
                       ;
                   if (*line)
                       ,*line++ = '\0';
                   unsetenv(name);
               }
           }
           while (*line++)
               ;
       }
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_setenv][=auth_setenv=]] scans through =as->spool=, modifying the environment
   according to =BI_SETENV= and =BI_UNSETENV= instructions.

** auth_getvalue
   :PROPERTIES:
   :CUSTOM_ID: auth_getvalue
   :END:
   @@html: <details> <summary> @@
   #+BEGIN_SRC c
   char *auth_getvalue(auth_session_t *as, char *what)
   #+END_SRC
   @@html: </summary> @@
   #+begin_src c
   {
       char *line, *v, *value;
       int n, len;

       len = strlen(what);

           for (line = as->spool; line < as->spool + as->index;) {
           if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
               goto next;
           line += sizeof(BI_VALUE) - 1;

           if (!isblank((unsigned char)*line))
               goto next;

           while (isblank((unsigned char)*++line))
               ;

           if (strncmp(line, what, len) != 0 ||
               !isblank((unsigned char)line[len]))
               goto next;
           line += len;
           while (isblank((unsigned char)*++line))
               ;
           value = strdup(line);
           if (value == NULL)
               return (NULL);

           /*
            ,* XXX - There should be a more standardized
            ,* routine for doing this sort of thing.
            ,*/
           for (line = v = value; *line; ++line) {
               if (*line == '\\') {
                   switch (*++line) {
                   case 'r':
                       ,*v++ = '\r';
                       break;
                   case 'n':
                       ,*v++ = '\n';
                       break;
                   case 't':
                       ,*v++ = '\t';
                       break;
                   case '0': case '1': case '2':
                   case '3': case '4': case '5':
                   case '6': case '7':
                       n = *line - '0';
                       if (isdigit((unsigned char)line[1])) {
                           ++line;
                           n <<= 3;
                           n |= *line-'0';
                       }
                       if (isdigit((unsigned char)line[1])) {
                           ++line;
                           n <<= 3;
                           n |= *line-'0';
                       }
                       break;
                   default:
                       ,*v++ = *line;
                       break;
                   }
               } else
                   ,*v++ = *line;
           }
           ,*v = '\0';
           return (value);
   next:
           while (*line++)
               ;
       }
       return (NULL);
   }
   #+end_src
   @@html: </details> @@

   [[https://man.openbsd.org/auth_subr.3#auth_getvalue~2][=auth_getvalue=]] scans =as->spool= looking for lines beginning with
   =BI_VALUE=. It then checks if the next word is equal to =what=.

   When it finds the desired line, it duplicates the string, converts
   escape sequences in the value, and returns the newly created
   string.

   For convenience, the function [[https://man.openbsd.org/man3/authenticate.3#auth_mkvalue][=auth_mkvalue(3)=]] can be used inside
   of the authentication module to create and return appropriately
   escaped value strings.

** auth_getchallenge
   :PROPERTIES:
   :CUSTOM_ID: auth_getchallenge
   :END:

   The [[https://man.openbsd.org/auth_subr.3#auth_getchallenge][=auth_subr(3)=]] man page claims this function exists, but I
   can't find it anywhere in the source code. I suspect this is an
   error.

* auth_open
  :PROPERTIES:
  :CUSTOM_ID: auth_open
  :END:
  @@html: <details> <summary> @@
  #+begin_src c
  auth_session_t *auth_open(void)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      auth_session_t *as;

      if ((as = calloc(1, sizeof(auth_session_t))) != NULL) {
          as->service = defservice;
          as->fd = -1;
      }

      return (as);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/auth_subr.3#auth_open][=auth_open=]] is used by several functions to create a new auth
  session. It allocates an [[#auth_session_t][=auth_session_t=]] struct on the heap, sets
  its default =service= to that defined by =LOGIN_DEFSERVICE= in
  =/include/login_cap.h=, which is currently ="login"=.

  #+begin_src c
  #define	LOGIN_DEFSERVICE	"login"
  #+end_src

  It then sets the =fd= field to =-1=, and returns the pointer.

* auth_usercheck
  :PROPERTIES:
  :CUSTOM_ID: auth_usercheck
  :END:

  @@html: <details> <summary> @@
  #+BEGIN_SRC c
  auth_session_t *auth_usercheck(char *name, char *style, char *type, char *password)
  #+END_SRC
  @@html: </summary> @@
  #+begin_src c
  {
      char namebuf[LOGIN_NAME_MAX + 1 + NAME_MAX + 1];
      char pwbuf[_PW_BUF_LEN];
      auth_session_t *as;
      login_cap_t *lc;
      struct passwd pwstore, *pwd = NULL;
      char *slash;

      if (!_auth_validuser(name))
          return (NULL);
      if (strlcpy(namebuf, name, sizeof(namebuf)) >= sizeof(namebuf))
          return (NULL);
      name = namebuf;

      /*
       ,* Split up user:style names if we were not given a style
       ,*/
      if (style == NULL && (style = strchr(name, ':')) != NULL)
          ,*style++ = '\0';

      /*
       ,* Cope with user/instance.  We are only using this to get
       ,* the class so it is okay if we strip a /root instance
       ,* The actual login script will pay attention to the instance.
       ,*/
      getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
      if (pwd == NULL) {
          if ((slash = strchr(name, '/')) != NULL) {
              ,*slash = '\0';
              getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
              ,*slash = '/';
          }
      }
      if ((lc = login_getclass(pwd ? pwd->pw_class : NULL)) == NULL)
          return (NULL);

      if ((style = login_getstyle(lc, style, type)) == NULL) {
          login_close(lc);
          return (NULL);
      }

      if (password) {
          if ((as = auth_open()) == NULL) {
              login_close(lc);
              return (NULL);
          }
          auth_setitem(as, AUTHV_SERVICE, "response");
          auth_setdata(as, "", 1);
          auth_setdata(as, password, strlen(password) + 1);
          explicit_bzero(password, strlen(password));
      } else
          as = NULL;
      as = auth_verify(as, style, name, lc->lc_class, (char *)NULL);
      login_close(lc);
      return (as);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/man3/authenticate.3#auth_usercheck][=auth_usercheck=]] first checks that =*name= is a valid username. This
  means that it doesn't begin with a hyphen, had a non-zero length.

  If =*style= is =NULL=, it checks if =*name= is in the =user:style=
  format, and splits it accordingly.

  It then gets the user's password database entry through
  [[https://man.openbsd.org/man3/getpwnam.3#getpwnam_r][=getpwman_r(3)=]], which operates on the [[https://man.openbsd.org/passwd.5][=passwd(5)=]] database. After
  it uses that to retrieve the user's login class using
  [[https://man.openbsd.org/login_getclass#login_getclass][=login_getclass(3)=]], which returns a =login_cap_t=. Login classes
  are stored in the [[https://man.openbsd.org/man5/login.conf.5][=login.conf(5)=]] database.

  That struct is then passed into [[https://man.openbsd.org/login_getclass#login_getstyle][=login_getstyle(3)=]], which also
  received the =*style= and =*type=. If =*type= is =NULL=, it returns
  the first available login style for that class. If =*style= is
  specified, it is returned if available, otherwise =NULL= is
  returned, which causes =auth_usercheck= to return =NULL= as well.

  It then creates a pointer =as= of type [[#auth_session_t][=auth_session_t=]], and handles
  it differently based on whether =*password= is =NULL=.

  - If the password is a string, it creates a new session using
    [[#auth_open][=auth_open=]] and assigns it to =as=. It then sets the session
    =service= to ="response"=, and adds the =password= string to the
    session's =data=.

    #+BEGIN_SRC c
    auth_setitem(as, AUTHV_SERVICE, "response");
    auth_setdata(as, "", 1);
    auth_setdata(as, password, strlen(password) + 1);
    #+END_SRC

  - If =*password= is =NULL=, it sets =as= to =NULL=.

  It then passes the =auth_session_t= pointer (=as=), =*name=,
  =*style=, login class (=lc->lc_class=), and a =NULL= char pointer to
  [[#auth_verify][=auth_verify=]]. Finally it returns the auth session pointer.

  #+begin_src c
  as = auth_verify(as, style, name, lc->lc_class, (char *)NULL);
  // [...] some cleanup
  return (as);
  #+end_src

* auth_verify
  :PROPERTIES:
  :CUSTOM_ID: auth_verify
  :END:
  @@html: <details> <summary> @@
  #+BEGIN_SRC c
  auth_session_t *auth_verify(auth_session_t *as, char *style, char *name, ...)
  #+END_SRC
  @@html: </summary> @@
  #+begin_src c
  {
      va_list ap;
      char path[PATH_MAX];

      if ((name == NULL || style == NULL) && as == NULL)
          return (NULL);

      if (as == NULL && (as = auth_open()) == NULL)
          return (NULL);
      auth_setstate(as, 0);

      if (style != NULL && auth_setitem(as, AUTHV_STYLE, style) < 0)
          return (as);

      if (name != NULL && auth_setitem(as, AUTHV_NAME, name) < 0)
          return (as);

      style = auth_getitem(as, AUTHV_STYLE);
      name = auth_getitem(as, AUTHV_NAME);
      if (!_auth_validuser(name))
          return (as);

      snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style);
      va_start(ap, name);
      auth_set_va_list(as, ap);
      auth_call(as, path, auth_getitem(as, AUTHV_STYLE), "-s",
          auth_getitem(as, AUTHV_SERVICE), "--", name, (char *)NULL);
      va_end(ap);
      return (as);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/man3/authenticate.3#auth_verify][=auth_verify=]] is used as a frontend for [[#auth_call][=auth_call=]].

  It creates an auth session using =auth_open= if =*as= is =NULL=.

  The =state= of the session is set to =0=.

  It sets the =name= and =style= of the session, if the
  =*style= and/or =*name= are non-=NULL=.

  After that it constructs the path of the authentication module,
  placing it in the variable =path=. It is constructed by combining
  =_PATH_AUTHPROG=, which is defined in =login_cap.h= as
  =/usr/libexec/auth/login_=, and the authentication style. For the
  case of auth style =passwd=, it would result in the path
  =/usr/libexec/auth/login_passwd=.

  #+begin_src c
  snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style);
  #+end_src

  It then copies its variable arguments to the auth session using
  [[#auth_set_va_list][=auth_set_va_list=]].

  Then =auth_call= is called with the session struct, the path to the
  auth module, the auth style, the "-s" flag followed by the service
  (=login=, =challenge=, or =response=), a double dash, the user name,
  and a =NULL= character pointer. The return value of =auth_call= is
  ignored and a pointer to the auth session is returned immediately
  afterwards.

  #+BEGIN_SRC c
  va_start(ap, name);
  auth_set_va_list(as, ap);
  auth_call(as, path, auth_getitem(as, AUTHV_STYLE), "-s",
            auth_getitem(as, AUTHV_SERVICE), "--", name, (char *)NULL);
  va_end(ap);
  return (as);
  #+END_SRC

* auth_call
  :PROPERTIES:
  :CUSTOM_ID: auth_call
  :END:
  @@html: <details> <summary> @@
  #+BEGIN_SRC c
  int auth_call(auth_session_t *as, char *path, ...)
  #+END_SRC
  @@html: </summary> @@
  #+begin_src c
  {
      char *line;
      struct authdata *data;
      struct authopts *opt;
      pid_t pid;
      int status;
      int okay;
      int pfd[2];
      int argc;
      char *argv[64];		/* 64 args should be more than enough */
  #define	Nargc	(sizeof(argv)/sizeof(argv[0]))

      va_start(as->ap0, path);

      argc = 0;
      if ((argv[argc] = _auth_next_arg(as)) != NULL)
          ++argc;

      if (as->fd != -1) {
          argv[argc++] = "-v";
          argv[argc++] = "fd=4";		/* AUTH_FD, see below */
      }
      /* XXX - fail if out of space in argv */
      for (opt = as->optlist; opt != NULL; opt = opt->next) {
          if (argc < Nargc - 2) {
              argv[argc++] = "-v";
              argv[argc++] = opt->opt;
          } else {
              syslog(LOG_ERR, "too many authentication options");
              goto fail;
          }
      }
      while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
          ++argc;

      if (argc >= Nargc - 1 && _auth_next_arg(as)) {
          if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
              va_end(as->ap0);
              explicit_bzero(&(as->ap0), sizeof(as->ap0));
          }
          if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
              va_end(as->ap);
              explicit_bzero(&(as->ap), sizeof(as->ap));
          }
          syslog(LOG_ERR, "too many arguments");
          goto fail;
      }

      argv[argc] = NULL;

      if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) == -1) {
          syslog(LOG_ERR, "unable to create backchannel %m");
          warnx("internal resource failure");
          goto fail;
      }

      switch (pid = fork()) {
      case -1:
          syslog(LOG_ERR, "%s: %m", path);
          warnx("internal resource failure");
          close(pfd[0]);
          close(pfd[1]);
          goto fail;
      case 0:
  #define	COMM_FD	3
  #define	AUTH_FD	4
          if (dup2(pfd[1], COMM_FD) == -1)
              err(1, "dup of backchannel");
          if (as->fd != -1) {
              if (dup2(as->fd, AUTH_FD) == -1)
                  err(1, "dup of auth fd");
              closefrom(AUTH_FD + 1);
          } else
              closefrom(COMM_FD + 1);
          execve(path, argv, auth_environ);
          syslog(LOG_ERR, "%s: %m", path);
          err(1, "%s", path);
      default:
          close(pfd[1]);
          if (as->fd != -1) {
              close(as->fd);		/* so child has only ref */
              as->fd = -1;
          }
          while ((data = as->data) != NULL) {
              as->data = data->next;
              if (data->len > 0) {
                  write(pfd[0], data->ptr, data->len);
                  explicit_bzero(data->ptr, data->len);
              }
              free(data);
          }
          as->index = 0;
          _auth_spool(as, pfd[0]);
          close(pfd[0]);
          do {
              if (waitpid(pid, &status, 0) != -1) {
                  if (!WIFEXITED(status))
                      goto fail;
                  break;
              }
              /*
               ,* could get ECHILD if it was waited for by
               ,* another thread or from a signal handler
               ,*/
          } while (errno == EINTR);
      }

      /*
       ,* Now scan the spooled data
       ,* It is easier to wait for all the data before starting
       ,* to scan it.
       ,*/
          for (line = as->spool; line < as->spool + as->index;) {
          if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
              line += sizeof(BI_REJECT) - 1;
              if (!*line || *line == ' ' || *line == '\t') {
                  while (*line == ' ' || *line == '\t')
                      ++line;
                  if (!strcasecmp(line, "silent")) {
                      as->state = AUTH_SILENT;
                      break;
                  }
                  if (!strcasecmp(line, "challenge")) {
                      as->state  = AUTH_CHALLENGE;
                      break;
                  }
                  if (!strcasecmp(line, "expired")) {
                      as->state  = AUTH_EXPIRED;
                      break;
                  }
                  if (!strcasecmp(line, "pwexpired")) {
                      as->state  = AUTH_PWEXPIRED;
                      break;
                  }
              }
              break;
          } else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
              line += sizeof(BI_AUTH) - 1;
              if (!*line || *line == ' ' || *line == '\t') {
                  while (*line == ' ' || *line == '\t')
                      ++line;
                  if (*line == '\0')
                      as->state |= AUTH_OKAY;
                  else if (!strcasecmp(line, "root"))
                      as->state |= AUTH_ROOTOKAY;
                  else if (!strcasecmp(line, "secure"))
                      as->state |= AUTH_SECURE;
              }
          } else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
              line += sizeof(BI_REMOVE) - 1;
              while (*line == ' ' || *line == '\t')
                  ++line;
              if (*line)
                  _add_rmlist(as, line);
          }
          while (*line++)
              ;
      }

      if (WEXITSTATUS(status))
          as->state &= ~AUTH_ALLOW;

      okay = as->state & AUTH_ALLOW;

      if (!okay)
          auth_clrenv(as);

      if (0) {
  fail:
          auth_clrenv(as);
          as->state = 0;
          okay = -1;
      }

      while ((data = as->data) != NULL) {
          as->data = data->next;
          free(data);
      }

      if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
          va_end(as->ap0);
          explicit_bzero(&(as->ap0), sizeof(as->ap0));
      }

      if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
          va_end(as->ap);
          explicit_bzero(&(as->ap), sizeof(as->ap));
      }
      return (okay);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/auth_subr.3#auth_call~2][=auth_call=]] is responsible for setting up the environment,
  calling the modules, and communicating with them.

  An array of char pointers called =argv= is allocated to hold the arguments for the
  auth module.

  #+BEGIN_SRC c
  char *argv[64];		/* 64 args should be more than enough */
  #+END_SRC

  First, the variable arguments are placed in =as->ap0=.

  [[#_auth_next_arg][=_auth_next_arg=]] is called once, with the result being set as the
  first element in =argv=. If =as->fd= is set, adds =-v= and =fd=4= to
  =argv=.

  Then it loops through the =optlist= and appends =-v= followed the
  option for each of them.

  After that the rest of the arguments are retrieved from
  =_auth_next_arg= and added to the end of =argv=. Finally a =NULL= is
  added to the end of =argv=.

  Next a socket pair of type =PF_LOCAL, SOCK_STREAM= is created. This
  is called the "back channel", and is used to communicate with the
  authentication module.

  The process then calls [[https://man.openbsd.org/man2/fork.2][=fork(2)=]].

  Here two constants are set for the back channel and optional
  authentication file descriptors.

  #+begin_src c
  #define	COMM_FD	3
  #define	AUTH_FD	4
  #+end_src

  In the child process, the back channel is set to file descriptor 3,
  or =COMM_FD= using =dup2(3)=. If =as->fd=, is not =-1=, it is set to
  file descriptor 4, or =AUTH_FD=, also using [[https://man.openbsd.org/man2/dup.2#dup2][=dup2(3)=]]. The remainder
  of the file descriptors are closed using [[https://man.openbsd.org/man2/closefrom.2][=closefrom(2)=]] by calling
  either =closefrom(COMM_FD + 1)= or =closefrom(AUTH_FD + 1)=,
  depending on whether or not =AUTH_FD= is used.

  The child process then executes the module.

  #+begin_src c
  execve(path, argv, auth_environ);
  #+end_src

  =auth_environ= is defined at the top of the file as a very minimal
  environment.

  #+BEGIN_SRC c
  static char *auth_environ[] = {
      "PATH=" _PATH_DEFPATH,
      "SHELL=" _PATH_BSHELL,
      NULL,
  };
  #+END_SRC

  Where both constants are defined in =/include/paths.h=.

  #+BEGIN_SRC c
  #define	_PATH_DEFPATH	"/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin"
  #define	_PATH_BSHELL	"/bin/sh"
  #+END_SRC

  In the parent process, the child's end of the back channel is
  closed, and so is the parent's copy of =as->fd= if it exists.

  The data from =as->data= is then written to the back channel
  sequentially, zeroed, and freed.

  Next =as->index= is set to =0=.

  The response from the authentication module is then read from the
  back channel and put into =as->spool= with an optional received file
  descriptor placed in =as->fd=, using [[#_auth_spool][=_auth_spool=]].

  #+begin_src c
  _auth_spool(as, pfd[0]);
  #+end_src

  Once the back channel data has finished spooling, it is scanned for
  key words defined in =login_cap.h=.

  #+BEGIN_SRC c
  #define BI_AUTH         "authorize"         /* Accepted authentication */
  #define BI_REJECT       "reject"            /* Rejected authentication */
  #define BI_CHALLENGE    "reject challenge"  /* Reject with a challenge */
  #define BI_SILENT       "reject silent"     /* Reject silently */
  #define BI_REMOVE       "remove"            /* remove file on error */
  #define BI_ROOTOKAY     "authorize root"    /* root authenticated */
  #define BI_SECURE       "authorize secure"  /* okay on non-secure line */
  #define BI_SETENV       "setenv"            /* set environment variable */
  #define BI_UNSETENV     "unsetenv"          /* unset environment variable */
  #define BI_VALUE        "value"             /* set local variable */
  #define BI_EXPIRED      "reject expired"    /* account expired */
  #define BI_PWEXPIRED    "reject pwexpired"  /* password expired */
  #define BI_FDPASS       "fd"                /* child is passing an fd */
  #+END_SRC

  The [[https://man.openbsd.org/login.conf][=login.conf(5)=]] man page once again goes into greater detail on
  these values.

  #+BEGIN_SRC
  authorize  The user has been authorized.

  authorize secure
             The user has been authorized and root should be allowed to
             login even if this is not a secure terminal.  This should only
             be sent by authentication styles that are secure over insecure
             lines.

  reject     Authorization is rejected.  This overrides any indication that
             the user was authorized (though one would question the wisdom
             in sending both a reject and an authorize command).

  reject challenge
             Authorization was rejected and a challenge has been made
             available via the value challenge.

  reject silent
             Authorization is rejected, but no error messages should be
             generated.

  remove file
             If the login session fails for any reason, remove file before
             termination.

  setenv name value
             If the login session succeeds, the environment variable name
             should be set to the specified value.

  unsetenv name
             If the login session succeeds, the environment variable name
             should be removed.

  value name value
             Set the internal variable name to the specified value.  The
             value should only contain printable characters.  Several \
             sequences may be used to introduce non printing characters.
             These are:

             \n      A newline.

             \r      A carriage return.

             \t      A tab.

             \xxx    The character represented by the octal value xxx.  The
                     value may be one, two, or three octal digits.

             \c      The string is replaced by the value of c.  This allows
                     quoting an initial space or the \ character itself.


             The following values are currently defined:

             challenge
                     See section on challenges below.

             errormsg
                     If set, the value is the reason authentication failed.
                     The calling program may choose to display this when
                     rejecting the user, but display is not required.

  #+END_SRC

  The scanner is looking for lines that begin with =BI_AUTH=,
  =BI_REJECT=, or =BI_REMOVE=.

  Here =as->state= is set according to the values defined on
  =login_cap.h=.

  #+BEGIN_SRC c
  /*
   * bits which can be returned by authenticate()/auth_scan()
   */
  #define  AUTH_OKAY       0x01            /* user authenticated */
  #define  AUTH_ROOTOKAY   0x02            /* authenticated as root */
  #define  AUTH_SECURE     0x04            /* secure login */
  #define  AUTH_SILENT     0x08            /* silent rejection */
  #define  AUTH_CHALLENGE  0x10            /* a challenge was given */
  #define  AUTH_EXPIRED    0x20            /* account expired */
  #define  AUTH_PWEXPIRED  0x40            /* password expired */
  #+END_SRC

  If an authorization is received (any line starting with =BI_AUTH=),
  the appropriate state is bitwise =or=-ed onto =as->state=, allowing
  multiple authorizations, such as a case where both =BI_ROOTOKAY=,
  resulting in a state of =AUTH_ROOTOKAY=, and =BI_SECURE=, resulting
  in a state of =AUTH_SECURE= are both sent.

  If a rejection is received (any line starting with =BI_REJECT=),
  =as->state= is set according to the rejection, and the scanning is
  stopped. Rejections are final and take precedence over any
  authorizations.

  For any lines beginning with =BI_REMOVE=, the file names after the
  key word are sent to [[#_add_rmlist][=_add_rmlist=]].
  #+begin_src c
  _add_rmlist(as, line);
  #+end_src

  After scanning is complete, the exit status of the process is
  checked. A non-zero exit status means the request will get denied.

  An =okay= value is then defined by masking the state with the value
  =AUTH_ALLOW=.

  #+begin_src c
  okay = as->state & AUTH_ALLOW;
  #+end_src

  =AUTH_ALLOW= is defined in =login_cap.h=.

  #+begin_src c
  #define	AUTH_ALLOW	(AUTH_OKAY | AUTH_ROOTOKAY | AUTH_SECURE)
  #+end_src

  If the status results in a rejection, [[#auth_clrenv][=auth_clrenv=]] is called with
  =as=. This removes any requests the login script has made to set
  environment variables from =as->spool=.

  =okay= is then returned to the caller.

** _auth_next_arg
   :PROPERTIES:
   :CUSTOM_ID: _auth_next_arg
   :END:

   @@html: <details> <summary> @@
   #+BEGIN_SRC c
   static char *_auth_next_arg(auth_session_t *as)
   #+END_SRC
   @@html: </summary> @@
   #+begin_src c
   {
       char *arg;

       if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
           if ((arg = va_arg(as->ap0, char *)) != NULL)
               return (arg);
           va_end(as->ap0);
           explicit_bzero(&(as->ap0), sizeof(as->ap0));
       }
       if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
           if ((arg = va_arg(as->ap, char *)) != NULL)
               return (arg);
           va_end(as->ap);
           explicit_bzero(&(as->ap), sizeof(as->ap));
       }
       return (NULL);
   }
   #+end_src
   @@html: </details> @@

   Loops through =as->ap0= then =as->ap=, returning one argument per
   call. Calls =va_end= on each list once it finishes with them, then
   [[https://man.openbsd.org/man3/bzero.3#explicit_bzero][=explicit_bzero(3)=]]'s them.

   Finally when it's gone through both lists, returns =NULL=

** _auth_spool
   :PROPERTIES:
   :CUSTOM_ID: _auth_spool
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   static void _auth_spool(auth_session_t *as, int fd)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       ssize_t r;
       char *b, *s;

       for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) {
           r = read(fd, as->spool + as->index,
               sizeof(as->spool) - as->index);
           if (r <= 0) {
               as->spool[as->index] = '\0';
               return;
           }
           b = as->spool + as->index;
           as->index += r;
           /*
            ,* Convert newlines into NULs to allow easy scanning of the
            ,* file and receive an fd if there is a BI_FDPASS message.
            ,* XXX - checking for BI_FDPASS here is annoying but
            ,*       we need to avoid the read() slurping in control data.
            ,*/
           while (r-- > 0) {
               if (*b++ == '\n') {
                   b[-1] = '\0';
                   if (strcasecmp(s, BI_FDPASS) == 0)
                       _recv_fd(as, fd);
                   s = b;
               }
           }
       }

       syslog(LOG_ERR, "Overflowed backchannel spool buffer");
       errx(1, "System error in authentication program");
   }
   #+end_src
   @@html: </details> @@

   =_auth_spool='s job is to read data from =fd= and place it in
   =as->spool=, and to update =as->index= with the length of the data
   on the spool. While spooling it converts newlines to =NUL='s in
   order to parse the output more easily. It also handles any file
   descriptors passed through the back channel by sending them to
   [[#_recv_fd][=_recv_fd=]].

   #+begin_src c
   // [...]
   if (strcasecmp(s, BI_FDPASS) == 0)
       _recv_fd(as, fd);
   #+end_src

** _recv_fd
   :PROPERTIES:
   :CUSTOM_ID: _recv_fd
   :END:

   @@html: <details> <summary> @@
   #+begin_src c
   static void _recv_fd(auth_session_t *as, int fd)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       struct msghdr msg;
       struct cmsghdr *cmp;
       union {
           struct cmsghdr hdr;
           char buf[CMSG_SPACE(sizeof(int))];
       } cmsgbuf;

       memset(&msg, 0, sizeof(msg));
       msg.msg_control = &cmsgbuf.buf;
       msg.msg_controllen = sizeof(cmsgbuf.buf);
       if (recvmsg(fd, &msg, 0) == -1)
           syslog(LOG_ERR, "recvmsg: %m");
       else if (msg.msg_flags & MSG_TRUNC)
           syslog(LOG_ERR, "message truncated");
       else if (msg.msg_flags & MSG_CTRUNC)
           syslog(LOG_ERR, "control message truncated");
       else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL)
           syslog(LOG_ERR, "missing control message");
       else {
           if (cmp->cmsg_level != SOL_SOCKET)
               syslog(LOG_ERR, "unexpected cmsg_level %d",
                   cmp->cmsg_level);
           else if (cmp->cmsg_type != SCM_RIGHTS)
               syslog(LOG_ERR, "unexpected cmsg_type %d",
                   cmp->cmsg_type);
           else if (cmp->cmsg_len != CMSG_LEN(sizeof(int)))
               syslog(LOG_ERR, "bad cmsg_len %d",
                   cmp->cmsg_len);
           else {
               if (as->fd != -1)
                   close(as->fd);
               as->fd = *(int *)CMSG_DATA(cmp);
           }
       }
   }
   #+end_src
   @@html: </details> @@

   =_recv_fd= reads control messages, also called ancillary data, from
   =fd= and tries to receive a file descriptor. It does this using the
   [[https://man.openbsd.org/CMSG_DATA.3][control message API]].

   If it receives one and =as->fd= is equal to =-1=, it sets it to the
   received file descriptor. Otherwise it closes the received file
   descriptor.

** _add_rmlist
   :PROPERTIES:
   :CUSTOM_ID: _add_rmlist
   :END:
   @@html: <details> <summary> @@
   #+begin_src c
   static void _add_rmlist(auth_session_t *as, char *file)
   #+end_src
   @@html: </summary> @@
   #+begin_src c
   {
       struct rmfiles *rm;
       size_t i = strlen(file) + 1;

       // XXX should rangecheck i since we are about to add?

       if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
           syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
           return;
       }
       rm->file = (char *)(rm + 1);
       rm->next = as->rmlist;
       strlcpy(rm->file, file, i);
       as->rmlist = rm;
   }
   #+end_src
   @@html: </details> @@

   =_add_rmlist= is used to add to the list of files to be removed
   after authentication is complete

   A =rmfiles= struct is allocated and appended to the end of the
   =as->rmlist= linked list.

* auth_close
  :PROPERTIES:
  :CUSTOM_ID: auth_close
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  int auth_close(auth_session_t *as)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      struct rmfiles *rm;
      struct authopts *opt;
      struct authdata *data;
      int s;

      /*
       ,* Save our return value
       ,*/
      s = as->state & AUTH_ALLOW;

      if (s == 0)
          as->index = 0;

      auth_setenv(as);


      /*
       ,* Clean out the rmlist and remove specified files if the
       ,* authentication failed
       ,*/
      while ((rm = as->rmlist) != NULL) {
          as->rmlist = rm->next;
          if (s == 0)
              unlink(rm->file);
          free(rm);
      }

      /*
       ,* Clean out the opt list
       ,*/
      while ((opt = as->optlist) != NULL) {
          as->optlist = opt->next;
          free(opt);
      }

      /*
       ,* Clean out data
       ,*/
      while ((data = as->data) != NULL) {
          if (as->data->len)
              explicit_bzero(as->data->ptr, as->data->len);
          as->data = data->next;
          free(data);
      }

      if (as->pwd != NULL) {
          explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
          free(as->pwd);
          as->pwd = NULL;
      }

      /*
       ,* Clean up random variables
       ,*/
      if (as->service && as->service != defservice)
          free(as->service);
      free(as->challenge);
      free(as->class);
      free(as->style);
      free(as->name);

      free(as);
      return (s);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/auth_subr.3#auth_close][=auth_close=]] is responsible for setting the environment variables,
  removing any files requested by the authentication module, and
  freeing =as=.

  First it saves the allow state of =as->state= in a variable =s=.

  #+begin_src c
  s = as->state & AUTH_ALLOW;
  #+end_src

  If =s= is equal to =0= (failure), =as->index= is set to =0=, truncating
  =as->spool= so that no further functions will be able to read from
  it.

  It then modifies the environment using [[#auth_setenv][=auth_setenv=]]

  #+begin_src c
  auth_setenv(as);
  #+end_src

  All =as->rmlist= structs are checked. If =s= is equal to =0=, the
  files are deleted. All =rmlist= structs are then freed.

  All =as->optlist= structs are freed.

  All =as->data= structs are [[https://man.openbsd.org/man3/bzero.3#explicit_bzero][=explicit_bzero(3)=]]'d and then freed.

  =as->pwd= is =explicit_bzero='d and freed.

  All remaining structs referenced by =as= are freed.

  =as= is freed.

  =s= is returned.

* auth_userchallenge
  :PROPERTIES:
  :CUSTOM_ID: auth_userchallenge
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  auth_session_t *auth_userchallenge(char *name, char *style, char *type, char **challengep)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      char namebuf[LOGIN_NAME_MAX + 1 + NAME_MAX + 1];
      auth_session_t *as;
      login_cap_t *lc;
      struct passwd pwstore, *pwd = NULL;
      char *slash, pwbuf[_PW_BUF_LEN];

      if (!_auth_validuser(name))
          return (NULL);
      if (strlen(name) >= sizeof(namebuf))
          return (NULL);
      strlcpy(namebuf, name, sizeof namebuf);
      name = namebuf;

      /*
       ,* Split up user:style names if we were not given a style
       ,*/
      if (style == NULL && (style = strchr(name, ':')) != NULL)
          ,*style++ = '\0';

      /*
       ,* Cope with user/instance.  We are only using this to get
       ,* the class so it is okay if we strip a /root instance
       ,* The actual login script will pay attention to the instance.
       ,*/
      getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
      if (pwd == NULL) {
          if ((slash = strchr(name, '/')) != NULL) {
              ,*slash = '\0';
              getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
              ,*slash = '/';
          }
      }
      if ((lc = login_getclass(pwd ? pwd->pw_class : NULL)) == NULL)
          return (NULL);

      if ((style = login_getstyle(lc, style, type)) == NULL ||
          (as = auth_open()) == NULL) {
          login_close(lc);
          return (NULL);
      }
      if (auth_setitem(as, AUTHV_STYLE, style) < 0 ||
          auth_setitem(as, AUTHV_NAME, name) < 0 ||
          auth_setitem(as, AUTHV_CLASS, lc->lc_class) < 0) {
          auth_close(as);
          login_close(lc);
          return (NULL);
      }
      login_close(lc);
      ,*challengep = auth_challenge(as);
      return (as);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/man3/authenticate.3#auth_userchallenge][=auth_userchallenge=]] is used when the authentication style requires
  that the user be presented with a challenge, but the user cannot be
  directly interacted with over the terminal. As an example, this
  might be used in cases where the user is using S/KEY authentication
  over SSH.

  A fair portion of this function is very similar to
  [[#auth_usercheck][=auth_usercheck=]]. Instead of having a password argument however, it
  has a pointer to string, which is used to return the challenge to
  the calling function.

  It first checks that =*name= is a valid username. This means that it
  doesn't begin with a hyphen, had a non-zero length.

  If =*style= is =NULL=, it checks if =*name= is in the =user:style=
  format, and splits it accordingly.

  It then gets the user's password database entry through
  [[https://man.openbsd.org/man3/getpwnam.3#getpwnam_r][=getpwman_r(3)=]], which operates on the [[https://man.openbsd.org/passwd.5][=passwd(5)=]] database. It then
  uses that to retrieve the user's login class using
  [[https://man.openbsd.org/login_getclass#login_getclass][=login_getclass(3)=]], which returns a =login_cap_t=. Login classes
  are stored in the [[https://man.openbsd.org/man5/login.conf.5][=login.conf(5)=]] database.

  That struct is then passed into [[https://man.openbsd.org/login_getclass#login_getstyle][=login_getstyle(3)=]], which also
  received the =*style= and =*type=. If =*type= is =NULL=, it returns
  the first available login style for that class. If =*style= is
  specified, it is returned if available, otherwise =NULL= is
  returned, which causes =auth_userchallenge= to return =NULL= as
  well.

  This is where =auth_userchallenge= and [[#auth_usercheck][=auth_usercheck=]] begin to diverge.

  It creates a new auth session using [[#auth_open][=auth_open=]] as variable =as=.

  The =style=, =name= and =class= properties of the session are then
  set using [[#auth_setitem][=auth_setitem=]].

  It then calls [[#auth_challenge][=auth_challenge=]] with =as= as the argument. The return
  value from that call is used to set =*challengep=, and =as= is
  returned.

  #+begin_src c
  *challengep = auth_challenge(as);
  return (as);
  #+end_src

* auth_challenge
  :PROPERTIES:
  :CUSTOM_ID: auth_challenge
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  char *auth_challenge(auth_session_t *as)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      char path[PATH_MAX];
      int len;

      if (as == NULL || as->style == NULL || as->name == NULL ||
          !_auth_validuser(as->name))
          return (NULL);

      len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
      if (len < 0 || len >= sizeof(path))
          return (NULL);

      as->state = 0;

      free(as->challenge);
      as->challenge = NULL;

      auth_call(as, path, as->style, "-s", "challenge", "--", as->name,
          as->class, (char *)NULL);
      if (as->state & AUTH_CHALLENGE)
          as->challenge = auth_getvalue(as, "challenge");
      as->state = 0;
      as->index = 0;	/* toss our data */
      return (as->challenge);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/auth_subr.3#auth_challenge][=auth_challenge=]], much like [[#auth_verify][=auth_verify=]] is a function that acts as
  a front-end for [[#auth_call][=auth_call=]], except used specifically for
  challenges.

  First the session =as= is checked. If it's =NULL=, or =as->style= is
  =NULL=, =as->name= is =NULL=, or if the username begins with a
  hyphen, or has a length of zero, the function returns =NULL=.

  Then the path to the auth module is created.

  #+begin_src c
  snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
  #+end_src

  =as->state= and =as->challenge= are then reset, in case they were
  already set.

  Then [[#auth_call][=auth_call=]] is called, with the challenge style set.

  #+begin_src c
  auth_call(as, path, as->style, "-s", "challenge", "--", as->name, as->class, (char *)NULL);
  #+end_src

  =as->state= is checked for the =AUTH_CHALLENGE= bit, and if it's
  present, the challenge is extracted from the back channel output,
  and used to set =as->challenge=.

  #+begin_src c
  if (as->state & AUTH_CHALLENGE)
		as->challenge = auth_getvalue(as, "challenge");
  #+end_src

  =as->state= and =as->index= are then set to zero, discarding the
  data.

  =as->challenge= is then returned.

* auth_userresponse
  :PROPERTIES:
  :CUSTOM_ID: auth_userresponse
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  int auth_userresponse(auth_session_t *as, char *response, int more)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      char path[PATH_MAX];
      char *style, *name, *challenge, *class;
      int len;

      if (as == NULL)
          return (0);

      auth_setstate(as, 0);

      if ((style = auth_getitem(as, AUTHV_STYLE)) == NULL ||
          (name = auth_getitem(as, AUTHV_NAME)) == NULL ||
          !_auth_validuser(name)) {
          if (more == 0)
              return (auth_close(as));
          return(0);
      }

      len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style);
      if (len < 0 || len >= sizeof(path)) {
          if (more == 0)
              return (auth_close(as));
          return (0);
      }

      challenge = auth_getitem(as, AUTHV_CHALLENGE);
      class = auth_getitem(as, AUTHV_CLASS);

      if (challenge)
          auth_setdata(as, challenge, strlen(challenge) + 1);
      else
          auth_setdata(as, "", 1);
      if (response) {
          auth_setdata(as, response, strlen(response) + 1);
          explicit_bzero(response, strlen(response));
      } else
          auth_setdata(as, "", 1);

      auth_call(as, path, style, "-s", "response", "--", name,
                class, (char *)NULL);

      /*
       * If they authenticated then make sure they did not expire
       */
      if (auth_getstate(as) & AUTH_ALLOW)
          auth_check_expire(as);
      if (more == 0)
          return (auth_close(as));
      return (auth_getstate(as) & AUTH_ALLOW);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/man3/authenticate.3#auth_userresponse][=auth_userresponse=]] is used to pass the user's response from
  [[#auth_userchallenge][=auth_userchallenge=]] back to the authentication module. Similar to
  =auth_userchallenge=, it is also a front-end for [[#auth_call][=auth_call=]].

  If =as= is =NULL=, =0= is returned.

  The state of =as= is then set to =0=.
  #+begin_src c
  auth_setstate(as, 0);
  #+end_src

  =as= is then checked to ensure all the required items are set. Then
  it checks if =as->style= or =as->name= are =NULL=, or if the
  username is invalid using [[#_auth_validuser][=auth_validuser=]]. If any of those checks
  fail, and =more= is equal to =0=, then the session is closed using
  [[#auth_close][=auth_close=]], and the return value of that returned. Otherwise =0=
  is returned.

  Then the path to the [[#modules][auth module]] is created.

  The challenge and class of the session are extracted and stored in
  variables =challenge= and =class= respectively.

  If =challenge= contains data, its contents are added to the
  =as->data= spool, otherwise an empty string is added to the spool.

  If =response= contains data, it is added to the data spool as well,
  and then =respose= is =explicit_bzero='d. Otherwise an empty string
  is added to the data spool.

  Next [[#auth_call][=auth_call=]] is used to call the auth module with service type
  =response=.

  #+begin_src c
  auth_call(as, path, style, "-s", "response", "--", name,
          class, (char *)NULL);
  #+end_src

  If the request is allowed, it's checked to make sure it's not
  expired using [[#auth_check_expire][=auth_check_expire=]].

  If =more= is equal to =0=, the session is closed using [[#auth_close][=auth_close=]].

  The allow state of the session is then returned.

  #+begin_src c
  return (auth_getstate(as) & AUTH_ALLOW);
  #+end_src

* auth_approval
  :PROPERTIES:
  :CUSTOM_ID: auth_approval
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  int auth_approval(auth_session_t *as, login_cap_t *lc, char *name, char *type)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      int close_on_exit, close_lc_on_exit, len;
      struct passwd pwstore, *pwd;
      char *approve, *s, path[PATH_MAX], pwbuf[_PW_BUF_LEN];

      pwd = NULL;
      close_on_exit = as == NULL;
      close_lc_on_exit = lc == NULL;

      if (as != NULL && name == NULL)
          name = auth_getitem(as, AUTHV_NAME);

      if (as != NULL)
          pwd = auth_getpwd(as);

      if (pwd == NULL) {
          if (name != NULL) {
              if (!_auth_validuser(name)) {
                  warnx("cannot approve who we don't recognize");
                  return (0);
              }
              getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
          } else {
              getpwuid_r(getuid(), &pwstore, pwbuf, sizeof(pwbuf),
                  &pwd);
              if (pwd == NULL) {
                  syslog(LOG_ERR, "no such user id %u", getuid());
                  warnx("cannot approve who we don't recognize");
                  return (0);
              }
              name = pwd->pw_name;
          }
      }

      if (name == NULL)
          name = pwd->pw_name;

      if (lc == NULL) {
          if (strlen(name) >= PATH_MAX) {
              syslog(LOG_ERR, "username to login %.*s...",
                  PATH_MAX, name);
              warnx("username too long");
              return (0);
          }
          if (pwd == NULL && (approve = strchr(name, '.')) != NULL) {
              strlcpy(path, name, sizeof path);
              path[approve - name] = '\0';
              getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
          }
          lc = login_getclass(pwd ? pwd->pw_class : NULL);
          if (lc == NULL) {
              warnx("unable to classify user");
              return (0);
          }
      }

      if (!type)
          type = LOGIN_DEFSERVICE;
      else {
          if (strncmp(type, "approve-", 8) == 0)
              type += 8;

          len = snprintf(path, sizeof(path), "approve-%s", type);
          if (len < 0 || len >= sizeof(path)) {
              if (close_lc_on_exit)
                  login_close(lc);
              syslog(LOG_ERR, "approval path too long %.*s...",
                  PATH_MAX, type);
              warnx("approval script path too long");
              return (0);
          }
      }

      if ((approve = login_getcapstr(lc, s = path, NULL, NULL)) == NULL)
          approve = login_getcapstr(lc, s = "approve", NULL, NULL);

      if (approve && approve[0] != '/') {
          if (close_lc_on_exit)
              login_close(lc);
          syslog(LOG_ERR, "Invalid %s script: %s", s, approve);
          warnx("invalid path to approval script");
          free(approve);
          return (0);
      }

      if (as == NULL && (as = auth_open()) == NULL) {
          if (close_lc_on_exit)
              login_close(lc);
          syslog(LOG_ERR, "%m");
          warn(NULL);
          free(approve);
          return (0);
      }

      auth_setstate(as, AUTH_OKAY);
      if (auth_setitem(as, AUTHV_NAME, name) < 0) {
          syslog(LOG_ERR, "%m");
          warn(NULL);
          goto out;
      }
      if (auth_check_expire(as) < 0)	/* is this account expired */
          goto out;
      if (_auth_checknologin(lc,
          auth_getitem(as, AUTHV_INTERACTIVE) != NULL)) {
          auth_setstate(as, (auth_getstate(as) & ~AUTH_ALLOW));
          goto out;
      }
      if (login_getcapbool(lc, "requirehome", 0) && pwd && pwd->pw_dir &&
          pwd->pw_dir[0]) {
          struct stat sb;

          if (stat(pwd->pw_dir, &sb) == -1 || !S_ISDIR(sb.st_mode) ||
              (pwd->pw_uid && sb.st_uid == pwd->pw_uid &&
              (sb.st_mode & S_IXUSR) == 0)) {
              auth_setstate(as, (auth_getstate(as) & ~AUTH_ALLOW));
              goto out;
          }
      }
      if (approve)
          auth_call(as, approve, strrchr(approve, '/') + 1, "--", name,
              lc->lc_class, type, (char *)NULL);

  out:
      free(approve);
      if (close_lc_on_exit)
          login_close(lc);

      if (close_on_exit)
          return (auth_close(as));
      return (auth_getstate(as) & AUTH_ALLOW);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/man3/authenticate.3#auth_approval][=auth_approval=]] is used to check a user against the [[#approval][approval script]]
  for service =type=. It is a front end for [[#auth_call][=auth_call=]]. Approval
  script types all begin with =approval-=.

  Before running the scripts, first the validity of the account is
  checked. This is done first using [[#auth_check_expired][=auth_check_expired=]], then
  [[#_auth_checknologin][=_auth_checknologin=]], and finally [[https://man.openbsd.org/login_getcapbool#login_getcapbool][=login_getcapbool=]] to ensure the
  user has a home directory if one is required by their login class.

  If =type= doesn't begin with =approval-= it will be prepended
  internally.

  if =as= is =NULL=, an auth session will be created and destroyed
  inside the function.

  If =lc= is =NULL=, it will be retrieved internally by looking up
  =name=.

  If =type= is =NULL=, the default of =LOGIN_DEFSERVICE= is used. This
  is defined in =login_cap.h= as =login=. This should call the default
  =approval= script, according to the [[https://man.openbsd.org/login.conf#CAPABILITIES][=CAPABILITIES=]] section of the
  =login.conf= man page.

  It returns either =0= for disapproval, or non-zero for approval.

* auth_check_expire
  :PROPERTIES:
  :CUSTOM_ID: auth_check_expire
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  quad_t auth_check_expire(auth_session_t *as)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
          as->state &= ~AUTH_ALLOW;
          as->state |= AUTH_EXPIRED;	/* XXX */
          return (-1);
      }

      if (as->pwd == NULL)
          return (0);

      if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
          if (as->now.tv_sec == 0)
              WRAP(gettimeofday)(&as->now, NULL);
          if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
              as->state &= ~AUTH_ALLOW;
              as->state |= AUTH_EXPIRED;
          }
          if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
              return (-1);
          return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
      }
      return (0);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/auth_subr.3#auth_check_expire][=auth_check_expire=]] is used to check if the account used for a
  session is expired. If an account is valid, it returns =0=.
  Otherwise it returns a negative number representing the number of
  seconds elapsed since the account expired. If there's no account
  associated with the session, it will return =-1=.

  It first checks if =as->pwd= is set, and if it isn't it tries to set
  it using [[#auth_setpwd][=auth_setpwd=]]. If both of those fail, then it returns =-1=
  and removes the =AUTH_ALLOW= bitmask from =as->state=, and adds the
  bitmask for =AUTH_EXPIRED=.

  Interestingly, if there's an account name associated with the
  session but it doesn't exist on the system, this function will still
  return =0= instead of =-1=.

* auth_check_change
  :PROPERTIES:
  :CUSTOM_ID: auth_check_change
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  quad_t auth_check_change(auth_session_t *as)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
          as->state &= ~AUTH_ALLOW;
          as->state |= AUTH_PWEXPIRED;	/* XXX */
          return (-1);
      }

      if (as->pwd == NULL)
          return (0);

      if (as->pwd && (quad_t)as->pwd->pw_change) {
          if (as->now.tv_sec == 0)
              WRAP(gettimeofday)(&as->now, NULL);
          if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
              as->state &= ~AUTH_ALLOW;
              as->state |= AUTH_PWEXPIRED;
          }
          if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
              return (-1);
          return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
      }
      return (0);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/auth_subr.3#auth_check_change][=auth_check_change=]] is used to check if the password associated with
  an account is expired. If the password isn't expired, it returns
  =0=. Otherwise it returns a negative number representing the number
  of seconds elapsed since the password expired. If there's no account
  associated with the session, it will return =-1=.

  It operates very similarly to [[#auth_check_expire][=auth_check_expire=]].

* auth_checknologin
  :PROPERTIES:
  :CUSTOM_ID: auth_checknologin
  :END:
  @@html: <details> <summary> @@
  #+begin_src c
  void auth_checknologin(login_cap_t *lc)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      if (_auth_checknologin(lc, 1))
          exit(1);
  }

  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/authenticate.3#auth_checknologin][=auth_checknologin=]] is a simple wrapper around the internal
  [[#_auth_checknologin][=_auth_checknologin=]]. If the user is now allowed to login, it prints
  the reason and calls =exit(1)=.

* auth_cat
  :PROPERTIES:
  :CUSTOM_ID: auth_cat
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  int auth_cat(char *file)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      int fd, nchars;
      char tbuf[8192];

      if ((fd = open(file, O_RDONLY, 0)) == -1)
          return (0);
      while ((nchars = read(fd, tbuf, sizeof(tbuf))) > 0)
          (void)write(fileno(stdout), tbuf, nchars);
      (void)close(fd);
      return (1);
  }
  #+end_src
  @@html: </details> @@

  [[https://man.openbsd.org/man3/authenticate.3#auth_cat][=auth_cat=]] is a helper function that will write the contents of a
  =file= to =stdout=. It returns =0= on failure or =1= on success.

* _auth_validuser
  :PROPERTIES:
  :CUSTOM_ID: _auth_validuser
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  int _auth_validuser(const char *name)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      /* User name must be specified and may not start with a '-'. */
      if (*name == '\0' || *name == '-') {
          syslog(LOG_ERR, "invalid user name %s", name);
          return 0;
      }
      return 1;
  }
  #+end_src
  @@html: </details> @@

  =_auth_validuser= is a small helper function used to check if a
  username passes some very basic validity criteria. Those being that
  it must not be an empty sting, and that it doesn't start with a
  hyphen.

  If a username is invalid, it is logged in the syslog.

* _auth_checknologin
  :PROPERTIES:
  :CUSTOM_ID: _auth_checknologin
  :END:

  @@html: <details> <summary> @@
  #+begin_src c
  static int _auth_checknologin(login_cap_t *lc, int print)
  #+end_src
  @@html: </summary> @@
  #+begin_src c
  {
      struct stat sb;
      char *nologin;
      int mustfree;

      if (login_getcapbool(lc, "ignorenologin", 0))
          return (0);

      /*
       ,* If we fail to get the nologin file due to a database error,
       ,* assume there should have been one...
       ,*/
      nologin = login_getcapstr(lc, "nologin", "", NULL);
      mustfree = nologin && *nologin != '\0';
      if (nologin == NULL)
          goto print_nologin;

      /* First try the nologin file specified in login.conf. */
      if (*nologin != '\0' && stat(nologin, &sb) == 0)
          goto print_nologin;
      if (mustfree) {
          free(nologin);
          mustfree = 0;
      }

      /* If that doesn't exist try _PATH_NOLOGIN. */
      if (stat(_PATH_NOLOGIN, &sb) == 0) {
          nologin = _PATH_NOLOGIN;
          goto print_nologin;
      }

      /* Couldn't stat any nologin files, must be OK to login. */
      return (0);

  print_nologin:
      if (print) {
          if (!nologin || *nologin == '\0' || auth_cat(nologin) == 0) {
              puts("Logins are not allowed at this time.");
              fflush(stdout);
          }
      }
      if (mustfree)
          free(nologin);
      return (-1);
  }
  #+end_src
  @@html: </details> @@

  =_auth_checknologin= is a helper function in =authenticate.c=. It is
  used to check the =nologin= status of the account. If =print= is
  non-zero, it will print the reason for the failure, and print the
  contents of the nologin file using [[#auth_cat][=auth_cat=]].

  It returns =0= if the user is allowed to login, and =-1= otherwise.

* Notes
  https://web.archive.org/web/20170327150148/http://www.penzin.net/bsdauth/
  - In the man page for [[https://man.openbsd.org/auth_subr.3#auth_call][=auth_call=]] it says
    #+begin_src text
    path    The full path name of the login script to run.  The call will
                 fail if path does not pass the requirements of the secure_path(3)
                 function.
    #+end_src

    However I don't see this enforced anywhere, I even wrote a small test
    script to prove it.

    #+CAPTION: =authfail.c=
    #+begin_src c
    #include <sys/types.h>
    #include <login_cap.h>
    #include <bsd_auth.h>
    #include <stdio.h>

    int main(void) {
        auth_session_t *as;

        as = auth_open();
        auth_call(as, "/home/dante/auth_tests/authtest/test", "hello", NULL);
        auth_close(as);
    }
    #+end_src

    Changing ="/home/dante/auth_tests/authtest/test"= to the location
    of the =test= binary.

    #+CAPTION: =test.c=
    #+begin_src c
    #include <stdio.h>

    int main(void) {
        printf("Hello! I don't have a secure path!\n");
        return 0;
    }
    #+end_src

    #+CAPTION: =Makefile=
    #+begin_src makefile
    CFLAGS = -Wall -Wextra

    run: authfail test
        ./authfail

    authfail: authfail.c
        $(CC) -o $@ $(CFLAGS) $<

    test: test.c
        $(CC) -o $@ $(CFLAGS) $<
    #+end_src

    Which results in the following:

    #+begin_src text
    $ pwd && ls -l && make
    /home/dante/auth_tests/authtest
    total 12
    -rw-r--r--  1 dante  dante  143 May 30 19:20 Makefile
    -rw-r--r--  1 dante  dante  248 May 29 19:30 authfail.c
    -rw-r--r--  1 dante  dante  115 May 29 19:22 test.c
    cc -o authfail -Wall -Wextra authfail.c
    cc -o test -Wall -Wextra test.c
    ./authfail
    Hello! I don't have a secure path!
    #+end_src

  - The manpage also says the path is limited to =/bin/= and =/usr/bin=,
    which is also not the case.

  - The man page describes the interface for =auth_getitem= is in the
    format of =AUTH_<item>=, but in reality it is =AUTHV_<item>=.

 # Ask jcs about the file descriptor situation, I don't understand it
 # after reading both the man page and source.

 - The [[#auth_getchallenge][=auth_getchallenge=]] function in the [[https://man.openbsd.org/auth_subr.3#auth_getchallenge][=auth_subr(3)=]] man page
   doesn't seem to exist in the source code.

* Graph

[[file:graph.svg]]

* Copyright
  :PROPERTIES:
  :CUSTOM_ID: copyright
  :END:

  @@html: <details> <summary> @@
  *Click here to expand copyright notices*
  @@html: </summary> @@
  #+CAPTION: From [[https://github.com/openbsd/src/blob/master/lib/libc/gen/authenticate.c][=authenticate.c=]]
  #+begin_src text
  /*	$OpenBSD: authenticate.c,v 1.28 2019/12/04 06:25:45 deraadt Exp $	*/

  /*-
   ,* Copyright (c) 1997 Berkeley Software Design, Inc. All rights reserved.
   ,*
   ,* Redistribution and use in source and binary forms, with or without
   ,* modification, are permitted provided that the following conditions
   ,* are met:
   ,* 1. Redistributions of source code must retain the above copyright
   ,*    notice, this list of conditions and the following disclaimer.
   ,* 2. Redistributions in binary form must reproduce the above copyright
   ,*    notice, this list of conditions and the following disclaimer in the
   ,*    documentation and/or other materials provided with the distribution.
   ,* 3. All advertising materials mentioning features or use of this software
   ,*    must display the following acknowledgement:
   ,*	This product includes software developed by Berkeley Software Design,
   ,*	Inc.
   ,* 4. The name of Berkeley Software Design, Inc.  may not be used to endorse
   ,*    or promote products derived from this software without specific prior
   ,*    written permission.
   ,*
   ,* THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
   ,* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   ,* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ,* ARE DISCLAIMED.  IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
   ,* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   ,* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   ,* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   ,* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   ,* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   ,* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   ,* SUCH DAMAGE.
   ,*
   ,*	BSDI $From: authenticate.c,v 2.21 1999/09/08 22:33:26 prb Exp $
   ,*/
  #+end_src

  #+CAPTION: From [[https://github.com/openbsd/src/blob/master/lib/libc/gen/auth_subr.c][=auth_subr.c=]]
  #+begin_src text
  /*	$OpenBSD: auth_subr.c,v 1.56 2020/10/13 04:42:28 guenther Exp $	*/

  /*
   ,* Copyright (c) 2000-2002,2004 Todd C. Miller <millert@openbsd.org>
   ,*
   ,* Permission to use, copy, modify, and distribute this software for any
   ,* purpose with or without fee is hereby granted, provided that the above
   ,* copyright notice and this permission notice appear in all copies.
   ,*
   ,* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   ,* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   ,* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   ,* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   ,* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   ,* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   ,* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   ,*/
  /*-
   ,* Copyright (c) 1995,1996,1997 Berkeley Software Design, Inc.
   ,* All rights reserved.
   ,*
   ,* Redistribution and use in source and binary forms, with or without
   ,* modification, are permitted provided that the following conditions
   ,* are met:
   ,* 1. Redistributions of source code must retain the above copyright
   ,*    notice, this list of conditions and the following disclaimer.
   ,* 2. Redistributions in binary form must reproduce the above copyright
   ,*    notice, this list of conditions and the following disclaimer in the
   ,*    documentation and/or other materials provided with the distribution.
   ,* 3. All advertising materials mentioning features or use of this software
   ,*    must display the following acknowledgement:
   ,*	This product includes software developed by Berkeley Software Design,
   ,*	Inc.
   ,* 4. The name of Berkeley Software Design, Inc.  may not be used to endorse
   ,*    or promote products derived from this software without specific prior
   ,*    written permission.
   ,*
   ,* THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
   ,* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   ,* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ,* ARE DISCLAIMED.  IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
   ,* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   ,* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   ,* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   ,* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   ,* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   ,* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   ,* SUCH DAMAGE.
   ,*
   ,*	BSDI $From: auth_subr.c,v 2.4 1999/09/08 04:10:40 prb Exp $
   ,*/
  #+end_src
  @@html: </details> @@

#+begin_export html
<style>
  details > summary {
    list-style: none;
  }
  details > summary::-webkit-details-marker {
    display: none;
  }
</style>
#+end_export