diff options
-rw-r--r-- | mtpd.c | 53 | ||||
-rw-r--r-- | mtpd.h | 1 | ||||
-rw-r--r-- | pptp.c | 206 |
3 files changed, 245 insertions, 15 deletions
@@ -418,3 +418,56 @@ ret: close(session_fd); close(tunnel_fd); } + +/** + * Start pppd daemon with pppopptp-android plugin. + * + * @param pptp_fd PPTP socket file descriptor + */ +void start_pppd_pptp(int pptp_fd) +{ + if (pppd_pid) { + log_print(WARNING, "Pppd is already started (pid = %d)", pppd_pid); + goto ret; + } + + log_print(INFO, "Starting pppd (pptp_fd = %d)", pptp_fd); + + pppd_pid = fork(); + if (pppd_pid < 0) { + log_print(FATAL, "Fork() %s", strerror(errno)); + exit(SYSTEM_ERROR); + } + + if (!pppd_pid) { + char pptp_fd_str[FD_MAX_LEN + 1]; + + snprintf(pptp_fd_str, FD_MAX_LEN + 1, "%d", pptp_fd); + + const char *pptp_args[] = { + "pppd", + "nodetach", + "plugin", + "pppopptp-android.so", + "pptp_socket", + pptp_fd_str, + }; + const size_t args_len = ARRAY_SIZE(pptp_args) + pppd_argc + 1; + char *args[args_len]; + + /* Populate args[] from pptp_args[] and pppd_argv[] */ + memcpy(args, pptp_args, sizeof(pptp_args)); + memcpy(args + ARRAY_SIZE(pptp_args), pppd_argv, + sizeof(char *) * pppd_argc); + args[args_len - 1] = NULL; + + execvp("pppd", args); + log_print(FATAL, "Exec() %s", strerror(errno)); + exit(SYSTEM_ERROR); /* Pretending a fatal error in pppd. */ + } + + log_print(INFO, "Pppd started (pid = %d)", pppd_pid); + +ret: + close(pptp_fd); +} @@ -72,6 +72,7 @@ void create_socket(int family, int type, char *server, char *port); void start_pppd(int pppox); void start_pppd_ol2tp(int tunnel_fd, int session_fd, int tunnel_id, int session_id); +void start_pppd_pptp(int pptp_fd); /* Each protocol must implement everything defined in this structure. Note that * timeout intervals are in milliseconds, where zero means forever. To indicate @@ -19,15 +19,19 @@ * Data packets are handled by PPPoPNS driver which can be found in Android * kernel tree. */ +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> +#include <netdb.h> +#include <unistd.h> #include <arpa/inet.h> #include <linux/netdevice.h> #include <linux/if_pppox.h> +#include <linux/types.h> #include "mtpd.h" @@ -72,9 +76,10 @@ static uint8_t lengths[] = { #define HEADER_SIZE 8 #define MIN_MESSAGE_SIZE 10 -static uint16_t local; -static uint16_t remote; +static __be16 local; +static __be16 remote; static uint16_t state; +static const char *remote_name; /* server host name or IP address */ #define MAX_PACKET_LENGTH 220 @@ -207,14 +212,16 @@ static int recv_packet() if (incoming.header.type == CONTROL_MESSAGE) { return 1; } - log_print(DEBUG, "Ignored non-control message (type = %d)", - ntohs(incoming.header.type)); + log_print(DEBUG, "Ignored non-control message (type = %u)", + (unsigned)ntohs(incoming.header.type)); } return 0; } static int pptp_connect(char **arguments) { + remote_name = arguments[0]; + create_socket(AF_UNSPEC, SOCK_STREAM, arguments[0], arguments[1]); log_print(DEBUG, "Sending SCCRQ"); @@ -229,10 +236,40 @@ static int pptp_connect(char **arguments) return 0; } -static int create_pppox() +/** + * Check if upstream kernel implementation is enabled. + * + * @return true if upstream PPTP is enabled in kernel and false otherwise + */ +static bool check_pptp(void) +{ + int fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_PPTP); + + if (fd < 0) { + return false; + } else { + close(fd); + return true; + } +} + +/** + * Create OPNS session. + * + * @deprecated It will be removed soon in favor of upstream PPTP. + * + * @return PPPoX socket file descriptor + */ +static int create_pppox_opns(void) { - int pppox = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OPNS); + int pppox; + + log_print(WARNING, "Using deprecated OPNS protocol. " + "Its support will be removed soon. " + "Please enable PPTP support in your kernel"); + log_print(INFO, "Creating PPPoX socket"); + pppox = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OPNS); if (pppox == -1) { log_print(FATAL, "Socket() %s", strerror(errno)); @@ -253,6 +290,135 @@ static int create_pppox() return pppox; } +/** + * Get IP address by host name. + * + * @param name Host name to get IP address for + * + * @return IP address for given host name + */ +static struct in_addr get_addr_by_name(const char *name) +{ + struct addrinfo hints; + struct addrinfo *res, *rp; + struct in_addr addr; + int err; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; /* allow only IPv4 */ + hints.ai_socktype = SOCK_DGRAM; /* UDP */ + hints.ai_protocol = 0; /* any protocol */ + + err = getaddrinfo(name, NULL, &hints, &res); + if (err) { + log_print(FATAL, "%s: getaddrinfo: %s", __func__, gai_strerror(err)); + exit(SYSTEM_ERROR); + } + + for (rp = res; rp != NULL; rp = rp->ai_next) { + /* For now we only support IPv4 */ + if (rp->ai_family == AF_INET) { + addr = ((struct sockaddr_in *)rp->ai_addr)->sin_addr; + break; + } + } + + if (rp == NULL) { + log_print(FATAL, "%s: No IPv4 addresses found", __func__); + freeaddrinfo(res); + exit(SYSTEM_ERROR); + } + + freeaddrinfo(res); + + return addr; +} + +/** + * Get local IP address. + * + * Make a socket connection with remote server and then call getsockname() on + * the connected socket. This will return the local IP address. + * + * @param remote_addr Server IP address + * + * @return Local IP address + */ +static struct in_addr get_local_addr(struct in_addr remote_addr) +{ + int sock; + struct sockaddr_in addr; + socklen_t addr_len; + + addr_len = sizeof(struct sockaddr_in); + addr.sin_addr = remote_addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(0); + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + log_print(FATAL, "%s: Socket() %s", __func__, strerror(errno)); + exit(SYSTEM_ERROR); + } + + if (connect(sock, (struct sockaddr*)&addr, sizeof(addr))) { + close(sock); + log_print(FATAL, "%s: Connect() %s", __func__, strerror(errno)); + exit(SYSTEM_ERROR); + } + + getsockname(sock, (struct sockaddr*)&addr, &addr_len); + close(sock); + + return addr.sin_addr; +} + +/** + * Create PPTP session. + * + * @return PPTP socket file descriptor + */ +static int create_pppox_pptp(void) +{ + int pptp_fd; + struct sockaddr_pppox src, dst; + struct in_addr remote_addr; /* server IP address */ + struct in_addr local_addr; /* client IP address */ + + remote_addr = get_addr_by_name(remote_name); + local_addr = get_local_addr(remote_addr); + + src.sa_family = AF_PPPOX; + src.sa_protocol = PX_PROTO_PPTP; + src.sa_addr.pptp.call_id = ntohs(local); + src.sa_addr.pptp.sin_addr = local_addr; + + dst.sa_family = AF_PPPOX; + dst.sa_protocol = PX_PROTO_PPTP; + dst.sa_addr.pptp.call_id = ntohs(remote); + dst.sa_addr.pptp.sin_addr = remote_addr; + + pptp_fd = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_PPTP); + if (pptp_fd < 0) { + log_print(FATAL, "Failed to create PPTP socket (%s)", strerror(errno)); + exit(SYSTEM_ERROR); + } + + if (bind(pptp_fd, (struct sockaddr*)&src, sizeof(src))) { + log_print(FATAL, "Failed to bind PPTP socket (%s)", strerror(errno)); + close(pptp_fd); + exit(SYSTEM_ERROR); + } + + if (connect(pptp_fd, (struct sockaddr*)&dst, sizeof(dst))) { + log_print(FATAL, "Failed to connect PPTP socket (%s)", strerror(errno)); + close(pptp_fd); + exit(SYSTEM_ERROR); + } + + return pptp_fd; +} + static int pptp_process() { int result = recv_packet(); @@ -284,7 +450,7 @@ static int pptp_process() local = random(); } log_print(DEBUG, "Received SCCRP -> Sending OCRQ " - "(local = %d)", local); + "(local = %u)", (unsigned)ntohs(local)); log_print(INFO, "Tunnel established"); state = OCRQ; set_message(OCRQ); @@ -309,10 +475,16 @@ static int pptp_process() if (state == OCRQ && incoming.ocrp.peer == local) { if (ESTABLISHED(incoming.ocrp.result)) { remote = incoming.ocrp.call; - log_print(DEBUG, "Received OCRQ (remote = %d)", remote); + log_print(DEBUG, "Received OCRQ (remote = %u)", + (unsigned)ntohs(remote)); log_print(INFO, "Session established"); state = OCRP; - start_pppd(create_pppox()); + + if (check_pptp()) + start_pppd_pptp(create_pppox_pptp()); + else + start_pppd(create_pppox_opns()); + return 0; } log_print(DEBUG, "Received OCRP (result = %d)", @@ -333,7 +505,8 @@ static int pptp_process() * outgoing calls. However, some implementation only acts as PNS and * always uses CCRQ to clear a call, so here we still handle it. */ if (state == OCRP && incoming.ccrq.call == remote) { - log_print(DEBUG, "Received CCRQ (remote = %d)", remote); + log_print(DEBUG, "Received CCRQ (remote = %u)", + (unsigned)ntohs(remote)); log_print(INFO, "Remote server hung up"); return -REMOTE_REQUESTED; } @@ -341,7 +514,8 @@ static int pptp_process() case CDN: if (state == OCRP && incoming.cdn.call == remote) { - log_print(DEBUG, "Received CDN (remote = %d)", remote); + log_print(DEBUG, "Received CDN (remote = %u)", + (unsigned)ntohs(remote)); log_print(INFO, "Remote server hung up"); return -REMOTE_REQUESTED; } @@ -361,8 +535,9 @@ static int pptp_process() return 0; case ICRQ: - log_print(DEBUG, "Received ICRQ (remote = %d) -> Sending ICRP " - "with error", incoming.icrq.call); + log_print(DEBUG, "Received ICRQ (remote = %u, call = %u) -> " + "Sending ICRP with error", (unsigned)ntohs(remote), + (unsigned)ntohs(incoming.icrq.call)); set_message(ICRP); outgoing.icrp.peer = incoming.icrq.call; outgoing.icrp.result = RESULT_ERROR; @@ -370,8 +545,9 @@ static int pptp_process() return 0; case OCRQ: - log_print(DEBUG, "Received OCRQ (remote = %d) -> Sending OCRP " - "with error", incoming.ocrq.call); + log_print(DEBUG, "Received OCRQ (remote = %u, call = %u) -> " + "Sending OCRP with error", (unsigned)ntohs(remote), + (unsigned)ntohs(incoming.ocrq.call)); set_message(OCRP); outgoing.ocrp.peer = incoming.ocrq.call; outgoing.ocrp.result = RESULT_ERROR; |