// SoftEther VPN Source Code - Developer Edition Master Branch // Cedar Communication Module // Proto_PPP.c // PPP protocol stack #include "CedarPch.h" // PPP main thread void PPPThread(THREAD *thread, void *param) { PPP_SESSION *p = (PPP_SESSION *)param; UINT i; PPP_LCP *c; USHORT us; UINT ui; USHORT next_protocol = 0; bool ret = false; char ipstr1[128], ipstr2[128]; bool authReqSent = false; UINT64 now = Tick64(); // Validate arguments if (thread == NULL || param == NULL) { return; } // Initialize Debug("PPP Initialize"); PPPSetStatus(p, PPP_STATUS_CONNECTED); p->IPv4_State = PPP_PROTO_STATUS_CLOSED; p->IPv6_State = PPP_PROTO_STATUS_CLOSED; p->Mru1 = p->Mru2 = PPP_MRU_DEFAULT; p->RecvPacketList = NewList(NULL); p->SentReqPacketList = NewList(NULL); p->DelayedPackets = NewList(PPPDelayedPacketsComparator); p->MsChapV2_UseDoubleMsChapV2 = CedarIsThereAnyEapEnabledRadiusConfig(p->Cedar); Debug("MsChapV2_UseDoubleMsChapV2 = 0x%x\n", p->MsChapV2_UseDoubleMsChapV2); //// Link establishment phase Debug("PPP Link establishment phase\n"); IPToStr(ipstr1, sizeof(ipstr1), &p->ClientIP); IPToStr(ipstr2, sizeof(ipstr2), &p->ServerIP); PPPLog(p, "LP_CONNECTED", p->Postfix, ipstr1, p->ClientHostname, p->ClientPort, ipstr2, p->ServerPort, p->ClientSoftwareName, p->AdjustMss); // We need that so we don't time out on connection immediately p->LastRecvTime = Tick64(); Debug("PPP starting main dataloop\n"); // Dataloop active if the receiving tube is still connected while (true) { PPP_LCP *lcp; bool receivedPacketProcessed = false; TUBE *tubes[2]; UINT r; PPPGetNextPacket(p); if (p->CurrentPacket != NULL) { // First we process any possible unsupported packets receivedPacketProcessed = PPPRejectUnsupportedPacket(p, p->CurrentPacket); // Now do some basic processing if (!receivedPacketProcessed && p->CurrentPacket->IsControl && p->CurrentPacket->Protocol == PPP_PROTOCOL_LCP) { if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_ECHO_REQUEST && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus)) { // Immediately return the echo response to the echo request PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET)); pp2->IsControl = true; pp2->Protocol = PPP_PROTOCOL_LCP; pp2->Lcp = NewPPPLCP(PPP_LCP_CODE_ECHO_RESPONSE, p->CurrentPacket->Lcp->Id); pp2->Lcp->Data = Clone(p->CurrentPacket->Lcp->Data, p->CurrentPacket->Lcp->DataSize); pp2->Lcp->DataSize = p->CurrentPacket->Lcp->DataSize; if (PPPSendPacketAndFree(p, pp2) == false) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; } receivedPacketProcessed = true; } else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_ECHO_RESPONSE && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus)) { receivedPacketProcessed = true; // Ignore the Echo response packet } else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_DROP && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus)) { receivedPacketProcessed = true; // Ignore the Drop packet } else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_IDENTIFICATION && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus)) { receivedPacketProcessed = true; // Ignore the Identification packet WHERE; } else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_TERMINATE_REQ) { PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));; receivedPacketProcessed = true; // Return the Terminate ACK If a Terminate Request has been received pp2->IsControl = true; pp2->Protocol = PPP_PROTOCOL_LCP; pp2->Lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_ACK, p->CurrentPacket->Lcp->Id); pp2->Lcp->Data = Clone(p->CurrentPacket->Lcp->Data, p->CurrentPacket->Lcp->DataSize); pp2->Lcp->DataSize = p->CurrentPacket->Lcp->DataSize; p->IsTerminateReceived = true; if (PPPSendPacketAndFree(p, pp2) == false) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; } else { SleepThread(100); PPPSetStatus(p, PPP_STATUS_CLOSED); } } else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_TERMINATE_ACK) { PPPSetStatus(p, PPP_STATUS_CLOSED); } } // Process responses if (!receivedPacketProcessed && p->CurrentPacket != NULL && p->CurrentPacket->IsControl && PPP_CODE_IS_RESPONSE(p->CurrentPacket->Protocol, p->CurrentPacket->Lcp->Code) && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus)) { PPP_PACKET* request = NULL; // Removing from resend list for (i = 0; i < LIST_NUM(p->SentReqPacketList); i++) { PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i); if (t->Id == p->CurrentPacket->Lcp->Id) { request = t->Packet; Delete(p->SentReqPacketList, t); Free(t); break; } } PPPProcessResponsePacket(p, p->CurrentPacket, request); FreePPPPacket(request); receivedPacketProcessed = true; } // Process requests if (!receivedPacketProcessed && p->CurrentPacket != NULL && p->CurrentPacket->IsControl && PPP_CODE_IS_REQUEST(p->CurrentPacket->Protocol, p->CurrentPacket->Lcp->Code) && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus)) { PPPProcessRequestPacket(p, p->CurrentPacket); receivedPacketProcessed = true; } // Process data packets, discarded before we got any links up if (!receivedPacketProcessed && p->CurrentPacket != NULL && !p->CurrentPacket->IsControl && p->PPPStatus == PPP_STATUS_NETWORK_LAYER && p->Ipc != NULL) { UINT64 timeBeforeLoop = Tick64(); while (true) { UINT64 nowL; // Here client to server if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IP && p->IPv4_State == PPP_PROTO_STATUS_OPENED) { receivedPacketProcessed = true; IPCSendIPv4(p->Ipc, p->CurrentPacket->Data, p->CurrentPacket->DataSize); } else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IP) { Debug("Got IPv4 packet before IPv4 ready!\n"); } else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IPV6 && p->IPv6_State == PPP_PROTO_STATUS_OPENED) { receivedPacketProcessed = true; Debug("IPv6 to be implemented\n"); } else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IPV6) { Debug("Got IPv6 packet before IPv6 ready!\n"); } // Let's break out of the loop once in a while so we don't get stuck here endlessly nowL = Tick64(); if (nowL > timeBeforeLoop + PPP_PACKET_RESEND_INTERVAL) { break; } PPPGetNextPacket(p); if (p->CurrentPacket == NULL) { break; } // Making sure we got a correctly parsed packet by rejecting all invalid ones if (PPPRejectUnsupportedPacket(p, p->CurrentPacket)) { break; } if (p->CurrentPacket->IsControl || p->PPPStatus != PPP_STATUS_NETWORK_LAYER || p->Ipc == NULL) { PPPAddNextPacket(p, p->CurrentPacket, 0); p->CurrentPacket = NULL; break; } } } if (!receivedPacketProcessed && p->CurrentPacket != NULL) { Debug("Unprocessed and unrejected packet, protocol = 0x%x\n", p->CurrentPacket->Protocol); } } else if (p->PPPStatus == PPP_STATUS_BEFORE_AUTH && p->AuthProtocol == PPP_PROTOCOL_CHAP) { // We got to start CHAP when we got no LCP packets from the client on previous iteration // which means we parsed all the client requests and responses Debug("Starting PPP Authentication phase MS-CHAP v2\n"); lcp = BuildMSCHAP2ChallengePacket(p); if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_CHAP, lcp)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; } PPPSetStatus(p, PPP_STATUS_AUTHENTICATING); } if (p->PPPStatus == PPP_STATUS_CONNECTED && !authReqSent) { // MSCHAPv2 code PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0); UCHAR ms_chap_v2_code[3]; WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP); ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2; Debug("Request MSCHAPv2\n"); Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, ms_chap_v2_code, sizeof(ms_chap_v2_code))); if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; } authReqSent = true; } if (p->PPPStatus == PPP_STATUS_AUTHENTICATING) { Debug("Tick waiting for auth...\n"); } if (p->PPPStatus == PPP_STATUS_AUTH_FAIL) { Debug("PPP auth failed, giving up\n"); p->DisconnectCauseCode = 15; p->DisconnectCauseDirection = 1; PPPSetStatus(p, PPP_STATUS_CLOSING); } if (p->PPPStatus == PPP_STATUS_NETWORK_LAYER) { UINT64 timeBeforeLoop; if (p->DhcpAllocated) { if (now >= p->DhcpNextRenewTime) { IP ip; // DHCP renewal procedure p->DhcpNextRenewTime = now + p->DhcpRenewInterval; UINTToIP(&ip, p->ClientAddressOption.ServerAddress); IPCDhcpRenewIP(p->Ipc, &ip); } } IPCProcessL3Events(p->Ipc); timeBeforeLoop = Tick64(); while (true) { UINT64 nowL; BLOCK *b = IPCRecvIPv4(p->Ipc); PPP_PACKET *pp; PPP_PACKET tmp; if (b == NULL) { break; } // Since receiving the IP packet, send it to the client by PPP pp = &tmp; pp->IsControl = false; pp->Protocol = PPP_PROTOCOL_IP; pp->Lcp = NULL; pp->Data = b->Buf; pp->DataSize = b->Size; PPPSendPacketEx(p, pp, true); FreePPPPacketEx(pp, true); Free(b); // Let's break out of the loop once in a while so we don't get stuck here endlessly nowL = Tick64(); if (nowL > timeBeforeLoop + PPP_PACKET_RESEND_INTERVAL) { break; } } FlushTubeFlushList(p->FlushList); } if (p->PPPStatus == PPP_STATUS_AUTH_SUCCESS) { Debug("PPP auth success, ready for network layer on next tick\n"); p->AuthOk = true; PPPSetStatus(p, PPP_STATUS_NETWORK_LAYER); } if ((p->PPPStatus == PPP_STATUS_CLOSING || p->PPPStatus == PPP_STATUS_FAIL) && IsTubeConnected(p->TubeRecv) && IsTubeConnected(p->TubeSend)) { Debug("Trying to cleanly close the connection, status = 0x%x\n", p->PPPStatus); PPPSetStatus(p, PPP_STATUS_CLOSING_WAIT); lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_REQ, 0); if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, lcp)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; } } if (!PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) || p->PPPStatus == PPP_STATUS_CLOSING_WAIT) { PPPProcessRetransmissions(p); PPPSendEchoRequest(p); } tubes[0] = p->TubeRecv; if (p->PPPStatus == PPP_STATUS_NETWORK_LAYER && p->Ipc != NULL && IsIPCConnected(p->Ipc)) { r = GetNextIntervalForInterrupt(p->Ipc->Interrupt); tubes[1] = p->Ipc->Sock->RecvTube; WaitForTubes(tubes, 2, MIN(r, PPP_PACKET_RESEND_INTERVAL)); } else { WaitForTubes(tubes, 1, 100); } if (IsTubeConnected(p->TubeRecv) == false || IsTubeConnected(p->TubeSend) == false) { // Higher-level protocol is disconnected PPPLog(p, "LP_UPPER_PROTOCOL_DISCONNECTED", p->Postfix); break; } if (IsIPCConnected(p->Ipc) == false && p->PPPStatus == PPP_STATUS_NETWORK_LAYER) { // IPC VPN session is disconnected PPPLog(p, "LP_VPN_SESSION_TERMINATED"); break; } // Time-out inspection if ((p->LastRecvTime + (UINT64)p->DataTimeout) <= now) { // Communication time-out occurs PPPLog(p, "LP_DATA_TIMEOUT"); break; } // Maximum PPP session time of the user reached inspection if (p->UserConnectionTick != 0 && p->UserConnectionTimeout != 0 && p->UserConnectionTick + p->UserConnectionTimeout <= now) { // User connection time-out occurs PPPLog(p, "LP_USER_TIMEOUT"); break; } // Terminate if the PPP disconnected if (p->IsTerminateReceived) { PPPLog(p, "LP_NORMAL_TERMINATE"); break; } if (p->PPPStatus == PPP_STATUS_FAIL || p->PPPStatus == PPP_STATUS_CLOSED) { Debug("Exiting main dataloop, status = 0x%x\n", p->PPPStatus); break; } } Debug("Exited main dataloop, status = 0x%x\n", p->PPPStatus); if (p->PPPStatus != PPP_STATUS_FAIL) { IP ip; char tmp[MAX_SIZE]; // Disconnected normally PPPLog(p, "LP_DISCONNECTED"); if (p != NULL && p->DhcpAllocated && IsIPCConnected(p->Ipc) && p->ClientAddressOption.ServerAddress != 0) { // If any address is assigned from the DHCP, release it UINTToIP(&ip, p->ClientAddressOption.ServerAddress); IPToStr(tmp, sizeof(tmp), &ip); Debug("Releasing IP Address from DHCP Server %s...\n", tmp); IPCDhcpFreeIP(p->Ipc, &ip); IPCProcessL3Events(p->Ipc); SleepThread(300); } } else { PPPLog(p, "LP_DISCONNECTED_ABNORMAL"); } FreePPPSession(p); Debug("PPP Session ended correctly\n"); } // Entry point // Create a new PPP session PPP_SESSION *NewPPPSession(CEDAR *cedar, IP *client_ip, UINT client_port, IP *server_ip, UINT server_port, TUBE *send_tube, TUBE *recv_tube, char *postfix, char *client_software_name, char *client_hostname, char *crypt_name, UINT adjust_mss) { PPP_SESSION *p; THREAD *t; // Validate arguments if (cedar == NULL || client_ip == NULL || server_ip == NULL || send_tube == NULL || recv_tube == NULL) { return NULL; } if (IsEmptyStr(postfix)) { postfix = "PPP"; } if (IsEmptyStr(crypt_name)) { crypt_name = ""; } if (IsEmptyStr(client_software_name)) { client_software_name = "PPP VPN Client"; } // Data structure initialization p = ZeroMalloc(sizeof(PPP_SESSION)); p->EnableMSCHAPv2 = true; p->AuthProtocol = NULL; p->MsChapV2_ErrorCode = 691; p->EapClient = NULL; p->DataTimeout = PPP_DATA_TIMEOUT; p->PacketRecvTimeout = PPP_PACKET_RECV_TIMEOUT; p->UserConnectionTimeout = 0; p->Cedar = cedar; AddRef(cedar->ref); p->AdjustMss = adjust_mss; StrCpy(p->CryptName, sizeof(p->CryptName), crypt_name); Copy(&p->ClientIP, client_ip, sizeof(IP)); p->ClientPort = client_port; Copy(&p->ServerIP, server_ip, sizeof(IP)); p->ServerPort = server_port; p->TubeRecv = recv_tube; p->TubeSend = send_tube; AddRef(p->TubeRecv->Ref); AddRef(p->TubeSend->Ref); StrCpy(p->Postfix, sizeof(p->Postfix), postfix); StrCpy(p->ClientSoftwareName, sizeof(p->ClientSoftwareName), client_software_name); if (IsEmptyStr(client_hostname)) { IPToStr(p->ClientHostname, sizeof(p->ClientHostname), client_ip); } else { StrCpy(p->ClientHostname, sizeof(p->ClientHostname), client_hostname); } p->FlushList = NewTubeFlushList(); // Thread creation t = NewThread(PPPThread, p); p->SessionThread = t; return p; } // PPP processing functions // Finds out if a packet is supported, if not - sends a notification to the peer // result: false - supported, true - unsupported bool PPPRejectUnsupportedPacket(PPP_SESSION *p, PPP_PACKET *pp) { return PPPRejectUnsupportedPacketEx(p, pp, false); } bool PPPRejectUnsupportedPacketEx(PPP_SESSION *p, PPP_PACKET *pp, bool force) { bool result = false; if (p == NULL || pp == NULL) { return false; } if (PPP_IS_SUPPORTED_PROTOCOL(pp->Protocol) == false || force == true) { // Unsupported algorithm PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET)); BUF *buf; UCHAR c; USHORT us; Debug("Rejecting PPP protocol = 0x%x\n", pp->Protocol); result = true; pp2->Protocol = PPP_PROTOCOL_LCP; pp2->IsControl = false; buf = NewBuf(); // Code c = PPP_LCP_CODE_PROTOCOL_REJECT; WriteBuf(buf, &c, 1); // ID c = p->NextId++; WriteBuf(buf, &c, 1); // Length us = Endian16(pp->DataSize + 6); WriteBuf(buf, &us, 2); // Rejected Protocol us = Endian16(pp->Protocol); WriteBuf(buf, &us, 2); // Packet Data WriteBuf(buf, pp->Data, pp->DataSize); pp2->Data = Clone(buf->Buf, buf->Size); pp2->DataSize = buf->Size; FreeBuf(buf); if (!PPPSendPacketAndFree(p, pp2)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; } } return result; } // Do the retransmissions if needed bool PPPProcessRetransmissions(PPP_SESSION *p) { INT64 i = 0; UINT64 now = Tick64(); UINT64 count; if (p->SentReqPacketList == NULL) { Debug("Somehow SentReqPacketList is NULL!\n"); return false; } // Making it signed but expanding to 64 bits count = LIST_NUM(p->SentReqPacketList); if (count == 0) { return true; } for (i = count - 1; i >= 0; --i) { PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i); if (t->TimeoutTime <= now) { Debug("Timing out on resending control packet protocol = 0x%x\n", t->Packet->Protocol); Delete(p->SentReqPacketList, t); FreePPPPacket(t->Packet); Free(t); } else if (t->ResendTime <= now) { Debug("Resending control packet protocol = 0x%x\n", t->Packet->Protocol); if (!PPPSendPacketEx(p, t->Packet, false)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } t->ResendTime = now + PPP_PACKET_RESEND_INTERVAL; } } return true; } // Send the PPP Echo Request bool PPPSendEchoRequest(PPP_SESSION *p) { UINT64 now = Tick64(); if (p->NextEchoSendTime == 0 || now >= p->NextEchoSendTime) { PPP_PACKET *pp; char echo_data[] = "\0\0\0\0Aho Baka Manuke"; p->NextEchoSendTime = now + (UINT64)PPP_ECHO_SEND_INTERVAL; if (IsIPCConnected(p->Ipc)) { AddInterrupt(p->Ipc->Interrupt, p->NextEchoSendTime); } // Validate arguments if (p == NULL) { return false; } pp = ZeroMalloc(sizeof(PPP_PACKET)); pp->Protocol = PPP_PROTOCOL_LCP; pp->IsControl = true; pp->Lcp = NewPPPLCP(PPP_LCP_CODE_ECHO_REQUEST, 0); pp->Lcp->Data = Clone(echo_data, sizeof(echo_data)); pp->Lcp->DataSize = sizeof(echo_data); if (!PPPSendPacketAndFree(p, pp)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } return true; } return false; } // Processes response packets bool PPPProcessResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req) { if (req == NULL) { Debug("We received a response for... What? We never sent this request, protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code); // Let's just discard this, as this was probably already parsed, and we just stumbled upon a resend return false; } switch (pp->Protocol) { case PPP_PROTOCOL_LCP: return PPPProcessLCPResponsePacket(p, pp, req); break; case PPP_PROTOCOL_PAP: Debug("Got a response PAP, which is invalid, we should get a request instead\n"); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; break; case PPP_PROTOCOL_CHAP: return PPPProcessCHAPResponsePacket(p, pp, req); break; case PPP_PROTOCOL_IPCP: return PPPProcessIPCPResponsePacket(p, pp, req); break; case PPP_PROTOCOL_IPV6CP: Debug("IPv6CP to be implemented\n"); break; default: Debug("We received a response for an unsupported protocol??? Should be filtered out already! protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } return false; } bool PPPProcessLCPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req) { UINT i; bool isAccepted = !PPP_LCP_CODE_IS_NEGATIVE(pp->Lcp->Code); bool result = true; // MSCHAPv2 code UCHAR ms_chap_v2_code[3]; WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP); ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2; // We got one of rejects here, not NACKs if (!isAccepted && pp->Lcp->Code == PPP_LCP_CODE_PROTOCOL_REJECT) { // If we receive a protocol reject before we finished authenticating // probably means the PPP client is not compatible anyway so we fail the connection if (p->PPPStatus != PPP_STATUS_NETWORK_LAYER) { USHORT* protocol = pp->Lcp->Data; Debug("Protocol 0x%x rejected before auth, probably unsupported client, failing connection\n", *protocol); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } else { USHORT* protocol = pp->Lcp->Data; if (*protocol == PPP_PROTOCOL_IPCP || *protocol == PPP_PROTOCOL_IP) { p->IPv4_State == PPP_PROTO_STATUS_REJECTED; } if (*protocol == PPP_PROTOCOL_IPV6CP || *protocol == PPP_PROTOCOL_IPV6) { p->IPv6_State == PPP_PROTO_STATUS_REJECTED; } } } if (!isAccepted && pp->Lcp->Code == PPP_LCP_CODE_CODE_REJECT) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++) { PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i); PPP_OPTION *opt = NULL; switch (t->Type) { case PPP_LCP_OPTION_MRU: // MRU if (t->DataSize == sizeof(USHORT)) { USHORT value = READ_USHORT(t->Data); if (!isAccepted) { if (pp->Lcp->Code != PPP_LCP_CODE_NAK) { Debug("MRU setup failed, rejected"); p->Mru1 = p->Mru2 = PPP_MRU_DEFAULT; } if (value < PPP_MRU_MIN || value > PPP_MRU_MAX) { Debug("Couldn't agree on an MRU! Breaking link... MRU = 0x%x\n", value); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } else { PPP_LCP* lcp = NewPPPLCP(PPP_LCP_CODE_REQ, 0); Add(lcp->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &value, sizeof(USHORT))); if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, lcp)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } Debug("PPP: Server got %u as MRU from NACK, re-requesting\n", p->Mru2); } } else if (value < PPP_MRU_MIN || value > PPP_MRU_MAX) { Debug("The client somehow ACKed an invalid MRU, breaking link... MRU = 0x%x\n", value); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; result = false; } else { p->Mru2 = value; Debug("PPP: Server set %u as MRU\n", p->Mru2); } } break; case PPP_LCP_OPTION_AUTH: opt = PPPGetOptionValue(req->Lcp, PPP_LCP_OPTION_AUTH); if (opt == NULL) { Debug("We got some weird response with option absent in request, wut? Disconnecting\n"); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } if (opt->DataSize == sizeof(ms_chap_v2_code) && Cmp(opt->Data, ms_chap_v2_code, opt->DataSize) == 0) { // Try to request PAP then if (!isAccepted || !p->EnableMSCHAPv2) { UINT64 offer = 0; PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0); USHORT proto = Endian16(PPP_LCP_AUTH_PAP); Copy(&offer, t->Data, t->DataSize > sizeof(UINT64) ? sizeof(UINT64) : t->DataSize); Debug("NACK proto with code = 0x%x, cypher = 0x%x, offered cypher = 0x%x\n", pp->Lcp->Code, *((USHORT*)(opt->Data)), offer); Debug("Request PAP\n"); Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &proto, sizeof(USHORT))); if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } } else if (p->AuthProtocol == NULL) { p->AuthProtocol = PPP_PROTOCOL_CHAP; Debug("Setting BEFORE_AUTH from ACK on LCP response parse on CHAP accept\n"); PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH); } } else if (opt->DataSize == sizeof(USHORT) && *((USHORT*)(opt->Data)) == Endian16(PPP_LCP_AUTH_PAP)) { // We couldn't agree on auth proto, failing connection if (!isAccepted) { UINT64 offer = 0; Copy(&offer, t->Data, t->DataSize > sizeof(UINT64) ? sizeof(UINT64) : t->DataSize); Debug("NACK proto with code = 0x%x, cypher = 0x%x, offered cypher = 0x%x\n", pp->Lcp->Code, *((USHORT*)(opt->Data)), offer); Debug("Couldn't agree on auth protocol!\n"); PPPLog(p, "LP_PAP_MSCHAPV2_REJECTED"); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } else if (p->AuthProtocol == NULL) { p->AuthProtocol = PPP_PROTOCOL_PAP; Debug("Setting BEFORE_AUTH from ACK on LCP response parse on PAP accept\n"); PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH); } } break; } } return result; } // Process CHAP responses bool PPPProcessCHAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req) { PPP_LCP* lcp; if (pp->Lcp->Code == PPP_CHAP_CODE_RESPONSE) { bool ok = false; if (p->PPPStatus != PPP_STATUS_AUTHENTICATING && !p->AuthOk) { Debug("Receiving CHAP response packets outside of auth status, some errors probably!"); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } if (p->AuthProtocol != PPP_PROTOCOL_CHAP) { Debug("Receiving CHAP packet when auth protocol set to 0x%x\n", p->AuthProtocol); PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_PAP", pp->Protocol); PPPRejectUnsupportedPacketEx(p, pp, true); return false; } ok = PPPParseMSCHAP2ResponsePacket(p, pp); // If we got only first packet of double CHAP then send second challenge if (ok && p->MsChapV2_UseDoubleMsChapV2 && p->EapClient != NULL && p->Ipc == NULL) { lcp = BuildMSCHAP2ChallengePacket(p); if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_CHAP, lcp)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } } // We got a successful MSCHAPv2 response, so let's send a SUCCESS else if (ok) { char hex[MAX_SIZE]; char ret_str[MAX_SIZE]; BUF* lcp_ret_data = NewBuf(); PPP_PACKET* res = ZeroMalloc(sizeof(PPP_PACKET)); BinToStr(hex, sizeof(hex), p->MsChapV2_ServerResponse, 20); Format(ret_str, sizeof(ret_str), "S=%s", hex); WriteBuf(lcp_ret_data, ret_str, StrLen(ret_str)); lcp = NewPPPLCP(PPP_CHAP_CODE_SUCCESS, p->MsChapV2_PacketId); lcp->Data = Clone(lcp_ret_data->Buf, lcp_ret_data->Size); lcp->DataSize = lcp_ret_data->Size; if (lcp_ret_data != NULL) { FreeBuf(lcp_ret_data); } res->Lcp = lcp; res->IsControl = true; res->Protocol = PPP_PROTOCOL_CHAP; if (!PPPSendPacketAndFree(p, res)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } p->AuthOk = true; PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS); } // We failed MSCHAPv2 auth else { char hex[MAX_SIZE]; char ret_str[MAX_SIZE]; BUF* lcp_ret_data = NewBuf(); PPP_PACKET* res = ZeroMalloc(sizeof(PPP_PACKET)); BinToStr(hex, sizeof(hex), p->MsChapV2_ServerChallenge, 16); Format(ret_str, sizeof(ret_str), "E=%u R=0 C=%s V=3", p->MsChapV2_ErrorCode, hex); WriteBuf(lcp_ret_data, ret_str, StrLen(ret_str)); lcp = NewPPPLCP(PPP_CHAP_CODE_FAILURE, p->MsChapV2_PacketId); lcp->Data = Clone(lcp_ret_data->Buf, lcp_ret_data->Size); lcp->DataSize = lcp_ret_data->Size; if (lcp_ret_data != NULL) { FreeBuf(lcp_ret_data); } res->Lcp = lcp; res->IsControl = true; res->Protocol = PPP_PROTOCOL_CHAP; if (!PPPSendPacketAndFree(p, res)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } PPPLog(p, "LP_CHAP_FAILED"); PPPSetStatus(p, PPP_STATUS_AUTH_FAIL); } return ok; } return false; } // Process IPCP responses bool PPPProcessIPCPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req) { bool isAccepted = !PPP_LCP_CODE_IS_NEGATIVE(pp->Lcp->Code); IP addrStruct; char addrStr[MAX_SIZE]; UINT addr; IP prevAddrStruct; char prevAddrStr[MAX_SIZE]; UINT prevAddr; PPP_LCP* c; UINT ui; if (!PPPGetIPAddressValueFromLCP(pp->Lcp, PPP_IPCP_OPTION_IP, &addrStruct) || pp->Lcp->Code == PPP_LCP_CODE_REJECT || pp->Lcp->Code == PPP_LCP_CODE_CODE_REJECT) { Debug("Unsupported IPCP protocol"); p->IPv4_State = PPP_PROTO_STATUS_REJECTED; PPPRejectUnsupportedPacketEx(p, pp, true); return false; } // We're dealing either with ACK or NACK addr = IPToUINT(&addrStruct); IPToStr(addrStr, MAX_SIZE, &addrStruct); if (isAccepted) { Debug("Accepted server IP address of %s\n", addrStr); // We already configured client address, now server address is also confirmed, ready for IPv4 data flow if (p->IPv4_State == PPP_PROTO_STATUS_CONFIG) { p->IPv4_State = PPP_PROTO_STATUS_CONFIG_WAIT; } return true; } p->IPv4_State == PPP_PROTO_STATUS_CONFIG; PPPGetIPAddressValueFromLCP(req->Lcp, PPP_IPCP_OPTION_IP, &prevAddrStruct); prevAddr = IPToUINT(&prevAddrStruct); IPToStr(prevAddrStr, MAX_SIZE, &prevAddrStruct); Debug("Denied server IP address %s, proposed %s\n", prevAddrStr, addrStr); // Fallback mechanism - just request 192.0.0.8 if (prevAddr == Endian32(0xc0000008)) { Debug("We already tried the fallback IP of 192.0.0.8, giving up\n"); p->IPv4_State = PPP_PROTO_STATUS_REJECTED; PPPRejectUnsupportedPacketEx(p, pp, true); return false; } c = NewPPPLCP(PPP_LCP_CODE_REQ, 0); ui = Endian32(0xc0000008); // We always push 192.0.0.8, which is defined in RFC7600 as dummy IPv4 address. Add(c->OptionList, NewPPPOption(PPP_IPCP_OPTION_IP, &ui, sizeof(UINT))); if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPCP, c)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } return false; } // Processes request packets bool PPPProcessRequestPacket(PPP_SESSION *p, PPP_PACKET *pp) { switch (pp->Protocol) { case PPP_PROTOCOL_LCP: return PPPProcessLCPRequestPacket(p, pp); break; case PPP_PROTOCOL_PAP: return PPPProcessPAPRequestPacket(p, pp); break; case PPP_PROTOCOL_CHAP: Debug("Got a CHAP request, which is invalid, we should get CHAP response instead\n"); PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; break; case PPP_PROTOCOL_IPCP: return PPPProcessIPCPRequestPacket(p, pp); break; case PPP_PROTOCOL_IPV6CP: PPPRejectUnsupportedPacketEx(p, pp, true); Debug("IPv6CP to be implemented\n"); break; default: Debug("Unsupported protocols should be already filtered out! protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code); return false; break; } return false; } bool PPPProcessLCPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp) { bool result = true; UINT i = 0; USHORT NegotiatedAuthProto = PPP_UNSPECIFIED; USHORT NegotiatedMRU = PPP_UNSPECIFIED; // MSCHAPv2 code UCHAR ms_chap_v2_code[3]; WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP); ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2; Debug("Got LCP packet request ID=%i OptionsListSize=%i\n", pp->Lcp->Id, LIST_NUM(pp->Lcp->OptionList)); for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++) { PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i); switch (t->Type) { case PPP_LCP_OPTION_AUTH: t->IsSupported = true; if (t->DataSize == sizeof(USHORT) && *((USHORT*)t->Data) == Endian16(PPP_LCP_AUTH_PAP) && p->AuthProtocol != PPP_PROTOCOL_CHAP) { t->IsAccepted = true; NegotiatedAuthProto = PPP_PROTOCOL_PAP; } else if (t->DataSize == sizeof(ms_chap_v2_code) && Cmp(t->Data, ms_chap_v2_code, t->DataSize) == 0) { t->IsAccepted = true; NegotiatedAuthProto = PPP_PROTOCOL_CHAP; } else { // We're recommending MSCHAPv2 by default as a more secure algo t->IsAccepted = false; t->AltDataSize = sizeof(ms_chap_v2_code); Copy(t->AltData, ms_chap_v2_code, sizeof(ms_chap_v2_code)); } break; case PPP_LCP_OPTION_MRU: t->IsSupported = true; if (t->DataSize == sizeof(USHORT)) { USHORT value = READ_USHORT(t->Data); if (value < PPP_MRU_MIN || value > PPP_MRU_MAX) { t->IsAccepted = false; value = MAKESURE(value, PPP_MRU_MIN, PPP_MRU_MAX); //Debug("MRU not accepted, sending NACK with value = 0x%x\n", value); t->AltDataSize = sizeof(USHORT); WRITE_USHORT(t->AltData, value); } else { t->IsAccepted = true; NegotiatedMRU = value; //Debug("MRU accepted, value = 0x%x\n", value); } } else { t->IsAccepted = false; t->AltDataSize = sizeof(USHORT); WRITE_USHORT(t->AltData, PPP_MRU_DEFAULT); } break; default: t->IsSupported = false; Debug("Unsupported LCP option = 0x%x\n", t->Type); break; } } if (PPPRejectLCPOptions(p, pp)) { Debug("Rejected LCP options...\n"); return false; } if (PPPNackLCPOptions(p, pp)) { Debug("NACKed LCP options...\n"); return false; } if (!PPPAckLCPOptions(p, pp)) { return false; } if (NegotiatedAuthProto != PPP_UNSPECIFIED) { if (p->AuthProtocol == NULL) { p->AuthProtocol = NegotiatedAuthProto; PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH); Debug("Setting BEFORE_AUTH from REQ on LCP request parse\n"); } } if (NegotiatedMRU != PPP_UNSPECIFIED) { p->Mru1 = NegotiatedMRU; } return true; } bool PPPProcessPAPRequestPacket(PPP_SESSION *p, PPP_PACKET* pp) { if (p->PPPStatus != PPP_STATUS_BEFORE_AUTH && !p->AuthOk) { PPP_LCP* lcp = NewPPPLCP(PPP_PAP_CODE_NAK, pp->Lcp->Id); PPP_PACKET* ret = ZeroMalloc(sizeof(PPP_PACKET)); Debug("Got a PAP request before we're ready for AUTH procedure!\n"); ret->IsControl = true; ret->Protocol = PPP_PROTOCOL_PAP; ret->Lcp = lcp; if (!PPPSendPacketAndFree(p, ret)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } return false; } if (p->AuthProtocol != PPP_PROTOCOL_PAP) { Debug("Trying to auth with PAP when should be 0x%x\n", p->AuthProtocol); PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_CHAP", pp->Protocol); // Forcing rejection of PAP on configured MSCHAPv2 PPPRejectUnsupportedPacketEx(p, pp, true); return false; } if (!p->AuthOk) { UCHAR *data; UINT size; PPPSetStatus(p, PPP_STATUS_AUTHENTICATING); if (p->Ipc == NULL) { // PAP // Extract the ID and the password data = pp->Lcp->Data; size = pp->Lcp->DataSize; if (size >= 1) { UCHAR len_id = data[0]; data++; size--; if (size >= len_id) { char username[256]; char password[256]; Zero(username, sizeof(username)); Zero(password, sizeof(password)); Copy(username, data, len_id); data += len_id; size -= len_id; if (size >= 1) { UCHAR len_pass = data[0]; data++; size--; if (size >= len_pass) { IPC *ipc; char id[MAX_SIZE]; char hub[MAX_SIZE]; ETHERIP_ID d; Zero(id, sizeof(id)); Zero(hub, sizeof(hub)); Copy(password, data, len_pass); Debug("PPP: id=%s, pw=%s\n", username, password); // The user name is divided into the ID and the virtual HUB name Zero(&d, sizeof(d)); PPPParseUsername(p->Cedar, username, &d); StrCpy(id, sizeof(id), d.UserName); StrCpy(hub, sizeof(hub), d.HubName); if (IsEmptyStr(id) == false) { // Attempt to connect with IPC UINT error_code; ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password, &error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort, p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, NULL, IPC_LAYER_3); if (ipc != NULL) { p->Ipc = ipc; // Setting user timeouts p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000; p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000; p->UserConnectionTick = Tick64(); p->AuthOk = true; } else { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } } } } } } } else { // Return success for a request from the second time when it is successfully authenticated once p->AuthOk = true; } } if (p->AuthOk) { PPP_LCP* lcp = NewPPPLCP(PPP_PAP_CODE_ACK, pp->Lcp->Id); PPP_PACKET* ret = ZeroMalloc(sizeof(PPP_PACKET)); ret->IsControl = true; ret->Protocol = PPP_PROTOCOL_PAP; ret->Lcp = lcp; if (!PPPSendPacketAndFree(p, ret)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } if (p->PPPStatus == PPP_STATUS_AUTHENTICATING) { PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS); } return true; } if (!p->AuthOk) { PPP_LCP* lcp = NewPPPLCP(PPP_PAP_CODE_NAK, pp->Lcp->Id); PPP_PACKET* ret = ZeroMalloc(sizeof(PPP_PACKET)); ret->IsControl = true; ret->Protocol = PPP_PROTOCOL_PAP; ret->Lcp = lcp; if (!PPPSendPacketAndFree(p, ret)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } if (p->PPPStatus == PPP_STATUS_AUTHENTICATING) { PPPSetStatus(p, PPP_STATUS_AUTH_FAIL); PPPLog(p, "LP_PAP_FAILED"); } return false; } } bool PPPProcessIPCPRequestPacket(PPP_SESSION *p, PPP_PACKET* pp) { PPP_IPOPTION o; PPP_IPOPTION res; PPP_OPTION *dummyIpOption; UINT dummyIp = 0; DHCP_OPTION_LIST cao; IP client_ip; IP subnet; IP zero; IP gw; bool ok = true; bool processed = false; bool isEmptyIpAddress = false; PPP_LCP* c; if (p->IPv4_State == PPP_PROTO_STATUS_REJECTED) { Debug("We got an IPCP packet after we had it rejected\n"); return PPPRejectUnsupportedPacketEx(p, pp, true); } if (!PPPGetIPOptionFromLCP(&o, pp->Lcp)) { Debug("IPCP request without client IP address received! Treating as zeroed out client IP...\n"); isEmptyIpAddress = true; dummyIpOption = NewPPPOption(PPP_IPCP_OPTION_IP, &dummyIp, sizeof(UINT)); dummyIpOption->IsSupported = true; dummyIpOption->IsAccepted = false; Add(pp->Lcp->OptionList, dummyIpOption); } // Process if not configured yet by server if ((IsZero(&p->ClientAddressOption, sizeof(DHCP_OPTION_LIST)) || isEmptyIpAddress) && ok) { // Decide if we received a static IP from client and it is allowed if (IsZeroIP(&o.IpAddress) == false) { if (p->Ipc->Policy->DHCPForce == false) { if (p->DhcpAllocated == false) { if (p->UseStaticIPAddress == false) { DHCP_OPTION_LIST cao; // The client specify an IP address Zero(&cao, sizeof(cao)); cao.ClientAddress = IPToUINT(&o.IpAddress); Copy(&p->ClientAddressOption, &cao, sizeof(cao)); p->UseStaticIPAddress = true; } } } } else { p->UseStaticIPAddress = false; } // Get additional information for static clients if (p->UseStaticIPAddress) { if (p->DhcpIpInformTried == false) { // Get additional information such as the subnet mask from the DHCP server SetIP(&subnet, 255, 0, 0, 0); Zero(&zero, sizeof(zero)); UINTToIP(&client_ip, p->ClientAddressOption.ClientAddress); Zero(&cao, sizeof(cao)); IPCSetIPv4Parameters(p->Ipc, &client_ip, &subnet, &zero, NULL); p->DhcpIpInformTried = true; PPPLog(p, "LP_DHCP_INFORM_TRYING"); if (IPCDhcpRequestInformIP(p->Ipc, &cao, p->TubeRecv, &client_ip)) { Debug("IPCDhcpRequestInformIP ok.\n"); Copy(&p->ClientAddressOption, &cao, sizeof(cao)); p->ClientAddressOption.ClientAddress = IPToUINT(&client_ip); if (true) { char server_ip_str[64]; char subnet_str[64], defgw_str[64]; char dns1_str[64], dns2_str[64]; char wins1_str[64], wins2_str[64]; IPToStr32(server_ip_str, sizeof(server_ip_str), cao.ServerAddress); IPToStr32(subnet_str, sizeof(subnet_str), cao.SubnetMask); IPToStr32(defgw_str, sizeof(defgw_str), cao.Gateway); IPToStr32(dns1_str, sizeof(dns1_str), cao.DnsServer); IPToStr32(dns2_str, sizeof(dns2_str), cao.DnsServer2); IPToStr32(wins1_str, sizeof(wins1_str), cao.WinsServer); IPToStr32(wins2_str, sizeof(wins2_str), cao.WinsServer2); PPPLog(p, "LP_DHCP_INFORM_OK", subnet_str, defgw_str, cao.DomainName, dns1_str, dns2_str, wins1_str, wins2_str, server_ip_str); } } else { Debug("IPCDhcpRequestInformIP failed.\n"); ok = false; p->DhcpIpInformTried = false; PPPLog(p, "LP_DHCP_INFORM_NG"); } IPCSetIPv4Parameters(p->Ipc, &zero, &zero, &zero, NULL); } } // Get IP address and additional information from DHCP else { if (p->DhcpIpAllocTried == false) { DHCP_OPTION_LIST cao; Zero(&cao, sizeof(cao)); p->DhcpIpAllocTried = true; PPPLog(p, "LP_DHCP_REQUEST_TRYING"); if (IPCDhcpAllocateIP(p->Ipc, &cao, p->TubeRecv)) { UINT t; Debug("IPCDhcpAllocateIP ok.\n"); // IP address has been determined Copy(&p->ClientAddressOption, &cao, sizeof(cao)); p->DhcpAllocated = true; // Determine the DHCP update interval t = cao.LeaseTime; if (t == 0) { t = 600; } t = t / 3; if (t == 0) { t = 1; } p->DhcpRenewInterval = (UINT64)t * (UINT64)1000; p->DhcpNextRenewTime = Tick64() + p->DhcpRenewInterval; if (true) { char client_ip_str[64], server_ip_str[64]; char subnet_str[64], defgw_str[64]; char dns1_str[64], dns2_str[64]; char wins1_str[64], wins2_str[64]; IPToStr32(client_ip_str, sizeof(client_ip_str), cao.ClientAddress); IPToStr32(server_ip_str, sizeof(server_ip_str), cao.ServerAddress); IPToStr32(subnet_str, sizeof(subnet_str), cao.SubnetMask); IPToStr32(defgw_str, sizeof(defgw_str), cao.Gateway); IPToStr32(dns1_str, sizeof(dns1_str), cao.DnsServer); IPToStr32(dns2_str, sizeof(dns2_str), cao.DnsServer2); IPToStr32(wins1_str, sizeof(wins1_str), cao.WinsServer); IPToStr32(wins2_str, sizeof(wins2_str), cao.WinsServer2); PPPLog(p, "LP_DHCP_REQUEST_OK", client_ip_str, subnet_str, defgw_str, cao.DomainName, dns1_str, dns2_str, wins1_str, wins2_str, server_ip_str, cao.LeaseTime); } } else { Debug("IPCDhcpAllocateIP failed.\n"); p->DhcpIpAllocTried = false; ok = false; PPPLog(p, "LP_DHCP_REQUEST_NG"); } } } } // If we already have a configured IP data - send it along if (IsValidUnicastIPAddressUINT4(p->ClientAddressOption.ClientAddress) && p->ClientAddressOption.SubnetMask != 0 && ok) { // Success to determine the address UINTToIP(&subnet, p->ClientAddressOption.SubnetMask); UINTToIP(&gw, p->ClientAddressOption.Gateway); Zero(&res, sizeof(res)); UINTToIP(&res.IpAddress, p->ClientAddressOption.ClientAddress); UINTToIP(&res.DnsServer1, p->ClientAddressOption.DnsServer); UINTToIP(&res.DnsServer2, p->ClientAddressOption.DnsServer2); UINTToIP(&res.WinsServer1, p->ClientAddressOption.WinsServer); UINTToIP(&res.WinsServer2, p->ClientAddressOption.WinsServer2); if (IPCSetIPv4Parameters(p->Ipc, &res.IpAddress, &subnet, &gw, &p->ClientAddressOption.ClasslessRoute)) { char client_ip_str[64]; char subnet_str[64], defgw_str[64]; char dns1_str[64], dns2_str[64]; char wins1_str[64], wins2_str[64]; // IPv4 parameters have been set for the first time Debug("Param First Set.\n"); IPToStr(client_ip_str, sizeof(client_ip_str), &res.IpAddress); IPToStr(subnet_str, sizeof(subnet_str), &subnet); IPToStr(defgw_str, sizeof(defgw_str), &gw); IPToStr(dns1_str, sizeof(dns1_str), &res.DnsServer1); IPToStr(dns2_str, sizeof(dns2_str), &res.DnsServer2); IPToStr(wins1_str, sizeof(wins1_str), &res.WinsServer1); IPToStr(wins2_str, sizeof(wins2_str), &res.WinsServer2); PPPLog(p, "LP_SET_IPV4_PARAM", client_ip_str, subnet_str, defgw_str, dns1_str, dns2_str, wins1_str, wins2_str); } /*// Backporting static configuration received from client - let him use whatever he wants, // he won't accept anything else anyway (as per testing with Windows clients) if (!IsZeroIP(&o.DnsServer1)) { CopyIP(&res.DnsServer1, &o.DnsServer1); Debug("Setting DNS1 from client\n"); } if (!IsZeroIP(&o.DnsServer2)) { CopyIP(&res.DnsServer2, &o.DnsServer2); Debug("Setting DNS2 from client\n"); } if (!IsZeroIP(&o.WinsServer1)) { CopyIP(&res.WinsServer1, &o.WinsServer1); Debug("Setting WINS1 from client\n"); } if (!IsZeroIP(&o.WinsServer2)) { CopyIP(&res.WinsServer2, &o.WinsServer2); Debug("Setting WINS2 from client\n"); }*/ /*if (!IsZeroIP(&res.DnsServer1) && IsZeroIP(&res.DnsServer2)) { CopyIP(&res.DnsServer2, &res.DnsServer1); } if (!IsZeroIP(&res.WinsServer1) && IsZeroIP(&res.WinsServer2)) { CopyIP(&res.WinsServer2, &res.WinsServer1); }*/ PPPSetIPOptionToLCP(&res, pp->Lcp, true); } // We couldn't configure address for the client else { // Failed to determine the address Debug("IP Address Determination Failed.\n"); Zero(&res, sizeof(res)); // We will try to reconfigure if we receive another request by wiping all data Zero(&p->ClientAddressOption, sizeof(DHCP_OPTION_LIST)); p->UseStaticIPAddress = false; PPPSetIPOptionToLCP(&res, pp->Lcp, true); } if (PPPRejectLCPOptionsEx(p, pp, processed)) { Debug("Rejected IPCP options ID = 0x%x\n", pp->Lcp->Id); processed = true; } if (ok && PPPNackLCPOptionsEx(p, pp, processed)) { Debug("NACKed IPCP options ID = 0x%x\n", pp->Lcp->Id); processed = true; } // We will delay this packet ACK and send the server IP first, then wait for a reparse // it is kind of dirty but fixes issues on some clients (namely VPN Client Pro on Android) if (p->IPv4_State == PPP_PROTO_STATUS_CLOSED && p->ClientAddressOption.ServerAddress != NULL && ok) { PPP_LCP* c = NewPPPLCP(PPP_LCP_CODE_REQ, 0); UINT ui = p->ClientAddressOption.ServerAddress; Add(c->OptionList, NewPPPOption(PPP_IPCP_OPTION_IP, &ui, sizeof(UINT))); if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPCP, c)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } p->IPv4_State = PPP_PROTO_STATUS_CONFIG; if (!processed) { PPPAddNextPacket(p, pp, 1); } return false; } // We still haven't received any answer from client about server IP, keep waiting... if ((p->IPv4_State == PPP_PROTO_STATUS_CONFIG || p->IPv4_State == PPP_PROTO_STATUS_CLOSED) && !processed) { PPPAddNextPacket(p, pp, 1); return false; } //Debug("PPPAckLCPOptionsEx ok=%x, processed=%x", ok, processed); if (!ok || !PPPAckLCPOptionsEx(p, pp, processed)) { return false; } Debug("ACKed IPCP options ID = 0x%x\n", pp->Lcp->Id); if (ok && p->IPv4_State == PPP_PROTO_STATUS_CONFIG_WAIT) { p->IPv4_State = PPP_PROTO_STATUS_OPENED; Debug("IPv4 OPENED\n"); } return ok; } // LCP option based packets utility bool PPPRejectLCPOptions(PPP_SESSION *p, PPP_PACKET* pp) { return PPPRejectLCPOptionsEx(p, pp, false); } bool PPPRejectLCPOptionsEx(PPP_SESSION *p, PPP_PACKET* pp, bool simulate) { UINT i = 0; bool toBeRejected = false; PPP_PACKET* ret; for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++) { PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i); if (t->IsSupported == false) { toBeRejected = true; break; } } if (toBeRejected == false) { return false; } ret = ZeroMalloc(sizeof(PPP_PACKET)); ret->IsControl = true; ret->Protocol = pp->Protocol; // Return a Reject if there are unsupported parameters ret->Lcp = NewPPPLCP(PPP_LCP_CODE_REJECT, pp->Lcp->Id); for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++) { PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i); if (t->IsSupported == false) { // Attach the original option value as is Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize)); Debug("Rejected LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol); } } if (LIST_NUM(ret->Lcp->OptionList) == 0 || simulate) { FreePPPPacket(ret); return false; } PPPSendPacketAndFree(p, ret); return true; } bool PPPNackLCPOptions(PPP_SESSION *p, PPP_PACKET* pp) { return PPPNackLCPOptionsEx(p, pp, false); } bool PPPNackLCPOptionsEx(PPP_SESSION *p, PPP_PACKET* pp, bool simulate) { UINT i = 0; PPP_PACKET* ret; bool toBeNACKed = false; for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++) { PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i); if (t->IsAccepted == false && t->IsSupported == true) { toBeNACKed = true; break; } } if (toBeNACKed == false) { return false; } ret = ZeroMalloc(sizeof(PPP_PACKET)); ret->IsControl = true; ret->Protocol = pp->Protocol; // Return a NAK if there are any unacceptable parameter // even that all parameters are supported ret->Lcp = NewPPPLCP(PPP_LCP_CODE_NAK, pp->Lcp->Id); for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++) { PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i); if (t->IsAccepted == false && t->IsSupported == true) { // Replace the original option value with an acceptable value Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->AltData, t->AltDataSize)); Debug("NACKed LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol); } } if (LIST_NUM(ret->Lcp->OptionList) == 0 || simulate) { FreePPPPacket(ret); return false; } PPPSendPacketAndFree(p, ret); return true; } bool PPPAckLCPOptions(PPP_SESSION *p, PPP_PACKET* pp) { return PPPAckLCPOptionsEx(p, pp, false); } bool PPPAckLCPOptionsEx(PPP_SESSION *p, PPP_PACKET* pp, bool simulate) { UINT i = 0; PPP_PACKET* ret; bool toBeACKed = false; if (LIST_NUM(pp->Lcp->OptionList) == 0) { // We acknoweldge an empty option list toBeACKed = true; Debug("ACKing empty LCP options list, id=%i\n", pp->Lcp->Id); } for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++) { PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i); if (t->IsAccepted == true && t->IsSupported == true) { toBeACKed = true; break; } } if (toBeACKed == false) { return false; } ret = ZeroMalloc(sizeof(PPP_PACKET)); ret->IsControl = true; ret->Protocol = pp->Protocol; // Return an ACK if all parameters are accepted ret->Lcp = NewPPPLCP(PPP_LCP_CODE_ACK, pp->Lcp->Id); for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++) { PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i); if (t->IsAccepted == true && t->IsSupported == true) { // Attach the original option value as is Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize)); Debug("ACKed LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol); } } if (simulate) { FreePPPPacket(ret); return false; } PPPSendPacketAndFree(p, ret); return true; } // PPP networking functions // Send a request packet in the PPP bool PPPSendAndRetransmitRequest(PPP_SESSION *p, USHORT protocol, PPP_LCP *c) { PPP_PACKET *pp; UINT64 now = Tick64(); PPP_REQUEST_RESEND* resend; // Validate arguments if (p == NULL || c == NULL) { return false; } pp = ZeroMalloc(sizeof(PPP_PACKET)); pp->Protocol = protocol; pp->IsControl = true; pp->Lcp = c; if (pp->Lcp->Id == 0) { pp->Lcp->Id = p->NextId++; } // Send the PPP packet if (!PPPSendPacketEx(p, pp, false)) { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } resend = ZeroMalloc(sizeof(PPP_REQUEST_RESEND)); resend->Id = pp->Lcp->Id; resend->Packet = pp; resend->ResendTime = now + PPP_PACKET_RESEND_INTERVAL; resend->TimeoutTime = now + p->PacketRecvTimeout; Add(p->SentReqPacketList, resend); return true; } // Send the PPP packet and frees the sent packet bool PPPSendPacketAndFree(PPP_SESSION *p, PPP_PACKET *pp) { bool result = PPPSendPacketEx(p, pp, false); FreePPPPacket(pp); return result; } // Send the PPP packet bool PPPSendPacketEx(PPP_SESSION *p, PPP_PACKET *pp, bool no_flush) { bool ret = false; BUF *b; // Validate arguments if (p == NULL || pp == NULL) { return false; } b = BuildPPPPacketData(pp); if (b == NULL) { return false; } ret = TubeSendEx(p->TubeSend, b->Buf, b->Size, NULL, no_flush); if (no_flush) { AddTubeToFlushList(p->FlushList, p->TubeSend); } FreeBuf(b); return ret; } // Receive a PPP packet PPP_PACKET *PPPRecvPacket(PPP_SESSION *p, bool async) { TUBEDATA *d; PPP_PACKET *pp; // Validate arguments if (p == NULL) { return NULL; } LABEL_LOOP: if (async == false) { d = TubeRecvSync(p->TubeRecv, p->PacketRecvTimeout); } else { d = TubeRecvAsync(p->TubeRecv); } if (d == NULL) { return NULL; } pp = ParsePPPPacket(d->Data, d->DataSize); FreeTubeData(d); if (pp == NULL) { // A broken packet is received goto LABEL_LOOP; } p->LastRecvTime = Tick64(); return pp; } PPP_PACKET *PPPGetNextPacket(PPP_SESSION *p) { PPP_PACKET* ret = NULL; UINT i = 0; if (p->CurrentPacket != NULL) { FreePPPPacket(p->CurrentPacket); p->CurrentPacket = NULL; } for (i = 0; i < LIST_NUM(p->DelayedPackets); i++) { PPP_DELAYED_PACKET* t = LIST_DATA(p->DelayedPackets, i); if (t->DelayTicks > 0) { t->DelayTicks--; } else { ret = t->Packet; Delete(p->DelayedPackets, t); Free(t); break; } } if (ret != NULL) { p->CurrentPacket = ret; return ret; } ret = PPPRecvPacket(p, true); if (ret != NULL && ret->IsControl && ret->Lcp != NULL) { PPP_DELAYED_PACKET* firstRelated = NULL; for (i = 0; i < LIST_NUM(p->DelayedPackets); i++) { PPP_DELAYED_PACKET* t = LIST_DATA(p->DelayedPackets, i); char related = PPPRelatedPacketComparator(ret, t->Packet); if (related != 0xF && related != 0xE) { if (related == 0) { // It's the same packet, just remove it and wait for it's delays FreePPPPacket(ret); firstRelated = NULL; ret = NULL; break; } if (related == 1) { // We got a packet which should come later than any of delayed ones PPPAddNextPacket(p, ret, t->DelayTicks); firstRelated = NULL; ret = NULL; break; } if (related == -1) { char prevFoundRelated = -1; if (firstRelated != NULL) { prevFoundRelated = PPPRelatedPacketComparator(t->Packet, firstRelated->Packet); } if (prevFoundRelated == -1) { firstRelated = t; } } } } if (firstRelated != NULL) { PPPAddNextPacket(p, ret, firstRelated->DelayTicks); ret = NULL; } } p->CurrentPacket = ret; return ret; } void PPPAddNextPacket(PPP_SESSION *p, PPP_PACKET *pp, UINT delay) { PPP_DELAYED_PACKET* t = ZeroMalloc(sizeof(PPP_DELAYED_PACKET)); UINT i; if (p->CurrentPacket == pp) { p->CurrentPacket = NULL; } t->Packet = pp; t->DelayTicks = delay; Add(p->DelayedPackets, t); Sort(p->DelayedPackets); /*Debug("after sorting delayeds\n"); for (i = 0; i < LIST_NUM(p->DelayedPackets); i++) { t = LIST_DATA(p->DelayedPackets, i); if (t->Packet->Lcp != NULL) { Debug("> Packet proto = 0x%x, id = 0x%x, code = 0x%x, delay = 0x%x\n", t->Packet->Protocol, t->Packet->Lcp->Id, t->Packet->Lcp->Code, t->DelayTicks); } } Debug("after sorting delayeds end\n");*/ } int PPPDelayedPacketsComparator(const void* a, const void* b) { PPP_DELAYED_PACKET* first = a; PPP_DELAYED_PACKET* second = b; char related = PPPRelatedPacketComparator(first->Packet, second->Packet); if (related == 0xF || related == 0xE) { if (first->DelayTicks < second->DelayTicks) { return -1; } if (first->DelayTicks > second->DelayTicks) { return 1; } return 0; } // We make all delay ticks to be accounted with the sorting if (related <= 1 && related >= -1) { if (related == -1 && first->DelayTicks >= second->DelayTicks) { second->DelayTicks = first->DelayTicks; second->DelayTicks++; } else if (related == 1 && first->DelayTicks <= second->DelayTicks) { first->DelayTicks = second->DelayTicks; first->DelayTicks++; } return related; } return 0; } // -1 - packet a comes before packet b // 0 - this is the same packet // 1 - packet a comes after packet b // 0xF - packet is not related // 0xE - we got an error while comparing, treating as not related would be the most correct char PPPRelatedPacketComparator(PPP_PACKET* a, PPP_PACKET* b) { if (a->IsControl && b->IsControl && a->Lcp != NULL && b->Lcp != NULL && a->Protocol == b->Protocol && PPP_CODE_IS_REQUEST(a->Protocol, a->Lcp->Code) == PPP_CODE_IS_REQUEST(b->Protocol, b->Lcp->Code) && PPP_CODE_IS_RESPONSE(a->Protocol, a->Lcp->Code) == PPP_CODE_IS_RESPONSE(b->Protocol, b->Lcp->Code)) { // The packet is related! if (a->Lcp->Id < b->Lcp->Id) { return -1; } else if (a->Lcp->Id == b->Lcp->Id) { if (a->Lcp->Code == b->Lcp->Code) { return 0; } else { return 0xE; } } else if (a->Lcp->Id > b->Lcp->Id) { return 1; } else { return 0xE; } } else { // The packet is not related! return 0xF; } } // PPP utility functions // Packet structure creation utilities // Create the LCP PPP_LCP *NewPPPLCP(UCHAR code, UCHAR id) { PPP_LCP *c = ZeroMalloc(sizeof(PPP_LCP)); c->Code = code; c->Id = id; c->OptionList = NewListFast(NULL); return c; } // Create a new PPP options PPP_OPTION *NewPPPOption(UCHAR type, void *data, UINT size) { PPP_OPTION *o; // Validate arguments if (size != 0 && data == NULL) { return NULL; } o = ZeroMalloc(sizeof(PPP_OPTION)); o->Type = type; Copy(o->Data, data, size); o->DataSize = size; return o; } // Packet parse utilities // Analyse the PPP packet PPP_PACKET *ParsePPPPacket(void *data, UINT size) { PPP_PACKET *pp; UCHAR *buf; // Validate arguments if (data == NULL || size == 0) { return NULL; } pp = ZeroMalloc(sizeof(PPP_PACKET)); buf = (UCHAR *)data; // Address if (size < 1) { goto LABEL_ERROR; } if (buf[0] != 0xff) { goto LABEL_ERROR; } size--; buf++; // Control if (size < 1) { goto LABEL_ERROR; } if (buf[0] != 0x03) { goto LABEL_ERROR; } size--; buf++; // Protocol if (size < 2) { goto LABEL_ERROR; } pp->Protocol = READ_USHORT(buf); size -= 2; buf += 2; if (pp->Protocol == PPP_PROTOCOL_LCP || pp->Protocol == PPP_PROTOCOL_PAP || pp->Protocol == PPP_PROTOCOL_CHAP || pp->Protocol == PPP_PROTOCOL_IPCP || pp->Protocol == PPP_PROTOCOL_IPV6CP) { pp->IsControl = true; } pp->Data = Clone(buf, size); pp->DataSize = size; if (pp->IsControl) { pp->Lcp = PPPParseLCP(pp->Protocol, pp->Data, pp->DataSize); if (pp->Lcp == NULL) { goto LABEL_ERROR; } } return pp; LABEL_ERROR: FreePPPPacket(pp); return NULL; } // Analyse the LCP data PPP_LCP *PPPParseLCP(USHORT protocol, void *data, UINT size) { UCHAR *buf; PPP_LCP *c; USHORT len; bool has_option_list = false; // Validate arguments if (data == NULL || size == 0) { return NULL; } buf = (UCHAR *)data; c = ZeroMalloc(sizeof(PPP_LCP)); c->OptionList = NewListFast(NULL); // Code if (size < 1) { goto LABEL_ERROR; } c->Code = buf[0]; buf++; size--; // ID if (size < 1) { goto LABEL_ERROR; } c->Id = buf[0]; buf++; size--; // Length if (size < 2) { goto LABEL_ERROR; } len = READ_USHORT(buf); if (len < 4) { goto LABEL_ERROR; } len -= 4; buf += 2; size -= 2; // Options or Data if (size < len) { goto LABEL_ERROR; } has_option_list = PPP_CODE_IS_WITH_OPTION_LIST(protocol, c->Code); if (has_option_list == false) { c->Data = Clone(buf, size); c->DataSize = size; } else { // Option List while (len >= 1) { PPP_OPTION o; Zero(&o, sizeof(o)); // Type if (len < 1) { goto LABEL_ERROR; } o.Type = buf[0]; buf++; len--; // Length if (len < 1) { goto LABEL_ERROR; } o.DataSize = buf[0]; if (o.DataSize < 2) { goto LABEL_ERROR; } o.DataSize -= 2; buf++; len--; // Data if (len < o.DataSize) { goto LABEL_ERROR; } Copy(o.Data, buf, o.DataSize); buf += o.DataSize; len -= o.DataSize; Add(c->OptionList, Clone(&o, sizeof(o))); } } return c; LABEL_ERROR: FreePPPLCP(c); return NULL; } // Analyse MS CHAP v2 Response packet bool PPPParseMSCHAP2ResponsePacket(PPP_SESSION* p, PPP_PACKET* pp) { bool ok = false; char client_ip_tmp[256]; EAP_CLIENT *eap; UCHAR client_response_buffer[49]; UCHAR *client_challenge_16; UCHAR *client_response_24; char username_tmp[MAX_SIZE]; IPC *ipc = NULL; char id[MAX_SIZE]; char hub[MAX_SIZE]; char password[MAX_SIZE]; char server_challenge_hex[MAX_SIZE]; char client_challenge_hex[MAX_SIZE]; char client_response_hex[MAX_SIZE]; char eap_client_hex[64]; ETHERIP_ID d; UINT error_code; UINT64 eap_client_ptr = (UINT64)p->EapClient; if (pp->Lcp != NULL && pp->Lcp->DataSize >= 51) { BUF *b; if (pp->Lcp->Id != p->MsChapV2_PacketId) { Debug("Got incorrect LCP PacketId! Should be 0x%x, got 0x%x\n", p->MsChapV2_PacketId, pp->Lcp->Id); p->MsChapV2_PacketId = pp->Lcp->Id; } b = NewBuf(); WriteBuf(b, pp->Lcp->Data, pp->Lcp->DataSize); SeekBuf(b, 0, 0); if (ReadBufChar(b) == 49) { ReadBuf(b, client_response_buffer, 49); Zero(username_tmp, sizeof(username_tmp)); ReadBuf(b, username_tmp, sizeof(username_tmp) - 1); Debug("MS-CHAPv2: id=%s\n", username_tmp); client_challenge_16 = client_response_buffer + 0; client_response_24 = client_response_buffer + 16 + 8; Copy(p->MsChapV2_ClientChallenge, client_challenge_16, 16); Copy(p->MsChapV2_ClientResponse, client_response_24, 24); Zero(id, sizeof(id)); Zero(hub, sizeof(hub)); // The user name is divided into the ID and the virtual HUB name Zero(&d, sizeof(d)); PPPParseUsername(p->Cedar, username_tmp, &d); StrCpy(id, sizeof(id), d.UserName); StrCpy(hub, sizeof(hub), d.HubName); Debug("MS-CHAPv2: username=%s, hubname=%s\n", id, hub); IPToStr(client_ip_tmp, sizeof(client_ip_tmp), &p->ClientIP); // Convert the MS-CHAPv2 data to a password string BinToStr(server_challenge_hex, sizeof(server_challenge_hex), p->MsChapV2_ServerChallenge, sizeof(p->MsChapV2_ServerChallenge)); BinToStr(client_challenge_hex, sizeof(client_challenge_hex), p->MsChapV2_ClientChallenge, sizeof(p->MsChapV2_ClientChallenge)); BinToStr(client_response_hex, sizeof(client_response_hex), p->MsChapV2_ClientResponse, sizeof(p->MsChapV2_ClientResponse)); BinToStr(eap_client_hex, sizeof(eap_client_hex), &eap_client_ptr, 8); Format(password, sizeof(password), "%s%s:%s:%s:%s:%s", IPC_PASSWORD_MSCHAPV2_TAG, username_tmp, server_challenge_hex, client_challenge_hex, client_response_hex, eap_client_hex); if (p->MsChapV2_UseDoubleMsChapV2 && p->EapClient == NULL) { Debug("Double MSCHAPv2 creating EAP client\n"); eap = HubNewEapClient(p->Cedar, hub, client_ip_tmp, id, "L3:PPP"); if (eap) { ok = true; p->EapClient = eap; } else { PPPSetStatus(p, PPP_STATUS_FAIL); WHERE; return false; } } else if (p->Ipc == NULL) { Debug("MSCHAPv2 creating IPC\n"); ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password, &error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort, p->ClientHostname, p->CryptName, false, p->AdjustMss, p->EapClient, NULL, + IPC_LAYER_3); if (ipc != NULL) { p->Ipc = ipc; // Setting user timeouts p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000; p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000; p->UserConnectionTick = Tick64(); Copy(p->MsChapV2_ServerResponse, ipc->MsChapV2_ServerResponse, 20); ok = true; p->AuthOk = true; } } else { Debug("Got weird packet when we already have an active IPC! Ipc = 0x%x, AuthOk = 0x%x, Status = 0x%x\n", p->Ipc, p->AuthOk, p->PPPStatus); ok = p->AuthOk; } } FreeBuf(b); } else { Debug("Got invalid MSCHAPv2 packet\n"); } return ok; } // Packet building utilities // Build a PPP packet data BUF *BuildPPPPacketData(PPP_PACKET *pp) { BUF *ret; UCHAR c; USHORT us; // Validate arguments if (pp == NULL) { return NULL; } ret = NewBuf(); // Address c = 0xff; WriteBuf(ret, &c, 1); // Control c = 0x03; WriteBuf(ret, &c, 1); // Protocol us = Endian16(pp->Protocol); WriteBuf(ret, &us, 2); if (pp->IsControl) { // LCP BUF *b = BuildLCPData(pp->Lcp); WriteBufBuf(ret, b); FreeBuf(b); } else { // Data WriteBuf(ret, pp->Data, pp->DataSize); } SeekBuf(ret, 0, 0); return ret; } // Build the LCP packet data BUF *BuildLCPData(PPP_LCP *c) { BUF *b; UCHAR zero = 0; UINT i; // Validate arguments if (c == NULL) { return NULL; } b = NewBuf(); // Code WriteBuf(b, &c->Code, 1); // ID WriteBuf(b, &c->Id, 1); // Length (to be updated later) zero = 0; WriteBuf(b, &zero, 1); WriteBuf(b, &zero, 1); if (c->Data == NULL) { // Option List for (i = 0; i < LIST_NUM(c->OptionList); i++) { PPP_OPTION *o = LIST_DATA(c->OptionList, i); UCHAR sz = o->DataSize + 2; WriteBuf(b, &o->Type, 1); WriteBuf(b, &sz, 1); WriteBuf(b, o->Data, o->DataSize); } } else { // Data WriteBuf(b, c->Data, c->DataSize); } SeekBuf(b, 0, 0); // Update Length WRITE_USHORT(((UCHAR *)b->Buf) + 2, b->Size); return b; } // Build the MS CHAP v2 challenge packet PPP_LCP *BuildMSCHAP2ChallengePacket(PPP_SESSION* p) { PPP_LCP *lcp; BUF *b; char machine_name[MAX_SIZE]; UINT64 now = Tick64(); // Generate a Server Challenge packet of MS-CHAP v2 GetMachineHostName(machine_name, sizeof(machine_name)); if (p->EapClient == NULL) { MsChapV2Server_GenerateChallenge(p->MsChapV2_ServerChallenge); } else { Copy(p->MsChapV2_ServerChallenge, p->EapClient->MsChapV2Challenge.Chap_ChallengeValue, 16); } p->MsChapV2_PacketId = p->NextId++; lcp = NewPPPLCP(PPP_CHAP_CODE_CHALLENGE, p->MsChapV2_PacketId); b = NewBuf(); WriteBufChar(b, 16); WriteBuf(b, p->MsChapV2_ServerChallenge, sizeof(p->MsChapV2_ServerChallenge)); WriteBuf(b, machine_name, StrLen(machine_name)); lcp->Data = Clone(b->Buf, b->Size); lcp->DataSize = b->Size; FreeBuf(b); Debug("Building MS-CHAP v2 Challenge\n"); return lcp; } // IPCP packet utilities // Set the IP options of PPP to LCP bool PPPSetIPOptionToLCP(PPP_IPOPTION *o, PPP_LCP *c, bool only_modify) { bool ret = false; // Validate arguments if (c == NULL || o == NULL) { return false; } ret = PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_IP, &o->IpAddress, only_modify); PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_DNS1, &o->DnsServer1, only_modify); PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_DNS2, &o->DnsServer2, only_modify); PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_WINS1, &o->WinsServer1, only_modify); PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_WINS2, &o->WinsServer2, only_modify); return ret; } // Get the IP options of PPP from LCP bool PPPGetIPOptionFromLCP(PPP_IPOPTION *o, PPP_LCP *c) { bool ret; // Validate arguments if (c == NULL || o == NULL) { return false; } Zero(o, sizeof(PPP_IPOPTION)); ret = PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_IP, &o->IpAddress); PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_DNS1, &o->DnsServer1); PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_DNS2, &o->DnsServer2); PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_WINS1, &o->WinsServer1); PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_WINS2, &o->WinsServer2); return ret; } // Set the IP address data to the option list of the LCP bool PPPSetIPAddressValueToLCP(PPP_LCP *c, UINT type, IP *ip, bool only_modify) { IP ip2; UINT ui; // Validate arguments if (c == NULL || ip == NULL) { return false; } ui = IPToUINT(ip); if (PPPGetIPAddressValueFromLCP(c, type, &ip2)) { PPP_OPTION *opt; opt = PPPGetOptionValue(c, type); if (opt != NULL) { if (IsZeroIP(ip) == false) { if (CmpIpAddr(&ip2, ip) == 0) { // No change opt->IsAccepted = true; opt->IsSupported = true; } else { // Changed opt->IsAccepted = false; opt->IsSupported = true; opt->AltDataSize = 4; Copy(opt->AltData, &ui, 4); } } else { // The parameter itself is not supported // (if the IP address is 0.0.0.0) opt->IsSupported = false; opt->IsAccepted = false; } } return true; } else { if (IsZeroIP(ip) == false) { // Add as a new item if (only_modify != false) { return false; } else { PPP_OPTION *opt2 = NewPPPOption(type, &ui, 4); UCHAR ipstr[MAX_SIZE]; opt2->IsAccepted = true; opt2->IsSupported = true; Copy(opt2->AltData, opt2->Data, opt2->DataSize); opt2->AltDataSize = opt2->DataSize; Add(c->OptionList, opt2); IPToStr(ipstr, MAX_SIZE, ip); return true; } } else { return false; } } } // Get the IP address data from the option list of the LCP bool PPPGetIPAddressValueFromLCP(PPP_LCP *c, UINT type, IP *ip) { PPP_OPTION *opt; UINT ui; // Validate arguments if (c == NULL || ip == NULL) { return false; } opt = PPPGetOptionValue(c, type); if (opt == NULL) { return false; } if (opt->DataSize != 4) { return false; } opt->IsSupported = true; ui = *((UINT *)opt->Data); UINTToIP(ip, ui); return true; } // Other packet utilities // Get the option value PPP_OPTION *PPPGetOptionValue(PPP_LCP *c, UCHAR type) { UINT i; // Validate arguments if (c == NULL) { return NULL; } for (i = 0; i < LIST_NUM(c->OptionList); i++) { PPP_OPTION *t = LIST_DATA(c->OptionList, i); if (t->Type == type) { return t; } } return NULL; } // Check whether the Virtual HUB with the specified name exist? bool IsHubExistsWithLock(CEDAR *cedar, char *hubname) { bool ret = false; // Validate arguments if (cedar == NULL || hubname == NULL) { return false; } LockList(cedar->HubList); { ret = IsHub(cedar, hubname); } UnlockList(cedar->HubList); return ret; } // Sets the PPP status without overwriting the FAIL status void PPPSetStatus(PPP_SESSION *p, UINT status) { if (status == PPP_STATUS_FAIL) { Debug("SETTING PPP_STATUS_FAIL!!!\n"); } if (!PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) || PPP_STATUS_IS_UNAVAILABLE(status)) { p->PPPStatus = status; } } // Memory freeing functions // Release the PPP session void FreePPPSession(PPP_SESSION *p) { UINT i; // Validate arguments if (p == NULL) { return; } // Release the memory for (i = 0; i < LIST_NUM(p->RecvPacketList); i++) { PPP_PACKET *pp = LIST_DATA(p->RecvPacketList, i); FreePPPPacket(pp); } ReleaseList(p->RecvPacketList); for (i = 0; i < LIST_NUM(p->SentReqPacketList); i++) { PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i); FreePPPPacket(t->Packet); Free(t); } ReleaseList(p->SentReqPacketList); for (i = 0; i < LIST_NUM(p->DelayedPackets); i++) { PPP_DELAYED_PACKET *t = LIST_DATA(p->DelayedPackets, i); FreePPPPacket(t->Packet); Free(t); } ReleaseList(p->DelayedPackets); if (p->CurrentPacket != NULL) { FreePPPPacket(p->CurrentPacket); } if (p->TubeRecv != NULL) { // Record the PPP disconnect reason code for L2TP p->TubeRecv->IntParam1 = p->DisconnectCauseCode; p->TubeRecv->IntParam2 = p->DisconnectCauseDirection; } FreeTubeFlushList(p->FlushList); TubeDisconnect(p->TubeRecv); TubeDisconnect(p->TubeSend); ReleaseCedar(p->Cedar); ReleaseTube(p->TubeRecv); ReleaseTube(p->TubeSend); if (p->Ipc != NULL) { FreeIPC(p->Ipc); } PPPFreeEapClient(p); Free(p); } // Release the LCP void FreePPPLCP(PPP_LCP *c) { // Validate arguments if (c == NULL) { return; } FreePPPOptionList(c->OptionList); Free(c->Data); Free(c); } // Release the PPP options list void FreePPPOptionList(LIST *o) { UINT i; // Validate arguments if (o == NULL) { return; } for (i = 0; i < LIST_NUM(o); i++) { PPP_OPTION *t = LIST_DATA(o, i); Free(t); } ReleaseList(o); } // Release the PPP packet void FreePPPPacket(PPP_PACKET *pp) { FreePPPPacketEx(pp, false); } void FreePPPPacketEx(PPP_PACKET *pp, bool no_free_struct) { // Validate arguments if (pp == NULL) { return; } FreePPPLCP(pp->Lcp); Free(pp->Data); if (no_free_struct == false) { Free(pp); } } // Free the associated EAP client void PPPFreeEapClient(PPP_SESSION *p) { if (p == NULL) { return; } if (p->EapClient != NULL) { ReleaseEapClient(p->EapClient); p->EapClient = NULL; } } // Utility functions used not only in PPP stack // Separate into the user name and the Virtual HUB name by analyzing the string bool PPPParseUsername(CEDAR *cedar, char *src_username, ETHERIP_ID *dst) { UINT i, len, last_at, first_en; char token1[MAX_SIZE]; // username char token2[MAX_SIZE]; // hub_name char src[MAX_SIZE]; // Validate arguments Zero(dst, sizeof(ETHERIP_ID)); if (cedar == NULL || src == NULL || dst == NULL) { return false; } StrCpy(src, sizeof(src), src_username); Trim(src); // Search for the first "\\" in the string len = StrLen(src); first_en = SearchStrEx(src, "\\", 0, true); if (first_en != INFINITE && first_en >= 1 && (first_en < (len - 1))) { StrCpy(token1, sizeof(token1), src + first_en + 1); StrCpy(token2, sizeof(token2), src); token2[first_en] = 0; // Confirm whether the hubname exists if the virtual HUB name is // specified like as hubname\username if (IsHubExistsWithLock(cedar, token2) == false) { // If the hubname does not exist, restore to the original name StrCpy(token1, sizeof(token1), src); ClearStr(token2, sizeof(token2)); } } else { // Search for the separator character's last position in the string len = StrLen(src); last_at = INFINITE; for (i = 0; i < len; i++) { char c = src[i]; if (c == cedar->UsernameHubSeparator) { last_at = i; } } Zero(token1, sizeof(token1)); Zero(token2, sizeof(token2)); if (last_at == INFINITE) { // The separator character is not specifiedd StrCpy(token1, sizeof(token1), src); } else { StrCpy(token1, sizeof(token1), src); token1[last_at] = 0; StrCpy(token2, sizeof(token2), src + last_at + 1); } // Check whether such Virtual HUB exists If the virtual HUB name is specified if (IsEmptyStr(token2) == false) { if (IsHubExistsWithLock(cedar, token2) == false) { // Because the specified virtual HUB name doesn't exist, it's considered to be a part of the user name StrCpy(token1, sizeof(token1), src); ClearStr(token2, sizeof(token2)); } } } if (IsEmptyStr(token2)) { // Select the default Virtual HUB if the Virtual HUB name is not specified StrCpy(token2, sizeof(token2), SERVER_DEFAULT_HUB_NAME); if (cedar->Server != NULL && cedar->Server->IPsecServer != NULL) { Lock(cedar->Server->IPsecServer->LockSettings); { IPsecNormalizeServiceSetting(cedar->Server->IPsecServer); StrCpy(token2, sizeof(token2), cedar->Server->IPsecServer->Services.L2TP_DefaultHub); } Unlock(cedar->Server->IPsecServer->LockSettings); } } // Return the results StrCpy(dst->HubName, sizeof(dst->HubName), token2); StrCpy(dst->UserName, sizeof(dst->UserName), token1); return true; } // Generate the NT hash of the password void GenerateNtPasswordHash(UCHAR *dst, char *password) { UCHAR *tmp; UINT tmp_size; UINT i, len; // Validate arguments if (dst == NULL || password == NULL) { return; } // Generate a Unicode password len = StrLen(password); tmp_size = len * 2; tmp = ZeroMalloc(tmp_size); for (i = 0;i < len;i++) { tmp[i * 2] = password[i]; } // Hashing HashMd4(dst, tmp, tmp_size); Free(tmp); } // Generate the MS-CHAPv2 server-side challenge void MsChapV2Server_GenerateChallenge(UCHAR *dst) { // Validate arguments if (dst == NULL) { return; } Rand(dst, 16); } // Generate the MS-CHAPv2 client-side challenge void MsChapV2Client_GenerateChallenge(UCHAR *dst) { // Validate arguments if (dst == NULL) { return; } Rand(dst, 16); } // Generate a 8 bytes challenge void MsChapV2_GenerateChallenge8(UCHAR *dst, UCHAR *client_challenge, UCHAR *server_challenge, char *username) { BUF *b; UCHAR hash[SHA1_SIZE]; char username2[MAX_SIZE]; char domainname2[MAX_SIZE]; // Validate arguments if (dst == NULL || client_challenge == NULL || server_challenge == NULL) { return; } b = NewBuf(); WriteBuf(b, client_challenge, 16); WriteBuf(b, server_challenge, 16); ParseNtUsername(username, username2, sizeof(username2), domainname2, sizeof(domainname2), true); if (IsEmptyStr(username2) == false) { WriteBuf(b, username2, StrLen(username2)); } Sha1(hash, b->Buf, b->Size); FreeBuf(b); Copy(dst, hash, 8); } // Generate the MS-CHAPv2 client response void MsChapV2Client_GenerateResponse(UCHAR *dst, UCHAR *challenge8, UCHAR *nt_password_hash) { UCHAR password_hash_2[21]; UCHAR key1[8], key2[8], key3[8]; // Validate arguments if (dst == NULL || challenge8 == NULL || nt_password_hash == NULL) { return; } Zero(password_hash_2, sizeof(password_hash_2)); Copy(password_hash_2, nt_password_hash, 16); Zero(key1, sizeof(key1)); Zero(key2, sizeof(key2)); Zero(key3, sizeof(key3)); Copy(key1, password_hash_2 + 0, 7); Copy(key2, password_hash_2 + 7, 7); Copy(key3, password_hash_2 + 14, 7); DesEcbEncrypt(dst + 0, challenge8, key1); DesEcbEncrypt(dst + 8, challenge8, key2); DesEcbEncrypt(dst + 16, challenge8, key3); } // Generate a hash of the hash of the NT password void GenerateNtPasswordHashHash(UCHAR *dst_hash, UCHAR *src_hash) { // Validate arguments if (dst_hash == NULL || src_hash == NULL) { return; } HashMd4(dst_hash, src_hash, 16); } // Generate the MS-CHAPv2 server response void MsChapV2Server_GenerateResponse(UCHAR *dst, UCHAR *nt_password_hash_hash, UCHAR *client_response, UCHAR *challenge8) { UCHAR digest[SHA1_SIZE]; BUF *b; char *magic1 = "Magic server to client signing constant"; char *magic2 = "Pad to make it do more than one iteration"; // Validate arguments if (dst == NULL || nt_password_hash_hash == NULL || client_response == NULL || challenge8 == NULL) { return; } b = NewBuf(); WriteBuf(b, nt_password_hash_hash, 16); WriteBuf(b, client_response, 24); WriteBuf(b, magic1, StrLen(magic1)); Sha1(digest, b->Buf, b->Size); FreeBuf(b); b = NewBuf(); WriteBuf(b, digest, sizeof(digest)); WriteBuf(b, challenge8, 8); WriteBuf(b, magic2, StrLen(magic2)); Sha1(dst, b->Buf, b->Size); FreeBuf(b); } // Verify whether the password matches one that is specified by the user in the MS-CHAPv2 bool MsChapV2VerityPassword(IPC_MSCHAP_V2_AUTHINFO *d, char *password) { UCHAR ntlm_hash[MD5_SIZE]; UCHAR challenge8[8]; UCHAR client_response[24]; // Validate arguments if (d == NULL || password == NULL) { return false; } GenerateNtPasswordHash(ntlm_hash, password); MsChapV2_GenerateChallenge8(challenge8, d->MsChapV2_ClientChallenge, d->MsChapV2_ServerChallenge, d->MsChapV2_PPPUsername); MsChapV2Client_GenerateResponse(client_response, challenge8, ntlm_hash); if (Cmp(client_response, d->MsChapV2_ClientResponse, 24) != 0) { return false; } return true; } // Estimate the password in the brute force for the request packet of MS-CHAPv2 char *MsChapV2DoBruteForce(IPC_MSCHAP_V2_AUTHINFO *d, LIST *password_list) { UINT i; // Validate arguments if (d == NULL || password_list == NULL) { return NULL; } for (i = 0;i < LIST_NUM(password_list);i++) { char *s = LIST_DATA(password_list, i); char tmp[MAX_SIZE]; UINT j, max; UINT len; StrCpy(tmp, sizeof(tmp), s); len = StrLen(tmp); max = Power(2, MIN(len, 9)); for (j = 0;j < max;j++) { SetStrCaseAccordingToBits(tmp, j); if (MsChapV2VerityPassword(d, tmp)) { return CopyStr(tmp); } } } return NULL; }