Skip to content

Commit

Permalink
Attempt to handle Pulse password/passcode auth flow better
Browse files Browse the repository at this point in the history
Lots of special cases here for primary/secondary and retries.

Signed-off-by: David Woodhouse <dwmw2@infradead.org>
  • Loading branch information
dwmw2 committed Jun 28, 2019
1 parent 717b813 commit 45cbcd2
Showing 1 changed file with 185 additions and 72 deletions.
257 changes: 185 additions & 72 deletions pulse.c
Expand Up @@ -73,6 +73,12 @@
#define TTLS_RECV gnutls_record_recv
#endif

/* Flags for prompt handling during authentication, based on the contents of the 0xd73 AVP (qv). */
#define PROMPT_PRIMARY 1
#define PROMPT_USERNAME 2
#define PROMPT_PASSWORD 4
#define PROMPT_GTC_NEXT 0x10000

static void buf_append_be16(struct oc_text_buf *buf, uint16_t val)
{
unsigned char b[2];
Expand Down Expand Up @@ -767,8 +773,8 @@ static int pulse_request_realm_entry(struct openconnect_info *vpninfo, struct oc

memset(&f, 0, sizeof(f));
memset(&o, 0, sizeof(o));
f.auth_id = (char *)"pulse_realm_entry";
f.opts = &o;
f.auth_id = (char *)"pulse_realm_entry";
f.opts = &o;

f.message = _("Enter Pulse user realm:");

Expand Down Expand Up @@ -990,32 +996,40 @@ static int pulse_request_session_kill(struct openconnect_info *vpninfo, struct o
}

static int pulse_request_user_auth(struct openconnect_info *vpninfo, struct oc_text_buf *reqbuf,
uint8_t eap_ident, char *user_prompt, char *pass_prompt)
uint8_t eap_ident, int prompt_flags, char *user_prompt, char *pass_prompt)
{
struct oc_auth_form f;
struct oc_form_opt o[2];
unsigned char eap_avp[23];
int l;
int ret;

memset(&f, 0, sizeof(f));
memset(o, 0, sizeof(o));
f.auth_id = (char *)"pulse_user";
f.opts = &o[0];

f.message = _("Enter user credentials:");

o[0].next = &o[1];
o[0].type = OC_FORM_OPT_TEXT;
o[0].name = (char *)"username";
if (!user_prompt || asprintf(&o[0].label, "%s:", user_prompt) < 0) {
user_prompt = NULL;
o[0].label = (char *)_("Username:");
f.auth_id = (char *) ((prompt_flags & PROMPT_PRIMARY) ? "pulse_user" : "pulse_secondary");
f.opts = &o[1]; /* Point to password prompt in case that's all we use */

f.message = (prompt_flags & PROMPT_PRIMARY) ? _("Enter user credentials:") : _("Enter secondary credentials:");

if (prompt_flags & PROMPT_USERNAME) {
f.opts = &o[0];
o[0].next = NULL; /* Again, for now */
o[0].type = OC_FORM_OPT_TEXT;
o[0].name = (char *)"username";
if (user_prompt)
o[0].label = user_prompt;
else
o[0].label = (char *) ((prompt_flags & PROMPT_PRIMARY) ? _("Username:") : _("Secondary username:"));
}

o[1].type = OC_FORM_OPT_PASSWORD;
o[1].name = (char *)"password";
if (!pass_prompt || asprintf(&o[1].label, "%s:", pass_prompt) < 0) {
pass_prompt = NULL;
o[1].label = (char *)_("Password:");
if (prompt_flags & PROMPT_PASSWORD) {
/* Might be referenced from o[0] or directly from f.opts */
o[0].next = &o[1];
o[1].type = OC_FORM_OPT_PASSWORD;
o[1].name = (char *)"password";
if (pass_prompt)
o[1].label = pass_prompt;
else
o[1].label = (char *) ((prompt_flags & PROMPT_PRIMARY) ? _("Password:") : _("Secondary password:"));
}

ret = process_auth_form(vpninfo, &f);
Expand All @@ -1027,74 +1041,99 @@ static int pulse_request_user_auth(struct openconnect_info *vpninfo, struct oc_t
free_pass(&o[0]._value);
}
if (o[1]._value) {
unsigned char eap_avp[23];
int l = strlen(o[1]._value);
l = strlen(o[1]._value);
if (l > 253) {
free_pass(&o[1]._value);
return -EINVAL;
}
} else {
/* Their client actually resubmits the primary password when
* a secondary password is requested. But it doesn't seem to
* be necessary; might even just be a bug. */
l = 0;
}

/* AVP flags+mandatory+length */
store_be32(eap_avp, AVP_CODE_EAP_MESSAGE);
store_be32(eap_avp + 4, (AVP_MANDATORY << 24) + sizeof(eap_avp) + l);

/* EAP header: code/ident/len */
eap_avp[8] = EAP_RESPONSE;
eap_avp[9] = eap_ident;
store_be16(eap_avp + 10, l + 15); /* EAP length */
store_be32(eap_avp + 12, EXPANDED_JUNIPER);
store_be32(eap_avp + 16, 2);

/* EAP Juniper/2 payload: 02 02 <len> <password> */
eap_avp[20] = eap_avp[21] = 0x02;
eap_avp[22] = l + 2; /* Why 2? */
buf_append_bytes(reqbuf, eap_avp, sizeof(eap_avp));
/* AVP flags+mandatory+length */
store_be32(eap_avp, AVP_CODE_EAP_MESSAGE);
store_be32(eap_avp + 4, (AVP_MANDATORY << 24) + sizeof(eap_avp) + l);

/* EAP header: code/ident/len */
eap_avp[8] = EAP_RESPONSE;
eap_avp[9] = eap_ident;
store_be16(eap_avp + 10, l + 15); /* EAP length */
store_be32(eap_avp + 12, EXPANDED_JUNIPER);
store_be32(eap_avp + 16, 2);

/* EAP Juniper/2 payload: 02 02 <len> <password> */
eap_avp[20] = eap_avp[21] = 0x02;
eap_avp[22] = l + 2; /* Why 2? */
buf_append_bytes(reqbuf, eap_avp, sizeof(eap_avp));
if (o[1]._value) {
buf_append_bytes(reqbuf, o[1]._value, l);
free_pass(&o[1]._value);
}

/* Padding */
if ((sizeof(eap_avp) + l) & 3) {
uint32_t pad = 0;
/* Padding */
if ((sizeof(eap_avp) + l) & 3) {
uint32_t pad = 0;

buf_append_bytes(reqbuf, &pad,
4 - ((sizeof(eap_avp) + l) & 3));
}
free_pass(&o[1]._value);
buf_append_bytes(reqbuf, &pad,
4 - ((sizeof(eap_avp) + l) & 3));
}

ret = 0;
out:
if (user_prompt)
free(o[0].label);
if (pass_prompt)
free(o[1].label);

return ret;
}

static int pulse_request_gtc(struct openconnect_info *vpninfo, struct oc_text_buf *reqbuf,
uint8_t eap_ident, char *user_prompt, char *gtc_prompt)
uint8_t eap_ident, int prompt_flags, char *user_prompt, char *pass_prompt,
char *gtc_prompt)
{
struct oc_auth_form f;
struct oc_form_opt o[2];
int ret;

memset(&f, 0, sizeof(f));
memset(o, 0, sizeof(o));
f.auth_id = (char *)"pulse_gtc";
f.opts = o;

f.message = _("Token code request:");
f.auth_id = (char *)"pulse_gtc";

o[0].next = &o[1];
o[0].type = OC_FORM_OPT_TEXT;
o[0].name = (char *)"username";
if (!user_prompt || asprintf(&o[0].label, "%s:", user_prompt) < 0) {
user_prompt = NULL;
o[0].label = (char *)_("Username:");
/* The first prompt always seems to be 'Enter SecurID PASSCODE:' and is ignored. */
if (gtc_prompt && (prompt_flags & PROMPT_GTC_NEXT))
f.message = gtc_prompt;
else
f.message = _("Token code request:");

if (prompt_flags & PROMPT_USERNAME) {
f.opts = &o[0];
o[0].next = &o[1];
o[0].type = OC_FORM_OPT_TEXT;
o[0].name = (char *)"username";
if (user_prompt)
o[0].label = user_prompt;
else
o[0].label = (char *) ((prompt_flags & PROMPT_PRIMARY) ? _("Username:") : _("Secondary username:"));
} else {
f.opts = &o[1];
}

o[1].type = OC_FORM_OPT_PASSWORD;
o[1].name = (char *)"tokencode";
o[1].label = gtc_prompt;

/*
* For retries, we have a gtc_prompt and we just say 'Please enter response:'.
* Otherwise, use the pass_prompt if it exists, or create our own based
* on whether it's primary authentication or not.
*/
if (prompt_flags & PROMPT_GTC_NEXT) {
o[1].label = _("Please enter response:");
} else if (pass_prompt) {
o[1].label = pass_prompt;
} else if (prompt_flags & PROMPT_PRIMARY) {
o[1].label = _("Please enter your passcode:");
} else {
o[1].label = _("Please enter your secondary token information:");
}

if (!can_gen_tokencode(vpninfo, &f, &o[1]))
o[1].type = OC_FORM_OPT_TOKEN;
Expand Down Expand Up @@ -1146,12 +1185,50 @@ static int pulse_request_gtc(struct openconnect_info *vpninfo, struct oc_text_bu
}
ret = 0;
out:
if (user_prompt)
free(o[0].label);

return ret;
}

static int dup_prompt(char **p, uint8_t *avp_p, int avp_len)
{
char *ret = NULL;

free(*p);
*p = NULL;

if (!avp_len) {
return 0;
} else if (avp_p[avp_len - 1] == ':') {
ret = strndup((char *)avp_p, avp_len);
} else {
ret = calloc(avp_len + 2, 1);
if (ret) {
memcpy(ret, avp_p, avp_len);
ret[avp_len] = ':';
ret[avp_len + 1] = 0;
}
}

if (ret) {
*p = ret;
return 0;
} else
return -ENOMEM;
}

/*
* There is complex client-side logic around when to (re)prompt for a password.
* The first prompt always needs it, whether it's a TokenCode request (EAP-06)
* or a normal password request (EAP-Expanded-Juniper/2). If a password request
* fails (0x81) then we prompt for username again in case that's what was wrong.
*
* If there's a secondary password request, it might need a *secondary* username.
* The first request comes with a 0xd73 AVP which has a single integer:
* 1: prompt for both username and password.
* 3: Prompt for password only.
* 5: Prompt for username only.
*
*/

/* IF-T/TLS session establishment is the same for both pulse_obtain_cookie() and
* pulse_connect(). We have to go through the EAP phase of the connection either
* way; it's just that we might do it with just the cookie, or we might need to
Expand All @@ -1174,6 +1251,8 @@ static int pulse_authenticate(struct openconnect_info *vpninfo, int connecting)
uint8_t j2_code = 0;
void *ttls = NULL;
char *user_prompt = NULL, *pass_prompt = NULL, *gtc_prompt = NULL, *signin_prompt = NULL;
char *user2_prompt = NULL, *pass2_prompt = NULL;
int prompt_flags = PROMPT_PRIMARY | PROMPT_USERNAME | PROMPT_PASSWORD;

/* XXX: We should do what cstp_connect() does to check that configuration
hasn't changed on a reconnect. */
Expand Down Expand Up @@ -1452,6 +1531,12 @@ static int pulse_authenticate(struct openconnect_info *vpninfo, int connecting)
free(signin_prompt);
signin_prompt = NULL;

/* If there's a follow-on GTC prompt, remember it's not the first */
if (gtc_found)
prompt_flags |= PROMPT_GTC_NEXT;
else
prompt_flags &= ~PROMPT_GTC_NEXT;

realm_entry = realms_found = j2_found = old_sessions = 0, gtc_found = 0;
eap = recv_eap_packet(vpninfo, ttls, (void *)bytes, sizeof(bytes));
if (!eap) {
Expand Down Expand Up @@ -1504,11 +1589,34 @@ static int pulse_authenticate(struct openconnect_info *vpninfo, int connecting)
ret = -EPERM;
goto out;
} else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd80) {
free(user_prompt);
user_prompt = strndup(avp_p, avp_len);
dup_prompt(&user_prompt, avp_p, avp_len);
} else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd81) {
free(pass_prompt);
pass_prompt = strndup(avp_p, avp_len);
dup_prompt(&pass_prompt, avp_p, avp_len);
} else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd82) {
dup_prompt(&user2_prompt, avp_p, avp_len);
} else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd83) {
dup_prompt(&pass2_prompt, avp_p, avp_len);
} else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd73) {
uint32_t val;

if (avp_len != 4)
goto auth_unknown;
val = load_be32(avp_p);

switch (val) {
case 1: /* Prompt for both username and password. */
prompt_flags = PROMPT_PASSWORD | PROMPT_USERNAME;
break;
case 3: /* Prompt for password.*/
prompt_flags = PROMPT_PASSWORD;
break;
case 5: /* Prompt for username.*/
prompt_flags = PROMPT_USERNAME;
break;

default:
goto auth_unknown;
}
} else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd7b) {
free(signin_prompt);
signin_prompt = strndup(avp_p, avp_len);
Expand Down Expand Up @@ -1596,17 +1704,20 @@ static int pulse_authenticate(struct openconnect_info *vpninfo, int connecting)
j2_code);

