Commits

Anonymous committed 063bb92

Add PPTP support in mtpd.

If control socket does not exist, parse arguments from command line.

Comments (0)

Files changed (3)

 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := mtpd.c l2tp.c
+LOCAL_SRC_FILES := mtpd.c l2tp.c pptp.c
 LOCAL_SHARED_LIBRARIES := libcutils
 LOCAL_CFLAGS := -DANDROID_CHANGES
 
 int the_socket = -1;
 
 extern struct protocol l2tp;
-static struct protocol *protocols[] = {&l2tp, NULL};
+extern struct protocol pptp;
+static struct protocol *protocols[] = {&l2tp, &pptp, NULL};
 static struct protocol *the_protocol;
 
 static int pppd_argc;
     int control;
     int i;
 
+    if ((i = android_get_control_socket("mtpd")) == -1) {
+        return -1;
+    }
     log_print(DEBUG, "Waiting for control socket");
-    i = android_get_control_socket("mtpd");
-    if (i == -1 || listen(i, 1) == -1 || (control = accept(i, NULL, 0)) == -1) {
+    if (listen(i, 1) == -1 || (control = accept(i, NULL, 0)) == -1) {
         log_print(FATAL, "Cannot get control socket");
         exit(SYSTEM_ERROR);
     }
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* A simple implementation of PPTP Network Server (RFC 2637) which only
+ * creates a single session. The following code only handles control packets.
+ * Data packets are handled by PPPoPNS driver which can be found in Android
+ * kernel tree. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <linux/if_pppopns.h>
+
+#include "mtpd.h"
+
+enum pptp_message {
+    SCCRQ = 1,
+    SCCRP = 2,
+    STOPCCRQ = 3,
+    STOPCCRP = 4,
+    ECHORQ = 5,
+    ECHORP = 6,
+    OCRQ = 7,
+    OCRP = 8,
+    ICRQ = 9,
+    ICRP = 10,
+    ICCN = 11,
+    CCRQ = 12,
+    CDN = 13,
+    WEN = 14,
+    SLI = 15,
+    MESSAGE_MAX = 15,
+};
+
+static char *messages[] = {
+    NULL, "SCCRQ", "SCCRP", "STOPCCRQ", "STOPCCRP", "ECHORQ", "ECHORP",
+    "OCRQ", "OCRP", "ICRQ", "ICRP", "ICCN", "CCRQ", "CDN", "WEN", "SLI",
+};
+
+static uint8_t lengths[] = {
+    0, 156, 156, 16, 16, 16, 20, 168, 32, 220, 24, 28, 16, 148, 40, 24,
+};
+
+#define CONTROL_MESSAGE         htons(1)
+#define MAGIC_COOKIE            htonl(0x1A2B3C4D)
+#define PROTOCOL_VERSION        htons(0x0100)
+
+#define RESULT_OK               1
+#define RESULT_ERROR            2
+
+/* Some implementation uses 0 instead of 1, so we allow both of them. */
+#define ESTABLISHED(result)     (result <= 1)
+
+#define HEADER_SIZE             8
+#define MIN_MESSAGE_SIZE        10
+
+static uint16_t local;
+static uint16_t remote;
+static uint16_t state;
+
+#define MAX_PACKET_LENGTH       220
+
+/* We define all the fields we used in this structure. Type conversion and byte
+ * alignment are solved in one place. Although it looks a little bit ugly, it
+ * really makes life easier. */
+static struct packet {
+    int length;
+    int expect;
+    union {
+        uint8_t buffer[MAX_PACKET_LENGTH];
+        struct {
+            struct __attribute__((packed)) {
+                uint16_t length;
+                uint16_t type;
+                uint32_t cookie;
+            } header;
+            uint16_t message;
+            uint16_t reserved;
+            union {
+                struct __attribute__((packed)) {
+                    uint16_t protocol_version;
+                    uint8_t result;
+                    uint8_t error;
+                    uint32_t framing;
+                    uint32_t bearer;
+                    uint16_t channels;
+                    uint16_t firmware_revision;
+                    char host[64];
+                } sccrp, sccrq;
+                struct __attribute__((packed)) {
+                    uint16_t call;
+                    uint16_t serial;
+                    uint32_t minimum_speed;
+                    uint32_t maximum_speed;
+                    uint32_t bearer;
+                    uint32_t framing;
+                    uint16_t window_size;
+                } ocrq;
+                struct __attribute__((packed)) {
+                    uint16_t call;
+                    uint16_t peer;
+                    uint8_t result;
+                } ocrp, icrp;
+                struct __attribute__((packed)) {
+                    uint32_t identifier;
+                    uint8_t result;
+                } echorq, echorp;
+                struct __attribute__((packed)) {
+                    uint16_t call;
+                } icrq, ccrq, cdn;
+            };
+        } __attribute__((packed));
+    } __attribute__((aligned(4)));
+} incoming, outgoing;
+
+static void set_message(uint16_t message)
+{
+    uint16_t length = lengths[message];
+    memset(outgoing.buffer, 0, length);
+    outgoing.length = length;
+    outgoing.header.length = htons(length);
+    outgoing.header.type = CONTROL_MESSAGE;
+    outgoing.header.cookie = MAGIC_COOKIE;
+    outgoing.message = htons(message);
+}
+
+static void send_packet()
+{
+    send(the_socket, outgoing.buffer, outgoing.length, 0);
+}
+
+static int recv_packet()
+{
+    int length;
+
+    /* We are going to read a new message if incoming.expect is 0. */
+    if (!incoming.expect) {
+        incoming.length = 0;
+        incoming.expect = HEADER_SIZE;
+    }
+
+    /* The longest message defined in RFC 2637 is 220 bytes, but the protocol
+     * itself allows up to 65536 bytes. Therefore we always read a complete
+     * message but only keep the first 220 bytes before passing up. */
+    length = incoming.expect - incoming.length;
+    if (incoming.length >= MAX_PACKET_LENGTH) {
+        uint8_t buffer[length];
+        length = recv(the_socket, buffer, length, 0);
+    } else {
+        if (incoming.expect > MAX_PACKET_LENGTH) {
+            length = MAX_PACKET_LENGTH - incoming.length;
+        }
+        length = recv(the_socket, &incoming.buffer[incoming.length], length, 0);
+    }
+    if (length == -1) {
+        if (errno == EINTR) {
+            return 0;
+        }
+        log_print(FATAL, "Recv() %s", strerror(errno));
+        exit(NETWORK_ERROR);
+    }
+    if (length == 0) {
+        log_print(DEBUG, "Connection closed");
+        log_print(INFO, "Remote server hung up");
+        return -REMOTE_REQUESTED;
+    }
+    incoming.length += length;
+
+    /* If incoming.header is valid, check cookie and update incoming.expect. */
+    if (incoming.length == HEADER_SIZE && incoming.expect == HEADER_SIZE) {
+        if (incoming.header.cookie != MAGIC_COOKIE) {
+            log_print(DEBUG, "Loss of synchronization");
+            log_print(ERROR, "Protocol error");
+            return -PROTOCOL_ERROR;
+        }
+        incoming.expect = ntohs(incoming.header.length);
+        if (incoming.expect < HEADER_SIZE) {
+            log_print(DEBUG, "Invalid message length");
+            log_print(ERROR, "Protocol error");
+            return -PROTOCOL_ERROR;
+        }
+    }
+
+    /* Now we have a complete message. Reset incoming.expect. */
+    if (incoming.length == incoming.expect) {
+        incoming.expect = 0;
+
+        /* Return 1 if it is a control message. */
+        if (incoming.header.type == CONTROL_MESSAGE) {
+            return 1;
+        }
+        log_print(DEBUG, "Ignored non-control message (type = %d)",
+                  ntohs(incoming.header.type));
+    }
+    return 0;
+}
+
+static int pptp_connect(int argc, char **argv)
+{
+    if (argc < 2) {
+        return -USAGE_ERROR;
+    }
+    create_socket(AF_UNSPEC, SOCK_STREAM, argv[0], argv[1]);
+
+    log_print(DEBUG, "Sending SCCRQ");
+    state = SCCRQ;
+    set_message(SCCRQ);
+    outgoing.sccrq.protocol_version = PROTOCOL_VERSION;
+    outgoing.sccrq.framing = htonl(3);
+    outgoing.sccrq.bearer = htonl(3);
+    outgoing.sccrq.channels = htons(1);
+    strcpy(outgoing.sccrq.host, "anonymous");
+    send_packet();
+    return 0;
+}
+
+static int create_pppox()
+{
+    int pppox;
+    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));
+        exit(SYSTEM_ERROR);
+    } else {
+        struct sockaddr_pppopns address = {
+            .sa_family = AF_PPPOX,
+            .sa_protocol = PX_PROTO_OPNS,
+            .tcp_socket = the_socket,
+            .local = local,
+            .remote = remote,
+        };
+        if (connect(pppox, (struct sockaddr *)&address, sizeof(address)) != 0) {
+            log_print(FATAL, "Connect() %s", strerror(errno));
+            exit(SYSTEM_ERROR);
+        }
+    }
+    return pppox;
+}
+
+static int pptp_process()
+{
+    int result = recv_packet();
+    if (result <= 0) {
+        return result;
+    }
+
+    if (incoming.length < MIN_MESSAGE_SIZE) {
+        log_print(DEBUG, "Control message too short");
+        return 0;
+    }
+    incoming.message = ntohs(incoming.message);
+    if (incoming.message > MESSAGE_MAX || !messages[incoming.message]) {
+        log_print(DEBUG, "Received UNKNOWN %d", incoming.message);
+        return 0;
+    }
+    if (incoming.length < lengths[incoming.message]) {
+        log_print(DEBUG, "Received %s with invalid length (length = %d)",
+                  messages[incoming.message], incoming.length);
+        return 0;
+    }
+
+    switch(incoming.message) {
+        case SCCRP:
+            if (state == SCCRQ) {
+                if (incoming.sccrp.protocol_version == PROTOCOL_VERSION
+                    && ESTABLISHED(incoming.sccrp.result)) {
+                    while (!local) {
+                        local = random();
+                    }
+                    log_print(DEBUG, "Received SCCRP -> Sending OCRQ "
+                              "(local = %d)", local);
+                    log_print(INFO, "Tunnel established");
+                    state = OCRQ;
+                    set_message(OCRQ);
+                    outgoing.ocrq.call = local;
+                    outgoing.ocrq.serial = random();
+                    outgoing.ocrq.minimum_speed = htonl(1000);
+                    outgoing.ocrq.maximum_speed = htonl(100000000);
+                    outgoing.ocrq.bearer = htonl(3);
+                    outgoing.ocrq.framing = htonl(3);
+                    outgoing.ocrq.window_size = htons(8192);
+                    send_packet();
+                    return 0;
+                }
+                log_print(DEBUG, "Received SCCRP (result = %d)",
+                          incoming.sccrq.result);
+                log_print(INFO, "Remote server hung up");
+                return -REMOTE_REQUESTED;
+            }
+            break;
+
+        case OCRP:
+            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(INFO, "Session established");
+                    state = OCRP;
+                    start_pppd(create_pppox());
+                    return 0;
+                }
+                log_print(DEBUG, "Received OCRP (result = %d)",
+                          incoming.ocrp.result);
+                log_print(INFO, "Remote server hung up");
+                return -REMOTE_REQUESTED;
+            }
+            break;
+
+        case STOPCCRQ:
+            log_print(DEBUG, "Received STOPCCRQ");
+            log_print(INFO, "Remote server hung up");
+            state = STOPCCRQ;
+            return -REMOTE_REQUESTED;
+
+        case CCRQ:
+            /* According to RFC 2637 page 45, we should never receive CCRQ for
+             * 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(INFO, "Remote server hung up");
+                return -REMOTE_REQUESTED;
+            }
+            break;
+
+        case CDN:
+            if (state == OCRP && incoming.cdn.call == remote) {
+                log_print(DEBUG, "Received CDN (remote = %d)", remote);
+                log_print(INFO, "Remote server hung up");
+                return -REMOTE_REQUESTED;
+            }
+            break;
+
+        case ECHORQ:
+            log_print(DEBUG, "Received ECHORQ -> Sending ECHORP");
+            set_message(ECHORP);
+            outgoing.echorp.identifier = incoming.echorq.identifier;
+            outgoing.echorp.result = RESULT_OK;
+            send_packet();
+            return 0;
+
+        case WEN:
+        case SLI:
+            log_print(DEBUG, "Recevied %s", messages[incoming.message]);
+            return 0;
+
+        case ICRQ:
+            log_print(DEBUG, "Received ICRQ (remote = %d) -> Sending ICRP "
+                      "with error", incoming.icrq.call);
+            set_message(ICRP);
+            outgoing.icrp.peer = incoming.icrq.call;
+            outgoing.icrp.result = RESULT_ERROR;
+            send_packet();
+            return 0;
+
+        case OCRQ:
+            log_print(DEBUG, "Received OCRQ (remote = %d) -> Sending OCRP "
+                      "with error", incoming.ocrq.call);
+            set_message(OCRP);
+            outgoing.ocrp.peer = incoming.ocrq.call;
+            outgoing.ocrp.result = RESULT_ERROR;
+            send_packet();
+            return 0;
+    }
+
+    /* We reach here if we got an unexpected message. Just log it. */
+    log_print(DEBUG, "Received UNEXPECTED %s", messages[incoming.message]);
+    return 0;
+}
+
+static int pptp_timeout()
+{
+    return 0;
+}
+
+static void pptp_shutdown()
+{
+    /* Normally we should send STOPCCRQ and wait for STOPCCRP, but this might
+     * block for a long time. Here we simply take the shortcut: do nothing. */
+}
+
+struct protocol pptp = {
+    .name = "pptp",
+    .usage = "<server> <port>",
+    .connect = pptp_connect,
+    .process = pptp_process,
+    .timeout = pptp_timeout,
+    .shutdown = pptp_shutdown,
+};