914 lines
34 KiB
C
914 lines
34 KiB
C
/*
|
|
* emuagent : agent de pilotage de l'iPod émulé (iPhone OS 2.x), lancé par launchd en root.
|
|
*
|
|
* 1. Réseau : lance pppd sur l'UART1 (/dev/tty.bluetooth, reliée à ipod-netd côté hôte) et publie le lien dans le
|
|
* SCDynamicStore de configd (IPv4, routeur, DNS), pour qu'iOS le considère comme le réseau actif.
|
|
* 2. Pilotage : serveur TCP sur le port 7788 (redirigé par ipod-netd vers 127.0.0.1:7788 sur l'hôte).
|
|
*
|
|
* Protocole : une requête = une ligne, champs séparés par des tabulations. Réponse : "OK <n> [info]\n" ou
|
|
* "ERR <n>\n", suivi de n octets. Pour "put", n octets de contenu suivent la ligne de requête.
|
|
*/
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
/* SCDynamicStore est dans SystemConfiguration.framework mais marqué indisponible dans les en-têtes iPhone */
|
|
typedef const struct __SCDynamicStore *SCDynamicStoreRef;
|
|
extern SCDynamicStoreRef SCDynamicStoreCreate(CFAllocatorRef, CFStringRef, void *, void *);
|
|
extern Boolean SCDynamicStoreSetValue(SCDynamicStoreRef, CFStringRef, CFPropertyListRef);
|
|
extern CFPropertyListRef SCDynamicStoreCopyValue(SCDynamicStoreRef, CFStringRef);
|
|
extern CFArrayRef SCDynamicStoreCopyKeyList(SCDynamicStoreRef, CFStringRef);
|
|
#include <arpa/inet.h>
|
|
#include <dirent.h>
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <ifaddrs.h>
|
|
#include <net/if.h>
|
|
/* net/route.h absent du SDK iPhone : structures du socket de routage de XNU 1228 (Darwin 9) */
|
|
struct rt_metrics {
|
|
u_int32_t rmx_locks, rmx_mtu, rmx_hopcount;
|
|
int32_t rmx_expire;
|
|
u_int32_t rmx_recvpipe, rmx_sendpipe, rmx_ssthresh, rmx_rtt, rmx_rttvar, rmx_pksent;
|
|
u_int32_t rmx_filler[4];
|
|
};
|
|
struct rt_msghdr {
|
|
u_short rtm_msglen;
|
|
u_char rtm_version, rtm_type;
|
|
u_short rtm_index;
|
|
int rtm_flags, rtm_addrs;
|
|
pid_t rtm_pid;
|
|
int rtm_seq, rtm_errno, rtm_use;
|
|
u_int32_t rtm_inits;
|
|
struct rt_metrics rtm_rmx;
|
|
};
|
|
#define RTM_VERSION 5
|
|
#define RTM_ADD 0x1
|
|
#define RTM_CHANGE 0x3
|
|
#define NET_RT_DUMP 1
|
|
#define RTF_UP 0x1
|
|
#define RTF_GATEWAY 0x2
|
|
#define RTF_STATIC 0x800
|
|
#define RTA_DST 0x1
|
|
#define RTA_GATEWAY 0x2
|
|
#define RTA_NETMASK 0x4
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <signal.h>
|
|
#include <spawn.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/time.h>
|
|
#include <sys/wait.h>
|
|
#include <termios.h>
|
|
#include <notify.h>
|
|
#include <unistd.h>
|
|
|
|
#define VERSION "12"
|
|
#define PORT 7788
|
|
#define PPP_TTY "/dev/tty.bluetooth"
|
|
#define GUEST_IP "10.0.2.15"
|
|
#define GW_IP "10.0.2.2"
|
|
#define DNS_IP "10.0.2.3"
|
|
#define SERVICE_ID "emuppp" /* pppd publie son état sous State:/Network/Service/<id> */
|
|
|
|
extern char **environ;
|
|
static FILE *con;
|
|
static pid_t pppd_pid;
|
|
static SCDynamicStoreRef store;
|
|
static int net_published;
|
|
|
|
static void clog_(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
if (!con) return;
|
|
fputs("emuagent: ", con);
|
|
va_start(ap, fmt);
|
|
vfprintf(con, fmt, ap);
|
|
va_end(ap);
|
|
fputc('\n', con);
|
|
}
|
|
|
|
/* ---------- réseau ---------- */
|
|
|
|
static void start_pppd(void)
|
|
{
|
|
char *argv[] = { "/usr/sbin/pppd", PPP_TTY, "115200", "local", "nocrtscts", "noauth", "nodetach",
|
|
"persist", "maxfail", "0", "holdoff", "2", "defaultroute", "noipdefault",
|
|
"ipcp-accept-local", "ipcp-accept-remote", "usepeerdns", "novj", "noccp",
|
|
"lcp-echo-interval", "0", "mtu", "1500", "mru", "1500", "serviceid", SERVICE_ID, NULL };
|
|
posix_spawn_file_actions_t fa;
|
|
posix_spawn_file_actions_init(&fa);
|
|
posix_spawn_file_actions_addopen(&fa, 0, "/dev/null", O_RDONLY, 0);
|
|
posix_spawn_file_actions_addopen(&fa, 1, "/dev/console", O_WRONLY, 0);
|
|
posix_spawn_file_actions_addopen(&fa, 2, "/dev/console", O_WRONLY, 0);
|
|
if (posix_spawn(&pppd_pid, argv[0], &fa, NULL, argv, environ) != 0) {
|
|
clog_("pppd : échec du lancement (%s)", strerror(errno));
|
|
pppd_pid = 0;
|
|
} else {
|
|
clog_("pppd lancé (pid %d) sur %s", pppd_pid, PPP_TTY);
|
|
}
|
|
posix_spawn_file_actions_destroy(&fa);
|
|
}
|
|
|
|
static int ppp_up(char *ifname, size_t len)
|
|
{
|
|
struct ifaddrs *ifa, *i;
|
|
int up = 0;
|
|
if (getifaddrs(&ifa)) return 0;
|
|
for (i = ifa; i; i = i->ifa_next) {
|
|
if (!strncmp(i->ifa_name, "ppp", 3) && i->ifa_addr && i->ifa_addr->sa_family == AF_INET && (i->ifa_flags & IFF_UP)) {
|
|
strncpy(ifname, i->ifa_name, len);
|
|
up = 1;
|
|
}
|
|
}
|
|
freeifaddrs(ifa);
|
|
return up;
|
|
}
|
|
|
|
static CFArrayRef str_array(const char *s)
|
|
{
|
|
CFStringRef v = CFStringCreateWithCString(NULL, s, kCFStringEncodingUTF8);
|
|
CFArrayRef a = CFArrayCreate(NULL, (const void **)&v, 1, &kCFTypeArrayCallBacks);
|
|
CFRelease(v);
|
|
return a;
|
|
}
|
|
|
|
static void set_key(CFStringRef key, CFDictionaryRef val)
|
|
{
|
|
if (!SCDynamicStoreSetValue(store, key, val)) clog_("SCDynamicStoreSetValue a échoué");
|
|
}
|
|
|
|
/* Route par défaut via la passerelle PPP (RTM_ADD sur le socket de routage, comme route add default) */
|
|
static int route_msg(int type)
|
|
{
|
|
struct {
|
|
struct rt_msghdr h;
|
|
struct sockaddr_in dst, gw, mask;
|
|
} m;
|
|
memset(&m, 0, sizeof m);
|
|
m.h.rtm_msglen = sizeof m;
|
|
m.h.rtm_version = RTM_VERSION;
|
|
m.h.rtm_type = type;
|
|
m.h.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
|
|
m.h.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
|
|
m.h.rtm_pid = getpid();
|
|
m.h.rtm_seq = 1;
|
|
m.dst.sin_len = m.gw.sin_len = m.mask.sin_len = sizeof(struct sockaddr_in);
|
|
m.dst.sin_family = m.gw.sin_family = m.mask.sin_family = AF_INET;
|
|
inet_aton(GW_IP, &m.gw.sin_addr);
|
|
int s = socket(PF_ROUTE, SOCK_RAW, 0);
|
|
if (s < 0) return -1;
|
|
int r = write(s, &m, sizeof m) == sizeof m ? 0 : -1;
|
|
int e = errno;
|
|
close(s);
|
|
errno = e;
|
|
return r;
|
|
}
|
|
|
|
static int add_default_route(void)
|
|
{
|
|
if (route_msg(RTM_ADD) == 0) return 0;
|
|
if (errno == EEXIST && route_msg(RTM_CHANGE) == 0) return 0; /* une route par défaut existait déjà */
|
|
clog_("route par défaut : %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
static CFMutableDictionaryRef dict(void)
|
|
{
|
|
return CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
}
|
|
|
|
/*
|
|
* Déclare le service PPP côté Setup: (ce que contiendrait preferences.plist pour un service configuré) : sans cela,
|
|
* IPMonitor ignore l'état publié par pppd et n'installe ni la route par défaut ni le DNS global.
|
|
*/
|
|
static void publish_setup(void)
|
|
{
|
|
if (!store) store = SCDynamicStoreCreate(NULL, CFSTR("emuagent"), NULL, NULL);
|
|
if (!store) { clog_("SCDynamicStoreCreate a échoué"); return; }
|
|
|
|
CFMutableDictionaryRef svc = dict(), itf = dict(), ipv4 = dict(), glob = dict();
|
|
CFDictionarySetValue(svc, CFSTR("UserDefinedName"), CFSTR("Émulateur (PPP)"));
|
|
set_key(CFSTR("Setup:/Network/Service/" SERVICE_ID), svc);
|
|
CFDictionarySetValue(itf, CFSTR("Type"), CFSTR("PPP"));
|
|
CFDictionarySetValue(itf, CFSTR("SubType"), CFSTR("PPPSerial"));
|
|
CFDictionarySetValue(itf, CFSTR("DeviceName"), CFSTR("tty.bluetooth"));
|
|
set_key(CFSTR("Setup:/Network/Service/" SERVICE_ID "/Interface"), itf);
|
|
CFDictionarySetValue(ipv4, CFSTR("ConfigMethod"), CFSTR("PPP"));
|
|
set_key(CFSTR("Setup:/Network/Service/" SERVICE_ID "/IPv4"), ipv4);
|
|
CFArrayRef order = str_array(SERVICE_ID);
|
|
CFDictionarySetValue(glob, CFSTR("ServiceOrder"), order);
|
|
set_key(CFSTR("Setup:/Network/Global/IPv4"), glob);
|
|
CFRelease(order); CFRelease(svc); CFRelease(itf); CFRelease(ipv4); CFRelease(glob);
|
|
}
|
|
|
|
/* Lien monté : vérifie qu'IPMonitor a bien installé la route par défaut, sinon la pose nous-mêmes */
|
|
static void publish_network(const char *ifname)
|
|
{
|
|
net_published = 1;
|
|
sleep(1);
|
|
if (add_default_route() == 0) clog_("lien %s prêt (%s via %s, DNS %s)", ifname, GUEST_IP, GW_IP, DNS_IP);
|
|
}
|
|
|
|
static void net_tick(void)
|
|
{
|
|
int status;
|
|
char ifname[IFNAMSIZ];
|
|
if (pppd_pid && waitpid(pppd_pid, &status, WNOHANG) == pppd_pid) {
|
|
clog_("pppd terminé (statut %d), relance", status);
|
|
pppd_pid = 0;
|
|
net_published = 0;
|
|
}
|
|
if (!pppd_pid) { publish_setup(); start_pppd(); }
|
|
int up = ppp_up(ifname, sizeof ifname);
|
|
if (up && !net_published) publish_network(ifname);
|
|
if (!up) net_published = 0;
|
|
}
|
|
|
|
/* ---------- réponses ---------- */
|
|
|
|
static int send_all(int fd, const void *b, size_t n)
|
|
{
|
|
const char *p = b;
|
|
while (n) {
|
|
ssize_t w = write(fd, p, n);
|
|
if (w <= 0) { if (w < 0 && errno == EINTR) continue; if (!w) errno = EIO; return -1; }
|
|
p += w; n -= w;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void reply(int fd, int ok, const char *info, const void *data, size_t len)
|
|
{
|
|
char hdr[128];
|
|
snprintf(hdr, sizeof hdr, "%s %zu%s%s\n", ok ? "OK" : "ERR", len, info ? " " : "", info ? info : "");
|
|
send_all(fd, hdr, strlen(hdr));
|
|
if (len) send_all(fd, data, len);
|
|
}
|
|
|
|
static void reply_str(int fd, int ok, const char *fmt, ...)
|
|
{
|
|
char buf[4096];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof buf, fmt, ap);
|
|
va_end(ap);
|
|
reply(fd, ok, NULL, buf, strlen(buf));
|
|
}
|
|
|
|
/* tampon dynamique */
|
|
struct buf { char *p; size_t n, cap; };
|
|
static void bput(struct buf *b, const void *d, size_t n)
|
|
{
|
|
if (b->n + n + 1 > b->cap) {
|
|
b->cap = (b->n + n + 1) * 2;
|
|
b->p = realloc(b->p, b->cap);
|
|
}
|
|
memcpy(b->p + b->n, d, n);
|
|
b->n += n;
|
|
b->p[b->n] = 0;
|
|
}
|
|
static void bprintf(struct buf *b, const char *fmt, ...)
|
|
{
|
|
char tmp[2048];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
int n = vsnprintf(tmp, sizeof tmp, fmt, ap);
|
|
va_end(ap);
|
|
bput(b, tmp, n < (int)sizeof tmp ? n : (int)sizeof tmp - 1);
|
|
}
|
|
|
|
/* ---------- commandes ---------- */
|
|
|
|
static void cmd_exec(int fd, char **args, int nargs)
|
|
{
|
|
int pfd[2];
|
|
pid_t pid;
|
|
if (nargs < 1) { reply_str(fd, 0, "usage: exec<TAB>programme<TAB>args..."); return; }
|
|
pipe(pfd);
|
|
posix_spawn_file_actions_t fa;
|
|
posix_spawn_file_actions_init(&fa);
|
|
posix_spawn_file_actions_addopen(&fa, 0, "/dev/null", O_RDONLY, 0);
|
|
posix_spawn_file_actions_adddup2(&fa, pfd[1], 1);
|
|
posix_spawn_file_actions_adddup2(&fa, pfd[1], 2);
|
|
posix_spawn_file_actions_addclose(&fa, pfd[0]);
|
|
char *argv[64];
|
|
int i;
|
|
for (i = 0; i < nargs && i < 63; i++) argv[i] = args[i];
|
|
argv[i] = NULL;
|
|
int err = posix_spawn(&pid, argv[0], &fa, NULL, argv, environ);
|
|
posix_spawn_file_actions_destroy(&fa);
|
|
close(pfd[1]);
|
|
if (err) { close(pfd[0]); reply_str(fd, 0, "posix_spawn %s : %s", argv[0], strerror(err)); return; }
|
|
|
|
struct buf out = { 0 };
|
|
char tmp[4096];
|
|
time_t start = time(NULL);
|
|
fcntl(pfd[0], F_SETFL, O_NONBLOCK);
|
|
for (;;) {
|
|
fd_set rs;
|
|
struct timeval tv = { 1, 0 };
|
|
FD_ZERO(&rs); FD_SET(pfd[0], &rs);
|
|
if (select(pfd[0] + 1, &rs, NULL, NULL, &tv) > 0) {
|
|
ssize_t n = read(pfd[0], tmp, sizeof tmp);
|
|
if (n > 0) { bput(&out, tmp, n); continue; }
|
|
if (n == 0) break;
|
|
}
|
|
if (time(NULL) - start > 120) { kill(pid, SIGKILL); bprintf(&out, "\n[emuagent: délai dépassé, processus tué]\n"); break; }
|
|
}
|
|
close(pfd[0]);
|
|
int status = 0;
|
|
waitpid(pid, &status, 0);
|
|
char info[32];
|
|
snprintf(info, sizeof info, "%d", WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status));
|
|
reply(fd, 1, info, out.p, out.n);
|
|
free(out.p);
|
|
}
|
|
|
|
static int (*p_SBSLaunchApplication)(CFStringRef, CFURLRef, CFArrayRef, CFDictionaryRef, CFStringRef, CFStringRef, unsigned);
|
|
static CFStringRef (*p_SBSApplicationLaunchingErrorString)(int);
|
|
static CFStringRef (*p_SBSCopyFrontmostApplicationDisplayIdentifier)(void);
|
|
static unsigned (*p_SBSSpringBoardServerPort)(void);
|
|
static int (*p_SBGetScreenLockStatus)(unsigned, unsigned char *, unsigned char *);
|
|
static int (*p_SBDimScreen)(unsigned, unsigned char);
|
|
|
|
static void load_sbs(void)
|
|
{
|
|
static void *h;
|
|
if (h) return;
|
|
h = dlopen("/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices", RTLD_NOW);
|
|
if (!h) return;
|
|
p_SBSSpringBoardServerPort = dlsym(h, "SBSSpringBoardServerPort");
|
|
p_SBGetScreenLockStatus = dlsym(h, "SBGetScreenLockStatus");
|
|
p_SBDimScreen = dlsym(h, "SBDimScreen");
|
|
p_SBSLaunchApplication = dlsym(h, "SBSLaunchApplication");
|
|
p_SBSApplicationLaunchingErrorString = dlsym(h, "SBSApplicationLaunchingErrorString");
|
|
p_SBSCopyFrontmostApplicationDisplayIdentifier = dlsym(h, "SBSCopyFrontmostApplicationDisplayIdentifier");
|
|
}
|
|
|
|
static void cfstr(CFStringRef s, char *out, size_t len)
|
|
{
|
|
out[0] = 0;
|
|
if (s) CFStringGetCString(s, out, len, kCFStringEncodingUTF8);
|
|
}
|
|
|
|
/* API MIG du firmware 2.2.1 : les deux états sont des octets, retour kern_return_t. */
|
|
static void cmd_screen(int fd, int wake)
|
|
{
|
|
load_sbs();
|
|
if (!p_SBSSpringBoardServerPort || !p_SBGetScreenLockStatus || !p_SBDimScreen) {
|
|
reply_str(fd, 0, "API d'écran indisponible"); return;
|
|
}
|
|
unsigned port = p_SBSSpringBoardServerPort();
|
|
unsigned char locked = 0, passcode = 0;
|
|
int rc = p_SBGetScreenLockStatus(port, &locked, &passcode);
|
|
if (rc) { reply_str(fd, 0, "SBGetScreenLockStatus : %d", rc); return; }
|
|
if (wake) {
|
|
/* SBApplicationRequestedDeviceUnlock affiche le clavier de code même
|
|
sans code activé. Réveiller seulement l'écran, puis glisser côté hôte. */
|
|
rc = p_SBDimScreen(port, 0);
|
|
if (rc) { reply_str(fd, 0, "SBDimScreen : %d", rc); return; }
|
|
}
|
|
reply_str(fd, 1, "locked=%u passcode=%u", locked, passcode);
|
|
}
|
|
|
|
static void cmd_launch(int fd, char **args, int nargs)
|
|
{
|
|
load_sbs();
|
|
if (!p_SBSLaunchApplication) { reply_str(fd, 0, "SpringBoardServices indisponible"); return; }
|
|
if (nargs < 1) { reply_str(fd, 0, "usage: launch<TAB>bundle-id[<TAB>fichier stdout/stderr ou -[<TAB>url]]"); return; }
|
|
CFStringRef id = CFStringCreateWithCString(NULL, args[0], kCFStringEncodingUTF8);
|
|
CFStringRef out = nargs > 1 && args[1][0] != '-' ? CFStringCreateWithCString(NULL, args[1], kCFStringEncodingUTF8) : NULL;
|
|
CFURLRef url = NULL;
|
|
if (nargs > 2) {
|
|
CFStringRef us = CFStringCreateWithCString(NULL, args[2], kCFStringEncodingUTF8);
|
|
url = CFURLCreateWithString(NULL, us, NULL);
|
|
CFRelease(us);
|
|
}
|
|
/* SpringBoard refuse les demandes de lancement venant de root (« security policy error ») : l'appel est fait
|
|
par un fils qui passe en mobile (501) */
|
|
int r = -1;
|
|
pid_t child = fork();
|
|
if (child == 0) {
|
|
setgid(501);
|
|
setuid(501);
|
|
_exit(p_SBSLaunchApplication(id, url, NULL, NULL, out, out, 0) & 0xff);
|
|
}
|
|
int status = 0;
|
|
for (int i = 0; child > 0 && i < 300; i++) { /* 30 s max : SpringBoard peut être occupé (relance, cache des apps) */
|
|
if (waitpid(child, &status, WNOHANG) == child) { if (WIFEXITED(status)) r = WEXITSTATUS(status); child = 0; break; }
|
|
usleep(100000);
|
|
}
|
|
if (child > 0) {
|
|
kill(child, SIGKILL);
|
|
waitpid(child, NULL, 0);
|
|
if (url) CFRelease(url);
|
|
CFRelease(id);
|
|
if (out) CFRelease(out);
|
|
reply_str(fd, 0, "SpringBoard ne répond pas (relance ou reconstruction du cache des apps en cours ?)");
|
|
return;
|
|
}
|
|
if (url) CFRelease(url);
|
|
CFRelease(id);
|
|
if (out) CFRelease(out);
|
|
if (r == 0) { reply_str(fd, 1, "lancé"); return; }
|
|
char err[256] = "?";
|
|
if (p_SBSApplicationLaunchingErrorString) cfstr(p_SBSApplicationLaunchingErrorString(r), err, sizeof err);
|
|
reply_str(fd, 0, "SBSLaunchApplication : %d (%s)", r, err);
|
|
}
|
|
|
|
/* fetch<TAB>http://hôte[:port]/chemin : résolution DNS + GET HTTP depuis l'iPod (diagnostic réseau) */
|
|
static void cmd_fetch(int fd, char **args, int nargs)
|
|
{
|
|
char host[256], path[1024] = "/", port[8] = "80";
|
|
if (nargs < 1 || strncmp(args[0], "http://", 7)) { reply_str(fd, 0, "usage: fetch<TAB>http://hôte[:port]/chemin"); return; }
|
|
const char *h = args[0] + 7, *slash = strchr(h, '/');
|
|
size_t hl = slash ? (size_t)(slash - h) : strlen(h);
|
|
if (hl >= sizeof host) hl = sizeof host - 1;
|
|
memcpy(host, h, hl); host[hl] = 0;
|
|
if (slash) strncpy(path, slash, sizeof path - 1);
|
|
char *colon = strchr(host, ':');
|
|
if (colon) { *colon = 0; strncpy(port, colon + 1, sizeof port - 1); }
|
|
|
|
struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }, *res;
|
|
int e = getaddrinfo(host, port, &hints, &res);
|
|
if (e) { reply_str(fd, 0, "getaddrinfo %s : %s", host, gai_strerror(e)); return; }
|
|
char ip[32];
|
|
inet_ntop(AF_INET, &((struct sockaddr_in *)res->ai_addr)->sin_addr, ip, sizeof ip);
|
|
int s = socket(AF_INET, SOCK_STREAM, 0);
|
|
struct timeval to = { 15, 0 };
|
|
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof to);
|
|
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &to, sizeof to);
|
|
if (connect(s, res->ai_addr, res->ai_addrlen)) { reply_str(fd, 0, "connect %s (%s) : %s", host, ip, strerror(errno)); close(s); freeaddrinfo(res); return; }
|
|
freeaddrinfo(res);
|
|
char req[1400];
|
|
snprintf(req, sizeof req, "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: emuagent\r\nConnection: close\r\n\r\n", path, host);
|
|
send_all(s, req, strlen(req));
|
|
struct buf b = { 0 };
|
|
bprintf(&b, "[%s -> %s]\n", host, ip);
|
|
char tmp[4096];
|
|
ssize_t n;
|
|
while (b.n < 256 * 1024 && (n = read(s, tmp, sizeof tmp)) > 0) bput(&b, tmp, n);
|
|
close(s);
|
|
reply(fd, 1, NULL, b.p, b.n);
|
|
free(b.p);
|
|
}
|
|
|
|
/* sc[<TAB>clé] : liste des clés du SCDynamicStore, ou valeur d'une clé (plist XML) */
|
|
static void cmd_sc(int fd, char **args, int nargs)
|
|
{
|
|
if (!store) store = SCDynamicStoreCreate(NULL, CFSTR("emuagent"), NULL, NULL);
|
|
CFStringRef k = CFStringCreateWithCString(NULL, nargs ? args[0] : ".*", kCFStringEncodingUTF8);
|
|
CFPropertyListRef v = nargs && !strchr(args[0], '*') ? SCDynamicStoreCopyValue(store, k) : SCDynamicStoreCopyKeyList(store, k);
|
|
CFRelease(k);
|
|
if (!v) { reply_str(fd, 0, "clé absente"); return; }
|
|
CFDataRef x = CFPropertyListCreateXMLData(NULL, v);
|
|
reply(fd, 1, NULL, CFDataGetBytePtr(x), CFDataGetLength(x));
|
|
CFRelease(x);
|
|
CFRelease(v);
|
|
}
|
|
|
|
/* routes : table de routage IPv4 (sysctl NET_RT_DUMP) */
|
|
static void cmd_routes(int fd)
|
|
{
|
|
int mib[6] = { CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_DUMP, 0 };
|
|
size_t len = 0;
|
|
if (sysctl(mib, 6, NULL, &len, NULL, 0)) { reply_str(fd, 0, "sysctl : %s", strerror(errno)); return; }
|
|
char *b = malloc(len), *p;
|
|
sysctl(mib, 6, b, &len, NULL, 0);
|
|
struct buf out = { 0 };
|
|
for (p = b; p < b + len;) {
|
|
struct rt_msghdr *h = (struct rt_msghdr *)p;
|
|
struct sockaddr *sa = (struct sockaddr *)(h + 1);
|
|
char txt[3][32] = { "-", "-", "-" };
|
|
for (int i = 0; i < 3; i++) {
|
|
if (!(h->rtm_addrs & (1 << i))) continue;
|
|
if (sa->sa_family == AF_INET) inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr, txt[i], 32);
|
|
else if (sa->sa_family == AF_LINK) strcpy(txt[i], "link");
|
|
else if (sa->sa_len == 0) strcpy(txt[i], "0");
|
|
sa = (struct sockaddr *)((char *)sa + (sa->sa_len ? ((sa->sa_len + 3) & ~3) : 4));
|
|
}
|
|
char ifn[IFNAMSIZ] = "?";
|
|
if_indextoname(h->rtm_index, ifn);
|
|
bprintf(&out, "%-16s gw %-16s mask %-16s %s flags 0x%x\n", txt[0], txt[1], txt[2], ifn, h->rtm_flags);
|
|
p += h->rtm_msglen;
|
|
}
|
|
free(b);
|
|
reply(fd, 1, NULL, out.p ? out.p : "", out.n);
|
|
free(out.p);
|
|
}
|
|
|
|
static void cmd_front(int fd)
|
|
{
|
|
load_sbs();
|
|
char s[256] = "";
|
|
if (p_SBSCopyFrontmostApplicationDisplayIdentifier) {
|
|
CFStringRef f = p_SBSCopyFrontmostApplicationDisplayIdentifier();
|
|
cfstr(f, s, sizeof s);
|
|
if (f) CFRelease(f);
|
|
}
|
|
reply_str(fd, 1, "%s", s[0] ? s : "com.apple.springboard");
|
|
}
|
|
|
|
static void list_apps_in(struct buf *b, const char *dir, int depth)
|
|
{
|
|
DIR *d = opendir(dir);
|
|
struct dirent *e;
|
|
if (!d) return;
|
|
while ((e = readdir(d))) {
|
|
char path[1024];
|
|
if (e->d_name[0] == '.') continue;
|
|
snprintf(path, sizeof path, "%s/%s", dir, e->d_name);
|
|
size_t l = strlen(e->d_name);
|
|
if (l > 4 && !strcmp(e->d_name + l - 4, ".app")) {
|
|
CFURLRef u = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8 *)path, strlen(path), true);
|
|
CFBundleRef bundle = CFBundleCreate(NULL, u);
|
|
char id[256] = "", name[256] = "";
|
|
if (bundle) {
|
|
cfstr(CFBundleGetIdentifier(bundle), id, sizeof id);
|
|
CFTypeRef n = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleDisplayName"));
|
|
if (!n) n = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleNameKey);
|
|
if (n && CFGetTypeID(n) == CFStringGetTypeID()) cfstr(n, name, sizeof name);
|
|
CFRelease(bundle);
|
|
}
|
|
CFRelease(u);
|
|
bprintf(b, "%s\t%s\t%s\n", id, name, path);
|
|
} else if (depth > 0) {
|
|
list_apps_in(b, path, depth - 1);
|
|
}
|
|
}
|
|
closedir(d);
|
|
}
|
|
|
|
static void cmd_apps(int fd)
|
|
{
|
|
struct buf b = { 0 };
|
|
list_apps_in(&b, "/Applications", 0);
|
|
list_apps_in(&b, "/private/var/mobile/Applications", 1);
|
|
reply(fd, 1, NULL, b.p, b.n);
|
|
free(b.p);
|
|
}
|
|
|
|
static int mkdirs(const char *path)
|
|
{
|
|
char tmp[1024];
|
|
strncpy(tmp, path, sizeof tmp - 1);
|
|
tmp[sizeof tmp - 1] = 0;
|
|
for (char *p = tmp + 1; *p; p++) {
|
|
if (*p == '/') { *p = 0; mkdir(tmp, 0755); *p = '/'; }
|
|
}
|
|
return mkdir(tmp, 0755) == 0 || errno == EEXIST ? 0 : -1;
|
|
}
|
|
|
|
static int read_full(int fd, void *b, size_t n)
|
|
{
|
|
char *p = b;
|
|
while (n) {
|
|
ssize_t r = read(fd, p, n);
|
|
if (r <= 0) { if (r < 0 && errno == EINTR) continue; if (!r) errno = EIO; return -1; }
|
|
p += r; n -= r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Transferts par blocs : ne pas allouer la taille du fichier sur l'iPod. */
|
|
static void cmd_put(int fd, char **args, int nargs)
|
|
{
|
|
if (nargs < 3) { reply_str(fd, 0, "usage: put<TAB>chemin<TAB>mode octal<TAB>taille + données"); return; }
|
|
char *end;
|
|
errno = 0;
|
|
unsigned long size = strtoul(args[2], &end, 10);
|
|
if (errno || !args[2][0] || args[2][0] == '-' || *end) {
|
|
reply_str(fd, 0, "taille invalide"); return;
|
|
}
|
|
char dir[1024];
|
|
if (strlen(args[0]) >= sizeof dir) { reply_str(fd, 0, "chemin trop long"); return; }
|
|
strcpy(dir, args[0]);
|
|
char *slash = strrchr(dir, '/');
|
|
if (slash && slash != dir) { *slash = 0; mkdirs(dir); }
|
|
char tmp[1100];
|
|
snprintf(tmp, sizeof tmp, "%s.emuagent-XXXXXX", args[0]);
|
|
int f = mkstemp(tmp);
|
|
if (f < 0) { reply_str(fd, 0, "création : %s", strerror(errno)); return; }
|
|
char data[65536];
|
|
size_t remaining = size;
|
|
int failed = 0;
|
|
while (remaining) {
|
|
size_t chunk = remaining < sizeof data ? remaining : sizeof data;
|
|
if (read_full(fd, data, chunk) || send_all(f, data, chunk)) { failed = 1; break; }
|
|
remaining -= chunk;
|
|
}
|
|
if (!failed && (fchmod(f, strtoul(args[1], NULL, 8)) || fsync(f))) failed = 1;
|
|
int saved = errno;
|
|
if (close(f) && !failed) { failed = 1; saved = errno; }
|
|
if (!failed && rename(tmp, args[0])) { failed = 1; saved = errno; }
|
|
if (failed) {
|
|
unlink(tmp);
|
|
reply_str(fd, 0, "écriture de %s : %s", args[0], strerror(saved));
|
|
return;
|
|
}
|
|
reply_str(fd, 1, "%lu octets écrits", size);
|
|
}
|
|
|
|
static void cmd_get(int fd, char **args, int nargs)
|
|
{
|
|
if (nargs < 1) { reply_str(fd, 0, "usage: get<TAB>chemin"); return; }
|
|
int f = open(args[0], O_RDONLY | O_NONBLOCK);
|
|
struct stat st;
|
|
if (f < 0 || fstat(f, &st) || !S_ISREG(st.st_mode)) {
|
|
reply_str(fd, 0, "lecture de %s impossible", args[0]);
|
|
if (f >= 0) close(f);
|
|
return;
|
|
}
|
|
char hdr[128], data[65536];
|
|
snprintf(hdr, sizeof hdr, "OK %llu\n", (unsigned long long)st.st_size);
|
|
if (send_all(fd, hdr, strlen(hdr))) { close(f); return; }
|
|
off_t remaining = st.st_size;
|
|
while (remaining) {
|
|
size_t chunk = remaining < (off_t)sizeof data ? (size_t)remaining : sizeof data;
|
|
/* Après l'en-tête, une erreur impose de fermer la connexion :
|
|
le client détecte la réponse incomplète et conserve son ancien fichier. */
|
|
if (read_full(f, data, chunk) || send_all(fd, data, chunk)) {
|
|
close(f); shutdown(fd, SHUT_RDWR); return;
|
|
}
|
|
remaining -= chunk;
|
|
}
|
|
close(f);
|
|
}
|
|
|
|
static void cmd_ls(int fd, char **args, int nargs)
|
|
{
|
|
const char *path = nargs ? args[0] : "/";
|
|
DIR *d = opendir(path);
|
|
if (!d) { reply_str(fd, 0, "%s : %s", path, strerror(errno)); return; }
|
|
struct buf b = { 0 };
|
|
struct dirent *e;
|
|
while ((e = readdir(d))) {
|
|
char p[1024];
|
|
struct stat st;
|
|
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
|
snprintf(p, sizeof p, "%s/%s", path, e->d_name);
|
|
if (lstat(p, &st)) continue;
|
|
bprintf(&b, "%c%04o %5d %5d %10lld %s\n", S_ISDIR(st.st_mode) ? 'd' : S_ISLNK(st.st_mode) ? 'l' : '-',
|
|
st.st_mode & 07777, st.st_uid, st.st_gid, (long long)st.st_size, e->d_name);
|
|
}
|
|
closedir(d);
|
|
reply(fd, 1, NULL, b.p ? b.p : "", b.n);
|
|
free(b.p);
|
|
}
|
|
|
|
static int rm_rf(const char *path)
|
|
{
|
|
struct stat st;
|
|
if (lstat(path, &st)) return -1;
|
|
if (S_ISDIR(st.st_mode)) {
|
|
DIR *d = opendir(path);
|
|
struct dirent *e;
|
|
while (d && (e = readdir(d))) {
|
|
char p[1024];
|
|
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
|
snprintf(p, sizeof p, "%s/%s", path, e->d_name);
|
|
rm_rf(p);
|
|
}
|
|
if (d) closedir(d);
|
|
return rmdir(path);
|
|
}
|
|
return unlink(path);
|
|
}
|
|
|
|
static void cmd_ps(int fd)
|
|
{
|
|
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
|
|
size_t len = 0;
|
|
sysctl(mib, 3, NULL, &len, NULL, 0);
|
|
struct kinfo_proc *procs = malloc(len);
|
|
if (sysctl(mib, 3, procs, &len, NULL, 0)) { reply_str(fd, 0, "sysctl : %s", strerror(errno)); free(procs); return; }
|
|
struct buf b = { 0 };
|
|
bprintf(&b, " PID PPID UID COMMAND\n");
|
|
for (size_t i = 0; i < len / sizeof *procs; i++) {
|
|
struct kinfo_proc *p = &procs[i];
|
|
bprintf(&b, "%5d %5d %5d %s\n", p->kp_proc.p_pid, p->kp_eproc.e_ppid, p->kp_eproc.e_ucred.cr_uid, p->kp_proc.p_comm);
|
|
}
|
|
reply(fd, 1, NULL, b.p, b.n);
|
|
free(b.p);
|
|
free(procs);
|
|
}
|
|
|
|
static int kill_by_name(const char *name, int sig)
|
|
{
|
|
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }, n = 0;
|
|
size_t len = 0;
|
|
sysctl(mib, 3, NULL, &len, NULL, 0);
|
|
struct kinfo_proc *procs = malloc(len);
|
|
if (!sysctl(mib, 3, procs, &len, NULL, 0)) {
|
|
for (size_t i = 0; i < len / sizeof *procs; i++) {
|
|
if (!strcmp(procs[i].kp_proc.p_comm, name) && !kill(procs[i].kp_proc.p_pid, sig)) n++;
|
|
}
|
|
}
|
|
free(procs);
|
|
return n;
|
|
}
|
|
|
|
static void cmd_kill(int fd, char **args, int nargs)
|
|
{
|
|
if (nargs < 1) { reply_str(fd, 0, "usage: kill<TAB>pid|nom[<TAB>signal]"); return; }
|
|
int sig = nargs > 1 ? atoi(args[1]) : SIGTERM;
|
|
char *end;
|
|
long pid = strtol(args[0], &end, 10);
|
|
if (!*end) {
|
|
if (kill(pid, sig)) reply_str(fd, 0, "kill %ld : %s", pid, strerror(errno));
|
|
else reply_str(fd, 1, "1");
|
|
} else {
|
|
int n = kill_by_name(args[0], sig);
|
|
reply_str(fd, n > 0, "%d", n);
|
|
}
|
|
}
|
|
|
|
static void cmd_net(int fd)
|
|
{
|
|
struct ifaddrs *ifa, *i;
|
|
struct buf b = { 0 };
|
|
if (!getifaddrs(&ifa)) {
|
|
for (i = ifa; i; i = i->ifa_next) {
|
|
if (i->ifa_addr && i->ifa_addr->sa_family == AF_INET) {
|
|
char a[32], dst[32] = "";
|
|
inet_ntop(AF_INET, &((struct sockaddr_in *)i->ifa_addr)->sin_addr, a, sizeof a);
|
|
if (i->ifa_dstaddr && (i->ifa_flags & IFF_POINTOPOINT))
|
|
inet_ntop(AF_INET, &((struct sockaddr_in *)i->ifa_dstaddr)->sin_addr, dst, sizeof dst);
|
|
bprintf(&b, "%s %s%s%s %s\n", i->ifa_name, a, dst[0] ? " -> " : "", dst, (i->ifa_flags & IFF_UP) ? "up" : "down");
|
|
}
|
|
}
|
|
freeifaddrs(ifa);
|
|
}
|
|
bprintf(&b, "pppd: %s, configd: %s\n", pppd_pid ? "actif" : "arrêté", net_published ? "publié" : "non publié");
|
|
reply(fd, 1, NULL, b.p, b.n);
|
|
free(b.p);
|
|
}
|
|
|
|
static void handle(int fd, char *line)
|
|
{
|
|
char *args[64];
|
|
int n = 0;
|
|
for (char *t = strtok(line, "\t"); t && n < 64; t = strtok(NULL, "\t")) args[n++] = t;
|
|
if (!n) return;
|
|
char *cmd = args[0];
|
|
char **a = args + 1;
|
|
int na = n - 1;
|
|
if (!strcmp(cmd, "ping")) reply_str(fd, 1, "emuagent %s", VERSION);
|
|
else if (!strcmp(cmd, "exec")) cmd_exec(fd, a, na);
|
|
else if (!strcmp(cmd, "launch")) cmd_launch(fd, a, na);
|
|
else if (!strcmp(cmd, "front")) cmd_front(fd);
|
|
else if (!strcmp(cmd, "screen")) cmd_screen(fd, 0);
|
|
else if (!strcmp(cmd, "wake")) cmd_screen(fd, 1);
|
|
else if (!strcmp(cmd, "fetch")) cmd_fetch(fd, a, na);
|
|
else if (!strcmp(cmd, "sc")) cmd_sc(fd, a, na);
|
|
else if (!strcmp(cmd, "routes")) cmd_routes(fd);
|
|
else if (!strcmp(cmd, "settime")) {
|
|
/* Réglage explicite de l'heure pour les tests (secondes Unix). */
|
|
struct timeval tv = { na ? strtol(a[0], NULL, 10) : 0, 0 };
|
|
if (na && !settimeofday(&tv, NULL)) {
|
|
notify_post("com.apple.system.clock_set"); /* SpringBoard et les apps mettent leur horloge à jour */
|
|
reply_str(fd, 1, "%ld", (long)tv.tv_sec);
|
|
}
|
|
else reply_str(fd, 0, "settimeofday : %s", strerror(errno));
|
|
}
|
|
else if (!strcmp(cmd, "apps")) cmd_apps(fd);
|
|
else if (!strcmp(cmd, "put")) cmd_put(fd, a, na);
|
|
else if (!strcmp(cmd, "get")) cmd_get(fd, a, na);
|
|
else if (!strcmp(cmd, "ls")) cmd_ls(fd, a, na);
|
|
else if (!strcmp(cmd, "rm")) { if (na && !rm_rf(a[0])) reply_str(fd, 1, "supprimé"); else reply_str(fd, 0, "rm : %s", strerror(errno)); }
|
|
else if (!strcmp(cmd, "mkdir")) { if (na && !mkdirs(a[0])) reply_str(fd, 1, "ok"); else reply_str(fd, 0, "mkdir : %s", strerror(errno)); }
|
|
else if (!strcmp(cmd, "ps")) cmd_ps(fd);
|
|
else if (!strcmp(cmd, "kill")) cmd_kill(fd, a, na);
|
|
else if (!strcmp(cmd, "net")) cmd_net(fd);
|
|
else if (!strcmp(cmd, "respring")) reply_str(fd, kill_by_name("SpringBoard", SIGTERM) > 0, "SpringBoard relancé");
|
|
else if (!strcmp(cmd, "sync")) { sync(); reply_str(fd, 1, "ok"); }
|
|
else reply_str(fd, 0, "commande inconnue : %s (ping exec launch fetch sc routes settime screen wake front apps put get ls rm mkdir ps kill net respring sync)", cmd);
|
|
}
|
|
|
|
static void serve_client(int fd)
|
|
{
|
|
char line[8192];
|
|
size_t len = 0;
|
|
for (;;) {
|
|
char c;
|
|
ssize_t r = read(fd, &c, 1);
|
|
if (r <= 0) break;
|
|
if (c == '\n') {
|
|
line[len] = 0;
|
|
handle(fd, line);
|
|
len = 0;
|
|
} else if (len < sizeof line - 1) {
|
|
line[len++] = c;
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
/* diagnostic : si /private/var/root/emuagent-ttytest existe, lit la ligne série brute pendant 20 s avant pppd */
|
|
static void tty_selftest(void)
|
|
{
|
|
struct stat st;
|
|
if (stat("/private/var/root/emuagent-ttytest", &st)) return;
|
|
int fd = open(PPP_TTY, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
|
clog_("ttytest : open %s -> %d (%s)", PPP_TTY, fd, fd < 0 ? strerror(errno) : "ok");
|
|
if (fd < 0) return;
|
|
struct termios t;
|
|
tcgetattr(fd, &t); cfmakeraw(&t); cfsetspeed(&t, B115200); t.c_cflag |= CLOCAL | CREAD; t.c_cflag &= ~CRTSCTS;
|
|
tcsetattr(fd, TCSANOW, &t);
|
|
write(fd, "EMUAGENT-TTYTEST\n", 17);
|
|
time_t start = time(NULL);
|
|
size_t total = 0;
|
|
while (time(NULL) - start < 20) {
|
|
char b[256];
|
|
ssize_t n = read(fd, b, sizeof b);
|
|
if (n > 0) {
|
|
total += n;
|
|
char hex[64] = "";
|
|
for (int i = 0; i < n && i < 16; i++) sprintf(hex + 3 * i, "%02x ", (unsigned char)b[i]);
|
|
clog_("ttytest : %zd octets : %s", n, hex);
|
|
} else {
|
|
usleep(100000);
|
|
}
|
|
}
|
|
clog_("ttytest : %zu octets reçus au total", total);
|
|
close(fd);
|
|
}
|
|
|
|
/* iPhone OS 2.x does not implement the public idle-sleep assertions.
|
|
* Its root power domain does support SleepDisabled, which is required
|
|
* because this emulated SoC cannot resume from deep sleep. */
|
|
static void prevent_idle_sleep(void)
|
|
{
|
|
void *h = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_NOW);
|
|
CFMutableDictionaryRef (*matching)(const char *) = h ? dlsym(h, "IOServiceMatching") : NULL;
|
|
unsigned (*get_service)(unsigned, CFDictionaryRef) = h ? dlsym(h, "IOServiceGetMatchingService") : NULL;
|
|
int (*set_property)(unsigned, CFStringRef, CFTypeRef) = h ? dlsym(h, "IORegistryEntrySetCFProperty") : NULL;
|
|
int (*release)(unsigned) = h ? dlsym(h, "IOObjectRelease") : NULL;
|
|
if (!matching || !get_service || !set_property || !release) {
|
|
clog_("controle de veille IOKit indisponible");
|
|
return;
|
|
}
|
|
unsigned root = get_service(0, matching("IOPMrootDomain"));
|
|
if (!root) {
|
|
clog_("IOPMrootDomain indisponible");
|
|
return;
|
|
}
|
|
int rc = set_property(root, CFSTR("SleepDisabled"), kCFBooleanTrue);
|
|
release(root);
|
|
clog_("SleepDisabled: 0x%x", rc);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
con = fopen("/dev/console", "w");
|
|
if (con) setvbuf(con, NULL, _IONBF, 0);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
clog_("version %s, port %d", VERSION, PORT);
|
|
prevent_idle_sleep();
|
|
tty_selftest();
|
|
|
|
/* agent relancé (mise à jour, crash) : le pppd de l'instance précédente tient encore la ligne série */
|
|
if (kill_by_name("pppd", SIGTERM) > 0) { clog_("ancien pppd arrêté"); sleep(2); }
|
|
|
|
int s = socket(AF_INET, SOCK_STREAM, 0), one = 1;
|
|
fcntl(s, F_SETFD, FD_CLOEXEC); /* sinon pppd hérite du port d'écoute */
|
|
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
|
|
struct sockaddr_in a = { .sin_family = AF_INET, .sin_port = htons(PORT), .sin_addr.s_addr = htonl(INADDR_ANY) };
|
|
while (bind(s, (struct sockaddr *)&a, sizeof a)) { clog_("bind : %s, nouvel essai", strerror(errno)); sleep(2); }
|
|
listen(s, 4);
|
|
|
|
for (;;) {
|
|
pid_t z;
|
|
int zs;
|
|
while ((z = waitpid(-1, &zs, WNOHANG)) > 0) {
|
|
if (z == pppd_pid) { clog_("pppd terminé (statut %d), relance", zs); pppd_pid = 0; net_published = 0; }
|
|
}
|
|
net_tick();
|
|
fd_set rs;
|
|
struct timeval tv = { 2, 0 };
|
|
FD_ZERO(&rs);
|
|
FD_SET(s, &rs);
|
|
if (select(s + 1, &rs, NULL, NULL, &tv) > 0) {
|
|
int c = accept(s, NULL, NULL);
|
|
if (c >= 0) {
|
|
fcntl(c, F_SETFD, FD_CLOEXEC);
|
|
/* un processus par client : une commande longue ou bloquée ne gêne ni les autres ni le suivi réseau */
|
|
pid_t p = fork();
|
|
if (p == 0) {
|
|
close(s);
|
|
struct timeval to = { 300, 0 };
|
|
setsockopt(c, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof to);
|
|
serve_client(c);
|
|
_exit(0);
|
|
}
|
|
close(c);
|
|
}
|
|
}
|
|
}
|
|
}
|