Files
qemu-ios/contrib/ipod-emu/agent/emuagent.c
T

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);
}
}
}
}