/* Present user/password form to user */
ret = pulse_request_user_auth(vpninfo, reqbuf, eap2_ident,
user_prompt, pass_prompt);
ret = pulse_request_user_auth(vpninfo, reqbuf, eap2_ident, prompt_flags,
(prompt_flags & PROMPT_PRIMARY) ? user_prompt : user2_prompt,
(prompt_flags & PROMPT_PRIMARY) ? pass_prompt : pass2_prompt);
if (ret)
goto out;
} else if (gtc_found) {
vpn_progress(vpninfo, PRG_TRACE,
_("Pulse password general token code request\n"));

/* Present user/password form to user */
ret = pulse_request_gtc(vpninfo, reqbuf, eap2_ident,
user_prompt, gtc_prompt);
ret = pulse_request_gtc(vpninfo, reqbuf, eap2_ident, prompt_flags,
(prompt_flags & PROMPT_PRIMARY) ? user_prompt : user2_prompt,
(prompt_flags & PROMPT_PRIMARY) ? pass_prompt : pass2_prompt,
gtc_prompt);
if (ret)
goto out;
} else if (old_sessions) {
Expand Down Expand Up @@ -1677,6 +1788,8 @@ static int pulse_authenticate(struct openconnect_info *vpninfo, int connecting)
vpninfo->ttls_recvbuf = NULL;
free(user_prompt);
free(pass_prompt);
free(user2_prompt);
free(pass2_prompt);
free(gtc_prompt);
free(signin_prompt);
return ret;
Expand Down

0 comments on commit 45cbcd2

Please sign in to comment.