diff options
author | Dante Catalfamo | 2021-05-28 15:46:38 -0400 |
---|---|---|
committer | Dante Catalfamo | 2021-05-28 15:46:38 -0400 |
commit | aae4ca4e9a36280c3dd9b9d753f181eddbd12ce7 (patch) | |
tree | 06f40bfcd728e3c39a8afc5503c2a05d63700c35 | |
parent | 55433eb5096f01b86e01e0bb76e353e8c7dfc18f (diff) | |
download | blog-aae4ca4e9a36280c3dd9b9d753f181eddbd12ce7.tar.gz blog-aae4ca4e9a36280c3dd9b9d753f181eddbd12ce7.tar.bz2 blog-aae4ca4e9a36280c3dd9b9d753f181eddbd12ce7.zip |
bsd-auth: add remaining function definitions from those mentioned
-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=]]. |