google-authenticator/0002755000175000017500000000000012151455610013660 5ustar ocsiocsigoogle-authenticator/.hgsub0000644000175000017500000000026612151455610014773 0ustar ocsiocsimobile/ios/externals/google-toolbox-for-mac = [svn]https://google-toolbox-for-mac.googlecode.com/svn/trunk/ mobile/ios/externals/zxing = [svn]https://zxing.googlecode.com/svn/trunk/ google-authenticator/libpam/0002755000175000017500000000000012151455610015124 5ustar ocsiocsigoogle-authenticator/libpam/hmac.h0000644000175000017500000000162712151455610016211 0ustar ocsiocsi// HMAC_SHA1 implementation // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef _HMAC_H_ #define _HMAC_H_ #include void hmac_sha1(const uint8_t *key, int keyLength, const uint8_t *data, int dataLength, uint8_t *result, int resultLength) __attribute__((visibility("hidden"))); #endif /* _HMAC_H_ */ google-authenticator/libpam/pam_google_authenticator.c0000644000175000017500000013640112151455610022336 0ustar ocsiocsi// PAM module for two-factor authentication. // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef linux // We much rather prefer to use setfsuid(), but this function is unfortunately // not available on all systems. #include #define HAS_SETFSUID #endif #ifndef PAM_EXTERN #define PAM_EXTERN #endif #if !defined(LOG_AUTHPRIV) && defined(LOG_AUTH) #define LOG_AUTHPRIV LOG_AUTH #endif #define PAM_SM_AUTH #define PAM_SM_SESSION #include #include #include "base32.h" #include "hmac.h" #include "sha1.h" #define MODULE_NAME "pam_google_authenticator" #define SECRET "~/.google_authenticator" typedef struct Params { const char *secret_filename_spec; enum { NULLERR=0, NULLOK, SECRETNOTFOUND } nullok; int noskewadj; int echocode; int fixed_uid; uid_t uid; enum { PROMPT = 0, TRY_FIRST_PASS, USE_FIRST_PASS } pass_mode; int forward_pass; } Params; static char oom; #if defined(DEMO) || defined(TESTING) static char error_msg[128]; const char *get_error_msg(void) __attribute__((visibility("default"))); const char *get_error_msg(void) { return error_msg; } #endif static void log_message(int priority, pam_handle_t *pamh, const char *format, ...) { char *service = NULL; if (pamh) pam_get_item(pamh, PAM_SERVICE, (void *)&service); if (!service) service = ""; char logname[80]; snprintf(logname, sizeof(logname), "%s(" MODULE_NAME ")", service); va_list args; va_start(args, format); #if !defined(DEMO) && !defined(TESTING) openlog(logname, LOG_CONS | LOG_PID, LOG_AUTHPRIV); vsyslog(priority, format, args); closelog(); #else if (!*error_msg) { vsnprintf(error_msg, sizeof(error_msg), format, args); } #endif va_end(args); if (priority == LOG_EMERG) { // Something really bad happened. There is no way we can proceed safely. _exit(1); } } static int converse(pam_handle_t *pamh, int nargs, const struct pam_message **message, struct pam_response **response) { struct pam_conv *conv; int retval = pam_get_item(pamh, PAM_CONV, (void *)&conv); if (retval != PAM_SUCCESS) { return retval; } return conv->conv(nargs, message, response, conv->appdata_ptr); } static const char *get_user_name(pam_handle_t *pamh) { // Obtain the user's name const char *username; if (pam_get_item(pamh, PAM_USER, (void *)&username) != PAM_SUCCESS || !username || !*username) { log_message(LOG_ERR, pamh, "No user name available when checking verification code"); return NULL; } return username; } static char *get_secret_filename(pam_handle_t *pamh, const Params *params, const char *username, int *uid) { // Check whether the administrator decided to override the default location // for the secret file. const char *spec = params->secret_filename_spec ? params->secret_filename_spec : SECRET; // Obtain the user's id and home directory struct passwd pwbuf, *pw = NULL; char *buf = NULL; char *secret_filename = NULL; if (!params->fixed_uid) { #ifdef _SC_GETPW_R_SIZE_MAX int len = sysconf(_SC_GETPW_R_SIZE_MAX); if (len <= 0) { len = 4096; } #else int len = 4096; #endif buf = malloc(len); *uid = -1; if (buf == NULL || getpwnam_r(username, &pwbuf, buf, len, &pw) || !pw || !pw->pw_dir || *pw->pw_dir != '/') { err: log_message(LOG_ERR, pamh, "Failed to compute location of secret file"); free(buf); free(secret_filename); return NULL; } } // Expand filename specification to an actual filename. if ((secret_filename = strdup(spec)) == NULL) { goto err; } int allow_tilde = 1; for (int offset = 0; secret_filename[offset];) { char *cur = secret_filename + offset; char *var = NULL; size_t var_len = 0; const char *subst = NULL; if (allow_tilde && *cur == '~') { var_len = 1; if (!pw) { goto err; } subst = pw->pw_dir; var = cur; } else if (secret_filename[offset] == '$') { if (!memcmp(cur, "${HOME}", 7)) { var_len = 7; if (!pw) { goto err; } subst = pw->pw_dir; var = cur; } else if (!memcmp(cur, "${USER}", 7)) { var_len = 7; subst = username; var = cur; } } if (var) { size_t subst_len = strlen(subst); char *resized = realloc(secret_filename, strlen(secret_filename) + subst_len); if (!resized) { goto err; } var += resized - secret_filename; secret_filename = resized; memmove(var + subst_len, var + var_len, strlen(var + var_len) + 1); memmove(var, subst, subst_len); offset = var + subst_len - resized; allow_tilde = 0; } else { allow_tilde = *cur == '/'; ++offset; } } *uid = params->fixed_uid ? params->uid : pw->pw_uid; free(buf); return secret_filename; } static int setuser(int uid) { #ifdef HAS_SETFSUID // The semantics for setfsuid() are a little unusual. On success, the // previous user id is returned. On failure, the current user id is returned. int old_uid = setfsuid(uid); if (uid != setfsuid(uid)) { setfsuid(old_uid); return -1; } #else int old_uid = geteuid(); if (old_uid != uid && seteuid(uid)) { return -1; } #endif return old_uid; } static int setgroup(int gid) { #ifdef HAS_SETFSUID // The semantics of setfsgid() are a little unusual. On success, the // previous group id is returned. On failure, the current groupd id is // returned. int old_gid = setfsgid(gid); if (gid != setfsgid(gid)) { setfsgid(old_gid); return -1; } #else int old_gid = getegid(); if (old_gid != gid && setegid(gid)) { return -1; } #endif return old_gid; } static int drop_privileges(pam_handle_t *pamh, const char *username, int uid, int *old_uid, int *old_gid) { // Try to become the new user. This might be necessary for NFS mounted home // directories. // First, look up the user's default group #ifdef _SC_GETPW_R_SIZE_MAX int len = sysconf(_SC_GETPW_R_SIZE_MAX); if (len <= 0) { len = 4096; } #else int len = 4096; #endif char *buf = malloc(len); if (!buf) { log_message(LOG_ERR, pamh, "Out of memory"); return -1; } struct passwd pwbuf, *pw; if (getpwuid_r(uid, &pwbuf, buf, len, &pw) || !pw) { log_message(LOG_ERR, pamh, "Cannot look up user id %d", uid); free(buf); return -1; } gid_t gid = pw->pw_gid; free(buf); int gid_o = setgroup(gid); int uid_o = setuser(uid); if (uid_o < 0) { if (gid_o >= 0) { if (setgroup(gid_o) < 0 || setgroup(gid_o) != gid_o) { // Inform the caller that we were unsuccessful in resetting the group. *old_gid = gid_o; } } log_message(LOG_ERR, pamh, "Failed to change user id to \"%s\"", username); return -1; } if (gid_o < 0 && (gid_o = setgroup(gid)) < 0) { // In most typical use cases, the PAM module will end up being called // while uid=0. This allows the module to change to an arbitrary group // prior to changing the uid. But there are many ways that PAM modules // can be invoked and in some scenarios this might not work. So, we also // try changing the group _after_ changing the uid. It might just work. if (setuser(uid_o) < 0 || setuser(uid_o) != uid_o) { // Inform the caller that we were unsuccessful in resetting the uid. *old_uid = uid_o; } log_message(LOG_ERR, pamh, "Failed to change group id for user \"%s\" to %d", username, (int)gid); return -1; } *old_uid = uid_o; *old_gid = gid_o; return 0; } static int open_secret_file(pam_handle_t *pamh, const char *secret_filename, struct Params *params, const char *username, int uid, off_t *size, time_t *mtime) { // Try to open "~/.google_authenticator" *size = 0; *mtime = 0; int fd = open(secret_filename, O_RDONLY); struct stat sb; if (fd < 0 || fstat(fd, &sb) < 0) { if (params->nullok != NULLERR && errno == ENOENT) { // The user doesn't have a state file, but the admininistrator said // that this is OK. We still return an error from open_secret_file(), // but we remember that this was the result of a missing state file. params->nullok = SECRETNOTFOUND; } else { log_message(LOG_ERR, pamh, "Failed to read \"%s\"", secret_filename); } error: if (fd >= 0) { close(fd); } return -1; } // Check permissions on "~/.google_authenticator" if ((sb.st_mode & 03577) != 0400 || !S_ISREG(sb.st_mode) || sb.st_uid != (uid_t)uid) { char buf[80]; if (params->fixed_uid) { sprintf(buf, "user id %d", params->uid); username = buf; } log_message(LOG_ERR, pamh, "Secret file \"%s\" must only be accessible by %s", secret_filename, username); goto error; } // Sanity check for file length if (sb.st_size < 1 || sb.st_size > 64*1024) { log_message(LOG_ERR, pamh, "Invalid file size for \"%s\"", secret_filename); goto error; } *size = sb.st_size; *mtime = sb.st_mtime; return fd; } static char *read_file_contents(pam_handle_t *pamh, const char *secret_filename, int *fd, off_t filesize) { // Read file contents char *buf = malloc(filesize + 1); if (!buf || read(*fd, buf, filesize) != filesize) { close(*fd); *fd = -1; log_message(LOG_ERR, pamh, "Could not read \"%s\"", secret_filename); error: if (buf) { memset(buf, 0, filesize); free(buf); } return NULL; } close(*fd); *fd = -1; // The rest of the code assumes that there are no NUL bytes in the file. if (memchr(buf, 0, filesize)) { log_message(LOG_ERR, pamh, "Invalid file contents in \"%s\"", secret_filename); goto error; } // Terminate the buffer with a NUL byte. buf[filesize] = '\000'; return buf; } static int is_totp(const char *buf) { return !!strstr(buf, "\" TOTP_AUTH"); } static int write_file_contents(pam_handle_t *pamh, const char *secret_filename, off_t old_size, time_t old_mtime, const char *buf) { // Safely overwrite the old secret file. char *tmp_filename = malloc(strlen(secret_filename) + 2); if (tmp_filename == NULL) { removal_failure: log_message(LOG_ERR, pamh, "Failed to update secret file \"%s\"", secret_filename); return -1; } strcat(strcpy(tmp_filename, secret_filename), "~"); int fd = open(tmp_filename, O_WRONLY|O_CREAT|O_NOFOLLOW|O_TRUNC|O_EXCL, 0400); if (fd < 0) { goto removal_failure; } // Make sure the secret file is still the same. This prevents attackers // from opening a lot of pending sessions and then reusing the same // scratch code multiple times. struct stat sb; if (stat(secret_filename, &sb) != 0 || sb.st_size != old_size || sb.st_mtime != old_mtime) { log_message(LOG_ERR, pamh, "Secret file \"%s\" changed while trying to use " "scratch code\n", secret_filename); unlink(tmp_filename); free(tmp_filename); close(fd); return -1; } // Write the new file contents if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf) || rename(tmp_filename, secret_filename) != 0) { unlink(tmp_filename); free(tmp_filename); close(fd); goto removal_failure; } free(tmp_filename); close(fd); return 0; } static uint8_t *get_shared_secret(pam_handle_t *pamh, const char *secret_filename, const char *buf, int *secretLen) { // Decode secret key int base32Len = strcspn(buf, "\n"); *secretLen = (base32Len*5 + 7)/8; uint8_t *secret = malloc(base32Len + 1); if (secret == NULL) { *secretLen = 0; return NULL; } memcpy(secret, buf, base32Len); secret[base32Len] = '\000'; if ((*secretLen = base32_decode(secret, secret, base32Len)) < 1) { log_message(LOG_ERR, pamh, "Could not find a valid BASE32 encoded secret in \"%s\"", secret_filename); memset(secret, 0, base32Len); free(secret); return NULL; } memset(secret + *secretLen, 0, base32Len + 1 - *secretLen); return secret; } #ifdef TESTING static time_t current_time; void set_time(time_t t) __attribute__((visibility("default"))); void set_time(time_t t) { current_time = t; } static time_t get_time(void) { return current_time; } #else static time_t get_time(void) { return time(NULL); } #endif static int get_timestamp(void) { return get_time()/30; } static int comparator(const void *a, const void *b) { return *(unsigned int *)a - *(unsigned int *)b; } static char *get_cfg_value(pam_handle_t *pamh, const char *key, const char *buf) { size_t key_len = strlen(key); for (const char *line = buf; *line; ) { const char *ptr; if (line[0] == '"' && line[1] == ' ' && !memcmp(line+2, key, key_len) && (!*(ptr = line+2+key_len) || *ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')) { ptr += strspn(ptr, " \t"); size_t val_len = strcspn(ptr, "\r\n"); char *val = malloc(val_len + 1); if (!val) { log_message(LOG_ERR, pamh, "Out of memory"); return &oom; } else { memcpy(val, ptr, val_len); val[val_len] = '\000'; return val; } } else { line += strcspn(line, "\r\n"); line += strspn(line, "\r\n"); } } return NULL; } static int set_cfg_value(pam_handle_t *pamh, const char *key, const char *val, char **buf) { size_t key_len = strlen(key); char *start = NULL; char *stop = NULL; // Find an existing line, if any. for (char *line = *buf; *line; ) { char *ptr; if (line[0] == '"' && line[1] == ' ' && !memcmp(line+2, key, key_len) && (!*(ptr = line+2+key_len) || *ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')) { start = line; stop = start + strcspn(start, "\r\n"); stop += strspn(stop, "\r\n"); break; } else { line += strcspn(line, "\r\n"); line += strspn(line, "\r\n"); } } // If no existing line, insert immediately after the first line. if (!start) { start = *buf + strcspn(*buf, "\r\n"); start += strspn(start, "\r\n"); stop = start; } // Replace [start..stop] with the new contents. size_t val_len = strlen(val); size_t total_len = key_len + val_len + 4; if (total_len <= stop - start) { // We are decreasing out space requirements. Shrink the buffer and pad with // NUL characters. size_t tail_len = strlen(stop); memmove(start + total_len, stop, tail_len + 1); memset(start + total_len + tail_len, 0, stop - start - total_len + 1); } else { // Must resize existing buffer. We cannot call realloc(), as it could // leave parts of the buffer content in unused parts of the heap. size_t buf_len = strlen(*buf); size_t tail_len = buf_len - (stop - *buf); char *resized = malloc(buf_len - (stop - start) + total_len + 1); if (!resized) { log_message(LOG_ERR, pamh, "Out of memory"); return -1; } memcpy(resized, *buf, start - *buf); memcpy(resized + (start - *buf) + total_len, stop, tail_len + 1); memset(*buf, 0, buf_len); free(*buf); start = start - *buf + resized; *buf = resized; } // Fill in new contents. start[0] = '"'; start[1] = ' '; memcpy(start + 2, key, key_len); start[2+key_len] = ' '; memcpy(start+3+key_len, val, val_len); start[3+key_len+val_len] = '\n'; // Check if there are any other occurrences of "value". If so, delete them. for (char *line = start + 4 + key_len + val_len; *line; ) { char *ptr; if (line[0] == '"' && line[1] == ' ' && !memcmp(line+2, key, key_len) && (!*(ptr = line+2+key_len) || *ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')) { start = line; stop = start + strcspn(start, "\r\n"); stop += strspn(stop, "\r\n"); size_t tail_len = strlen(stop); memmove(start, stop, tail_len + 1); memset(start + tail_len, 0, stop - start); line = start; } else { line += strcspn(line, "\r\n"); line += strspn(line, "\r\n"); } } return 0; } static long get_hotp_counter(pam_handle_t *pamh, const char *buf) { const char *counter_str = get_cfg_value(pamh, "HOTP_COUNTER", buf); if (counter_str == &oom) { // Out of memory. This is a fatal error return -1; } long counter = 0; if (counter_str) { counter = strtol(counter_str, NULL, 10); } free((void *)counter_str); return counter; } static int rate_limit(pam_handle_t *pamh, const char *secret_filename, int *updated, char **buf) { const char *value = get_cfg_value(pamh, "RATE_LIMIT", *buf); if (!value) { // Rate limiting is not enabled for this account return 0; } else if (value == &oom) { // Out of memory. This is a fatal error. return -1; } // Parse both the maximum number of login attempts and the time interval // that we are looking at. const char *endptr = value, *ptr; int attempts, interval; errno = 0; if (((attempts = (int)strtoul(ptr = endptr, (char **)&endptr, 10)) < 1) || ptr == endptr || attempts > 100 || errno || (*endptr != ' ' && *endptr != '\t') || ((interval = (int)strtoul(ptr = endptr, (char **)&endptr, 10)) < 1) || ptr == endptr || interval > 3600 || errno) { free((void *)value); log_message(LOG_ERR, pamh, "Invalid RATE_LIMIT option. Check \"%s\".", secret_filename); return -1; } // Parse the time stamps of all previous login attempts. unsigned int now = get_time(); unsigned int *timestamps = malloc(sizeof(int)); if (!timestamps) { oom: free((void *)value); log_message(LOG_ERR, pamh, "Out of memory"); return -1; } timestamps[0] = now; int num_timestamps = 1; while (*endptr && *endptr != '\r' && *endptr != '\n') { unsigned int timestamp; errno = 0; if ((*endptr != ' ' && *endptr != '\t') || ((timestamp = (int)strtoul(ptr = endptr, (char **)&endptr, 10)), errno) || ptr == endptr) { free((void *)value); free(timestamps); log_message(LOG_ERR, pamh, "Invalid list of timestamps in RATE_LIMIT. " "Check \"%s\".", secret_filename); return -1; } num_timestamps++; unsigned int *tmp = (unsigned int *)realloc(timestamps, sizeof(int) * num_timestamps); if (!tmp) { free(timestamps); goto oom; } timestamps = tmp; timestamps[num_timestamps-1] = timestamp; } free((void *)value); value = NULL; // Sort time stamps, then prune all entries outside of the current time // interval. qsort(timestamps, num_timestamps, sizeof(int), comparator); int start = 0, stop = -1; for (int i = 0; i < num_timestamps; ++i) { if (timestamps[i] < now - interval) { start = i+1; } else if (timestamps[i] > now) { break; } stop = i; } // Error out, if there are too many login attempts. int exceeded = 0; if (stop - start + 1 > attempts) { exceeded = 1; start = stop - attempts + 1; } // Construct new list of timestamps within the current time interval. char *list = malloc(25 * (2 + (stop - start + 1)) + 4); if (!list) { free(timestamps); goto oom; } sprintf(list, "%d %d", attempts, interval); char *prnt = strchr(list, '\000'); for (int i = start; i <= stop; ++i) { prnt += sprintf(prnt, " %u", timestamps[i]); } free(timestamps); // Try to update RATE_LIMIT line. if (set_cfg_value(pamh, "RATE_LIMIT", list, buf) < 0) { free(list); return -1; } free(list); // Mark the state file as changed. *updated = 1; // If necessary, notify the user of the rate limiting that is in effect. if (exceeded) { log_message(LOG_ERR, pamh, "Too many concurrent login attempts. Please try again."); return -1; } return 0; } static char *get_first_pass(pam_handle_t *pamh) { const void *password = NULL; if (pam_get_item(pamh, PAM_AUTHTOK, &password) == PAM_SUCCESS && password) { return strdup((const char *)password); } return NULL; } static char *request_pass(pam_handle_t *pamh, int echocode, const char *prompt) { // Query user for verification code const struct pam_message msg = { .msg_style = echocode, .msg = prompt }; const struct pam_message *msgs = &msg; struct pam_response *resp = NULL; int retval = converse(pamh, 1, &msgs, &resp); char *ret = NULL; if (retval != PAM_SUCCESS || resp == NULL || resp->resp == NULL || *resp->resp == '\000') { log_message(LOG_ERR, pamh, "Did not receive verification code from user"); if (retval == PAM_SUCCESS && resp && resp->resp) { ret = resp->resp; } } else { ret = resp->resp; } // Deallocate temporary storage if (resp) { if (!ret) { free(resp->resp); } free(resp); } return ret; } /* Checks for possible use of scratch codes. Returns -1 on error, 0 on success, * and 1, if no scratch code had been entered, and subsequent tests should be * applied. */ static int check_scratch_codes(pam_handle_t *pamh, const char *secret_filename, int *updated, char *buf, int code) { // Skip the first line. It contains the shared secret. char *ptr = buf + strcspn(buf, "\n"); // Check if this is one of the scratch codes char *endptr = NULL; for (;;) { // Skip newlines and blank lines while (*ptr == '\r' || *ptr == '\n') { ptr++; } // Skip any lines starting with double-quotes. They contain option fields if (*ptr == '"') { ptr += strcspn(ptr, "\n"); continue; } // Try to interpret the line as a scratch code errno = 0; int scratchcode = (int)strtoul(ptr, &endptr, 10); // Sanity check that we read a valid scratch code. Scratchcodes are all // numeric eight-digit codes. There must not be any other information on // that line. if (errno || ptr == endptr || (*endptr != '\r' && *endptr != '\n' && *endptr) || scratchcode < 10*1000*1000 || scratchcode >= 100*1000*1000) { break; } // Check if the code matches if (scratchcode == code) { // Remove scratch code after using it while (*endptr == '\n' || *endptr == '\r') { ++endptr; } memmove(ptr, endptr, strlen(endptr) + 1); memset(strrchr(ptr, '\000'), 0, endptr - ptr + 1); // Mark the state file as changed *updated = 1; // Successfully removed scratch code. Allow user to log in. return 0; } ptr = endptr; } // No scratch code has been used. Continue checking other types of codes. return 1; } static int window_size(pam_handle_t *pamh, const char *secret_filename, const char *buf) { const char *value = get_cfg_value(pamh, "WINDOW_SIZE", buf); if (!value) { // Default window size is 3. This gives us one 30s window before and // after the current one. free((void *)value); return 3; } else if (value == &oom) { // Out of memory. This is a fatal error. return 0; } char *endptr; errno = 0; int window = (int)strtoul(value, &endptr, 10); if (errno || !*value || value == endptr || (*endptr && *endptr != ' ' && *endptr != '\t' && *endptr != '\n' && *endptr != '\r') || window < 1 || window > 100) { free((void *)value); log_message(LOG_ERR, pamh, "Invalid WINDOW_SIZE option in \"%s\"", secret_filename); return 0; } free((void *)value); return window; } /* If the DISALLOW_REUSE option has been set, record timestamps have been * used to log in successfully and disallow their reuse. * * Returns -1 on error, and 0 on success. */ static int invalidate_timebased_code(int tm, pam_handle_t *pamh, const char *secret_filename, int *updated, char **buf) { char *disallow = get_cfg_value(pamh, "DISALLOW_REUSE", *buf); if (!disallow) { // Reuse of tokens is not explicitly disallowed. Allow the login request // to proceed. return 0; } else if (disallow == &oom) { // Out of memory. This is a fatal error. return -1; } // Allow the user to customize the window size parameter. int window = window_size(pamh, secret_filename, *buf); if (!window) { // The user configured a non-standard window size, but there was some // error with the value of this parameter. free((void *)disallow); return -1; } // The DISALLOW_REUSE option is followed by all known timestamps that are // currently unavailable for login. for (char *ptr = disallow; *ptr;) { // Skip white-space, if any ptr += strspn(ptr, " \t\r\n"); if (!*ptr) { break; } // Parse timestamp value. char *endptr; errno = 0; int blocked = (int)strtoul(ptr, &endptr, 10); // Treat syntactically invalid options as an error if (errno || ptr == endptr || (*endptr != ' ' && *endptr != '\t' && *endptr != '\r' && *endptr != '\n' && *endptr)) { free((void *)disallow); return -1; } if (tm == blocked) { // The code is currently blocked from use. Disallow login. free((void *)disallow); log_message(LOG_ERR, pamh, "Trying to reuse a previously used time-based code. " "Retry again in 30 seconds. " "Warning! This might mean, you are currently subject to a " "man-in-the-middle attack."); return -1; } // If the blocked code is outside of the possible window of timestamps, // remove it from the file. if (blocked - tm >= window || tm - blocked >= window) { endptr += strspn(endptr, " \t"); memmove(ptr, endptr, strlen(endptr) + 1); } else { ptr = endptr; } } // Add the current timestamp to the list of disallowed timestamps. char *resized = realloc(disallow, strlen(disallow) + 40); if (!resized) { free((void *)disallow); log_message(LOG_ERR, pamh, "Failed to allocate memory when updating \"%s\"", secret_filename); return -1; } disallow = resized; sprintf(strrchr(disallow, '\000'), " %d" + !*disallow, tm); if (set_cfg_value(pamh, "DISALLOW_REUSE", disallow, buf) < 0) { free((void *)disallow); return -1; } free((void *)disallow); // Mark the state file as changed *updated = 1; // Allow access. return 0; } /* Given an input value, this function computes the hash code that forms the * expected authentication token. */ #ifdef TESTING int compute_code(const uint8_t *secret, int secretLen, unsigned long value) __attribute__((visibility("default"))); #else static #endif int compute_code(const uint8_t *secret, int secretLen, unsigned long value) { uint8_t val[8]; for (int i = 8; i--; value >>= 8) { val[i] = value; } uint8_t hash[SHA1_DIGEST_LENGTH]; hmac_sha1(secret, secretLen, val, 8, hash, SHA1_DIGEST_LENGTH); memset(val, 0, sizeof(val)); int offset = hash[SHA1_DIGEST_LENGTH - 1] & 0xF; unsigned int truncatedHash = 0; for (int i = 0; i < 4; ++i) { truncatedHash <<= 8; truncatedHash |= hash[offset + i]; } memset(hash, 0, sizeof(hash)); truncatedHash &= 0x7FFFFFFF; truncatedHash %= 1000000; return truncatedHash; } /* If a user repeated attempts to log in with the same time skew, remember * this skew factor for future login attempts. */ static int check_time_skew(pam_handle_t *pamh, const char *secret_filename, int *updated, char **buf, int skew, int tm) { int rc = -1; // Parse current RESETTING_TIME_SKEW line, if any. char *resetting = get_cfg_value(pamh, "RESETTING_TIME_SKEW", *buf); if (resetting == &oom) { // Out of memory. This is a fatal error. return -1; } // If the user can produce a sequence of three consecutive codes that fall // within a day of the current time. And if he can enter these codes in // quick succession, then we allow the time skew to be reset. // N.B. the number "3" was picked so that it would not trigger the rate // limiting limit if set up with default parameters. unsigned int tms[3]; int skews[sizeof(tms)/sizeof(int)]; int num_entries = 0; if (resetting) { char *ptr = resetting; // Read the three most recent pairs of time stamps and skew values into // our arrays. while (*ptr && *ptr != '\r' && *ptr != '\n') { char *endptr; errno = 0; unsigned int i = (int)strtoul(ptr, &endptr, 10); if (errno || ptr == endptr || (*endptr != '+' && *endptr != '-')) { break; } ptr = endptr; int j = (int)strtoul(ptr + 1, &endptr, 10); if (errno || ptr == endptr || (*endptr != ' ' && *endptr != '\t' && *endptr != '\r' && *endptr != '\n' && *endptr)) { break; } if (*ptr == '-') { j = -j; } if (num_entries == sizeof(tms)/sizeof(int)) { memmove(tms, tms+1, sizeof(tms)-sizeof(int)); memmove(skews, skews+1, sizeof(skews)-sizeof(int)); } else { ++num_entries; } tms[num_entries-1] = i; skews[num_entries-1] = j; ptr = endptr; } // If the user entered an identical code, assume they are just getting // desperate. This doesn't actually provide us with any useful data, // though. Don't change any state and hope the user keeps trying a few // more times. if (num_entries && tm + skew == tms[num_entries-1] + skews[num_entries-1]) { free((void *)resetting); return -1; } } free((void *)resetting); // Append new timestamp entry if (num_entries == sizeof(tms)/sizeof(int)) { memmove(tms, tms+1, sizeof(tms)-sizeof(int)); memmove(skews, skews+1, sizeof(skews)-sizeof(int)); } else { ++num_entries; } tms[num_entries-1] = tm; skews[num_entries-1] = skew; // Check if we have the required amount of valid entries. if (num_entries == sizeof(tms)/sizeof(int)) { unsigned int last_tm = tms[0]; int last_skew = skews[0]; int avg_skew = last_skew; for (int i = 1; i < sizeof(tms)/sizeof(int); ++i) { // Check that we have a consecutive sequence of timestamps with no big // gaps in between. Also check that the time skew stays constant. Allow // a minor amount of fuzziness on all parameters. if (tms[i] <= last_tm || tms[i] > last_tm+2 || last_skew - skew < -1 || last_skew - skew > 1) { goto keep_trying; } last_tm = tms[i]; last_skew = skews[i]; avg_skew += last_skew; } avg_skew /= (int)(sizeof(tms)/sizeof(int)); // The user entered the required number of valid codes in quick // succession. Establish a new valid time skew for all future login // attempts. char time_skew[40]; sprintf(time_skew, "%d", avg_skew); if (set_cfg_value(pamh, "TIME_SKEW", time_skew, buf) < 0) { return -1; } rc = 0; keep_trying:; } // Set the new RESETTING_TIME_SKEW line, while the user is still trying // to reset the time skew. char reset[80 * (sizeof(tms)/sizeof(int))]; *reset = '\000'; if (rc) { for (int i = 0; i < num_entries; ++i) { sprintf(strrchr(reset, '\000'), " %d%+d" + !*reset, tms[i], skews[i]); } } if (set_cfg_value(pamh, "RESETTING_TIME_SKEW", reset, buf) < 0) { return -1; } // Mark the state file as changed *updated = 1; return rc; } /* Checks for time based verification code. Returns -1 on error, 0 on success, * and 1, if no time based code had been entered, and subsequent tests should * be applied. */ static int check_timebased_code(pam_handle_t *pamh, const char*secret_filename, int *updated, char **buf, const uint8_t*secret, int secretLen, int code, Params *params) { if (!is_totp(*buf)) { // The secret file does not actually contain information for a time-based // code. Return to caller and see if any other authentication methods // apply. return 1; } if (code < 0 || code >= 1000000) { // All time based verification codes are no longer than six digits. return 1; } // Compute verification codes and compare them with user input const int tm = get_timestamp(); const char *skew_str = get_cfg_value(pamh, "TIME_SKEW", *buf); if (skew_str == &oom) { // Out of memory. This is a fatal error return -1; } int skew = 0; if (skew_str) { skew = (int)strtol(skew_str, NULL, 10); } free((void *)skew_str); int window = window_size(pamh, secret_filename, *buf); if (!window) { return -1; } for (int i = -((window-1)/2); i <= window/2; ++i) { unsigned int hash = compute_code(secret, secretLen, tm + skew + i); if (hash == (unsigned int)code) { return invalidate_timebased_code(tm + skew + i, pamh, secret_filename, updated, buf); } } if (!params->noskewadj) { // The most common failure mode is for the clocks to be insufficiently // synchronized. We can detect this and store a skew value for future // use. skew = 1000000; for (int i = 0; i < 25*60; ++i) { unsigned int hash = compute_code(secret, secretLen, tm - i); if (hash == (unsigned int)code && skew == 1000000) { // Don't short-circuit out of the loop as the obvious difference in // computation time could be a signal that is valuable to an attacker. skew = -i; } hash = compute_code(secret, secretLen, tm + i); if (hash == (unsigned int)code && skew == 1000000) { skew = i; } } if (skew != 1000000) { return check_time_skew(pamh, secret_filename, updated, buf, skew, tm); } } return 1; } /* Checks for counter based verification code. Returns -1 on error, 0 on * success, and 1, if no counter based code had been entered, and subsequent * tests should be applied. */ static int check_counterbased_code(pam_handle_t *pamh, const char*secret_filename, int *updated, char **buf, const uint8_t*secret, int secretLen, int code, Params *params, long hotp_counter, int *must_advance_counter) { if (hotp_counter < 1) { // The secret file did not actually contain information for a counter-based // code. Return to caller and see if any other authentication methods // apply. return 1; } if (code < 0 || code >= 1000000) { // All counter based verification codes are no longer than six digits. return 1; } // Compute [window_size] verification codes and compare them with user input. // Future codes are allowed in case the user computed but did not use a code. int window = window_size(pamh, secret_filename, *buf); if (!window) { return -1; } for (int i = 0; i < window; ++i) { unsigned int hash = compute_code(secret, secretLen, hotp_counter + i); if (hash == (unsigned int)code) { char counter_str[40]; sprintf(counter_str, "%ld", hotp_counter + i + 1); if (set_cfg_value(pamh, "HOTP_COUNTER", counter_str, buf) < 0) { return -1; } *updated = 1; *must_advance_counter = 0; return 0; } } *must_advance_counter = 1; return 1; } static int parse_user(pam_handle_t *pamh, const char *name, uid_t *uid) { char *endptr; errno = 0; long l = strtol(name, &endptr, 10); if (!errno && endptr != name && l >= 0 && l <= INT_MAX) { *uid = (uid_t)l; return 0; } #ifdef _SC_GETPW_R_SIZE_MAX int len = sysconf(_SC_GETPW_R_SIZE_MAX); if (len <= 0) { len = 4096; } #else int len = 4096; #endif char *buf = malloc(len); if (!buf) { log_message(LOG_ERR, pamh, "Out of memory"); return -1; } struct passwd pwbuf, *pw; if (getpwnam_r(name, &pwbuf, buf, len, &pw) || !pw) { free(buf); log_message(LOG_ERR, pamh, "Failed to look up user \"%s\"", name); return -1; } *uid = pw->pw_uid; free(buf); return 0; } static int parse_args(pam_handle_t *pamh, int argc, const char **argv, Params *params) { params->echocode = PAM_PROMPT_ECHO_OFF; for (int i = 0; i < argc; ++i) { if (!memcmp(argv[i], "secret=", 7)) { free((void *)params->secret_filename_spec); params->secret_filename_spec = argv[i] + 7; } else if (!memcmp(argv[i], "user=", 5)) { uid_t uid; if (parse_user(pamh, argv[i] + 5, &uid) < 0) { return -1; } params->fixed_uid = 1; params->uid = uid; } else if (!strcmp(argv[i], "try_first_pass")) { params->pass_mode = TRY_FIRST_PASS; } else if (!strcmp(argv[i], "use_first_pass")) { params->pass_mode = USE_FIRST_PASS; } else if (!strcmp(argv[i], "forward_pass")) { params->forward_pass = 1; } else if (!strcmp(argv[i], "noskewadj")) { params->noskewadj = 1; } else if (!strcmp(argv[i], "nullok")) { params->nullok = NULLOK; } else if (!strcmp(argv[i], "echo-verification-code") || !strcmp(argv[i], "echo_verification_code")) { params->echocode = PAM_PROMPT_ECHO_ON; } else { log_message(LOG_ERR, pamh, "Unrecognized option \"%s\"", argv[i]); return -1; } } return 0; } static int google_authenticator(pam_handle_t *pamh, int flags, int argc, const char **argv) { int rc = PAM_SESSION_ERR; const char *username; char *secret_filename = NULL; int uid = -1, old_uid = -1, old_gid = -1, fd = -1; off_t filesize = 0; time_t mtime = 0; char *buf = NULL; uint8_t *secret = NULL; int secretLen = 0; #if defined(DEMO) || defined(TESTING) *error_msg = '\000'; #endif // Handle optional arguments that configure our PAM module Params params = { 0 }; if (parse_args(pamh, argc, argv, ¶ms) < 0) { return rc; } // Read and process status file, then ask the user for the verification code. int early_updated = 0, updated = 0; if ((username = get_user_name(pamh)) && (secret_filename = get_secret_filename(pamh, ¶ms, username, &uid)) && !drop_privileges(pamh, username, uid, &old_uid, &old_gid) && (fd = open_secret_file(pamh, secret_filename, ¶ms, username, uid, &filesize, &mtime)) >= 0 && (buf = read_file_contents(pamh, secret_filename, &fd, filesize)) && (secret = get_shared_secret(pamh, secret_filename, buf, &secretLen)) && rate_limit(pamh, secret_filename, &early_updated, &buf) >= 0) { long hotp_counter = get_hotp_counter(pamh, buf); int must_advance_counter = 0; char *pw = NULL, *saved_pw = NULL; for (int mode = 0; mode < 4; ++mode) { // In the case of TRY_FIRST_PASS, we don't actually know whether we // get the verification code from the system password or from prompting // the user. We need to attempt both. // This only works correctly, if all failed attempts leave the global // state unchanged. if (updated || pw) { // Oops. There is something wrong with the internal logic of our // code. This error should never trigger. The unittest checks for // this. if (pw) { memset(pw, 0, strlen(pw)); free(pw); pw = NULL; } rc = PAM_SESSION_ERR; break; } switch (mode) { case 0: // Extract possible verification code case 1: // Extract possible scratch code if (params.pass_mode == USE_FIRST_PASS || params.pass_mode == TRY_FIRST_PASS) { pw = get_first_pass(pamh); } break; default: if (mode != 2 && // Prompt for pw and possible verification code mode != 3) { // Prompt for pw and possible scratch code rc = PAM_SESSION_ERR; continue; } if (params.pass_mode == PROMPT || params.pass_mode == TRY_FIRST_PASS) { if (!saved_pw) { // If forwarding the password to the next stacked PAM module, // we cannot tell the difference between an eight digit scratch // code or a two digit password immediately followed by a six // digit verification code. We have to loop and try both // options. saved_pw = request_pass(pamh, params.echocode, params.forward_pass ? "Password & verification code: " : "Verification code: "); } if (saved_pw) { pw = strdup(saved_pw); } } break; } if (!pw) { continue; } // We are often dealing with a combined password and verification // code. Separate them now. int pw_len = strlen(pw); int expected_len = mode & 1 ? 8 : 6; char ch; if (pw_len < expected_len || // Verification are six digits starting with '0'..'9', // scratch codes are eight digits starting with '1'..'9' (ch = pw[pw_len - expected_len]) > '9' || ch < (expected_len == 8 ? '1' : '0')) { invalid: memset(pw, 0, pw_len); free(pw); pw = NULL; continue; } char *endptr; errno = 0; long l = strtol(pw + pw_len - expected_len, &endptr, 10); if (errno || l < 0 || *endptr) { goto invalid; } int code = (int)l; memset(pw + pw_len - expected_len, 0, expected_len); if ((mode == 2 || mode == 3) && !params.forward_pass) { // We are explicitly configured so that we don't try to share // the password with any other stacked PAM module. We must // therefore verify that the user entered just the verification // code, but no password. if (*pw) { goto invalid; } } // Check all possible types of verification codes. switch (check_scratch_codes(pamh, secret_filename, &updated, buf, code)){ case 1: if (hotp_counter > 0) { switch (check_counterbased_code(pamh, secret_filename, &updated, &buf, secret, secretLen, code, ¶ms, hotp_counter, &must_advance_counter)) { case 0: rc = PAM_SUCCESS; break; case 1: goto invalid; default: break; } } else { switch (check_timebased_code(pamh, secret_filename, &updated, &buf, secret, secretLen, code, ¶ms)) { case 0: rc = PAM_SUCCESS; break; case 1: goto invalid; default: break; } } break; case 0: rc = PAM_SUCCESS; break; default: break; } break; } // Update the system password, if we were asked to forward // the system password. We already removed the verification // code from the end of the password. if (rc == PAM_SUCCESS && params.forward_pass) { if (!pw || pam_set_item(pamh, PAM_AUTHTOK, pw) != PAM_SUCCESS) { rc = PAM_SESSION_ERR; } } // Clear out password and deallocate memory if (pw) { memset(pw, 0, strlen(pw)); free(pw); } if (saved_pw) { memset(saved_pw, 0, strlen(saved_pw)); free(saved_pw); } // If an hotp login attempt has been made, the counter must always be // advanced by at least one. if (must_advance_counter) { char counter_str[40]; sprintf(counter_str, "%ld", hotp_counter + 1); if (set_cfg_value(pamh, "HOTP_COUNTER", counter_str, &buf) < 0) { rc = PAM_SESSION_ERR; } updated = 1; } // If nothing matched, display an error message if (rc != PAM_SUCCESS) { log_message(LOG_ERR, pamh, "Invalid verification code"); } } // If the user has not created a state file with a shared secret, and if // the administrator set the "nullok" option, this PAM module completes // successfully, without ever prompting the user. if (params.nullok == SECRETNOTFOUND) { rc = PAM_SUCCESS; } // Persist the new state. if (early_updated || updated) { if (write_file_contents(pamh, secret_filename, filesize, mtime, buf) < 0) { // Could not persist new state. Deny access. rc = PAM_SESSION_ERR; } } if (fd >= 0) { close(fd); } if (old_gid >= 0) { if (setgroup(old_gid) >= 0 && setgroup(old_gid) == old_gid) { old_gid = -1; } } if (old_uid >= 0) { if (setuser(old_uid) < 0 || setuser(old_uid) != old_uid) { log_message(LOG_EMERG, pamh, "We switched users from %d to %d, " "but can't switch back", old_uid, uid); } } free(secret_filename); // Clean up if (buf) { memset(buf, 0, strlen(buf)); free(buf); } if (secret) { memset(secret, 0, secretLen); free(secret); } return rc; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) __attribute__((visibility("default"))); PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { return google_authenticator(pamh, flags, argc, argv); } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) __attribute__((visibility("default"))); PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_SUCCESS; } PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) __attribute__((visibility("default"))); PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { return google_authenticator(pamh, flags, argc, argv); } #ifdef PAM_STATIC struct pam_module _pam_listfile_modstruct = { MODULE_NAME, pam_sm_authenticate, pam_sm_setcred, NULL, pam_sm_open_session, NULL, NULL }; #endif google-authenticator/libpam/hmac.c0000644000175000017500000000467712151455610016214 0ustar ocsiocsi// HMAC_SHA1 implementation // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include "hmac.h" #include "sha1.h" void hmac_sha1(const uint8_t *key, int keyLength, const uint8_t *data, int dataLength, uint8_t *result, int resultLength) { SHA1_INFO ctx; uint8_t hashed_key[SHA1_DIGEST_LENGTH]; if (keyLength > 64) { // The key can be no bigger than 64 bytes. If it is, we'll hash it down to // 20 bytes. sha1_init(&ctx); sha1_update(&ctx, key, keyLength); sha1_final(&ctx, hashed_key); key = hashed_key; keyLength = SHA1_DIGEST_LENGTH; } // The key for the inner digest is derived from our key, by padding the key // the full length of 64 bytes, and then XOR'ing each byte with 0x36. uint8_t tmp_key[64]; for (int i = 0; i < keyLength; ++i) { tmp_key[i] = key[i] ^ 0x36; } memset(tmp_key + keyLength, 0x36, 64 - keyLength); // Compute inner digest sha1_init(&ctx); sha1_update(&ctx, tmp_key, 64); sha1_update(&ctx, data, dataLength); uint8_t sha[SHA1_DIGEST_LENGTH]; sha1_final(&ctx, sha); // The key for the outer digest is derived from our key, by padding the key // the full length of 64 bytes, and then XOR'ing each byte with 0x5C. for (int i = 0; i < keyLength; ++i) { tmp_key[i] = key[i] ^ 0x5C; } memset(tmp_key + keyLength, 0x5C, 64 - keyLength); // Compute outer digest sha1_init(&ctx); sha1_update(&ctx, tmp_key, 64); sha1_update(&ctx, sha, SHA1_DIGEST_LENGTH); sha1_final(&ctx, sha); // Copy result to output buffer and truncate or pad as necessary memset(result, 0, resultLength); if (resultLength > SHA1_DIGEST_LENGTH) { resultLength = SHA1_DIGEST_LENGTH; } memcpy(result, sha, resultLength); // Zero out all internal data structures memset(hashed_key, 0, sizeof(hashed_key)); memset(sha, 0, sizeof(sha)); memset(tmp_key, 0, sizeof(tmp_key)); } google-authenticator/libpam/sha1.h0000644000175000017500000000224512151455610016132 0ustar ocsiocsi// SHA1 header file // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef SHA1_H__ #define SHA1_H__ #include #define SHA1_BLOCKSIZE 64 #define SHA1_DIGEST_LENGTH 20 typedef struct { uint32_t digest[8]; uint32_t count_lo, count_hi; uint8_t data[SHA1_BLOCKSIZE]; int local; } SHA1_INFO; void sha1_init(SHA1_INFO *sha1_info) __attribute__((visibility("hidden"))); void sha1_update(SHA1_INFO *sha1_info, const uint8_t *buffer, int count) __attribute__((visibility("hidden"))); void sha1_final(SHA1_INFO *sha1_info, uint8_t digest[20]) __attribute__((visibility("hidden"))); #endif google-authenticator/libpam/base32.c0000644000175000017500000000464712151455610016360 0ustar ocsiocsi// Base32 implementation // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include "base32.h" int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) { int buffer = 0; int bitsLeft = 0; int count = 0; for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { uint8_t ch = *ptr; if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { continue; } buffer <<= 5; // Deal with commonly mistyped characters if (ch == '0') { ch = 'O'; } else if (ch == '1') { ch = 'L'; } else if (ch == '8') { ch = 'B'; } // Look up one base32 digit if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { ch = (ch & 0x1F) - 1; } else if (ch >= '2' && ch <= '7') { ch -= '2' - 26; } else { return -1; } buffer |= ch; bitsLeft += 5; if (bitsLeft >= 8) { result[count++] = buffer >> (bitsLeft - 8); bitsLeft -= 8; } } if (count < bufSize) { result[count] = '\000'; } return count; } int base32_encode(const uint8_t *data, int length, uint8_t *result, int bufSize) { if (length < 0 || length > (1 << 28)) { return -1; } int count = 0; if (length > 0) { int buffer = data[0]; int next = 1; int bitsLeft = 8; while (count < bufSize && (bitsLeft > 0 || next < length)) { if (bitsLeft < 5) { if (next < length) { buffer <<= 8; buffer |= data[next++] & 0xFF; bitsLeft += 8; } else { int pad = 5 - bitsLeft; buffer <<= pad; bitsLeft += pad; } } int index = 0x1F & (buffer >> (bitsLeft - 5)); bitsLeft -= 5; result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; } } if (count < bufSize) { result[count] = '\000'; } return count; } google-authenticator/libpam/FILEFORMAT0000644000175000017500000000511012151455610016472 0ustar ocsiocsiAll configuration data and state is kept in ~/.google_authenticator The file is all ASCII, but is kept in a very simple-to-parse and rigid file format. The file size is currently limited to 1kB. This should be generous even when using a very large list of scratch codes. The first line is the base32 encoded secret. It uses characters in the range A..Z2..7. The following lines are optional. They all start with a double quote character, followed by a space character. Followed by an option name. Option names are all upper-case and must include an underscore. This ensures that they cannot accidentally appear anywhere else in the file. Options can be followed by option-specific parameters. Currently, the following options are recognized: DISALLOW_REUSE if present, this signals the a time-based token can only ever be used exactly once. Any attempt to log in using the same token will be denied. This means, that users can typically not log in faster than once every ~30 seconds. The option is followed by a space-separated list of time stamps that have previously been used for login attempts. This option has no effect when HOTP_COUNTER is present. RATE_LIMIT n m ... this optional parameter restricts the number of logins to at most "n" within each "m" second interval. Additional parameters in this line are undocumented; they are used internally to keep track of state. TOTP_AUTH the presence of this option indicates that the secret can be used to authenticate users with a time-based token. HOTP_COUNTER n the presence of this option indicates that the secret can be used to authenticate users with a counter-based token. The argument "n" represents which counter value the token will accept next. It should be initialized to 1. WINDOW_SIZE n the default window size is 3, allowing up to one extra valid token before and after the currently active one. This might be too restrictive if the client and the server experience significant time skew. You can provide a parameter to increase the login window size from 3 to "n" In counter-based mode, this option is the number of valid tokens after the currently active one. The default is almost certainly too restrictive for most users as invalid login attempts and generated-but-not-used tokens both contribute to synchronization problems. Any all-numeric sequence of eight-digit numbers are randomly generated one-time tokens. The user can enter any arbitrary one-time code to log into his account. The code will then be removed from the file. google-authenticator/libpam/pam_google_authenticator_unittest.c0000644000175000017500000005234612151455610024302 0ustar ocsiocsi// Unittest for the PAM module. This is part of the Google Authenticator // project. // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include "base32.h" #include "hmac.h" #if !defined(PAM_BAD_ITEM) // FreeBSD does not know about PAM_BAD_ITEM. And PAM_SYMBOL_ERR is an "enum", // we can't test for it at compile-time. #define PAM_BAD_ITEM PAM_SYMBOL_ERR #endif static const char pw[] = "0123456789"; static char *response = ""; static void *pam_module; static enum { TWO_PROMPTS, COMBINED_PASSWORD, COMBINED_PROMPT } conv_mode; static int num_prompts_shown = 0; static int conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { // Keep track of how often the conversation callback is executed. ++num_prompts_shown; if (conv_mode == COMBINED_PASSWORD) { return PAM_CONV_ERR; } if (num_msg == 1 && msg[0]->msg_style == PAM_PROMPT_ECHO_OFF) { *resp = malloc(sizeof(struct pam_response)); assert(*resp); (*resp)->resp = conv_mode == TWO_PROMPTS ? strdup(response) : strcat(strcpy(malloc(sizeof(pw) + strlen(response)), pw), response); (*resp)->resp_retcode = 0; return PAM_SUCCESS; } return PAM_CONV_ERR; } #ifdef sun #define PAM_CONST #else #define PAM_CONST const #endif int pam_get_item(const pam_handle_t *pamh, int item_type, PAM_CONST void **item) __attribute__((visibility("default"))); int pam_get_item(const pam_handle_t *pamh, int item_type, PAM_CONST void **item) { switch (item_type) { case PAM_SERVICE: { static const char *service = "google_authenticator_unittest"; memcpy(item, &service, sizeof(&service)); return PAM_SUCCESS; } case PAM_USER: { char *user = getenv("USER"); memcpy(item, &user, sizeof(&user)); return PAM_SUCCESS; } case PAM_CONV: { static struct pam_conv conv = { .conv = conversation }, *p_conv = &conv; memcpy(item, &p_conv, sizeof(p_conv)); return PAM_SUCCESS; } case PAM_AUTHTOK: { static char *authtok = NULL; if (conv_mode == COMBINED_PASSWORD) { authtok = realloc(authtok, sizeof(pw) + strlen(response)); *item = strcat(strcpy(authtok, pw), response); } else { *item = pw; } return PAM_SUCCESS; } default: return PAM_BAD_ITEM; } } int pam_set_item(pam_handle_t *pamh, int item_type, PAM_CONST void *item) __attribute__((visibility("default"))); int pam_set_item(pam_handle_t *pamh, int item_type, PAM_CONST void *item) { switch (item_type) { case PAM_AUTHTOK: if (strcmp((char *)item, pw)) { return PAM_BAD_ITEM; } return PAM_SUCCESS; default: return PAM_BAD_ITEM; } } static const char *get_error_msg(void) { const char *(*get_error_msg)(void) = (const char *(*)(void))dlsym(pam_module, "get_error_msg"); return get_error_msg ? get_error_msg() : ""; } static void print_diagnostics(int signo) { if (*get_error_msg()) { fprintf(stderr, "%s\n", get_error_msg()); } _exit(1); } static void verify_prompts_shown(int expected_prompts_shown) { assert(num_prompts_shown == expected_prompts_shown); // Reset for the next count. num_prompts_shown = 0; } int main(int argc, char *argv[]) { // Testing Base32 encoding puts("Testing base32 encoding"); static const uint8_t dat[] = "Hello world..."; uint8_t enc[((sizeof(dat) + 4)/5)*8 + 1]; assert(base32_encode(dat, sizeof(dat), enc, sizeof(enc)) == sizeof(enc)-1); assert(!strcmp((char *)enc, "JBSWY3DPEB3W64TMMQXC4LQA")); puts("Testing base32 decoding"); uint8_t dec[sizeof(dat)]; assert(base32_decode(enc, dec, sizeof(dec)) == sizeof(dec)); assert(!memcmp(dat, dec, sizeof(dat))); // Testing HMAC_SHA1 puts("Testing HMAC_SHA1"); uint8_t hmac[20]; hmac_sha1((uint8_t *)"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C" "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19" "\x1A\x1B\x1C\x1D\x1E\x1F !\"#$%&'()*+,-./0123456789:" ";<=>?", 64, (uint8_t *)"Sample #1", 9, hmac, sizeof(hmac)); assert(!memcmp(hmac, (uint8_t []) { 0x4F, 0x4C, 0xA3, 0xD5, 0xD6, 0x8B, 0xA7, 0xCC, 0x0A, 0x12, 0x08, 0xC9, 0xC6, 0x1E, 0x9C, 0x5D, 0xA0, 0x40, 0x3C, 0x0A }, sizeof(hmac))); hmac_sha1((uint8_t *)"0123456789:;<=>?@ABC", 20, (uint8_t *)"Sample #2", 9, hmac, sizeof(hmac)); assert(!memcmp(hmac, (uint8_t []) { 0x09, 0x22, 0xD3, 0x40, 0x5F, 0xAA, 0x3D, 0x19, 0x4F, 0x82, 0xA4, 0x58, 0x30, 0x73, 0x7D, 0x5C, 0xC6, 0xC7, 0x5D, 0x24 }, sizeof(hmac))); hmac_sha1((uint8_t *)"PQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" "\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A" "\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96" "\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2" "\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE" "\xAF\xB0\xB1\xB2\xB3", 100, (uint8_t *)"Sample #3", 9, hmac, sizeof(hmac)); assert(!memcmp(hmac, (uint8_t []) { 0xBC, 0xF4, 0x1E, 0xAB, 0x8B, 0xB2, 0xD8, 0x02, 0xF3, 0xD0, 0x5C, 0xAF, 0x7C, 0xB0, 0x92, 0xEC, 0xF8, 0xD1, 0xA3, 0xAA }, sizeof(hmac))); hmac_sha1((uint8_t *)"pqrstuvwxyz{|}~\x7F\x80\x81\x82\x83\x84\x85\x86\x87" "\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94" "\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0", 49, (uint8_t *)"Sample #4", 9, hmac, sizeof(hmac)); assert(!memcmp(hmac, (uint8_t []) { 0x9E, 0xA8, 0x86, 0xEF, 0xE2, 0x68, 0xDB, 0xEC, 0xCE, 0x42, 0x0C, 0x75, 0x24, 0xDF, 0x32, 0xE0, 0x75, 0x1A, 0x2A, 0x26 }, sizeof(hmac))); // Load the PAM module puts("Loading PAM module"); pam_module = dlopen("./pam_google_authenticator_testing.so", RTLD_LAZY | RTLD_GLOBAL); assert(pam_module != NULL); signal(SIGABRT, print_diagnostics); // Look up public symbols int (*pam_sm_open_session)(pam_handle_t *, int, int, const char **) = (int (*)(pam_handle_t *, int, int, const char **)) dlsym(pam_module, "pam_sm_open_session"); assert(pam_sm_open_session != NULL); // Look up private test-only API void (*set_time)(time_t t) = (void (*)(time_t))dlsym(pam_module, "set_time"); assert(set_time); int (*compute_code)(uint8_t *, int, unsigned long) = (int (*)(uint8_t*, int, unsigned long))dlsym(pam_module, "compute_code"); assert(compute_code); for (int otp_mode = 0; otp_mode < 8; ++otp_mode) { // Create a secret file with a well-known test vector char fn[] = "/tmp/.google_authenticator_XXXXXX"; int fd = mkstemp(fn); assert(fd >= 0); static const uint8_t secret[] = "2SH3V3GDW7ZNMGYE"; assert(write(fd, secret, sizeof(secret)-1) == sizeof(secret)-1); assert(write(fd, "\n\" TOTP_AUTH", 12) == 12); close(fd); uint8_t binary_secret[sizeof(secret)]; size_t binary_secret_len = base32_decode(secret, binary_secret, sizeof(binary_secret)); // Set up test argc/argv parameters to let the PAM module know where to // find our secret file const char *targv[] = { malloc(strlen(fn) + 8), NULL, NULL, NULL, NULL }; strcat(strcpy((char *)targv[0], "secret="), fn); int targc; int expected_good_prompts_shown; int expected_bad_prompts_shown; switch (otp_mode) { case 0: puts("\nRunning tests, querying for verification code"); conv_mode = TWO_PROMPTS; targc = 1; expected_good_prompts_shown = expected_bad_prompts_shown = 1; break; case 1: puts("\nRunning tests, querying for verification code, " "forwarding system pass"); conv_mode = COMBINED_PROMPT; targv[1] = strdup("forward_pass"); targc = 2; expected_good_prompts_shown = expected_bad_prompts_shown = 1; break; case 2: puts("\nRunning tests with use_first_pass"); conv_mode = COMBINED_PASSWORD; targv[1] = strdup("use_first_pass"); targc = 2; expected_good_prompts_shown = expected_bad_prompts_shown = 0; break; case 3: puts("\nRunning tests with use_first_pass, forwarding system pass"); conv_mode = COMBINED_PASSWORD; targv[1] = strdup("use_first_pass"); targv[2] = strdup("forward_pass"); targc = 3; expected_good_prompts_shown = expected_bad_prompts_shown = 0; break; case 4: puts("\nRunning tests with try_first_pass, combining codes"); conv_mode = COMBINED_PASSWORD; targv[1] = strdup("try_first_pass"); targc = 2; expected_good_prompts_shown = 0; expected_bad_prompts_shown = 2; break; case 5: puts("\nRunning tests with try_first_pass, combining codes, " "forwarding system pass"); conv_mode = COMBINED_PASSWORD; targv[1] = strdup("try_first_pass"); targv[2] = strdup("forward_pass"); targc = 3; expected_good_prompts_shown = 0; expected_bad_prompts_shown = 2; break; case 6: puts("\nRunning tests with try_first_pass, querying for codes"); conv_mode = TWO_PROMPTS; targv[1] = strdup("try_first_pass"); targc = 2; expected_good_prompts_shown = expected_bad_prompts_shown = 1; break; default: assert(otp_mode == 7); puts("\nRunning tests with try_first_pass, querying for codes, " "forwarding system pass"); conv_mode = COMBINED_PROMPT; targv[1] = strdup("try_first_pass"); targv[2] = strdup("forward_pass"); targc = 3; expected_good_prompts_shown = expected_bad_prompts_shown = 1; break; } // Make sure num_prompts_shown is still 0. verify_prompts_shown(0); // Set the timestamp that this test vector needs set_time(10000*30); response = "123456"; // Check if we can log in when using an invalid verification code puts("Testing failed login attempt"); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_bad_prompts_shown); // Check required number of digits if (conv_mode == TWO_PROMPTS) { puts("Testing required number of digits"); response = "50548"; assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_bad_prompts_shown); response = "0050548"; assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_bad_prompts_shown); response = "00050548"; assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_bad_prompts_shown); } // Test a blank response puts("Testing a blank response"); response = ""; assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_bad_prompts_shown); // Set the response that we should send back to the authentication module response = "050548"; // Test handling of missing state files puts("Test handling of missing state files"); const char *old_secret = targv[0]; targv[0] = "secret=/NOSUCHFILE"; assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(0); targv[targc++] = "nullok"; targv[targc] = NULL; assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(0); targv[--targc] = NULL; targv[0] = old_secret; // Check if we can log in when using a valid verification code puts("Testing successful login"); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); // Test the WINDOW_SIZE option puts("Testing WINDOW_SIZE option"); for (int *tm = (int []){ 9998, 9999, 10001, 10002, 10000, -1 }, *res = (int []){ PAM_SESSION_ERR, PAM_SUCCESS, PAM_SUCCESS, PAM_SESSION_ERR, PAM_SUCCESS }; *tm >= 0;) { set_time(*tm++ * 30); assert(pam_sm_open_session(NULL, 0, targc, targv) == *res++); verify_prompts_shown(expected_good_prompts_shown); } assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "\n\" WINDOW_SIZE 6\n", 17) == 17); close(fd); for (int *tm = (int []){ 9996, 9997, 10002, 10003, 10000, -1 }, *res = (int []){ PAM_SESSION_ERR, PAM_SUCCESS, PAM_SUCCESS, PAM_SESSION_ERR, PAM_SUCCESS }; *tm >= 0;) { set_time(*tm++ * 30); assert(pam_sm_open_session(NULL, 0, targc, targv) == *res++); verify_prompts_shown(expected_good_prompts_shown); } // Test the DISALLOW_REUSE option puts("Testing DISALLOW_REUSE option"); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "\" DISALLOW_REUSE\n", 17) == 17); close(fd); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_good_prompts_shown); // Test that DISALLOW_REUSE expires old entries from the re-use list char *old_response = response; for (int i = 10001; i < 10008; ++i) { set_time(i * 30); char buf[7]; response = buf; sprintf(response, "%06d", compute_code(binary_secret, binary_secret_len, i)); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); } set_time(10000 * 30); response = old_response; assert((fd = open(fn, O_RDONLY)) >= 0); char state_file_buf[4096] = { 0 }; assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); close(fd); const char *disallow = strstr(state_file_buf, "\" DISALLOW_REUSE "); assert(disallow); assert(!memcmp(disallow + 17, "10002 10003 10004 10005 10006 10007\n", 36)); // Test the RATE_LIMIT option puts("Testing RATE_LIMIT option"); assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "\" RATE_LIMIT 4 120\n", 19) == 19); close(fd); for (int *tm = (int []){ 20000, 20001, 20002, 20003, 20004, 20006, -1 }, *res = (int []){ PAM_SUCCESS, PAM_SUCCESS, PAM_SUCCESS, PAM_SUCCESS, PAM_SESSION_ERR, PAM_SUCCESS, -1 }; *tm >= 0;) { set_time(*tm * 30); char buf[7]; response = buf; sprintf(response, "%06d", compute_code(binary_secret, binary_secret_len, *tm++)); assert(pam_sm_open_session(NULL, 0, targc, targv) == *res); verify_prompts_shown( *res != PAM_SUCCESS ? 0 : expected_good_prompts_shown); ++res; } set_time(10000 * 30); response = old_response; assert(!chmod(fn, 0600)); assert((fd = open(fn, O_RDWR)) >= 0); memset(state_file_buf, 0, sizeof(state_file_buf)); assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); const char *rate_limit = strstr(state_file_buf, "\" RATE_LIMIT "); assert(rate_limit); assert(!memcmp(rate_limit + 13, "4 120 600060 600090 600120 600180\n", 35)); // Test trailing space in RATE_LIMIT. This is considered a file format // error. char *eol = strchr(rate_limit, '\n'); *eol = ' '; assert(!lseek(fd, 0, SEEK_SET)); assert(write(fd, state_file_buf, strlen(state_file_buf)) == strlen(state_file_buf)); close(fd); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(0); assert(!strncmp(get_error_msg(), "Invalid list of timestamps in RATE_LIMIT", 40)); *eol = '\n'; assert(!chmod(fn, 0600)); assert((fd = open(fn, O_WRONLY)) >= 0); assert(write(fd, state_file_buf, strlen(state_file_buf)) == strlen(state_file_buf)); close(fd); // Test TIME_SKEW option puts("Testing TIME_SKEW"); for (int i = 0; i < 4; ++i) { set_time((12000 + i)*30); char buf[7]; response = buf; sprintf(response, "%06d", compute_code(binary_secret, binary_secret_len, 11000 + i)); assert(pam_sm_open_session(NULL, 0, targc, targv) == (i >= 2 ? PAM_SUCCESS : PAM_SESSION_ERR)); verify_prompts_shown(expected_good_prompts_shown); } set_time(12010 * 30); char buf[7]; response = buf; sprintf(response, "%06d", compute_code(binary_secret, binary_secret_len, 11010)); assert(pam_sm_open_session(NULL, 0, 1, (const char *[]){ "noskewadj", 0 }) == PAM_SESSION_ERR); verify_prompts_shown(0); set_time(10000*30); // Test scratch codes puts("Testing scratch codes"); response = "12345678"; assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_bad_prompts_shown); assert(!chmod(fn, 0600)); assert((fd = open(fn, O_APPEND | O_WRONLY)) >= 0); assert(write(fd, "12345678\n", 9) == 9); close(fd); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_bad_prompts_shown); // Set up secret file for counter-based codes. assert(!chmod(fn, 0600)); assert((fd = open(fn, O_TRUNC | O_WRONLY)) >= 0); assert(write(fd, secret, sizeof(secret)-1) == sizeof(secret)-1); assert(write(fd, "\n\" HOTP_COUNTER 1\n", 18) == 18); close(fd); response = "293240"; // Check if we can log in when using a valid verification code puts("Testing successful counter-based login"); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); // Verify that the hotp counter incremented assert((fd = open(fn, O_RDONLY)) >= 0); memset(state_file_buf, 0, sizeof(state_file_buf)); assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); close(fd); const char *hotp_counter = strstr(state_file_buf, "\" HOTP_COUNTER "); assert(hotp_counter); assert(!memcmp(hotp_counter + 15, "2\n", 2)); // Check if we can log in when using an invalid verification code // (including the same code a second time) puts("Testing failed counter-based login attempt"); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SESSION_ERR); verify_prompts_shown(expected_bad_prompts_shown); // Verify that the hotp counter incremented assert((fd = open(fn, O_RDONLY)) >= 0); memset(state_file_buf, 0, sizeof(state_file_buf)); assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); close(fd); hotp_counter = strstr(state_file_buf, "\" HOTP_COUNTER "); assert(hotp_counter); assert(!memcmp(hotp_counter + 15, "3\n", 2)); response = "932068"; // Check if we can log in using a future valid verification code (using // default window_size of 3) puts("Testing successful future counter-based login"); assert(pam_sm_open_session(NULL, 0, targc, targv) == PAM_SUCCESS); verify_prompts_shown(expected_good_prompts_shown); // Verify that the hotp counter incremented assert((fd = open(fn, O_RDONLY)) >= 0); memset(state_file_buf, 0, sizeof(state_file_buf)); assert(read(fd, state_file_buf, sizeof(state_file_buf)-1) > 0); close(fd); hotp_counter = strstr(state_file_buf, "\" HOTP_COUNTER "); assert(hotp_counter); assert(!memcmp(hotp_counter + 15, "6\n", 2)); // Remove the temporarily created secret file unlink(fn); // Release memory for the test arguments for (int i = 0; i < targc; ++i) { free((void *)targv[i]); } } // Unload the PAM module dlclose(pam_module); puts("DONE"); return 0; } google-authenticator/libpam/totp.html0000644000175000017500000002231612151455610017002 0ustar ocsiocsi TOTP Debugger

