diff options
Diffstat (limited to 'misc.c')
-rw-r--r-- | misc.c | 238 |
1 files changed, 200 insertions, 38 deletions
@@ -5,7 +5,7 @@ * packet encryption, packet authentication, and * packet compression. * - * Copyright (C) 2002-2009 OpenVPN Technologies, Inc. <sales@openvpn.net> + * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 @@ -26,6 +26,7 @@ #include "buffer.h" #include "misc.h" +#include "base64.h" #include "tun.h" #include "error.h" #include "thread.h" @@ -155,9 +156,8 @@ set_nice (int niceval) { #ifdef HAVE_NICE errno = 0; - nice (niceval); - if (errno != 0) - msg (M_WARN | M_ERRNO, "WARNING: nice %d failed", niceval); + if (nice (niceval) < 0 && errno != 0) + msg (M_WARN | M_ERRNO, "WARNING: nice %d failed: %s", niceval, strerror(errno)); else msg (M_INFO, "nice %d succeeded", niceval); #else @@ -1168,25 +1168,57 @@ test_file (const char *filename) /* create a temporary filename in directory */ const char * -create_temp_filename (const char *directory, const char *prefix, struct gc_arena *gc) +create_temp_file (const char *directory, const char *prefix, struct gc_arena *gc) { static unsigned int counter; struct buffer fname = alloc_buf_gc (256, gc); + int fd; + const char *retfname = NULL; + unsigned int attempts = 0; - mutex_lock_static (L_CREATE_TEMP); - ++counter; - mutex_unlock_static (L_CREATE_TEMP); - - { - uint8_t rndbytes[16]; - const char *rndstr; - - prng_bytes (rndbytes, sizeof (rndbytes)); - rndstr = format_hex_ex (rndbytes, sizeof (rndbytes), 40, 0, NULL, gc); - buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr); - } + do + { + uint8_t rndbytes[16]; + const char *rndstr; + + ++attempts; + mutex_lock_static (L_CREATE_TEMP); + ++counter; + mutex_unlock_static (L_CREATE_TEMP); + + prng_bytes (rndbytes, sizeof rndbytes); + rndstr = format_hex_ex (rndbytes, sizeof rndbytes, 40, 0, NULL, gc); + buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr); + + retfname = gen_path (directory, BSTR (&fname), gc); + if (!retfname) + { + msg (M_FATAL, "Failed to create temporary filename and path"); + return NULL; + } + + /* Atomically create the file. Errors out if the file already + exists. */ + fd = open (retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd != -1) + { + close (fd); + return retfname; + } + else if (fd == -1 && errno != EEXIST) + { + /* Something else went wrong, no need to retry. */ + struct gc_arena gcerr = gc_new (); + msg (M_FATAL, "Could not create temporary file '%s': %s", + retfname, strerror_ts (errno, &gcerr)); + gc_free (&gcerr); + return NULL; + } + } + while (attempts < 6); - return gen_path (directory, BSTR (&fname), gc); + msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts); + return NULL; } /* @@ -1197,7 +1229,7 @@ create_temp_filename (const char *directory, const char *prefix, struct gc_arena const char * hostname_randomize(const char *hostname, struct gc_arena *gc) { - const int n_rnd_bytes = 6; +# define n_rnd_bytes 6 char *hst = string_alloc(hostname, gc); char *dot = strchr(hst, '.'); @@ -1216,6 +1248,7 @@ hostname_randomize(const char *hostname, struct gc_arena *gc) } else return hostname; +# undef n_rnd_bytes } #else @@ -1365,10 +1398,11 @@ get_console_input (const char *prompt, const bool echo, char *input, const int c */ bool -get_user_pass (struct user_pass *up, - const char *auth_file, - const char *prefix, - const unsigned int flags) +get_user_pass_cr (struct user_pass *up, + const char *auth_file, + const char *prefix, + const unsigned int flags, + const char *auth_challenge) { struct gc_arena gc = gc_new (); @@ -1376,14 +1410,20 @@ get_user_pass (struct user_pass *up, { const bool from_stdin = (!auth_file || !strcmp (auth_file, "stdin")); + if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED) + msg (M_WARN, "Note: previous '%s' credentials failed", prefix); + #ifdef ENABLE_MANAGEMENT /* - * Get username/password from standard input? + * Get username/password from management interface? */ if (management && ((auth_file && streq (auth_file, "management")) || (from_stdin && (flags & GET_USER_PASS_MANAGEMENT))) && management_query_user_pass_enabled (management)) { + if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED) + management_auth_failure (management, prefix, "previous auth credentials failed"); + if (!management_query_user_pass (management, up, prefix, flags)) { if ((flags & GET_USER_PASS_NOFATAL) != 0) @@ -1415,22 +1455,47 @@ get_user_pass (struct user_pass *up, */ else if (from_stdin) { - struct buffer user_prompt = alloc_buf_gc (128, &gc); - struct buffer pass_prompt = alloc_buf_gc (128, &gc); - - buf_printf (&user_prompt, "Enter %s Username:", prefix); - buf_printf (&pass_prompt, "Enter %s Password:", prefix); - - if (!(flags & GET_USER_PASS_PASSWORD_ONLY)) +#ifdef ENABLE_CLIENT_CR + if (auth_challenge) { - if (!get_console_input (BSTR (&user_prompt), true, up->username, USER_PASS_LEN)) - msg (M_FATAL, "ERROR: could not read %s username from stdin", prefix); - if (strlen (up->username) == 0) - msg (M_FATAL, "ERROR: %s username is empty", prefix); + struct auth_challenge_info *ac = get_auth_challenge (auth_challenge, &gc); + if (ac) + { + char *response = (char *) gc_malloc (USER_PASS_LEN, false, &gc); + struct buffer packed_resp; + + buf_set_write (&packed_resp, (uint8_t*)up->password, USER_PASS_LEN); + msg (M_INFO, "CHALLENGE: %s", ac->challenge_text); + if (!get_console_input ("Response:", BOOL_CAST(ac->flags&CR_ECHO), response, USER_PASS_LEN)) + msg (M_FATAL, "ERROR: could not read challenge response from stdin"); + strncpynt (up->username, ac->user, USER_PASS_LEN); + buf_printf (&packed_resp, "CRV1::%s::%s", ac->state_id, response); + } + else + { + msg (M_FATAL, "ERROR: received malformed challenge request from server"); + } } + else +#endif + { + struct buffer user_prompt = alloc_buf_gc (128, &gc); + struct buffer pass_prompt = alloc_buf_gc (128, &gc); + + buf_printf (&user_prompt, "Enter %s Username:", prefix); + buf_printf (&pass_prompt, "Enter %s Password:", prefix); - if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN)) - msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix); + if (!(flags & GET_USER_PASS_PASSWORD_ONLY)) + { + if (!get_console_input (BSTR (&user_prompt), true, up->username, USER_PASS_LEN)) + msg (M_FATAL, "ERROR: could not read %s username from stdin", prefix); + if (strlen (up->username) == 0) + msg (M_FATAL, "ERROR: %s username is empty", prefix); + } + + if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN)) + msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix); + } } else { @@ -1494,6 +1559,101 @@ get_user_pass (struct user_pass *up, return true; } +#ifdef ENABLE_CLIENT_CR + +/* + * Parse a challenge message returned along with AUTH_FAILED. + * The message is formatted as such: + * + * CRV1:<flags>:<state_id>:<username_base64>:<challenge_text> + * + * flags: a series of optional, comma-separated flags: + * E : echo the response when the user types it + * R : a response is required + * + * state_id: an opaque string that should be returned to the server + * along with the response. + * + * username_base64 : the username formatted as base64 + * + * challenge_text : the challenge text to be shown to the user + * + * Example challenge: + * + * CRV1:R,E:Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l:Y3Ix:Please enter token PIN + * + * After showing the challenge_text and getting a response from the user + * (if R flag is specified), the client should submit the following + * auth creds back to the OpenVPN server: + * + * Username: [username decoded from username_base64] + * Password: CRV1::<state_id>::<response_text> + * + * Where state_id is taken from the challenge request and response_text + * is what the user entered in response to the challenge_text. + * If the R flag is not present, response_text may be the empty + * string. + * + * Example response (suppose the user enters "8675309" for the token PIN): + * + * Username: cr1 ("Y3Ix" base64 decoded) + * Password: CRV1::Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l::8675309 + */ +struct auth_challenge_info * +get_auth_challenge (const char *auth_challenge, struct gc_arena *gc) +{ + if (auth_challenge) + { + struct auth_challenge_info *ac; + const int len = strlen (auth_challenge); + char *work = (char *) gc_malloc (len+1, false, gc); + char *cp; + + struct buffer b; + buf_set_read (&b, (const uint8_t *)auth_challenge, len); + + ALLOC_OBJ_CLEAR_GC (ac, struct auth_challenge_info, gc); + + /* parse prefix */ + if (!buf_parse(&b, ':', work, len)) + return NULL; + if (strcmp(work, "CRV1")) + return NULL; + + /* parse flags */ + if (!buf_parse(&b, ':', work, len)) + return NULL; + for (cp = work; *cp != '\0'; ++cp) + { + const char c = *cp; + if (c == 'E') + ac->flags |= CR_ECHO; + else if (c == 'R') + ac->flags |= CR_RESPONSE; + } + + /* parse state ID */ + if (!buf_parse(&b, ':', work, len)) + return NULL; + ac->state_id = string_alloc(work, gc); + + /* parse user name */ + if (!buf_parse(&b, ':', work, len)) + return NULL; + ac->user = (char *) gc_malloc (strlen(work)+1, true, gc); + base64_decode(work, (void*)ac->user); + + /* parse challenge text */ + ac->challenge_text = string_alloc(BSTR(&b), gc); + + return ac; + } + else + return NULL; +} + +#endif + #if AUTO_USERID static const char * @@ -1558,14 +1718,16 @@ void purge_user_pass (struct user_pass *up, const bool force) { const bool nocache = up->nocache; + static bool warn_shown = false; if (nocache || force) { CLEAR (*up); up->nocache = nocache; } - else + else if (!warn_shown) { msg (M_WARN, "WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this"); + warn_shown = true; } } |