diff --git a/gatchat/gatppp.c b/gatchat/gatppp.c index f767f4a0..e3266efc 100644 --- a/gatchat/gatppp.c +++ b/gatchat/gatppp.c @@ -64,11 +64,13 @@ struct _GAtPPP { struct pppcp_data *ipcp; struct ppp_net *net; struct ppp_chap *chap; + struct ppp_pap *pap; GAtHDLC *hdlc; gint mru; gint mtu; char username[256]; char password[256]; + GAtPPPAuthMethod auth_method; GAtPPPConnectFunc connect_cb; gpointer connect_data; GAtPPPDisconnectFunc disconnect_cb; @@ -150,13 +152,15 @@ static inline gboolean ppp_drop_packet(GAtPPP *ppp, guint16 protocol) return TRUE; break; case PPP_PHASE_AUTHENTICATION: - if (protocol != LCP_PROTOCOL && protocol != CHAP_PROTOCOL) + if (protocol != LCP_PROTOCOL && protocol != CHAP_PROTOCOL && + protocol != PAP_PROTOCOL) return TRUE; break; case PPP_PHASE_DEAD: return TRUE; case PPP_PHASE_NETWORK: if (protocol != LCP_PROTOCOL && protocol != CHAP_PROTOCOL && + protocol != PAP_PROTOCOL && protocol != IPCP_PROTO) return TRUE; break; @@ -222,6 +226,12 @@ static void ppp_receive(const unsigned char *buf, gsize len, void *data) case IPCP_PROTO: pppcp_process_packet(ppp->ipcp, packet, len - offset); break; + case PAP_PROTOCOL: + if (ppp->pap) + ppp_pap_process_packet(ppp->pap, packet, len - offset); + else + pppcp_send_protocol_reject(ppp->lcp, buf, len); + break; case CHAP_PROTOCOL: if (ppp->chap) { ppp_chap_process_packet(ppp->chap, packet, @@ -359,6 +369,12 @@ void ppp_set_auth(GAtPPP *ppp, const guint8* auth_data) guint16 proto = get_host_short(auth_data); switch (proto) { + case PAP_PROTOCOL: + if (ppp->pap) + ppp_pap_free(ppp->pap); + + ppp->pap = ppp_pap_new(ppp); + break; case CHAP_PROTOCOL: if (ppp->chap) ppp_chap_free(ppp->chap); @@ -437,10 +453,18 @@ void ppp_ipcp_finished_notify(GAtPPP *ppp) void ppp_lcp_up_notify(GAtPPP *ppp) { - /* Wait for the peer to send us a challenge if we expect auth */ if (ppp->chap != NULL) { + /* Wait for the peer to send us a challenge. */ ppp_enter_phase(ppp, PPP_PHASE_AUTHENTICATION); return; + } else if (ppp->pap != NULL) { + /* Try to send an Authenticate-Request and wait for reply. */ + if (ppp_pap_start(ppp->pap) == TRUE) + ppp_enter_phase(ppp, PPP_PHASE_AUTHENTICATION); + else + /* It'll never work out. */ + ppp_auth_notify(ppp, FALSE); + return; } /* Otherwise proceed as if auth succeeded */ @@ -588,6 +612,22 @@ const char *g_at_ppp_get_password(GAtPPP *ppp) return ppp->password; } +gboolean g_at_ppp_set_auth_method(GAtPPP *ppp, GAtPPPAuthMethod method) +{ + if (method != G_AT_PPP_AUTH_METHOD_CHAP && + method != G_AT_PPP_AUTH_METHOD_PAP) + return FALSE; + + ppp->auth_method = method; + + return TRUE; +} + +GAtPPPAuthMethod g_at_ppp_get_auth_method(GAtPPP *ppp) +{ + return ppp->auth_method; +} + void g_at_ppp_set_recording(GAtPPP *ppp, const char *filename) { if (ppp == NULL) @@ -727,6 +767,9 @@ void g_at_ppp_unref(GAtPPP *ppp) else if (ppp->fd >= 0) close(ppp->fd); + if (ppp->pap) + ppp_pap_free(ppp->pap); + if (ppp->chap) ppp_chap_free(ppp->chap); @@ -794,6 +837,9 @@ static GAtPPP *ppp_init_common(gboolean is_server, guint32 ip) /* initialize IPCP state */ ppp->ipcp = ipcp_new(ppp, is_server, ip); + /* chap authentication by default */ + ppp->auth_method = G_AT_PPP_AUTH_METHOD_CHAP; + return ppp; } diff --git a/gatchat/gatppp.h b/gatchat/gatppp.h index b5a22346..213f7e90 100644 --- a/gatchat/gatppp.h +++ b/gatchat/gatppp.h @@ -43,6 +43,11 @@ typedef enum _GAtPPPDisconnectReason { G_AT_PPP_REASON_LOCAL_CLOSE, /* Normal user close */ } GAtPPPDisconnectReason; +typedef enum _GAtPPPAuthMethod { + G_AT_PPP_AUTH_METHOD_CHAP, + G_AT_PPP_AUTH_METHOD_PAP, +} GAtPPPAuthMethod; + typedef void (*GAtPPPConnectFunc)(const char *iface, const char *local, const char *peer, const char *dns1, const char *dns2, @@ -74,6 +79,9 @@ gboolean g_at_ppp_set_credentials(GAtPPP *ppp, const char *username, const char *g_at_ppp_get_username(GAtPPP *ppp); const char *g_at_ppp_get_password(GAtPPP *ppp); +gboolean g_at_ppp_set_auth_method(GAtPPP *ppp, GAtPPPAuthMethod method); +GAtPPPAuthMethod g_at_ppp_get_auth_method(GAtPPP *ppp); + void g_at_ppp_set_recording(GAtPPP *ppp, const char *filename); void g_at_ppp_set_server_info(GAtPPP *ppp, const char *remote_ip, diff --git a/gatchat/ppp.h b/gatchat/ppp.h index 718575b3..ac1a7ef2 100644 --- a/gatchat/ppp.h +++ b/gatchat/ppp.h @@ -22,6 +22,7 @@ #include "ppp_cp.h" #define LCP_PROTOCOL 0xc021 +#define PAP_PROTOCOL 0xc023 #define CHAP_PROTOCOL 0xc223 #define IPCP_PROTO 0x8021 #define IPV6CP_PROTO 0x8057 @@ -38,6 +39,7 @@ struct ppp_chap; struct ppp_net; +struct ppp_pap; struct ppp_header { guint8 address; @@ -109,6 +111,13 @@ void ppp_chap_free(struct ppp_chap *chap); void ppp_chap_process_packet(struct ppp_chap *chap, const guint8 *new_packet, gsize len); +/* PAP related functions */ +struct ppp_pap *ppp_pap_new(GAtPPP *ppp); +void ppp_pap_free(struct ppp_pap *pap); +gboolean ppp_pap_start(struct ppp_pap *pap); +void ppp_pap_process_packet(struct ppp_pap *pap, const guint8 *new_packet, + gsize len); + /* TUN / Network related functions */ struct ppp_net *ppp_net_new(GAtPPP *ppp, int fd); const char *ppp_net_get_interface(struct ppp_net *net); diff --git a/gatchat/ppp_auth.c b/gatchat/ppp_auth.c index 1ddf7624..84cd861d 100644 --- a/gatchat/ppp_auth.c +++ b/gatchat/ppp_auth.c @@ -54,6 +54,38 @@ enum chap_code { FAILURE }; +struct pap_header { + guint8 code; + guint8 identifier; + guint16 length; + guint8 data[0]; +} __attribute__((packed)); + +struct ppp_pap { + GAtPPP *ppp; + struct ppp_header *authreq; + guint16 authreq_len; + guint retry_timer; + guint retries; +}; + +enum pap_code { + PAP_REQUEST = 1, + PAP_ACK, + PAP_NAK +}; + +/* + * RFC 1334 2.1.1: + * The Authenticate-Request packet MUST be repeated until a valid + * reply packet is received, or an optional retry counter expires. + * + * If we don't get a reply after this many attempts, we can safely + * assume we're never going to get one. + */ +#define PAP_MAX_RETRY 3 /* attempts */ +#define PAP_TIMEOUT 10 /* seconds */ + static void chap_process_challenge(struct ppp_chap *chap, const guint8 *packet) { const struct chap_header *header = (const struct chap_header *) packet; @@ -166,3 +198,110 @@ struct ppp_chap *ppp_chap_new(GAtPPP *ppp, guint8 method) return chap; } + +void ppp_pap_process_packet(struct ppp_pap *pap, const guint8 *new_packet, + gsize len) +{ + guint8 code; + + if (len < sizeof(struct pap_header)) + return; + + code = new_packet[0]; + + switch (code) { + case PAP_ACK: + g_source_remove(pap->retry_timer); + pap->retry_timer = 0; + ppp_auth_notify(pap->ppp, TRUE); + break; + case PAP_NAK: + g_source_remove(pap->retry_timer); + pap->retry_timer = 0; + ppp_auth_notify(pap->ppp, FALSE); + break; + default: + break; + } +} + +static gboolean ppp_pap_timeout(gpointer user_data) +{ + struct ppp_pap *pap = (struct ppp_pap *)user_data; + struct pap_header *authreq; + + if (++pap->retries >= PAP_MAX_RETRY) { + pap->retry_timer = 0; + ppp_auth_notify(pap->ppp, FALSE); + return FALSE; + } + + /* + * RFC 1334 2.2.1: + * The Identifier field MUST be changed each time an + * Authenticate-Request packet is issued. + */ + authreq = (struct pap_header *)&pap->authreq->info; + authreq->identifier++; + + ppp_transmit(pap->ppp, (guint8 *)pap->authreq, pap->authreq_len); + + return TRUE; +} + +gboolean ppp_pap_start(struct ppp_pap *pap) +{ + struct pap_header *authreq; + struct ppp_header *packet; + const char *username = g_at_ppp_get_username(pap->ppp); + const char *password = g_at_ppp_get_password(pap->ppp); + guint16 length; + + length = sizeof(*authreq) + strlen(username) + strlen(password) + 2; + packet = ppp_packet_new(length, PAP_PROTOCOL); + if (packet == NULL) + return FALSE; + pap->authreq = packet; + pap->authreq_len = length; + + authreq = (struct pap_header *)&packet->info; + authreq->code = PAP_REQUEST; + authreq->identifier = 1; + authreq->length = htons(length); + + authreq->data[0] = (unsigned char) strlen(username); + memcpy(authreq->data + 1, username, strlen(username)); + authreq->data[strlen(username) + 1] = (unsigned char)strlen(password); + memcpy(authreq->data + 1 + strlen(username) + 1, password, + strlen(password)); + + /* Transmit the packet and schedule a retry. */ + ppp_transmit(pap->ppp, (guint8 *)packet, length); + pap->retries = 0; + pap->retry_timer = g_timeout_add_seconds(PAP_TIMEOUT, + ppp_pap_timeout, pap); + + return TRUE; +} + +void ppp_pap_free(struct ppp_pap *pap) +{ + if (pap->retry_timer != 0) + g_source_remove(pap->retry_timer); + if (pap->authreq != NULL) + g_free(pap->authreq); + g_free(pap); +} + +struct ppp_pap *ppp_pap_new(GAtPPP *ppp) +{ + struct ppp_pap *pap; + + pap = g_try_new0(struct ppp_pap, 1); + if (pap == NULL) + return NULL; + + pap->ppp = ppp; + + return pap; +} diff --git a/gatchat/ppp_lcp.c b/gatchat/ppp_lcp.c index 4f420f17..df9cd0ef 100644 --- a/gatchat/ppp_lcp.c +++ b/gatchat/ppp_lcp.c @@ -238,25 +238,49 @@ static enum rcr_result lcp_rcr(struct pppcp_data *pppcp, guint8 method = option_data[2]; guint8 *option; - if ((proto == CHAP_PROTOCOL) && (method == MD5)) - break; + switch (g_at_ppp_get_auth_method(ppp)) { + case G_AT_PPP_AUTH_METHOD_CHAP: + if (proto == CHAP_PROTOCOL && method == MD5) + break; - /* - * try to suggest CHAP & MD5. If we are out - * of memory, just reject. - */ + /* + * Try to suggest CHAP/MD5. + * Just reject if we run out of memory. + */ + option = g_try_malloc0(5); + if (option == NULL) + return RCR_REJECT; - option = g_try_malloc0(5); - if (option == NULL) - return RCR_REJECT; + option[0] = AUTH_PROTO; + option[1] = 5; + put_network_short(&option[2], CHAP_PROTOCOL); + option[4] = MD5; + *new_options = option; + *new_len = 5; - option[0] = AUTH_PROTO; - option[1] = 5; - put_network_short(&option[2], CHAP_PROTOCOL); - option[4] = MD5; - *new_options = option; - *new_len = 5; - return RCR_NAK; + return RCR_NAK; + + case G_AT_PPP_AUTH_METHOD_PAP: + if (proto == PAP_PROTOCOL) + break; + + /* + * Try to suggest PAP. + * Just reject if we run out of memory. + */ + option = g_try_malloc0(4); + if (option == NULL) + return RCR_REJECT; + + option[0] = AUTH_PROTO; + option[1] = 4; + put_network_short(&option[2], PAP_PROTOCOL); + *new_options = option; + *new_len = 4; + + return RCR_NAK; + } + break; } case ACCM: case PFC: