474 lines
13 KiB
C
474 lines
13 KiB
C
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <string.h>
|
||
|
#include <errno.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <sys/poll.h>
|
||
|
#include <sys/wait.h>
|
||
|
#include <netdb.h>
|
||
|
#include <signal.h>
|
||
|
#include <unistd.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <time.h>
|
||
|
#include <limits.h>
|
||
|
|
||
|
#ifdef ANDROID_CHANGES
|
||
|
#include <android/log.h>
|
||
|
#include <cutils/sockets.h>
|
||
|
#endif
|
||
|
|
||
|
#include "mtpd.h"
|
||
|
#include "NetdClient.h"
|
||
|
|
||
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||
|
/* Characters count in string with max value of unsigned type t */
|
||
|
#define TYPE_STRLEN_U(t) ((((sizeof(t) * CHAR_BIT) * 1233) >> 12) + 1)
|
||
|
/* Length of string with max file descriptor value */
|
||
|
#define FD_MAX_LEN TYPE_STRLEN_U(int)
|
||
|
|
||
|
int the_socket = -1;
|
||
|
|
||
|
extern struct protocol l2tp;
|
||
|
extern struct protocol pptp;
|
||
|
static struct protocol *protocols[] = {&l2tp, &pptp, NULL};
|
||
|
static struct protocol *the_protocol;
|
||
|
|
||
|
static char *interface;
|
||
|
static int pppd_argc;
|
||
|
static char **pppd_argv;
|
||
|
static pid_t pppd_pid;
|
||
|
|
||
|
/* We redirect signals to a pipe in order to prevent race conditions. */
|
||
|
static int signals[2];
|
||
|
|
||
|
static void interrupt(int signal)
|
||
|
{
|
||
|
write(signals[1], &signal, sizeof(int));
|
||
|
}
|
||
|
|
||
|
static int initialize(int argc, char **argv)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; protocols[i]; ++i) {
|
||
|
struct protocol *p = protocols[i];
|
||
|
if (argc - 3 >= p->arguments && !strcmp(argv[2], p->name)) {
|
||
|
log_print(INFO, "Using protocol %s", p->name);
|
||
|
the_protocol = p;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!the_protocol) {
|
||
|
printf("Usages:\n");
|
||
|
for (i = 0; protocols[i]; ++i) {
|
||
|
struct protocol *p = protocols[i];
|
||
|
printf(" %s interface %s %s pppd-arguments\n",
|
||
|
argv[0], p->name, p->usage);
|
||
|
}
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
interface = argv[1];
|
||
|
pppd_argc = argc - 3 - the_protocol->arguments;
|
||
|
pppd_argv = &argv[3 + the_protocol->arguments];
|
||
|
return the_protocol->connect(&argv[3]);
|
||
|
}
|
||
|
|
||
|
static void stop_pppd()
|
||
|
{
|
||
|
if (pppd_pid) {
|
||
|
int status;
|
||
|
log_print(INFO, "Sending signal to pppd (pid = %d)", pppd_pid);
|
||
|
kill(pppd_pid, SIGTERM);
|
||
|
waitpid(pppd_pid, &status, 0);
|
||
|
pppd_pid = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef ANDROID_CHANGES
|
||
|
|
||
|
static int android_get_control_and_arguments(int *argc, char ***argv)
|
||
|
{
|
||
|
static char *args[32];
|
||
|
int control;
|
||
|
int i;
|
||
|
|
||
|
if ((i = android_get_control_socket("mtpd")) == -1) {
|
||
|
return -1;
|
||
|
}
|
||
|
log_print(DEBUG, "Waiting for control socket");
|
||
|
if (listen(i, 1) == -1 || (control = accept(i, NULL, 0)) == -1) {
|
||
|
log_print(FATAL, "Cannot get control socket");
|
||
|
exit(SYSTEM_ERROR);
|
||
|
}
|
||
|
close(i);
|
||
|
fcntl(control, F_SETFD, FD_CLOEXEC);
|
||
|
|
||
|
args[0] = (*argv)[0];
|
||
|
for (i = 1; i < 32; ++i) {
|
||
|
unsigned char bytes[2];
|
||
|
if (recv(control, &bytes[0], 1, 0) != 1 ||
|
||
|
recv(control, &bytes[1], 1, 0) != 1) {
|
||
|
log_print(FATAL, "Cannot get argument length");
|
||
|
exit(SYSTEM_ERROR);
|
||
|
} else {
|
||
|
int length = bytes[0] << 8 | bytes[1];
|
||
|
int offset = 0;
|
||
|
|
||
|
if (length == 0xFFFF) {
|
||
|
break;
|
||
|
}
|
||
|
args[i] = malloc(length + 1);
|
||
|
while (offset < length) {
|
||
|
int n = recv(control, &args[i][offset], length - offset, 0);
|
||
|
if (n > 0) {
|
||
|
offset += n;
|
||
|
} else {
|
||
|
log_print(FATAL, "Cannot get argument value");
|
||
|
exit(SYSTEM_ERROR);
|
||
|
}
|
||
|
}
|
||
|
args[i][length] = 0;
|
||
|
}
|
||
|
}
|
||
|
log_print(DEBUG, "Received %d arguments", i - 1);
|
||
|
|
||
|
*argc = i;
|
||
|
*argv = args;
|
||
|
return control;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
int main(int argc, char **argv)
|
||
|
{
|
||
|
struct pollfd pollfds[3];
|
||
|
int control = -1;
|
||
|
int timeout;
|
||
|
int status;
|
||
|
|
||
|
#ifdef ANDROID_CHANGES
|
||
|
control = android_get_control_and_arguments(&argc, &argv);
|
||
|
shutdown(control, SHUT_WR);
|
||
|
#endif
|
||
|
|
||
|
srandom(time(NULL));
|
||
|
|
||
|
if (pipe(signals) == -1) {
|
||
|
log_print(FATAL, "Pipe() %s", strerror(errno));
|
||
|
exit(SYSTEM_ERROR);
|
||
|
}
|
||
|
fcntl(signals[0], F_SETFD, FD_CLOEXEC);
|
||
|
fcntl(signals[1], F_SETFD, FD_CLOEXEC);
|
||
|
|
||
|
timeout = initialize(argc, argv);
|
||
|
|
||
|
signal(SIGHUP, interrupt);
|
||
|
signal(SIGINT, interrupt);
|
||
|
signal(SIGTERM, interrupt);
|
||
|
signal(SIGCHLD, interrupt);
|
||
|
signal(SIGPIPE, SIG_IGN);
|
||
|
atexit(stop_pppd);
|
||
|
|
||
|
pollfds[0].fd = the_socket;
|
||
|
pollfds[0].events = POLLIN;
|
||
|
pollfds[1].fd = signals[0];
|
||
|
pollfds[1].events = POLLIN;
|
||
|
pollfds[2].fd = control;
|
||
|
pollfds[2].events = 0;
|
||
|
|
||
|
while (timeout >= 0) {
|
||
|
if (poll(pollfds, 3, timeout ? timeout : -1) == -1 && errno != EINTR) {
|
||
|
log_print(FATAL, "Poll() %s", strerror(errno));
|
||
|
exit(SYSTEM_ERROR);
|
||
|
}
|
||
|
if (pollfds[1].revents) {
|
||
|
break;
|
||
|
}
|
||
|
if (pollfds[2].revents) {
|
||
|
interrupt(SIGTERM);
|
||
|
}
|
||
|
timeout = pollfds[0].revents ?
|
||
|
the_protocol->process() : the_protocol->timeout();
|
||
|
#ifdef ANDROID_CHANGES
|
||
|
if (!access("/data/misc/vpn/abort", F_OK)) {
|
||
|
interrupt(SIGTERM);
|
||
|
}
|
||
|
if (!timeout) {
|
||
|
timeout = 5000;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (timeout < 0) {
|
||
|
status = -timeout;
|
||
|
} else {
|
||
|
int signal;
|
||
|
read(signals[0], &signal, sizeof(int));
|
||
|
log_print(INFO, "Received signal %d", signal);
|
||
|
if (signal == SIGCHLD && waitpid(pppd_pid, &status, WNOHANG) == pppd_pid
|
||
|
&& WIFEXITED(status)) {
|
||
|
status = WEXITSTATUS(status);
|
||
|
log_print(INFO, "Pppd is terminated (status = %d)", status);
|
||
|
status += PPPD_EXITED;
|
||
|
pppd_pid = 0;
|
||
|
} else {
|
||
|
status = USER_REQUESTED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
stop_pppd();
|
||
|
the_protocol->shutdown();
|
||
|
log_print(INFO, "Mtpd is terminated (status = %d)", status);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
void log_print(int level, char *format, ...)
|
||
|
{
|
||
|
if (level >= 0 && level <= LOG_MAX) {
|
||
|
#ifdef ANDROID_CHANGES
|
||
|
static int levels[5] = {
|
||
|
ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN,
|
||
|
ANDROID_LOG_ERROR, ANDROID_LOG_FATAL
|
||
|
};
|
||
|
va_list ap;
|
||
|
va_start(ap, format);
|
||
|
__android_log_vprint(levels[level], "mtpd", format, ap);
|
||
|
va_end(ap);
|
||
|
#else
|
||
|
static char *levels = "DIWEF";
|
||
|
va_list ap;
|
||
|
fprintf(stderr, "%c: ", levels[level]);
|
||
|
va_start(ap, format);
|
||
|
vfprintf(stderr, format, ap);
|
||
|
va_end(ap);
|
||
|
fputc('\n', stderr);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void create_socket(int family, int type, char *server, char *port)
|
||
|
{
|
||
|
struct addrinfo hints = {
|
||
|
.ai_flags = AI_NUMERICSERV,
|
||
|
.ai_family = family,
|
||
|
.ai_socktype = type,
|
||
|
};
|
||
|
struct addrinfo *records;
|
||
|
struct addrinfo *r;
|
||
|
int error;
|
||
|
|
||
|
log_print(INFO, "Connecting to %s port %s via %s", server, port, interface);
|
||
|
|
||
|
error = getaddrinfo(server, port, &hints, &records);
|
||
|
if (error) {
|
||
|
log_print(FATAL, "Getaddrinfo() %s", (error == EAI_SYSTEM) ?
|
||
|
strerror(errno) : gai_strerror(error));
|
||
|
exit(NETWORK_ERROR);
|
||
|
}
|
||
|
|
||
|
for (r = records; r; r = r->ai_next) {
|
||
|
int s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
|
||
|
if (!setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, interface,
|
||
|
strlen(interface)) && !connect(s, r->ai_addr, r->ai_addrlen)) {
|
||
|
the_socket = s;
|
||
|
break;
|
||
|
}
|
||
|
close(s);
|
||
|
}
|
||
|
|
||
|
freeaddrinfo(records);
|
||
|
|
||
|
if (the_socket == -1) {
|
||
|
log_print(FATAL, "Connect() %s", strerror(errno));
|
||
|
exit(NETWORK_ERROR);
|
||
|
}
|
||
|
|
||
|
#ifdef ANDROID_CHANGES
|
||
|
protectFromVpn(the_socket);
|
||
|
#endif
|
||
|
|
||
|
fcntl(the_socket, F_SETFD, FD_CLOEXEC);
|
||
|
log_print(INFO, "Connection established (socket = %d)", the_socket);
|
||
|
}
|
||
|
|
||
|
void start_pppd(int pppox)
|
||
|
{
|
||
|
if (pppd_pid) {
|
||
|
log_print(WARNING, "Pppd is already started (pid = %d)", pppd_pid);
|
||
|
close(pppox);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
log_print(INFO, "Starting pppd (pppox = %d)", pppox);
|
||
|
|
||
|
pppd_pid = fork();
|
||
|
if (pppd_pid < 0) {
|
||
|
log_print(FATAL, "Fork() %s", strerror(errno));
|
||
|
exit(SYSTEM_ERROR);
|
||
|
}
|
||
|
|
||
|
if (!pppd_pid) {
|
||
|
char *args[pppd_argc + 5];
|
||
|
char number[FD_MAX_LEN + 1];
|
||
|
|
||
|
snprintf(number, FD_MAX_LEN + 1, "%d", pppox);
|
||
|
args[0] = "pppd";
|
||
|
args[1] = "nodetach";
|
||
|
args[2] = "pppox";
|
||
|
args[3] = number;
|
||
|
memcpy(&args[4], pppd_argv, sizeof(char *) * pppd_argc);
|
||
|
args[4 + pppd_argc] = 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);
|
||
|
close(pppox);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Start pppd daemon with pppol2tp-android plugin.
|
||
|
*
|
||
|
* @param tunnel_fd Tunnel socket file descriptor
|
||
|
* @param session_fd Session socket file descriptor
|
||
|
* @param tunnel_id Tunnel ID; must be in host byte order
|
||
|
* @param session_id Session ID; must be in host byte order
|
||
|
*/
|
||
|
void start_pppd_ol2tp(int tunnel_fd, int session_fd, int tunnel_id,
|
||
|
int session_id)
|
||
|
{
|
||
|
if (pppd_pid) {
|
||
|
log_print(WARNING, "Pppd is already started (pid = %d)", pppd_pid);
|
||
|
goto ret;
|
||
|
}
|
||
|
|
||
|
log_print(INFO, "Starting pppd (tunnel_fd = %d, session_fd = %d)",
|
||
|
tunnel_fd, session_fd);
|
||
|
|
||
|
pppd_pid = fork();
|
||
|
if (pppd_pid < 0) {
|
||
|
log_print(FATAL, "Fork() %s", strerror(errno));
|
||
|
exit(SYSTEM_ERROR);
|
||
|
}
|
||
|
|
||
|
if (!pppd_pid) {
|
||
|
char tunnel_fd_str[FD_MAX_LEN + 1];
|
||
|
char session_fd_str[FD_MAX_LEN + 1];
|
||
|
char tunnel_id_str[FD_MAX_LEN + 1];
|
||
|
char session_id_str[FD_MAX_LEN + 1];
|
||
|
|
||
|
snprintf(tunnel_fd_str, FD_MAX_LEN + 1, "%d", tunnel_fd);
|
||
|
snprintf(session_fd_str, FD_MAX_LEN + 1, "%d", session_fd);
|
||
|
snprintf(tunnel_id_str, FD_MAX_LEN + 1, "%d", tunnel_id);
|
||
|
snprintf(session_id_str, FD_MAX_LEN + 1, "%d", session_id);
|
||
|
|
||
|
const char *l2tp_args[] = {
|
||
|
"pppd",
|
||
|
"nodetach",
|
||
|
"plugin",
|
||
|
"pppol2tp-android.so",
|
||
|
"session_fd",
|
||
|
session_fd_str,
|
||
|
"tunnel_fd",
|
||
|
tunnel_fd_str,
|
||
|
"session_id",
|
||
|
session_id_str,
|
||
|
"tunnel_id",
|
||
|
tunnel_id_str,
|
||
|
};
|
||
|
const size_t args_len = ARRAY_SIZE(l2tp_args) + pppd_argc + 1;
|
||
|
char *args[args_len];
|
||
|
|
||
|
/* Populate args[] from l2tp_args[] and pppd_argv[] */
|
||
|
memcpy(args, l2tp_args, sizeof(l2tp_args));
|
||
|
memcpy(args + ARRAY_SIZE(l2tp_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(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);
|
||
|
}
|