diff options
Diffstat (limited to 'content/posts/WIP-how-bsd-authentication-works')
| -rw-r--r-- | content/posts/WIP-how-bsd-authentication-works/index.org | 624 | 
1 files changed, 618 insertions, 6 deletions
| diff --git a/content/posts/WIP-how-bsd-authentication-works/index.org b/content/posts/WIP-how-bsd-authentication-works/index.org index 4090a95..5d4843d 100644 --- a/content/posts/WIP-how-bsd-authentication-works/index.org +++ b/content/posts/WIP-how-bsd-authentication-works/index.org @@ -601,10 +601,32 @@     :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> @@     =auth_clrenv= removes all lines containing =BI_SETENV= and     =BI_UNSETENV= from =as->spool=. This is explained under the @@ -614,10 +636,62 @@     :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> @@     =auth_setenv= scans through =as->spool=, modifying the environment     according to =BI_SETENV= and =BI_UNSETENV= instructions. @@ -626,10 +700,87 @@     :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> @@     =auth_getvalue= scans =as->spool= looking for lines beginning with     =BI_VALUE=. It then checks if the next word is equal to =what=. @@ -646,10 +797,24 @@    :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> @@    =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 @@ -667,9 +832,70 @@    :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> @@    =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. @@ -720,10 +946,44 @@    :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> @@    =auth_verify= is used as a frontend for [[#auth_call][=auth_call=]]. @@ -768,10 +1028,204 @@    :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> @@    =auth_call= is responsible for setting up the environment,    calling the modules, and communicating with them. @@ -1077,9 +1531,79 @@    :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> @@    =auth_close= is responsible for setting the environment variables,    removing any files requested by the authentication module, and @@ -1121,9 +1645,66 @@    :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> @@    =auth_userchallenge= is used when the authentication style requires    that the user be presented with a challenge, but the user cannot be @@ -1176,9 +1757,39 @@    :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> @@    =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 @@ -1280,6 +1891,7 @@    }    #+end_src    @@html: </details> @@ +    =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=]]. | 
