Merge changes from topic "pppopns"

* changes:
  mtpd: Use PPTP implementation from upstream kernel
  mtpd: pptp: Fix endianness issues in log prints
diff --git a/mtpd.c b/mtpd.c
index 53ea362..ac724ac 100644
--- a/mtpd.c
+++ b/mtpd.c
@@ -418,3 +418,56 @@
     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);
+}
diff --git a/mtpd.h b/mtpd.h
index dad3ea6..290a583 100644
--- a/mtpd.h
+++ b/mtpd.h
@@ -72,6 +72,7 @@
 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
diff --git a/pptp.c b/pptp.c
index c748d1a..80eb24a 100644
--- a/pptp.c
+++ b/pptp.c
@@ -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 @@
 #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 @@
         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 @@
     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 pppox = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OPNS);
+    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;
+
+    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 @@
     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 @@
                         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 @@
             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 @@
              * 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 @@
 
         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 @@
             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 @@
             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;