summaryrefslogtreecommitdiffstats
path: root/content/posts/how-bsd-authentication-works/index.org
diff options
context:
space:
mode:
Diffstat (limited to 'content/posts/how-bsd-authentication-works/index.org')
-rw-r--r--content/posts/how-bsd-authentication-works/index.org2811
1 files changed, 2811 insertions, 0 deletions
diff --git a/content/posts/how-bsd-authentication-works/index.org b/content/posts/how-bsd-authentication-works/index.org
new file mode 100644
index 0000000..f0623c2
--- /dev/null
+++ b/content/posts/how-bsd-authentication-works/index.org
@@ -0,0 +1,2811 @@
+#+TITLE: How BSD Authentication Works
+#+DATE: 2021-10-18T17:27:13-04:00
+#+DRAFT: true
+#+SHOWTOC: true
+#+DESCRIPTION: A walkthrough of how OpenBSD's BSD Auth framework functions
+#+TAGS[]: openbsd security
+#+KEYWORDS[]: openbsd security
+#+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 other systems like AIX, Solaris,
+ 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)=]].
+
+* 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.
+
+ I've also created a [[#graph][graph]] at the bottom of the post to help
+ visualize the function calls.
+
+ 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. This communication is
+ covered in greater detail in the [[#auth_call][=auth_call=]] section.
+
+ 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=]].
+
+ For cases where challenge / response authentication is required and
+ the user can't interacting through =stdin= and =stdout=,
+ [[#auth_userchallenge][=auth_userchallenge=]] and [[#auth_userresponse][=auth_userresponse=]] can be used.
+
+* 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=]].
+
+* 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 strings as arguments: =name=, =style=, =type=, and
+ =password=. It returns either a =0= for failure, of a non-zero value
+ for success.
+
+ @@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. This is =passwd= on normal accounts.
+ - The style can be one of the installed authentication methods, like
+ =passwd=, =radius=, =skey=, =yubikey=, etc.
+ - Styles can also be installed through BSD Auth module packages
+ - =type= is the authentication type
+ - Types are defined in =login.conf= and as 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.
+ - =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 passed to the auth module
+ as a =response=
+
+ =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= ([[#auth_item_t][=auth_item_t=]]), 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.
+
+ *Note*: As of writing, the man page displays the incorrect name for
+ the constants.
+
+ #+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 =as->ap=.
+
+** 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 [[#auth_mkvalue][=auth_mkvalue=]] 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=]] is very similar to [[#auth_userokay][=auth_userokay=]]. It takes the
+ same arguments, except it returns the [[#auth_session_t][=auth_session_t=]] struct
+ instead of just the status.
+
+ It first checks that =name= is valid according to [[#_auth_validuser][=_auth_validuser=]].
+
+ 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 [[https://man.openbsd.org/man2/dup.2#dup2][=dup2(3)=]]. If =as->fd=, is not =-1=, it is set to
+ file descriptor 4, or =AUTH_FD=, also using =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 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.
+
+ If an authorization is received (any line starting with =BI_AUTH=),
+ the appropriate state is bitwise =or=-ed onto =as->state=. This
+ allows multiple authorizations, such as a case where both
+ =BI_ROOTOKAY= and =BI_SECURE= are sent. This would result in a state
+ of =AUTH_OKAY || AUTH_ROOTOKAY || AUTH_SECURE=.
+
+ 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 using [[#_auth_validuser][=_auth_validuser=]].
+
+ 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, indicating the
+ auth module has returned a challenge. 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. 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 similarly to how it is
+ created in [[#auth_verify][=auth_verify=]].
+
+ 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=]]
+ and the return value from it is returned.
+
+ 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=.
+
+* 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_mkvalue
+ :PROPERTIES:
+ :CUSTOM_ID: auth_mkvalue
+ :END:
+
+ @@html: <details> <summary> @@
+ #+begin_src c
+ char *auth_mkvalue(char *value)
+ #+end_src
+
+ @@html: </summary> @@
+ #+begin_src c
+ {
+ char *big, *p;
+
+ big = malloc(strlen(value) * 4 + 1);
+ if (big == NULL)
+ return (NULL);
+ /*
+ ,* XXX - There should be a more standardized
+ ,* routine for doing this sort of thing.
+ ,*/
+ for (p = big; *value; ++value) {
+ switch (*value) {
+ case '\r':
+ ,*p++ = '\\';
+ ,*p++ = 'r';
+ break;
+ case '\n':
+ ,*p++ = '\\';
+ ,*p++ = 'n';
+ break;
+ case '\\':
+ ,*p++ = '\\';
+ ,*p++ = *value;
+ break;
+ case '\t':
+ case ' ':
+ if (p == big)
+ ,*p++ = '\\';
+ ,*p++ = *value;
+ break;
+ default:
+ if (!isprint((unsigned char)*value)) {
+ ,*p++ = '\\';
+ ,*p++ = ((*value >> 6) & 0x3) + '0';
+ ,*p++ = ((*value >> 3) & 0x7) + '0';
+ ,*p++ = ((*value ) & 0x7) + '0';
+ } else
+ ,*p++ = *value;
+ break;
+ }
+ }
+ ,*p = '\0';
+ return (big);
+ }
+ #+end_src
+ @@html: </details> @@
+
+ [[https://man.openbsd.org/authenticate.3#auth_mkvalue][=auth_mkvalue=]] creates an escaped string which can be decoded by [[#auth_getvalue][=auth_getvalue=]].
+
+* _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.
+
+ It returns =1= if the username is valid, otherwise it returns =0=.
+
+* _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.
+
+* Call Graph
+ :PROPERTIES:
+ :CUSTOM_ID: graph
+ :END:
+
+ #+ATTR_HTML: :title Authentication call graph
+ #+ATTR_HTML: :title Authentication call graph
+ [[file:graph.svg]]
+
+ @@html: <details> <summary> @@
+ *Click here* to see the code that generates the call graph.
+ @@html: </summary> @@
+#+INCLUDE: "gen_dot.rb" src ruby
+ @@html: </details> @@
+
+* 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