diff options
Diffstat (limited to 'content/posts/how-bsd-authentication-works/index.org')
-rw-r--r-- | content/posts/how-bsd-authentication-works/index.org | 2811 |
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 |