#+TITLE: How BSD Authentication Works #+DATE: 2020-11-02T16:49:46-05:00 #+DRAFT: true #+SHOWTOC: true #+DESCRIPTION: #+TAGS[]: openbsd #+KEYWORDS[]: openbsd #+SLUG: #+SUMMARY: #+ATTR_HTML: :title OpenBSD Internals #+ATTR_HTML: :alt OpenBSD mascot cutaway view with spinning gears inside [[file:openbsd_internals.gif]] * History :PROPERTIES: :CUSTOM_ID: history :END: The way OpenBSD authenticates users is quite different from other Unix-like operating systems. Most systems from AIX to Solaris, including Linux, the other BSDs, and MacOS, use a framework called [[https://en.wikipedia.org/wiki/Pluggable_authentication_module][Pluggable Authentication Module]] (PAM). The two main implementations are [[http://www.linux-pam.org/][Linux PAM]] and [[https://www.openpam.org/][OpenPAM]]. PAM modules are created as dynamically loaded shared objects, which communicate using a combination of common and implementation specific interfaces ([[https://linux.die.net/man/3/pam][Linux-PAM]] and [[https://www.freebsd.org/cgi/man.cgi?query=pam&apropos=0&sektion=3&manpath=FreeBSD+12.1-RELEASE+and+Ports&arch=default&format=html][OpenPAM]]). It's configured using the [[https://linux.die.net/man/5/pam.d][pam.d]] directory and [[https://www.freebsd.org/cgi/man.cgi?query=pam.conf&sektion=5&apropos=0&manpath=FreeBSD+12.1-RELEASE+and+Ports][pam.conf]] file. While it can be flexible, it's highly complex and very easy to mis-configure, leaving you open to strange and hard to track down authentication bugs. On top of that, the fact that it's a shared library means that any vulnerability in a poorly vetted authentication module gives attackers direct access to the internals of your application. Author Michael W. Lucas said it best when he described PAM as [[https://www.youtube.com/watch?v=-CXp3byvI1g][unstandardized black magic]]. OpenBSD on the other hand uses a mechanism called BSD Authentication. It was originally developed for a now-defunct proprietary operating system called [[https://en.wikipedia.org/wiki/BSD/OS][BSD/OS]] by [[https://en.wikipedia.org/wiki/Berkeley_Software_Design][Berkeley Software Design Inc.]], who later donated the system. It was then adopted by OpenBSD in release 2.9. BSD Auth is comparatively much simpler than PAM. Modules or, authentication "styles", are instead stand alone applications or scripts that communicate over IPC. The module has no ability to interfere with the parent and can very easily revoke permissions using [[https://man.openbsd.org/pledge][=pledge(2)=]] or [[https://man.openbsd.org/unveil][=unveil(2)=]]. The BSD Authentication system of configured through [[https://man.openbsd.org/login.conf][=login.conf(5)=]]. * Why :PROPERTIES: :CUSTOM_ID: why :END: There isn't much on the internet about how to use BSD Authentication. I was curious about how the internals worked, and I figured someone else might be too :^) * Documentation :PROPERTIES: :CUSTOM_ID: documentation :END: All of the high level authentication functions are described in [[https://man.openbsd.org/authenticate][=authenticate(3)=]], with the lower level functions being described in [[https://man.openbsd.org/auth_subr][=auth_subr(3)=]]. Click on any function prototype in this post to see its definition. All code snippets from this blog post belong to the OpenBSD contributors. Please see the [[#copyright][Copyright]] section for details. * BSD Auth Modules :PROPERTIES: :CUSTOM_ID: modules :END: Modules are located in =/usr/libexec/auth/= with the naming convention =login_<style>=. They accept arguments in the following form. #+BEGIN_SRC shell login_<style> [-s service] [-v key=value] user [class] #+END_SRC - =<style>= is the authentication method. This could be =passwd=, =radius=, =skey=, =yubikey=, etc. There's more information about available styles in [[https://man.openbsd.org/login.conf][=login.conf(5)=]] under the [[https://man.openbsd.org/login.conf#AUTHENTICATION][=AUTHENTICATION=]] header. - =service= is the service type. Typically authentication methods will accept one of three values here: =login=, =challenge=, or =response=. =login= is the default if it's not specified, and is used to let the module know to interact with the user directly through =stdin= and =stdout=, while =challenge= and =response= are used to pass messages back and forth through the BSD Auth API. Each style's man page will have more details on these. - =-v key=value= is an optional argument. There can be more than one arguments in this style. This is used to pass extra data to the program under certain circumstances. - =user= is the name of the user to be authenticated. - =class= is optional and specifies the login class to use for the user. =login= and =su= pass in extra data as =-v= flags. #+CAPTION: Taken from [[https://man.openbsd.org/login.conf][=login.conf(5)=]] #+BEGIN_SRC The login(1) program provides the following through the -v option: auth_type The type of authentication to use. fqdn The hostname provided to login by the -h option. hostname The name login(1) will place in the utmp file for the remote hostname. local_addr The local IP address given to login(1) by the -L option. lastchance Set to "yes" when a user's password has expired but the user is being given one last chance to login and update the password. login This is a new login session (as opposed to a simple identity check). remote_addr The remote IP address given to login(1) by the -R option. style The style of authentication used for this user (see approval scripts below). The su(1) program provides the following through the -v option: wheel Set to either "yes" or "no" to indicate if the user is in group wheel when they are trying to become root. Some authentication types require the user to be in group wheel when using the su(1) program to become super user. #+END_SRC The auth module communicates with its caller through what's called the "back channel" on file descriptor 3. Some modules require an extra file descriptor to be passed in for stateful challenge/response authentication. In these cases, an extra =-v fd=4= argument will be passed. Theoretically this =fd= can be any number, but in practice =fd=4= is hard-coded. Most modules also have a hidden flag =-d=, which sets the back channel do =stdio=, presumably for debugging purposes. The simplest way to authenticate a user with BSD Auth is by using [[#auth_userokay][=auth_userokay=]]. ** TODO How are these configured in login.conf? * Approval Scripts :PROPERTIES: :CUSTOM_ID: approval :END: Approval scripts can be much simpler than the full login modules used by the other functions. They are given the same back-channel as auth modules, but should not explicitly authenticate or revoke users. They should exit with a zero status for approval, or non-zero status to signal disapproval. Approval scrips receive arguments in the following form. #+begin_src shell approve [-v name=value] username class service #+end_src It can also receive extra key-value =-v= arguments in the same format as [[#modules][auth modules]]. More information is available in the [[https://man.openbsd.org/login.conf#APPROVAL][=APPROVAL=]] section of the =login.conf= man page. Approval scripts are run using [[#auth_approval][=auth_approval=]]. ** TODO How are these configured in login.conf? * auth_userokay :PROPERTIES: :CUSTOM_ID: auth_userokay :END: [[https://man.openbsd.org/authenticate.3#auth_userokay][=auth_userokay=]] is the highest level function, and easiest to use. It takes four character arrays as arguments, =name=, =style=, =type=, and =password=. It returns either a =0= for failure, of a non-zero value for success. This function lives inside =/lib/libc/gen/authenticate.c= @@html: <details> <summary> @@ #+BEGIN_SRC c int auth_userokay(char *name, char *style, char *type, char *password); #+END_SRC @@html: </summary> @@ #+begin_src c { auth_session_t *as; as = auth_usercheck(name, style, type, password); return (as != NULL ? auth_close(as) : 0); } #+end_src @@html: </details> @@ - =name= is the name of the user to be authenticated - =style= is the login method to be used - If =style= is =NULL=, the user's default login style will be used. By default this is =passwd= on normal accounts. - The style can be one of the installed authentication methods, like =passwd=, =radius=, =skey=, =yubikey=, etc. - There's more information about available styles in =login.conf(5)= - Styles can also be installed through BSD Auth module packages - =type= is the authentication type - Types are defined in =login.conf= and define a group of allowed auth styles - If =type= is =NULL=, use the auth type for the user's login class. The default type is =auth-default=, which allows =psaswd= and =skey= auth methods. - There's more information about how to add methods in =login.conf(5)= - =password= is the password to test - If =password= is =NULL=, then the user is interactively prompted. This is required for auth styles using challenge-response methods. - If =password= is specified, then it's non-interactively tested =auth_userokay= is just a wrapper around [[#auth_usercheck][=auth_usercheck=]] that takes care of closing the session using [[#auth_close][=auth_close=]], and returning the resulting value. * auth_session_t :PROPERTIES: :CUSTOM_ID: auth_session_t :END: =auth_session_t= is the main data structure used to represent the authentication session. #+BEGIN_SRC c struct auth_session_t { char *name; /* name of use being authenticated */ char *style; /* style of authentication used */ char *class; /* class of user */ char *service; /* type of service being performed */ char *challenge; /* last challenge issued */ int flags; /* see below */ struct passwd *pwd; /* password entry for user */ struct timeval now; /* time of authentication */ int state; /* authenticated state */ struct rmfiles *rmlist; /* list of files to remove on failure */ struct authopts *optlist; /* list of options to scripts */ struct authdata *data; /* additional data to send to scripts */ char spool[MAXSPOOLSIZE]; /* data returned from login script */ int index; /* how much returned thus far */ int fd; /* connection to authenticator */ va_list ap0; /* argument list to auth_call */ va_list ap; /* additional arguments to auth_call */ }; #+END_SRC Where =MAXSPOOLSIZE=, =authdata=, =authopts=, and =rmfiles= are defined as #+BEGIN_SRC c #define MAXSPOOLSIZE (8*1024) /* Spool up to 8K of back info */ struct rmfiles { struct rmfiles *next; char *file; }; struct authopts { struct authopts *next; char *opt; }; struct authdata { struct authdata *next; void *ptr; size_t len; }; #+END_SRC There are several functions which get used to operate on =auth_session_t= to keep it opaque. ** auth_setdata :PROPERTIES: :CUSTOM_ID: auth_setdata :END: @@html: <details> <summary> @@ #+begin_src c int auth_setdata(auth_session_t *as, void *ptr, size_t len) #+end_src @@html: </summary> @@ #+begin_src c { struct authdata *data, *dp; if (len <= 0) return (0); if ((data = malloc(sizeof(*data) + len)) == NULL) return (-1); data->next = NULL; data->len = len; data->ptr = data + 1; memcpy(data->ptr, ptr, len); if (as->data == NULL) as->data = data; else { for (dp = as->data; dp->next != NULL; dp = dp->next) ; dp->next = data; } return (0); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_setdata~2][=auth_setdata=]] allocates and initializes a new =authdata= struct, storing a copy of the data from =*ptr= and =len=. It then point the =next= field on the last =authdata= struct in =*as= to its location. It returns =0= on success. ** auth_setitem / auth_getitem :PROPERTIES: :CUSTOM_ID: auth_setitem :END: @@html: <details> <summary> @@ #+begin_src c int auth_setitem(auth_session_t *as, auth_item_t item, char *value) #+end_src @@html: </summary> @@ #+begin_src c { if (as == NULL) { errno = EINVAL; return (-1); } switch (item) { case AUTHV_ALL: if (value != NULL) { errno = EINVAL; return (-1); } auth_setitem(as, AUTHV_CHALLENGE, NULL); auth_setitem(as, AUTHV_CLASS, NULL); auth_setitem(as, AUTHV_NAME, NULL); auth_setitem(as, AUTHV_SERVICE, NULL); auth_setitem(as, AUTHV_STYLE, NULL); auth_setitem(as, AUTHV_INTERACTIVE, NULL); return (0); case AUTHV_CHALLENGE: if (value == as->challenge) return (0); if (value != NULL && (value = strdup(value)) == NULL) return (-1); free(as->challenge); as->challenge = value; return (0); case AUTHV_CLASS: if (value == as->class) return (0); if (value != NULL && (value = strdup(value)) == NULL) return (-1); free(as->class); as->class = value; return (0); case AUTHV_NAME: if (value == as->name) return (0); if (value != NULL && !_auth_validuser(value)) { errno = EINVAL; return (-1); } if (value != NULL && (value = strdup(value)) == NULL) return (-1); free(as->name); as->name = value; return (0); case AUTHV_SERVICE: if (value == as->service) return (0); if (value == NULL || strcmp(value, defservice) == 0) value = defservice; else if ((value = strdup(value)) == NULL) return (-1); if (as->service && as->service != defservice) free(as->service); as->service = value; return (0); case AUTHV_STYLE: if (value == as->style) return (0); if (value == NULL || strchr(value, '/') != NULL || (value = strdup(value)) == NULL) return (-1); free(as->style); as->style = value; return (0); case AUTHV_INTERACTIVE: if (value == NULL) as->flags &= ~AF_INTERACTIVE; else as->flags |= ~AF_INTERACTIVE; return (0); default: errno = EINVAL; return (-1); } } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_setitem][=auth_setitem=]] is used to set one of several different fields of =as= to =value=. Depending on the value of =item=, it can be the =challenge=, =class=, =name=, =service=, =style=, or =interactive= field. If =*value= is =NULL=, it clears that field. If =item= is =AUTHV_ALL= and =*value= is =NULL=, all fields are cleared. It returns =0= on success. #+CAPTION: Taken from [[https://man.openbsd.org/auth_subr.3#auth_getitem][=auth_subr(3)=]] #+begin_src text AUTH_CHALLENGE The latest challenge, if any, set for the session. AUTH_CLASS The class of the user, as defined by the /etc/login.conf file. This value is not directly used by BSD Authentication, rather, it is passed to the login scripts for their possible use. AUTH_INTERACTIVE If set to any value, then the session is tagged as interactive. If not set, the session is not interactive. When the value is requested it is always either NULL or “True”. The auth subroutines may choose to provide additional information to standard output or standard error when the session is interactive. There is no functional change in the operation of the subroutines. AUTH_NAME The name of the user being authenticated. The name should include the instance, if any, that is being requested. AUTH_SERVICE The service requesting the authentication. Initially it is set to the default service which provides the traditional interactive service. AUTH_STYLE The style of authentication being performed, as defined by the /etc/login.conf file. The style determines which login script should actually be used. #+end_src @@html: <details> <summary> @@ #+begin_src c char *auth_getitem(auth_session_t *as, auth_item_t item) #+end_src @@html: </summary> @@ #+begin_src c { if (as != NULL) { switch (item) { case AUTHV_CHALLENGE: return (as->challenge); case AUTHV_CLASS: return (as->class); case AUTHV_NAME: return (as->name); case AUTHV_SERVICE: return (as->service ? as->service : defservice); case AUTHV_STYLE: return (as->style); case AUTHV_INTERACTIVE: return ((as->flags & AF_INTERACTIVE) ? "True" : NULL); default: break; } } return (NULL); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_getitem][=auth_getitem=]] is used to return the value of the fields listed above. *** auth_item_t :PROPERTIES: :CUSTOM_ID: auth_item_t :END: =auth_item_t= is an enum defined in =/include/bsd_auth.h=. #+begin_src c typedef enum { AUTHV_ALL, AUTHV_CHALLENGE, AUTHV_CLASS, AUTHV_NAME, AUTHV_SERVICE, AUTHV_STYLE, AUTHV_INTERACTIVE } auth_item_t; #+end_src ** auth_setoption :PROPERTIES: :CUSTOM_ID: auth_setoption :END: @@html: <details> <summary> @@ #+begin_src c int auth_setoption(auth_session_t *as, char *n, char *v) #+end_src @@html: </summary> @@ #+begin_src c { struct authopts *opt; size_t len = strlen(n) + strlen(v) + 2; int ret; if ((opt = malloc(sizeof(*opt) + len)) == NULL) return (-1); opt->opt = (char *)(opt + 1); ret = snprintf(opt->opt, len, "%s=%s", n, v); if (ret < 0 || ret >= len) { free(opt); errno = ENAMETOOLONG; return (-1); } opt->next = as->optlist; as->optlist = opt; return(0); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_setoption][=auth_setoption=]] initializes a new =authopts= struct, and sets the =*opt= field to a string formatted as =sprintf("%s=%s", n, v)=. It then point the =*next= field on the last =authopts= struct in =*as= to its location. It returns =0= on success. ** auth_setstate / auth_getstate :PROPERTIES: :CUSTOM_ID: auth_setstate :END: @@html: <details> <summary> @@ #+begin_src c void auth_setstate(auth_session_t *as, int s) #+end_src @@html: </summary> @@ #+begin_src c { as->state = s; } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_setstate][=auth_setstate=]] sets the =state= of =*as= to =s=. @@html: <details> <summary> @@ #+begin_src c int auth_getstate(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { return (as->state); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_getstate][=auth_getstate=]] return the =state= of =*as=. ** auth_setpwd / auth_getpwd :PROPERTIES: :CUSTOM_ID: auth_setpwd :END: @@html: <details> <summary> @@ #+begin_src c int auth_setpwd(auth_session_t *as, struct passwd *pwd) #+end_src @@html: </summary> @@ #+begin_src c { struct passwd pwstore; char *instance, pwbuf[_PW_BUF_LEN]; if (pwd == NULL && as->pwd == NULL && as->name == NULL) return (-1); /* true failure */ if (pwd == NULL) { /* * If we were not passed in a pwd structure we need to * go find one for ourself. Always look up the username * (if it is defined) in the passwd database to see if there * is an entry for the user. If not, either use the current * entry or simply return a 1 which implies there is * no user by that name here. This is not a failure, just * a point of information. */ if (as->name == NULL) return (0); getpwnam_r(as->name, &pwstore, pwbuf, sizeof(pwbuf), &pwd); if (pwd == NULL) { instance = strchr(as->name, '/'); if (instance == NULL) return (as->pwd ? 0 : 1); if (strcmp(instance, "/root") == 0) { getpwnam_r(instance + 1, &pwstore, pwbuf, sizeof(pwbuf), &pwd); } if (pwd == NULL) return (as->pwd ? 0 : 1); } } if ((pwd = pw_dup(pwd)) == NULL) return (-1); /* true failure */ if (as->pwd) { explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd)); free(as->pwd); } as->pwd = pwd; return (0); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_setpwd][=auth_setpwd=]] is used to retrieve and set the [[https://man.openbsd.org/man3/getpwnam.3][password database]] entry in =as= if one isn't already set. If a passwd entry is passed in through =pwd=, it uses that to set =as->pwd=. If =pwd= is =NULL=, it tries to find the passwd entry associated with =as->name=. If it finds one, it sets =as->pwd= and returns =0=. If there is no entry with that username, it returns =1=. @@html: <details> <summary> @@ #+begin_src c struct passwd *auth_getpwd(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { return (as->pwd); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_getpwd][=auth_getpwd=]] returns =as->pwd=. ** auth_set_va_list :PROPERTIES: :CUSTOM_ID: auth_set_va_list :END: @@html: <details> <summary> @@ #+begin_src c void auth_set_va_list(auth_session_t *as, va_list ap) #+end_src @@html: </summary> @@ #+begin_src c { va_copy(as->ap, ap); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_set_va_list][=auth_set_va_list=]] copies =ap= to the =ap= field in =*as= ** auth_clrenv :PROPERTIES: :CUSTOM_ID: auth_clrenv :END: @@html: <details> <summary> @@ #+begin_src c void auth_clrenv(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { char *line; for (line = as->spool; line < as->spool + as->index;) { if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) { if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) { line[0] = 'i'; line[1] = 'g'; line[2] = 'n'; } } else if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) { if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) { line[2] = 'i'; line[3] = 'g'; line[4] = 'n'; } } while (*line++) ; } } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_clrenv][=auth_clrenv=]] removes all lines containing =BI_SETENV= and =BI_UNSETENV= from =as->spool=. This is explained under the [[#auth_call][=auth_call=]] section. ** auth_clroption :PROPERTIES: :CUSTOM_ID: auth_clroption :END: @@html: <details> <summary> @@ #+begin_src c void auth_clroption(auth_session_t *as, char *option) #+end_src @@html: </summary> @@ #+begin_src c { struct authopts *opt, *oopt; size_t len; len = strlen(option); if ((opt = as->optlist) == NULL) return; if (strncmp(opt->opt, option, len) == 0 && (opt->opt[len] == '=' || opt->opt[len] == '\0')) { as->optlist = opt->next; free(opt); return; } while ((oopt = opt->next) != NULL) { if (strncmp(oopt->opt, option, len) == 0 && (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) { opt->next = oopt->next; free(oopt); return; } opt = oopt; } } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_clroption][=auth_clroption=]] removes the option named =option= from =as=. ** auth_clroptions :PROPERTIES: :CUSTOM_ID: auth_clroptions :END: @@html: <details> <summary> @@ #+begin_src c void auth_clroptions(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { struct authopts *opt; while ((opt = as->optlist) != NULL) { as->optlist = opt->next; free(opt); } } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_clroptions][=auth_clroptions=]] clears all options from =as=. ** auth_setenv :PROPERTIES: :CUSTOM_ID: auth_setenv :END: @@html: <details> <summary> @@ #+begin_src c void auth_setenv(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { char *line, *name; /* ,* Set any environment variables we were asked for ,*/ for (line = as->spool; line < as->spool + as->index;) { if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) { if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) { /* only do it once! */ line[0] = 'd'; line[1] = 'i'; line[2] = 'd'; line += sizeof(BI_SETENV) - 1; for (name = line; isblank((unsigned char)*name); ++name) ; for (line = name; ,*line && !isblank((unsigned char)*line); ++line) ; if (*line) ,*line++ = '\0'; for (; isblank((unsigned char)*line); ++line) ; if (*line != '\0' && setenv(name, line, 1)) warn("setenv(%s, %s)", name, line); } } else if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) { if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) { /* only do it once! */ line[2] = 'd'; line[3] = 'i'; line[4] = 'd'; line += sizeof(BI_UNSETENV) - 1; for (name = line; isblank((unsigned char)*name); ++name) ; for (line = name; ,*line && !isblank((unsigned char)*line); ++line) ; if (*line) ,*line++ = '\0'; unsetenv(name); } } while (*line++) ; } } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_setenv][=auth_setenv=]] scans through =as->spool=, modifying the environment according to =BI_SETENV= and =BI_UNSETENV= instructions. ** auth_getvalue :PROPERTIES: :CUSTOM_ID: auth_getvalue :END: @@html: <details> <summary> @@ #+BEGIN_SRC c char *auth_getvalue(auth_session_t *as, char *what) #+END_SRC @@html: </summary> @@ #+begin_src c { char *line, *v, *value; int n, len; len = strlen(what); for (line = as->spool; line < as->spool + as->index;) { if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0) goto next; line += sizeof(BI_VALUE) - 1; if (!isblank((unsigned char)*line)) goto next; while (isblank((unsigned char)*++line)) ; if (strncmp(line, what, len) != 0 || !isblank((unsigned char)line[len])) goto next; line += len; while (isblank((unsigned char)*++line)) ; value = strdup(line); if (value == NULL) return (NULL); /* ,* XXX - There should be a more standardized ,* routine for doing this sort of thing. ,*/ for (line = v = value; *line; ++line) { if (*line == '\\') { switch (*++line) { case 'r': ,*v++ = '\r'; break; case 'n': ,*v++ = '\n'; break; case 't': ,*v++ = '\t'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': n = *line - '0'; if (isdigit((unsigned char)line[1])) { ++line; n <<= 3; n |= *line-'0'; } if (isdigit((unsigned char)line[1])) { ++line; n <<= 3; n |= *line-'0'; } break; default: ,*v++ = *line; break; } } else ,*v++ = *line; } ,*v = '\0'; return (value); next: while (*line++) ; } return (NULL); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_getvalue~2][=auth_getvalue=]] scans =as->spool= looking for lines beginning with =BI_VALUE=. It then checks if the next word is equal to =what=. When it finds the desired line, it duplicates the string, converts escape sequences in the value, and returns the newly created string. For convenience, the function [[https://man.openbsd.org/man3/authenticate.3#auth_mkvalue][=auth_mkvalue(3)=]] can be used inside of the authentication module to create and return appropriately escaped value strings. ** auth_getchallenge :PROPERTIES: :CUSTOM_ID: auth_getchallenge :END: The [[https://man.openbsd.org/auth_subr.3#auth_getchallenge][=auth_subr(3)=]] man page claims this function exists, but I can't find it anywhere in the source code. I suspect this is an error. * auth_open :PROPERTIES: :CUSTOM_ID: auth_open :END: @@html: <details> <summary> @@ #+begin_src c auth_session_t *auth_open(void) #+end_src @@html: </summary> @@ #+begin_src c { auth_session_t *as; if ((as = calloc(1, sizeof(auth_session_t))) != NULL) { as->service = defservice; as->fd = -1; } return (as); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_open][=auth_open=]] is used by several functions to create a new auth session. It allocates an [[#auth_session_t][=auth_session_t=]] struct on the heap, sets its default =service= to that defined by =LOGIN_DEFSERVICE= in =/include/login_cap.h=, which is currently ="login"=. #+begin_src c #define LOGIN_DEFSERVICE "login" #+end_src It then sets the =fd= field to =-1=, and returns the pointer. * auth_usercheck :PROPERTIES: :CUSTOM_ID: auth_usercheck :END: @@html: <details> <summary> @@ #+BEGIN_SRC c auth_session_t *auth_usercheck(char *name, char *style, char *type, char *password) #+END_SRC @@html: </summary> @@ #+begin_src c { char namebuf[LOGIN_NAME_MAX + 1 + NAME_MAX + 1]; char pwbuf[_PW_BUF_LEN]; auth_session_t *as; login_cap_t *lc; struct passwd pwstore, *pwd = NULL; char *slash; if (!_auth_validuser(name)) return (NULL); if (strlcpy(namebuf, name, sizeof(namebuf)) >= sizeof(namebuf)) return (NULL); name = namebuf; /* ,* Split up user:style names if we were not given a style ,*/ if (style == NULL && (style = strchr(name, ':')) != NULL) ,*style++ = '\0'; /* ,* Cope with user/instance. We are only using this to get ,* the class so it is okay if we strip a /root instance ,* The actual login script will pay attention to the instance. ,*/ getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd); if (pwd == NULL) { if ((slash = strchr(name, '/')) != NULL) { ,*slash = '\0'; getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd); ,*slash = '/'; } } if ((lc = login_getclass(pwd ? pwd->pw_class : NULL)) == NULL) return (NULL); if ((style = login_getstyle(lc, style, type)) == NULL) { login_close(lc); return (NULL); } if (password) { if ((as = auth_open()) == NULL) { login_close(lc); return (NULL); } auth_setitem(as, AUTHV_SERVICE, "response"); auth_setdata(as, "", 1); auth_setdata(as, password, strlen(password) + 1); explicit_bzero(password, strlen(password)); } else as = NULL; as = auth_verify(as, style, name, lc->lc_class, (char *)NULL); login_close(lc); return (as); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/man3/authenticate.3#auth_usercheck][=auth_usercheck=]] first checks that =*name= is a valid username. This means that it doesn't begin with a hyphen, had a non-zero length. If =*style= is =NULL=, it checks if =*name= is in the =user:style= format, and splits it accordingly. It then gets the user's password database entry through [[https://man.openbsd.org/man3/getpwnam.3#getpwnam_r][=getpwman_r(3)=]], which operates on the [[https://man.openbsd.org/passwd.5][=passwd(5)=]] database. After it uses that to retrieve the user's login class using [[https://man.openbsd.org/login_getclass#login_getclass][=login_getclass(3)=]], which returns a =login_cap_t=. Login classes are stored in the [[https://man.openbsd.org/man5/login.conf.5][=login.conf(5)=]] database. That struct is then passed into [[https://man.openbsd.org/login_getclass#login_getstyle][=login_getstyle(3)=]], which also received the =*style= and =*type=. If =*type= is =NULL=, it returns the first available login style for that class. If =*style= is specified, it is returned if available, otherwise =NULL= is returned, which causes =auth_usercheck= to return =NULL= as well. It then creates a pointer =as= of type [[#auth_session_t][=auth_session_t=]], and handles it differently based on whether =*password= is =NULL=. - If the password is a string, it creates a new session using [[#auth_open][=auth_open=]] and assigns it to =as=. It then sets the session =service= to ="response"=, and adds the =password= string to the session's =data=. #+BEGIN_SRC c auth_setitem(as, AUTHV_SERVICE, "response"); auth_setdata(as, "", 1); auth_setdata(as, password, strlen(password) + 1); #+END_SRC - If =*password= is =NULL=, it sets =as= to =NULL=. It then passes the =auth_session_t= pointer (=as=), =*name=, =*style=, login class (=lc->lc_class=), and a =NULL= char pointer to [[#auth_verify][=auth_verify=]]. Finally it returns the auth session pointer. #+begin_src c as = auth_verify(as, style, name, lc->lc_class, (char *)NULL); // [...] some cleanup return (as); #+end_src * auth_verify :PROPERTIES: :CUSTOM_ID: auth_verify :END: @@html: <details> <summary> @@ #+BEGIN_SRC c auth_session_t *auth_verify(auth_session_t *as, char *style, char *name, ...) #+END_SRC @@html: </summary> @@ #+begin_src c { va_list ap; char path[PATH_MAX]; if ((name == NULL || style == NULL) && as == NULL) return (NULL); if (as == NULL && (as = auth_open()) == NULL) return (NULL); auth_setstate(as, 0); if (style != NULL && auth_setitem(as, AUTHV_STYLE, style) < 0) return (as); if (name != NULL && auth_setitem(as, AUTHV_NAME, name) < 0) return (as); style = auth_getitem(as, AUTHV_STYLE); name = auth_getitem(as, AUTHV_NAME); if (!_auth_validuser(name)) return (as); snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style); va_start(ap, name); auth_set_va_list(as, ap); auth_call(as, path, auth_getitem(as, AUTHV_STYLE), "-s", auth_getitem(as, AUTHV_SERVICE), "--", name, (char *)NULL); va_end(ap); return (as); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/man3/authenticate.3#auth_verify][=auth_verify=]] is used as a frontend for [[#auth_call][=auth_call=]]. It creates an auth session using =auth_open= if =*as= is =NULL=. The =state= of the session is set to =0=. It sets the =name= and =style= of the session, if the =*style= and/or =*name= are non-=NULL=. After that it constructs the path of the authentication module, placing it in the variable =path=. It is constructed by combining =_PATH_AUTHPROG=, which is defined in =login_cap.h= as =/usr/libexec/auth/login_=, and the authentication style. For the case of auth style =passwd=, it would result in the path =/usr/libexec/auth/login_passwd=. #+begin_src c snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style); #+end_src It then copies its variable arguments to the auth session using [[#auth_set_va_list][=auth_set_va_list=]]. Then =auth_call= is called with the session struct, the path to the auth module, the auth style, the "-s" flag followed by the service (=login=, =challenge=, or =response=), a double dash, the user name, and a =NULL= character pointer. The return value of =auth_call= is ignored and a pointer to the auth session is returned immediately afterwards. #+BEGIN_SRC c va_start(ap, name); auth_set_va_list(as, ap); auth_call(as, path, auth_getitem(as, AUTHV_STYLE), "-s", auth_getitem(as, AUTHV_SERVICE), "--", name, (char *)NULL); va_end(ap); return (as); #+END_SRC * auth_call :PROPERTIES: :CUSTOM_ID: auth_call :END: @@html: <details> <summary> @@ #+BEGIN_SRC c int auth_call(auth_session_t *as, char *path, ...) #+END_SRC @@html: </summary> @@ #+begin_src c { char *line; struct authdata *data; struct authopts *opt; pid_t pid; int status; int okay; int pfd[2]; int argc; char *argv[64]; /* 64 args should be more than enough */ #define Nargc (sizeof(argv)/sizeof(argv[0])) va_start(as->ap0, path); argc = 0; if ((argv[argc] = _auth_next_arg(as)) != NULL) ++argc; if (as->fd != -1) { argv[argc++] = "-v"; argv[argc++] = "fd=4"; /* AUTH_FD, see below */ } /* XXX - fail if out of space in argv */ for (opt = as->optlist; opt != NULL; opt = opt->next) { if (argc < Nargc - 2) { argv[argc++] = "-v"; argv[argc++] = opt->opt; } else { syslog(LOG_ERR, "too many authentication options"); goto fail; } } while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as))) ++argc; if (argc >= Nargc - 1 && _auth_next_arg(as)) { if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) { va_end(as->ap0); explicit_bzero(&(as->ap0), sizeof(as->ap0)); } if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) { va_end(as->ap); explicit_bzero(&(as->ap), sizeof(as->ap)); } syslog(LOG_ERR, "too many arguments"); goto fail; } argv[argc] = NULL; if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) == -1) { syslog(LOG_ERR, "unable to create backchannel %m"); warnx("internal resource failure"); goto fail; } switch (pid = fork()) { case -1: syslog(LOG_ERR, "%s: %m", path); warnx("internal resource failure"); close(pfd[0]); close(pfd[1]); goto fail; case 0: #define COMM_FD 3 #define AUTH_FD 4 if (dup2(pfd[1], COMM_FD) == -1) err(1, "dup of backchannel"); if (as->fd != -1) { if (dup2(as->fd, AUTH_FD) == -1) err(1, "dup of auth fd"); closefrom(AUTH_FD + 1); } else closefrom(COMM_FD + 1); execve(path, argv, auth_environ); syslog(LOG_ERR, "%s: %m", path); err(1, "%s", path); default: close(pfd[1]); if (as->fd != -1) { close(as->fd); /* so child has only ref */ as->fd = -1; } while ((data = as->data) != NULL) { as->data = data->next; if (data->len > 0) { write(pfd[0], data->ptr, data->len); explicit_bzero(data->ptr, data->len); } free(data); } as->index = 0; _auth_spool(as, pfd[0]); close(pfd[0]); do { if (waitpid(pid, &status, 0) != -1) { if (!WIFEXITED(status)) goto fail; break; } /* ,* could get ECHILD if it was waited for by ,* another thread or from a signal handler ,*/ } while (errno == EINTR); } /* ,* Now scan the spooled data ,* It is easier to wait for all the data before starting ,* to scan it. ,*/ for (line = as->spool; line < as->spool + as->index;) { if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) { line += sizeof(BI_REJECT) - 1; if (!*line || *line == ' ' || *line == '\t') { while (*line == ' ' || *line == '\t') ++line; if (!strcasecmp(line, "silent")) { as->state = AUTH_SILENT; break; } if (!strcasecmp(line, "challenge")) { as->state = AUTH_CHALLENGE; break; } if (!strcasecmp(line, "expired")) { as->state = AUTH_EXPIRED; break; } if (!strcasecmp(line, "pwexpired")) { as->state = AUTH_PWEXPIRED; break; } } break; } else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) { line += sizeof(BI_AUTH) - 1; if (!*line || *line == ' ' || *line == '\t') { while (*line == ' ' || *line == '\t') ++line; if (*line == '\0') as->state |= AUTH_OKAY; else if (!strcasecmp(line, "root")) as->state |= AUTH_ROOTOKAY; else if (!strcasecmp(line, "secure")) as->state |= AUTH_SECURE; } } else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) { line += sizeof(BI_REMOVE) - 1; while (*line == ' ' || *line == '\t') ++line; if (*line) _add_rmlist(as, line); } while (*line++) ; } if (WEXITSTATUS(status)) as->state &= ~AUTH_ALLOW; okay = as->state & AUTH_ALLOW; if (!okay) auth_clrenv(as); if (0) { fail: auth_clrenv(as); as->state = 0; okay = -1; } while ((data = as->data) != NULL) { as->data = data->next; free(data); } if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) { va_end(as->ap0); explicit_bzero(&(as->ap0), sizeof(as->ap0)); } if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) { va_end(as->ap); explicit_bzero(&(as->ap), sizeof(as->ap)); } return (okay); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_call~2][=auth_call=]] is responsible for setting up the environment, calling the modules, and communicating with them. An array of char pointers called =argv= is allocated to hold the arguments for the auth module. #+BEGIN_SRC c char *argv[64]; /* 64 args should be more than enough */ #+END_SRC First, the variable arguments are placed in =as->ap0=. [[#_auth_next_arg][=_auth_next_arg=]] is called once, with the result being set as the first element in =argv=. If =as->fd= is set, adds =-v= and =fd=4= to =argv=. Then it loops through the =optlist= and appends =-v= followed the option for each of them. After that the rest of the arguments are retrieved from =_auth_next_arg= and added to the end of =argv=. Finally a =NULL= is added to the end of =argv=. Next a socket pair of type =PF_LOCAL, SOCK_STREAM= is created. This is called the "back channel", and is used to communicate with the authentication module. The process then calls [[https://man.openbsd.org/man2/fork.2][=fork(2)=]]. Here two constants are set for the back channel and optional authentication file descriptors. #+begin_src c #define COMM_FD 3 #define AUTH_FD 4 #+end_src In the child process, the back channel is set to file descriptor 3, or =COMM_FD= using =dup2(3)=. If =as->fd=, is not =-1=, it is set to file descriptor 4, or =AUTH_FD=, also using [[https://man.openbsd.org/man2/dup.2#dup2][=dup2(3)=]]. The remainder of the file descriptors are closed using [[https://man.openbsd.org/man2/closefrom.2][=closefrom(2)=]] by calling either =closefrom(COMM_FD + 1)= or =closefrom(AUTH_FD + 1)=, depending on whether or not =AUTH_FD= is used. The child process then executes the module. #+begin_src c execve(path, argv, auth_environ); #+end_src =auth_environ= is defined at the top of the file as a very minimal environment. #+BEGIN_SRC c static char *auth_environ[] = { "PATH=" _PATH_DEFPATH, "SHELL=" _PATH_BSHELL, NULL, }; #+END_SRC Where both constants are defined in =/include/paths.h=. #+BEGIN_SRC c #define _PATH_DEFPATH "/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin" #define _PATH_BSHELL "/bin/sh" #+END_SRC In the parent process, the child's end of the back channel is closed, and so is the parent's copy of =as->fd= if it exists. The data from =as->data= is then written to the back channel sequentially, zeroed, and freed. Next =as->index= is set to =0=. The response from the authentication module is then read from the back channel and put into =as->spool= with an optional received file descriptor placed in =as->fd=, using [[#_auth_spool][=_auth_spool=]]. #+begin_src c _auth_spool(as, pfd[0]); #+end_src Once the back channel data has finished spooling, it is scanned for key words defined in =login_cap.h=. #+BEGIN_SRC c #define BI_AUTH "authorize" /* Accepted authentication */ #define BI_REJECT "reject" /* Rejected authentication */ #define BI_CHALLENGE "reject challenge" /* Reject with a challenge */ #define BI_SILENT "reject silent" /* Reject silently */ #define BI_REMOVE "remove" /* remove file on error */ #define BI_ROOTOKAY "authorize root" /* root authenticated */ #define BI_SECURE "authorize secure" /* okay on non-secure line */ #define BI_SETENV "setenv" /* set environment variable */ #define BI_UNSETENV "unsetenv" /* unset environment variable */ #define BI_VALUE "value" /* set local variable */ #define BI_EXPIRED "reject expired" /* account expired */ #define BI_PWEXPIRED "reject pwexpired" /* password expired */ #define BI_FDPASS "fd" /* child is passing an fd */ #+END_SRC The [[https://man.openbsd.org/login.conf][=login.conf(5)=]] man page once again goes into greater detail on these values. #+BEGIN_SRC authorize The user has been authorized. authorize secure The user has been authorized and root should be allowed to login even if this is not a secure terminal. This should only be sent by authentication styles that are secure over insecure lines. reject Authorization is rejected. This overrides any indication that the user was authorized (though one would question the wisdom in sending both a reject and an authorize command). reject challenge Authorization was rejected and a challenge has been made available via the value challenge. reject silent Authorization is rejected, but no error messages should be generated. remove file If the login session fails for any reason, remove file before termination. setenv name value If the login session succeeds, the environment variable name should be set to the specified value. unsetenv name If the login session succeeds, the environment variable name should be removed. value name value Set the internal variable name to the specified value. The value should only contain printable characters. Several \ sequences may be used to introduce non printing characters. These are: \n A newline. \r A carriage return. \t A tab. \xxx The character represented by the octal value xxx. The value may be one, two, or three octal digits. \c The string is replaced by the value of c. This allows quoting an initial space or the \ character itself. The following values are currently defined: challenge See section on challenges below. errormsg If set, the value is the reason authentication failed. The calling program may choose to display this when rejecting the user, but display is not required. #+END_SRC The scanner is looking for lines that begin with =BI_AUTH=, =BI_REJECT=, or =BI_REMOVE=. Here =as->state= is set according to the values defined on =login_cap.h=. #+BEGIN_SRC c /* * bits which can be returned by authenticate()/auth_scan() */ #define AUTH_OKAY 0x01 /* user authenticated */ #define AUTH_ROOTOKAY 0x02 /* authenticated as root */ #define AUTH_SECURE 0x04 /* secure login */ #define AUTH_SILENT 0x08 /* silent rejection */ #define AUTH_CHALLENGE 0x10 /* a challenge was given */ #define AUTH_EXPIRED 0x20 /* account expired */ #define AUTH_PWEXPIRED 0x40 /* password expired */ #+END_SRC If an authorization is received (any line starting with =BI_AUTH=), the appropriate state is bitwise =or=-ed onto =as->state=, allowing multiple authorizations, such as a case where both =BI_ROOTOKAY=, resulting in a state of =AUTH_ROOTOKAY=, and =BI_SECURE=, resulting in a state of =AUTH_SECURE= are both sent. If a rejection is received (any line starting with =BI_REJECT=), =as->state= is set according to the rejection, and the scanning is stopped. Rejections are final and take precedence over any authorizations. For any lines beginning with =BI_REMOVE=, the file names after the key word are sent to [[#_add_rmlist][=_add_rmlist=]]. #+begin_src c _add_rmlist(as, line); #+end_src After scanning is complete, the exit status of the process is checked. A non-zero exit status means the request will get denied. An =okay= value is then defined by masking the state with the value =AUTH_ALLOW=. #+begin_src c okay = as->state & AUTH_ALLOW; #+end_src =AUTH_ALLOW= is defined in =login_cap.h=. #+begin_src c #define AUTH_ALLOW (AUTH_OKAY | AUTH_ROOTOKAY | AUTH_SECURE) #+end_src If the status results in a rejection, [[#auth_clrenv][=auth_clrenv=]] is called with =as=. This removes any requests the login script has made to set environment variables from =as->spool=. =okay= is then returned to the caller. ** _auth_next_arg :PROPERTIES: :CUSTOM_ID: _auth_next_arg :END: @@html: <details> <summary> @@ #+BEGIN_SRC c static char *_auth_next_arg(auth_session_t *as) #+END_SRC @@html: </summary> @@ #+begin_src c { char *arg; if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) { if ((arg = va_arg(as->ap0, char *)) != NULL) return (arg); va_end(as->ap0); explicit_bzero(&(as->ap0), sizeof(as->ap0)); } if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) { if ((arg = va_arg(as->ap, char *)) != NULL) return (arg); va_end(as->ap); explicit_bzero(&(as->ap), sizeof(as->ap)); } return (NULL); } #+end_src @@html: </details> @@ Loops through =as->ap0= then =as->ap=, returning one argument per call. Calls =va_end= on each list once it finishes with them, then [[https://man.openbsd.org/man3/bzero.3#explicit_bzero][=explicit_bzero(3)=]]'s them. Finally when it's gone through both lists, returns =NULL= ** _auth_spool :PROPERTIES: :CUSTOM_ID: _auth_spool :END: @@html: <details> <summary> @@ #+begin_src c static void _auth_spool(auth_session_t *as, int fd) #+end_src @@html: </summary> @@ #+begin_src c { ssize_t r; char *b, *s; for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) { r = read(fd, as->spool + as->index, sizeof(as->spool) - as->index); if (r <= 0) { as->spool[as->index] = '\0'; return; } b = as->spool + as->index; as->index += r; /* ,* Convert newlines into NULs to allow easy scanning of the ,* file and receive an fd if there is a BI_FDPASS message. ,* XXX - checking for BI_FDPASS here is annoying but ,* we need to avoid the read() slurping in control data. ,*/ while (r-- > 0) { if (*b++ == '\n') { b[-1] = '\0'; if (strcasecmp(s, BI_FDPASS) == 0) _recv_fd(as, fd); s = b; } } } syslog(LOG_ERR, "Overflowed backchannel spool buffer"); errx(1, "System error in authentication program"); } #+end_src @@html: </details> @@ =_auth_spool='s job is to read data from =fd= and place it in =as->spool=, and to update =as->index= with the length of the data on the spool. While spooling it converts newlines to =NUL='s in order to parse the output more easily. It also handles any file descriptors passed through the back channel by sending them to [[#_recv_fd][=_recv_fd=]]. #+begin_src c // [...] if (strcasecmp(s, BI_FDPASS) == 0) _recv_fd(as, fd); #+end_src ** _recv_fd :PROPERTIES: :CUSTOM_ID: _recv_fd :END: @@html: <details> <summary> @@ #+begin_src c static void _recv_fd(auth_session_t *as, int fd) #+end_src @@html: </summary> @@ #+begin_src c { struct msghdr msg; struct cmsghdr *cmp; union { struct cmsghdr hdr; char buf[CMSG_SPACE(sizeof(int))]; } cmsgbuf; memset(&msg, 0, sizeof(msg)); msg.msg_control = &cmsgbuf.buf; msg.msg_controllen = sizeof(cmsgbuf.buf); if (recvmsg(fd, &msg, 0) == -1) syslog(LOG_ERR, "recvmsg: %m"); else if (msg.msg_flags & MSG_TRUNC) syslog(LOG_ERR, "message truncated"); else if (msg.msg_flags & MSG_CTRUNC) syslog(LOG_ERR, "control message truncated"); else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL) syslog(LOG_ERR, "missing control message"); else { if (cmp->cmsg_level != SOL_SOCKET) syslog(LOG_ERR, "unexpected cmsg_level %d", cmp->cmsg_level); else if (cmp->cmsg_type != SCM_RIGHTS) syslog(LOG_ERR, "unexpected cmsg_type %d", cmp->cmsg_type); else if (cmp->cmsg_len != CMSG_LEN(sizeof(int))) syslog(LOG_ERR, "bad cmsg_len %d", cmp->cmsg_len); else { if (as->fd != -1) close(as->fd); as->fd = *(int *)CMSG_DATA(cmp); } } } #+end_src @@html: </details> @@ =_recv_fd= reads control messages, also called ancillary data, from =fd= and tries to receive a file descriptor. It does this using the [[https://man.openbsd.org/CMSG_DATA.3][control message API]]. If it receives one and =as->fd= is equal to =-1=, it sets it to the received file descriptor. Otherwise it closes the received file descriptor. ** _add_rmlist :PROPERTIES: :CUSTOM_ID: _add_rmlist :END: @@html: <details> <summary> @@ #+begin_src c static void _add_rmlist(auth_session_t *as, char *file) #+end_src @@html: </summary> @@ #+begin_src c { struct rmfiles *rm; size_t i = strlen(file) + 1; // XXX should rangecheck i since we are about to add? if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) { syslog(LOG_ERR, "Failed to allocate rmfiles: %m"); return; } rm->file = (char *)(rm + 1); rm->next = as->rmlist; strlcpy(rm->file, file, i); as->rmlist = rm; } #+end_src @@html: </details> @@ =_add_rmlist= is used to add to the list of files to be removed after authentication is complete A =rmfiles= struct is allocated and appended to the end of the =as->rmlist= linked list. * auth_close :PROPERTIES: :CUSTOM_ID: auth_close :END: @@html: <details> <summary> @@ #+begin_src c int auth_close(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { struct rmfiles *rm; struct authopts *opt; struct authdata *data; int s; /* ,* Save our return value ,*/ s = as->state & AUTH_ALLOW; if (s == 0) as->index = 0; auth_setenv(as); /* ,* Clean out the rmlist and remove specified files if the ,* authentication failed ,*/ while ((rm = as->rmlist) != NULL) { as->rmlist = rm->next; if (s == 0) unlink(rm->file); free(rm); } /* ,* Clean out the opt list ,*/ while ((opt = as->optlist) != NULL) { as->optlist = opt->next; free(opt); } /* ,* Clean out data ,*/ while ((data = as->data) != NULL) { if (as->data->len) explicit_bzero(as->data->ptr, as->data->len); as->data = data->next; free(data); } if (as->pwd != NULL) { explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd)); free(as->pwd); as->pwd = NULL; } /* ,* Clean up random variables ,*/ if (as->service && as->service != defservice) free(as->service); free(as->challenge); free(as->class); free(as->style); free(as->name); free(as); return (s); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_close][=auth_close=]] is responsible for setting the environment variables, removing any files requested by the authentication module, and freeing =as=. First it saves the allow state of =as->state= in a variable =s=. #+begin_src c s = as->state & AUTH_ALLOW; #+end_src If =s= is equal to =0= (failure), =as->index= is set to =0=, truncating =as->spool= so that no further functions will be able to read from it. It then modifies the environment using [[#auth_setenv][=auth_setenv=]] #+begin_src c auth_setenv(as); #+end_src All =as->rmlist= structs are checked. If =s= is equal to =0=, the files are deleted. All =rmlist= structs are then freed. All =as->optlist= structs are freed. All =as->data= structs are [[https://man.openbsd.org/man3/bzero.3#explicit_bzero][=explicit_bzero(3)=]]'d and then freed. =as->pwd= is =explicit_bzero='d and freed. All remaining structs referenced by =as= are freed. =as= is freed. =s= is returned. * auth_userchallenge :PROPERTIES: :CUSTOM_ID: auth_userchallenge :END: @@html: <details> <summary> @@ #+begin_src c auth_session_t *auth_userchallenge(char *name, char *style, char *type, char **challengep) #+end_src @@html: </summary> @@ #+begin_src c { char namebuf[LOGIN_NAME_MAX + 1 + NAME_MAX + 1]; auth_session_t *as; login_cap_t *lc; struct passwd pwstore, *pwd = NULL; char *slash, pwbuf[_PW_BUF_LEN]; if (!_auth_validuser(name)) return (NULL); if (strlen(name) >= sizeof(namebuf)) return (NULL); strlcpy(namebuf, name, sizeof namebuf); name = namebuf; /* ,* Split up user:style names if we were not given a style ,*/ if (style == NULL && (style = strchr(name, ':')) != NULL) ,*style++ = '\0'; /* ,* Cope with user/instance. We are only using this to get ,* the class so it is okay if we strip a /root instance ,* The actual login script will pay attention to the instance. ,*/ getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd); if (pwd == NULL) { if ((slash = strchr(name, '/')) != NULL) { ,*slash = '\0'; getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd); ,*slash = '/'; } } if ((lc = login_getclass(pwd ? pwd->pw_class : NULL)) == NULL) return (NULL); if ((style = login_getstyle(lc, style, type)) == NULL || (as = auth_open()) == NULL) { login_close(lc); return (NULL); } if (auth_setitem(as, AUTHV_STYLE, style) < 0 || auth_setitem(as, AUTHV_NAME, name) < 0 || auth_setitem(as, AUTHV_CLASS, lc->lc_class) < 0) { auth_close(as); login_close(lc); return (NULL); } login_close(lc); ,*challengep = auth_challenge(as); return (as); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/man3/authenticate.3#auth_userchallenge][=auth_userchallenge=]] is used when the authentication style requires that the user be presented with a challenge, but the user cannot be directly interacted with over the terminal. As an example, this might be used in cases where the user is using S/KEY authentication over SSH. A fair portion of this function is very similar to [[#auth_usercheck][=auth_usercheck=]]. Instead of having a password argument however, it has a pointer to string, which is used to return the challenge to the calling function. It first checks that =*name= is a valid username. This means that it doesn't begin with a hyphen, had a non-zero length. If =*style= is =NULL=, it checks if =*name= is in the =user:style= format, and splits it accordingly. It then gets the user's password database entry through [[https://man.openbsd.org/man3/getpwnam.3#getpwnam_r][=getpwman_r(3)=]], which operates on the [[https://man.openbsd.org/passwd.5][=passwd(5)=]] database. It then uses that to retrieve the user's login class using [[https://man.openbsd.org/login_getclass#login_getclass][=login_getclass(3)=]], which returns a =login_cap_t=. Login classes are stored in the [[https://man.openbsd.org/man5/login.conf.5][=login.conf(5)=]] database. That struct is then passed into [[https://man.openbsd.org/login_getclass#login_getstyle][=login_getstyle(3)=]], which also received the =*style= and =*type=. If =*type= is =NULL=, it returns the first available login style for that class. If =*style= is specified, it is returned if available, otherwise =NULL= is returned, which causes =auth_userchallenge= to return =NULL= as well. This is where =auth_userchallenge= and [[#auth_usercheck][=auth_usercheck=]] begin to diverge. It creates a new auth session using [[#auth_open][=auth_open=]] as variable =as=. The =style=, =name= and =class= properties of the session are then set using [[#auth_setitem][=auth_setitem=]]. It then calls [[#auth_challenge][=auth_challenge=]] with =as= as the argument. The return value from that call is used to set =*challengep=, and =as= is returned. #+begin_src c *challengep = auth_challenge(as); return (as); #+end_src * auth_challenge :PROPERTIES: :CUSTOM_ID: auth_challenge :END: @@html: <details> <summary> @@ #+begin_src c char *auth_challenge(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { char path[PATH_MAX]; int len; if (as == NULL || as->style == NULL || as->name == NULL || !_auth_validuser(as->name)) return (NULL); len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style); if (len < 0 || len >= sizeof(path)) return (NULL); as->state = 0; free(as->challenge); as->challenge = NULL; auth_call(as, path, as->style, "-s", "challenge", "--", as->name, as->class, (char *)NULL); if (as->state & AUTH_CHALLENGE) as->challenge = auth_getvalue(as, "challenge"); as->state = 0; as->index = 0; /* toss our data */ return (as->challenge); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_challenge][=auth_challenge=]], much like [[#auth_verify][=auth_verify=]] is a function that acts as a front-end for [[#auth_call][=auth_call=]], except used specifically for challenges. First the session =as= is checked. If it's =NULL=, or =as->style= is =NULL=, =as->name= is =NULL=, or if the username begins with a hyphen, or has a length of zero, the function returns =NULL=. Then the path to the auth module is created. #+begin_src c snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style); #+end_src =as->state= and =as->challenge= are then reset, in case they were already set. Then [[#auth_call][=auth_call=]] is called, with the challenge style set. #+begin_src c auth_call(as, path, as->style, "-s", "challenge", "--", as->name, as->class, (char *)NULL); #+end_src =as->state= is checked for the =AUTH_CHALLENGE= bit, and if it's present, the challenge is extracted from the back channel output, and used to set =as->challenge=. #+begin_src c if (as->state & AUTH_CHALLENGE) as->challenge = auth_getvalue(as, "challenge"); #+end_src =as->state= and =as->index= are then set to zero, discarding the data. =as->challenge= is then returned. * auth_userresponse :PROPERTIES: :CUSTOM_ID: auth_userresponse :END: @@html: <details> <summary> @@ #+begin_src c int auth_userresponse(auth_session_t *as, char *response, int more) #+end_src @@html: </summary> @@ #+begin_src c { char path[PATH_MAX]; char *style, *name, *challenge, *class; int len; if (as == NULL) return (0); auth_setstate(as, 0); if ((style = auth_getitem(as, AUTHV_STYLE)) == NULL || (name = auth_getitem(as, AUTHV_NAME)) == NULL || !_auth_validuser(name)) { if (more == 0) return (auth_close(as)); return(0); } len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style); if (len < 0 || len >= sizeof(path)) { if (more == 0) return (auth_close(as)); return (0); } challenge = auth_getitem(as, AUTHV_CHALLENGE); class = auth_getitem(as, AUTHV_CLASS); if (challenge) auth_setdata(as, challenge, strlen(challenge) + 1); else auth_setdata(as, "", 1); if (response) { auth_setdata(as, response, strlen(response) + 1); explicit_bzero(response, strlen(response)); } else auth_setdata(as, "", 1); auth_call(as, path, style, "-s", "response", "--", name, class, (char *)NULL); /* * If they authenticated then make sure they did not expire */ if (auth_getstate(as) & AUTH_ALLOW) auth_check_expire(as); if (more == 0) return (auth_close(as)); return (auth_getstate(as) & AUTH_ALLOW); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/man3/authenticate.3#auth_userresponse][=auth_userresponse=]] is used to pass the user's response from [[#auth_userchallenge][=auth_userchallenge=]] back to the authentication module. Similar to =auth_userchallenge=, it is also a front-end for [[#auth_call][=auth_call=]]. If =as= is =NULL=, =0= is returned. The state of =as= is then set to =0=. #+begin_src c auth_setstate(as, 0); #+end_src =as= is then checked to ensure all the required items are set. Then it checks if =as->style= or =as->name= are =NULL=, or if the username is invalid using [[#_auth_validuser][=auth_validuser=]]. If any of those checks fail, and =more= is equal to =0=, then the session is closed using [[#auth_close][=auth_close=]], and the return value of that returned. Otherwise =0= is returned. Then the path to the [[#modules][auth module]] is created. The challenge and class of the session are extracted and stored in variables =challenge= and =class= respectively. If =challenge= contains data, its contents are added to the =as->data= spool, otherwise an empty string is added to the spool. If =response= contains data, it is added to the data spool as well, and then =respose= is =explicit_bzero='d. Otherwise an empty string is added to the data spool. Next [[#auth_call][=auth_call=]] is used to call the auth module with service type =response=. #+begin_src c auth_call(as, path, style, "-s", "response", "--", name, class, (char *)NULL); #+end_src If the request is allowed, it's checked to make sure it's not expired using [[#auth_check_expire][=auth_check_expire=]]. If =more= is equal to =0=, the session is closed using [[#auth_close][=auth_close=]]. The allow state of the session is then returned. #+begin_src c return (auth_getstate(as) & AUTH_ALLOW); #+end_src * auth_approval :PROPERTIES: :CUSTOM_ID: auth_approval :END: @@html: <details> <summary> @@ #+begin_src c int auth_approval(auth_session_t *as, login_cap_t *lc, char *name, char *type) #+end_src @@html: </summary> @@ #+begin_src c { int close_on_exit, close_lc_on_exit, len; struct passwd pwstore, *pwd; char *approve, *s, path[PATH_MAX], pwbuf[_PW_BUF_LEN]; pwd = NULL; close_on_exit = as == NULL; close_lc_on_exit = lc == NULL; if (as != NULL && name == NULL) name = auth_getitem(as, AUTHV_NAME); if (as != NULL) pwd = auth_getpwd(as); if (pwd == NULL) { if (name != NULL) { if (!_auth_validuser(name)) { warnx("cannot approve who we don't recognize"); return (0); } getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd); } else { getpwuid_r(getuid(), &pwstore, pwbuf, sizeof(pwbuf), &pwd); if (pwd == NULL) { syslog(LOG_ERR, "no such user id %u", getuid()); warnx("cannot approve who we don't recognize"); return (0); } name = pwd->pw_name; } } if (name == NULL) name = pwd->pw_name; if (lc == NULL) { if (strlen(name) >= PATH_MAX) { syslog(LOG_ERR, "username to login %.*s...", PATH_MAX, name); warnx("username too long"); return (0); } if (pwd == NULL && (approve = strchr(name, '.')) != NULL) { strlcpy(path, name, sizeof path); path[approve - name] = '\0'; getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pwd); } lc = login_getclass(pwd ? pwd->pw_class : NULL); if (lc == NULL) { warnx("unable to classify user"); return (0); } } if (!type) type = LOGIN_DEFSERVICE; else { if (strncmp(type, "approve-", 8) == 0) type += 8; len = snprintf(path, sizeof(path), "approve-%s", type); if (len < 0 || len >= sizeof(path)) { if (close_lc_on_exit) login_close(lc); syslog(LOG_ERR, "approval path too long %.*s...", PATH_MAX, type); warnx("approval script path too long"); return (0); } } if ((approve = login_getcapstr(lc, s = path, NULL, NULL)) == NULL) approve = login_getcapstr(lc, s = "approve", NULL, NULL); if (approve && approve[0] != '/') { if (close_lc_on_exit) login_close(lc); syslog(LOG_ERR, "Invalid %s script: %s", s, approve); warnx("invalid path to approval script"); free(approve); return (0); } if (as == NULL && (as = auth_open()) == NULL) { if (close_lc_on_exit) login_close(lc); syslog(LOG_ERR, "%m"); warn(NULL); free(approve); return (0); } auth_setstate(as, AUTH_OKAY); if (auth_setitem(as, AUTHV_NAME, name) < 0) { syslog(LOG_ERR, "%m"); warn(NULL); goto out; } if (auth_check_expire(as) < 0) /* is this account expired */ goto out; if (_auth_checknologin(lc, auth_getitem(as, AUTHV_INTERACTIVE) != NULL)) { auth_setstate(as, (auth_getstate(as) & ~AUTH_ALLOW)); goto out; } if (login_getcapbool(lc, "requirehome", 0) && pwd && pwd->pw_dir && pwd->pw_dir[0]) { struct stat sb; if (stat(pwd->pw_dir, &sb) == -1 || !S_ISDIR(sb.st_mode) || (pwd->pw_uid && sb.st_uid == pwd->pw_uid && (sb.st_mode & S_IXUSR) == 0)) { auth_setstate(as, (auth_getstate(as) & ~AUTH_ALLOW)); goto out; } } if (approve) auth_call(as, approve, strrchr(approve, '/') + 1, "--", name, lc->lc_class, type, (char *)NULL); out: free(approve); if (close_lc_on_exit) login_close(lc); if (close_on_exit) return (auth_close(as)); return (auth_getstate(as) & AUTH_ALLOW); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/man3/authenticate.3#auth_approval][=auth_approval=]] is used to check a user against the [[#approval][approval script]] for service =type=. It is a front end for [[#auth_call][=auth_call=]]. Approval script types all begin with =approval-=. Before running the scripts, first the validity of the account is checked. This is done first using [[#auth_check_expired][=auth_check_expired=]], then [[#_auth_checknologin][=_auth_checknologin=]], and finally [[https://man.openbsd.org/login_getcapbool#login_getcapbool][=login_getcapbool=]] to ensure the user has a home directory if one is required by their login class. If =type= doesn't begin with =approval-= it will be prepended internally. if =as= is =NULL=, an auth session will be created and destroyed inside the function. If =lc= is =NULL=, it will be retrieved internally by looking up =name=. If =type= is =NULL=, the default of =LOGIN_DEFSERVICE= is used. This is defined in =login_cap.h= as =login=. This should call the default =approval= script, according to the [[https://man.openbsd.org/login.conf#CAPABILITIES][=CAPABILITIES=]] section of the =login.conf= man page. It returns either =0= for disapproval, or non-zero for approval. * auth_check_expire :PROPERTIES: :CUSTOM_ID: auth_check_expire :END: @@html: <details> <summary> @@ #+begin_src c quad_t auth_check_expire(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) { as->state &= ~AUTH_ALLOW; as->state |= AUTH_EXPIRED; /* XXX */ return (-1); } if (as->pwd == NULL) return (0); if (as->pwd && (quad_t)as->pwd->pw_expire != 0) { if (as->now.tv_sec == 0) WRAP(gettimeofday)(&as->now, NULL); if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) { as->state &= ~AUTH_ALLOW; as->state |= AUTH_EXPIRED; } if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire) return (-1); return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec); } return (0); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_check_expire][=auth_check_expire=]] is used to check if the account used for a session is expired. If an account is valid, it returns =0=. Otherwise it returns a negative number representing the number of seconds elapsed since the account expired. If there's no account associated with the session, it will return =-1=. It first checks if =as->pwd= is set, and if it isn't it tries to set it using [[#auth_setpwd][=auth_setpwd=]]. If both of those fail, then it returns =-1= and removes the =AUTH_ALLOW= bitmask from =as->state=, and adds the bitmask for =AUTH_EXPIRED=. Interestingly, if there's an account name associated with the session but it doesn't exist on the system, this function will still return =0= instead of =-1=. * auth_check_change :PROPERTIES: :CUSTOM_ID: auth_check_change :END: @@html: <details> <summary> @@ #+begin_src c quad_t auth_check_change(auth_session_t *as) #+end_src @@html: </summary> @@ #+begin_src c { if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) { as->state &= ~AUTH_ALLOW; as->state |= AUTH_PWEXPIRED; /* XXX */ return (-1); } if (as->pwd == NULL) return (0); if (as->pwd && (quad_t)as->pwd->pw_change) { if (as->now.tv_sec == 0) WRAP(gettimeofday)(&as->now, NULL); if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) { as->state &= ~AUTH_ALLOW; as->state |= AUTH_PWEXPIRED; } if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change) return (-1); return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec); } return (0); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/auth_subr.3#auth_check_change][=auth_check_change=]] is used to check if the password associated with an account is expired. If the password isn't expired, it returns =0=. Otherwise it returns a negative number representing the number of seconds elapsed since the password expired. If there's no account associated with the session, it will return =-1=. It operates very similarly to [[#auth_check_expire][=auth_check_expire=]]. * auth_checknologin :PROPERTIES: :CUSTOM_ID: auth_checknologin :END: @@html: <details> <summary> @@ #+begin_src c void auth_checknologin(login_cap_t *lc) #+end_src @@html: </summary> @@ #+begin_src c { if (_auth_checknologin(lc, 1)) exit(1); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/authenticate.3#auth_checknologin][=auth_checknologin=]] is a simple wrapper around the internal [[#_auth_checknologin][=_auth_checknologin=]]. If the user is now allowed to login, it prints the reason and calls =exit(1)=. * auth_cat :PROPERTIES: :CUSTOM_ID: auth_cat :END: @@html: <details> <summary> @@ #+begin_src c int auth_cat(char *file) #+end_src @@html: </summary> @@ #+begin_src c { int fd, nchars; char tbuf[8192]; if ((fd = open(file, O_RDONLY, 0)) == -1) return (0); while ((nchars = read(fd, tbuf, sizeof(tbuf))) > 0) (void)write(fileno(stdout), tbuf, nchars); (void)close(fd); return (1); } #+end_src @@html: </details> @@ [[https://man.openbsd.org/man3/authenticate.3#auth_cat][=auth_cat=]] is a helper function that will write the contents of a =file= to =stdout=. It returns =0= on failure or =1= on success. * _auth_validuser :PROPERTIES: :CUSTOM_ID: _auth_validuser :END: @@html: <details> <summary> @@ #+begin_src c int _auth_validuser(const char *name) #+end_src @@html: </summary> @@ #+begin_src c { /* User name must be specified and may not start with a '-'. */ if (*name == '\0' || *name == '-') { syslog(LOG_ERR, "invalid user name %s", name); return 0; } return 1; } #+end_src @@html: </details> @@ =_auth_validuser= is a small helper function used to check if a username passes some very basic validity criteria. Those being that it must not be an empty sting, and that it doesn't start with a hyphen. If a username is invalid, it is logged in the syslog. * _auth_checknologin :PROPERTIES: :CUSTOM_ID: _auth_checknologin :END: @@html: <details> <summary> @@ #+begin_src c static int _auth_checknologin(login_cap_t *lc, int print) #+end_src @@html: </summary> @@ #+begin_src c { struct stat sb; char *nologin; int mustfree; if (login_getcapbool(lc, "ignorenologin", 0)) return (0); /* ,* If we fail to get the nologin file due to a database error, ,* assume there should have been one... ,*/ nologin = login_getcapstr(lc, "nologin", "", NULL); mustfree = nologin && *nologin != '\0'; if (nologin == NULL) goto print_nologin; /* First try the nologin file specified in login.conf. */ if (*nologin != '\0' && stat(nologin, &sb) == 0) goto print_nologin; if (mustfree) { free(nologin); mustfree = 0; } /* If that doesn't exist try _PATH_NOLOGIN. */ if (stat(_PATH_NOLOGIN, &sb) == 0) { nologin = _PATH_NOLOGIN; goto print_nologin; } /* Couldn't stat any nologin files, must be OK to login. */ return (0); print_nologin: if (print) { if (!nologin || *nologin == '\0' || auth_cat(nologin) == 0) { puts("Logins are not allowed at this time."); fflush(stdout); } } if (mustfree) free(nologin); return (-1); } #+end_src @@html: </details> @@ =_auth_checknologin= is a helper function in =authenticate.c=. It is used to check the =nologin= status of the account. If =print= is non-zero, it will print the reason for the failure, and print the contents of the nologin file using [[#auth_cat][=auth_cat=]]. It returns =0= if the user is allowed to login, and =-1= otherwise. * Notes https://web.archive.org/web/20170327150148/http://www.penzin.net/bsdauth/ - In the man page for [[https://man.openbsd.org/auth_subr.3#auth_call][=auth_call=]] it says #+begin_src text path The full path name of the login script to run. The call will fail if path does not pass the requirements of the secure_path(3) function. #+end_src However I don't see this enforced anywhere, I even wrote a small test script to prove it. #+CAPTION: =authfail.c= #+begin_src c #include <sys/types.h> #include <login_cap.h> #include <bsd_auth.h> #include <stdio.h> int main(void) { auth_session_t *as; as = auth_open(); auth_call(as, "/home/dante/auth_tests/authtest/test", "hello", NULL); auth_close(as); } #+end_src Changing ="/home/dante/auth_tests/authtest/test"= to the location of the =test= binary. #+CAPTION: =test.c= #+begin_src c #include <stdio.h> int main(void) { printf("Hello! I don't have a secure path!\n"); return 0; } #+end_src #+CAPTION: =Makefile= #+begin_src makefile CFLAGS = -Wall -Wextra run: authfail test ./authfail authfail: authfail.c $(CC) -o $@ $(CFLAGS) $< test: test.c $(CC) -o $@ $(CFLAGS) $< #+end_src Which results in the following: #+begin_src text $ pwd && ls -l && make /home/dante/auth_tests/authtest total 12 -rw-r--r-- 1 dante dante 143 May 30 19:20 Makefile -rw-r--r-- 1 dante dante 248 May 29 19:30 authfail.c -rw-r--r-- 1 dante dante 115 May 29 19:22 test.c cc -o authfail -Wall -Wextra authfail.c cc -o test -Wall -Wextra test.c ./authfail Hello! I don't have a secure path! #+end_src - The manpage also says the path is limited to =/bin/= and =/usr/bin=, which is also not the case. - The man page describes the interface for =auth_getitem= is in the format of =AUTH_<item>=, but in reality it is =AUTHV_<item>=. # Ask jcs about the file descriptor situation, I don't understand it # after reading both the man page and source. - The [[#auth_getchallenge][=auth_getchallenge=]] function in the [[https://man.openbsd.org/auth_subr.3#auth_getchallenge][=auth_subr(3)=]] man page doesn't seem to exist in the source code. * Graph [[file:graph.svg]] * Copyright :PROPERTIES: :CUSTOM_ID: copyright :END: @@html: <details> <summary> @@ *Click here to expand copyright notices* @@html: </summary> @@ #+CAPTION: From [[https://github.com/openbsd/src/blob/master/lib/libc/gen/authenticate.c][=authenticate.c=]] #+begin_src text /* $OpenBSD: authenticate.c,v 1.28 2019/12/04 06:25:45 deraadt Exp $ */ /*- ,* Copyright (c) 1997 Berkeley Software Design, Inc. All rights reserved. ,* ,* Redistribution and use in source and binary forms, with or without ,* modification, are permitted provided that the following conditions ,* are met: ,* 1. Redistributions of source code must retain the above copyright ,* notice, this list of conditions and the following disclaimer. ,* 2. Redistributions in binary form must reproduce the above copyright ,* notice, this list of conditions and the following disclaimer in the ,* documentation and/or other materials provided with the distribution. ,* 3. All advertising materials mentioning features or use of this software ,* must display the following acknowledgement: ,* This product includes software developed by Berkeley Software Design, ,* Inc. ,* 4. The name of Berkeley Software Design, Inc. may not be used to endorse ,* or promote products derived from this software without specific prior ,* written permission. ,* ,* THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND ,* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ,* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ,* ARE DISCLAIMED. IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE ,* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ,* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ,* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ,* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ,* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ,* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ,* SUCH DAMAGE. ,* ,* BSDI $From: authenticate.c,v 2.21 1999/09/08 22:33:26 prb Exp $ ,*/ #+end_src #+CAPTION: From [[https://github.com/openbsd/src/blob/master/lib/libc/gen/auth_subr.c][=auth_subr.c=]] #+begin_src text /* $OpenBSD: auth_subr.c,v 1.56 2020/10/13 04:42:28 guenther Exp $ */ /* ,* Copyright (c) 2000-2002,2004 Todd C. Miller <millert@openbsd.org> ,* ,* Permission to use, copy, modify, and distribute this software for any ,* purpose with or without fee is hereby granted, provided that the above ,* copyright notice and this permission notice appear in all copies. ,* ,* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ,* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ,* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ,* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ,* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ,* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ,* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ,*/ /*- ,* Copyright (c) 1995,1996,1997 Berkeley Software Design, Inc. ,* All rights reserved. ,* ,* Redistribution and use in source and binary forms, with or without ,* modification, are permitted provided that the following conditions ,* are met: ,* 1. Redistributions of source code must retain the above copyright ,* notice, this list of conditions and the following disclaimer. ,* 2. Redistributions in binary form must reproduce the above copyright ,* notice, this list of conditions and the following disclaimer in the ,* documentation and/or other materials provided with the distribution. ,* 3. All advertising materials mentioning features or use of this software ,* must display the following acknowledgement: ,* This product includes software developed by Berkeley Software Design, ,* Inc. ,* 4. The name of Berkeley Software Design, Inc. may not be used to endorse ,* or promote products derived from this software without specific prior ,* written permission. ,* ,* THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND ,* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ,* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ,* ARE DISCLAIMED. IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE ,* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ,* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ,* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ,* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ,* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ,* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ,* SUCH DAMAGE. ,* ,* BSDI $From: auth_subr.c,v 2.4 1999/09/08 04:10:40 prb Exp $ ,*/ #+end_src @@html: </details> @@ #+begin_export html <style> details > summary { list-style: none; } details > summary::-webkit-details-marker { display: none; } </style> #+end_export