TOTP Debugger

Enter secret key in BASE32:            
Account label: 
Interval: 30s 
Time:   
Timestamp:   
TOTP:   



WARNING! This website is a development and debugging tool only. Do not use it to generate security tokens for logging into your account. You should never store, process or otherwise access the secret key on the same machine that you use for accessing your account. Doing so completely defeats the security of two-step verification.

Instead, use one of the mobile phone clients made available by the Google Authenticator project. Or follow the instructions provided by Google.

If you ever entered your real secret key into this website, please immediately reset your secret key.

google-authenticator/libpam/sha1.c0000644000175000017500000002570112151455610016127 0ustar ocsiocsi/* * Copyright 2010 Google Inc. * Author: Markus Gutschke * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * An earlier version of this file was originally released into the public * domain by its authors. It has been modified to make the code compile and * link as part of the Google Authenticator project. These changes are * copyrighted by Google Inc. and released under the Apache License, * Version 2.0. * * The previous authors' terms are included below: */ /***************************************************************************** * * File: sha1.c * * Purpose: Implementation of the SHA1 message-digest algorithm. * * NIST Secure Hash Algorithm * Heavily modified by Uwe Hollerbach * from Peter C. Gutmann's implementation as found in * Applied Cryptography by Bruce Schneier * Further modifications to include the "UNRAVEL" stuff, below * * This code is in the public domain * ***************************************************************************** */ #define _BSD_SOURCE #include // Defines BYTE_ORDER, iff _BSD_SOURCE is defined #include #include "sha1.h" #if !defined(BYTE_ORDER) #if defined(_BIG_ENDIAN) #define BYTE_ORDER 4321 #elif defined(_LITTLE_ENDIAN) #define BYTE_ORDER 1234 #else #error Need to define BYTE_ORDER #endif #endif #ifndef TRUNC32 #define TRUNC32(x) ((x) & 0xffffffffL) #endif /* SHA f()-functions */ #define f1(x,y,z) ((x & y) | (~x & z)) #define f2(x,y,z) (x ^ y ^ z) #define f3(x,y,z) ((x & y) | (x & z) | (y & z)) #define f4(x,y,z) (x ^ y ^ z) /* SHA constants */ #define CONST1 0x5a827999L #define CONST2 0x6ed9eba1L #define CONST3 0x8f1bbcdcL #define CONST4 0xca62c1d6L /* truncate to 32 bits -- should be a null op on 32-bit machines */ #define T32(x) ((x) & 0xffffffffL) /* 32-bit rotate */ #define R32(x,n) T32(((x << n) | (x >> (32 - n)))) /* the generic case, for when the overall rotation is not unraveled */ #define FG(n) \ T = T32(R32(A,5) + f##n(B,C,D) + E + *WP++ + CONST##n); \ E = D; D = C; C = R32(B,30); B = A; A = T /* specific cases, for when the overall rotation is unraveled */ #define FA(n) \ T = T32(R32(A,5) + f##n(B,C,D) + E + *WP++ + CONST##n); B = R32(B,30) #define FB(n) \ E = T32(R32(T,5) + f##n(A,B,C) + D + *WP++ + CONST##n); A = R32(A,30) #define FC(n) \ D = T32(R32(E,5) + f##n(T,A,B) + C + *WP++ + CONST##n); T = R32(T,30) #define FD(n) \ C = T32(R32(D,5) + f##n(E,T,A) + B + *WP++ + CONST##n); E = R32(E,30) #define FE(n) \ B = T32(R32(C,5) + f##n(D,E,T) + A + *WP++ + CONST##n); D = R32(D,30) #define FT(n) \ A = T32(R32(B,5) + f##n(C,D,E) + T + *WP++ + CONST##n); C = R32(C,30) static void sha1_transform(SHA1_INFO *sha1_info) { int i; uint8_t *dp; uint32_t T, A, B, C, D, E, W[80], *WP; dp = sha1_info->data; #undef SWAP_DONE #if BYTE_ORDER == 1234 #define SWAP_DONE for (i = 0; i < 16; ++i) { T = *((uint32_t *) dp); dp += 4; W[i] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) | ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff); } #endif #if BYTE_ORDER == 4321 #define SWAP_DONE for (i = 0; i < 16; ++i) { T = *((uint32_t *) dp); dp += 4; W[i] = TRUNC32(T); } #endif #if BYTE_ORDER == 12345678 #define SWAP_DONE for (i = 0; i < 16; i += 2) { T = *((uint32_t *) dp); dp += 8; W[i] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) | ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff); T >>= 32; W[i+1] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) | ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff); } #endif #if BYTE_ORDER == 87654321 #define SWAP_DONE for (i = 0; i < 16; i += 2) { T = *((uint32_t *) dp); dp += 8; W[i] = TRUNC32(T >> 32); W[i+1] = TRUNC32(T); } #endif #ifndef SWAP_DONE #define SWAP_DONE for (i = 0; i < 16; ++i) { T = *((uint32_t *) dp); dp += 4; W[i] = TRUNC32(T); } #endif /* SWAP_DONE */ for (i = 16; i < 80; ++i) { W[i] = W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]; W[i] = R32(W[i], 1); } A = sha1_info->digest[0]; B = sha1_info->digest[1]; C = sha1_info->digest[2]; D = sha1_info->digest[3]; E = sha1_info->digest[4]; WP = W; #ifdef UNRAVEL FA(1); FB(1); FC(1); FD(1); FE(1); FT(1); FA(1); FB(1); FC(1); FD(1); FE(1); FT(1); FA(1); FB(1); FC(1); FD(1); FE(1); FT(1); FA(1); FB(1); FC(2); FD(2); FE(2); FT(2); FA(2); FB(2); FC(2); FD(2); FE(2); FT(2); FA(2); FB(2); FC(2); FD(2); FE(2); FT(2); FA(2); FB(2); FC(2); FD(2); FE(3); FT(3); FA(3); FB(3); FC(3); FD(3); FE(3); FT(3); FA(3); FB(3); FC(3); FD(3); FE(3); FT(3); FA(3); FB(3); FC(3); FD(3); FE(3); FT(3); FA(4); FB(4); FC(4); FD(4); FE(4); FT(4); FA(4); FB(4); FC(4); FD(4); FE(4); FT(4); FA(4); FB(4); FC(4); FD(4); FE(4); FT(4); FA(4); FB(4); sha1_info->digest[0] = T32(sha1_info->digest[0] + E); sha1_info->digest[1] = T32(sha1_info->digest[1] + T); sha1_info->digest[2] = T32(sha1_info->digest[2] + A); sha1_info->digest[3] = T32(sha1_info->digest[3] + B); sha1_info->digest[4] = T32(sha1_info->digest[4] + C); #else /* !UNRAVEL */ #ifdef UNROLL_LOOPS FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(1); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(2); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(3); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); FG(4); #else /* !UNROLL_LOOPS */ for (i = 0; i < 20; ++i) { FG(1); } for (i = 20; i < 40; ++i) { FG(2); } for (i = 40; i < 60; ++i) { FG(3); } for (i = 60; i < 80; ++i) { FG(4); } #endif /* !UNROLL_LOOPS */ sha1_info->digest[0] = T32(sha1_info->digest[0] + A); sha1_info->digest[1] = T32(sha1_info->digest[1] + B); sha1_info->digest[2] = T32(sha1_info->digest[2] + C); sha1_info->digest[3] = T32(sha1_info->digest[3] + D); sha1_info->digest[4] = T32(sha1_info->digest[4] + E); #endif /* !UNRAVEL */ } /* initialize the SHA digest */ void sha1_init(SHA1_INFO *sha1_info) { sha1_info->digest[0] = 0x67452301L; sha1_info->digest[1] = 0xefcdab89L; sha1_info->digest[2] = 0x98badcfeL; sha1_info->digest[3] = 0x10325476L; sha1_info->digest[4] = 0xc3d2e1f0L; sha1_info->count_lo = 0L; sha1_info->count_hi = 0L; sha1_info->local = 0; } /* update the SHA digest */ void sha1_update(SHA1_INFO *sha1_info, const uint8_t *buffer, int count) { int i; uint32_t clo; clo = T32(sha1_info->count_lo + ((uint32_t) count << 3)); if (clo < sha1_info->count_lo) { ++sha1_info->count_hi; } sha1_info->count_lo = clo; sha1_info->count_hi += (uint32_t) count >> 29; if (sha1_info->local) { i = SHA1_BLOCKSIZE - sha1_info->local; if (i > count) { i = count; } memcpy(((uint8_t *) sha1_info->data) + sha1_info->local, buffer, i); count -= i; buffer += i; sha1_info->local += i; if (sha1_info->local == SHA1_BLOCKSIZE) { sha1_transform(sha1_info); } else { return; } } while (count >= SHA1_BLOCKSIZE) { memcpy(sha1_info->data, buffer, SHA1_BLOCKSIZE); buffer += SHA1_BLOCKSIZE; count -= SHA1_BLOCKSIZE; sha1_transform(sha1_info); } memcpy(sha1_info->data, buffer, count); sha1_info->local = count; } static void sha1_transform_and_copy(unsigned char digest[20], SHA1_INFO *sha1_info) { sha1_transform(sha1_info); digest[ 0] = (unsigned char) ((sha1_info->digest[0] >> 24) & 0xff); digest[ 1] = (unsigned char) ((sha1_info->digest[0] >> 16) & 0xff); digest[ 2] = (unsigned char) ((sha1_info->digest[0] >> 8) & 0xff); digest[ 3] = (unsigned char) ((sha1_info->digest[0] ) & 0xff); digest[ 4] = (unsigned char) ((sha1_info->digest[1] >> 24) & 0xff); digest[ 5] = (unsigned char) ((sha1_info->digest[1] >> 16) & 0xff); digest[ 6] = (unsigned char) ((sha1_info->digest[1] >> 8) & 0xff); digest[ 7] = (unsigned char) ((sha1_info->digest[1] ) & 0xff); digest[ 8] = (unsigned char) ((sha1_info->digest[2] >> 24) & 0xff); digest[ 9] = (unsigned char) ((sha1_info->digest[2] >> 16) & 0xff); digest[10] = (unsigned char) ((sha1_info->digest[2] >> 8) & 0xff); digest[11] = (unsigned char) ((sha1_info->digest[2] ) & 0xff); digest[12] = (unsigned char) ((sha1_info->digest[3] >> 24) & 0xff); digest[13] = (unsigned char) ((sha1_info->digest[3] >> 16) & 0xff); digest[14] = (unsigned char) ((sha1_info->digest[3] >> 8) & 0xff); digest[15] = (unsigned char) ((sha1_info->digest[3] ) & 0xff); digest[16] = (unsigned char) ((sha1_info->digest[4] >> 24) & 0xff); digest[17] = (unsigned char) ((sha1_info->digest[4] >> 16) & 0xff); digest[18] = (unsigned char) ((sha1_info->digest[4] >> 8) & 0xff); digest[19] = (unsigned char) ((sha1_info->digest[4] ) & 0xff); } /* finish computing the SHA digest */ void sha1_final(SHA1_INFO *sha1_info, uint8_t digest[20]) { int count; uint32_t lo_bit_count, hi_bit_count; lo_bit_count = sha1_info->count_lo; hi_bit_count = sha1_info->count_hi; count = (int) ((lo_bit_count >> 3) & 0x3f); ((uint8_t *) sha1_info->data)[count++] = 0x80; if (count > SHA1_BLOCKSIZE - 8) { memset(((uint8_t *) sha1_info->data) + count, 0, SHA1_BLOCKSIZE - count); sha1_transform(sha1_info); memset((uint8_t *) sha1_info->data, 0, SHA1_BLOCKSIZE - 8); } else { memset(((uint8_t *) sha1_info->data) + count, 0, SHA1_BLOCKSIZE - 8 - count); } sha1_info->data[56] = (uint8_t)((hi_bit_count >> 24) & 0xff); sha1_info->data[57] = (uint8_t)((hi_bit_count >> 16) & 0xff); sha1_info->data[58] = (uint8_t)((hi_bit_count >> 8) & 0xff); sha1_info->data[59] = (uint8_t)((hi_bit_count >> 0) & 0xff); sha1_info->data[60] = (uint8_t)((lo_bit_count >> 24) & 0xff); sha1_info->data[61] = (uint8_t)((lo_bit_count >> 16) & 0xff); sha1_info->data[62] = (uint8_t)((lo_bit_count >> 8) & 0xff); sha1_info->data[63] = (uint8_t)((lo_bit_count >> 0) & 0xff); sha1_transform_and_copy(digest, sha1_info); } /***EOF***/ google-authenticator/libpam/google-authenticator.c0000644000175000017500000006004012151455610021412 0ustar ocsiocsi// Helper program to generate a new secret for use in two-factor // authentication. // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "base32.h" #include "hmac.h" #include "sha1.h" #define SECRET "/.google_authenticator" #define SECRET_BITS 80 // Must be divisible by eight #define VERIFICATION_CODE_MODULUS (1000*1000) // Six digits #define SCRATCHCODES 5 // Number of initial scratchcodes #define SCRATCHCODE_LENGTH 8 // Eight digits per scratchcode #define BYTES_PER_SCRATCHCODE 4 // 32bit of randomness is enough #define BITS_PER_BASE32_CHAR 5 // Base32 expands space by 8/5 static enum { QR_UNSET=0, QR_NONE, QR_ANSI, QR_UTF8 } qr_mode = QR_UNSET; static int generateCode(const char *key, unsigned long tm) { uint8_t challenge[8]; for (int i = 8; i--; tm >>= 8) { challenge[i] = tm; } // Estimated number of bytes needed to represent the decoded secret. Because // of white-space and separators, this is an upper bound of the real number, // which we later get as a return-value from base32_decode() int secretLen = (strlen(key) + 7)/8*BITS_PER_BASE32_CHAR; // Sanity check, that our secret will fixed into a reasonably-sized static // array. if (secretLen <= 0 || secretLen > 100) { return -1; } // Decode secret from Base32 to a binary representation, and check that we // have at least one byte's worth of secret data. uint8_t secret[100]; if ((secretLen = base32_decode((const uint8_t *)key, secret, secretLen))<1) { return -1; } // Compute the HMAC_SHA1 of the secrete and the challenge. uint8_t hash[SHA1_DIGEST_LENGTH]; hmac_sha1(secret, secretLen, challenge, 8, hash, SHA1_DIGEST_LENGTH); // Pick the offset where to sample our hash value for the actual verification // code. int offset = hash[SHA1_DIGEST_LENGTH - 1] & 0xF; // Compute the truncated hash in a byte-order independent loop. unsigned int truncatedHash = 0; for (int i = 0; i < 4; ++i) { truncatedHash <<= 8; truncatedHash |= hash[offset + i]; } // Truncate to a smaller number of digits. truncatedHash &= 0x7FFFFFFF; truncatedHash %= VERIFICATION_CODE_MODULUS; return truncatedHash; } static const char *getUserName(uid_t uid) { struct passwd pwbuf, *pw; char *buf; #ifdef _SC_GETPW_R_SIZE_MAX int len = sysconf(_SC_GETPW_R_SIZE_MAX); if (len <= 0) { len = 4096; } #else int len = 4096; #endif buf = malloc(len); char *user; if (getpwuid_r(uid, &pwbuf, buf, len, &pw) || !pw) { user = malloc(32); snprintf(user, 32, "%d", uid); } else { user = strdup(pw->pw_name); if (!user) { perror("malloc()"); _exit(1); } } free(buf); return user; } static const char *urlEncode(const char *s) { char *ret = malloc(3*strlen(s) + 1); char *d = ret; do { switch (*s) { case '%': case '&': case '?': case '=': encode: sprintf(d, "%%%02X", (unsigned char)*s); d += 3; break; default: if ((*s && *s <= ' ') || *s >= '\x7F') { goto encode; } *d++ = *s; break; } } while (*s++); ret = realloc(ret, strlen(ret) + 1); return ret; } static const char *getURL(const char *secret, const char *label, char **encoderURL, const int use_totp) { const char *encodedLabel = urlEncode(label); char *url = malloc(strlen(encodedLabel) + strlen(secret) + 80); char totp = 'h'; if (use_totp) { totp = 't'; } sprintf(url, "otpauth://%cotp/%s?secret=%s", totp, encodedLabel, secret); if (encoderURL) { const char *encoder = "https://www.google.com/chart?chs=200x200&" "chld=M|0&cht=qr&chl="; const char *encodedURL = urlEncode(url); *encoderURL = strcat(strcpy(malloc(strlen(encoder) + strlen(encodedURL) + 1), encoder), encodedURL); free((void *)encodedURL); } free((void *)encodedLabel); return url; } #define ANSI_RESET "\x1B[0m" #define ANSI_BLACKONGREY "\x1B[30;47;27m" #define ANSI_WHITE "\x1B[27m" #define ANSI_BLACK "\x1B[7m" #define UTF8_BOTH "\xE2\x96\x88" #define UTF8_TOPHALF "\xE2\x96\x80" #define UTF8_BOTTOMHALF "\xE2\x96\x84" static void displayQRCode(const char *secret, const char *label, const int use_totp) { if (qr_mode == QR_NONE) { return; } char *encoderURL; const char *url = getURL(secret, label, &encoderURL, use_totp); puts(encoderURL); // Only newer systems have support for libqrencode. So, instead of requiring // it at build-time, we look for it at run-time. If it cannot be found, the // user can still type the code in manually, or he can copy the URL into // his browser. if (isatty(1)) { void *qrencode = dlopen("libqrencode.so.2", RTLD_NOW | RTLD_LOCAL); if (!qrencode) { qrencode = dlopen("libqrencode.so.3", RTLD_NOW | RTLD_LOCAL); } if (qrencode) { typedef struct { int version; int width; unsigned char *data; } QRcode; QRcode *(*QRcode_encodeString8bit)(const char *, int, int) = (QRcode *(*)(const char *, int, int)) dlsym(qrencode, "QRcode_encodeString8bit"); void (*QRcode_free)(QRcode *qrcode) = (void (*)(QRcode *))dlsym(qrencode, "QRcode_free"); if (QRcode_encodeString8bit && QRcode_free) { QRcode *qrcode = QRcode_encodeString8bit(url, 0, 1); char *ptr = (char *)qrcode->data; // Output QRCode using ANSI colors. Instead of black on white, we // output black on grey, as that works independently of whether the // user runs his terminals in a black on white or white on black color // scheme. // But this requires that we print a border around the entire QR Code. // Otherwise, readers won't be able to recognize it. if (qr_mode != QR_UTF8) { for (int i = 0; i < 2; ++i) { printf(ANSI_BLACKONGREY); for (int x = 0; x < qrcode->width + 4; ++x) printf(" "); puts(ANSI_RESET); } for (int y = 0; y < qrcode->width; ++y) { printf(ANSI_BLACKONGREY" "); int isBlack = 0; for (int x = 0; x < qrcode->width; ++x) { if (*ptr++ & 1) { if (!isBlack) { printf(ANSI_BLACK); } isBlack = 1; } else { if (isBlack) { printf(ANSI_WHITE); } isBlack = 0; } printf(" "); } if (isBlack) { printf(ANSI_WHITE); } puts(" "ANSI_RESET); } for (int i = 0; i < 2; ++i) { printf(ANSI_BLACKONGREY); for (int x = 0; x < qrcode->width + 4; ++x) printf(" "); puts(ANSI_RESET); } } else { // Drawing the QRCode with Unicode block elements is desirable as // it makes the code much smaller, which is often easier to scan. // Unfortunately, many terminal emulators do not display these // Unicode characters properly. printf(ANSI_BLACKONGREY); for (int i = 0; i < qrcode->width + 4; ++i) { printf(" "); } puts(ANSI_RESET); for (int y = 0; y < qrcode->width; y += 2) { printf(ANSI_BLACKONGREY" "); for (int x = 0; x < qrcode->width; ++x) { int top = qrcode->data[y*qrcode->width + x] & 1; int bottom = 0; if (y+1 < qrcode->width) { bottom = qrcode->data[(y+1)*qrcode->width + x] & 1; } if (top) { if (bottom) { printf(UTF8_BOTH); } else { printf(UTF8_TOPHALF); } } else { if (bottom) { printf(UTF8_BOTTOMHALF); } else { printf(" "); } } } puts(" "ANSI_RESET); } printf(ANSI_BLACKONGREY); for (int i = 0; i < qrcode->width + 4; ++i) { printf(" "); } puts(ANSI_RESET); } QRcode_free(qrcode); } dlclose(qrencode); } } free((char *)url); free(encoderURL); } static int maybe(const char *msg) { printf("\n%s (y/n) ", msg); fflush(stdout); char ch; do { ch = getchar(); } while (ch == ' ' || ch == '\r' || ch == '\n'); if (ch == 'y' || ch == 'Y') { return 1; } return 0; } static char *addOption(char *buf, size_t nbuf, const char *option) { assert(strlen(buf) + strlen(option) < nbuf); char *scratchCodes = strchr(buf, '\n'); assert(scratchCodes); scratchCodes++; memmove(scratchCodes + strlen(option), scratchCodes, strlen(scratchCodes) + 1); memcpy(scratchCodes, option, strlen(option)); return buf; } static char *maybeAddOption(const char *msg, char *buf, size_t nbuf, const char *option) { if (maybe(msg)) { buf = addOption(buf, nbuf, option); } return buf; } static void usage(void) { puts( "google-authenticator []\n" " -h, --help Print this message\n" " -c, --counter-based Set up counter-based (HOTP) verification\n" " -t, --time-based Set up time-based (TOTP) verification\n" " -d, --disallow-reuse Disallow reuse of previously used TOTP tokens\n" " -D, --allow-reuse Allow reuse of previously used TOTP tokens\n" " -f, --force Write file without first confirming with user\n" " -l, --label=